diff --git a/.gitea/workflows/check-flake.yaml b/.gitea/workflows/check-flake.yaml index a073f32..d8bf265 100644 --- a/.gitea/workflows/check-flake.yaml +++ b/.gitea/workflows/check-flake.yaml @@ -16,4 +16,23 @@ jobs: fetch-depth: 0 - name: Check Flake - run: nix flake check --all-systems --print-build-logs --log-format raw --show-trace \ No newline at end of file + run: nix flake check --all-systems --print-build-logs --log-format raw --show-trace + + - name: Push to cache + env: + XDG_CONFIG_HOME: ${{ runner.temp }}/.config + run: | + set -euo pipefail + attic login local "${{ vars.ATTIC_ENDPOINT }}" "${{ secrets.ATTIC_TOKEN }}" + # Get all system toplevel store paths, keeping only those valid in the local store + toplevels=$(nix eval .#nixosConfigurations --apply 'cs: map (n: "${cs.${n}.config.system.build.toplevel}") (builtins.attrNames cs)' --json \ + | jq -r '.[]' \ + | xargs -I{} sh -c 'nix path-info {} >/dev/null 2>&1 && echo {}' || true) + echo "Found $(echo "$toplevels" | wc -l) valid system toplevels" + # Expand to full closures, deduplicate, and filter out paths already + # signed by cache.nixos.org — only our custom builds need caching + paths=$(echo "$toplevels" \ + | xargs nix path-info -r --json \ + | jq -r '[to_entries[] | select(.value.signatures | all(startswith("cache.nixos.org") | not)) | .key] | unique[]') + echo "Pushing $(echo "$paths" | wc -l) unique paths to cache" + echo "$paths" | xargs attic push local:nixos diff --git a/common/binary-cache.nix b/common/binary-cache.nix index dcb0018..06f4dbb 100644 --- a/common/binary-cache.nix +++ b/common/binary-cache.nix @@ -1,4 +1,4 @@ -{ ... }: +{ config, ... }: { nix = { @@ -6,11 +6,11 @@ substituters = [ "https://cache.nixos.org/" "https://nix-community.cachix.org" - "http://s0.koi-bebop.ts.net:5000" + "http://s0.koi-bebop.ts.net:28338/nixos" ]; trusted-public-keys = [ "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" - "s0.koi-bebop.ts.net:OjbzD86YjyJZpCp9RWaQKANaflcpKhtzBMNP8I2aPUU=" + "nixos:SnTTQutdOJbAmxo6AQ3cbRt5w9f4byMXQODCieBH3PQ=" ]; # Allow substituters to be offline @@ -19,6 +19,11 @@ # and use this flag as intended for deciding if it should build missing # derivations locally. See https://github.com/NixOS/nix/issues/6901 fallback = true; + + # Authenticate to private nixos cache + netrc-file = config.age.secrets.attic-netrc.path; }; }; + + age.secrets.attic-netrc.file = ../secrets/attic-netrc.age; } diff --git a/common/server/atticd.nix b/common/server/atticd.nix new file mode 100644 index 0000000..9aefc5a --- /dev/null +++ b/common/server/atticd.nix @@ -0,0 +1,31 @@ +{ config, lib, ... }: + +{ + config = lib.mkIf (config.thisMachine.hasRole."binary-cache") { + services.atticd = { + enable = true; + environmentFile = config.age.secrets.atticd-credentials.path; + settings = { + listen = "[::]:28338"; + + chunking = { + nar-size-threshold = 64 * 1024; # 64 KiB + + # The preferred minimum size of a chunk, in bytes + min-size = 16 * 1024; # 16 KiB + + # The preferred average size of a chunk, in bytes + avg-size = 64 * 1024; # 64 KiB + + # The preferred maximum size of a chunk, in bytes + max-size = 256 * 1024; # 256 KiB + }; + + compression.type = "zstd"; + garbage-collection.default-retention-period = "6 months"; + }; + }; + + age.secrets.atticd-credentials.file = ../../secrets/atticd-credentials.age; + }; +} diff --git a/common/server/default.nix b/common/server/default.nix index c0e0f8a..a7c20cf 100644 --- a/common/server/default.nix +++ b/common/server/default.nix @@ -12,6 +12,7 @@ ./mailserver.nix ./nextcloud.nix ./gitea-actions-runner.nix + ./atticd.nix ./librechat.nix ./actualbudget.nix ./unifi.nix diff --git a/common/server/gitea-actions-runner.nix b/common/server/gitea-actions-runner.nix index fa03f46..aa68cc1 100644 --- a/common/server/gitea-actions-runner.nix +++ b/common/server/gitea-actions-runner.nix @@ -1,132 +1,78 @@ -{ config, pkgs, lib, ... }: +{ config, lib, ... }: -# Gitea Actions Runner. Starts 'host' runner that runs directly on the host inside of a nixos container -# This is useful for providing a real Nix/OS builder to gitea. -# Warning, NixOS containers are not secure. For example, the container shares the /nix/store -# Therefore, this should not be used to run untrusted code. -# To enable, assign a machine the 'gitea-actions-runner' system role - -# TODO: skipping running inside of nixos container for now because of issues getting docker/podman running +# Gitea Actions Runner inside a NixOS container. +# The container shares the host's /nix/store (read-only) and nix-daemon socket, +# so builds go through the host daemon and outputs land in the host store. +# Warning: NixOS containers are not fully secure — do not run untrusted code. +# To enable, assign a machine the 'gitea-actions-runner' system role. let thisMachineIsARunner = config.thisMachine.hasRole."gitea-actions-runner"; containerName = "gitea-runner"; + giteaRunnerUid = 991; + giteaRunnerGid = 989; in { config = lib.mkIf (thisMachineIsARunner && !config.boot.isContainer) { - # containers.${containerName} = { - # ephemeral = true; - # autoStart = true; - # # for podman - # enableTun = true; + containers.${containerName} = { + autoStart = true; + ephemeral = true; - # # privateNetwork = true; - # # hostAddress = "172.16.101.1"; - # # localAddress = "172.16.101.2"; + bindMounts = { + "/run/agenix/gitea-actions-runner-token" = { + hostPath = "/run/agenix/gitea-actions-runner-token"; + isReadOnly = true; + }; + "/var/lib/gitea-runner" = { + hostPath = "/var/lib/gitea-runner"; + isReadOnly = false; + }; + }; - # bindMounts = - # { - # "/run/agenix/gitea-actions-runner-token" = { - # hostPath = "/run/agenix/gitea-actions-runner-token"; - # isReadOnly = true; - # }; - # "/var/lib/gitea-runner" = { - # hostPath = "/var/lib/gitea-runner"; - # isReadOnly = false; - # }; - # }; + config = { config, lib, pkgs, ... }: { + system.stateVersion = "25.11"; - # extraFlags = [ - # # Allow podman - # ''--system-call-filter=thisystemcalldoesnotexistforsure'' - # ]; + services.gitea-actions-runner.instances.inst = { + enable = true; + name = containerName; + url = "https://git.neet.dev/"; + tokenFile = "/run/agenix/gitea-actions-runner-token"; + labels = [ "nixos:host" ]; + }; - # additionalCapabilities = [ - # "CAP_SYS_ADMIN" - # ]; + # Disable dynamic user so runner state persists via bind mount + systemd.services.gitea-runner-inst.serviceConfig.DynamicUser = lib.mkForce false; + users.users.gitea-runner = { + uid = giteaRunnerUid; + home = "/var/lib/gitea-runner"; + group = "gitea-runner"; + isSystemUser = true; + createHome = true; + }; + users.groups.gitea-runner.gid = giteaRunnerGid; - # config = { - # imports = allModules; + nix.settings.experimental-features = [ "nix-command" "flakes" ]; - # # speeds up evaluation - # nixpkgs.pkgs = pkgs; - - # networking.hostName = lib.mkForce containerName; - - # # don't use remote builders - # nix.distributedBuilds = lib.mkForce false; - - # environment.systemPackages = with pkgs; [ - # git - # # Gitea Actions rely heavily on node. Include it because it would be installed anyway. - # nodejs - # ]; - - # services.gitea-actions-runner.instances.inst = { - # enable = true; - # name = config.networking.hostName; - # url = "https://git.neet.dev/"; - # tokenFile = "/run/agenix/gitea-actions-runner-token"; - # labels = [ - # "ubuntu-latest:docker://node:18-bullseye" - # "nixos:host" - # ]; - # }; - - # # To allow building on the host, must override the the service's config so it doesn't use a dynamic user - # systemd.services.gitea-runner-inst.serviceConfig.DynamicUser = lib.mkForce false; - # users.users.gitea-runner = { - # home = "/var/lib/gitea-runner"; - # group = "gitea-runner"; - # isSystemUser = true; - # createHome = true; - # }; - # users.groups.gitea-runner = { }; - - # virtualisation.podman.enable = true; - # boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; - # }; - # }; - - # networking.nat.enable = true; - # networking.nat.internalInterfaces = [ - # "ve-${containerName}" - # ]; - # networking.ip_forward = true; - - # don't use remote builders - nix.distributedBuilds = lib.mkForce false; - - services.gitea-actions-runner.instances.inst = { - enable = true; - name = config.networking.hostName; - url = "https://git.neet.dev/"; - tokenFile = "/run/agenix/gitea-actions-runner-token"; - labels = [ - "ubuntu-latest:docker://node:18-bullseye" - "nixos:host" - ]; + environment.systemPackages = with pkgs; [ + git + nodejs + jq + attic-client + ]; + }; }; - environment.systemPackages = with pkgs; [ - git - # Gitea Actions rely heavily on node. Include it because it would be installed anyway. - nodejs - ]; - - # To allow building on the host, must override the the service's config so it doesn't use a dynamic user - systemd.services.gitea-runner-inst.serviceConfig.DynamicUser = lib.mkForce false; + # Matching user on host — the container's gitea-runner UID must be + # recognized by the host's nix-daemon as trusted (shared UID namespace) users.users.gitea-runner = { + uid = giteaRunnerUid; home = "/var/lib/gitea-runner"; group = "gitea-runner"; isSystemUser = true; createHome = true; }; - users.groups.gitea-runner = { }; - - virtualisation.podman.enable = true; - boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; + users.groups.gitea-runner.gid = giteaRunnerGid; age.secrets.gitea-actions-runner-token.file = ../../secrets/gitea-actions-runner-token.age; }; diff --git a/secrets/attic-netrc.age b/secrets/attic-netrc.age new file mode 100644 index 0000000..c3feeaf Binary files /dev/null and b/secrets/attic-netrc.age differ diff --git a/secrets/atticd-credentials.age b/secrets/atticd-credentials.age new file mode 100644 index 0000000..ed5fcda Binary files /dev/null and b/secrets/atticd-credentials.age differ diff --git a/secrets/binary-cache-push-sshkey.age b/secrets/binary-cache-push-sshkey.age deleted file mode 100644 index 3fdb50c..0000000 Binary files a/secrets/binary-cache-push-sshkey.age and /dev/null differ diff --git a/secrets/gitea-actions-runner-token.age b/secrets/gitea-actions-runner-token.age index 8bdbd51..5ff450e 100644 Binary files a/secrets/gitea-actions-runner-token.age and b/secrets/gitea-actions-runner-token.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 7e4131f..f9d042e 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -7,6 +7,9 @@ let # nobody is using this secret but I still need to be able to r/w it nobody = sshKeys.userKeys; + + # For secrets that all machines need to know + everyone = roles.personal ++ roles.server; in with roles; @@ -22,8 +25,10 @@ with roles; # nix binary cache # public key: s0.koi-bebop.ts.net:OjbzD86YjyJZpCp9RWaQKANaflcpKhtzBMNP8I2aPUU= "binary-cache-private-key.age".publicKeys = binary-cache; - # public key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINpUZFFL9BpBVqeeU63sFPhR9ewuhEZerTCDIGW1NPSB - "binary-cache-push-sshkey.age".publicKeys = nobody; # this value is directly given to gitea + + # attic binary cache + "atticd-credentials.age".publicKeys = binary-cache; + "attic-netrc.age".publicKeys = everyone; # vpn "pia-login.age".publicKeys = pia;