1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
{ config, inputs, lib, pkgs, ... }:
with lib;
let
cfg = config.modules.services.atticd;
in
{
imports = [
inputs.attic.nixosModules.atticd
];
options.modules.services.atticd = {
enable = mkEnableOption "Whether to enable atticd, a Multi-tenant Nix Binary Cache";
hosts = mkOption { type = types.listOf types.str; description = "List of FQDNs where attic is reachable at"; };
baseURL = mkOption { type = types.str; description = "URL to use as a API Endpoint"; };
storagePath = mkOption { type = types.path; description = "Path to the directory to store all files under"; };
watchStore = mkEnableOption "Whether to watch the Nix store for new paths";
secrets = {
attic-credentials = mkOption { type = types.path; description = "Path to the acme environment file"; };
};
};
config = mkMerge [
(mkIf cfg.enable {
services.atticd = {
enable = true;
credentialsFile = cfg.secrets.attic-credentials;
settings = {
listen = "[::]:4005";
allowed-hosts = cfg.hosts;
api-endpoint = cfg.baseURL;
database.url = "postgres://atticd?host=/run/postgresql";
require-proof-of-possession = false;
# Data chunking
#
# Warning: If you change any of the values here, it will be
# difficult to reuse existing chunks for newly-uploaded NARs
# since the cutpoints will be different. As a result, the
# deduplication ratio will suffer for a while after the change.
chunking = {
# The minimum NAR size to trigger chunking
#
# IUUU, chunking is disabled entirely for newly-uploaded NARs.
# If 1, all NARs are chunked.
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
};
storage = {
type = "local";
path = cfg.storagePath;
};
compression = {
type = "zstd";
level = 9;
};
garbage-collection = {
interval = "12 hours";
default-retention-period = "4 weeks";
};
};
};
services.postgresql.ensureDatabases = [ "atticd" ];
services.postgresql.ensureUsers = [
{
name = "atticd";
ensureDBOwnership = true;
}
];
services.nginx.virtualHosts = builtins.listToAttrs (map
(host: {
name = host;
value = {
extraConfig = ''
client_max_body_size 0;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
'';
};
})
cfg.hosts);
})
(mkIf cfg.watchStore {
systemd.services.atticd-watch-store = mkIf cfg.watchStore {
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
DynamicUser = true;
StateDirectory = "atticd-watch-store";
# NOTE: currently this expects `attic/config.toml` to be manually
# generated and placed on `/var/lib/atticd-watch-store`
Environment = "XDG_CONFIG_HOME=/var/lib/atticd-watch-store";
ExecStart = "${pkgs.attic}/bin/attic watch-store hydra";
Restart = "on-failure";
RestartSec = "5s";
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictNamespaces = true;
RestrictSUIDSGID = true;
};
};
modules.persistence.directories = [
"/var/lib/private/atticd-watch-store"
];
})
];
}
|