diff --git a/flake.lock b/flake.lock index bc65be0..0984eb0 100755 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1747575206, - "narHash": "sha256-NwmAFuDUO/PFcgaGGr4j3ozG9Pe5hZ/ogitWhY+D81k=", + "lastModified": 1750173260, + "narHash": "sha256-9P1FziAwl5+3edkfFcr5HeGtQUtrSdk/MksX39GieoA=", "owner": "ryantm", "repo": "agenix", - "rev": "4835b1dc898959d8547a871ef484930675cb47f1", + "rev": "531beac616433bac6f9e2a19feb8e99a22a66baf", "type": "github" }, "original": { @@ -26,11 +26,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1750013871, - "narHash": "sha256-UQx3rC3QDjD/sIen51+5Juk1rqN3y/sTeMY1WinmhqQ=", + "lastModified": 1751021896, + "narHash": "sha256-L9u68mNPPiuW7+OV5BKbXaj/AENTiiuEx8+QnMBjRlU=", "owner": "catppuccin", "repo": "nix", - "rev": "fe78fa558d6603481c03eb03a946eadb970d1801", + "rev": "a6b0e34d083c79f08efabb1fd6ccf12b882daae6", "type": "github" }, "original": { @@ -158,11 +158,11 @@ ] }, "locked": { - "lastModified": 1750127463, - "narHash": "sha256-K2xFtlD3PcKAZriOE3LaBLYmVfGQu+rIF4Jr1RFYR0Q=", + "lastModified": 1751384836, + "narHash": "sha256-7xRbl/VLXxE5DzJmk1wdKWJmPx8rAfNC/a6mXtqp5cc=", "owner": "nix-community", "repo": "home-manager", - "rev": "28eef8722d1af18ca13e687dbf485e1c653a0402", + "rev": "479f8889675770881033878a1c114fbfc6de7a4d", "type": "github" }, "original": { @@ -236,11 +236,11 @@ "spectrum": "spectrum" }, "locked": { - "lastModified": 1750196518, - "narHash": "sha256-HJYnJg3TvzFZjVgYHZgH3NtwqkqKiGVCJXpZlO4Y4EE=", + "lastModified": 1750358184, + "narHash": "sha256-17EYMeY5v8KRk9HW6Z4dExY8Wg4y/zM2eM2wbbx+vMs=", "owner": "astro", "repo": "microvm.nix", - "rev": "094da86a3e68f2f0d93b654e97b5d42398ead67d", + "rev": "fd9f5dba1ffee5ad6f29394b2a9e4c66c1ce77dc", "type": "github" }, "original": { @@ -251,11 +251,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1750083401, - "narHash": "sha256-ynqbgIYrg7P1fAKYqe8I/PMiLABBcNDYG9YaAP/d/C4=", + "lastModified": 1751393906, + "narHash": "sha256-I1x6K61ZcdFlqc07weRBy3erCAB0lVkX10i0c9eXjDI=", "owner": "nixos", "repo": "nixos-hardware", - "rev": "61837d2a33ccc1582c5fabb7bf9130d39fee59ad", + "rev": "f49bb3b4107a0917ee144337bb02d311033ee1ba", "type": "github" }, "original": { @@ -299,16 +299,16 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1750005367, - "narHash": "sha256-h/aac1dGLhS3qpaD2aZt25NdKY7b+JT0ZIP2WuGsJMU=", + "lastModified": 1751271578, + "narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6c64dabd3aa85e0c02ef1cdcb6e1213de64baee3", + "rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-25.05", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -422,11 +422,11 @@ "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1729422940, - "narHash": "sha256-DlvJv33ml5UTKgu4b0HauOfFIoDx6QXtbqUF3vWeRCY=", + "lastModified": 1750353031, + "narHash": "sha256-Bx7DOPLhkr8Z60U9Qw4l0OidzHoqLDKQH5rDV5ef59A=", "owner": "nix-community", "repo": "nixos-vscode-server", - "rev": "8b6db451de46ecf9b4ab3d01ef76e59957ff549f", + "rev": "4ec4859b12129c0436b0a471ed1ea6dd8a317993", "type": "github" }, "original": { @@ -443,11 +443,11 @@ ] }, "locked": { - "lastModified": 1750091187, - "narHash": "sha256-mjAol6qR+onnZwLUdYjmuBr/tnyozUBXz75tSePVU00=", + "lastModified": 1751383329, + "narHash": "sha256-52dUY8jEkuXEIZINYb+AVsrmw6FxMhBAG3K9J/2qiSo=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "cfdf98dac59a42e1642c533a5dbfb5bb242903b3", + "rev": "f29a4fece3b76c3e4579d67e2cf0cb8037f6a351", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index aa1275d..6f65292 100755 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ # flake.nix { inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixos-hardware.url = "github:nixos/nixos-hardware/master"; lix-module = { @@ -64,6 +64,8 @@ system = "x86_64-linux"; }; } + + { imports = builtins.attrValues nixosModules; } ]; }; diff --git a/home/regent/home.nix b/home/regent/home.nix index a72a81f..db87dd3 100755 --- a/home/regent/home.nix +++ b/home/regent/home.nix @@ -149,6 +149,7 @@ window#waybar { output = [ "HDMI-A-1" "DP-1" + "DP-2" ]; modules-left = [ "sway/workspaces" diff --git a/hosts/buer/default.nix b/hosts/buer/default.nix index 691f4e8..280a5ba 100755 --- a/hosts/buer/default.nix +++ b/hosts/buer/default.nix @@ -79,9 +79,6 @@ virtualisation.docker = { enable = true; enableOnBoot = true; - package = pkgs.docker.override { - buildGoModule = pkgs.buildGo123Module; - }; }; # ============================================================================= diff --git a/hosts/focalor/default.nix b/hosts/focalor/default.nix index 84044a2..46577a4 100755 --- a/hosts/focalor/default.nix +++ b/hosts/focalor/default.nix @@ -29,6 +29,12 @@ ../../host-secrets.nix ]; + modules.syncthing = { + enable = true; + openDefaultPorts = true; + disableDefaultFolder = true; + }; + # ============================================================================= # SYSTEM CONFIGURATION # ============================================================================= diff --git a/modules/headscale/default.nix b/modules/headscale/default.nix new file mode 100644 index 0000000..b72d009 --- /dev/null +++ b/modules/headscale/default.nix @@ -0,0 +1,229 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.modules.headscale; +in +{ + options = { + modules = { + headscale = { + enable = mkEnableOption "Deploy headscale"; + + oidcClientSecretPath = mkOption { + type = types.str; + default = "/etc/headscale/oidc_client_secret.key"; + description = "Path to OIDC client secret file"; + example = "config.age.secrets.headscale-oidc-key.path"; + }; + + litestream = { + enable = mkEnableOption "Enable litestream for headscale database backups"; + + replicas = mkOption { + type = types.listOf (types.attrsOf types.anything); + default = []; + description = "List of litestream replica configurations"; + example = [ + { + url = "s3://your-backup-bucket/headscale/db"; + access-key-id = "$LITESTREAM_ACCESS_KEY_ID"; + secret-access-key = "$LITESTREAM_SECRET_ACCESS_KEY"; + region = "us-east-1"; + } + ]; + }; + + backupPath = mkOption { + type = types.nullOr types.str; + default = null; + description = "Local backup path (alternative to S3)"; + example = "/backup/headscale"; + }; + + syncInterval = mkOption { + type = types.str; + default = "1s"; + description = "How often to sync to replicas"; + }; + + retention = mkOption { + type = types.str; + default = "72h"; + description = "How long to retain snapshots"; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Environment file containing S3 credentials (can be agenix secret)"; + example = "config.age.secrets.litestream-env.path"; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + services.headscale = { + enable = true; + address = "0.0.0.0"; + port = 8080; + + settings = { + server_url = "https://headscale.nekomimi.pet"; + + # Metrics and gRPC + metrics_listen_addr = "127.0.0.1:9090"; + grpc_listen_addr = "127.0.0.1:50443"; + grpc_allow_insecure = false; + + # Prefixes + prefixes = { + v4 = "100.64.0.0/10"; + v6 = "fd7a:115c:a1e0::/48"; + allocation = "sequential"; + }; + + # Database + database = { + type = "sqlite"; + sqlite = { + path = "/var/lib/headscale/db.sqlite"; + write_ahead_log = true; + }; + }; + + # Noise + noise = { + private_key_path = "/var/lib/headscale/noise_private.key"; + }; + + # DERP + derp = { + urls = [ + "https://controlplane.tailscale.com/derpmap/default" + ]; + paths = []; + auto_update_enabled = true; + update_frequency = "24h"; + server = { + enabled = false; + region_id = 999; + region_code = "headscale"; + region_name = "Headscale Embedded DERP"; + stun_listen_addr = "0.0.0.0:3478"; + private_key_path = "/var/lib/headscale/derp_server_private.key"; + automatically_add_embedded_derp_region = true; + ipv4 = "1.2.3.4"; + ipv6 = "2001:db8::1"; + }; + }; + + # DNS + dns = { + magic_dns = true; + base_domain = "dns.sharkgirl.pet"; + nameservers = { + global = [ + "100.64.0.7" + "1.1.1.1" + "1.0.0.1" + "2606:4700:4700::1111" + "2606:4700:4700::1001" + ]; + }; + search_domains = []; + }; + + # OIDC with configurable secret path + oidc = { + only_start_if_oidc_is_available = true; + issuer = "https://pocketid.nekomimi.pet"; + client_id = "f345acad-3eac-45b7-9d91-57f388987a57"; + client_secret_path = cfg.oidcClientSecretPath; + pkce = { + enabled = true; + method = "S256"; + }; + }; + + # Policy + policy = { + mode = "database"; + }; + + # TLS/ACME + acme_url = "https://acme-v02.api.letsencrypt.org/directory"; + acme_email = ""; + tls_letsencrypt_hostname = ""; + tls_letsencrypt_cache_dir = "/var/lib/headscale/cache"; + tls_letsencrypt_challenge_type = "HTTP-01"; + tls_letsencrypt_listen = ":http"; + tls_cert_path = ""; + tls_key_path = ""; + + # Logging + log = { + format = "text"; + level = "info"; + }; + + # Misc settings + disable_check_updates = false; + ephemeral_node_inactivity_timeout = "30m"; + unix_socket = "/var/run/headscale/headscale.sock"; + unix_socket_permission = "0770"; + logtail = { + enabled = false; + }; + randomize_client_port = false; + }; + }; + + # Configurable Litestream for SQLite database backups + services.litestream = mkIf cfg.litestream.enable { + enable = true; + settings = { + dbs = [ + { + path = "/var/lib/headscale/db.sqlite"; + sync-interval = cfg.litestream.syncInterval; + retention = cfg.litestream.retention; + replicas = + # Use custom replicas if provided + if cfg.litestream.replicas != [] then + cfg.litestream.replicas + # Otherwise use local backup if path is provided + else if cfg.litestream.backupPath != null then + [{ path = cfg.litestream.backupPath; }] + # Default empty (user must configure) + else + []; + } + ]; + }; + }; + + # Configure systemd service to use agenix secrets + systemd.services.headscale.serviceConfig = mkMerge [ + { + SupplementaryGroups = [ "headscale-secrets" ]; + } + # Add environment file for litestream if specified + (mkIf (cfg.litestream.enable && cfg.litestream.environmentFile != null) { + EnvironmentFile = cfg.litestream.environmentFile; + }) + ]; + + # Configure litestream service with environment file if specified + systemd.services.litestream = mkIf (cfg.litestream.enable && cfg.litestream.environmentFile != null) { + serviceConfig = { + EnvironmentFile = cfg.litestream.environmentFile; + }; + }; + + # Create a group for accessing secrets + users.groups.headscale-secrets = {}; + }; +} \ No newline at end of file diff --git a/modules/syncthing/default.nix b/modules/syncthing/default.nix new file mode 100644 index 0000000..0a77d0e --- /dev/null +++ b/modules/syncthing/default.nix @@ -0,0 +1,257 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.modules.syncthing; + + # Helper function to create a serviceConfig entry if the condition is met + mkServiceConfigOption = name: value: mkIf (value != null) { "${name}" = value; }; + + # Construct the settings object for Syncthing + syncthingSettings = mkMerge [ + # GUI configuration + (mkIf cfg.gui.enable { + gui = mkMerge [ + (mkIf (cfg.gui.user != null) { + user = cfg.gui.user; + }) + ]; + }) + + # Devices configuration + (mkIf (cfg.devices != {}) { + devices = mapAttrs (name: device: { + id = device.id; + } // optionalAttrs (device.name != null) { + name = device.name; + } // optionalAttrs (device.addresses != []) { + addresses = device.addresses; + }) cfg.devices; + }) + + # Folders configuration + (mkIf (cfg.folders != {}) { + folders = mapAttrs (name: folder: { + path = folder.path; + devices = folder.devices; + } // optionalAttrs (folder.ignorePerms != null) { + ignorePerms = folder.ignorePerms; + } // optionalAttrs (folder.type != null) { + type = folder.type; + } // optionalAttrs (folder.rescanIntervalS != null) { + rescanIntervalS = folder.rescanIntervalS; + } // optionalAttrs (folder.versioning != null) { + versioning = folder.versioning; + }) cfg.folders; + }) + + # Extra options + cfg.extraOptions + ]; +in +{ + options = { + modules.syncthing = { + enable = mkEnableOption "Deploy syncthing"; + + openDefaultPorts = mkOption { + type = types.bool; + default = true; + description = "Open ports in the firewall for Syncthing"; + }; + + disableDefaultFolder = mkOption { + type = types.bool; + default = true; + description = "Don't create default ~/Sync folder"; + }; + + gui = { + enable = mkEnableOption "Enable GUI configuration"; + + user = mkOption { + type = types.nullOr types.str; + default = null; + description = "GUI username"; + example = "myuser"; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to file containing GUI password"; + example = "config.age.secrets.syncthing-gui-password.path"; + }; + }; + + identity = { + keyPath = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to Syncthing private key for stable device ID"; + example = "config.age.secrets.syncthing-key.path"; + }; + + certPath = mkOption { + type = types.nullOr types.path; + default = null; + description = "Path to Syncthing certificate for stable device ID"; + example = "config.age.secrets.syncthing-cert.path"; + }; + }; + + devices = mkOption { + type = types.attrsOf (types.submodule { + options = { + id = mkOption { + type = types.str; + description = "Device ID"; + example = "DMWVMM6-MKEQVB4-I4UZTRH-5A6E24O-XHQTL3K-AAI5R5L-MXNMUGX-QTGRHQ2"; + }; + + name = mkOption { + type = types.nullOr types.str; + default = null; + description = "Device name (optional)"; + }; + + addresses = mkOption { + type = types.listOf types.str; + default = []; + description = "Device addresses"; + example = [ "tcp://192.168.1.100:22000" ]; + }; + }; + }); + default = {}; + description = "Syncthing devices configuration"; + example = { + "laptop" = { + id = "DMWVMM6-MKEQVB4-I4UZTRH-5A6E24O-XHQTL3K-AAI5R5L-MXNMUGX-QTGRHQ2"; + }; + "phone" = { + id = "ANOTHER-DEVICE-ID-GOES-HERE"; + addresses = [ "tcp://192.168.1.101:22000" ]; + }; + }; + }; + + folders = mkOption { + type = types.attrsOf (types.submodule { + options = { + path = mkOption { + type = types.str; + description = "Local folder path"; + example = "/home/myuser/Documents"; + }; + + devices = mkOption { + type = types.listOf (types.either types.str (types.submodule { + options = { + name = mkOption { + type = types.str; + description = "Device name"; + }; + + encryptionPasswordFile = mkOption { + type = types.path; + description = "Path to file containing encryption password"; + }; + }; + })); + default = []; + description = "List of devices that can access this folder"; + example = [ "laptop" "phone" ]; + }; + + ignorePerms = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether to ignore file permissions"; + }; + + type = mkOption { + type = types.nullOr (types.enum [ "sendreceive" "sendonly" "receiveonly" ]); + default = null; + description = "Folder type"; + }; + + rescanIntervalS = mkOption { + type = types.nullOr types.int; + default = null; + description = "Rescan interval in seconds"; + }; + + versioning = mkOption { + type = types.nullOr (types.submodule { + options = { + type = mkOption { + type = types.enum [ "external" "simple" "staggered" "trashcan" ]; + description = "Versioning type"; + }; + + params = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Versioning parameters"; + }; + }; + }); + default = null; + description = "Folder versioning configuration"; + }; + }; + }); + default = {}; + description = "Syncthing folders configuration"; + example = { + "Documents" = { + path = "/home/myuser/Documents"; + devices = [ "laptop" "phone" ]; + ignorePerms = false; + }; + "Sensitive" = { + path = "/home/myuser/Sensitive"; + devices = [ + "laptop" + { + name = "phone"; + encryptionPasswordFile = "/run/secrets/syncthing-sensitive-password"; + } + ]; + }; + }; + }; + + extraOptions = mkOption { + type = types.attrsOf types.anything; + default = {}; + description = "Additional Syncthing configuration options"; + }; + }; + }; + + config = mkIf cfg.enable { + services.syncthing = { + enable = true; + openDefaultPorts = cfg.openDefaultPorts; + # Set stable identity if provided + key = mkIf (cfg.identity.keyPath != null) cfg.identity.keyPath; + cert = mkIf (cfg.identity.certPath != null) cfg.identity.certPath; + # Combine all settings + settings = syncthingSettings; + }; + + # Configure systemd service options collectively + systemd.services.syncthing = { + # Add environment variable to disable default folder creation + environment.STNODEFAULTFOLDER = mkIf cfg.disableDefaultFolder "true"; + + # Add supplementary groups for secret access + serviceConfig.SupplementaryGroups = [ "syncthing-secrets" ]; + }; + + # Create a group for accessing secrets + users.groups.syncthing-secrets = {}; + }; +} \ No newline at end of file diff --git a/secrets/headscale-oidc-key.path b/secrets/headscale-oidc-key.path new file mode 100644 index 0000000..d6cec81 --- /dev/null +++ b/secrets/headscale-oidc-key.path @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> ssh-ed25519 i9wBeA DynOTJFDKsSyHKTG9XFAAcZf/T//KKyK8UG4aGgVH2c +o+ggJe/HZmPU+Ezw4u4m+l9bQ1furG7G4Oo7xS8PMAs +-> ssh-ed25519 UbxDgg b1XiosrWXL9WI1B7YnNSw16l1p4oa3zjDCCgkU/FxiU +MY8oubHMth/wDKn9kNOUkaY9ODvrKIn7DeZTuGxj4/g +-> ssh-ed25519 YYzA7Q 6ql+gutJfteQM75WL6ywEDA1+fIcYSpLPaTSKhqE1ic +tbwXx/feysvpOrxwpDi5B5PveSIbFH0qSsV6/xmo4hk +-> ssh-ed25519 3RWqPQ hNVnobsB1OB9woXtn1T1tXJL+1Dbasc9N2tjZdXa0Bw +9HlWIX7aroc8kTUW3rPlxvMSTSGJXbMcOEipdoQqnbw +--- h8toQGhp/wUgMkJ+RU0bV7E6pHRUM8mKLPcrDmbZ5NQ +!Å´·jÖ–n$ÔZªÿŒ$s9fÑö´Ï‰k›.ro—`èCU>Ë»©R‹F;H;—}JÚ(ú0ôáÛ U¦YP \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index e3d4571..e65ba7a 100755 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -16,4 +16,5 @@ in "garage-metrics-token.age".publicKeys = users ++ systems; "headscale-authkey.age".publicKeys = users ++ systems; + "headscale-oidc-key.path".publicKeys = users ++ systems; }