Skip to content

Commit 32aaa74

Browse files
pRizzcursoragent
andcommitted
feat(docker): move opencode runtime and align XDG mounts
Relocate opencode binaries/UI to /opt to avoid bind-mount shadowing, add state/cache mounts, and align default mount paths plus XDG envs for data persistence. Update status and wizard copy to reflect the new mount layout. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent fb5161a commit 32aaa74

8 files changed

Lines changed: 82 additions & 44 deletions

File tree

packages/cli-rust/src/commands/status.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ use opencode_cloud_core::Config;
1414
use opencode_cloud_core::bollard::service::MountTypeEnum;
1515
use opencode_cloud_core::config;
1616
use opencode_cloud_core::docker::{
17-
CONTAINER_NAME, HealthError, MOUNT_APP_DATA, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION,
18-
OPENCODE_WEB_PORT, ParsedMount, check_health, get_cli_version, get_image_version, load_state,
17+
CONTAINER_NAME, HealthError, MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION,
18+
MOUNT_STATE, OPENCODE_WEB_PORT, ParsedMount, check_health, get_cli_version, get_image_version,
19+
load_state,
1920
};
2021
use opencode_cloud_core::platform::{get_service_manager, is_service_registration_supported};
2122
use std::collections::HashMap;
@@ -531,10 +532,11 @@ fn display_mounts_section(
531532

532533
fn mount_purpose(target: &str) -> Option<&'static str> {
533534
match target {
534-
MOUNT_APP_DATA => Some("sessions + app data"),
535+
MOUNT_SESSION => Some("opencode data"),
536+
MOUNT_STATE => Some("opencode state"),
537+
MOUNT_CACHE => Some("opencode cache"),
535538
MOUNT_PROJECTS => Some("workspace files"),
536539
MOUNT_CONFIG => Some("opencode config"),
537-
MOUNT_SESSION => Some("opencode runtime + binaries"),
538540
_ => None,
539541
}
540542
}

packages/cli-rust/src/wizard/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ fn display_mounts_info(step: usize, total: usize, mounts: &[String]) -> Result<(
121121
return Ok(());
122122
}
123123

124-
println!("Persist opencode sessions, workspace, and config on your host using these mounts:");
124+
println!("Persist opencode data, state, cache, workspace, and config using these mounts:");
125125
println!();
126126
for mount in mounts {
127127
println!(" {}", style(mount).cyan());

packages/core/src/config/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use anyhow::{Context, Result};
1515
use jsonc_parser::parse_to_serde_value;
1616

1717
use crate::docker::mount::ParsedMount;
18-
use crate::docker::volume::MOUNT_SESSION;
1918
pub use paths::{get_config_dir, get_config_path, get_data_dir, get_hosts_path, get_pid_path};
2019
pub use schema::{Config, default_mounts, validate_bind_address};
2120
pub use validation::{
@@ -114,11 +113,12 @@ pub fn load_config() -> Result<Config> {
114113
Ok(parsed) => parsed,
115114
Err(_) => return true,
116115
};
117-
if parsed.container_path == MOUNT_SESSION {
116+
if parsed.container_path == "/opt/opencode"
117+
|| parsed.container_path.starts_with("/opt/opencode/")
118+
{
118119
removed_shadowing_mounts = true;
119120
tracing::warn!(
120-
"Skipping bind mount that overrides opencode binaries; use {} instead: {}",
121-
crate::docker::volume::MOUNT_APP_DATA,
121+
"Skipping bind mount that overrides opencode binaries: {}",
122122
mount_str
123123
);
124124
return false;
@@ -127,7 +127,7 @@ pub fn load_config() -> Result<Config> {
127127
});
128128

129129
if removed_shadowing_mounts {
130-
tracing::info!("Removed bind mounts that shadow {}", MOUNT_SESSION);
130+
tracing::info!("Removed bind mounts that shadow /opt/opencode");
131131
}
132132

133133
ensure_default_mount_dirs(&config)?;

packages/core/src/config/schema.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
//!
33
//! Defines the structure and defaults for the config.json file.
44
5-
use super::paths::{get_config_dir, get_data_dir};
6-
use crate::docker::volume::{MOUNT_APP_DATA, MOUNT_CONFIG, MOUNT_PROJECTS};
5+
use crate::docker::volume::{
6+
MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE,
7+
};
8+
use directories::BaseDirs;
79
use serde::{Deserialize, Serialize};
810
use std::net::{IpAddr, Ipv4Addr};
911
/// Main configuration structure for opencode-cloud
@@ -176,18 +178,21 @@ fn default_update_check() -> String {
176178
}
177179

178180
pub fn default_mounts() -> Vec<String> {
179-
let maybe_data_dir = get_data_dir();
180-
let maybe_config_dir = get_config_dir();
181-
let (Some(data_dir), Some(config_dir)) = (maybe_data_dir, maybe_config_dir) else {
181+
let Some(base_dirs) = BaseDirs::new() else {
182182
return Vec::new();
183183
};
184+
let home_dir = base_dirs.home_dir();
184185

185-
let app_data_dir = data_dir.join("container-data");
186+
let data_dir = home_dir.join(".local").join("share").join("opencode");
187+
let state_dir = home_dir.join(".local").join("state").join("opencode");
188+
let cache_dir = home_dir.join(".cache").join("opencode");
189+
let config_dir = home_dir.join(".config").join("opencode");
186190
let workspace_dir = data_dir.join("workspace");
187-
let config_dir = config_dir.join("container");
188191

189192
vec![
190-
format!("{}:{MOUNT_APP_DATA}", app_data_dir.display()),
193+
format!("{}:{MOUNT_SESSION}", data_dir.display()),
194+
format!("{}:{MOUNT_STATE}", state_dir.display()),
195+
format!("{}:{MOUNT_CACHE}", cache_dir.display()),
191196
format!("{}:{MOUNT_PROJECTS}", workspace_dir.display()),
192197
format!("{}:{MOUNT_CONFIG}", config_dir.display()),
193198
]

packages/core/src/docker/Dockerfile

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ RUN mkdir -p \
164164
/home/opencode/.config \
165165
/home/opencode/.local/bin \
166166
/home/opencode/.local/share \
167+
/home/opencode/.local/state \
167168
/home/opencode/.cache \
168169
/home/opencode/workspace
169170

@@ -529,16 +530,15 @@ RUN OPENCODE_COMMIT="c7fb116c1cf59a76b184f842ed3eb0113d93196b" \
529530
&& bun run build-single-ui \
530531
&& rm -rf /home/opencode/.bun/install/cache /home/opencode/.bun/cache /home/opencode/.cache/bun \
531532
&& cd /tmp/opencode-repo \
532-
&& mkdir -p /home/opencode/.local/share/opencode/bin \
533-
&& mkdir -p /home/opencode/.local/share/opencode/ui \
534-
&& cp /tmp/opencode-repo/packages/opencode/dist/opencode-*/bin/opencode /home/opencode/.local/share/opencode/bin/opencode \
535-
&& cp -R /tmp/opencode-repo/packages/opencode/dist/opencode-*/ui/. /home/opencode/.local/share/opencode/ui/ \
536-
&& chown -R opencode:opencode /home/opencode/.local/share/opencode \
537-
&& chmod +x /home/opencode/.local/share/opencode/bin/opencode \
538-
&& /home/opencode/.local/share/opencode/bin/opencode --version
533+
&& sudo mkdir -p /opt/opencode/bin /opt/opencode/ui \
534+
&& sudo cp /tmp/opencode-repo/packages/opencode/dist/opencode-*/bin/opencode /opt/opencode/bin/opencode \
535+
&& sudo cp -R /tmp/opencode-repo/packages/opencode/dist/opencode-*/ui/. /opt/opencode/ui/ \
536+
&& sudo chown -R opencode:opencode /opt/opencode \
537+
&& sudo chmod +x /opt/opencode/bin/opencode \
538+
&& /opt/opencode/bin/opencode --version
539539
540540
# Add opencode to PATH
541-
ENV PATH="/home/opencode/.local/share/opencode/bin:${PATH}"
541+
ENV PATH="/opt/opencode/bin:${PATH}"
542542
543543
# -----------------------------------------------------------------------------
544544
# opencode-broker Installation
@@ -651,10 +651,10 @@ RUN printf '%s\n' \
651651
'Type=simple' \
652652
'User=opencode' \
653653
'WorkingDirectory=/home/opencode/workspace' \
654-
'ExecStart=/home/opencode/.local/share/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0' \
654+
'ExecStart=/opt/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0' \
655655
'Restart=always' \
656656
'RestartSec=5' \
657-
'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' \
657+
'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' \
658658
'' \
659659
'[Install]' \
660660
'WantedBy=multi-user.target' \
@@ -700,7 +700,7 @@ RUN printf '%s\n' \
700700
' install -d -m 0755 /run/opencode' \
701701
' /usr/local/bin/opencode-broker &' \
702702
' # Use runuser to switch to opencode user without password prompt' \
703-
' 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"' \
703+
' exec runuser -u opencode -- sh -lc "cd /home/opencode/workspace && /opt/opencode/bin/opencode web --port 3000 --hostname 0.0.0.0"' \
704704
'fi' \
705705
> /usr/local/bin/entrypoint.sh && chmod +x /usr/local/bin/entrypoint.sh
706706

packages/core/src/docker/container.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
use super::dockerfile::{IMAGE_NAME_GHCR, IMAGE_TAG_DEFAULT};
77
use super::mount::ParsedMount;
88
use super::volume::{
9-
MOUNT_APP_DATA, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, VOLUME_CONFIG, VOLUME_PROJECTS,
10-
VOLUME_SESSION,
9+
MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, VOLUME_CACHE,
10+
VOLUME_CONFIG, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_STATE,
1111
};
1212
use super::{DockerClient, DockerError};
1313
use bollard::container::{
@@ -117,6 +117,8 @@ pub async fn create_container(
117117
});
118118
};
119119
add_volume_mount(MOUNT_SESSION, VOLUME_SESSION);
120+
add_volume_mount(MOUNT_STATE, VOLUME_STATE);
121+
add_volume_mount(MOUNT_CACHE, VOLUME_CACHE);
120122
add_volume_mount(MOUNT_PROJECTS, VOLUME_PROJECTS);
121123
add_volume_mount(MOUNT_CONFIG, VOLUME_CONFIG);
122124

@@ -201,7 +203,16 @@ pub async fn create_container(
201203
// Build environment variables
202204
let mut env = env_vars.unwrap_or_default();
203205
if !has_env_key(&env, "XDG_DATA_HOME") {
204-
env.push(format!("XDG_DATA_HOME={MOUNT_APP_DATA}"));
206+
env.push("XDG_DATA_HOME=/home/opencode/.local/share".to_string());
207+
}
208+
if !has_env_key(&env, "XDG_STATE_HOME") {
209+
env.push("XDG_STATE_HOME=/home/opencode/.local/state".to_string());
210+
}
211+
if !has_env_key(&env, "XDG_CONFIG_HOME") {
212+
env.push("XDG_CONFIG_HOME=/home/opencode/.config".to_string());
213+
}
214+
if !has_env_key(&env, "XDG_CACHE_HOME") {
215+
env.push("XDG_CACHE_HOME=/home/opencode/.cache".to_string());
205216
}
206217
// Add USE_SYSTEMD=1 when Cockpit is enabled to tell entrypoint to use systemd
207218
if cockpit_enabled_val && !has_env_key(&env, "USE_SYSTEMD") {

packages/core/src/docker/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ pub use users::{
6060

6161
// Volume management
6262
pub use volume::{
63-
MOUNT_APP_DATA, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, VOLUME_CONFIG, VOLUME_NAMES,
64-
VOLUME_PROJECTS, VOLUME_SESSION, ensure_volumes_exist, remove_all_volumes, remove_volume,
65-
volume_exists,
63+
MOUNT_CACHE, MOUNT_CONFIG, MOUNT_PROJECTS, MOUNT_SESSION, MOUNT_STATE, VOLUME_CACHE,
64+
VOLUME_CONFIG, VOLUME_NAMES, VOLUME_PROJECTS, VOLUME_SESSION, VOLUME_STATE,
65+
ensure_volumes_exist, remove_all_volumes, remove_volume, volume_exists,
6666
};
6767

6868
// Bind mount parsing and validation

packages/core/src/docker/volume.rs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,41 @@ use tracing::debug;
1111
/// Volume name for opencode data
1212
pub const VOLUME_SESSION: &str = "opencode-data";
1313

14+
/// Volume name for opencode state
15+
pub const VOLUME_STATE: &str = "opencode-state";
16+
17+
/// Volume name for opencode cache
18+
pub const VOLUME_CACHE: &str = "opencode-cache";
19+
1420
/// Volume name for project files
1521
pub const VOLUME_PROJECTS: &str = "opencode-workspace";
1622

1723
/// Volume name for opencode configuration
1824
pub const VOLUME_CONFIG: &str = "opencode-config";
1925

2026
/// All volume names as array for iteration
21-
pub const VOLUME_NAMES: [&str; 3] = [VOLUME_SESSION, VOLUME_PROJECTS, VOLUME_CONFIG];
27+
pub const VOLUME_NAMES: [&str; 5] = [
28+
VOLUME_SESSION,
29+
VOLUME_STATE,
30+
VOLUME_CACHE,
31+
VOLUME_PROJECTS,
32+
VOLUME_CONFIG,
33+
];
2234

2335
/// Mount point for opencode data inside container
24-
pub const MOUNT_SESSION: &str = "/home/opencode/.local/share";
36+
pub const MOUNT_SESSION: &str = "/home/opencode/.local/share/opencode";
37+
38+
/// Mount point for opencode state inside container
39+
pub const MOUNT_STATE: &str = "/home/opencode/.local/state/opencode";
2540

26-
/// Mount point for opencode app data inside container (XDG_DATA_HOME)
27-
pub const MOUNT_APP_DATA: &str = "/home/opencode/.local/share/opencode-cloud";
41+
/// Mount point for opencode cache inside container
42+
pub const MOUNT_CACHE: &str = "/home/opencode/.cache/opencode";
2843

2944
/// Mount point for project files inside container
3045
pub const MOUNT_PROJECTS: &str = "/home/opencode/workspace";
3146

3247
/// Mount point for configuration inside container
33-
pub const MOUNT_CONFIG: &str = "/home/opencode/.config";
48+
pub const MOUNT_CONFIG: &str = "/home/opencode/.config/opencode";
3449

3550
/// Ensure all required volumes exist
3651
///
@@ -126,23 +141,28 @@ mod tests {
126141
#[test]
127142
fn volume_constants_are_correct() {
128143
assert_eq!(VOLUME_SESSION, "opencode-data");
144+
assert_eq!(VOLUME_STATE, "opencode-state");
145+
assert_eq!(VOLUME_CACHE, "opencode-cache");
129146
assert_eq!(VOLUME_PROJECTS, "opencode-workspace");
130147
assert_eq!(VOLUME_CONFIG, "opencode-config");
131148
}
132149

133150
#[test]
134151
fn volume_names_array_has_all_volumes() {
135-
assert_eq!(VOLUME_NAMES.len(), 3);
152+
assert_eq!(VOLUME_NAMES.len(), 5);
136153
assert!(VOLUME_NAMES.contains(&VOLUME_SESSION));
154+
assert!(VOLUME_NAMES.contains(&VOLUME_STATE));
155+
assert!(VOLUME_NAMES.contains(&VOLUME_CACHE));
137156
assert!(VOLUME_NAMES.contains(&VOLUME_PROJECTS));
138157
assert!(VOLUME_NAMES.contains(&VOLUME_CONFIG));
139158
}
140159

141160
#[test]
142161
fn mount_points_are_correct() {
143-
assert_eq!(MOUNT_SESSION, "/home/opencode/.local/share");
144-
assert_eq!(MOUNT_APP_DATA, "/home/opencode/.local/share/opencode-cloud");
162+
assert_eq!(MOUNT_SESSION, "/home/opencode/.local/share/opencode");
163+
assert_eq!(MOUNT_STATE, "/home/opencode/.local/state/opencode");
164+
assert_eq!(MOUNT_CACHE, "/home/opencode/.cache/opencode");
145165
assert_eq!(MOUNT_PROJECTS, "/home/opencode/workspace");
146-
assert_eq!(MOUNT_CONFIG, "/home/opencode/.config");
166+
assert_eq!(MOUNT_CONFIG, "/home/opencode/.config/opencode");
147167
}
148168
}

0 commit comments

Comments
 (0)