From cfc0c52010f4a4913cc8efa1b93e76ec5372d0d1 Mon Sep 17 00:00:00 2001 From: pptx704 Date: Sun, 24 May 2026 22:02:31 +0000 Subject: [PATCH] v2.0.0 updated (#54) Co-authored-by: Tasnim Kabir Sadik Reviewed-on: https://git.omukk.dev/wrenn/wrenn/pulls/54 Co-authored-by: pptx704 Co-committed-by: pptx704 --- .woodpecker/pipeline.yml | 2 +- envd-rs/src/permissions/path.rs | 30 +++++++- envd-rs/src/rpc/process_handler.rs | 19 +++-- envd-rs/src/rpc/process_service.rs | 117 ++++++++++++++++++++++++++++- 4 files changed, 157 insertions(+), 11 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 1484def4..a0f00841 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,5 +1,5 @@ when: - - event: push + - event: [push, manual] branch: main steps: diff --git a/envd-rs/src/permissions/path.rs b/envd-rs/src/permissions/path.rs index cf6a1c2a..5bd25f11 100644 --- a/envd-rs/src/permissions/path.rs +++ b/envd-rs/src/permissions/path.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; use nix::unistd::{Gid, Uid}; -fn expand_tilde(path: &str, home_dir: &str) -> Result { +pub(crate) fn expand_tilde(path: &str, home_dir: &str) -> Result { if path.is_empty() || !path.starts_with('~') { return Ok(path.to_string()); } @@ -112,6 +112,34 @@ mod tests { assert_eq!(expand_tilde("relative/path", "/home/u").unwrap(), "relative/path"); } + #[test] + fn tilde_cmd_like() { + assert_eq!(expand_tilde("~/bin/myapp", "/home/user").unwrap(), "/home/user/bin/myapp"); + } + + #[test] + fn tilde_bare_path_arg() { + assert_eq!(expand_tilde("~", "/home/user").unwrap(), "/home/user"); + } + + #[test] + fn tilde_slash_only() { + assert_eq!(expand_tilde("~/", "/home/u").unwrap(), "/home/u/"); + } + + #[test] + fn tilde_embedded_not_expanded() { + assert_eq!(expand_tilde("/a/~/b", "/home/u").unwrap(), "/a/~/b"); + } + + #[test] + fn tilde_long_home_dir() { + assert_eq!( + expand_tilde("~/code/project", "/very/long/home/directory/path").unwrap(), + "/very/long/home/directory/path/code/project" + ); + } + // expand_and_resolve #[test] diff --git a/envd-rs/src/rpc/process_handler.rs b/envd-rs/src/rpc/process_handler.rs index a548e7e7..68084ed2 100644 --- a/envd-rs/src/rpc/process_handler.rs +++ b/envd-rs/src/rpc/process_handler.rs @@ -177,13 +177,22 @@ pub fn spawn_process( // commands run as a non-root user. Writing 100 to the process's own // oom_score_adj is always permitted (raising the score). let nice_delta = 0 - current_nice(); + let profile_source = r#"test -f /etc/profile && . /etc/profile +test -f "${HOME}/.bashrc" && . "${HOME}/.bashrc""#; let oom_script = if nice_delta > 0 { format!( - r#"echo 100 > /proc/$$/oom_score_adj && exec /usr/bin/nice -n {} "${{@}}""#, - nice_delta + r#"echo 100 > /proc/$$/oom_score_adj +{} +exec /usr/bin/nice -n {} "${{@}}""#, + profile_source, nice_delta, ) } else { - r#"echo 100 > /proc/$$/oom_score_adj && exec "$@""#.to_string() + format!( + r#"echo 100 > /proc/$$/oom_score_adj +{} +exec "$@""#, + profile_source + ) }; let mut wrapper_args = vec![ "-c".to_string(), @@ -222,7 +231,7 @@ pub fn spawn_process( let master_fd = pty_result.master; let slave_fd = pty_result.slave; - let mut command = std::process::Command::new("/bin/sh"); + let mut command = std::process::Command::new("/bin/bash"); command .args(&wrapper_args) .env_clear() @@ -322,7 +331,7 @@ pub fn spawn_process( tracing::info!(pid, cmd = cmd_str, "process started (pty)"); Ok(SpawnedProcess { handle, data_rx, end_rx }) } else { - let mut command = std::process::Command::new("/bin/sh"); + let mut command = std::process::Command::new("/bin/bash"); command .args(&wrapper_args) .env_clear() diff --git a/envd-rs/src/rpc/process_service.rs b/envd-rs/src/rpc/process_service.rs index e3816862..e33be290 100644 --- a/envd-rs/src/rpc/process_service.rs +++ b/envd-rs/src/rpc/process_service.rs @@ -6,7 +6,7 @@ use connectrpc::{ConnectError, Context, ErrorCode}; use dashmap::DashMap; use futures::Stream; -use crate::permissions::path::expand_and_resolve; +use crate::permissions::path::{expand_and_resolve, expand_tilde}; use crate::permissions::user::lookup_user; use crate::rpc::pb::process::*; use crate::rpc::process_handler::{self, DataEvent, ProcessHandle}; @@ -75,8 +75,8 @@ impl ProcessServiceImpl { let user = lookup_user(&username).map_err(|e| ConnectError::new(ErrorCode::Internal, e))?; - let cmd: &str = proc_config.cmd; - let args: Vec = proc_config.args.iter().map(|s| s.to_string()).collect(); + let cmd_raw: &str = proc_config.cmd; + let args_raw: Vec = proc_config.args.iter().map(|s| s.to_string()).collect(); let envs: HashMap = proc_config .envs .iter() @@ -84,6 +84,13 @@ impl ProcessServiceImpl { .collect(); let home_dir = user.dir.to_string_lossy().to_string(); + + let cmd = expand_tilde(cmd_raw, &home_dir) + .map_err(|e| ConnectError::new(ErrorCode::InvalidArgument, e))?; + let args: Vec = args_raw.into_iter() + .map(|a| expand_tilde(&a, &home_dir).unwrap_or(a)) + .collect(); + let cwd_str: &str = proc_config.cwd.unwrap_or(""); let default_workdir = self.state.defaults.workdir(); let cwd = expand_and_resolve(cwd_str, &home_dir, default_workdir.as_deref()) @@ -118,7 +125,7 @@ impl ProcessServiceImpl { ); let spawned = process_handler::spawn_process( - cmd, + &cmd, &args, &envs, effective_cwd, @@ -525,3 +532,105 @@ fn make_end_start_response(end: process_handler::EndEvent) -> StartResponse { ..Default::default() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cmd_expands_tilde_slash() { + let home_dir = "/home/testuser"; + let result = expand_tilde("~/bin/mytool", home_dir).unwrap(); + assert_eq!(result, "/home/testuser/bin/mytool"); + } + + #[test] + fn cmd_expands_bare_tilde() { + let home_dir = "/home/testuser"; + let result = expand_tilde("~", home_dir).unwrap(); + assert_eq!(result, "/home/testuser"); + } + + #[test] + fn cmd_passthrough_absolute() { + let home_dir = "/home/testuser"; + let result = expand_tilde("/usr/bin/env", home_dir).unwrap(); + assert_eq!(result, "/usr/bin/env"); + } + + #[test] + fn cmd_passthrough_relative_no_tilde() { + let home_dir = "/home/testuser"; + let result = expand_tilde("bin/tool", home_dir).unwrap(); + assert_eq!(result, "bin/tool"); + } + + #[test] + fn cmd_errors_on_other_user() { + let home_dir = "/home/testuser"; + assert!(expand_tilde("~other/bin/tool", home_dir).is_err()); + } + + #[test] + fn args_expands_tilde_slash() { + let home_dir = "/home/testuser"; + let result = expand_tilde("~/hi", home_dir).unwrap(); + assert_eq!(result, "/home/testuser/hi"); + } + + #[test] + fn args_expands_bare_tilde() { + let home_dir = "/home/testuser"; + let result = expand_tilde("~", home_dir).unwrap(); + assert_eq!(result, "/home/testuser"); + } + + #[test] + fn args_other_user_left_literal() { + let home_dir = "/home/testuser"; + let args_raw = vec!["~other".to_string(), "~other/path".to_string()]; + let args: Vec = args_raw.into_iter() + .map(|a| expand_tilde(&a, home_dir).unwrap_or(a)) + .collect(); + assert_eq!(args, vec!["~other", "~other/path"]); + } + + #[test] + fn args_passthrough_absolute() { + let home_dir = "/home/testuser"; + let result = expand_tilde("/tmp/file", home_dir).unwrap(); + assert_eq!(result, "/tmp/file"); + } + + #[test] + fn args_passthrough_relative_no_tilde() { + let home_dir = "/home/testuser"; + let result = expand_tilde("relative/path", home_dir).unwrap(); + assert_eq!(result, "relative/path"); + } + + #[test] + fn args_mixed_expands_tilde_keeps_rest() { + let home_dir = "/home/testuser"; + let args_raw = vec![ + "-p".to_string(), + "~/data".to_string(), + "/tmp/out".to_string(), + "~other".to_string(), + ]; + let args: Vec = args_raw.into_iter() + .map(|a| expand_tilde(&a, home_dir).unwrap_or(a)) + .collect(); + assert_eq!(args, vec!["-p", "/home/testuser/data", "/tmp/out", "~other"]); + } + + #[test] + fn args_empty_passthrough() { + let home_dir = "/home/testuser"; + let args_raw: Vec = vec![]; + let args: Vec = args_raw.into_iter() + .map(|a| expand_tilde(&a, home_dir).unwrap_or(a)) + .collect(); + assert!(args.is_empty()); + } +}