@opencode-cloud/core
Advanced tools
+1
-1
| [package] | ||
| name = "opencode-cloud-core" | ||
| version = "4.2.1" | ||
| version = "4.2.2" | ||
| edition = "2024" | ||
@@ -5,0 +5,0 @@ rust-version = "1.88" |
+1
-1
| { | ||
| "name": "@opencode-cloud/core", | ||
| "version": "4.2.1", | ||
| "version": "4.2.2", | ||
| "description": "Core NAPI bindings for opencode-cloud (internal package)", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+1
-1
@@ -12,3 +12,3 @@ # opencode-cloud | ||
| > [!WARNING] | ||
| > This project is a work in progress and evolving rapidly. Use with caution. | ||
| > This tool is still a work in progress and is rapidly evolving. Expect frequent updates and breaking changes. Follow updates at https://github.com/pRizz/opencode-cloud. Stability will be announced at some point. Use with caution. | ||
@@ -15,0 +15,0 @@ A production-ready toolkit for deploying and managing [opencode](https://github.com/anomalyco/opencode) as a persistent cloud service, **sandboxed inside a Docker container** for isolation and security. |
+25
-1
@@ -17,2 +17,3 @@ //! Configuration management for opencode-cloud | ||
| use crate::docker::mount::ParsedMount; | ||
| pub use paths::{get_config_dir, get_config_path, get_data_dir, get_hosts_path, get_pid_path}; | ||
@@ -103,3 +104,3 @@ pub use schema::{Config, default_mounts, validate_bind_address}; | ||
| // Deserialize into Config struct (deny_unknown_fields will reject unknown keys) | ||
| let config: Config = serde_json::from_value(parsed_value).with_context(|| { | ||
| let mut config: Config = serde_json::from_value(parsed_value).with_context(|| { | ||
| format!( | ||
@@ -111,2 +112,25 @@ "Invalid configuration in {}. Check for unknown fields or invalid values.", | ||
| let mut removed_shadowing_mounts = false; | ||
| config.mounts.retain(|mount_str| { | ||
| let parsed = match ParsedMount::parse(mount_str) { | ||
| Ok(parsed) => parsed, | ||
| Err(_) => return true, | ||
| }; | ||
| if parsed.container_path == "/opt/opencode" | ||
| || parsed.container_path.starts_with("/opt/opencode/") | ||
| { | ||
| removed_shadowing_mounts = true; | ||
| tracing::warn!( | ||
| "Skipping bind mount that overrides opencode binaries: {}", | ||
| mount_str | ||
| ); | ||
| return false; | ||
| } | ||
| true | ||
| }); | ||
| if removed_shadowing_mounts { | ||
| tracing::info!("Removed bind mounts that shadow /opt/opencode"); | ||
| } | ||
| ensure_default_mount_dirs(&config)?; | ||
@@ -113,0 +137,0 @@ Ok(config) |
+13
-8
@@ -5,4 +5,6 @@ //! Configuration schema for opencode-cloud | ||
| use super::paths::{get_config_dir, get_data_dir}; | ||
| use crate::docker::volume::{MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION}; | ||
| use crate::docker::volume::{ | ||
| MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, | ||
| }; | ||
| use directories::BaseDirs; | ||
| use serde::{Deserialize, Serialize}; | ||
@@ -180,14 +182,17 @@ use std::net::{IpAddr, Ipv4Addr}; | ||
| pub fn default_mounts() -> Vec<String> { | ||
| let maybe_data_dir = get_data_dir(); | ||
| let maybe_config_dir = get_config_dir(); | ||
| let (Some(data_dir), Some(config_dir)) = (maybe_data_dir, maybe_config_dir) else { | ||
| let Some(base_dirs) = BaseDirs::new() else { | ||
| return Vec::new(); | ||
| }; | ||
| let home_dir = base_dirs.home_dir(); | ||
| let session_dir = data_dir.join("data"); | ||
| let data_dir = home_dir.join(".local").join("share").join("opencode"); | ||
| let state_dir = home_dir.join(".local").join("state").join("opencode"); | ||
| let cache_dir = home_dir.join(".cache").join("opencode"); | ||
| let config_dir = home_dir.join(".config").join("opencode"); | ||
| let workspace_dir = data_dir.join("workspace"); | ||
| let config_dir = config_dir.join("container"); | ||
| vec![ | ||
| format!("{}:{MOUNT_SESSION}", session_dir.display()), | ||
| format!("{}:{MOUNT_SESSION}", data_dir.display()), | ||
| format!("{}:{MOUNT_STATE}", state_dir.display()), | ||
| format!("{}:{MOUNT_CACHE}", cache_dir.display()), | ||
| format!("{}:{MOUNT_PROJECTS}", workspace_dir.display()), | ||
@@ -194,0 +199,0 @@ format!("{}:{MOUNT_CONFIG}", config_dir.display()), |
+51
-29
@@ -9,3 +9,4 @@ //! Docker container lifecycle management | ||
| use super::volume::{ | ||
| MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, VOLUME_CONFIG, VOLUME_PROJECTS, VOLUME_SESSION, | ||
| MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, VOLUME_CACHE, | ||
| VOLUME_CONFIG, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_STATE, | ||
| }; | ||
@@ -20,3 +21,3 @@ use super::{DockerClient, DockerError}; | ||
| }; | ||
| use std::collections::HashMap; | ||
| use std::collections::{HashMap, HashSet}; | ||
| use tracing::debug; | ||
@@ -30,2 +31,7 @@ | ||
| fn has_env_key(env: &[String], key: &str) -> bool { | ||
| let prefix = format!("{key}="); | ||
| env.iter().any(|entry| entry.starts_with(&prefix)) | ||
| } | ||
| /// Create the opencode container with volume mounts | ||
@@ -91,26 +97,32 @@ /// | ||
| // Create volume mounts | ||
| let mut mounts = vec![ | ||
| Mount { | ||
| target: Some(MOUNT_SESSION.to_string()), | ||
| source: Some(VOLUME_SESSION.to_string()), | ||
| let mut bind_targets = HashSet::new(); | ||
| if let Some(ref user_mounts) = bind_mounts { | ||
| for parsed in user_mounts { | ||
| bind_targets.insert(parsed.container_path.clone()); | ||
| } | ||
| } | ||
| // Create volume mounts (skip if overridden by bind mounts) | ||
| let mut mounts = Vec::new(); | ||
| let mut add_volume_mount = |target: &str, source: &str| { | ||
| if bind_targets.contains(target) { | ||
| tracing::trace!( | ||
| "Skipping volume mount for {} (overridden by bind mount)", | ||
| target | ||
| ); | ||
| return; | ||
| } | ||
| mounts.push(Mount { | ||
| target: Some(target.to_string()), | ||
| source: Some(source.to_string()), | ||
| typ: Some(MountTypeEnum::VOLUME), | ||
| read_only: Some(false), | ||
| ..Default::default() | ||
| }, | ||
| Mount { | ||
| target: Some(MOUNT_PROJECTS.to_string()), | ||
| source: Some(VOLUME_PROJECTS.to_string()), | ||
| typ: Some(MountTypeEnum::VOLUME), | ||
| read_only: Some(false), | ||
| ..Default::default() | ||
| }, | ||
| Mount { | ||
| target: Some(MOUNT_CONFIG.to_string()), | ||
| source: Some(VOLUME_CONFIG.to_string()), | ||
| typ: Some(MountTypeEnum::VOLUME), | ||
| read_only: Some(false), | ||
| ..Default::default() | ||
| }, | ||
| ]; | ||
| }); | ||
| }; | ||
| add_volume_mount(MOUNT_SESSION, VOLUME_SESSION); | ||
| add_volume_mount(MOUNT_STATE, VOLUME_STATE); | ||
| add_volume_mount(MOUNT_CACHE, VOLUME_CACHE); | ||
| add_volume_mount(MOUNT_PROJECTS, VOLUME_PROJECTS); | ||
| add_volume_mount(MOUNT_CONFIG, VOLUME_CONFIG); | ||
@@ -196,10 +208,20 @@ // Add user-defined bind mounts from config/CLI | ||
| // Build environment variables | ||
| let mut env = env_vars.unwrap_or_default(); | ||
| if !has_env_key(&env, "XDG_DATA_HOME") { | ||
| env.push("XDG_DATA_HOME=/home/opencode/.local/share".to_string()); | ||
| } | ||
| if !has_env_key(&env, "XDG_STATE_HOME") { | ||
| env.push("XDG_STATE_HOME=/home/opencode/.local/state".to_string()); | ||
| } | ||
| if !has_env_key(&env, "XDG_CONFIG_HOME") { | ||
| env.push("XDG_CONFIG_HOME=/home/opencode/.config".to_string()); | ||
| } | ||
| if !has_env_key(&env, "XDG_CACHE_HOME") { | ||
| env.push("XDG_CACHE_HOME=/home/opencode/.cache".to_string()); | ||
| } | ||
| // Add USE_SYSTEMD=1 when Cockpit is enabled to tell entrypoint to use systemd | ||
| let final_env = if cockpit_enabled_val { | ||
| let mut env = env_vars.unwrap_or_default(); | ||
| if cockpit_enabled_val && !has_env_key(&env, "USE_SYSTEMD") { | ||
| env.push("USE_SYSTEMD=1".to_string()); | ||
| Some(env) | ||
| } else { | ||
| env_vars | ||
| }; | ||
| } | ||
| let final_env = if env.is_empty() { None } else { Some(env) }; | ||
@@ -206,0 +228,0 @@ // Create container config |
+11
-11
@@ -167,2 +167,3 @@ # ============================================================================= | ||
| /home/opencode/.local/share \ | ||
| /home/opencode/.local/state \ | ||
| /home/opencode/.cache \ | ||
@@ -533,12 +534,11 @@ /home/opencode/workspace | ||
| && cd /tmp/opencode-repo \ | ||
| && mkdir -p /home/opencode/.local/share/opencode/bin \ | ||
| && mkdir -p /home/opencode/.local/share/opencode/ui \ | ||
| && cp /tmp/opencode-repo/packages/opencode/dist/opencode-*/bin/opencode /home/opencode/.local/share/opencode/bin/opencode \ | ||
| && cp -R /tmp/opencode-repo/packages/opencode/dist/opencode-*/ui/. /home/opencode/.local/share/opencode/ui/ \ | ||
| && chown -R opencode:opencode /home/opencode/.local/share/opencode \ | ||
| && chmod +x /home/opencode/.local/share/opencode/bin/opencode \ | ||
| && /home/opencode/.local/share/opencode/bin/opencode --version | ||
| && sudo mkdir -p /opt/opencode/bin /opt/opencode/ui \ | ||
| && sudo cp /tmp/opencode-repo/packages/opencode/dist/opencode-*/bin/opencode /opt/opencode/bin/opencode \ | ||
| && sudo cp -R /tmp/opencode-repo/packages/opencode/dist/opencode-*/ui/. /opt/opencode/ui/ \ | ||
| && sudo chown -R opencode:opencode /opt/opencode \ | ||
| && sudo chmod +x /opt/opencode/bin/opencode \ | ||
| && /opt/opencode/bin/opencode --version | ||
| # Add opencode to PATH | ||
| ENV PATH="/home/opencode/.local/share/opencode/bin:${PATH}" | ||
| ENV PATH="/opt/opencode/bin:${PATH}" | ||
@@ -656,6 +656,6 @@ # ----------------------------------------------------------------------------- | ||
| 'WorkingDirectory=/home/opencode/workspace' \ | ||
| 'ExecStart=/home/opencode/.local/share/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0' \ | ||
| 'ExecStart=/opt/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0' \ | ||
| 'Restart=always' \ | ||
| 'RestartSec=5' \ | ||
| 'Environment=PATH=/home/opencode/.local/share/opencode/bin:/home/opencode/.local/bin:/home/opencode/.cargo/bin:/home/opencode/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \ | ||
| 'Environment=PATH=/opt/opencode/bin:/home/opencode/.local/bin:/home/opencode/.cargo/bin:/home/opencode/.local/share/mise/shims:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \ | ||
| '' \ | ||
@@ -706,3 +706,3 @@ '[Install]' \ | ||
| ' # Use runuser to switch to opencode user without password prompt' \ | ||
| ' exec runuser -u opencode -- sh -lc "cd /home/opencode/workspace && /home/opencode/.local/share/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0"' \ | ||
| ' exec runuser -u opencode -- sh -lc "cd /home/opencode/workspace && /opt/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0"' \ | ||
| 'fi' \ | ||
@@ -709,0 +709,0 @@ > /usr/local/bin/entrypoint.sh && chmod +x /usr/local/bin/entrypoint.sh |
@@ -63,4 +63,5 @@ //! Docker operations module | ||
| pub use volume::{ | ||
| MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS, | ||
| VOLUME_SESSION, ensure_volumes_exist, remove_all_volumes, remove_volume, volume_exists, | ||
| MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, VOLUME_CACHE, | ||
| VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_STATE, | ||
| ensure_volumes_exist, remove_all_volumes, remove_volume, volume_exists, | ||
| }; | ||
@@ -67,0 +68,0 @@ |
+30
-6
@@ -14,2 +14,8 @@ //! Docker volume management | ||
| /// Volume name for opencode state | ||
| pub const VOLUME_STATE: &str = "opencode-state"; | ||
| /// Volume name for opencode cache | ||
| pub const VOLUME_CACHE: &str = "opencode-cache"; | ||
| /// Volume name for project files | ||
@@ -22,7 +28,19 @@ pub const VOLUME_PROJECTS: &str = "opencode-workspace"; | ||
| /// All volume names as array for iteration | ||
| pub const VOLUME_NAMES: [&str; 3] = [VOLUME_SESSION, VOLUME_PROJECTS, VOLUME_CONFIG]; | ||
| pub const VOLUME_NAMES: [&str; 5] = [ | ||
| VOLUME_SESSION, | ||
| VOLUME_STATE, | ||
| VOLUME_CACHE, | ||
| VOLUME_PROJECTS, | ||
| VOLUME_CONFIG, | ||
| ]; | ||
| /// Mount point for opencode data inside container | ||
| pub const MOUNT_SESSION: &str = "/home/opencode/.local/share"; | ||
| pub const MOUNT_SESSION: &str = "/home/opencode/.local/share/opencode"; | ||
| /// Mount point for opencode state inside container | ||
| pub const MOUNT_STATE: &str = "/home/opencode/.local/state/opencode"; | ||
| /// Mount point for opencode cache inside container | ||
| pub const MOUNT_CACHE: &str = "/home/opencode/.cache/opencode"; | ||
| /// Mount point for project files inside container | ||
@@ -32,3 +50,3 @@ pub const MOUNT_PROJECTS: &str = "/home/opencode/workspace"; | ||
| /// Mount point for configuration inside container | ||
| pub const MOUNT_CONFIG: &str = "/home/opencode/.config"; | ||
| pub const MOUNT_CONFIG: &str = "/home/opencode/.config/opencode"; | ||
@@ -129,2 +147,4 @@ /// Ensure all required volumes exist | ||
| assert_eq!(VOLUME_SESSION, "opencode-data"); | ||
| assert_eq!(VOLUME_STATE, "opencode-state"); | ||
| assert_eq!(VOLUME_CACHE, "opencode-cache"); | ||
| assert_eq!(VOLUME_PROJECTS, "opencode-workspace"); | ||
@@ -136,4 +156,6 @@ assert_eq!(VOLUME_CONFIG, "opencode-config"); | ||
| fn volume_names_array_has_all_volumes() { | ||
| assert_eq!(VOLUME_NAMES.len(), 3); | ||
| assert_eq!(VOLUME_NAMES.len(), 5); | ||
| assert!(VOLUME_NAMES.contains(&VOLUME_SESSION)); | ||
| assert!(VOLUME_NAMES.contains(&VOLUME_STATE)); | ||
| assert!(VOLUME_NAMES.contains(&VOLUME_CACHE)); | ||
| assert!(VOLUME_NAMES.contains(&VOLUME_PROJECTS)); | ||
@@ -145,6 +167,8 @@ assert!(VOLUME_NAMES.contains(&VOLUME_CONFIG)); | ||
| fn mount_points_are_correct() { | ||
| assert_eq!(MOUNT_SESSION, "/home/opencode/.local/share"); | ||
| assert_eq!(MOUNT_SESSION, "/home/opencode/.local/share/opencode"); | ||
| assert_eq!(MOUNT_STATE, "/home/opencode/.local/state/opencode"); | ||
| assert_eq!(MOUNT_CACHE, "/home/opencode/.cache/opencode"); | ||
| assert_eq!(MOUNT_PROJECTS, "/home/opencode/workspace"); | ||
| assert_eq!(MOUNT_CONFIG, "/home/opencode/.config"); | ||
| assert_eq!(MOUNT_CONFIG, "/home/opencode/.config/opencode"); | ||
| } | ||
| } |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
315006
0.92%