|

feat(package): confine maintainer scripts via chroot with .SCRIPTS staging

Author: Chris Tusa <chris.tusa@leafscale.com>
Date: Jul 05, 2026 17:20
Changeset: 6a5359a64b22db9220a962b403f058371fed0cf4
Branch: default

Diff

diff -r 4bd00dffc89d -r 6a5359a64b22 src/core/package.reef
--- a/src/core/package.reef	Sun Jul 05 17:14:50 2026 -0500
+++ b/src/core/package.reef	Sun Jul 05 17:20:31 2026 -0500
@@ -546,26 +546,60 @@
 
 // The single place a maintainer-script shell command is assembled.
 // is_sh true  -> run under zsh; false -> execute the file directly.
-// Phase 1: only the non-chroot (host) branch exists.
 fn build_script_cmd(ctx: ScriptCtx, script_path: string, script_name: string, args: string, is_sh: bool): string
-    // $CORAL_ROOT value, from the script's point of view:
-    //   confined -> "/"  (added in Task 8);  host -> ctx.root
-    let coral_root = ctx.root
-
-    mut body = ""
-    if is_sh
-        body = "zsh \"" + script_path + "\""
+    mut cmd = ""
+    if ctx.do_chroot
+        // Confined: run inside <root>; from the script's view the target is "/".
+        let in_root_path = "/.SCRIPTS/" + script_name
+        mut body = ""
+        if is_sh
+            body = "zsh \"" + in_root_path + "\""
+        else
+            body = "\"" + in_root_path + "\""
+        end if
+        cmd = "chroot \"" + ctx.root + "\" env CORAL_ROOT=\"/\" " + body
     else
-        body = "\"" + script_path + "\""
+        // Host: normal install or --no-chroot-scripts. Export CORAL_ROOT=ctx.root.
+        mut body = ""
+        if is_sh
+            body = "zsh \"" + script_path + "\""
+        else
+            body = "\"" + script_path + "\""
+        end if
+        cmd = "CORAL_ROOT=\"" + ctx.root + "\" " + body
     end if
 
-    mut cmd = "CORAL_ROOT=\"" + coral_root + "\" " + body
     if str.length(args) > 0
         cmd = cmd + " " + args
     end if
     return cmd
 end build_script_cmd
 
+// Copy a package's .SCRIPTS dir into <root>/.SCRIPTS so it is reachable post-chroot.
+fn stage_scripts_in_root(pkg_scripts_dir: string, root: string): bool
+    let dest = path.join_path(root, ".SCRIPTS")
+    // Clean any stale staging first.
+    let rm_cmd = "rm -rf \"" + dest + "\""
+    let rm_pid = process.process_spawn_shell(rm_cmd)
+    process.process_wait(rm_pid)
+    // Copy preserving perms/owner/symlinks.
+    let cp_cmd = "cp -a \"" + pkg_scripts_dir + "\" \"" + dest + "\""
+    let pid = process.process_spawn_shell(cp_cmd)
+    if pid < 0
+        return false
+    end if
+    let code = process.process_wait(pid)
+    return code == 0
+end stage_scripts_in_root
+
+// Remove staged scripts from <root>/.SCRIPTS.
+proc unstage_scripts_in_root(root: string)
+    let dest = path.join_path(root, ".SCRIPTS")
+    let cmd = "rm -rf \"" + dest + "\""
+    let pid = process.process_spawn_shell(cmd)
+    process.process_wait(pid)
+end unstage_scripts_in_root
+
 // Run a script from the .SCRIPTS directory
 fn run_script(ctx: ScriptCtx, pkg_dir: string, script_name: string, script_args: string): bool
     let scripts_dir = path.join_path(pkg_dir, ".SCRIPTS")
@@ -575,15 +609,26 @@
         return true
     end if
 
+    if ctx.do_chroot
+        if not stage_scripts_in_root(scripts_dir, ctx.root)
+            return false
+        end if
+    end if
+
     let is_sh = str.ends_with(script_name, ".sh")
     let cmd = build_script_cmd(ctx, script_path, script_name, script_args, is_sh)
 
     let pid = process.process_spawn_shell(cmd)
-    if pid < 0
-        return false
+    mut ok = false
+    if pid >= 0
+        let exit_code = process.process_wait(pid)
+        ok = exit_code == 0
     end if
-    let exit_code = process.process_wait(pid)
-    return exit_code == 0
+
+    if ctx.do_chroot
+        unstage_scripts_in_root(ctx.root)
+    end if
+    return ok
 end run_script
 
 // Run pre-install script (passes $VERSION)
@@ -679,15 +724,26 @@
         return true
     end if
 
+    if ctx.do_chroot
+        if not stage_scripts_in_root(scripts_dir, ctx.root)
+            return false
+        end if
+    end if
+
     let is_sh = str.ends_with(script_name, ".sh")
     let cmd = build_script_cmd(ctx, script_path, script_name, script_args, is_sh)
 
     let pid = process.process_spawn_shell(cmd)
-    if pid < 0
-        return false
+    mut ok = false
+    if pid >= 0
+        let exit_code = process.process_wait(pid)
+        ok = exit_code == 0
     end if
-    let exit_code = process.process_wait(pid)
-    return exit_code == 0
+
+    if ctx.do_chroot
+        unstage_scripts_in_root(ctx.root)
+    end if
+    return ok
 end run_stored_script
 
 // Run pre-remove script from stored scripts directory (passes $VERSION)
diff -r 4bd00dffc89d -r 6a5359a64b22 test/script_cmd_test.reef
--- a/test/script_cmd_test.reef	Sun Jul 05 17:14:50 2026 -0500
+++ b/test/script_cmd_test.reef	Sun Jul 05 17:20:31 2026 -0500
@@ -40,6 +40,16 @@
         package.build_script_cmd(normal, "/tmp/x/.SCRIPTS/pre-remove.sh", "pre-remove.sh", "", true),
         "CORAL_ROOT=\"/\" zsh \"/tmp/x/.SCRIPTS/pre-remove.sh\"")
 
+    // Confined: root "/mnt", do_chroot true -> chroot prefix, in-root /.SCRIPTS path, CORAL_ROOT="/"
+    let jail = package.new_script_ctx("/mnt", true)
+    expect("chroot .sh",
+        package.build_script_cmd(jail, "/tmp/x/.SCRIPTS/post-install.sh", "post-install.sh", "\"1.0\"", true),
+        "chroot \"/mnt\" env CORAL_ROOT=\"/\" zsh \"/.SCRIPTS/post-install.sh\" \"1.0\"")
+
+    expect("chroot bare",
+        package.build_script_cmd(jail, "/tmp/x/.SCRIPTS/post-install", "post-install", "\"1.0\"", false),
+        "chroot \"/mnt\" env CORAL_ROOT=\"/\" \"/.SCRIPTS/post-install\" \"1.0\"")
+
     println("")
     if g_fail == 0
         println("RESULT: ALL PASS")