opencode_cloud_core/docker/
mod.rs1mod client;
16pub mod container;
17mod dockerfile;
18mod error;
19pub mod exec;
20mod health;
21pub mod image;
22pub mod mount;
23pub mod profile;
24pub mod progress;
25mod registry;
26pub mod state;
27pub mod update;
28pub mod users;
29mod version;
30pub mod volume;
31
32pub use client::{DockerClient, DockerEndpoint};
34pub use error::DockerError;
35pub use progress::ProgressReporter;
36
37pub use health::{
39 ExtendedHealthResponse, HealthError, HealthResponse, check_health, check_health_extended,
40};
41
42pub use dockerfile::{DOCKERFILE, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
44
45pub use image::{build_image, image_exists, pull_image, remove_images_by_name};
47pub use profile::{
48 DockerResourceNames, INSTANCE_LABEL_KEY, SANDBOX_INSTANCE_ENV, active_resource_names,
49 env_instance_id, remap_container_name, remap_image_tag, resource_names_for_instance,
50};
51
52pub use update::{UpdateResult, has_previous_image, rollback_image, update_image};
54
55pub use version::{
57 VERSION_LABEL, get_cli_version, get_image_version, get_registry_latest_version,
58 versions_compatible,
59};
60
61pub use exec::{
63 exec_command, exec_command_exit_code, exec_command_with_status, exec_command_with_stdin,
64};
65
66pub use users::{
68 UserInfo, create_user, delete_user, list_users, lock_user, persist_user, remove_persisted_user,
69 restore_persisted_users, set_user_password, unlock_user, user_exists,
70};
71
72pub use volume::{
74 MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, MOUNT_USERS,
75 VOLUME_CACHE, VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_STATE,
76 VOLUME_USERS, ensure_volumes_exist, remove_all_volumes, remove_volume, volume_exists,
77};
78
79pub async fn docker_supports_systemd(client: &DockerClient) -> Result<bool, DockerError> {
83 let info = client.inner().info().await.map_err(DockerError::from)?;
84
85 let os_type = info.os_type.unwrap_or_default();
86 if os_type.to_lowercase() != "linux" {
87 return Ok(false);
88 }
89
90 let operating_system = match info.operating_system {
91 Some(value) => value,
92 None => return Ok(false),
93 };
94 if operating_system.to_lowercase().contains("docker desktop") {
95 return Ok(false);
96 }
97
98 let security_options = match info.security_options {
99 Some(options) => options,
100 None => return Ok(false),
101 };
102 let is_rootless = security_options
103 .iter()
104 .any(|opt| opt.to_lowercase().contains("name=rootless"));
105 if is_rootless {
106 return Ok(false);
107 }
108
109 Ok(true)
110}
111
112pub use mount::{MountError, ParsedMount, check_container_path_warning, validate_mount_path};
114
115pub use container::{
117 CONTAINER_NAME, ContainerBindMount, ContainerPorts, OPENCODE_WEB_PORT, container_exists,
118 container_is_running, container_state, create_container, get_container_bind_mounts,
119 get_container_ports, remove_container, start_container, stop_container,
120};
121
122pub use state::{ImageState, clear_state, get_state_path, load_state, save_state};
124
125#[allow(clippy::too_many_arguments)]
140pub async fn setup_and_start(
141 client: &DockerClient,
142 opencode_web_port: Option<u16>,
143 env_vars: Option<Vec<String>>,
144 bind_address: Option<&str>,
145 cockpit_port: Option<u16>,
146 cockpit_enabled: Option<bool>,
147 systemd_enabled: Option<bool>,
148 bind_mounts: Option<Vec<mount::ParsedMount>>,
149) -> Result<String, DockerError> {
150 let names = active_resource_names();
151
152 volume::ensure_volumes_exist(client).await?;
154
155 let container_id = if container::container_exists(client, &names.container_name).await? {
157 let info = client
159 .inner()
160 .inspect_container(&names.container_name, None)
161 .await
162 .map_err(|e| {
163 DockerError::Container(format!("Failed to inspect existing container: {e}"))
164 })?;
165 info.id.unwrap_or_else(|| names.container_name.to_string())
166 } else {
167 container::create_container(
169 client,
170 None,
171 None,
172 opencode_web_port,
173 env_vars,
174 bind_address,
175 cockpit_port,
176 cockpit_enabled,
177 systemd_enabled,
178 bind_mounts,
179 )
180 .await?
181 };
182
183 if !container::container_is_running(client, &names.container_name).await? {
185 container::start_container(client, &names.container_name).await?;
186 }
187
188 users::restore_persisted_users(client, &names.container_name).await?;
190
191 Ok(container_id)
192}
193
194pub const DEFAULT_STOP_TIMEOUT_SECS: i64 = 30;
196
197pub async fn stop_service(
204 client: &DockerClient,
205 remove: bool,
206 timeout_secs: Option<i64>,
207) -> Result<(), DockerError> {
208 let names = active_resource_names();
209 let name = names.container_name.as_str();
210 let timeout = timeout_secs.unwrap_or(DEFAULT_STOP_TIMEOUT_SECS);
211
212 if !container::container_exists(client, name).await? {
214 return Err(DockerError::Container(format!(
215 "Container '{name}' does not exist"
216 )));
217 }
218
219 if container::container_is_running(client, name).await? {
221 container::stop_container(client, name, Some(timeout)).await?;
222 }
223
224 if remove {
226 container::remove_container(client, name, false).await?;
227 }
228
229 Ok(())
230}