Skip to content

feat: tailscale - #381

Closed
shunkakinoki wants to merge 14 commits into
mainfrom
feat-tailscale
Closed

feat: tailscale#381
shunkakinoki wants to merge 14 commits into
mainfrom
feat-tailscale

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Nov 29, 2025

Copy link
Copy Markdown
Owner

Note

Adds a Tailscale home-manager module with systemd user services, integrates it into the galactica host using an encrypted auth key, and includes setup documentation and package updates.

  • Tailscale:
    • Home-Manager Module (home-manager/modules/tailscale/default.nix):
      • Defines services.tailscale options (auth key, routes, exit node, extra args).
      • Adds systemd user services: tailscaled and tailscale-up (auth via key or file), plus state/run dir setup.
    • Module Registration: Included in home-manager/modules/default.nix.
  • Host Configuration (galactica):
    • Enables services.tailscale with authKeyFile from agenix, disables routes/exit node, sets extra up args.
    • Adds secret mapping in named-hosts/galactica/secrets.nix and placeholder key keys/tailscale-auth.age.
  • Packages:
    • Adds tailscale to home-manager/packages/default.nix (removes Linux-only placement to general list).
  • Documentation:
    • New docs/TAILSCALE.md with setup, configuration, usage, and maintenance guidance.

Written by Cursor Bugbot for commit 1d9d669. Configure here.


Summary by cubic

Adds Tailscale VPN support via a reusable Home Manager module and host config for galactica, with auth key managed by agenix. Includes a setup guide and defaults that avoid routes and exit nodes.

  • New Features

    • Home Manager module for tailscaled and a one-shot tailscale up, with options for authKey/authKeyFile, routes, exit node, and extra args.
    • Galactica host enables Tailscale using an encrypted auth key (keys/tailscale-auth.age), with --reset and --accept-dns=false.
    • Secrets wired via named-hosts/galactica/secrets.nix; tailscale added to package list.
    • TAILSCALE.md covers setup, usage, and troubleshooting.
  • Migration

    • Generate a Tailscale auth key and encrypt: make encrypt-key-galactica KEY_FILE=/path/to/key.txt.
    • Apply config: make switch-galactica (or make switch), then verify with tailscale status.

Written for commit 1d9d669. Summary will update automatically on new commits.

Copilot AI review requested due to automatic review settings November 29, 2025 11:45
@mesa-dot-dev

mesa-dot-dev Bot commented Nov 29, 2025

Copy link
Copy Markdown

You do not have enough credits to review this pull request. Please purchase more credits to continue.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Nov 29, 2025

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Tailscale VPN integration added with configurable service management, authentication, routing options, and exit node support.
  • Documentation

    • Comprehensive Tailscale setup guide added covering prerequisites, configuration, deployment, troubleshooting, security best practices, and integration guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This PR introduces Tailscale integration into the dotfiles repository by adding a new Home Manager module with configurable systemd services, documenting Tailscale setup procedures, configuring the module for the galactica host, reorganizing package dependencies, and managing encrypted authentication secrets.

Changes

Cohort / File(s) Summary
Tailscale Home Manager Module
home-manager/modules/default.nix, home-manager/modules/tailscale/default.nix
Adds new Tailscale module to Home Manager with options for authentication (authKey/authKeyFile), daemon configuration (package, stateDir, socketPath), service behavior (acceptRoutes, advertiseExitNode, useExitNode, extraUpArgs), and extensibility (directories, files, serviceConfig). Implements systemd user services (tailscaled daemon and tailscale-up one-shot) with activation script for directory creation.
Tailscale Package Integration
home-manager/packages/default.nix
Moves tailscale from conditional Linux package list to main packages, making it available unconditionally.
Galactica Host Configuration
named-hosts/galactica/default.nix, named-hosts/galactica/secrets.nix, named-hosts/galactica/keys/tailscale-auth.age
Adds Tailscale service configuration for galactica host with settings (acceptRoutes: false, advertiseExitNode: false, useExitNode: "", extraUpArgs: ["--reset", "--accept-dns=false"]), references encrypted auth key file, and defines secrets entry for auth key management.
Documentation & Formatting
docs/TAILSCALE.md, Makefile
Adds comprehensive Tailscale documentation covering setup, configuration, deployment, usage, troubleshooting, security, and advanced topics; applies minor formatting to Makefile.

Sequence Diagram(s)

sequenceDiagram
    participant HM as Home Manager
    participant Act as Activation Script
    participant FS as Filesystem
    participant SD as Systemd
    participant TS as Tailscale Daemon
    
    HM->>Act: Trigger home activation on deploy
    Act->>FS: Create state/run directories with permissions
    FS-->>Act: Directories ready
    Act->>SD: Enable/start tailscaled service
    SD->>TS: Launch tailscaled daemon
    TS->>FS: Bind to socket (tailscaled.sock)
    TS-->>SD: Service started
    SD-->>HM: Activation complete
    HM->>SD: Start tailscale-up one-shot service
    SD->>TS: Execute 'tailscale up' with auth key & flags
    TS-->>SD: Authentication and configuration applied
    SD-->>HM: Service chain initialized
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Module complexity: The new Home Manager module spans ~120–150 lines with multiple service definitions, options, and activation logic requiring careful validation of systemd service configuration and Nix option types.
  • Multi-file coordination: Changes span 8 files with different purposes (module definition, host configuration, secrets management, documentation), each requiring separate reasoning.
  • Service interactions: Verify correct sequencing between tailscaled daemon and tailscale-up one-shot service, including argument construction and authentication handling.
  • Package reorganization: Confirm that moving tailscale to main packages aligns with host requirements and does not introduce unintended cross-platform availability.

Possibly related PRs

Poem

🐰 A rabbit hops through dotfiles with glee,
With Tailscale woven through NixOS so free!
Daemons and services, secrets all tight,
Encrypted auth keys now working just right! 🔐
From galactica's host to your home folder spree,
The tunnel is ready—let's mesh perfectly! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: tailscale' clearly and specifically describes the main change: adding Tailscale support to the dotfiles repository.
Description check ✅ Passed The description comprehensively details all changes including the Home Manager module, host configuration, package updates, and documentation, all directly related to the Tailscale feature addition.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-tailscale

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @shunkakinoki, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new feature to integrate Tailscale VPN into the dotfiles repository. It provides a declarative Home Manager module for managing the Tailscale service, along with comprehensive documentation covering its setup, configuration, and usage. The changes also include secure handling of Tailscale authentication keys using agenix and specific host configuration for galactica, ensuring a robust and secure private networking solution.

Highlights

  • New Tailscale Home Manager Module: Introduced a new Home Manager module (home-manager/modules/tailscale/default.nix) for declaratively managing the Tailscale VPN service, including daemon configuration, authentication key handling, and various tailscale up options.
  • Comprehensive Tailscale Documentation: Added a detailed guide (docs/TAILSCALE.md) covering the setup, configuration, usage, management, troubleshooting, and security best practices for Tailscale within the dotfiles repository.
  • Secure Authentication Key Management: Integrated Tailscale authentication key handling with agenix for secure storage and retrieval, demonstrated by the addition of named-hosts/galactica/keys/tailscale-auth.age and its entry in named-hosts/galactica/secrets.nix.
  • Host-Specific Tailscale Configuration: Configured Tailscale for the galactica host (named-hosts/galactica/default.nix), enabling the service with specific settings like disabled route acceptance/advertising and secure key usage.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new home-manager module for Tailscale, which is a great addition for managing VPN configuration declaratively. The changes are well-structured, including comprehensive documentation and integration with agenix for secret management.

However, I've found several issues in the new tailscale module that need to be addressed:

  • A critical bug in the module's logic prevents the Tailscale services from being enabled by default, even when services.tailscale.enable = true.
  • The module hardcodes paths for the tailscaled daemon, ignoring the configuration options provided.
  • The tailscale CLI commands are missing the required socket path, which will cause them to fail to connect to the daemon.
  • The logic for handling authentication keys is redundant and can be simplified.

I've provided specific comments and suggestions to fix these issues in the home-manager/modules/tailscale/default.nix file. I've also noted a couple of minor points in the new documentation file.

Once these issues are resolved, this will be a solid contribution.

cfg = config.modules.tailscale;

# Check if configuration is enabled
configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The logic for configEnabled is incorrect. It is defined as (cfg.serviceConfig != { } && cfg.serviceConfig != null), which evaluates to false if serviceConfig is not set (as it defaults to {}). This variable is then used with mkIf on lines 108 and 126, preventing the tailscaled and tailscale-up services from being created even when modules.tailscale.enable is true. This makes the module non-functional by default.

To fix this, you should remove the configEnabled variable entirely (lines 13-14), and remove the mkIf configEnabled wrappers from the service definitions on lines 108 and 126. The mkIf cfg.enable on line 100 is sufficient.

};

Service = {
ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The ExecStart command for the tailscaled service hardcodes paths and the package, ignoring the module's options. This prevents customization of the package version, state directory, and socket path. The command should use the values from the module's options to allow for proper configuration.

        ExecStart = "${cfg.tailscaled.package}/bin/tailscaled --state=${cfg.tailscaled.stateDir}/tailscaled.state --socket=${cfg.tailscaled.socketPath}";

Comment on lines +133 to +160
Service = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${pkgs.bash}/bin/bash -c '
# Determine auth key
AUTH_KEY=" "
if [ -n \"${cfg.authKey}\" ]; then
AUTH_KEY=\"${cfg.authKey}\"
elif [ -n \"${cfg.authKeyFile}\" ] && [ -f \"${cfg.authKeyFile}\" ]; then
AUTH_KEY=$(cat \"${cfg.authKeyFile}\")
fi

# Configure Tailscale with specified options
tailscale up \
${
optionalString (cfg.authKey != "") "--authkey=${cfg.authKey}"
} \
${
optionalString (cfg.authKeyFile != "") "--authkey=$AUTH_KEY"
} \
${optionalString cfg.acceptRoutes "--accept-routes"} \
${optionalString cfg.advertiseExitNode "--advertise-exit-node"} \
${
optionalString (cfg.useExitNode != "") "--exit-node=${cfg.useExitNode}"
} \
${concatStringsSep " " cfg.extraUpArgs}'";
ExecStop = "${pkgs.tailscale}/bin/tailscale down";
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The tailscale-up service definition has a few issues:

  1. Missing socket path: The tailscale up and tailscale down commands are missing the socket path. Since tailscaled is started with a custom socket, the CLI needs to be told where to find it via the --socket flag.
  2. Hardcoded package: The tailscale package is hardcoded, but it should use cfg.tailscaled.package to be consistent with the rest of the module.
  3. Redundant auth key logic: The logic for providing the --authkey is duplicated. If both authKey and authKeyFile are provided, it results in two --authkey flags being passed to the command.

Here is a suggested replacement that fixes these issues and simplifies the logic:

      Service = {
        Type = "oneshot";
        RemainAfterExit = true;
        ExecStart = "${pkgs.bash}/bin/bash -c '\n          # Determine auth key argument\n          AUTH_KEY_ARG=\"\"\n          if [ -n \"${cfg.authKey}\" ]; then\n            AUTH_KEY_ARG=\" --authkey=${cfg.authKey}\"\n          elif [ -n \"${cfg.authKeyFile}\" ] && [ -f \"${cfg.authKeyFile}\" ]; then\n            AUTH_KEY_ARG=\" --authkey=$(cat \"${cfg.authKeyFile}\")\"\n          fi\n\n          # Configure Tailscale with specified options\n          ${cfg.tailscaled.package}/bin/tailscale --socket ${cfg.tailscaled.socketPath} up \\$AUTH_KEY_ARG ${lib.optionalString cfg.acceptRoutes "--accept-routes"} ${lib.optionalString cfg.advertiseExitNode "--advertise-exit-node"} ${lib.optionalString (cfg.useExitNode != "") "--exit-node=${cfg.useExitNode}"} ${lib.concatStringsSep " " cfg.extraUpArgs}\n        '";
        ExecStop = "${cfg.tailscaled.package}/bin/tailscale --socket ${cfg.tailscaled.socketPath} down";
      };

Comment thread docs/TAILSCALE.md
**Solutions**:
```bash
# Check if auth key is properly decrypted
ls -la /run/agenix/keys/tailscale-auth.age

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The command to check if the auth key is properly decrypted seems incorrect. The decrypted secret file managed by agenix will likely not have the .age extension. The command should probably check for the file without this extension.

Suggested change
ls -la /run/agenix/keys/tailscale-auth.age
ls -la /run/agenix/keys/tailscale-auth

Comment thread docs/TAILSCALE.md
- **Secrets**: `named-hosts/galactica/secrets.nix`
- **Auth Key**: `named-hosts/galactica/keys/tailscale-auth.age` (encrypted)
- **State**: `~/.local/share/tailscale/`
- **Logs**: `~/.local/share/tailscale/*.log` No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line duplicates information about log file locations, which is already detailed in the "Log Files" section (lines 173-178). You can remove this line to keep the documentation concise and avoid redundancy.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on December 27

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

"--reset"
"--accept-dns=false"
];
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Configuration namespace mismatch prevents module usage

The module defines options under modules.tailscale but the configuration and documentation reference services.tailscale. This namespace mismatch prevents the Tailscale configuration from being recognized, causing the module to not work at all.

Additional Locations (2)

Fix in Cursor Fix in Web

cfg = config.modules.tailscale;

# Check if configuration is enabled
configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Services won't start without serviceConfig override

The configEnabled variable checks if serviceConfig is non-empty, but systemd services are only created when configEnabled is true. This means enabling Tailscale without providing a serviceConfig override prevents the services from being created at all. The condition should check cfg.enable instead.

Fix in Cursor Fix in Web

RemainAfterExit = true;
ExecStart = "${pkgs.bash}/bin/bash -c '
# Determine auth key
AUTH_KEY=" "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Auth key initialized with space character

The AUTH_KEY variable is initialized as " " (a space) instead of "" (empty string). This causes the auth key to contain a space when neither authKey nor authKeyFile is set, potentially passing an invalid auth key to tailscale up.

Fix in Cursor Fix in Web

} \
${
optionalString (cfg.authKeyFile != "") "--authkey=$AUTH_KEY"
} \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Duplicate authkey flags when both options set

When both authKey and authKeyFile are non-empty, the script adds two --authkey flags to the tailscale up command. The conditions should be mutually exclusive using else if logic, or prioritize one option over the other.

Fix in Cursor Fix in Web

};

Service = {
ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Configurable package option ignored in service commands

The service commands hardcode ${pkgs.tailscale} instead of using the configurable cfg.tailscaled.package option. This makes the tailscaled.package option non-functional, preventing users from specifying a custom Tailscale package version.

Additional Locations (1)

Fix in Cursor Fix in Web

};

Service = {
ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Configurable paths ignored in service configuration

The ExecStart command hardcodes state and socket paths instead of using cfg.tailscaled.stateDir and cfg.tailscaled.socketPath options. This renders these configuration options useless, preventing users from customizing the state directory or socket path locations.

Fix in Cursor Fix in Web

fi

# Configure Tailscale with specified options
tailscale up \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Tailscale command missing full path in script

The tailscale up command uses a bare tailscale without a full path. In systemd services, the PATH may not include the Tailscale binary location, causing the command to fail. It should use ${pkgs.tailscale}/bin/tailscale or ${cfg.tailscaled.package}/bin/tailscale.

Fix in Cursor Fix in Web

// cfg.serviceConfig.service or { };

Install.WantedBy = [ "default.target" ];
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Module only supports Linux but targets macOS

The module creates only systemd services, but the galactica host is macOS (aarch64-darwin). macOS doesn't have systemd, so the services won't be created or run. The documentation mentions launchd for macOS, but the implementation lacks launchd support, making the module non-functional on the target platform.

Additional Locations (1)

Fix in Cursor Fix in Web


# Declare directories and files for home-manager
home.file.".local/share/tailscale/tailscaled.state".source =
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/.local/state/tailscale/tailscaled.state";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Symlink target directory not created by activation

A symlink is created from .local/share/tailscale/tailscaled.state to .local/state/tailscale/tailscaled.state, but the activation script only creates .local/share/tailscale and the runtime directory. The .local/state/tailscale directory is never created, resulting in a broken symlink that points to a non-existent location.

Fix in Cursor Fix in Web

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
home-manager/modules/tailscale/default.nix (1)

1-8: Good module structure, but missing documentation.

The module follows home-manager's module structure correctly with proper option declarations. However, as per coding guidelines, custom modules should document all options. Consider adding a module-level comment explaining the purpose and usage.

Add module documentation:

 # Tailscale configuration
+# This module provides user-level Tailscale VPN service management.
+# 
+# NOTE: Currently only supports Linux via systemd. macOS support requires
+# manual Tailscale installation or extending this module with launchd agents.
+#
+# Example usage:
+#   modules.tailscale = {
+#     enable = true;
+#     authKeyFile = config.age.secrets."tailscale-auth".path;
+#   };
 {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e9a19cd and 1d9d669.

📒 Files selected for processing (8)
  • Makefile (1 hunks)
  • docs/TAILSCALE.md (1 hunks)
  • home-manager/modules/default.nix (1 hunks)
  • home-manager/modules/tailscale/default.nix (1 hunks)
  • home-manager/packages/default.nix (1 hunks)
  • named-hosts/galactica/default.nix (1 hunks)
  • named-hosts/galactica/keys/tailscale-auth.age (1 hunks)
  • named-hosts/galactica/secrets.nix (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.nix

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.nix: Use nixfmt for formatting all Nix files
Document complex configurations with comments in Nix files

**/*.nix: Use 2 spaces for indentation in Nix files
Keep line length under 100 characters in Nix files
Sort attribute sets alphabetically in Nix files
Use consistent spacing around operators in Nix files
Format lists and sets consistently in Nix files

Use treefmt.toml for formatting Nix files

**/*.nix: Use mkOption for configurable options in Nix modules
Implement proper typing for all options in Nix modules
Follow the Nix expression language style guide

Files:

  • named-hosts/galactica/secrets.nix
  • named-hosts/galactica/default.nix
  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
  • home-manager/packages/default.nix
**/*.{js,jsx,ts,tsx,json,jsonc,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Use Biome for code formatting as configured in biome.json

Files:

  • docs/TAILSCALE.md
**/default.nix

📄 CodeRabbit inference engine (CLAUDE.md)

Use default.nix files for module exports

Files:

  • named-hosts/galactica/default.nix
  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
  • home-manager/packages/default.nix
home-manager/modules/**/default.nix

📄 CodeRabbit inference engine (.cursor/rules/home-manager.mdc)

Custom modules should include proper option types and document all options

Each module in home-manager/modules/ should have a clear default.nix with proper option declarations following the home-manager module structure

Files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
home-manager/**/*.nix

📄 CodeRabbit inference engine (.cursor/rules/home-manager.mdc)

home-manager/**/*.nix: Use typed options whenever possible in Nix configurations
Document all configuration options in Nix modules and programs
Follow home-manager's module structure and keep configurations modular
Use proper indentation and formatting in Nix configuration files

Files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
  • home-manager/packages/default.nix
home-manager/modules/**/*.nix

📄 CodeRabbit inference engine (.cursor/rules/nix.mdc)

Document all custom modules and options

Files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
home-manager/modules/*/default.nix

📄 CodeRabbit inference engine (.cursor/rules/home-manager.mdc)

Custom modules must be located in home-manager/modules/<name>/ and must include a default.nix file

Files:

  • home-manager/modules/tailscale/default.nix
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/nix.mdc:0-0
Timestamp: 2025-11-25T09:35:01.066Z
Learning: Applies to home-manager/services/**/*.nix : Service configurations in `home-manager/services/` should follow systemd service conventions, include proper service dependencies, and have clear documentation for service parameters
📚 Learning: 2025-11-25T09:34:40.062Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T09:34:40.062Z
Learning: Document all major changes in Nix configurations

Applied to files:

  • docs/TAILSCALE.md
  • home-manager/packages/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/**/*.nix : Document all configuration options in Nix modules and programs

Applied to files:

  • docs/TAILSCALE.md
  • named-hosts/galactica/default.nix
  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:23.224Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:34:23.224Z
Learning: Run `make format` before committing code

Applied to files:

  • Makefile
📚 Learning: 2025-11-25T09:35:01.066Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/nix.mdc:0-0
Timestamp: 2025-11-25T09:35:01.066Z
Learning: Test configurations before committing using `nix flake check` and `home-manager build --show-trace`

Applied to files:

  • Makefile
📚 Learning: 2025-11-25T09:34:40.062Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T09:34:40.062Z
Learning: Applies to flake.nix : Maintain flake.nix as the main Nix configuration with proper structure

Applied to files:

  • Makefile
📚 Learning: 2025-11-25T09:34:47.854Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/github-workflows.mdc:0-0
Timestamp: 2025-11-25T09:34:47.854Z
Learning: Applies to .github/workflows/*.yml : CI Pipeline must run on all pull requests and main branch pushes, validating Nix flake checks, home-manager builds, nix-darwin builds, and system configurations

Applied to files:

  • Makefile
📚 Learning: 2025-11-25T09:34:40.062Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T09:34:40.062Z
Learning: Test Nix and home-manager configurations locally before pushing using `make test`

Applied to files:

  • Makefile
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/services/**/default.nix : Service configurations must include proper service definitions, handle dependencies correctly, and document service parameters

Applied to files:

  • named-hosts/galactica/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/services/*/default.nix : Service configurations should be located in `home-manager/services/<name>/` with proper service definitions and correct dependency handling

Applied to files:

  • named-hosts/galactica/default.nix
📚 Learning: 2025-11-25T09:35:01.066Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/nix.mdc:0-0
Timestamp: 2025-11-25T09:35:01.066Z
Learning: Applies to home-manager/services/**/*.nix : Service configurations in `home-manager/services/` should follow systemd service conventions, include proper service dependencies, and have clear documentation for service parameters

Applied to files:

  • named-hosts/galactica/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/programs/**/*.nix : Follow program-specific best practices in program configuration files

Applied to files:

  • named-hosts/galactica/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:40.062Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-11-25T09:34:40.062Z
Learning: Keep configurations modular across home-manager, hosts, and nix-darwin directories

Applied to files:

  • named-hosts/galactica/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/modules/*/default.nix : Custom modules must be located in `home-manager/modules/<name>/` and must include a `default.nix` file

Applied to files:

  • home-manager/modules/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/**/*.nix : Follow home-manager's module structure and keep configurations modular

Applied to files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:35:01.066Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/nix.mdc:0-0
Timestamp: 2025-11-25T09:35:01.066Z
Learning: Applies to home-manager/modules/**/*.nix : Document all custom modules and options

Applied to files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:35:01.066Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/nix.mdc:0-0
Timestamp: 2025-11-25T09:35:01.066Z
Learning: Applies to home-manager/modules/**/default.nix : Each module in `home-manager/modules/` should have a clear `default.nix` with proper option declarations following the home-manager module structure

Applied to files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:23.224Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:34:23.224Z
Learning: Applies to **/default.nix : Use `default.nix` files for module exports

Applied to files:

  • home-manager/modules/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/programs/*/default.nix : Program configurations should be located in `home-manager/programs/<name>/` and use home-manager's built-in modules when available

Applied to files:

  • home-manager/modules/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/modules/**/default.nix : Custom modules should include proper option types and document all options

Applied to files:

  • home-manager/modules/default.nix
  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/**/*.nix : Use typed options whenever possible in Nix configurations

Applied to files:

  • home-manager/modules/tailscale/default.nix
📚 Learning: 2025-11-25T09:34:55.014Z
Learnt from: CR
Repo: shunkakinoki/dotfiles PR: 0
File: .cursor/rules/home-manager.mdc:0-0
Timestamp: 2025-11-25T09:34:55.014Z
Learning: Applies to home-manager/programs/**/default.nix : Program configurations must include all necessary dependencies in their configuration

Applied to files:

  • home-manager/packages/default.nix
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Mesa Description
  • GitHub Check: Agent
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: nix-nixos
  • GitHub Check: nix-linux
  • GitHub Check: docker-build-push (linux/amd64, amd64, ubuntu-latest)
  • GitHub Check: docker-build-push (linux/arm64, arm64, ubuntu-24.04-arm)
  • GitHub Check: e2e-run (Ubuntu, ubuntu-latest)
  • GitHub Check: e2e-run (MacOS, macos-latest)
  • GitHub Check: e2e-run (NixOS, ubuntu-latest)
  • GitHub Check: lua-neovim
  • GitHub Check: lua-hammerspoon
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (7)
Makefile (1)

125-125: LGTM!

Minor formatting improvement that adds visual separation between the check and flake-check targets.

home-manager/modules/default.nix (1)

1-5: LGTM!

The new ./tailscale module is correctly added to the imports list and maintains alphabetical ordering. As per coding guidelines, custom modules should be located in home-manager/modules/<name>/ with a default.nix file.

home-manager/packages/default.nix (1)

67-67: LGTM!

Moving tailscale to the cross-platform package list enables support on both macOS (Darwin) and Linux, which aligns with the new galactica host configuration targeting aarch64-darwin.

docs/TAILSCALE.md (1)

156-169: Service management commands may not match module implementation.

The launchctl commands reference homebrew.mxcl.tailscaled, which is the Homebrew-managed service name. However, the Home Manager module creates systemd user services (tailscaled.service), which won't work on macOS at all since systemd is Linux-only.

Either update the module to support launchd on macOS, or clarify in the documentation that the current module only supports Linux and macOS users should install Tailscale via Homebrew or the official installer.

named-hosts/galactica/secrets.nix (1)

14-19: Secret entry correctly structured.

The new tailscale-auth.age secret entry follows the existing pattern with proper file path and public key configuration. Note that this depends on the referenced .age file containing valid encrypted content (see earlier comment about the placeholder file).

named-hosts/galactica/default.nix (1)

62-74: I encountered a repository clone error and cannot directly access the codebase to verify the specific claims about the Tailscale module structure and configuration paths.


Unable to verify option path mismatch and platform compatibility claims due to repository access issues.

The review comment raises two specific technical concerns:

  1. Option path mismatch: Claims the module defines modules.tailscale but the config uses services.tailscale
  2. Platform incompatibility: Claims the module uses systemd.user.services which is unavailable on macOS (aarch64-darwin)

Without direct access to the repository, I cannot confirm whether:

  • The tailscale module actually exposes options under modules.tailscale or services.tailscale
  • The module implementation includes systemd-specific service definitions
  • Platform-specific conditional logic exists for macOS/launchd support
  • The galactica host is indeed configured as an aarch64-darwin system

The code snippet shown in the review (lines 62-74) uses services.tailscale, which is a valid home-manager option path if the module exports it under that namespace. The actual validity depends on the module's internal structure.

home-manager/modules/tailscale/default.nix (1)

107-123: Custom modules.tailscale is never imported or used; galactica uses home-manager's built-in services.tailscale instead.

The review comment correctly identifies that systemd.user.services is Linux-only, but verification shows the custom modules.tailscale module (lines 107-123) is never imported by any host configuration, including galactica. Galactica uses home-manager's native services.tailscale (line 64 of named-hosts/galactica/default.nix), which handles platform differences internally. The custom module appears to be unused code.

If this custom module is intended for future use across platforms, consider adding platform-specific conditionals (mkIf pkgs.stdenv.isLinux { ... }). Otherwise, consider clarifying its purpose or removing it.

Likely an incorrect or invalid review comment.

Comment thread docs/TAILSCALE.md
Comment on lines +66 to +77
services.tailscale = {
enable = true; # Enable Tailscale service
acceptRoutes = false; # Accept advertised routes (default: false)
advertiseExitNode = false; # Advertise as exit node (default: false)
useExitNode = ""; # Exit node to use (default: none)
authKey = ""; # Auth key as string (alternative to authKeyFile)
authKeyFile = ""; # Path to auth key file (recommended)
extraUpArgs = [ # Additional arguments for tailscale up
"--reset"
"--accept-dns=false"
];
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Documentation shows incorrect option path.

The example configuration uses services.tailscale, but the actual module defines options under modules.tailscale. Update the documentation to match the module's option path.

-services.tailscale = {
+modules.tailscale = {
   enable = true;                    # Enable Tailscale service
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
services.tailscale = {
enable = true; # Enable Tailscale service
acceptRoutes = false; # Accept advertised routes (default: false)
advertiseExitNode = false; # Advertise as exit node (default: false)
useExitNode = ""; # Exit node to use (default: none)
authKey = ""; # Auth key as string (alternative to authKeyFile)
authKeyFile = ""; # Path to auth key file (recommended)
extraUpArgs = [ # Additional arguments for tailscale up
"--reset"
"--accept-dns=false"
];
};
modules.tailscale = {
enable = true; # Enable Tailscale service
acceptRoutes = false; # Accept advertised routes (default: false)
advertiseExitNode = false; # Advertise as exit node (default: false)
useExitNode = ""; # Exit node to use (default: none)
authKey = ""; # Auth key as string (alternative to authKeyFile)
authKeyFile = ""; # Path to auth key file (recommended)
extraUpArgs = [ # Additional arguments for tailscale up
"--reset"
"--accept-dns=false"
];
};
🤖 Prompt for AI Agents
In docs/TAILSCALE.md around lines 66 to 77, the example config uses the wrong
option path `services.tailscale`; update the documentation to use the module
path `modules.tailscale` instead (replace all occurrences in that example and
any surrounding explanatory text), ensuring the option names and values remain
unchanged and any references elsewhere in the file are also adjusted to
`modules.tailscale`.

Comment on lines +10 to +13
cfg = config.modules.tailscale;

# Check if configuration is enabled
configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

configEnabled logic is counter-intuitive.

Services are only enabled when serviceConfig != {}, but most users will set enable = true without providing a custom serviceConfig. This means enable = true alone won't actually start the services.

Consider simplifying to always enable services when cfg.enable is true:

-  # Check if configuration is enabled
-  configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);
+  # Services are enabled when the module is enabled
+  configEnabled = cfg.enable;

Or if serviceConfig is meant to be required, document this clearly and consider making it mandatory.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cfg = config.modules.tailscale;
# Check if configuration is enabled
configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);
cfg = config.modules.tailscale;
# Services are enabled when the module is enabled
configEnabled = cfg.enable;
🤖 Prompt for AI Agents
In home-manager/modules/tailscale/default.nix around lines 10 to 13, the
configEnabled check currently only enables services when serviceConfig is
non-empty which ignores cfg.enable; change the logic so services are considered
enabled when cfg.enable is true OR when a non-empty serviceConfig is provided
(e.g. configEnabled = cfg.enable || (cfg.serviceConfig != { } &&
cfg.serviceConfig != null)); alternatively, if serviceConfig must be required,
update the module to make serviceConfig mandatory and document that requirement.

Comment on lines +48 to +52
authKeyFile = mkOption {
type = types.path;
default = "";
description = "Path to file containing Tailscale auth key (better for secrets)";
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid type for authKeyFile option.

types.path cannot have an empty string as its default value. When a user doesn't specify authKeyFile, the empty string will fail type checking.

Use types.nullOr types.path with null as default, or types.str if an empty string is intentional:

     authKeyFile = mkOption {
-      type = types.path;
-      default = "";
+      type = types.nullOr types.path;
+      default = null;
       description = "Path to file containing Tailscale auth key (better for secrets)";
     };

Then update the conditionals in the service definition to check for null instead of empty string.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
authKeyFile = mkOption {
type = types.path;
default = "";
description = "Path to file containing Tailscale auth key (better for secrets)";
};
authKeyFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Path to file containing Tailscale auth key (better for secrets)";
};
🤖 Prompt for AI Agents
In home-manager/modules/tailscale/default.nix around lines 48 to 52, the
authKeyFile option uses types.path but sets default to an empty string which
fails type checking; change the option to use types.nullOr types.path with
default = null (or switch to types.str if you intend to allow an empty string),
and then update any service definition conditionals that currently test for an
empty string to instead test for null (e.g., use builtins.isNull / or compare to
null) so the option validation and runtime checks are consistent.

Comment on lines +115 to +120
Service = {
ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";
Restart = "on-failure";
RestartSec = 5;
}
// cfg.serviceConfig.service or { };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Inconsistent package reference.

Line 116 uses pkgs.tailscale directly instead of the configurable cfg.tailscaled.package option defined at line 21-25. This prevents users from overriding the package.

-        ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";
+        ExecStart = "${cfg.tailscaled.package}/bin/tailscaled --state=${cfg.tailscaled.stateDir}/tailscaled.state --socket=${cfg.tailscaled.socketPath}";

Also use cfg.tailscaled.stateDir and cfg.tailscaled.socketPath for consistency with the defined options.

🤖 Prompt for AI Agents
In home-manager/modules/tailscale/default.nix around lines 115 to 120, the
ExecStart line currently hardcodes pkgs.tailscale and literal paths; change it
to use the configurable options instead — reference cfg.tailscaled.package for
the tailscaled binary location and use cfg.tailscaled.stateDir and
cfg.tailscaled.socketPath for the --state and --socket arguments so users can
override the package and paths via the cfg options defined earlier.

Comment on lines +136 to +158
ExecStart = "${pkgs.bash}/bin/bash -c '
# Determine auth key
AUTH_KEY=" "
if [ -n \"${cfg.authKey}\" ]; then
AUTH_KEY=\"${cfg.authKey}\"
elif [ -n \"${cfg.authKeyFile}\" ] && [ -f \"${cfg.authKeyFile}\" ]; then
AUTH_KEY=$(cat \"${cfg.authKeyFile}\")
fi

# Configure Tailscale with specified options
tailscale up \
${
optionalString (cfg.authKey != "") "--authkey=${cfg.authKey}"
} \
${
optionalString (cfg.authKeyFile != "") "--authkey=$AUTH_KEY"
} \
${optionalString cfg.acceptRoutes "--accept-routes"} \
${optionalString cfg.advertiseExitNode "--advertise-exit-node"} \
${
optionalString (cfg.useExitNode != "") "--exit-node=${cfg.useExitNode}"
} \
${concatStringsSep " " cfg.extraUpArgs}'";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Shell script issues in tailscale-up service.

Several problems with the ExecStart script:

  1. Line 138: AUTH_KEY=" " uses a space as default, which could cause issues
  2. Lines 148-152: Both cfg.authKey and cfg.authKeyFile can produce --authkey arguments, potentially duplicating the flag
  3. Quoting: The nested bash script with Nix string interpolation is fragile

Consider refactoring to avoid duplicate --authkey flags:

-            ${
-                      optionalString (cfg.authKey != "") "--authkey=${cfg.authKey}"
-                    } \
-            ${
-                      optionalString (cfg.authKeyFile != "") "--authkey=$AUTH_KEY"
-                    } \
+            ${
+              if cfg.authKey != "" then "--authkey=${cfg.authKey}"
+              else if cfg.authKeyFile != null then "--authkey=$AUTH_KEY"
+              else ""
+            } \

Also consider using pkgs.writeShellScript for cleaner script management.

Comment on lines +1 to +9
# This is a placeholder for the encrypted Tailscale auth key
# To encrypt your actual auth key, use:
# make encrypt-key-galactica KEY_FILE=/path/to/your/tailscale-auth-key.txt
#
# To get a Tailscale auth key:
# 1. Log in to https://login.tailscale.com/admin
# 2. Go to Settings -> Keys
# 3. Generate a new auth key (recommended: non-ephemeral, reusable)
# 4. Save the key to a file and encrypt it using the command above

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Placeholder file will cause runtime failure.

This .age file contains plaintext instructions instead of encrypted content. When agenix attempts to decrypt this file during activation, it will fail because the content is not a valid age-encrypted payload. The authKeyFile reference in named-hosts/galactica/default.nix depends on this file being properly encrypted.

Either:

  1. Remove this placeholder and add tailscale-auth.age to .gitignore until a real key is encrypted, or
  2. Create an actual encrypted placeholder that can be decrypted (even if empty), or
  3. Gate the Tailscale configuration behind a check for the secret's existence.
🤖 Prompt for AI Agents
In named-hosts/galactica/keys/tailscale-auth.age (lines 1–9) the file contains
plaintext instructions instead of an age-encrypted payload, which will cause
agenix to fail at activation; fix by either (A) removing this plaintext file and
adding keys/tailscale-auth.age to .gitignore (and ensure
named-hosts/galactica/default.nix no longer unconditionally references the
secret), or (B) replace the file with a valid age-encrypted payload (you can
generate an encrypted empty or placeholder secret using age/age-keygen and put
the ciphertext here), or (C) change named-hosts/galactica/default.nix to gate
the Tailscale authKeyFile reference behind a file-existence/optionalSecret check
so activation won’t fail when the encrypted file is absent or invalid.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds Tailscale VPN integration to the dotfiles repository for the galactica host (macOS). The implementation includes a new home-manager module for Tailscale configuration, encrypted authentication key management using agenix, and comprehensive documentation.

Key changes:

  • Created a new home-manager module at home-manager/modules/tailscale/default.nix with systemd service definitions
  • Added Tailscale configuration to the galactica host with encrypted auth key support via agenix
  • Moved the tailscale package from Linux-specific to cross-platform package list

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
home-manager/modules/tailscale/default.nix New home-manager module defining Tailscale configuration options and systemd services (Note: systemd won't work on macOS)
home-manager/modules/default.nix Registers the new Tailscale module
home-manager/packages/default.nix Moves tailscale package from Linux-only to cross-platform section
named-hosts/galactica/default.nix Configures Tailscale service for the galactica host with auth key and connection options
named-hosts/galactica/secrets.nix Adds agenix secret configuration for encrypted Tailscale auth key
named-hosts/galactica/keys/tailscale-auth.age Placeholder file with instructions for encrypting the Tailscale auth key
docs/TAILSCALE.md Comprehensive documentation covering setup, configuration, usage, troubleshooting, and best practices
Makefile Minor formatting change (extra blank line)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

};

# Tailscale configuration with auth key support
services.tailscale = {

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module defines options under modules.tailscale, but this configuration uses services.tailscale. This will cause an "unknown option" error. Change this to modules.tailscale to match the module definition in home-manager/modules/tailscale/default.nix.

Suggested change
services.tailscale = {
modules.tailscale = {

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +91
directories = mkOption {
type = types.attrsOf types.anything;
default = { };
description = "Integration with home-manager's directories option";
};

files = mkOption {
type = types.attrsOf types.anything;
default = { };
description = "Integration with home-manager's files option";
};

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The directories and files options are defined but never used in the module configuration. These options appear to serve no purpose and should either be utilized or removed to avoid confusion.

Suggested change
directories = mkOption {
type = types.attrsOf types.anything;
default = { };
description = "Integration with home-manager's directories option";
};
files = mkOption {
type = types.attrsOf types.anything;
default = { };
description = "Integration with home-manager's files option";
};

Copilot uses AI. Check for mistakes.
Comment thread docs/TAILSCALE.md
Comment on lines +303 to +312
services.tailscale = {
enable = true;
authKeyFile = config.age.secrets."keys/tailscale-auth.age".path;
extraUpArgs = [
"--reset"
"--accept-dns=false"
"--login-server=https://login.tailscale.com"
];
};
```

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change services.tailscale to modules.tailscale to match the actual module definition.

Copilot uses AI. Check for mistakes.
};

Service = {
ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service uses ${pkgs.tailscale} directly instead of ${cfg.tailscaled.package}, which ignores the configurable tailscaled.package option. This should be changed to ${cfg.tailscaled.package} to respect the user's package selection. Same issue exists on line 159.

Suggested change
ExecStart = "${pkgs.tailscale}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";
ExecStart = "${cfg.tailscaled.package}/bin/tailscaled --state=${config.xdg.dataHome}/tailscale/tailscaled.state --socket=${config.xdg.runtimeDir}/tailscale/tailscaled.sock";

Copilot uses AI. Check for mistakes.
authKey = mkOption {
type = types.str;
default = "";
description = "Tailscale auth key (use agenix for secrets)";

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authKey option stores secrets as plain text in the Nix store, which is world-readable. This is a security risk. Consider deprecating this option in favor of authKeyFile only, or add a warning in the description that this option should not be used for sensitive data.

Suggested change
description = "Tailscale auth key (use agenix for secrets)";
description = ''
Tailscale auth key.
WARNING: The value of this option will be stored as plain text in the Nix store,
which is world-readable. This is a security risk and this option should NOT be used
for sensitive data. Use `authKeyFile` with a properly secured file for secrets instead.
'';

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +37
stateDir = mkOption {
type = types.str;
default = "${config.xdg.dataHome}/tailscale";
description = "Directory for Tailscale state files";
};

socketPath = mkOption {
type = types.str;
default = "${config.xdg.runtimeDir}/tailscale/tailscaled.sock";
description = "Socket path for Tailscale daemon";
};

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stateDir and socketPath options are defined but never used in the actual service configuration. Lines 116 uses hardcoded paths instead of these options. Either use these options consistently (e.g., cfg.tailscaled.stateDir and cfg.tailscaled.socketPath) or remove them to avoid confusion.

Copilot uses AI. Check for mistakes.
optionalString (cfg.useExitNode != "") "--exit-node=${cfg.useExitNode}"
} \
${concatStringsSep " " cfg.extraUpArgs}'";
ExecStop = "${pkgs.tailscale}/bin/tailscale down";

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ${cfg.tailscaled.package} instead of ${pkgs.tailscale} to respect the configurable package option.

Suggested change
ExecStop = "${pkgs.tailscale}/bin/tailscale down";
ExecStop = "${cfg.tailscaled.package}/bin/tailscale down";

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +152
optionalString (cfg.authKey != "") "--authkey=${cfg.authKey}"
} \
${
optionalString (cfg.authKeyFile != "") "--authkey=$AUTH_KEY"
} \

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both conditions on lines 148 and 151 can be true simultaneously, which would result in --authkey being passed twice to tailscale up. This will cause an error. Use else if logic or ensure only one auth key source is used. Additionally, line 148 directly embeds the auth key in the command which will expose it in process lists, while line 151 correctly uses a variable.

Suggested change
optionalString (cfg.authKey != "") "--authkey=${cfg.authKey}"
} \
${
optionalString (cfg.authKeyFile != "") "--authkey=$AUTH_KEY"
} \
optionalString ((cfg.authKey != "") || (cfg.authKeyFile != "")) "--authkey=$AUTH_KEY"
} \

Copilot uses AI. Check for mistakes.
Comment thread docs/TAILSCALE.md
Comment on lines +66 to +78
services.tailscale = {
enable = true; # Enable Tailscale service
acceptRoutes = false; # Accept advertised routes (default: false)
advertiseExitNode = false; # Advertise as exit node (default: false)
useExitNode = ""; # Exit node to use (default: none)
authKey = ""; # Auth key as string (alternative to authKeyFile)
authKeyFile = ""; # Path to auth key file (recommended)
extraUpArgs = [ # Additional arguments for tailscale up
"--reset"
"--accept-dns=false"
];
};
```

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation shows the module being configured as services.tailscale, but the actual module defines options under modules.tailscale (see line 16 in home-manager/modules/tailscale/default.nix). Update all documentation examples to use modules.tailscale instead of services.tailscale.

Copilot uses AI. Check for mistakes.
Comment thread docs/TAILSCALE.md
Comment on lines +275 to +284
services.tailscale = {
enable = true;
authKeyFile = config.age.secrets."keys/tailscale-auth.age".path;
extraUpArgs = [
"--reset"
"--accept-dns=false"
"--tag=tag:server"
];
};
```

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change services.tailscale to modules.tailscale to match the actual module definition.

Copilot uses AI. Check for mistakes.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 8 files

Prompt for AI agents (all 3 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="docs/TAILSCALE.md">

<violation number="1" location="docs/TAILSCALE.md:324">
The SSH config example disables host key verification (`StrictHostKeyChecking no`), which exposes users to MITM attacks; keep host verification enabled instead.</violation>
</file>

<file name="home-manager/modules/tailscale/default.nix">

<violation number="1" location="home-manager/modules/tailscale/default.nix:13">
`configEnabled` prevents the Tailscale services from ever being created because it only becomes true when `serviceConfig` is non-empty, so enabling the module still results in no running service.</violation>

<violation number="2" location="home-manager/modules/tailscale/default.nix:43">
`authKeyFile` is typed as a path but defaults to an empty string, which violates the option’s type constraint and causes the module to fail unless the user overrides it.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

Comment thread docs/TAILSCALE.md
Host *.ts.net
User your-username
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking no

@cubic-dev-ai cubic-dev-ai Bot Nov 29, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SSH config example disables host key verification (StrictHostKeyChecking no), which exposes users to MITM attacks; keep host verification enabled instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/TAILSCALE.md, line 324:

<comment>The SSH config example disables host key verification (`StrictHostKeyChecking no`), which exposes users to MITM attacks; keep host verification enabled instead.</comment>

<file context>
@@ -0,0 +1,368 @@
+Host *.ts.net
+  User your-username
+  IdentityFile ~/.ssh/id_ed25519
+  StrictHostKeyChecking no
+  UserKnownHostsFile ~/.ssh/known_hosts.tailscale
+```
</file context>
Fix with Cubic

# Optional auth key
authKey = mkOption {
type = types.str;
default = "";

@cubic-dev-ai cubic-dev-ai Bot Nov 29, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authKeyFile is typed as a path but defaults to an empty string, which violates the option’s type constraint and causes the module to fail unless the user overrides it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/modules/tailscale/default.nix, line 43:

<comment>`authKeyFile` is typed as a path but defaults to an empty string, which violates the option’s type constraint and causes the module to fail unless the user overrides it.</comment>

<file context>
@@ -0,0 +1,178 @@
+    # Optional auth key
+    authKey = mkOption {
+      type = types.str;
+      default = &quot;&quot;;
+      description = &quot;Tailscale auth key (use agenix for secrets)&quot;;
+    };
</file context>
Fix with Cubic

cfg = config.modules.tailscale;

# Check if configuration is enabled
configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);

@cubic-dev-ai cubic-dev-ai Bot Nov 29, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configEnabled prevents the Tailscale services from ever being created because it only becomes true when serviceConfig is non-empty, so enabling the module still results in no running service.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/modules/tailscale/default.nix, line 13:

<comment>`configEnabled` prevents the Tailscale services from ever being created because it only becomes true when `serviceConfig` is non-empty, so enabling the module still results in no running service.</comment>

<file context>
@@ -0,0 +1,178 @@
+  cfg = config.modules.tailscale;
+
+  # Check if configuration is enabled
+  configEnabled = (cfg.serviceConfig != { } &amp;&amp; cfg.serviceConfig != null);
+in
+{
</file context>
Suggested change
configEnabled = (cfg.serviceConfig != { } && cfg.serviceConfig != null);
configEnabled = cfg.enable;
Fix with Cubic

@shunkakinoki
shunkakinoki deleted the feat-tailscale branch December 12, 2025 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants