yep, more dotfiles
1{ self
2, config
3, pkgs
4, upkgs
5, ...
6}:
7
8let
9 inherit (self.inputs) srvos agenix tangled;
10
11 ext-if = "eth0";
12 external-ip = "91.99.55.74";
13 external-netmask = 27;
14 external-gw = "144.x.x.255";
15 external-ip6 = "2a01:4f8:c2c:76d2::1";
16 external-netmask6 = 64;
17 external-gw6 = "fe80::1";
18
19 well-known-discord-dir = pkgs.writeTextDir ".well-known/discord" ''
20 dh=919234284ceb2aba439d15b9136073eb2308989b
21 '';
22 webfinger-dir = pkgs.writeTextDir ".well-known/webfinger" ''
23 {
24 "subject": "acct:milo@wiro.world",
25 "aliases": [
26 "mailto:milo@wiro.world",
27 "https://wiro.world/"
28 ],
29 "links": [
30 {
31 "rel": "http://wiro.world/rel/avatar",
32 "href": "https://wiro.world/logo.jpg",
33 "type": "image/jpeg"
34 },
35 {
36 "rel": "http://webfinger.net/rel/profile-page",
37 "href": "https://wiro.world/",
38 "type": "text/html"
39 },
40 {
41 "rel": "http://openid.net/specs/connect/1.0/issuer",
42 "href": "https://auth.wiro.world"
43 }
44 ]
45 }
46 '';
47 website-hostname = "wiro.world";
48
49 pds-port = 3001;
50 pds-hostname = "pds.wiro.world";
51
52 grafana-port = 3002;
53 grafana-hostname = "console.wiro.world";
54
55 tangled-owner = "did:plc:xhgrjm4mcx3p5h3y6eino6ti";
56 tangled-knot-port = 3003;
57 tangled-knot-hostname = "knot.wiro.world";
58 tangled-spindle-port = 3004;
59 tangled-spindle-hostname = "spindle.wiro.world";
60
61 thelounge-port = 3005;
62 thelounge-hostname = "lounge.wiro.world";
63
64 headscale-port = 3006;
65 headscale-hostname = "headscale.wiro.world";
66
67 lldap-port = 3007;
68 lldap-hostname = "ldap.wiro.world";
69
70 authelia-port = 3008;
71 authelia-hostname = "auth.wiro.world";
72
73 prometheus-port = 9001;
74 prometheus-node-exporter-port = 9002;
75 headscale-metrics-port = 9003;
76in
77{
78 imports = [
79 srvos.nixosModules.server
80 srvos.nixosModules.hardware-hetzner-cloud
81 srvos.nixosModules.mixins-terminfo
82
83 agenix.nixosModules.default
84
85 tangled.nixosModules.knot
86 tangled.nixosModules.spindle
87 ];
88
89 config = {
90 boot.loader.grub.enable = true;
91 boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" "ext4" ];
92
93 # Single network card is `eth0`
94 networking.usePredictableInterfaceNames = false;
95
96 networking.nameservers = [ "2001:4860:4860::8888" "2001:4860:4860::8844" ];
97
98 networking = {
99 interfaces.${ext-if} = {
100 ipv4.addresses = [{ address = external-ip; prefixLength = external-netmask; }];
101 ipv6.addresses = [{ address = external-ip6; prefixLength = external-netmask6; }];
102 };
103 defaultGateway = { interface = ext-if; address = external-gw; };
104 defaultGateway6 = { interface = ext-if; address = external-gw6; };
105
106 # Reflect firewall configuration on Hetzner
107 firewall.allowedTCPPorts = [ 22 80 443 ];
108 };
109
110 services.qemuGuest.enable = true;
111
112 services.openssh.enable = true;
113
114 services.tailscale.enable = true;
115
116 security.sudo.wheelNeedsPassword = false;
117
118 local.fragment.nix.enable = true;
119
120 programs.fish.enable = true;
121
122 services.fail2ban = {
123 enable = true;
124
125 maxretry = 5;
126 ignoreIP = [ ];
127
128 bantime = "24h";
129 bantime-increment = {
130 enable = true;
131 multipliers = "1 2 4 8 16 32 64";
132 maxtime = "168h";
133 overalljails = true;
134 };
135
136 jails = { };
137 };
138
139 services.caddy = {
140 enable = true;
141
142 globalConfig = ''
143 metrics { per_host }
144
145 on_demand_tls {
146 ask http://localhost:${toString pds-port}/tls-check
147 }
148 '';
149
150 virtualHosts.${website-hostname}.extraConfig =
151 ''
152 @discord {
153 path /.well-known/discord
154 method GET HEAD
155 }
156 route @discord {
157 header {
158 Access-Control-Allow-Origin "*"
159 X-Robots-Tag "noindex"
160 }
161 root ${well-known-discord-dir}
162 file_server
163 }
164 '' +
165 ''
166 @webfinger {
167 path /.well-known/webfinger
168 method GET HEAD
169 query resource=acct:milo@wiro.world
170 query resource=mailto:milo@wiro.world
171 query resource=https://wiro.world
172 query resource=https://wiro.world/
173 }
174 route @webfinger {
175 header {
176 Content-Type "application/jrd+json"
177 Access-Control-Allow-Origin "*"
178 X-Robots-Tag "noindex"
179 }
180 root ${webfinger-dir}
181 file_server
182 }
183 '' +
184 ''
185 reverse_proxy https://mrnossiom.github.io {
186 header_up Host {http.request.host}
187 }
188 '';
189
190 virtualHosts.${grafana-hostname}.extraConfig = ''
191 reverse_proxy http://localhost:${toString grafana-port}
192 '';
193
194 virtualHosts.${pds-hostname} = {
195 serverAliases = [ "*.${pds-hostname}" ];
196 extraConfig = ''
197 tls { on_demand }
198 reverse_proxy http://localhost:${toString pds-port}
199 '';
200 };
201
202 virtualHosts.${tangled-knot-hostname}.extraConfig = ''
203 reverse_proxy http://localhost:${toString tangled-knot-port}
204 '';
205
206 virtualHosts.${tangled-spindle-hostname}.extraConfig = ''
207 reverse_proxy http://localhost:${toString tangled-spindle-port}
208 '';
209
210 virtualHosts.${thelounge-hostname}.extraConfig = ''
211 reverse_proxy http://localhost:${toString thelounge-port}
212 '';
213
214 virtualHosts.${headscale-hostname}.extraConfig = ''
215 reverse_proxy http://localhost:${toString headscale-port}
216 '';
217
218 virtualHosts.${lldap-hostname}.extraConfig = ''
219 reverse_proxy http://localhost:${toString lldap-port}
220 '';
221
222 virtualHosts.${authelia-hostname}.extraConfig = ''
223 reverse_proxy http://localhost:${toString authelia-port}
224 '';
225 };
226
227 age.secrets.pds-env.file = ../../secrets/pds-env.age;
228 services.pds = {
229 enable = true;
230 package = upkgs.bluesky-pds;
231
232 settings = {
233 PDS_HOSTNAME = "pds.wiro.world";
234 PDS_PORT = pds-port;
235 # is in systemd /tmp subfolder
236 LOG_DESTINATION = "/tmp/pds.log";
237 };
238
239 environmentFiles = [
240 config.age.secrets.pds-env.path
241 ];
242 };
243
244 services.tangled-knot = {
245 enable = true;
246 openFirewall = true;
247
248 motd = "Welcome to @wiro.world's knot!\n";
249 server = {
250 listenAddr = "localhost:${toString tangled-knot-port}";
251 hostname = tangled-knot-hostname;
252 owner = tangled-owner;
253 };
254 };
255
256 services.tangled-spindle = {
257 enable = true;
258
259 server = {
260 listenAddr = "localhost:${toString tangled-spindle-port}";
261 hostname = tangled-spindle-hostname;
262 owner = tangled-owner;
263 };
264 };
265
266 age.secrets.grafana-oidc-secret = { file = ../../secrets/grafana-oidc-secret.age; owner = "grafana"; };
267 services.grafana = {
268 enable = true;
269
270 settings = {
271 server = {
272 http_port = grafana-port;
273 domain = grafana-hostname;
274 };
275
276 "auth.generic_auth" = {
277 enable = true;
278 name = "Authelia";
279 icon = "signin";
280
281 client_id = "grafana";
282 client_secret_path = config.age.secrets.grafana-oidc-secret.path;
283
284 scopes = [ "openid" "profile" "email" "groups" ];
285 auth_url = "https://auth.wiro.world/api/oidc/authorization";
286 token_url = "https://auth.wiro.world/api/oidc/token";
287 api_url = "https://auth.wiro.world/api/oidc/userinfo";
288 login_attribute_path = "preferred_username";
289 groups_attribute_path = "groups";
290 name_attribute_path = "name";
291 use_pkce = true;
292 };
293 };
294 };
295
296 services.prometheus = {
297 enable = true;
298 port = prometheus-port;
299
300 scrapeConfigs = [
301 {
302 job_name = "caddy";
303 static_configs = [{ targets = [ "localhost:${toString 2019}" ]; }];
304 }
305 {
306 job_name = "node";
307 static_configs = [{ targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ]; }];
308 }
309 ];
310
311 exporters.node = {
312 enable = true;
313 port = prometheus-node-exporter-port;
314 };
315 };
316
317 services.thelounge = {
318 enable = true;
319 port = thelounge-port;
320 public = false;
321
322 extraConfig = {
323 host = "127.0.0.1";
324 reverseProxy = true;
325
326 # TODO: use ldap, find a way to hide password
327 };
328 };
329
330 age.secrets.headscale-oidc-secret = { file = ../../secrets/headscale-oidc-secret.age; owner = config.services.headscale.user; };
331 services.headscale = {
332 enable = true;
333
334 port = headscale-port;
335 settings = {
336 server_url = "https://${headscale-hostname}";
337 metrics_listen_addr = "127.0.0.1:${toString headscale-metrics-port}";
338
339 # disable TLS
340 tls_cert_path = null;
341 tls_key_path = null;
342
343 dns = {
344 magic_dns = true;
345 base_domain = "net.wiro.world";
346 };
347
348 oidc = {
349 issuer = "https://auth.wiro.world";
350 client_id = "headscale";
351 client_secret_path = config.age.secrets.headscale-oidc-secret.path;
352 pkce.enable = true;
353 };
354 };
355 };
356
357 age.secrets.lldap-env.file = ../../secrets/lldap-env.age;
358 services.lldap = {
359 enable = true;
360 settings = {
361 http_url = "https://${lldap-hostname}";
362 http_port = lldap-port;
363
364 ldap_base_dn = "dc=wiro,dc=world";
365 };
366 environmentFile = config.age.secrets.lldap-env.path;
367 };
368
369 age.secrets.authelia-jwt-secret = { file = ../../secrets/authelia-jwt-secret.age; owner = config.services.authelia.instances.main.user; };
370 age.secrets.authelia-issuer-private-key = { file = ../../secrets/authelia-issuer-private-key.age; owner = config.services.authelia.instances.main.user; };
371 age.secrets.authelia-storage-key = { file = ../../secrets/authelia-storage-key.age; owner = config.services.authelia.instances.main.user; };
372 age.secrets.authelia-ldap-password = { file = ../../secrets/authelia-ldap-password.age; owner = config.services.authelia.instances.main.user; };
373 age.secrets.authelia-smtp-password = { file = ../../secrets/authelia-smtp-password.age; owner = config.services.authelia.instances.main.user; };
374 services.authelia.instances.main = {
375 enable = true;
376
377 secrets = {
378 jwtSecretFile = config.age.secrets.authelia-jwt-secret.path;
379 oidcIssuerPrivateKeyFile = config.age.secrets.authelia-issuer-private-key.path;
380 storageEncryptionKeyFile = config.age.secrets.authelia-storage-key.path;
381 };
382 environmentVariables = {
383 AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = config.age.secrets.authelia-ldap-password.path;
384 AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = config.age.secrets.authelia-smtp-password.path;
385 };
386 settings = {
387 server.address = "localhost:${toString authelia-port}";
388 storage.local.path = "/var/lib/authelia-main/db.sqlite3";
389
390 session = {
391 cookies = [{
392 domain = "wiro.world";
393 authelia_url = "https://${authelia-hostname}";
394 default_redirection_url = "https://wiro.world";
395 }];
396 };
397
398 authentication_backend.ldap = {
399 address = "ldap://localhost:3890";
400 timeout = "5m"; # replace with systemd dependency
401
402 user = "uid=authelia,ou=people,dc=wiro,dc=world";
403 # Set in `AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE`.
404 # password = "";
405
406 base_dn = "dc=wiro,dc=world";
407 users_filter = "(&({username_attribute}={input})(objectClass=person))";
408 groups_filter = "(&(member={dn})(objectClass=groupOfNames))";
409
410 # attributes = {
411 # # username = "user_id";
412 # username = "uid";
413 # display_name = "display_name";
414 # mail = "mail";
415 # group_name = "cn";
416 # };
417 };
418
419 access_control = {
420 default_policy = "deny";
421 rules = [
422 {
423 domain = "*.wiro.world";
424 policy = "one_factor";
425 }
426 ];
427 };
428
429
430 identity_providers.oidc = {
431 # enforce_pkce = "always";
432 clients = [
433 {
434 client_name = "Headscale";
435 client_id = "headscale";
436 client_secret = "$pbkdf2-sha256$310000$XY680D9gkSoWhD0UtYHNFg$ptWB3exOYCga6uq1N.oimuV3ILjK3F8lBWBpsBpibos";
437
438 redirect_uris = [ "https://headscale.wiro.world/oidc/callback" ];
439 }
440 {
441 client_name = "Grafana Console";
442 client_id = "grafana";
443 client_secret = "$pbkdf2-sha256$310000$UkwrqxTZodGMs9.Ca2cXAA$HCWFgQbFHGXZpuz.I3HHdkTZLUevRVGlhKEFaOlPmKs";
444
445 redirect_uris = [ "https://console.wiro.world/login/generic_oauth" ];
446 }
447 ];
448 };
449
450
451 notifier.smtp = {
452 address = "smtp://smtp.resend.com:2587";
453 username = "resend";
454 # Set in `AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE`.
455 # password = "";
456 sender = "authelia@wiro.world";
457 };
458 };
459 };
460 };
461}