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 progress;
24mod registry;
25pub mod state;
26pub mod update;
27pub mod users;
28mod version;
29pub mod volume;
30
31pub use client::{DockerClient, DockerEndpoint};
33pub use error::DockerError;
34pub use progress::ProgressReporter;
35
36pub use health::{
38 ExtendedHealthResponse, HealthError, HealthResponse, check_health, check_health_extended,
39};
40
41pub use dockerfile::{DOCKERFILE, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
43
44pub use image::{build_image, image_exists, pull_image, remove_images_by_name};
46
47pub use update::{UpdateResult, has_previous_image, rollback_image, update_image};
49
50pub use version::{
52 VERSION_LABEL, get_cli_version, get_image_version, get_registry_latest_version,
53 versions_compatible,
54};
55
56pub use exec::{
58 exec_command, exec_command_exit_code, exec_command_with_status, exec_command_with_stdin,
59};
60
61pub use users::{
63 UserInfo, create_user, delete_user, list_users, lock_user, persist_user, remove_persisted_user,
64 restore_persisted_users, set_user_password, unlock_user, user_exists,
65};
66
67pub use volume::{
69 MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, MOUNT_USERS,
70 VOLUME_CACHE, VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_STATE,
71 VOLUME_USERS, ensure_volumes_exist, remove_all_volumes, remove_volume, volume_exists,
72};
73
74pub async fn docker_supports_systemd(client: &DockerClient) -> Result<bool, DockerError> {
78 let info = client.inner().info().await.map_err(DockerError::from)?;
79
80 let os_type = info.os_type.unwrap_or_default();
81 if os_type.to_lowercase() != "linux" {
82 return Ok(false);
83 }
84
85 let operating_system = match info.operating_system {
86 Some(value) => value,
87 None => return Ok(false),
88 };
89 if operating_system.to_lowercase().contains("docker desktop") {
90 return Ok(false);
91 }
92
93 let security_options = match info.security_options {
94 Some(options) => options,
95 None => return Ok(false),
96 };
97 let is_rootless = security_options
98 .iter()
99 .any(|opt| opt.to_lowercase().contains("name=rootless"));
100 if is_rootless {
101 return Ok(false);
102 }
103
104 Ok(true)
105}
106
107pub use mount::{MountError, ParsedMount, check_container_path_warning, validate_mount_path};
109
110pub use container::{
112 CONTAINER_NAME, ContainerBindMount, ContainerPorts, OPENCODE_WEB_PORT, container_exists,
113 container_is_running, container_state, create_container, get_container_bind_mounts,
114 get_container_ports, remove_container, start_container, stop_container,
115};
116
117pub use state::{ImageState, clear_state, get_state_path, load_state, save_state};
119
120#[allow(clippy::too_many_arguments)]
135pub async fn setup_and_start(
136 client: &DockerClient,
137 opencode_web_port: Option<u16>,
138 env_vars: Option<Vec<String>>,
139 bind_address: Option<&str>,
140 cockpit_port: Option<u16>,
141 cockpit_enabled: Option<bool>,
142 systemd_enabled: Option<bool>,
143 bind_mounts: Option<Vec<mount::ParsedMount>>,
144) -> Result<String, DockerError> {
145 volume::ensure_volumes_exist(client).await?;
147
148 let container_id = if container::container_exists(client, container::CONTAINER_NAME).await? {
150 let info = client
152 .inner()
153 .inspect_container(container::CONTAINER_NAME, None)
154 .await
155 .map_err(|e| {
156 DockerError::Container(format!("Failed to inspect existing container: {e}"))
157 })?;
158 info.id
159 .unwrap_or_else(|| container::CONTAINER_NAME.to_string())
160 } else {
161 container::create_container(
163 client,
164 None,
165 None,
166 opencode_web_port,
167 env_vars,
168 bind_address,
169 cockpit_port,
170 cockpit_enabled,
171 systemd_enabled,
172 bind_mounts,
173 )
174 .await?
175 };
176
177 if !container::container_is_running(client, container::CONTAINER_NAME).await? {
179 container::start_container(client, container::CONTAINER_NAME).await?;
180 }
181
182 users::restore_persisted_users(client, container::CONTAINER_NAME).await?;
184
185 Ok(container_id)
186}
187
188pub const DEFAULT_STOP_TIMEOUT_SECS: i64 = 30;
190
191pub async fn stop_service(
198 client: &DockerClient,
199 remove: bool,
200 timeout_secs: Option<i64>,
201) -> Result<(), DockerError> {
202 let name = container::CONTAINER_NAME;
203 let timeout = timeout_secs.unwrap_or(DEFAULT_STOP_TIMEOUT_SECS);
204
205 if !container::container_exists(client, name).await? {
207 return Err(DockerError::Container(format!(
208 "Container '{name}' does not exist"
209 )));
210 }
211
212 if container::container_is_running(client, name).await? {
214 container::stop_container(client, name, Some(timeout)).await?;
215 }
216
217 if remove {
219 container::remove_container(client, name, false).await?;
220 }
221
222 Ok(())
223}