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