mod assets;
mod client;
pub mod container;
mod dockerfile;
mod error;
pub mod exec;
mod health;
pub mod image;
pub mod mount;
pub mod profile;
pub mod progress;
mod registry;
pub mod state;
pub mod update;
pub mod users;
mod version;
pub mod volume;
pub use client::{DockerClient, DockerEndpoint};
pub use error::DockerError;
pub use progress::ProgressReporter;
pub use health::{
ExtendedHealthResponse, HealthError, HealthResponse, check_health, check_health_extended,
};
pub use assets::{ENTRYPOINT_SH, HEALTHCHECK_SH, OPENCODE_CLOUD_BOOTSTRAP_SH};
pub use dockerfile::{DOCKERFILE, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
pub use image::{build_image, image_exists, pull_image, remove_images_by_name};
pub use profile::{
DockerResourceNames, INSTANCE_LABEL_KEY, SANDBOX_INSTANCE_ENV, active_resource_names,
env_instance_id, remap_container_name, remap_image_tag, resource_names_for_instance,
};
pub use update::{UpdateResult, has_previous_image, rollback_image, update_image};
pub use version::{
VERSION_LABEL, get_cli_version, get_image_version, get_registry_latest_version,
versions_compatible,
};
pub use exec::{
exec_command, exec_command_exit_code, exec_command_with_status, exec_command_with_stdin,
};
pub use users::{
UserInfo, create_user, delete_user, list_users, lock_user, persist_user, remove_persisted_user,
restore_persisted_users, set_user_password, unlock_user, user_exists,
};
pub use volume::{
MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_SSH, MOUNT_STATE, MOUNT_USERS,
VOLUME_CACHE, VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_SSH,
VOLUME_STATE, VOLUME_USERS, ensure_volumes_exist, remove_all_volumes, remove_volume,
volume_exists,
};
pub async fn docker_supports_systemd(client: &DockerClient) -> Result<bool, DockerError> {
let info = client.inner().info().await.map_err(DockerError::from)?;
let os_type = info.os_type.unwrap_or_default();
if os_type.to_lowercase() != "linux" {
return Ok(false);
}
let operating_system = match info.operating_system {
Some(value) => value,
None => return Ok(false),
};
if operating_system.to_lowercase().contains("docker desktop") {
return Ok(false);
}
let security_options = match info.security_options {
Some(options) => options,
None => return Ok(false),
};
let is_rootless = security_options
.iter()
.any(|opt| opt.to_lowercase().contains("name=rootless"));
if is_rootless {
return Ok(false);
}
Ok(true)
}
pub use mount::{MountError, ParsedMount, check_container_path_warning, validate_mount_path};
pub use container::{
CONTAINER_NAME, ContainerBindMount, ContainerPorts, OPENCODE_WEB_PORT, container_exists,
container_is_running, container_state, create_container, get_container_bind_mounts,
get_container_ports, remove_container, start_container, stop_container,
};
pub use state::{ImageState, clear_state, get_state_path, load_state, save_state};
#[allow(clippy::too_many_arguments)]
pub async fn setup_and_start(
client: &DockerClient,
opencode_web_port: Option<u16>,
env_vars: Option<Vec<String>>,
bind_address: Option<&str>,
cockpit_port: Option<u16>,
cockpit_enabled: Option<bool>,
systemd_enabled: Option<bool>,
bind_mounts: Option<Vec<mount::ParsedMount>>,
) -> Result<String, DockerError> {
let names = active_resource_names();
volume::ensure_volumes_exist(client).await?;
let container_id = if container::container_exists(client, &names.container_name).await? {
let info = client
.inner()
.inspect_container(&names.container_name, None)
.await
.map_err(|e| {
DockerError::Container(format!("Failed to inspect existing container: {e}"))
})?;
info.id.unwrap_or_else(|| names.container_name.to_string())
} else {
container::create_container(
client,
None,
None,
opencode_web_port,
env_vars,
bind_address,
cockpit_port,
cockpit_enabled,
systemd_enabled,
bind_mounts,
)
.await?
};
if !container::container_is_running(client, &names.container_name).await? {
container::start_container(client, &names.container_name).await?;
}
users::restore_persisted_users(client, &names.container_name).await?;
Ok(container_id)
}
pub const DEFAULT_STOP_TIMEOUT_SECS: i64 = 30;
pub async fn stop_service(
client: &DockerClient,
remove: bool,
timeout_secs: Option<i64>,
) -> Result<(), DockerError> {
let names = active_resource_names();
let name = names.container_name.as_str();
let timeout = timeout_secs.unwrap_or(DEFAULT_STOP_TIMEOUT_SECS);
if !container::container_exists(client, name).await? {
return Err(DockerError::Container(format!(
"Container '{name}' does not exist"
)));
}
if container::container_is_running(client, name).await? {
container::stop_container(client, name, Some(timeout)).await?;
}
if remove {
container::remove_container(client, name, false).await?;
}
Ok(())
}