diff --git a/.github/workflows/nixos-build.yml b/.github/workflows/nixos-build.yml index 65a8dcb..a93bc19 100644 --- a/.github/workflows/nixos-build.yml +++ b/.github/workflows/nixos-build.yml @@ -1,5 +1,4 @@ name: NixOS build - on: pull_request: branches: @@ -17,14 +16,11 @@ on: - ".sops.yaml" - "secrets/**" workflow_dispatch: - permissions: contents: read - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - jobs: discover-hosts: name: Discover NixOS hosts @@ -34,7 +30,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Nix uses: cachix/install-nix-action@v31 with: @@ -42,7 +37,6 @@ jobs: experimental-features = nix-command flakes accept-flake-config = true access-tokens = github.com=${{ github.token }} - - name: Evaluate NixOS hosts id: hosts run: | @@ -50,7 +44,6 @@ jobs: hosts_json=$(nix eval --json '.#nixosConfigurations' --apply 'configs: builtins.attrNames configs') echo "hosts=${hosts_json}" >> "$GITHUB_OUTPUT" echo "Discovered hosts: ${hosts_json}" - build-host: name: Build ${{ matrix.host }} needs: discover-hosts @@ -63,7 +56,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Nix uses: cachix/install-nix-action@v31 with: @@ -71,7 +63,6 @@ jobs: experimental-features = nix-command flakes accept-flake-config = true access-tokens = github.com=${{ github.token }} - - name: Build NixOS system run: | set -euo pipefail diff --git a/.github/workflows/nixos-eval.yml b/.github/workflows/nixos-eval.yml index 9f85994..39cbf7d 100644 --- a/.github/workflows/nixos-eval.yml +++ b/.github/workflows/nixos-eval.yml @@ -1,5 +1,4 @@ name: NixOS eval - on: pull_request: branches: @@ -17,14 +16,11 @@ on: - ".sops.yaml" - "secrets/**" workflow_dispatch: - permissions: contents: read - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - jobs: discover-hosts: name: Discover NixOS hosts @@ -34,7 +30,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Nix uses: cachix/install-nix-action@v31 with: @@ -42,7 +37,6 @@ jobs: experimental-features = nix-command flakes accept-flake-config = true access-tokens = github.com=${{ github.token }} - - name: Evaluate NixOS hosts id: hosts run: | @@ -50,7 +44,6 @@ jobs: hosts_json=$(nix eval --json '.#nixosConfigurations' --apply 'configs: builtins.attrNames configs') echo "hosts=${hosts_json}" >> "$GITHUB_OUTPUT" echo "Discovered hosts: ${hosts_json}" - eval-host: name: Eval ${{ matrix.host }} needs: discover-hosts @@ -63,7 +56,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Nix uses: cachix/install-nix-action@v31 with: @@ -71,7 +63,6 @@ jobs: experimental-features = nix-command flakes accept-flake-config = true access-tokens = github.com=${{ github.token }} - - name: Evaluate NixOS system derivation run: | set -euo pipefail diff --git a/hosts/default.nix b/hosts/default.nix index 3ee41f7..b2fe5d3 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -69,6 +69,7 @@ in "workloads/dev" "workloads/personal" "workloads/secure-storage" + "workloads/tailscale/client" ]; }; diff --git a/modules/applications/tailscale.nix b/modules/applications/tailscale.nix index 8c9fc30..7ac4de2 100644 --- a/modules/applications/tailscale.nix +++ b/modules/applications/tailscale.nix @@ -1,38 +1,118 @@ { lib, config, ... }: + let cfg = config.my.applications.tailscale; + + hasAdvertiseRoutes = cfg.advertiseRoutes != [ ]; + + computedRoutingFeatures = + if cfg.routingFeatures != "auto" then + cfg.routingFeatures + else if hasAdvertiseRoutes && cfg.acceptRoutes then + "both" + else if hasAdvertiseRoutes then + "server" + else if cfg.acceptRoutes then + "client" + else + "none"; + + computedOpenFirewall = if cfg.openFirewall != null then cfg.openFirewall else hasAdvertiseRoutes; + + computedSetFlags = [ + "--accept-dns=${lib.boolToString cfg.acceptDns}" + "--accept-routes=${lib.boolToString cfg.acceptRoutes}" + ] + ++ lib.optionals hasAdvertiseRoutes [ + "--advertise-routes=${lib.concatStringsSep "," cfg.advertiseRoutes}" + ] + ++ cfg.extraSetFlags; in { options.my.applications.tailscale = { - enable = lib.mkEnableOption "Tailscale VPN"; + enable = lib.mkEnableOption "Tailscale"; acceptDns = lib.mkOption { type = lib.types.bool; default = false; - description = "Accept DNS configuration from Tailscale"; + description = "Accept DNS configuration from Tailscale."; }; acceptRoutes = lib.mkOption { type = lib.types.bool; - default = true; - description = "Accept subnet routes from Tailscale"; + default = false; + description = "Accept subnet routes advertised by other Tailscale nodes."; + }; + + advertiseRoutes = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "10.50.0.0/16" ]; + description = "Subnet routes to advertise through this Tailscale node."; + }; + + routingFeatures = lib.mkOption { + type = lib.types.enum [ + "auto" + "none" + "client" + "server" + "both" + ]; + default = "auto"; + description = '' + Routing feature mode for Tailscale. + + auto: + - advertiseRoutes only -> server + - acceptRoutes only -> client + - both -> both + - neither -> none + ''; + }; + + openFirewall = lib.mkOption { + type = lib.types.nullOr lib.types.bool; + default = null; + description = '' + Whether to open the firewall for Tailscale's UDP port. + + null means automatic: + - true when advertiseRoutes is non-empty + - false otherwise + ''; + }; + + extraSetFlags = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Additional flags to pass to `tailscale set`."; }; extraUpFlags = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; - description = "Additional flags to pass to tailscale up"; + description = '' + Additional flags to pass to `tailscale up`. + + Note: on current NixOS this is only applied by the built-in + autoconnect service when services.tailscale.authKeyFile is set. + ''; }; }; config = lib.mkIf cfg.enable { services.tailscale = { enable = true; - extraUpFlags = [ - "--accept-dns=${if cfg.acceptDns then "true" else "false"}" - ] - ++ lib.optional cfg.acceptRoutes "--accept-routes" - ++ cfg.extraUpFlags; + + openFirewall = computedOpenFirewall; + useRoutingFeatures = computedRoutingFeatures; + + # 常時反映したい設定は tailscale set に寄せる + extraSetFlags = computedSetFlags; + + # authKeyFile を使う場合だけ効くものとして残す + inherit (cfg) extraUpFlags; }; }; } diff --git a/modules/features/network/tailscale.nix b/modules/features/network/tailscale.nix index 495e5aa..72798f9 100644 --- a/modules/features/network/tailscale.nix +++ b/modules/features/network/tailscale.nix @@ -1,31 +1,75 @@ { lib, config, ... }: + let cfg = config.my.features.network.tailscale; in { options.my.features.network.tailscale = { enable = lib.mkEnableOption "Tailscale VPN"; + acceptDns = lib.mkOption { type = lib.types.bool; default = false; - description = "Accept DNS configuration from Tailscale"; + description = "Accept DNS configuration from Tailscale."; }; + acceptRoutes = lib.mkOption { type = lib.types.bool; - default = true; - description = "Accept subnet routes from Tailscale"; + default = false; + description = "Accept subnet routes from Tailscale."; }; + + advertiseRoutes = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "10.50.0.0/16" ]; + description = "Subnet routes to advertise through this machine."; + }; + + routingFeatures = lib.mkOption { + type = lib.types.enum [ + "auto" + "none" + "client" + "server" + "both" + ]; + default = "auto"; + description = "Override Tailscale routing features. Usually leave this as auto."; + }; + + openFirewall = lib.mkOption { + type = lib.types.nullOr lib.types.bool; + default = null; + description = "Override Tailscale firewall opening. Usually leave this as null."; + }; + + extraSetFlags = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Additional flags to pass to `tailscale set`."; + }; + extraUpFlags = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; - description = "Additional flags to pass to tailscale up"; + description = "Additional flags to pass to `tailscale up`."; }; }; config = lib.mkIf cfg.enable { my.applications.tailscale = { enable = true; - inherit (cfg) acceptDns acceptRoutes extraUpFlags; + + inherit (cfg) + acceptDns + acceptRoutes + advertiseRoutes + routingFeatures + openFirewall + extraSetFlags + extraUpFlags + ; }; }; } diff --git a/profiles/platforms/laptop.nix b/profiles/platforms/laptop.nix index 20f357a..9a9729e 100644 --- a/profiles/platforms/laptop.nix +++ b/profiles/platforms/laptop.nix @@ -7,6 +7,5 @@ }; gui.camera.enable = true; identity.fingerprint.enable = true; - network.tailscale.enable = true; }; } diff --git a/profiles/workloads/srv.nix b/profiles/workloads/srv.nix index f0c433c..31892fb 100644 --- a/profiles/workloads/srv.nix +++ b/profiles/workloads/srv.nix @@ -1,7 +1,6 @@ { my.features = { cli.base.sshServer = true; - network.tailscale.enable = true; services.container.enable = true; }; } diff --git a/profiles/workloads/tailscale/client.nix b/profiles/workloads/tailscale/client.nix new file mode 100644 index 0000000..c9824b1 --- /dev/null +++ b/profiles/workloads/tailscale/client.nix @@ -0,0 +1,8 @@ +{ + my.features.network.tailscale = { + enable = true; + + acceptDns = false; + acceptRoutes = true; + }; +} diff --git a/profiles/workloads/tailscale/server.nix b/profiles/workloads/tailscale/server.nix new file mode 100644 index 0000000..dd41e9f --- /dev/null +++ b/profiles/workloads/tailscale/server.nix @@ -0,0 +1,12 @@ +{ + my.features.network.tailscale = { + enable = true; + + acceptDns = false; + acceptRoutes = false; + + advertiseRoutes = [ + "10.50.0.0/16" + ]; + }; +}