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}