at 25.11-pre 6.9 kB view raw
1{ 2 lib, 3 pkgs, 4 config, 5 ... 6}: 7 8let 9 inherit (lib) types; 10 11 cfg = config.services.atticd; 12 13 format = pkgs.formats.toml { }; 14 15 checkedConfigFile = 16 pkgs.runCommand "checked-attic-server.toml" 17 { 18 configFile = format.generate "server.toml" cfg.settings; 19 } 20 '' 21 export ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="$(${lib.getExe pkgs.openssl} genrsa -traditional 4096 | ${pkgs.coreutils}/bin/base64 -w0)" 22 export ATTIC_SERVER_DATABASE_URL="sqlite://:memory:" 23 ${lib.getExe cfg.package} --mode check-config -f $configFile 24 cat <$configFile >$out 25 ''; 26 27 atticadmShim = pkgs.writeShellScript "atticadm" '' 28 if [ -n "$ATTICADM_PWD" ]; then 29 cd "$ATTICADM_PWD" 30 if [ "$?" != "0" ]; then 31 >&2 echo "Warning: Failed to change directory to $ATTICADM_PWD" 32 fi 33 fi 34 35 exec ${cfg.package}/bin/atticadm -f ${checkedConfigFile} "$@" 36 ''; 37 38 atticadmWrapper = pkgs.writeShellScriptBin "atticd-atticadm" '' 39 exec systemd-run \ 40 --quiet \ 41 --pipe \ 42 --pty \ 43 --same-dir \ 44 --wait \ 45 --collect \ 46 --service-type=exec \ 47 --property=EnvironmentFile=${cfg.environmentFile} \ 48 --property=DynamicUser=yes \ 49 --property=User=${cfg.user} \ 50 --property=Environment=ATTICADM_PWD=$(pwd) \ 51 --working-directory / \ 52 -- \ 53 ${atticadmShim} "$@" 54 ''; 55 56 hasLocalPostgresDB = 57 let 58 url = cfg.settings.database.url or ""; 59 localStrings = [ 60 "localhost" 61 "127.0.0.1" 62 "/run/postgresql" 63 ]; 64 hasLocalStrings = lib.any (lib.flip lib.hasInfix url) localStrings; 65 in 66 config.services.postgresql.enable && lib.hasPrefix "postgresql://" url && hasLocalStrings; 67in 68{ 69 options = { 70 services.atticd = { 71 enable = lib.mkEnableOption "the atticd, the Nix Binary Cache server"; 72 73 package = lib.mkPackageOption pkgs "attic-server" { }; 74 75 environmentFile = lib.mkOption { 76 description = '' 77 Path to an EnvironmentFile containing required environment 78 variables: 79 80 - ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64: The base64-encoded RSA PEM PKCS1 of the 81 RS256 JWT secret. Generate it with `openssl genrsa -traditional 4096 | base64 -w0`. 82 ''; 83 type = types.nullOr types.path; 84 default = null; 85 }; 86 87 user = lib.mkOption { 88 description = '' 89 The group under which attic runs. 90 ''; 91 type = types.str; 92 default = "atticd"; 93 }; 94 95 group = lib.mkOption { 96 description = '' 97 The user under which attic runs. 98 ''; 99 type = types.str; 100 default = "atticd"; 101 }; 102 103 settings = lib.mkOption { 104 description = '' 105 Structured configurations of atticd. 106 See <https://github.com/zhaofengli/attic/blob/main/server/src/config-template.toml> 107 ''; 108 type = format.type; 109 default = { }; 110 }; 111 112 mode = lib.mkOption { 113 description = '' 114 Mode in which to run the server. 115 116 'monolithic' runs all components, and is suitable for single-node deployments. 117 118 'api-server' runs only the API server, and is suitable for clustering. 119 120 'garbage-collector' only runs the garbage collector periodically. 121 122 A simple NixOS-based Attic deployment will typically have one 'monolithic' and any number of 'api-server' nodes. 123 124 There are several other supported modes that perform one-off operations, but these are the only ones that make sense to run via the NixOS module. 125 ''; 126 type = lib.types.enum [ 127 "monolithic" 128 "api-server" 129 "garbage-collector" 130 ]; 131 default = "monolithic"; 132 }; 133 }; 134 }; 135 136 config = lib.mkIf cfg.enable { 137 assertions = [ 138 { 139 assertion = cfg.environmentFile != null; 140 message = '' 141 <option>services.atticd.environmentFile</option> is not set. 142 143 Run `openssl genrsa -traditional 4096 | base64 -w0` and create a file with the following contents: 144 145 ATTIC_SERVER_TOKEN_RS256_SECRET="output from command" 146 147 Then, set `services.atticd.environmentFile` to the quoted absolute path of the file. 148 ''; 149 } 150 ]; 151 152 services.atticd.settings = { 153 chunking = lib.mkDefault { 154 nar-size-threshold = 65536; 155 min-size = 16384; # 16 KiB 156 avg-size = 65536; # 64 KiB 157 max-size = 262144; # 256 KiB 158 }; 159 160 database.url = lib.mkDefault "sqlite:///var/lib/atticd/server.db?mode=rwc"; 161 162 # "storage" is internally tagged 163 # if the user sets something the whole thing must be replaced 164 storage = lib.mkDefault { 165 type = "local"; 166 path = "/var/lib/atticd/storage"; 167 }; 168 }; 169 170 systemd.services.atticd = { 171 wantedBy = [ "multi-user.target" ]; 172 after = [ "network-online.target" ] ++ lib.optionals hasLocalPostgresDB [ "postgresql.service" ]; 173 requires = lib.optionals hasLocalPostgresDB [ "postgresql.service" ]; 174 wants = [ "network-online.target" ]; 175 176 serviceConfig = { 177 ExecStart = "${lib.getExe cfg.package} -f ${checkedConfigFile} --mode ${cfg.mode}"; 178 EnvironmentFile = cfg.environmentFile; 179 StateDirectory = "atticd"; # for usage with local storage and sqlite 180 DynamicUser = true; 181 User = cfg.user; 182 Group = cfg.group; 183 Restart = "on-failure"; 184 RestartSec = 10; 185 186 CapabilityBoundingSet = [ "" ]; 187 DeviceAllow = ""; 188 DevicePolicy = "closed"; 189 LockPersonality = true; 190 MemoryDenyWriteExecute = true; 191 NoNewPrivileges = true; 192 PrivateDevices = true; 193 PrivateTmp = true; 194 PrivateUsers = true; 195 ProcSubset = "pid"; 196 ProtectClock = true; 197 ProtectControlGroups = true; 198 ProtectHome = true; 199 ProtectHostname = true; 200 ProtectKernelLogs = true; 201 ProtectKernelModules = true; 202 ProtectKernelTunables = true; 203 ProtectProc = "invisible"; 204 ProtectSystem = "strict"; 205 ReadWritePaths = 206 let 207 path = cfg.settings.storage.path; 208 isDefaultStateDirectory = path == "/var/lib/atticd" || lib.hasPrefix "/var/lib/atticd/" path; 209 in 210 lib.optionals (cfg.settings.storage.type or "" == "local" && !isDefaultStateDirectory) [ path ]; 211 RemoveIPC = true; 212 RestrictAddressFamilies = [ 213 "AF_INET" 214 "AF_INET6" 215 "AF_UNIX" 216 ]; 217 RestrictNamespaces = true; 218 RestrictRealtime = true; 219 RestrictSUIDSGID = true; 220 SystemCallArchitectures = "native"; 221 SystemCallFilter = [ 222 "@system-service" 223 "~@resources" 224 "~@privileged" 225 ]; 226 UMask = "0077"; 227 }; 228 }; 229 230 environment.systemPackages = [ 231 atticadmWrapper 232 ]; 233 }; 234}