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