yep, more dotfiles

server: split large server file into weird-row-server host

wiro.world 124dc64d bc427963

verified
+1002 -763
+1 -1
README.md
··· 20 20 - `fragments`: Home Manager configuration fragments 21 21 - `options`: Home Manager configuration flags 22 22 - `profiles`: Base Home Manager configurations to build upon (e.g. `desktop`, `minimal`) 23 - - `lib`: Additional custom lib and flake helpers 23 + - `lib`: Additional custom lib and flake helpers 24 24 - `modules`: modules that fill a missing feature of NixOS or Home Manager 25 25 - `nixos`: NixOS related config 26 26 - `hardware/<hostname>.nix`: Device-specific settings like settings generated by `nixos-generate-config`
+1 -1
apps/flash-installer.nix
··· 29 29 fi 30 30 31 31 echo "Flashing to $dev" 32 - 32 + 33 33 # Format selected disk 34 34 pv -tpreb "${isoPath}" | sudo dd bs=4M of="$dev" iflag=fullblock conv=notrunc,noerror oflag=sync 35 35 '';
+2 -1
configurations.nix
··· 19 19 20 20 # Servers 21 21 "weird-row-server" = createSystem pkgs [ 22 - (system "weird-row-server" "server") 22 + (host "weird-row-server") 23 23 (managedDiskLayout "ext4-hetzner" { device = "sda"; swapSize = 2; }) 24 + # TODO: should we keep a real user there? 24 25 (user "milomoisson" { description = "Milo Moisson"; profile = "server"; keys = keys.users; }) 25 26 ]; 26 27 };
+3 -1
home-manager/fragments/git.nix
··· 162 162 }; 163 163 164 164 git = { 165 - pagers.externalDiffCommand = "difft --color=always"; 165 + pagers = [ 166 + { externalDiffCommand = "difft --color=always"; } 167 + ]; 166 168 }; 167 169 168 170 # to be declarative or not to be declarative?
+1 -1
home-manager/fragments/shell.nix
··· 150 150 ''; 151 151 152 152 # Quickly explore a derivation (using registry syntax) 153 - # e.g. `cdd nixpkgs#fontforge` or `cdd unixpkgs#fontforge` 153 + # e.g. `cdd nixpkgs#fontforge` or `cdd unixpkgs#fontforge` 154 154 cdd = "cd (nix build --no-link --print-out-paths $argv | ${lib.getExe pkgs.fzf})"; 155 155 } // lib.optionalAttrs (!flags.onlyCached) { 156 156 # Quickly get outta here to test something
+5 -4
home-manager/fragments/stylix.nix
··· 11 11 cfg = config.local.fragment.stylix; 12 12 in 13 13 { 14 - imports = [ stylix.homeModules.stylix ]; 14 + imports = [ 15 + stylix.homeModules.stylix 16 + # issues a warning because we use `useGlobalPkgs` 17 + { config.stylix.overlays.enable = false; } 18 + ]; 15 19 16 20 options.local.fragment.stylix.enable = lib.mkEnableOption '' 17 21 Stylix related ··· 26 30 stylix = { 27 31 enable = true; 28 32 base16Scheme = lib.mkDefault "${pkgs.base16-schemes}/share/themes/onedark-dark.yaml"; 29 - 30 - # issues a warning because we use `useGlobalPkgs` 31 - overlays.enable = false; 32 33 33 34 image = ../../assets/wallpaper-binary-cloud.png; 34 35
+1 -1
home-manager/fragments/waybar.nix
··· 232 232 .modules-right widget .module { 233 233 padding: 0 1rem; 234 234 235 - color: @base07; 235 + color: @base07; 236 236 } 237 237 238 238 /* Round first and last child of left, right and center modules. Disable rounding on the sides*/
+163
hosts/weird-row-server/authelia.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + authelia-port = 3008; 7 + authelia-hostname = "auth.wiro.world"; 8 + 9 + authelia-metrics-port = 9004; 10 + headscale-hostname = "headscale.wiro.world"; 11 + grafana-hostname = "console.wiro.world"; 12 + miniflux-hostname = "news.wiro.world"; 13 + in 14 + { 15 + config = { 16 + age.secrets.authelia-jwt-secret = { file = secrets/authelia-jwt-secret.age; owner = config.services.authelia.instances.main.user; }; 17 + age.secrets.authelia-issuer-private-key = { file = secrets/authelia-issuer-private-key.age; owner = config.services.authelia.instances.main.user; }; 18 + age.secrets.authelia-storage-key = { file = secrets/authelia-storage-key.age; owner = config.services.authelia.instances.main.user; }; 19 + age.secrets.authelia-ldap-password = { file = secrets/authelia-ldap-password.age; owner = config.services.authelia.instances.main.user; }; 20 + age.secrets.authelia-smtp-password = { file = secrets/authelia-smtp-password.age; owner = config.services.authelia.instances.main.user; }; 21 + services.authelia.instances.main = { 22 + enable = true; 23 + 24 + secrets = { 25 + jwtSecretFile = config.age.secrets.authelia-jwt-secret.path; 26 + oidcIssuerPrivateKeyFile = config.age.secrets.authelia-issuer-private-key.path; 27 + storageEncryptionKeyFile = config.age.secrets.authelia-storage-key.path; 28 + }; 29 + environmentVariables = { 30 + AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = config.age.secrets.authelia-ldap-password.path; 31 + AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = config.age.secrets.authelia-smtp-password.path; 32 + }; 33 + settings = { 34 + server.address = "localhost:${toString authelia-port}"; 35 + storage.local.path = "/var/lib/authelia-main/db.sqlite3"; 36 + telemetry.metrics = { 37 + enabled = true; 38 + address = "tcp://:${toString authelia-metrics-port}/metrics"; 39 + }; 40 + 41 + session = { 42 + cookies = [{ 43 + domain = "wiro.world"; 44 + authelia_url = "https://${authelia-hostname}"; 45 + default_redirection_url = "https://wiro.world"; 46 + }]; 47 + }; 48 + 49 + authentication_backend.ldap = { 50 + address = "ldap://localhost:3890"; 51 + timeout = "5m"; # replace with systemd dependency 52 + 53 + user = "uid=authelia,ou=people,dc=wiro,dc=world"; 54 + # Set in `AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE`. 55 + # password = ""; 56 + 57 + base_dn = "dc=wiro,dc=world"; 58 + users_filter = "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))"; 59 + additional_users_dn = "ou=people"; 60 + groups_filter = "(&(member={dn})(objectClass=groupOfNames))"; 61 + additional_groups_dn = "ou=groups"; 62 + 63 + attributes = { 64 + username = "uid"; 65 + display_name = "cn"; 66 + given_name = "givenname"; 67 + family_name = "last_name"; 68 + mail = "mail"; 69 + picture = "avatar"; 70 + 71 + group_name = "cn"; 72 + }; 73 + }; 74 + 75 + access_control = { 76 + default_policy = "deny"; 77 + # Rules are sequential and do not apply to OIDC 78 + rules = [ 79 + { 80 + domain = "headscale.wiro.world"; 81 + policy = "two_factor"; 82 + 83 + } 84 + { 85 + domain = "news.wiro.world"; 86 + policy = "one_factor"; 87 + 88 + subject = [ [ "group:miniflux" "oauth2:client:miniflux" ] ]; 89 + } 90 + { 91 + domain = "*.wiro.world"; 92 + policy = "two_factor"; 93 + } 94 + ]; 95 + }; 96 + 97 + identity_providers.oidc = { 98 + enforce_pkce = "always"; 99 + 100 + authorization_policies = 101 + let 102 + mkStrictPolicy = policy: subject: 103 + { default_policy = "deny"; rules = [{ inherit policy subject; }]; }; 104 + in 105 + { 106 + headscale = mkStrictPolicy "two_factor" [ "group:headscale" ]; 107 + tailscale = mkStrictPolicy "two_factor" [ "group:headscale" ]; 108 + grafana = mkStrictPolicy "one_factor" [ "group:grafana" ]; 109 + miniflux = mkStrictPolicy "one_factor" [ "group:miniflux" ]; 110 + }; 111 + 112 + claims_policies.headscale = { id_token = [ "email" "name" "preferred_username" "picture" "groups" ]; }; 113 + 114 + clients = [ 115 + { 116 + client_name = "Headscale"; 117 + client_id = "headscale"; 118 + client_secret = "$pbkdf2-sha256$310000$XY680D9gkSoWhD0UtYHNFg$ptWB3exOYCga6uq1N.oimuV3ILjK3F8lBWBpsBpibos"; 119 + redirect_uris = [ "https://${headscale-hostname}/oidc/callback" ]; 120 + authorization_policy = "headscale"; 121 + claims_policy = "headscale"; 122 + } 123 + { 124 + client_name = "Tailscale"; 125 + client_id = "tailscale"; 126 + client_secret = "$pbkdf2-sha256$310000$PcUaup9aWKI9ZLeCF6.avw$FpsTxkDaxcoQlBi8aIacegXpjEDiCI6nXcaHyZ2Sxyc"; 127 + redirect_uris = [ "https://login.tailscale.com/a/oauth_response" ]; 128 + authorization_policy = "tailscale"; 129 + } 130 + { 131 + client_name = "Grafana Console"; 132 + client_id = "grafana"; 133 + client_secret = "$pbkdf2-sha256$310000$UkwrqxTZodGMs9.Ca2cXAA$HCWFgQbFHGXZpuz.I3HHdkTZLUevRVGlhKEFaOlPmKs"; 134 + redirect_uris = [ "https://${grafana-hostname}/login/generic_oauth" ]; 135 + authorization_policy = "grafana"; 136 + } 137 + { 138 + client_name = "Miniflux"; 139 + client_id = "miniflux"; 140 + client_secret = "$pbkdf2-sha256$310000$uPqbWfCOBXDY6nV1vsx3uA$HOWG2hL.c/bs9Dwaee3b9DxjH7KFO.SaZMbasXV9Vdw"; 141 + redirect_uris = [ "https://${miniflux-hostname}/oauth2/oidc/callback" ]; 142 + authorization_policy = "miniflux"; 143 + } 144 + ]; 145 + }; 146 + 147 + notifier.smtp = { 148 + address = "smtp://smtp.resend.com:2587"; 149 + username = "resend"; 150 + # Set in `AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE`. 151 + # password = ""; 152 + sender = "authelia@wiro.world"; 153 + }; 154 + }; 155 + }; 156 + 157 + services.caddy = { 158 + virtualHosts.${authelia-hostname}.extraConfig = '' 159 + reverse_proxy http://localhost:${toString authelia-port} 160 + ''; 161 + }; 162 + }; 163 + }
+137
hosts/weird-row-server/default.nix
··· 1 + { self 2 + , config 3 + , pkgs 4 + , ... 5 + }: 6 + 7 + let 8 + inherit (self.inputs) srvos; 9 + 10 + ext-if = "eth0"; 11 + external-ip = "91.99.55.74"; 12 + external-netmask = 27; 13 + external-gw = "144.x.x.255"; 14 + external-ip6 = "2a01:4f8:c2c:76d2::1"; 15 + external-netmask6 = 64; 16 + external-gw6 = "fe80::1"; 17 + 18 + website-hostname = "wiro.world"; 19 + 20 + static-hostname = "static.wiro.world"; 21 + in 22 + { 23 + imports = [ 24 + srvos.nixosModules.server 25 + srvos.nixosModules.hardware-hetzner-cloud 26 + srvos.nixosModules.mixins-terminfo 27 + 28 + ./authelia.nix 29 + ./goatcounter.nix 30 + ./grafana.nix 31 + ./headscale.nix 32 + ./hypixel-bank-tracker.nix 33 + ./lldap.nix 34 + ./miniflux.nix 35 + ./pds.nix 36 + ./tangled.nix 37 + ./thelounge.nix 38 + ./tuwunel.nix 39 + ./vaultwarden.nix 40 + ./warrior.nix 41 + ./webfinger.nix 42 + ]; 43 + 44 + config = { 45 + boot.loader.grub.enable = true; 46 + boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" "ext4" ]; 47 + 48 + # Single network card is `eth0` 49 + networking.usePredictableInterfaceNames = false; 50 + 51 + networking.nameservers = [ "2001:4860:4860::8888" "2001:4860:4860::8844" ]; 52 + 53 + networking = { 54 + interfaces.${ext-if} = { 55 + ipv4.addresses = [{ address = external-ip; prefixLength = external-netmask; }]; 56 + ipv6.addresses = [{ address = external-ip6; prefixLength = external-netmask6; }]; 57 + }; 58 + defaultGateway = { interface = ext-if; address = external-gw; }; 59 + defaultGateway6 = { interface = ext-if; address = external-gw6; }; 60 + 61 + # Reflect firewall configuration on Hetzner 62 + firewall.allowedTCPPorts = [ 22 80 443 ]; 63 + }; 64 + 65 + services.qemuGuest.enable = true; 66 + 67 + services.openssh.enable = true; 68 + 69 + # age.secrets.tailscale-authkey.file = secrets/tailscale-authkey.age; 70 + services.tailscale = { 71 + enable = true; 72 + extraSetFlags = [ "--advertise-exit-node" ]; 73 + # authKeyFile = config.age.secrets.tailscale-authkey.path; 74 + authKeyParameters = { 75 + baseURL = "https://headscale.wiro.world"; 76 + ephemeral = true; 77 + preauthorized = true; 78 + }; 79 + }; 80 + 81 + security.sudo.wheelNeedsPassword = false; 82 + 83 + local.fragment.nix.enable = true; 84 + 85 + programs.fish.enable = true; 86 + 87 + services.fail2ban = { 88 + enable = true; 89 + 90 + maxretry = 5; 91 + ignoreIP = [ ]; 92 + 93 + bantime = "24h"; 94 + bantime-increment = { 95 + enable = true; 96 + multipliers = "1 2 4 8 16 32 64"; 97 + maxtime = "168h"; 98 + overalljails = true; 99 + }; 100 + 101 + jails = { }; 102 + }; 103 + 104 + services.caddy = { 105 + enable = true; 106 + package = pkgs.caddy.withPlugins { 107 + doInstallCheck = false; 108 + plugins = [ 109 + "github.com/tailscale/caddy-tailscale@v0.0.0-20251016213337-01d084e119cb" 110 + "github.com/caddy-dns/hetzner@v2.0.0-preview-1" 111 + ]; 112 + hash = "sha256-muKwDYs5Jp4ib/psZxpp1Kyfsqz6wPz/lpHFGtx67uY="; 113 + }; 114 + 115 + globalConfig = '' 116 + tailscale { 117 + ephemeral 118 + } 119 + ''; 120 + 121 + virtualHosts.${website-hostname}.extraConfig = 122 + # TODO: host website on server with automatic deployment 123 + '' 124 + reverse_proxy https://mrnossiom.github.io { 125 + header_up Host {http.request.host} 126 + } 127 + ''; 128 + 129 + virtualHosts.${static-hostname}.extraConfig = '' 130 + root /var/www/static 131 + file_server browse 132 + ''; 133 + }; 134 + 135 + # TODO: use bind to declare dns records declaratively 136 + }; 137 + }
+25
hosts/weird-row-server/goatcounter.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + goatcounter-port = 3010; 7 + goatcounter-hostname = "stats.wiro.world"; 8 + in 9 + { 10 + config = { 11 + services.goatcounter = { 12 + enable = true; 13 + 14 + port = goatcounter-port; 15 + proxy = true; 16 + extraArgs = [ "-automigrate" ]; 17 + }; 18 + 19 + services.caddy = { 20 + virtualHosts.${goatcounter-hostname}.extraConfig = '' 21 + reverse_proxy http://localhost:${toString goatcounter-port} 22 + ''; 23 + }; 24 + }; 25 + }
+84
hosts/weird-row-server/grafana.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + grafana-port = 3002; 7 + grafana-hostname = "console.wiro.world"; 8 + 9 + prometheus-port = 9001; 10 + prometheus-node-exporter-port = 9002; 11 + caddy-metrics-port = 2019; 12 + authelia-metrics-port = 9004; 13 + headscale-metrics-port = 9003; 14 + in 15 + { 16 + config = { 17 + age.secrets.grafana-oidc-secret = { file = secrets/grafana-oidc-secret.age; owner = "grafana"; }; 18 + services.grafana = { 19 + enable = true; 20 + 21 + settings = { 22 + server = { 23 + http_port = grafana-port; 24 + domain = grafana-hostname; 25 + root_url = "https://${grafana-hostname}"; 26 + }; 27 + 28 + "auth.generic_oauth" = { 29 + enable = true; 30 + name = "Authelia"; 31 + icon = "signin"; 32 + 33 + client_id = "grafana"; 34 + client_secret_path = config.age.secrets.grafana-oidc-secret.path; 35 + auto_login = true; 36 + 37 + scopes = [ "openid" "profile" "email" "groups" ]; 38 + auth_url = "https://auth.wiro.world/api/oidc/authorization"; 39 + token_url = "https://auth.wiro.world/api/oidc/token"; 40 + api_url = "https://auth.wiro.world/api/oidc/userinfo"; 41 + use_pkce = true; 42 + }; 43 + }; 44 + }; 45 + 46 + services.prometheus = { 47 + enable = true; 48 + port = prometheus-port; 49 + 50 + exporters.node = { 51 + enable = true; 52 + port = prometheus-node-exporter-port; 53 + }; 54 + 55 + scrapeConfigs = [ 56 + { 57 + job_name = "caddy"; 58 + static_configs = [{ targets = [ "localhost:${toString caddy-metrics-port}" ]; }]; 59 + } 60 + { 61 + job_name = "node-exporter"; 62 + static_configs = [{ targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ]; }]; 63 + } 64 + { 65 + job_name = "headscale"; 66 + static_configs = [{ targets = [ "localhost:${toString headscale-metrics-port}" ]; }]; 67 + } 68 + { 69 + job_name = "authelia"; 70 + static_configs = [{ targets = [ "localhost:${toString authelia-metrics-port}" ]; }]; 71 + } 72 + ]; 73 + }; 74 + 75 + services.caddy = { 76 + globalConfig = '' 77 + metrics { per_host } 78 + ''; 79 + virtualHosts.${grafana-hostname}.extraConfig = '' 80 + reverse_proxy http://localhost:${toString grafana-port} 81 + ''; 82 + }; 83 + }; 84 + }
+84
hosts/weird-row-server/headscale.nix
··· 1 + { pkgs 2 + , config 3 + , ... 4 + }: 5 + 6 + let 7 + json-format = pkgs.formats.json { }; 8 + 9 + headscale-port = 3006; 10 + headscale-derp-port = 3478; 11 + headscale-hostname = "headscale.wiro.world"; 12 + 13 + headscale-metrics-port = 9003; 14 + in 15 + { 16 + config = { 17 + networking.firewall.allowedUDPPorts = [ headscale-derp-port ]; 18 + 19 + age.secrets.headscale-oidc-secret = { file = secrets/headscale-oidc-secret.age; owner = config.services.headscale.user; }; 20 + services.headscale = { 21 + enable = true; 22 + 23 + port = headscale-port; 24 + settings = { 25 + server_url = "https://${headscale-hostname}"; 26 + metrics_listen_addr = "127.0.0.1:${toString headscale-metrics-port}"; 27 + 28 + policy.path = json-format.generate "policy.json" { 29 + acls = [ 30 + { 31 + action = "accept"; 32 + src = [ "autogroup:member" ]; 33 + dst = [ "autogroup:self:*" ]; 34 + } 35 + ]; 36 + ssh = [ 37 + { 38 + action = "accept"; 39 + src = [ "autogroup:member" ]; 40 + dst = [ "autogroup:self" ]; 41 + # Adding root here is privilege escalation as a feature :) 42 + users = [ "autogroup:nonroot" ]; 43 + } 44 + ]; 45 + }; 46 + 47 + # disable TLS 48 + tls_cert_path = null; 49 + tls_key_path = null; 50 + 51 + dns = { 52 + magic_dns = true; 53 + base_domain = "net.wiro.world"; 54 + 55 + override_local_dns = true; 56 + # Quad9 nameservers 57 + nameservers.global = [ "9.9.9.9" "149.112.112.112" "2620:fe::fe" "2620:fe::9" ]; 58 + }; 59 + 60 + oidc = { 61 + only_start_if_oidc_is_available = true; 62 + issuer = "https://auth.wiro.world"; 63 + client_id = "headscale"; 64 + client_secret_path = config.age.secrets.headscale-oidc-secret.path; 65 + scope = [ "openid" "profile" "email" "groups" ]; 66 + pkce.enabled = true; 67 + }; 68 + 69 + derp.server = { 70 + enable = true; 71 + stun_listen_addr = "0.0.0.0:${toString headscale-derp-port}"; 72 + }; 73 + }; 74 + }; 75 + # headscale only starts if oidc is available 76 + systemd.services.headscale.after = [ "authelia-main.service" ]; 77 + 78 + services.caddy = { 79 + virtualHosts.${headscale-hostname}.extraConfig = '' 80 + reverse_proxy http://localhost:${toString headscale-port} 81 + ''; 82 + }; 83 + }; 84 + }
+42
hosts/weird-row-server/hypixel-bank-tracker.nix
··· 1 + { self 2 + , config 3 + , ... 4 + }: 5 + 6 + let 7 + inherit (self.inputs) hypixel-bank-tracker; 8 + 9 + hbt-main-port = 3013; 10 + hbt-banana-port = 3014; 11 + in 12 + { 13 + imports = [ hypixel-bank-tracker.nixosModules.default ]; 14 + 15 + config = { 16 + age.secrets.hypixel-bank-tracker-main.file = secrets/hypixel-bank-tracker-main.age; 17 + services.hypixel-bank-tracker.instances.main = { 18 + enable = true; 19 + 20 + port = hbt-main-port; 21 + environmentFile = config.age.secrets.hypixel-bank-tracker-main.path; 22 + }; 23 + 24 + age.secrets.hypixel-bank-tracker-banana.file = secrets/hypixel-bank-tracker-banana.age; 25 + services.hypixel-bank-tracker.instances.banana = { 26 + enable = true; 27 + 28 + port = hbt-banana-port; 29 + environmentFile = config.age.secrets.hypixel-bank-tracker-banana.path; 30 + }; 31 + 32 + services.caddy = { 33 + virtualHosts."hypixel-bank-tracker.xyz".extraConfig = '' 34 + reverse_proxy http://localhost:${toString config.services.hypixel-bank-tracker.instances.main.port} 35 + ''; 36 + 37 + virtualHosts."banana.hypixel-bank-tracker.xyz".extraConfig = '' 38 + reverse_proxy http://localhost:${toString config.services.hypixel-bank-tracker.instances.banana.port} 39 + ''; 40 + }; 41 + }; 42 + }
+38
hosts/weird-row-server/lldap.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + lldap-port = 3007; 7 + lldap-hostname = "ldap.wiro.world"; 8 + in 9 + { 10 + config = { 11 + age.secrets.lldap-env.file = secrets/lldap-env.age; 12 + users.users.lldap = { isSystemUser = true; group = "lldap"; }; 13 + users.groups.lldap = { }; 14 + age.secrets.lldap-user-pass = { file = secrets/lldap-user-pass.age; owner = "lldap"; }; 15 + services.lldap = { 16 + enable = true; 17 + 18 + silenceForceUserPassResetWarning = true; 19 + 20 + settings = { 21 + http_url = "https://${lldap-hostname}"; 22 + http_port = lldap-port; 23 + 24 + ldap_user_pass_file = config.age.secrets.lldap-user-pass.path; 25 + force_ldap_user_pass_reset = false; 26 + 27 + ldap_base_dn = "dc=wiro,dc=world"; 28 + }; 29 + environmentFile = config.age.secrets.lldap-env.path; 30 + }; 31 + 32 + services.caddy = { 33 + virtualHosts.${lldap-hostname}.extraConfig = '' 34 + reverse_proxy http://localhost:${toString lldap-port} 35 + ''; 36 + }; 37 + }; 38 + }
+52
hosts/weird-row-server/miniflux.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + miniflux-port = 3012; 7 + miniflux-hostname = "news.wiro.world"; 8 + in 9 + { 10 + config = { 11 + users.users.miniflux = { isSystemUser = true; group = "miniflux"; }; 12 + users.groups.miniflux = { }; 13 + age.secrets.miniflux-oidc-secret = { file = secrets/miniflux-oidc-secret.age; owner = "miniflux"; }; 14 + services.miniflux = { 15 + enable = true; 16 + 17 + createDatabaseLocally = true; 18 + config = { 19 + BASE_URL = "https://${miniflux-hostname}/"; 20 + LISTEN_ADDR = "127.0.0.1:${toString miniflux-port}"; 21 + CREATE_ADMIN = 0; 22 + 23 + METRICS_COLLECTOR = 1; 24 + 25 + OAUTH2_PROVIDER = "oidc"; 26 + OAUTH2_OIDC_PROVIDER_NAME = "wiro.world SSO"; 27 + OAUTH2_CLIENT_ID = "miniflux"; 28 + OAUTH2_CLIENT_SECRET_FILE = config.age.secrets.miniflux-oidc-secret.path; 29 + OAUTH2_REDIRECT_URL = "https://${miniflux-hostname}/oauth2/oidc/callback"; 30 + OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://auth.wiro.world"; 31 + OAUTH2_USER_CREATION = 1; 32 + DISABLE_LOCAL_AUTH = 1; 33 + 34 + RUN_MIGRATIONS = 1; 35 + 36 + # NetNewsWire is a very good iOS oss client that integrates well 37 + # https://b.j4.lc/2025/05/05/setting-up-netnewswire-with-miniflux/ 38 + }; 39 + }; 40 + 41 + services.prometheus.scrapeConfigs = [{ 42 + job_name = "miniflux"; 43 + static_configs = [{ targets = [ "localhost:${toString miniflux-port}" ]; }]; 44 + }]; 45 + 46 + services.caddy = { 47 + virtualHosts.${miniflux-hostname}.extraConfig = '' 48 + reverse_proxy http://localhost:${toString miniflux-port} 49 + ''; 50 + }; 51 + }; 52 + }
+43
hosts/weird-row-server/pds.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + pds-port = 3001; 7 + pds-hostname = "pds.wiro.world"; 8 + in 9 + { 10 + config = { 11 + age.secrets.pds-env.file = secrets/pds-env.age; 12 + services.bluesky-pds = { 13 + enable = true; 14 + 15 + settings = { 16 + PDS_HOSTNAME = "pds.wiro.world"; 17 + PDS_PORT = pds-port; 18 + # is in systemd /tmp subfolder 19 + LOG_DESTINATION = "/tmp/pds.log"; 20 + }; 21 + 22 + environmentFiles = [ 23 + config.age.secrets.pds-env.path 24 + ]; 25 + }; 26 + 27 + services.caddy = { 28 + globalConfig = '' 29 + on_demand_tls { 30 + ask http://localhost:${toString pds-port}/tls-check 31 + } 32 + ''; 33 + 34 + virtualHosts.${pds-hostname} = { 35 + serverAliases = [ "*.${pds-hostname}" ]; 36 + extraConfig = '' 37 + tls { on_demand } 38 + reverse_proxy http://localhost:${toString pds-port} 39 + ''; 40 + }; 41 + }; 42 + }; 43 + }
+54
hosts/weird-row-server/tangled.nix
··· 1 + { self 2 + , config 3 + , ... 4 + }: 5 + 6 + let 7 + inherit (self.inputs) tangled; 8 + 9 + tangled-owner = "did:plc:xhgrjm4mcx3p5h3y6eino6ti"; 10 + tangled-knot-port = 3003; 11 + tangled-knot-hostname = "knot.wiro.world"; 12 + tangled-spindle-port = 3004; 13 + tangled-spindle-hostname = "spindle.wiro.world"; 14 + in 15 + { 16 + imports = [ 17 + tangled.nixosModules.knot 18 + tangled.nixosModules.spindle 19 + ]; 20 + 21 + config = { 22 + services.tangled.knot = { 23 + enable = true; 24 + openFirewall = true; 25 + 26 + motd = "Welcome to @wiro.world's knot!\n"; 27 + server = { 28 + listenAddr = "localhost:${toString tangled-knot-port}"; 29 + hostname = tangled-knot-hostname; 30 + owner = tangled-owner; 31 + }; 32 + }; 33 + 34 + services.tangled.spindle = { 35 + enable = true; 36 + 37 + server = { 38 + listenAddr = "localhost:${toString tangled-spindle-port}"; 39 + hostname = tangled-spindle-hostname; 40 + owner = tangled-owner; 41 + }; 42 + }; 43 + 44 + services.caddy = { 45 + virtualHosts.${tangled-knot-hostname}.extraConfig = '' 46 + reverse_proxy http://localhost:${toString tangled-knot-port} 47 + ''; 48 + 49 + virtualHosts.${tangled-spindle-hostname}.extraConfig = '' 50 + reverse_proxy http://localhost:${toString tangled-spindle-port} 51 + ''; 52 + }; 53 + }; 54 + }
+30
hosts/weird-row-server/thelounge.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + thelounge-port = 3005; 7 + thelounge-hostname = "lounge.wiro.world"; 8 + in 9 + { 10 + config = { 11 + services.thelounge = { 12 + enable = true; 13 + port = thelounge-port; 14 + public = false; 15 + 16 + extraConfig = { 17 + host = "127.0.0.1"; 18 + reverseProxy = true; 19 + 20 + # TODO: use ldap, find a way to hide password 21 + }; 22 + }; 23 + 24 + services.caddy = { 25 + virtualHosts.${thelounge-hostname}.extraConfig = '' 26 + reverse_proxy http://localhost:${toString thelounge-port} 27 + ''; 28 + }; 29 + }; 30 + }
+45
hosts/weird-row-server/tuwunel.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + matrix-port = 3009; 7 + matrix-hostname = "matrix.wiro.world"; 8 + 9 + website-hostname = "wiro.world"; 10 + in 11 + { 12 + config = { 13 + age.secrets.tuwunel-registration-tokens = { file = secrets/tuwunel-registration-tokens.age; owner = config.services.matrix-tuwunel.user; }; 14 + services.matrix-tuwunel = { 15 + enable = true; 16 + 17 + settings.global = { 18 + address = [ "127.0.0.1" ]; 19 + port = [ matrix-port ]; 20 + 21 + server_name = "wiro.world"; 22 + well_known = { 23 + client = "https://matrix.wiro.world"; 24 + server = "matrix.wiro.world:443"; 25 + }; 26 + 27 + grant_admin_to_first_user = true; 28 + new_user_displayname_suffix = ""; 29 + 30 + allow_registration = true; 31 + registration_token_file = config.age.secrets.tuwunel-registration-tokens.path; 32 + }; 33 + }; 34 + 35 + services.caddy = { 36 + virtualHosts.${matrix-hostname}.extraConfig = '' 37 + reverse_proxy /_matrix/* http://localhost:${toString matrix-port} 38 + ''; 39 + 40 + virtualHosts.${website-hostname}.extraConfig = '' 41 + reverse_proxy /.well-known/matrix/* http://localhost:${toString matrix-port} 42 + ''; 43 + }; 44 + }; 45 + }
+38
hosts/weird-row-server/vaultwarden.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + vaultwarden-port = 3011; 7 + vaultwarden-hostname = "vault.wiro.world"; 8 + in 9 + { 10 + config = { 11 + age.secrets.vaultwarden-env.file = secrets/vaultwarden-env.age; 12 + services.vaultwarden = { 13 + enable = true; 14 + 15 + environmentFile = config.age.secrets.vaultwarden-env.path; 16 + config = { 17 + ROCKET_PORT = vaultwarden-port; 18 + DOMAIN = "https://${vaultwarden-hostname}"; 19 + SIGNUPS_ALLOWED = false; 20 + ADMIN_TOKEN = "$argon2id$v=19$m=65540,t=3,p=4$YIe9wmrTsmjgZNPxe8m34O/d3XW3Fl/uZPPLQs79dAc$mjDVQSdBJqz2uBJuxtAvCIoHPzOnTDhNPuhER3dhHrY"; 21 + 22 + SMTP_HOST = "smtp.resend.com"; 23 + SMTP_PORT = 2465; 24 + SMTP_SECURITY = "force_tls"; 25 + SMTP_USERNAME = "resend"; 26 + # SMTP_PASSWORD = ...; # Via secret env 27 + SMTP_FROM = "bitwarden@wiro.world"; 28 + SMTP_FROM_NAME = "Bitwarden wiro.world"; 29 + }; 30 + }; 31 + 32 + services.caddy = { 33 + virtualHosts.${vaultwarden-hostname}.extraConfig = '' 34 + reverse_proxy http://localhost:${toString vaultwarden-port} 35 + ''; 36 + }; 37 + }; 38 + }
+28
hosts/weird-row-server/warrior.nix
··· 1 + { config 2 + , ... 3 + }: 4 + 5 + let 6 + warrior-port = 3015; 7 + warrior-hostname = "warrior.wiro.world"; 8 + 9 + authelia-port = 3008; 10 + in 11 + { 12 + config = { 13 + virtualisation.oci-containers.containers.archive-warrior = { 14 + image = "atdr.meo.ws/archiveteam/warrior-dockerfile"; 15 + ports = [ "127.0.0.1:${toString warrior-port}:8001" ]; 16 + pull = "newer"; 17 + }; 18 + 19 + services.caddy = { 20 + virtualHosts.${warrior-hostname}.extraConfig = '' 21 + forward_auth localhost:${toString authelia-port} { 22 + uri /api/authz/forward-auth 23 + } 24 + reverse_proxy http://localhost:${toString warrior-port} 25 + ''; 26 + }; 27 + }; 28 + }
+77
hosts/weird-row-server/webfinger.nix
··· 1 + { pkgs 2 + , config 3 + , ... 4 + }: 5 + 6 + let 7 + webfinger-dir = pkgs.writeTextDir ".well-known/webfinger" '' 8 + { 9 + "subject": "acct:milo@wiro.world", 10 + "aliases": [ 11 + "mailto:milo@wiro.world", 12 + "https://wiro.world/" 13 + ], 14 + "links": [ 15 + { 16 + "rel": "http://wiro.world/rel/avatar", 17 + "href": "https://wiro.world/logo.jpg", 18 + "type": "image/jpeg" 19 + }, 20 + { 21 + "rel": "http://webfinger.net/rel/profile-page", 22 + "href": "https://wiro.world/", 23 + "type": "text/html" 24 + }, 25 + { 26 + "rel": "http://openid.net/specs/connect/1.0/issuer", 27 + "href": "https://auth.wiro.world" 28 + } 29 + ] 30 + } 31 + ''; 32 + 33 + well-known-discord-dir = pkgs.writeTextDir ".well-known/discord" '' 34 + dh=919234284ceb2aba439d15b9136073eb2308989b 35 + ''; 36 + 37 + website-hostname = "wiro.world"; 38 + in 39 + { 40 + config = { 41 + services.caddy = { 42 + virtualHosts.${website-hostname}.extraConfig = '' 43 + @webfinger { 44 + path /.well-known/webfinger 45 + method GET HEAD 46 + query resource=acct:milo@wiro.world 47 + query resource=mailto:milo@wiro.world 48 + query resource=https://wiro.world 49 + query resource=https://wiro.world/ 50 + } 51 + route @webfinger { 52 + header { 53 + Content-Type "application/jrd+json" 54 + Access-Control-Allow-Origin "*" 55 + X-Robots-Tag "noindex" 56 + } 57 + root ${webfinger-dir} 58 + file_server 59 + } 60 + '' + 61 + '' 62 + @discord { 63 + path /.well-known/discord 64 + method GET HEAD 65 + } 66 + route @discord { 67 + header { 68 + Access-Control-Allow-Origin "*" 69 + X-Robots-Tag "noindex" 70 + } 71 + root ${well-known-discord-dir} 72 + file_server 73 + } 74 + ''; 75 + }; 76 + }; 77 + }
+8 -1
lib/flake/default.nix
··· 44 44 ../../nixos/hardware/${hostName}.nix 45 45 ../../nixos/profiles/${profile}.nix 46 46 ]; 47 - networking.hostName = hostName; 47 + config.networking.hostName = hostName; 48 + }; 49 + host = hostName: { 50 + imports = [ 51 + ../../nixos/hardware/${hostName}.nix 52 + ../../hosts/${hostName}/default.nix 53 + ]; 54 + config.networking.hostName = hostName; 48 55 }; 49 56 user = import ./user.nix; 50 57 managedDiskLayout = import ./managedDiskLayout.nix;
+1 -1
lib/flake/managedDiskLayout.nix
··· 26 26 27 27 The recommended amount from RedHat is: 28 28 29 - Amount of RAM Recommended swap space Recommended swap space 29 + Amount of RAM Recommended swap space Recommended swap space 30 30 in the system if allowing for hibernation 31 31 —————————————— —————————————————————————— ——————————————————————————— 32 32 ⩽ 2 GB 2 times the amount of RAM 3 times the amount of RAM
+1 -1
modules/home-manager/xcompose.nix
··· 14 14 loadConfigInEnv = mkOption { 15 15 description = '' 16 16 Load the XCompose file by passing the `XCOMPOSEFILE` environment variable instead of linking to ~/.XCompose. 17 - 17 + 18 18 That is nice to avoid cluttering the HOME directory, it's preferable to disable it when experimenting 19 19 with your compose config to reload faster than having to reload your VM. 20 20 '';
+1 -1
nixos/fragments/kanata/arsenik.kbd.lisp
··· 59 59 ;; Numrow layer 60 60 (deflayer numrow 61 61 XX XX XX XX XX XX XX XX XX XX XX 62 - XX XX XX XX XX XX XX XX XX XX 62 + XX XX XX XX XX XX XX XX XX XX 63 63 @1 @2 @3 @4 @5 @6 @7 @8 @9 @0 64 64 XX XX XX XX XX XX XX XX XX XX XX 65 65 XX XX XX XX XX
+2 -2
nixos/fragments/logiops.nix
··· 23 23 let 24 24 cid = { 25 25 # Control IDs │ reprog? │ fn key? │ mouse key? │ gesture support? 26 - leftMouse = 80; # 0x50 │ │ │ YES │ 27 - rightMouse = 81; # 0x51 │ │ │ YES │ 26 + leftMouse = 80; # 0x50 │ │ │ YES │ 27 + rightMouse = 81; # 0x51 │ │ │ YES │ 28 28 middleMouse = 81; # 0x52 │ YES │ │ YES │ YES 29 29 back = 83; # 0x53 │ YES │ │ YES │ YES 30 30 forward = 86; # 0x56 │ YES │ │ YES │ YES
+1 -1
nixos/fragments/security.nix
··· 20 20 security.polkit.enable = true; 21 21 security.rtkit.enable = true; 22 22 23 - # Systemd Login 23 + # Systemd Login 24 24 services.logind.settings.Login = { 25 25 HandleLidSwitch = "suspend"; 26 26 IdleAction = "lock";
+1 -1
nixos/profiles/laptop.nix
··· 91 91 }; 92 92 users.users.${config.local.user.username}.extraGroups = [ "wireshark" "plugdev" ]; 93 93 94 - # This option is already filled with aliases that snowball and have 94 + # This option is already filled with aliases that snowball and have 95 95 # priority on fish internal `ls` aliases 96 96 environment.shellAliases = { ls = null; ll = null; l = null; }; 97 97 programs.fish.enable = true;
-724
nixos/profiles/server.nix
··· 1 - { self 2 - , config 3 - , pkgs 4 - , ... 5 - }: 6 - 7 - let 8 - inherit (self.inputs) srvos hypixel-bank-tracker tangled; 9 - 10 - json-format = pkgs.formats.json { }; 11 - 12 - ext-if = "eth0"; 13 - external-ip = "91.99.55.74"; 14 - external-netmask = 27; 15 - external-gw = "144.x.x.255"; 16 - external-ip6 = "2a01:4f8:c2c:76d2::1"; 17 - external-netmask6 = 64; 18 - external-gw6 = "fe80::1"; 19 - 20 - well-known-discord-dir = pkgs.writeTextDir ".well-known/discord" '' 21 - dh=919234284ceb2aba439d15b9136073eb2308989b 22 - ''; 23 - webfinger-dir = pkgs.writeTextDir ".well-known/webfinger" '' 24 - { 25 - "subject": "acct:milo@wiro.world", 26 - "aliases": [ 27 - "mailto:milo@wiro.world", 28 - "https://wiro.world/" 29 - ], 30 - "links": [ 31 - { 32 - "rel": "http://wiro.world/rel/avatar", 33 - "href": "https://wiro.world/logo.jpg", 34 - "type": "image/jpeg" 35 - }, 36 - { 37 - "rel": "http://webfinger.net/rel/profile-page", 38 - "href": "https://wiro.world/", 39 - "type": "text/html" 40 - }, 41 - { 42 - "rel": "http://openid.net/specs/connect/1.0/issuer", 43 - "href": "https://auth.wiro.world" 44 - } 45 - ] 46 - } 47 - ''; 48 - 49 - website-hostname = "wiro.world"; 50 - 51 - pds-port = 3001; 52 - pds-hostname = "pds.wiro.world"; 53 - 54 - grafana-port = 3002; 55 - grafana-hostname = "console.wiro.world"; 56 - 57 - tangled-owner = "did:plc:xhgrjm4mcx3p5h3y6eino6ti"; 58 - tangled-knot-port = 3003; 59 - tangled-knot-hostname = "knot.wiro.world"; 60 - tangled-spindle-port = 3004; 61 - tangled-spindle-hostname = "spindle.wiro.world"; 62 - 63 - thelounge-port = 3005; 64 - thelounge-hostname = "lounge.wiro.world"; 65 - 66 - headscale-port = 3006; 67 - headscale-derp-port = 3478; 68 - headscale-hostname = "headscale.wiro.world"; 69 - 70 - lldap-port = 3007; 71 - lldap-hostname = "ldap.wiro.world"; 72 - 73 - authelia-port = 3008; 74 - authelia-hostname = "auth.wiro.world"; 75 - 76 - matrix-port = 3009; 77 - matrix-hostname = "matrix.wiro.world"; 78 - 79 - goatcounter-port = 3010; 80 - goatcounter-hostname = "stats.wiro.world"; 81 - 82 - vaultwarden-port = 3011; 83 - vaultwarden-hostname = "vault.wiro.world"; 84 - 85 - miniflux-port = 3012; 86 - miniflux-hostname = "news.wiro.world"; 87 - 88 - static-hostname = "static.wiro.world"; 89 - 90 - hbt-main-port = 3013; 91 - hbt-banana-port = 3014; 92 - 93 - warrior-port = 3015; 94 - warrior-hostname = "warrior.wiro.world"; 95 - 96 - prometheus-port = 9001; 97 - prometheus-node-exporter-port = 9002; 98 - headscale-metrics-port = 9003; 99 - authelia-metrics-port = 9004; 100 - in 101 - { 102 - imports = [ 103 - srvos.nixosModules.server 104 - srvos.nixosModules.hardware-hetzner-cloud 105 - srvos.nixosModules.mixins-terminfo 106 - 107 - hypixel-bank-tracker.nixosModules.default 108 - 109 - tangled.nixosModules.knot 110 - tangled.nixosModules.spindle 111 - ]; 112 - 113 - config = { 114 - boot.loader.grub.enable = true; 115 - boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" "ext4" ]; 116 - 117 - # Single network card is `eth0` 118 - networking.usePredictableInterfaceNames = false; 119 - 120 - networking.nameservers = [ "2001:4860:4860::8888" "2001:4860:4860::8844" ]; 121 - 122 - networking = { 123 - interfaces.${ext-if} = { 124 - ipv4.addresses = [{ address = external-ip; prefixLength = external-netmask; }]; 125 - ipv6.addresses = [{ address = external-ip6; prefixLength = external-netmask6; }]; 126 - }; 127 - defaultGateway = { interface = ext-if; address = external-gw; }; 128 - defaultGateway6 = { interface = ext-if; address = external-gw6; }; 129 - 130 - # Reflect firewall configuration on Hetzner 131 - firewall.allowedTCPPorts = [ 22 80 443 ]; 132 - firewall.allowedUDPPorts = [ headscale-derp-port ]; 133 - }; 134 - 135 - services.qemuGuest.enable = true; 136 - 137 - services.openssh.enable = true; 138 - 139 - services.tailscale.enable = true; 140 - 141 - security.sudo.wheelNeedsPassword = false; 142 - 143 - local.fragment.nix.enable = true; 144 - 145 - programs.fish.enable = true; 146 - 147 - services.fail2ban = { 148 - enable = true; 149 - 150 - maxretry = 5; 151 - ignoreIP = [ ]; 152 - 153 - bantime = "24h"; 154 - bantime-increment = { 155 - enable = true; 156 - multipliers = "1 2 4 8 16 32 64"; 157 - maxtime = "168h"; 158 - overalljails = true; 159 - }; 160 - 161 - jails = { }; 162 - }; 163 - 164 - services.caddy = { 165 - enable = true; 166 - 167 - globalConfig = '' 168 - metrics { per_host } 169 - 170 - on_demand_tls { 171 - ask http://localhost:${toString pds-port}/tls-check 172 - } 173 - ''; 174 - 175 - virtualHosts.${website-hostname}.extraConfig = 176 - '' 177 - @discord { 178 - path /.well-known/discord 179 - method GET HEAD 180 - } 181 - route @discord { 182 - header { 183 - Access-Control-Allow-Origin "*" 184 - X-Robots-Tag "noindex" 185 - } 186 - root ${well-known-discord-dir} 187 - file_server 188 - } 189 - '' + 190 - '' 191 - @webfinger { 192 - path /.well-known/webfinger 193 - method GET HEAD 194 - query resource=acct:milo@wiro.world 195 - query resource=mailto:milo@wiro.world 196 - query resource=https://wiro.world 197 - query resource=https://wiro.world/ 198 - } 199 - route @webfinger { 200 - header { 201 - Content-Type "application/jrd+json" 202 - Access-Control-Allow-Origin "*" 203 - X-Robots-Tag "noindex" 204 - } 205 - root ${webfinger-dir} 206 - file_server 207 - } 208 - '' + 209 - '' 210 - reverse_proxy /.well-known/matrix/* http://localhost:${toString matrix-port} 211 - '' + 212 - # TODO: host website on server with automatic deployment 213 - '' 214 - reverse_proxy https://mrnossiom.github.io { 215 - header_up Host {http.request.host} 216 - } 217 - ''; 218 - 219 - virtualHosts.${grafana-hostname}.extraConfig = '' 220 - reverse_proxy http://localhost:${toString grafana-port} 221 - ''; 222 - 223 - virtualHosts.${pds-hostname} = { 224 - serverAliases = [ "*.${pds-hostname}" ]; 225 - extraConfig = '' 226 - tls { on_demand } 227 - reverse_proxy http://localhost:${toString pds-port} 228 - ''; 229 - }; 230 - 231 - virtualHosts.${tangled-knot-hostname}.extraConfig = '' 232 - reverse_proxy http://localhost:${toString tangled-knot-port} 233 - ''; 234 - 235 - virtualHosts.${tangled-spindle-hostname}.extraConfig = '' 236 - reverse_proxy http://localhost:${toString tangled-spindle-port} 237 - ''; 238 - 239 - virtualHosts.${thelounge-hostname}.extraConfig = '' 240 - reverse_proxy http://localhost:${toString thelounge-port} 241 - ''; 242 - 243 - virtualHosts.${headscale-hostname}.extraConfig = '' 244 - reverse_proxy http://localhost:${toString headscale-port} 245 - ''; 246 - 247 - virtualHosts.${lldap-hostname}.extraConfig = '' 248 - reverse_proxy http://localhost:${toString lldap-port} 249 - ''; 250 - 251 - virtualHosts.${authelia-hostname}.extraConfig = '' 252 - reverse_proxy http://localhost:${toString authelia-port} 253 - ''; 254 - 255 - virtualHosts.${matrix-hostname}.extraConfig = '' 256 - reverse_proxy /_matrix/* http://localhost:${toString matrix-port} 257 - ''; 258 - 259 - virtualHosts.${goatcounter-hostname}.extraConfig = '' 260 - reverse_proxy http://localhost:${toString goatcounter-port} 261 - ''; 262 - 263 - virtualHosts.${vaultwarden-hostname}.extraConfig = '' 264 - reverse_proxy http://localhost:${toString vaultwarden-port} 265 - ''; 266 - 267 - virtualHosts.${miniflux-hostname}.extraConfig = '' 268 - reverse_proxy http://localhost:${toString miniflux-port} 269 - ''; 270 - 271 - virtualHosts.${static-hostname}.extraConfig = '' 272 - root /var/www/static 273 - file_server browse 274 - ''; 275 - 276 - virtualHosts."hypixel-bank-tracker.xyz".extraConfig = '' 277 - reverse_proxy http://localhost:${toString hbt-main-port} 278 - ''; 279 - virtualHosts."banana.hypixel-bank-tracker.xyz".extraConfig = '' 280 - reverse_proxy http://localhost:${toString hbt-banana-port} 281 - ''; 282 - 283 - virtualHosts.${warrior-hostname}.extraConfig = '' 284 - forward_auth localhost:${toString authelia-port} { 285 - uri /api/authz/forward-auth 286 - } 287 - reverse_proxy http://localhost:${toString warrior-port} 288 - ''; 289 - }; 290 - 291 - age.secrets.pds-env.file = ../../secrets/pds-env.age; 292 - services.bluesky-pds = { 293 - enable = true; 294 - 295 - settings = { 296 - PDS_HOSTNAME = "pds.wiro.world"; 297 - PDS_PORT = pds-port; 298 - # is in systemd /tmp subfolder 299 - LOG_DESTINATION = "/tmp/pds.log"; 300 - }; 301 - 302 - environmentFiles = [ 303 - config.age.secrets.pds-env.path 304 - ]; 305 - }; 306 - 307 - services.tangled.knot = { 308 - enable = true; 309 - openFirewall = true; 310 - 311 - motd = "Welcome to @wiro.world's knot!\n"; 312 - server = { 313 - listenAddr = "localhost:${toString tangled-knot-port}"; 314 - hostname = tangled-knot-hostname; 315 - owner = tangled-owner; 316 - }; 317 - }; 318 - 319 - services.tangled.spindle = { 320 - enable = true; 321 - 322 - server = { 323 - listenAddr = "localhost:${toString tangled-spindle-port}"; 324 - hostname = tangled-spindle-hostname; 325 - owner = tangled-owner; 326 - }; 327 - }; 328 - 329 - age.secrets.grafana-oidc-secret = { file = ../../secrets/grafana-oidc-secret.age; owner = "grafana"; }; 330 - services.grafana = { 331 - enable = true; 332 - 333 - settings = { 334 - server = { 335 - http_port = grafana-port; 336 - domain = grafana-hostname; 337 - root_url = "https://${grafana-hostname}"; 338 - }; 339 - 340 - "auth.generic_oauth" = { 341 - enable = true; 342 - name = "Authelia"; 343 - icon = "signin"; 344 - 345 - client_id = "grafana"; 346 - client_secret_path = config.age.secrets.grafana-oidc-secret.path; 347 - auto_login = true; 348 - 349 - scopes = [ "openid" "profile" "email" "groups" ]; 350 - auth_url = "https://auth.wiro.world/api/oidc/authorization"; 351 - token_url = "https://auth.wiro.world/api/oidc/token"; 352 - api_url = "https://auth.wiro.world/api/oidc/userinfo"; 353 - use_pkce = true; 354 - }; 355 - }; 356 - }; 357 - 358 - services.prometheus = { 359 - enable = true; 360 - port = prometheus-port; 361 - 362 - scrapeConfigs = [ 363 - { 364 - job_name = "caddy"; 365 - static_configs = [{ targets = [ "localhost:${toString 2019}" ]; }]; 366 - } 367 - { 368 - job_name = "node-exporter"; 369 - static_configs = [{ targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ]; }]; 370 - } 371 - { 372 - job_name = "headscale"; 373 - static_configs = [{ targets = [ "localhost:${toString headscale-metrics-port}" ]; }]; 374 - } 375 - { 376 - job_name = "authelia"; 377 - static_configs = [{ targets = [ "localhost:${toString authelia-metrics-port}" ]; }]; 378 - } 379 - { 380 - job_name = "miniflux"; 381 - static_configs = [{ targets = [ "localhost:${toString miniflux-port}" ]; }]; 382 - } 383 - ]; 384 - 385 - exporters.node = { 386 - enable = true; 387 - port = prometheus-node-exporter-port; 388 - }; 389 - }; 390 - 391 - services.thelounge = { 392 - enable = true; 393 - port = thelounge-port; 394 - public = false; 395 - 396 - extraConfig = { 397 - host = "127.0.0.1"; 398 - reverseProxy = true; 399 - 400 - # TODO: use ldap, find a way to hide password 401 - }; 402 - }; 403 - 404 - age.secrets.headscale-oidc-secret = { file = ../../secrets/headscale-oidc-secret.age; owner = config.services.headscale.user; }; 405 - # TODO: add dependency on authelia 406 - services.headscale = { 407 - enable = true; 408 - 409 - port = headscale-port; 410 - settings = { 411 - server_url = "https://${headscale-hostname}"; 412 - metrics_listen_addr = "127.0.0.1:${toString headscale-metrics-port}"; 413 - 414 - policy.path = json-format.generate "policy.json" { 415 - acls = [ 416 - { 417 - action = "accept"; 418 - src = [ "autogroup:member" ]; 419 - dst = [ "autogroup:self:*" ]; 420 - } 421 - ]; 422 - ssh = [ 423 - { 424 - action = "accept"; 425 - src = [ "autogroup:member" ]; 426 - dst = [ "autogroup:self" ]; 427 - # Adding root here is privilege escalation as a feature :) 428 - users = [ "autogroup:nonroot" ]; 429 - } 430 - ]; 431 - }; 432 - 433 - # disable TLS 434 - tls_cert_path = null; 435 - tls_key_path = null; 436 - 437 - dns = { 438 - magic_dns = true; 439 - base_domain = "net.wiro.world"; 440 - 441 - override_local_dns = true; 442 - # Quad9 nameservers 443 - nameservers.global = [ "9.9.9.9" "149.112.112.112" "2620:fe::fe" "2620:fe::9" ]; 444 - }; 445 - 446 - oidc = { 447 - only_start_if_oidc_is_available = true; 448 - issuer = "https://auth.wiro.world"; 449 - client_id = "headscale"; 450 - client_secret_path = config.age.secrets.headscale-oidc-secret.path; 451 - scope = [ "openid" "profile" "email" "groups" ]; 452 - pkce.enabled = true; 453 - }; 454 - 455 - derp.server = { 456 - enable = true; 457 - stun_listen_addr = "0.0.0.0:${toString headscale-derp-port}"; 458 - }; 459 - }; 460 - }; 461 - 462 - age.secrets.lldap-env.file = ../../secrets/lldap-env.age; 463 - users.users.lldap = { isSystemUser = true; group = "lldap"; }; 464 - users.groups.lldap = { }; 465 - age.secrets.lldap-user-pass = { file = ../../secrets/lldap-user-pass.age; owner = "lldap"; }; 466 - services.lldap = { 467 - enable = true; 468 - settings = { 469 - http_url = "https://${lldap-hostname}"; 470 - http_port = lldap-port; 471 - 472 - ldap_user_pass_file = config.age.secrets.lldap-user-pass.path; 473 - force_ldap_user_pass_reset = false; 474 - 475 - ldap_base_dn = "dc=wiro,dc=world"; 476 - }; 477 - environmentFile = config.age.secrets.lldap-env.path; 478 - }; 479 - 480 - age.secrets.authelia-jwt-secret = { file = ../../secrets/authelia-jwt-secret.age; owner = config.services.authelia.instances.main.user; }; 481 - age.secrets.authelia-issuer-private-key = { file = ../../secrets/authelia-issuer-private-key.age; owner = config.services.authelia.instances.main.user; }; 482 - age.secrets.authelia-storage-key = { file = ../../secrets/authelia-storage-key.age; owner = config.services.authelia.instances.main.user; }; 483 - age.secrets.authelia-ldap-password = { file = ../../secrets/authelia-ldap-password.age; owner = config.services.authelia.instances.main.user; }; 484 - age.secrets.authelia-smtp-password = { file = ../../secrets/authelia-smtp-password.age; owner = config.services.authelia.instances.main.user; }; 485 - services.authelia.instances.main = { 486 - enable = true; 487 - 488 - secrets = { 489 - jwtSecretFile = config.age.secrets.authelia-jwt-secret.path; 490 - oidcIssuerPrivateKeyFile = config.age.secrets.authelia-issuer-private-key.path; 491 - storageEncryptionKeyFile = config.age.secrets.authelia-storage-key.path; 492 - }; 493 - environmentVariables = { 494 - AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = config.age.secrets.authelia-ldap-password.path; 495 - AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = config.age.secrets.authelia-smtp-password.path; 496 - }; 497 - settings = { 498 - server.address = "localhost:${toString authelia-port}"; 499 - storage.local.path = "/var/lib/authelia-main/db.sqlite3"; 500 - telemetry.metrics = { 501 - enabled = true; 502 - address = "tcp://:${toString authelia-metrics-port}/metrics"; 503 - }; 504 - 505 - session = { 506 - cookies = [{ 507 - domain = "wiro.world"; 508 - authelia_url = "https://${authelia-hostname}"; 509 - default_redirection_url = "https://wiro.world"; 510 - }]; 511 - }; 512 - 513 - authentication_backend.ldap = { 514 - address = "ldap://localhost:3890"; 515 - timeout = "5m"; # replace with systemd dependency 516 - 517 - user = "uid=authelia,ou=people,dc=wiro,dc=world"; 518 - # Set in `AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE`. 519 - # password = ""; 520 - 521 - base_dn = "dc=wiro,dc=world"; 522 - users_filter = "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))"; 523 - additional_users_dn = "ou=people"; 524 - groups_filter = "(&(member={dn})(objectClass=groupOfNames))"; 525 - additional_groups_dn = "ou=groups"; 526 - 527 - attributes = { 528 - username = "uid"; 529 - display_name = "cn"; 530 - given_name = "givenname"; 531 - family_name = "last_name"; 532 - mail = "mail"; 533 - picture = "avatar"; 534 - 535 - group_name = "cn"; 536 - }; 537 - }; 538 - 539 - access_control = { 540 - default_policy = "deny"; 541 - # Rules are sequential and do not apply to OIDC 542 - rules = [ 543 - { 544 - domain = "headscale.wiro.world"; 545 - policy = "two_factor"; 546 - 547 - } 548 - { 549 - domain = "news.wiro.world"; 550 - policy = "one_factor"; 551 - 552 - subject = [ [ "group:miniflux" "oauth2:client:miniflux" ] ]; 553 - } 554 - { 555 - domain = "*.wiro.world"; 556 - policy = "two_factor"; 557 - } 558 - ]; 559 - }; 560 - 561 - identity_providers.oidc = { 562 - enforce_pkce = "always"; 563 - 564 - authorization_policies = 565 - let 566 - mkStrictPolicy = policy: subject: 567 - { default_policy = "deny"; rules = [{ inherit policy subject; }]; }; 568 - in 569 - { 570 - headscale = mkStrictPolicy "two_factor" [ "group:headscale" ]; 571 - tailscale = mkStrictPolicy "two_factor" [ "group:headscale" ]; 572 - grafana = mkStrictPolicy "one_factor" [ "group:grafana" ]; 573 - miniflux = mkStrictPolicy "one_factor" [ "group:miniflux" ]; 574 - }; 575 - 576 - claims_policies.headscale = { id_token = [ "email" "name" "preferred_username" "picture" "groups" ]; }; 577 - 578 - clients = [ 579 - { 580 - client_name = "Headscale"; 581 - client_id = "headscale"; 582 - client_secret = "$pbkdf2-sha256$310000$XY680D9gkSoWhD0UtYHNFg$ptWB3exOYCga6uq1N.oimuV3ILjK3F8lBWBpsBpibos"; 583 - redirect_uris = [ "https://${headscale-hostname}/oidc/callback" ]; 584 - authorization_policy = "headscale"; 585 - claims_policy = "headscale"; 586 - } 587 - { 588 - client_name = "Tailscale"; 589 - client_id = "tailscale"; 590 - client_secret = "$pbkdf2-sha256$310000$PcUaup9aWKI9ZLeCF6.avw$FpsTxkDaxcoQlBi8aIacegXpjEDiCI6nXcaHyZ2Sxyc"; 591 - redirect_uris = [ "https://login.tailscale.com/a/oauth_response" ]; 592 - authorization_policy = "tailscale"; 593 - } 594 - { 595 - client_name = "Grafana Console"; 596 - client_id = "grafana"; 597 - client_secret = "$pbkdf2-sha256$310000$UkwrqxTZodGMs9.Ca2cXAA$HCWFgQbFHGXZpuz.I3HHdkTZLUevRVGlhKEFaOlPmKs"; 598 - redirect_uris = [ "https://${grafana-hostname}/login/generic_oauth" ]; 599 - authorization_policy = "grafana"; 600 - } 601 - { 602 - client_name = "Miniflux"; 603 - client_id = "miniflux"; 604 - client_secret = "$pbkdf2-sha256$310000$uPqbWfCOBXDY6nV1vsx3uA$HOWG2hL.c/bs9Dwaee3b9DxjH7KFO.SaZMbasXV9Vdw"; 605 - redirect_uris = [ "https://${miniflux-hostname}/oauth2/oidc/callback" ]; 606 - authorization_policy = "miniflux"; 607 - } 608 - ]; 609 - }; 610 - 611 - notifier.smtp = { 612 - address = "smtp://smtp.resend.com:2587"; 613 - username = "resend"; 614 - # Set in `AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE`. 615 - # password = ""; 616 - sender = "authelia@wiro.world"; 617 - }; 618 - }; 619 - }; 620 - 621 - age.secrets.tuwunel-registration-tokens = { file = ../../secrets/tuwunel-registration-tokens.age; owner = config.services.matrix-tuwunel.user; }; 622 - services.matrix-tuwunel = { 623 - enable = true; 624 - 625 - settings.global = { 626 - address = [ "127.0.0.1" ]; 627 - port = [ matrix-port ]; 628 - 629 - server_name = "wiro.world"; 630 - well_known = { 631 - client = "https://matrix.wiro.world"; 632 - server = "matrix.wiro.world:443"; 633 - }; 634 - 635 - grant_admin_to_first_user = true; 636 - new_user_displayname_suffix = ""; 637 - 638 - allow_registration = true; 639 - registration_token_file = config.age.secrets.tuwunel-registration-tokens.path; 640 - }; 641 - }; 642 - 643 - services.goatcounter = { 644 - enable = true; 645 - 646 - port = goatcounter-port; 647 - proxy = true; 648 - extraArgs = [ "-automigrate" ]; 649 - }; 650 - 651 - age.secrets.vaultwarden-env.file = ../../secrets/vaultwarden-env.age; 652 - services.vaultwarden = { 653 - enable = true; 654 - 655 - environmentFile = config.age.secrets.vaultwarden-env.path; 656 - config = { 657 - ROCKET_PORT = vaultwarden-port; 658 - DOMAIN = "https://${vaultwarden-hostname}"; 659 - SIGNUPS_ALLOWED = false; 660 - ADMIN_TOKEN = "$argon2id$v=19$m=65540,t=3,p=4$YIe9wmrTsmjgZNPxe8m34O/d3XW3Fl/uZPPLQs79dAc$mjDVQSdBJqz2uBJuxtAvCIoHPzOnTDhNPuhER3dhHrY"; 661 - 662 - SMTP_HOST = "smtp.resend.com"; 663 - SMTP_PORT = 2465; 664 - SMTP_SECURITY = "force_tls"; 665 - SMTP_USERNAME = "resend"; 666 - # SMTP_PASSWORD = ...; # Via secret env 667 - SMTP_FROM = "bitwarden@wiro.world"; 668 - SMTP_FROM_NAME = "Bitwarden wiro.world"; 669 - }; 670 - }; 671 - 672 - users.users.miniflux = { isSystemUser = true; group = "miniflux"; }; 673 - users.groups.miniflux = { }; 674 - age.secrets.miniflux-oidc-secret = { file = ../../secrets/miniflux-oidc-secret.age; owner = "miniflux"; }; 675 - services.miniflux = { 676 - enable = true; 677 - 678 - createDatabaseLocally = true; 679 - config = { 680 - BASE_URL = "https://${miniflux-hostname}/"; 681 - LISTEN_ADDR = "127.0.0.1:${toString miniflux-port}"; 682 - CREATE_ADMIN = 0; 683 - 684 - METRICS_COLLECTOR = 1; 685 - 686 - OAUTH2_PROVIDER = "oidc"; 687 - OAUTH2_OIDC_PROVIDER_NAME = "wiro.world SSO"; 688 - OAUTH2_CLIENT_ID = "miniflux"; 689 - OAUTH2_CLIENT_SECRET_FILE = config.age.secrets.miniflux-oidc-secret.path; 690 - OAUTH2_REDIRECT_URL = "https://${miniflux-hostname}/oauth2/oidc/callback"; 691 - OAUTH2_OIDC_DISCOVERY_ENDPOINT = "https://auth.wiro.world"; 692 - OAUTH2_USER_CREATION = 1; 693 - DISABLE_LOCAL_AUTH = 1; 694 - 695 - RUN_MIGRATIONS = 1; 696 - 697 - # NetNewsWire is a very good iOS oss client that integrates well 698 - # https://b.j4.lc/2025/05/05/setting-up-netnewswire-with-miniflux/ 699 - }; 700 - }; 701 - 702 - age.secrets.hypixel-bank-tracker-main.file = ../../secrets/hypixel-bank-tracker-main.age; 703 - services.hypixel-bank-tracker.instances.main = { 704 - enable = true; 705 - 706 - port = hbt-main-port; 707 - environmentFile = config.age.secrets.hypixel-bank-tracker-main.path; 708 - }; 709 - 710 - age.secrets.hypixel-bank-tracker-banana.file = ../../secrets/hypixel-bank-tracker-banana.age; 711 - services.hypixel-bank-tracker.instances.banana = { 712 - enable = true; 713 - 714 - port = hbt-banana-port; 715 - environmentFile = config.age.secrets.hypixel-bank-tracker-banana.path; 716 - }; 717 - 718 - virtualisation.oci-containers.containers.archive-warrior = { 719 - image = "atdr.meo.ws/archiveteam/warrior-dockerfile"; 720 - ports = [ "127.0.0.1:${toString warrior-port}:8001" ]; 721 - pull = "newer"; 722 - }; 723 - }; 724 - }
+9 -2
secrets.nix
··· 1 1 let 2 2 inherit (builtins) listToAttrs attrNames; 3 + 4 + # Map the name and value of all items of an attrset 3 5 mapAttrs' = 4 6 f: 5 7 set: 6 8 listToAttrs (map (attr: f attr set.${attr}) (attrNames set)); 9 + 10 + keys = import ./secrets/keys.nix; 11 + 12 + prependAttrsName = prefix: mapAttrs' (name: value: { name = prefix + name; inherit value; }); 13 + secretsDir = path: prependAttrsName (path + "/") ((import ./${path}/default.nix) keys); 7 14 in 8 15 9 - # You can use agenix directly at repo top-level instead of having to change directory into `secrets/` 10 - mapAttrs' (name: value: { name = ("secrets/" + name); inherit value; }) (import ./secrets/secrets.nix) 16 + secretsDir "secrets" 17 + // secretsDir "hosts/weird-row-server/secrets"
secrets/authelia-issuer-private-key.age hosts/weird-row-server/secrets/authelia-issuer-private-key.age
secrets/authelia-jwt-secret.age hosts/weird-row-server/secrets/authelia-jwt-secret.age
secrets/authelia-ldap-password.age hosts/weird-row-server/secrets/authelia-ldap-password.age
secrets/authelia-smtp-password.age hosts/weird-row-server/secrets/authelia-smtp-password.age
secrets/authelia-storage-key.age hosts/weird-row-server/secrets/authelia-storage-key.age
+22
secrets/default.nix
··· 1 + keys: 2 + 3 + let 4 + inherit (keys) sessions systems users; 5 + 6 + nixos = systems ++ users; 7 + home-manager = sessions ++ users; 8 + in 9 + { 10 + # Used in NixOS config 11 + "backup-rclone-googledrive.age".publicKeys = nixos; 12 + "backup-restic-key.age".publicKeys = nixos; 13 + 14 + # Used in Home Manager 15 + "api-crates-io.age".publicKeys = home-manager; 16 + "api-wakatime.age".publicKeys = home-manager; 17 + "api-wakapi.age".publicKeys = home-manager; 18 + 19 + # Not used in config but useful 20 + "pgp-ca5e.age".publicKeys = users; 21 + "ssh-uxgi.age".publicKeys = users; 22 + }
secrets/grafana-oidc-secret.age hosts/weird-row-server/secrets/grafana-oidc-secret.age
secrets/headscale-oidc-secret.age hosts/weird-row-server/secrets/headscale-oidc-secret.age
secrets/hypixel-bank-tracker-banana.age hosts/weird-row-server/secrets/hypixel-bank-tracker-banana.age
secrets/hypixel-bank-tracker-main.age hosts/weird-row-server/secrets/hypixel-bank-tracker-main.age
secrets/lldap-env.age hosts/weird-row-server/secrets/lldap-env.age
secrets/lldap-user-pass.age hosts/weird-row-server/secrets/lldap-user-pass.age
secrets/miniflux-oidc-secret.age hosts/weird-row-server/secrets/miniflux-oidc-secret.age
secrets/pds-env.age hosts/weird-row-server/secrets/pds-env.age
+2 -19
secrets/secrets.nix hosts/weird-row-server/secrets/default.nix
··· 1 + keys: 1 2 let 2 - inherit (import ./keys.nix) servers sessions systems users; 3 - 4 - nixos = systems ++ users; 5 - home-manager = sessions ++ users; 3 + inherit (keys) servers users; 6 4 deploy = servers ++ users; 7 5 in 8 6 { 9 - # Used in NixOS config 10 - "backup-rclone-googledrive.age".publicKeys = nixos; 11 - "backup-restic-key.age".publicKeys = nixos; 12 - 13 - # Used in Home Manager 14 - "api-crates-io.age".publicKeys = home-manager; 15 - "api-wakatime.age".publicKeys = home-manager; 16 - "api-wakapi.age".publicKeys = home-manager; 17 - 18 - # Used in server deployment 19 - 20 7 # Defines `PDS_JWT_SECRET`, `PDS_ADMIN_PASSWORD`, `PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX`, `PDS_EMAIL_SMTP_URL`, `PDS_EMAIL_FROM_ADDRESS`. 21 8 "pds-env.age".publicKeys = deploy; 22 9 # Defines `LLDAP_JWT_SECRET`, `LLDAP_KEY_SEED`. ··· 36 23 # Defines `HYPIXEL_API_KEY`, `PROFILE_UUID` 37 24 "hypixel-bank-tracker-main.age".publicKeys = deploy; 38 25 "hypixel-bank-tracker-banana.age".publicKeys = deploy; 39 - 40 - # Not used in config but useful 41 - "pgp-ca5e.age".publicKeys = users; 42 - "ssh-uxgi.age".publicKeys = users; 43 26 }
secrets/tuwunel-registration-tokens.age hosts/weird-row-server/secrets/tuwunel-registration-tokens.age
secrets/vaultwarden-env.age hosts/weird-row-server/secrets/vaultwarden-env.age