my nix configs for my servers and desktop
1{ config, lib, pkgs, ... }: 2 3with lib; 4let 5 cfg = config.modules.headscale; 6in 7{ 8 options = { 9 modules = { 10 headscale = { 11 enable = mkEnableOption "Deploy headscale"; 12 13 oidcClientSecretPath = mkOption { 14 type = types.str; 15 default = "/etc/headscale/oidc_client_secret.key"; 16 description = "Path to OIDC client secret file"; 17 example = "config.age.secrets.headscale-oidc-key.path"; 18 }; 19 20 litestream = { 21 enable = mkEnableOption "Enable litestream for headscale database backups"; 22 23 replicas = mkOption { 24 type = types.listOf (types.attrsOf types.anything); 25 default = []; 26 description = "List of litestream replica configurations"; 27 example = [ 28 { 29 url = "s3://your-backup-bucket/headscale/db"; 30 access-key-id = "$LITESTREAM_ACCESS_KEY_ID"; 31 secret-access-key = "$LITESTREAM_SECRET_ACCESS_KEY"; 32 region = "us-east-1"; 33 } 34 ]; 35 }; 36 37 backupPath = mkOption { 38 type = types.nullOr types.str; 39 default = null; 40 description = "Local backup path (alternative to S3)"; 41 example = "/backup/headscale"; 42 }; 43 44 syncInterval = mkOption { 45 type = types.str; 46 default = "1s"; 47 description = "How often to sync to replicas"; 48 }; 49 50 retention = mkOption { 51 type = types.str; 52 default = "72h"; 53 description = "How long to retain snapshots"; 54 }; 55 56 environmentFile = mkOption { 57 type = types.nullOr types.path; 58 default = null; 59 description = "Environment file containing S3 credentials (can be agenix secret)"; 60 example = "config.age.secrets.litestream-env.path"; 61 }; 62 }; 63 }; 64 }; 65 }; 66 67 config = mkIf cfg.enable { 68 services.headscale = { 69 enable = true; 70 address = "0.0.0.0"; 71 port = 8080; 72 73 settings = { 74 server_url = "https://headscale.nekomimi.pet"; 75 76 # Metrics and gRPC 77 metrics_listen_addr = "127.0.0.1:9090"; 78 grpc_listen_addr = "127.0.0.1:50443"; 79 grpc_allow_insecure = false; 80 81 # Prefixes 82 prefixes = { 83 v4 = "100.64.0.0/10"; 84 v6 = "fd7a:115c:a1e0::/48"; 85 allocation = "sequential"; 86 }; 87 88 # Database 89 database = { 90 type = "sqlite"; 91 sqlite = { 92 path = "/var/lib/headscale/db.sqlite"; 93 write_ahead_log = true; 94 }; 95 }; 96 97 # Noise 98 noise = { 99 private_key_path = "/var/lib/headscale/noise_private.key"; 100 }; 101 102 # DERP 103 derp = { 104 urls = [ 105 "https://controlplane.tailscale.com/derpmap/default" 106 ]; 107 paths = []; 108 auto_update_enabled = true; 109 update_frequency = "24h"; 110 server = { 111 enabled = false; 112 region_id = 999; 113 region_code = "headscale"; 114 region_name = "Headscale Embedded DERP"; 115 stun_listen_addr = "0.0.0.0:3478"; 116 private_key_path = "/var/lib/headscale/derp_server_private.key"; 117 automatically_add_embedded_derp_region = true; 118 ipv4 = "1.2.3.4"; 119 ipv6 = "2001:db8::1"; 120 }; 121 }; 122 123 # DNS 124 dns = { 125 magic_dns = true; 126 base_domain = "dns.sharkgirl.pet"; 127 nameservers = { 128 global = [ 129 "100.64.0.7" 130 "1.1.1.1" 131 "1.0.0.1" 132 "2606:4700:4700::1111" 133 "2606:4700:4700::1001" 134 ]; 135 }; 136 search_domains = []; 137 }; 138 139 # OIDC with configurable secret path 140 oidc = { 141 only_start_if_oidc_is_available = true; 142 issuer = "https://pocketid.nekomimi.pet"; 143 client_id = "f345acad-3eac-45b7-9d91-57f388987a57"; 144 client_secret_path = cfg.oidcClientSecretPath; 145 pkce = { 146 enabled = true; 147 method = "S256"; 148 }; 149 }; 150 151 # Policy 152 policy = { 153 mode = "database"; 154 }; 155 156 # TLS/ACME 157 acme_url = "https://acme-v02.api.letsencrypt.org/directory"; 158 acme_email = ""; 159 tls_letsencrypt_hostname = ""; 160 tls_letsencrypt_cache_dir = "/var/lib/headscale/cache"; 161 tls_letsencrypt_challenge_type = "HTTP-01"; 162 tls_letsencrypt_listen = ":http"; 163 tls_cert_path = ""; 164 tls_key_path = ""; 165 166 # Logging 167 log = { 168 format = "text"; 169 level = "info"; 170 }; 171 172 # Misc settings 173 disable_check_updates = false; 174 ephemeral_node_inactivity_timeout = "30m"; 175 unix_socket = "/var/run/headscale/headscale.sock"; 176 unix_socket_permission = "0770"; 177 logtail = { 178 enabled = false; 179 }; 180 randomize_client_port = false; 181 }; 182 }; 183 184 # Configurable Litestream for SQLite database backups 185 services.litestream = mkIf cfg.litestream.enable { 186 enable = true; 187 settings = { 188 dbs = [ 189 { 190 path = "/var/lib/headscale/db.sqlite"; 191 sync-interval = cfg.litestream.syncInterval; 192 retention = cfg.litestream.retention; 193 replicas = 194 # Use custom replicas if provided 195 if cfg.litestream.replicas != [] then 196 cfg.litestream.replicas 197 # Otherwise use local backup if path is provided 198 else if cfg.litestream.backupPath != null then 199 [{ path = cfg.litestream.backupPath; }] 200 # Default empty (user must configure) 201 else 202 []; 203 } 204 ]; 205 }; 206 }; 207 208 # Configure systemd service to use agenix secrets 209 systemd.services.headscale.serviceConfig = mkMerge [ 210 { 211 SupplementaryGroups = [ "headscale-secrets" ]; 212 } 213 # Add environment file for litestream if specified 214 (mkIf (cfg.litestream.enable && cfg.litestream.environmentFile != null) { 215 EnvironmentFile = cfg.litestream.environmentFile; 216 }) 217 ]; 218 219 # Configure litestream service with environment file if specified 220 systemd.services.litestream = mkIf (cfg.litestream.enable && cfg.litestream.environmentFile != null) { 221 serviceConfig = { 222 EnvironmentFile = cfg.litestream.environmentFile; 223 }; 224 }; 225 226 # Create a group for accessing secrets 227 users.groups.headscale-secrets = {}; 228 }; 229}