fix: fixed the symlink permission issue

This commit is contained in:
robcholz
2026-02-07 17:26:26 -05:00
parent 518ca14fd3
commit 166b035977
5 changed files with 139 additions and 21 deletions

View File

@@ -73,21 +73,7 @@ fn default_auto_shutdown_ms() -> u64 {
}
fn default_mounts() -> Vec<String> {
let home = match std::env::var("HOME") {
Ok(home) => PathBuf::from(home),
Err(_) => return Vec::new(),
};
let mut mounts = Vec::new();
let codex_host = home.join(".codex");
if codex_host.exists() {
mounts.push("~/.codex:/home/vibecoder/.codex:read-write".to_string());
}
let claude_host = home.join(".claude");
if claude_host.exists() {
mounts.push("~/.claude:/home/vibecoder/.claude:read-write".to_string());
}
mounts
Vec::new()
}
pub fn config_path(project_root: &Path) -> PathBuf {

View File

@@ -46,6 +46,16 @@ pub(crate) struct InstanceConfig {
pub(crate) vm_ipv4: Option<String>,
}
impl InstanceConfig {
pub(crate) fn ssh_user_display(&self) -> String {
if self.ssh_user.trim().is_empty() {
DEFAULT_SSH_USER.to_string()
} else {
self.ssh_user.clone()
}
}
}
fn default_ssh_user() -> String {
DEFAULT_SSH_USER.to_string()
}
@@ -361,6 +371,7 @@ pub(crate) fn build_ssh_login_actions(
project_name: &str,
guest_dir: &str,
key_name: &str,
home_links_script: &str,
) -> Vec<LoginAction> {
let config_guard = config.lock().expect("config mutex poisoned");
let ssh_user = config_guard.ssh_user.clone();
@@ -374,7 +385,8 @@ pub(crate) fn build_ssh_login_actions(
.replace("__SUDO_PASSWORD__", &sudo_password)
.replace("__PROJECT_NAME__", project_name)
.replace("__KEY_PATH__", &key_path)
.replace("__VIBEBOX_SHELL_SCRIPT__", &commands::render_shell_script());
.replace("__VIBEBOX_SHELL_SCRIPT__", &commands::render_shell_script())
.replace("__VIBEBOX_HOME_LINKS__", home_links_script);
let setup = vm::script_command_from_content("ssh_setup", &setup_script)
.expect("ssh setup script contained invalid marker");

View File

@@ -68,6 +68,9 @@ if [ -z "$USER_HOME" ]; then
USER_HOME="/home/${SSH_USER}"
fi
# Home mount links (config-driven)
__VIBEBOX_HOME_LINKS__
# Vibebox shell commands
install -d -m 755 /etc/profile.d
cat > /etc/profile.d/vibebox.sh <<'VIBEBOX_SHELL_EOF'

View File

@@ -19,8 +19,8 @@ use crate::{
config::CONFIG_PATH_ENV,
instance::SERIAL_LOG_NAME,
instance::{
InstanceConfig, build_ssh_login_actions, ensure_instance_dir, ensure_ssh_keypair,
extract_ipv4, load_or_create_instance_config, write_instance_config,
DEFAULT_SSH_USER, InstanceConfig, build_ssh_login_actions, ensure_instance_dir,
ensure_ssh_keypair, extract_ipv4, load_or_create_instance_config, write_instance_config,
},
session_manager::{
GLOBAL_DIR_NAME, INSTANCE_FILENAME, VM_MANAGER_PID_NAME, VM_MANAGER_SOCKET_NAME,
@@ -207,6 +207,113 @@ fn is_socket_path(path: &Path) -> bool {
.unwrap_or(false)
}
fn prepare_mounts_and_links(mut args: vm::VmArg, ssh_user: &str) -> (vm::VmArg, String) {
let mut links = Vec::new();
let mut mounts = Vec::with_capacity(args.mounts.len());
for spec in args.mounts {
let (rewritten, link) = rewrite_mount_spec(&spec, ssh_user);
if let Some(link) = link {
links.push(link);
}
mounts.push(rewritten);
}
args.mounts = mounts;
let script = render_home_links_script(&links, ssh_user);
(args, script)
}
struct HomeLink {
source: String,
target: String,
}
fn rewrite_mount_spec(spec: &str, ssh_user: &str) -> (String, Option<HomeLink>) {
let parts: Vec<&str> = spec.split(':').collect();
if parts.len() < 2 || parts.len() > 3 {
return (spec.to_string(), None);
}
let host = parts[0];
let guest = parts[1];
let mode = parts.get(2).copied();
let home_prefix = format!("/home/{ssh_user}");
let (rel, is_home) = if guest == "~" {
(String::new(), true)
} else if let Some(stripped) = guest.strip_prefix("~/") {
(stripped.to_string(), true)
} else if guest == home_prefix {
(String::new(), true)
} else if let Some(stripped) = guest.strip_prefix(&(home_prefix.clone() + "/")) {
(stripped.to_string(), true)
} else {
(String::new(), false)
};
if !is_home {
return (spec.to_string(), None);
}
let root_base = "/usr/local/vibebox-mounts";
let root_path = if rel.is_empty() {
root_base.to_string()
} else {
format!("{root_base}/{rel}")
};
let target = if rel.is_empty() {
home_prefix
} else {
format!("{home_prefix}/{rel}")
};
let rewritten = match mode {
Some(mode) => format!("{host}:{root_path}:{mode}"),
None => format!("{host}:{root_path}"),
};
(
rewritten,
Some(HomeLink {
source: root_path,
target,
}),
)
}
fn render_home_links_script(links: &[HomeLink], ssh_user: &str) -> String {
if links.is_empty() {
return String::new();
}
let mut lines = Vec::new();
lines.push("link_home() {".to_string());
lines.push(" src=\"$1\"".to_string());
lines.push(" dest=\"$2\"".to_string());
lines.push(" if [ -L \"$dest\" ]; then".to_string());
lines.push(" current=\"$(readlink \"$dest\" || true)\"".to_string());
lines.push(" if [ \"$current\" != \"$src\" ]; then".to_string());
lines.push(" rm -f \"$dest\"".to_string());
lines.push(" fi".to_string());
lines.push(" fi".to_string());
lines.push(" if [ ! -e \"$dest\" ]; then".to_string());
lines.push(" mkdir -p \"$(dirname \"$dest\")\"".to_string());
lines.push(" ln -s \"$src\" \"$dest\"".to_string());
lines.push(" fi".to_string());
lines.push(format!(
" chown -h \"{ssh_user}:{ssh_user}\" \"$dest\" 2>/dev/null || true"
));
lines.push("}".to_string());
for link in links {
let src = shell_escape(&link.source);
let dest = shell_escape(&link.target);
lines.push(format!("link_home {src} {dest}"));
}
lines.join("\n")
}
fn shell_escape(value: &str) -> String {
let escaped = value.replace('\'', "'\"'\"'");
format!("'{escaped}'")
}
struct PidFileGuard {
path: PathBuf,
}
@@ -532,6 +639,11 @@ fn run_manager_with(
write_instance_config(&instance_dir.join(INSTANCE_FILENAME), &config)?;
}
let config = Arc::new(Mutex::new(config));
let ssh_user = config
.lock()
.map(|cfg| cfg.ssh_user_display())
.unwrap_or_else(|_| DEFAULT_SSH_USER.to_string());
let (args, home_links_script) = prepare_mounts_and_links(args, &ssh_user);
let ssh_guest_dir = format!("/root/{}", GLOBAL_DIR_NAME);
let extra_shares = vec![DirectoryShare::new(
@@ -539,8 +651,13 @@ fn run_manager_with(
ssh_guest_dir.clone().into(),
true,
)?];
let extra_login_actions =
build_ssh_login_actions(&config, &project_name, ssh_guest_dir.as_str(), "ssh_key");
let extra_login_actions = build_ssh_login_actions(
&config,
&project_name,
ssh_guest_dir.as_str(),
"ssh_key",
&home_links_script,
);
let socket_path = instance_dir.join(VM_MANAGER_SOCKET_NAME);
if let Ok(stream) = UnixStream::connect(&socket_path) {

View File

@@ -7,4 +7,4 @@ mounts = [
]
[supervisor]
auto_shutdown_ms = 20000
auto_shutdown_ms = 2000