From b34f8828a27f5ed4db9aa280b9812c260a469928 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Tue, 1 Sep 2026 16:03:17 +0200 Subject: [PATCH] feat(test-guest): allow copy mode overrides Signed-off-by: Evan Lezar --- nix/test-guest/README.md | 20 ++++++++++++-------- nix/test-guest/run.sh | 22 ++++++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/nix/test-guest/README.md b/nix/test-guest/README.md index 0006a331fb..1865f9a82f 100644 --- a/nix/test-guest/README.md +++ b/nix/test-guest/README.md @@ -147,8 +147,9 @@ Configurations are Ansible playbooks stored under `nix/test-guest/configuration/ Configurations run in the order provided on the command line. OpenShell packages and copied files are installed after all configurations succeed. `--install` packages and `--copy` files are applied by a dedicated per-run -Ansible playbook. `--copy` preserves each source file's ordinary permission -bits. They are not stored in prepared VM cache entries. +transfer step. `--copy` preserves each source file's ordinary permission bits +unless an octal mode is supplied. They are not stored in prepared VM cache +entries. ## System provisioners @@ -319,13 +320,14 @@ For an x86_64 Linux guest, supply x86_64 binaries and use `package:deb:amd64`. T ## Copy files directly -Use `--copy SOURCE:DEST` to copy a regular file without creating a package. The -guest file preserves the source's ordinary permission bits: +Use `--copy SOURCE:DEST[:MODE]` to copy a regular file without creating a +package. If `MODE` is omitted, the guest file preserves the source's ordinary +permission bits. Supply a bare octal mode such as `755` to override them: ```shell nix run .#test-guest -- \ --distro ubuntu-24-04 \ - --copy ./openshell:/usr/local/bin/openshell \ + --copy ./openshell:/usr/local/bin/openshell:755 \ -- openshell --version ``` @@ -352,7 +354,8 @@ runner prints their location after shutdown. The final `30` accepts automatic recovery for up to 30 seconds; omit it to require the canary's immediate check. -The destination must be an absolute guest path. Copied files are installed with mode `0755`. +The destination must be an absolute guest path. Use bare octal permission bits +from `000` through `777` for explicit modes. ## Runner options @@ -360,8 +363,9 @@ The destination must be an absolute guest path. Copied files are installed with --distro NAME Base distro: ubuntu-24-04, ubuntu-26-04, centos, fedora, or rocky --with NAME Apply docker, podman-rootless, selinux, or snapd; repeatable --install PATH Install a .deb or .rpm package; repeatable ---copy SRC:DEST Copy a regular file into the guest, preserving its host mode; - repeatable +--copy SRC:DEST[:MODE] + Copy a regular file into the guest; use MODE when provided, + otherwise preserve the host mode; repeatable --ssh-port PORT Use a specific loopback SSH forwarding port --forward-port HOST_PORT:GUEST_PORT Forward a loopback host port to a guest port; repeatable diff --git a/nix/test-guest/run.sh b/nix/test-guest/run.sh index 045f2f441f..282ba2c460 100644 --- a/nix/test-guest/run.sh +++ b/nix/test-guest/run.sh @@ -16,8 +16,9 @@ Options: --with NAME Apply a configuration; repeatable (docker, podman-rootless, selinux, snapd) --provision NAME Apply a post-artifact system provisioner; repeatable --install PATH Install a .deb or .rpm package; repeatable - --copy SRC:DEST Copy a regular file to an absolute guest path, preserving - its host mode; repeatable + --copy SRC:DEST[:MODE] + Copy a regular file to an absolute guest path, using MODE + when provided, otherwise preserving its host mode; repeatable --ssh-port PORT Use a specific loopback SSH forwarding port --forward-port HOST_PORT:GUEST_PORT Forward a loopback host port to a guest port; repeatable @@ -268,13 +269,16 @@ packages=("${resolved_packages[@]}") resolved_copies=() for copy_spec in "${copies[@]}"; do source_path=${copy_spec%%:*} - destination=${copy_spec#*:} if [ "${source_path}" = "${copy_spec}" ] || ! source_path=$(realpath -- "${source_path}") || [ ! -f "${source_path}" ]; then echo "invalid --copy source: ${copy_spec}" >&2 exit 2 fi + copy_remainder=${copy_spec#*:} + destination=${copy_remainder%%:*} + mode_spec=${copy_remainder#"${destination}"} + mode_spec=${mode_spec#:} case "${destination}" in /*) if [[ ${destination} == *"/../"* ]] || [[ ${destination} == */.. ]]; then @@ -291,7 +295,12 @@ for copy_spec in "${copies[@]}"; do exit 2 ;; esac - resolved_copies+=("${source_path}:${destination}") + if [ -n "${mode_spec}" ]; then + mode=${mode_spec} + else + mode=$(preserved_file_mode "${source_path}") || exit 2 + fi + resolved_copies+=("${source_path}:${destination}:${mode}") done copies=("${resolved_copies[@]}") @@ -662,8 +671,9 @@ if [ "${#packages[@]}" -gt 0 ] || [ "${#copies[@]}" -gt 0 ]; then artifact_index=0 for copy_spec in "${copies[@]}"; do source_path=${copy_spec%%:*} - destination=${copy_spec#*:} - mode=$(preserved_file_mode "${source_path}") || exit 2 + copy_remainder=${copy_spec#*:} + destination=${copy_remainder%%:*} + mode=${copy_remainder#*:} remote_path=${artifact_staging_dir}/copy-${artifact_index} echo "==> Copying artifact: ${destination}" scp -q "${scp_args[@]}" \