{ config, lib, pkgs, ... }: let cfg = config.services.burrow.nixCache; garageCfg = config.services.burrow.garage; in { options.services.burrow.nixCache = { enable = lib.mkEnableOption "Burrow Nix binary cache"; domain = lib.mkOption { type = lib.types.str; default = "nix.burrow.net"; description = "Public domain for the Burrow Nix binary cache."; }; listenAddress = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "Attic listen address."; }; port = lib.mkOption { type = lib.types.port; default = 8080; description = "Attic listen port."; }; cacheName = lib.mkOption { type = lib.types.str; default = "burrow"; description = "Default public Attic cache name."; }; environmentFile = lib.mkOption { type = lib.types.path; default = garageCfg.environmentFile; description = "Environment file containing Attic JWT and Garage S3 credentials."; }; adminTokenFile = lib.mkOption { type = lib.types.path; default = "/var/lib/burrow/nix-cache/admin-token"; description = "Host-local file containing the generated Attic admin token."; }; ciPushTokenFile = lib.mkOption { type = lib.types.path; default = "/var/lib/burrow/nix-cache/ci-push-token"; description = "Host-local file containing the generated Attic CI push token."; }; ciPushTokenValidity = lib.mkOption { type = lib.types.str; default = "1y"; description = "Validity duration for the generated Attic CI push token."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = garageCfg.enable; message = "services.burrow.nixCache requires services.burrow.garage.enable = true."; } ]; services.atticd = { enable = true; environmentFile = cfg.environmentFile; settings = { listen = "${cfg.listenAddress}:${toString cfg.port}"; allowed-hosts = [ cfg.domain ]; api-endpoint = "https://${cfg.domain}/"; require-proof-of-possession = true; storage = { type = "s3"; bucket = garageCfg.atticBucket; region = garageCfg.region; endpoint = "http://${garageCfg.apiListenAddress}:${toString garageCfg.apiPort}"; }; }; }; environment.systemPackages = [ pkgs.attic-client ]; services.caddy.virtualHosts."${cfg.domain}".extraConfig = '' encode gzip zstd reverse_proxy ${cfg.listenAddress}:${toString cfg.port} ''; systemd.services.atticd = { after = [ "burrow-garage-bootstrap.service" ]; wants = [ "burrow-garage-bootstrap.service" ]; requires = [ "burrow-garage-bootstrap.service" ]; }; systemd.services.burrow-nix-cache-bootstrap = { description = "Bootstrap Burrow Nix binary cache"; after = [ "atticd.service" ]; requires = [ "atticd.service" ]; wantedBy = [ "multi-user.target" ]; path = [ pkgs.attic-client pkgs.coreutils ]; serviceConfig = { Type = "oneshot"; User = "root"; Group = "root"; }; script = '' set -euo pipefail install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.adminTokenFile})" install -d -m 0700 "$(dirname ${lib.escapeShellArg cfg.ciPushTokenFile})" for _ in $(seq 1 60); do if (: > /dev/tcp/${cfg.listenAddress}/${toString cfg.port}) >/dev/null 2>&1; then break fi sleep 1 done if [ ! -s ${lib.escapeShellArg cfg.adminTokenFile} ]; then umask 077 atticd-atticadm make-token \ --sub burrow-nix-cache-bootstrap \ --validity 10y \ --create-cache '*' \ --pull '*' \ --push '*' \ --delete '*' \ --configure-cache '*' \ --configure-cache-retention '*' \ > ${lib.escapeShellArg cfg.adminTokenFile} fi token="$(cat ${lib.escapeShellArg cfg.adminTokenFile})" export HOME=/var/lib/burrow/nix-cache install -d -m 0700 "$HOME" attic login local http://${cfg.listenAddress}:${toString cfg.port} "$token" --set-default if ! attic cache info ${lib.escapeShellArg "local:${cfg.cacheName}"} >/dev/null 2>&1; then attic cache create --public --priority 39 ${lib.escapeShellArg "local:${cfg.cacheName}"} fi if [ ! -s ${lib.escapeShellArg cfg.ciPushTokenFile} ]; then umask 077 atticd-atticadm make-token \ --sub burrow-ci-cache-publisher \ --validity ${lib.escapeShellArg cfg.ciPushTokenValidity} \ --pull ${lib.escapeShellArg cfg.cacheName} \ --push ${lib.escapeShellArg cfg.cacheName} \ > ${lib.escapeShellArg cfg.ciPushTokenFile} fi ''; }; }; }