[runner@forgejo-runner:~]$ cat /etc/nixos/flake.nix # /etc/nixos/flake.nix on the RUNNER machine { # Description of the flake description = "NixOS Forgejo Runner - Working state with ExecStartPre override and permissions fix"; # Define the inputs for this flake inputs = { # Use nixpkgs from the nixos-unstable channel nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # Declare home-manager input (currently not used in this specific NixOS configuration) home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; # Ensure home-manager uses the same nixpkgs }; }; # Define the outputs of this flake outputs = { self, nixpkgs, ... }@inputs: { # Define a NixOS configuration named "forgejo-runner" nixosConfigurations."forgejo-runner" = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; # Target system architecture specialArgs = { inherit inputs; }; # Make flake inputs available to modules # List of NixOS modules to include in this configuration modules = [ # Import hardware-specific configuration ./hardware-configuration.nix # Inline module for the main system configuration ( { config, pkgs, lib, ... }: let # Configuration for the specific runner instance, accessed from the defined service runnerInstanceCfg = config.services.gitea-actions-runner.instances."my-forgejo-instance"; # Define consistent paths and user/group names actualStateDir = "/var/lib/gitea-actions-runner/my-forgejo-instance"; # Expected state/working directory runnerUser = "gitea-actions-runner"; # Desired user for the runner service runnerGroup = "gitea-actions-runner"; # Desired group for the runner service # Helper to escape characters in labels for CSV-like list (used by original module) escapeLabel = label: builtins.replaceStrings ["\"" "\\"] ["\\\"" "\\\\"] label; # Get labels list from config, ensure it's a list labelsList = runnerInstanceCfg.labels or []; # Concatenate labels into a Nix string like: "\"label1\"","\"label2 with space\"" concatLabels = lib.concatStringsSep "," (map (l: "\"${escapeLabel l}\"") labelsList); # Custom script to handle runner registration, overriding the module's default ExecStartPre correctedRegisterScript = pkgs.writeShellScript "gitea-register-runner-my-forgejo-instance-final" '' set -ex # Exit on error and print commands echo "--- Debugging correctedRegisterScript (v5 - working version) ---" echo "Running as user: $(id)" # Display current user # Define variables for clarity and to use values from Nix config ACT_RUNNER_BIN="${config.services.gitea-actions-runner.package}/bin/act_runner" INSTANCE_URL="${runnerInstanceCfg.url}" TOKEN_FILE="${runnerInstanceCfg.tokenFile}" RUNNER_NAME="${runnerInstanceCfg.name}" # Use single quotes for shell assignment to preserve the exact string from Nix LABELS_CONCAT_FROM_NIX='${concatLabels}' echo "Nix concatLabels raw value: $LABELS_CONCAT_FROM_NIX" # Labels are temporarily disabled in the act_runner call for stability LABELS_ARG="" echo "Labels Argument String: [TEMPORARILY DISABLED FOR STABILITY]" # Check if token file exists and is not empty if [ ! -f "$TOKEN_FILE" ]; then echo "Error: Token file $TOKEN_FILE not found!" exit 1 fi TOKEN_VALUE=$(cat "$TOKEN_FILE") if [ -z "$TOKEN_VALUE" ]; then echo "Error: Token file $TOKEN_FILE is empty!" exit 1 fi echo "Token Value (first 10 chars for logging): $(echo "$TOKEN_VALUE" | cut -c1-10)..." # Check if act_runner binary is executable if [ ! -x "$ACT_RUNNER_BIN" ]; then echo "Error: act_runner binary not found or not executable at $ACT_RUNNER_BIN" exit 1 fi # Create state directory and set permissions if running as root # (script is prefixed with '+' in ExecStartPre, so it runs as root) mkdir -p "${actualStateDir}" if [ "$(id -u)" = "0" ]; then current_owner_group=$(stat -c '%U:%G' "${actualStateDir}") target_owner_group="${runnerUser}:${runnerGroup}" if [ "$current_owner_group" != "$target_owner_group" ]; then chown "$target_owner_group" "${actualStateDir}" || echo "chown on ${actualStateDir} failed, but continuing." fi current_perms=$(stat -c '%a' "${actualStateDir}") if [[ "$current_perms" != "750" && "$current_perms" != "700" ]]; then # Ensure user can rwx chmod 0750 "${actualStateDir}" || echo "chmod on ${actualStateDir} failed, but continuing." fi fi # Change to the state directory for act_runner operations cd "${actualStateDir}" echo "Current directory after cd: $(pwd)" # Remove old .runner and .labels files to ensure fresh registration rm -f .runner rm -f .labels # Register the runner if .runner file doesn't exist if [ ! -f .runner ]; then echo "Attempting registration..." echo "Executing: $ACT_RUNNER_BIN register --instance \"$INSTANCE_URL\" --token \"REDACTED\" --name \"$RUNNER_NAME\" --no-interactive $LABELS_ARG" "$ACT_RUNNER_BIN" register \ --instance "$INSTANCE_URL" \ --token "$TOKEN_VALUE" \ --name "$RUNNER_NAME" \ --no-interactive \ $LABELS_ARG # LABELS_ARG is currently empty fi # Adjust ownership of files created by act_runner (which ran as root) # This is crucial for the main daemon (running as runnerUser) to access them. if [ -f .runner ]; then echo "Adjusting ownership of .runner file to ${runnerUser}:${runnerGroup}" chown ${runnerUser}:${runnerGroup} .runner chmod 640 .runner # rw- for user, r-- for group fi if [ -f .labels ]; then echo "Adjusting ownership of .labels file to ${runnerUser}:${runnerGroup}" chown ${runnerUser}:${runnerGroup} .labels chmod 640 .labels fi echo "--- End Debugging correctedRegisterScript ---" ''; in { # --- Base System Configuration --- boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; networking.hostName = "forgejo-runner"; # System hostname networking.useDHCP = true; # Use DHCP for network configuration time.timeZone = "Europe/Berlin"; # Set system timezone i18n.defaultLocale = "de_DE.UTF-8"; # Set default locale nix.settings.experimental-features = [ "nix-command" "flakes" ]; # Enable Nix Flakes environment.systemPackages = with pkgs; [ vim fail2ban git curl tree ]; # Basic system packages # --- Administrative User 'runner' --- users.users.runner = { isNormalUser = true; description = "Admin for Runner"; group = "users"; extraGroups = [ "wheel" ]; # For sudo access openssh.authorizedKeys.keys = [ # SSH public key for remote login "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO18UxR+2HGer5HIUlgrk01rHmVjUrj7XgGLsG09PiID forgejo-runner" ]; }; # --- System User and Group for the Forgejo Runner Service --- users.users."gitea-actions-runner" = { isSystemUser = true; group = "gitea-actions-runner"; extraGroups = [ "docker" ]; # Allow this user to access Docker socket }; users.groups."gitea-actions-runner" = {}; # Ensure the group exists # --- SSH Server Configuration --- services.openssh = { enable = true; ports = [ 2424 ]; # Use a non-standard port for SSH settings = { PermitRootLogin = "no"; # Disable root login via SSH PubkeyAuthentication = true; # Allow public key authentication PasswordAuthentication = false; # Disable password authentication KbdInteractiveAuthentication = false; # Disable keyboard-interactive authentication }; }; # --- Firewall Configuration --- networking.firewall = { enable = true; allowedTCPPorts = [ 2424 ]; # Allow incoming SSH on the custom port }; # --- Fail2ban Service --- services.fail2ban.enable = true; # Intrusion prevention # --- Docker Service --- virtualisation.docker = { enable = true; daemon.settings = { "fixed-cidr-v6" = "fd00::/80"; "ipv6" = true; }; # Docker daemon settings }; # --- Forgejo Runner Service Definition --- services.gitea-actions-runner = { # Using the gitea-actions-runner module name package = pkgs.forgejo-runner; # Specify the runner package instances."my-forgejo-instance" = { # Define a runner instance enable = true; name = "my-forgejo-runner-01"; # Name displayed in Forgejo UI url = "https://git.kriegbaum.io"; # Your Forgejo instance URL tokenFile = "/etc/forgejo-runner/token"; # Path to the registration token file labels = [ # Labels for this runner "node-22:docker://node:22-bookworm" "nixos-latest:docker://${pkgs.nix}/bin/nix nixos/nix" ]; # User and group for this instance are set via systemd.services overrides below }; }; # --- Systemd Service Overrides for the Runner Instance --- # These overrides are applied to the systemd unit generated by the NixOS module # to fix issues with the default module behavior (e.g., DynamicUser, paths). systemd.services."gitea-runner-my\\x2dforgejo\\x2dinstance" = { # Provide necessary binaries for the correctedRegisterScript in its PATH path = [ pkgs.bash pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.gawk pkgs.findutils ]; serviceConfig = { DynamicUser = lib.mkForce false; # Disable DynamicUser, use a persistent one User = lib.mkForce runnerUser; # Force correct user for the service Group = lib.mkForce runnerGroup; # Force correct group for the service StateDirectory = lib.mkForce "gitea-actions-runner/my-forgejo-instance"; # Tell systemd to manage this state dir WorkingDirectory = lib.mkForce actualStateDir; # Set correct working dir for the daemon Environment = [ # Set HOME environment variable for the service "HOME=${actualStateDir}" ]; # Override ExecStartPre with our corrected script ExecStartPre = lib.mkForce [ "" # Clear any existing ExecStartPre from the module "+${correctedRegisterScript}" # Run our script as root (for initial mkdir/chown of state dir & .runner file) ]; }; }; # --- tmpfiles.d Rules --- # These rules ensure directories exist with correct permissions. # The symlink rules are kept as they were part of the working configuration. systemd.tmpfiles.rules = [ # Ensure the main state directory for the runner exists with correct owner/perms "d ${actualStateDir} 0750 ${runnerUser} ${runnerGroup} -" # Ensure the base for the symlink workaround exists with correct owner/perms "d /var/lib/gitea-runner 0755 ${runnerUser} ${runnerGroup} -" # Create a symlink from the path the original module's script might have used # to the actual state directory. This was part of the working setup. "L+ /var/lib/gitea-runner/my-forgejo-instance - - - - ${actualStateDir}" ]; # --- NixOS State Version --- # Ensures configuration compatibility across NixOS upgrades. system.stateVersion = "23.11"; } ) ]; }; }; }