{ config, pkgs, lib, ... }: let cfg = config.services.gitea-actions; storeDeps = pkgs.buildEnv { name = "store-deps"; paths = (with pkgs; [ bash cacert coreutils curl findutils gawk git gnugrep jq nix nodejs openssh ]) ++ cfg.storeDependencies; }; in { options = { services.gitea-actions = { enableRunner = lib.mkEnableOption "gitea-actions-runner"; giteaUrl = lib.mkOption { type = lib.types.str; default = config.services.gitea.settings.server.ROOT_URL; }; numInstances = lib.mkOption { type = lib.types.ints.unsigned; default = 2; description = "Number of instances of the gitea-actions-runner service to create"; }; storeDependencies = lib.mkOption { type = lib.types.listOf lib.types.package; default = []; description = "List of packages to symlink into the container"; }; additionalFlakeConfig = lib.mkOption { type = lib.types.str; default = ""; example = "accept-flake-config = true"; description = "Additional configuration to add to the nix.conf file"; }; kvm = lib.mkOption { type = lib.types.bool; default = false; description = "Enable KVM passthrough for the container"; }; zfsDataset = lib.mkOption { type = lib.types.str; default = "zroot/root/podman"; }; }; }; config = lib.mkIf cfg.enableRunner (lib.mkMerge [ { systemd.services.gitea-runner-nix-image = { wantedBy = [ "multi-user.target" ]; after = [ "podman.service" ]; requires = [ "podman.service" ]; script = '' set -eu -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 gitea-actions)) userid=$(cut -d: -f3 < <(getent passwd gitea-actions)) groupadd --prefix $(pwd) --gid "$groupid" gitea-actions emptypassword='$y$j9T$dLJlazrLCVKcOQ/zmu60E1$bAkbdgDaiz7niknOCasvKW3Tjxeca6WA/1fNe4UpeeC' useradd --prefix $(pwd) -p "$emptypassword" -m -d /tmp -u "$userid" -g "$groupid" -G gitea-actions gitea-actions cat < etc/nix/nix.conf experimental-features = nix-command flakes ${cfg.additionalFlakeConfig} 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 ''; path = with pkgs; [ config.virtualisation.podman.package getent gnutar shadow ]; serviceConfig = { RuntimeDirectory = "gitea-runner-nix-image"; WorkingDirectory = "/run/gitea-runner-nix-image"; Type = "oneshot"; RemainAfterExit = true; }; }; users = { groups.gitea-actions = { }; users.gitea-actions = { group = "gitea-actions"; description = "Used for running nix ci jobs"; home = "/run/gitea-runner-nix-image"; isSystemUser = true; }; }; } { virtualisation = { podman.enable = true; containers = { containersConf.settings.containers.dns_servers = config.networking.nameservers; storage.settings.storage.options.zfs.fsname = lib.mkIf config.boot.zfs.enabled "${cfg.zfsDataset}"; }; }; } { systemd.services = lib.genAttrs (builtins.genList (n: "gitea-runner-nix${builtins.toString n}") cfg.numInstances) (name: { after = [ "gitea-runner-nix-image.service" ]; requires = [ "gitea-runner-nix-image.service" ]; serviceConfig = { AmbientCapabilities = ""; CapabilityBoundingSet = ""; 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"; PrivateNetwork = false; MemoryDenyWriteExecute = false; ProcSubset = "all"; LockPersonality = false; DynamicUser = true; SystemCallFilter = [ "~@clock" "~@cpu-emulation" "~@module" "~@mount" "~@obsolete" "~@privileged" "~@raw-io" "~@reboot" "~@swap" "~capset" "~setdomainname" "~sethostname" ]; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" "AF_NETLINK" ]; }; }); services.gitea-actions-runner.instances = lib.genAttrs (builtins.genList (n: "nix${builtins.toString n}") cfg.numInstances) (iname: { enable = true; name = config.networking.hostName; url = cfg.giteaUrl; tokenFile = "/var/lib/gitea-runner/${iname}/token"; labels = [ "nix:docker://gitea-runner-nix" ]; settings.container = { options = "-e NIX_BUILD_SHELL=/bin/bash -e PAGER=cat -e PATH=/bin -e SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt${lib.optionalString cfg.kvm " --device /dev/kvm"} -v /nix:/nix -v ${storeDeps}/bin:/bin -v ${storeDeps}/etc/ssl:/etc/ssl --user gitea-actions"; network = "host"; valid_volumes = [ "/nix" "${storeDeps}/bin" "${storeDeps}/etc/ssl" ]; }; }); } ]); }