{ pkgs, config, lib, self, ... }: # Based on: https://git.clan.lol/clan/clan-infra/src/branch/main/modules/web01/gitea/actions-runner.nix with lib; let cfg = config.modules.services.gitea-runner; hostname = config.networking.hostName; giteaUrl = "https://git.vimium.com"; storeDepsBins = with pkgs; [ coreutils findutils gnugrep gawk git nix nix-update bash jq nodejs ]; storeDeps = pkgs.runCommand "store-deps" { } '' mkdir -p $out/bin for dir in ${toString storeDepsBins}; do for bin in "$dir"/bin/*; do ln -s "$bin" "$out/bin/$(basename "$bin")" done done # Add SSL CA certs mkdir -p $out/etc/ssl/certs cp -a "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" $out/etc/ssl/certs/ca-bundle.crt ''; in { options.modules.services.gitea-runner = { enable = mkOption { default = false; example = true; description = mdDoc "Enable a runner for Gitea Actions on this host"; }; }; config = mkIf cfg.enable { modules.podman.enable = true; systemd.services = { gitea-runner-nix-image = { wantedBy = [ "multi-user.target" ]; after = [ "podman.service" ]; requires = [ "podman.service" ]; path = [ config.virtualisation.podman.package pkgs.gnutar pkgs.shadow pkgs.getent ]; script = '' set -eux -o pipefail mkdir -p etc/nix # Create an unpriveleged user that we can use also without the run-as-user.sh script touch etc/passwd etc/group groupid=$(cut -d: -f3 < <(getent group nix-ci-user)) userid=$(cut -d: -f3 < <(getent passwd nix-ci-user)) groupadd --prefix $(pwd) --gid "$groupid" nix-ci-user emptypassword='$6$1ero.LwbisiU.h3D$GGmnmECbPotJoPQ5eoSTD6tTjKnSWZcjHoVTkxFLZP17W9hRi/XkmCiAMOfWruUwy8gMjINrBMNODc7cYEo4K.' useradd --prefix $(pwd) -p "$emptypassword" -m -d /tmp -u "$userid" -g "$groupid" -G nix-ci-user nix-ci-user cat < etc/nix/nix.conf accept-flake-config = true experimental-features = nix-command flakes NIX_CONFIG cat < etc/nsswitch.conf passwd: files mymachines systemd group: files mymachines systemd shadow: files hosts: files mymachines dns myhostname networks: files ethers: files services: files protocols: files rpc: files NSSWITCH # list the content as it will be imported into the container tar -cv . | tar -tvf - tar -cv . | podman import - gitea-runner-nix ''; serviceConfig = { RuntimeDirectory = "gitea-runner-nix-image"; WorkingDirectory = "/run/gitea-runner-nix-image"; Type = "oneshot"; RemainAfterExit = true; }; }; gitea-runner-nix = { after = [ "gitea-runner-nix-image.service" ]; requires = [ "gitea-runner-nix-image.service" ]; serviceConfig = { # Hardening (may overlap with DynamicUser=) # The following options are only for optimizing output of systemd-analyze AmbientCapabilities = ""; CapabilityBoundingSet = ""; # ProtectClock= adds DeviceAllow=char-rtc r DeviceAllow = ""; NoNewPrivileges = true; PrivateDevices = true; PrivateMounts = true; PrivateTmp = true; PrivateUsers = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectSystem = "strict"; RemoveIPC = true; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; UMask = "0066"; ProtectProc = "invisible"; SystemCallFilter = [ "~@clock" "~@cpu-emulation" "~@module" "~@mount" "~@obsolete" "~@raw-io" "~@reboot" "~@swap" # needed by go? #"~@resources" "~@privileged" "~capset" "~setdomainname" "~sethostname" ]; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ]; # Needs network access PrivateNetwork = false; # Cannot be true due to Node MemoryDenyWriteExecute = false; # The more restrictive "pid" option makes `nix` commands in CI emit # "GC Warning: Couldn't read /proc/stat" # You may want to set this to "pid" if not using `nix` commands ProcSubset = "all"; # Coverage programs for compiled code such as `cargo-tarpaulin` disable # ASLR (address space layout randomization) which requires the # `personality` syscall # You may want to set this to `true` if not using coverage tooling on # compiled code LockPersonality = false; # Note that this has some interactions with the User setting; so you may # want to consult the systemd docs if using both. DynamicUser = true; }; }; }; users.users.nix-ci-user = { group = "nix-ci-user"; description = "Used for running nix-based CI jobs"; home = "/var/empty"; isSystemUser = true; }; users.groups.nix-ci-user = { }; age.secrets."files/services/gitea-runner/${hostname}-token" = { file = "${self.inputs.secrets}/files/services/gitea-runner/${hostname}-token.age"; group = "podman"; }; services.gitea-actions-runner.instances = { act = { enable = true; url = giteaUrl; name = "act-runner-${hostname}"; tokenFile = config.age.secrets."files/services/gitea-runner/${hostname}-token".path; settings = { cache.enabled = true; runner.capacity = 4; }; labels = [ "debian-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest" "ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-latest" ]; }; nix = { enable = true; url = giteaUrl; name = "nix-runner-${hostname}"; tokenFile = config.age.secrets."files/services/gitea-runner/${hostname}-token".path; settings = { cache.enabled = true; container = { options = "-e NIX_BUILD_SHELL=/bin/bash -e PAGER=cat -e PATH=/bin -e SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt --device /dev/kvm -v /nix:/nix -v ${storeDeps}/bin:/bin -v ${storeDeps}/etc/ssl:/etc/ssl --user nix-ci-user"; network = "host"; valid_volumes = [ "/nix" "${storeDeps}/bin" "${storeDeps}/etc/ssl" ]; }; runner.capacity = 4; }; labels = [ "nix:docker://gitea-runner-nix" ]; }; }; }; }