Skip to main content

opencode_cloud_core/docker/
mod.rs

1//! Docker operations module
2//!
3//! This module provides Docker container management functionality including:
4//! - Docker client wrapper with connection handling
5//! - Docker-specific error types
6//! - Embedded Dockerfile for building the opencode image
7//! - Progress reporting for build and pull operations
8//! - Image build and pull operations
9//! - Volume management for persistent storage
10//! - Container lifecycle (create, start, stop, remove)
11//! - Container exec for running commands inside containers
12//! - User management operations (create, delete, lock/unlock users)
13//! - Image update and rollback operations
14
15mod client;
16pub mod container;
17mod dockerfile;
18mod error;
19pub mod exec;
20mod health;
21pub mod image;
22pub mod mount;
23pub mod progress;
24pub mod state;
25pub mod update;
26pub mod users;
27mod version;
28pub mod volume;
29
30// Core types
31pub use client::DockerClient;
32pub use error::DockerError;
33pub use progress::ProgressReporter;
34
35// Health check operations
36pub use health::{
37    ExtendedHealthResponse, HealthError, HealthResponse, check_health, check_health_extended,
38};
39
40// Dockerfile constants
41pub use dockerfile::{DOCKERFILE, IMAGE_NAME_DOCKERHUB, IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
42
43// Image operations
44pub use image::{build_image, image_exists, pull_image};
45
46// Update operations
47pub use update::{UpdateResult, has_previous_image, rollback_image, update_image};
48
49// Version detection
50pub use version::{VERSION_LABEL, get_cli_version, get_image_version, versions_compatible};
51
52// Container exec operations
53pub use exec::{exec_command, exec_command_exit_code, exec_command_with_stdin};
54
55// User management operations
56pub use users::{
57    UserInfo, create_user, delete_user, list_users, lock_user, set_user_password, unlock_user,
58    user_exists,
59};
60
61// Volume management
62pub use volume::{
63    MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS,
64    VOLUME_SESSION, ensure_volumes_exist, remove_all_volumes, remove_volume, volume_exists,
65};
66
67// Bind mount parsing and validation
68pub use mount::{MountError, ParsedMount, check_container_path_warning, validate_mount_path};
69
70// Container lifecycle
71pub use container::{
72    CONTAINER_NAME, ContainerBindMount, ContainerPorts, OPENCODE_WEB_PORT, container_exists,
73    container_is_running, container_state, create_container, get_container_bind_mounts,
74    get_container_ports, remove_container, start_container, stop_container,
75};
76
77// Image state tracking
78pub use state::{ImageState, clear_state, get_state_path, load_state, save_state};
79
80/// Full setup: ensure volumes exist, create container if needed, start it
81///
82/// This is the primary entry point for starting the opencode service.
83/// Returns the container ID on success.
84///
85/// # Arguments
86/// * `client` - Docker client
87/// * `opencode_web_port` - Port to bind on host for opencode web UI (defaults to OPENCODE_WEB_PORT)
88/// * `env_vars` - Additional environment variables (optional)
89/// * `bind_address` - IP address to bind on host (defaults to "127.0.0.1")
90/// * `cockpit_port` - Port to bind on host for Cockpit (defaults to 9090)
91/// * `cockpit_enabled` - Whether to enable Cockpit port mapping (defaults to true)
92/// * `bind_mounts` - User-defined bind mounts from config and CLI flags (optional)
93pub async fn setup_and_start(
94    client: &DockerClient,
95    opencode_web_port: Option<u16>,
96    env_vars: Option<Vec<String>>,
97    bind_address: Option<&str>,
98    cockpit_port: Option<u16>,
99    cockpit_enabled: Option<bool>,
100    bind_mounts: Option<Vec<mount::ParsedMount>>,
101) -> Result<String, DockerError> {
102    // Ensure volumes exist first
103    volume::ensure_volumes_exist(client).await?;
104
105    // Check if container already exists
106    let container_id = if container::container_exists(client, container::CONTAINER_NAME).await? {
107        // Get existing container ID
108        let info = client
109            .inner()
110            .inspect_container(container::CONTAINER_NAME, None)
111            .await
112            .map_err(|e| {
113                DockerError::Container(format!("Failed to inspect existing container: {e}"))
114            })?;
115        info.id
116            .unwrap_or_else(|| container::CONTAINER_NAME.to_string())
117    } else {
118        // Create new container
119        container::create_container(
120            client,
121            None,
122            None,
123            opencode_web_port,
124            env_vars,
125            bind_address,
126            cockpit_port,
127            cockpit_enabled,
128            bind_mounts,
129        )
130        .await?
131    };
132
133    // Start if not running
134    if !container::container_is_running(client, container::CONTAINER_NAME).await? {
135        container::start_container(client, container::CONTAINER_NAME).await?;
136    }
137
138    Ok(container_id)
139}
140
141/// Default graceful shutdown timeout in seconds
142pub const DEFAULT_STOP_TIMEOUT_SECS: i64 = 30;
143
144/// Stop and optionally remove the opencode container
145///
146/// # Arguments
147/// * `client` - Docker client
148/// * `remove` - Also remove the container after stopping
149/// * `timeout_secs` - Graceful shutdown timeout (default: 30 seconds)
150pub async fn stop_service(
151    client: &DockerClient,
152    remove: bool,
153    timeout_secs: Option<i64>,
154) -> Result<(), DockerError> {
155    let name = container::CONTAINER_NAME;
156    let timeout = timeout_secs.unwrap_or(DEFAULT_STOP_TIMEOUT_SECS);
157
158    // Check if container exists
159    if !container::container_exists(client, name).await? {
160        return Err(DockerError::Container(format!(
161            "Container '{name}' does not exist"
162        )));
163    }
164
165    // Stop if running
166    if container::container_is_running(client, name).await? {
167        container::stop_container(client, name, Some(timeout)).await?;
168    }
169
170    // Remove if requested
171    if remove {
172        container::remove_container(client, name, false).await?;
173    }
174
175    Ok(())
176}