diff --git a/nix/host-modules/hive-ci.nix b/nix/host-modules/hive-ci.nix index 7af88be9..b33fb22d 100644 --- a/nix/host-modules/hive-ci.nix +++ b/nix/host-modules/hive-ci.nix @@ -355,6 +355,31 @@ in systemd.services."gitea-runner-hive" = { path = [ pkgs.nix ]; + # A CI runner must be able to EXECUTE what it builds, and by + # default it cannot: the upstream unit uses `DynamicUser`, and + # systemd mounts a dynamic unit's state directory `noexec`. + # Measured on the live runner rather than inferred — from a job's + # own /proc/self/mountinfo: + # + # /var/lib/private ro,nosuid,nodev,noexec (tmpfs) + # /var/lib/private/gitea-runner rw,nosuid,nodev,noexec,idmapped + # + # with /tmp as the control at rw,nosuid,nodev — no noexec, and a + # script there executes fine. Nothing noticed for as long as every + # workflow built inside the nix sandbox and executed nothing in the + # workspace; the first job that compiled a build script into the + # runner's own target dir died with EACCES on execve, reported as + # "could not execute process ... (never executed)". + # + # Both spellings are listed because `ExecPaths=` resolves against + # the HOST root, where the dynamic-user layout makes + # /var/lib/gitea-runner a symlink into private/. The `-` prefix + # means "ignore if absent", so whichever layout the unit ends up + # with, the other is a no-op rather than a startup failure. + serviceConfig.ExecPaths = [ + "-/var/lib/gitea-runner" + "-/var/lib/private/gitea-runner" + ]; # Trust the hive CA in Node-based actions. With self-signed TLS, # forgejo's ROOT_URL is `https://forge.` (CA-signed leaf), # so actions like `upload-artifact` — whose Node HTTP client uses