Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

nix,shuttle: setup cgroups, xdg runtime dir, subord id ranges in alpine image

Signed-off-by: dawn <dawn@tangled.org>

dawn (Jun 27, 2026, 3:03 PM +0300) c7c025ca b8a0864d

+90 -5
+21 -4
nix/pkgs/spindle-alpine-image.nix
··· 40 40 mountpoint -q /sys || mount -t sysfs sys /sys 41 41 mountpoint -q /dev || mount -t devtmpfs dev /dev 42 42 mountpoint -q /dev/pts || { 43 - mkdir -p /dev/pts 43 + install -d /dev/pts 44 44 mount -t devpts devpts /dev/pts 45 45 } 46 46 mountpoint -q /dev/shm || { 47 - mkdir -p /dev/shm 47 + install -d /dev/shm 48 48 mount -t tmpfs -o mode=1777 shm /dev/shm 49 49 } 50 50 mountpoint -q /run || mount -t tmpfs -o mode=0755 run /run 51 51 mountpoint -q /tmp || mount -t tmpfs -o mode=1777 tmp /tmp 52 52 53 + # setup xdg runtime dir, podman eg. needs it 54 + install -d -m 0700 -o spindle-workflow -g spindle-workflow /run/user/970 55 + 56 + # cgroup2 setup, normally we would do this with rc-service 57 + # but minirootfs does not ship with those so we set it up ourselves. 58 + mountpoint -q /sys/fs/cgroup || { 59 + install -d /sys/fs/cgroup 60 + mount -t cgroup2 -o nsdelegate cgroup2 /sys/fs/cgroup 61 + chown -R spindle-workflow:spindle-workflow /sys/fs/cgroup 2>/dev/null || true 62 + } 63 + 53 64 # the initramfs mdev leaves these 0660, which breaks non-root workflows 54 65 chmod 666 /dev/null /dev/zero /dev/full /dev/random /dev/urandom /dev/tty /dev/ptmx 2>/dev/null 55 66 ··· 61 72 # /dev/vda is the squashfs root; the first spindle volume backs /workspace 62 73 if [ -b /dev/vdb ]; then 63 74 mount -t ext4 /dev/vdb /workspace 64 - mkdir -p /workspace/repo 65 - chown spindle-workflow:spindle-workflow /workspace /workspace/repo 75 + install -d -o spindle-workflow -g spindle-workflow /workspace /workspace/repo 66 76 fi 67 77 68 78 ip link set lo up ··· 207 217 echo "spindle-workflow:x:970:" >> rootfs/etc/group 208 218 echo "spindle-workflow:!::0:::::" >> rootfs/etc/shadow 209 219 mkdir -p rootfs/workspace 220 + 221 + # subordinate id ranges so the workflow user can run rootless containers 222 + # (podman/buildah): without these, user-namespace id mapping falls back to a 223 + # single 970->0 map and any layer that chowns to another uid fails. the range 224 + # is well clear of 970 and the 30000-block nixbld users. 225 + echo "spindle-workflow:100000:65536" >> rootfs/etc/subuid 226 + echo "spindle-workflow:100000:65536" >> rootfs/etc/subgid 210 227 211 228 # setup nix build users for the daemon 212 229 members=""
+35 -1
shuttle/src/exec.rs
··· 2 2 use crate::protocol::{self, Message, v1}; 3 3 use nix::unistd::{Group, User}; 4 4 use std::ffi::OsString; 5 + use std::path::PathBuf; 5 6 use std::time::Duration; 6 7 use tokio::sync::mpsc::Sender; 7 - use tracing::info; 8 + use tracing::{info, warn}; 8 9 9 10 const DEFAULT_USER: &str = "spindle-workflow"; 10 11 ··· 40 41 } 41 42 }; 42 43 44 + let mut env = run_as.login_env(); 45 + let runtime_dir = run_as.runtime_dir(); 46 + match runtime_dir.try_exists() { 47 + Ok(true) => env.push(( 48 + OsString::from("XDG_RUNTIME_DIR"), 49 + runtime_dir.into_os_string(), 50 + )), 51 + Ok(false) => {} 52 + Err(err) => warn!(error = %err, "could not stat XDG_RUNTIME_DIR for workflow user"), 53 + } 54 + 43 55 let mut spec = Spec::new(req.argv[0].clone()) 44 56 .args(req.argv[1..].iter().cloned()) 57 + .envs(env) 45 58 .envs(parse_env(&req.env)) 46 59 .run_as(run_as.uid, run_as.gid); 47 60 if !req.cwd.is_empty() { ··· 106 119 name: String, 107 120 uid: u32, 108 121 gid: u32, 122 + home: OsString, 123 + shell: OsString, 124 + } 125 + 126 + impl ResolvedUser { 127 + fn login_env(&self) -> Vec<(OsString, OsString)> { 128 + vec![ 129 + (OsString::from("USER"), OsString::from(&self.name)), 130 + (OsString::from("LOGNAME"), OsString::from(&self.name)), 131 + (OsString::from("HOME"), self.home.clone()), 132 + (OsString::from("SHELL"), self.shell.clone()), 133 + ] 134 + } 135 + 136 + fn runtime_dir(&self) -> PathBuf { 137 + PathBuf::from(format!("/run/user/{}", self.uid)) 138 + } 109 139 } 110 140 111 141 fn resolve_user(spec: &str) -> Result<ResolvedUser, String> { ··· 137 167 name: name.to_owned(), 138 168 uid: user.uid.as_raw(), 139 169 gid: user.gid.as_raw(), 170 + home: user.dir.into_os_string(), 171 + shell: user.shell.into_os_string(), 140 172 }), 141 173 Ok(None) => { 142 174 let uid = name ··· 146 178 name: name.to_owned(), 147 179 uid, 148 180 gid: uid, 181 + home: OsString::from("/"), 182 + shell: OsString::from("/bin/sh"), 149 183 }) 150 184 } 151 185 Err(error) => Err(format!("lookup workflow user {name:?}: {error}")),
+34
spindle/engines/microvm/test-spindle-microvm.sh
··· 814 814 echo "success: alpine guest booted, ran as workflow user, wrote workspace, cloned + installed over the network, and substituted+ran a package from cache.nixos.org over HTTPS" 815 815 } 816 816 817 + test_alpine_podman() { 818 + # install podman via apk and run a real container as the workflow user. 819 + # rootless podman lives entirely in the writable workspace (storage + runroot 820 + # under XDG dirs there), uses podman's default storage driver, and pulls over 821 + # the guest network like the other alpine tests. 822 + local out 823 + out=$(run_vm --spec "$ALPINE_IMAGE_SPEC_JSON" --name "alpine-podman" --timeout "300s" --no-cache -- /bin/sh -lc ' 824 + set -eu 825 + # no env setup here on purpose: shuttle seeds USER/LOGNAME/HOME/SHELL from the 826 + # workflow users passwd entry and provisions XDG_RUNTIME_DIR, so rootless podman 827 + # works out of the box. asserting those below doubles as a check on that. 828 + 829 + # shadow-uidmap ships newuidmap/newgidmap which rootless podman uses to apply the 830 + # /etc/subuid + /etc/subgid ranges baked into the image. 831 + apk add podman shadow-uidmap 832 + echo "user=$(id -un) USER=$USER HOME=$HOME XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR" 833 + echo "newuidmap=$(command -v newuidmap)" 834 + echo "podman_version=$(podman --version)" 835 + 836 + podman info >/dev/null 837 + echo "storage_driver=$(podman info --format "{{.Store.GraphDriverName}}")" 838 + 839 + podman run --rm --network=host docker.io/library/alpine cat /etc/alpine-release | sed "s/^/container_release=/" 840 + podman run --rm --network=host docker.io/library/alpine echo container-ran-ok 841 + ' sh) || return 1 842 + 843 + check_needles "$out" \ 844 + "user=spindle-workflow USER=spindle-workflow HOME=/workspace XDG_RUNTIME_DIR=/run/user/970" \ 845 + "newuidmap=/" "podman_version=" \ 846 + "storage_driver=" "container_release=[0-9]+\." "container-ran-ok" || return 1 847 + echo "success: alpine guest installed podman via apk and pulled + ran a rootless container" 848 + } 849 + 817 850 # asserts a store path's narinfo shows up in the local cache, retrying briefly 818 851 # since the post-build-hook enqueues uploads asynchronously. 819 852 cache_has_path() { ··· 914 947 TESTS=( 915 948 test_alpine 916 949 test_alpine_nix 950 + test_alpine_podman 917 951 test_realize 918 952 test_build_upload 919 953 test_ssh_store_upload