☃️

NixのHome ManagerでTailscaleをインストールする(systemdとlaunchdの設定)

2025/03/06に公開

はじめに

先日MacBookにNixのHome Managerを導入したのですが、その時にHomebrewを削除した快感が忘れられず、VPSのUbuntuにも導入してしまいました。

いつ契約したかもわからないVPSでしたが、 home.nix をそのまま持っていくだけでMacBookと同じ環境を再現できて感動です。 sudo apt remove しまくりたいと思います。

Tailscaleやmoshで外部から接続したい

さて、VPSなので当然外部から簡単に接続する方法を考えたいです。有名なのはプライベートネットワークを構築するための Tailscale や不安定な通信環境でSSHっぽいことができる mosh 辺りでしょうか。

Home Managerでは基本的に

home.nix
home.packages = with pkgs; [
  mosh
  tailscale
];

これだけでパッケージのインストールができるのですが、デーモン化するためにsystemd等の設定が必要な場合があります。

実際にmoshは勝手に動きましたが、tailscaleはそのままでは動きません。また、moshの代わりにインストールを試みたEternal Terminalもそのままでは動きませんでした。

systemdとlaunchdを設定する

systemdとlaunchdは home.nix 内で設定することができます。launchdはmacOSの「ログイン項目」になるものです。

以下はTailscaleとEternal Terminalの例です。

Tailscale

home.nix
{ config, pkgs, ... }:
let
  homeDirectory = /home/nixuser
in {
  # 省略

  # Tailscaleをインストール
  home.packages = [ pkgs.tailscale ];

  # Linux(Ubuntu)の場合はsystemdを設定
  systemd.user.services.tailscaled = if pkgs.stdenv.isDarwin then
    null
  else
    {
      Unit = {
        Description = "Tailscale Daemon";
        After = [ "network.target" ];
      };
      Service = {
        # 制限されたモードでtailscaledを立ち上げる
        # https://tailscale.com/kb/1112/userspace-networking?q=userspace
        # `${homeDirectory}/.nix-profile/bin/tailscaled` でも良いはず
        ExecStart = "${pkgs.tailscale}/bin/tailscaled --tun=userspace-networking --state=${homeDirectory}/.tailscale.state";
        Restart = "always";
      };
      Install = {
        WantedBy = [ "default.target" ];
      };
    };

    # macOSの場合はlaunchdを設定
    launchd.agents.tailscaled = if pkgs.stdenv.isDarwin then
      {
        enable = true;
        config = {
          Label = "com.nixuser.tailscaled";
          # 制限されたモードでtailscaledを立ち上げる
          ProgramArguments = [
            "${pkgs.tailscale}/bin/tailscaled"
            "--tun=userspace-networking"
            "--state=${$homeDirectory}/.tailscale.state"
          ];
          RunAtLoad = true;
          KeepAlive = true;
        };
      };
    else
      null;

それぞれ有効化します。

# systemdをユーザー権限で設定
systemctl --user enable --now tailscaled

# launchdをユーザー権限で設定
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.nixuser.tailscaled.plist

# ログイン
tailscale up

Eternal Terminal

home.nix
{ config, pkgs, ... }:
let
  homeDirectory = /home/nixuser
in {
  # 省略

  # Eternal Terminalをインストール
  home.packages = [ pkgs.eternal-terminal ];

  # Linux(Ubuntu)の場合はsystemdを設定
  systemd.user.services.tailscaled = if pkgs.stdenv.isDarwin then
    null
  else
    {
      Unit = {
        Description = "Tailscale Daemon";
        After = [ "network.target" ];
      };
      Service = {
        ExecStart = "${pkgs.eternal-terminal}/bin/etserver";
        Restart = "always";
      };
      Install = {
        WantedBy = [ "default.target" ];
      };
    };

    # macOSの場合はlaunchdを設定
    launchd.agents.tailscaled = if pkgs.stdenv.isDarwin then
      {
        enable = true;
        config = {
          Label = "com.nixuser.etserver";
          ProgramArguments = [ "${pkgs.eternal-terminal}/bin/etserver" ];
          RunAtLoad = true;
          KeepAlive = true;
        };
      };
    else
      null;
systemctl --user enable --now tailscaled
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.nixuser.etserver.plist

Eternal Terminalの接続先サーバーの場合は、以下のようにシンボリックリンクを作成しておきます。

sudo ln -s $HOME/.nix-profile/bin/etterminal /usr/local/bin/etterminal

接続を受け入れるときに etterminal コマンドを実行するのですが、 ~/.nix-profile/bin のパスが認識されないためです。

もしくは接続時にクライアント側からパス(サーバー側の etterminal のパス)を指定します。

et nixuser@hostname --terminal-path=/home/nixuser/.nix-profile/bin/etterminal

Home Managerは便利ですが、

  • OSのシステム環境
  • OSのユーザー環境
  • NixのHome Manager環境
  • Nixの開発シェル環境

の区別がややこしいですね。

Discussion