From 726681e5c231bf1ca7aeeae450b5ffe56258d9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Porta?= Date: Fri, 17 Jul 2026 23:15:07 +0000 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 19ed08c375be6d6a0b344ab1b9d3f76f3c29514b Author: João Porta Date: Fri Jul 17 23:12:12 2026 +0000 zsh commit 19df08188fa07b40b9b28a4665b015c4fa525dff Merge: 577d753 4e4cc38 Author: João Porta Date: Thu Jul 16 22:29:29 2026 +0000 Merge remote-tracking branch 'refs/remotes/origin/tailscale' into tailscale commit 577d753475be867146ebcacc79330a1e227043a5 Author: João Porta Date: Thu Jul 16 15:29:53 2026 +0000 tailscale commit 4e4cc381b9e333592184490ea9f749fe87ebbbb9 Author: João Porta Date: Thu Jul 16 15:29:53 2026 +0000 tailscale --- hosts/writter-deck/home.nix | 21 ++- .../home-manager/tailscale-daemon/default.nix | 142 ++++++++++++++++++ modules/home-manager/zshrc/default.nix | 32 ++-- result | 1 + result-activation | 1 + 5 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 modules/home-manager/tailscale-daemon/default.nix create mode 120000 result create mode 120000 result-activation diff --git a/hosts/writter-deck/home.nix b/hosts/writter-deck/home.nix index 60cebec..828abfd 100644 --- a/hosts/writter-deck/home.nix +++ b/hosts/writter-deck/home.nix @@ -1,8 +1,7 @@ -{ - config, - pkgs, - lib, - ... +{ config +, pkgs +, lib +, ... }: let @@ -47,6 +46,7 @@ in ../../modules/home-manager/tmux ../../modules/home-manager/eza ../../modules/home-manager/zoxide + ../../modules/home-manager/tailscale-daemon ]; home = { @@ -86,6 +86,10 @@ in eza.enable = true; bat.enable = true; zoxide.enable = true; + tailscale-daemon = { + enable = true; + acceptRoutes = true; + }; }; home.packages = with pkgs; [ @@ -97,11 +101,12 @@ in ripgrep fd mosh - tailscale fastfetch + lazygit stow - tmux + bluetuith ]; + # `tailscale`/`ts` shell helpers come from the tailscale-daemon module. # Non-NixOS host: fixes locales, ld paths, XDG desktop entries targets.genericLinux.enable = true; @@ -214,7 +219,7 @@ in serverAliveInterval = 30; }; "pc-ts" = { - hostname = "jpporta-nixos"; + hostname = "jpporta-nixos.taild23e4.ts.net"; user = "jpporta"; serverAliveInterval = 30; }; diff --git a/modules/home-manager/tailscale-daemon/default.nix b/modules/home-manager/tailscale-daemon/default.nix new file mode 100644 index 0000000..2d9c0a5 --- /dev/null +++ b/modules/home-manager/tailscale-daemon/default.nix @@ -0,0 +1,142 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.custom.tailscale-daemon; + + # The writter-deck is a non-NixOS host that can't create a real tailscale0 + # interface (missing TUN drivers). Userspace networking avoids that need. + # + # Layout: + # - Socket: %t/tailscaled/tailscaled.sock (per-session tmpfs) + # - State: $HOME/.local/state/tailscale/... (persistent) + # + # In systemd specifiers: + # %t → $XDG_RUNTIME_DIR (=/run/user/$UID) — works in systemd 249+ + # %S → $XDG_CONFIG_HOME (! NOT $XDG_STATE_HOME) — works in systemd 249+ + # + # Because %S points at the wrong place, we hard-code the persistent state + # path under $HOME/.local/state/, which is the XDG_STATE_HOME default. + homeDir = config.home.homeDirectory; + stateDir = "${homeDir}/.local/state/tailscale"; + stateFile = "${stateDir}/tailscaled.state"; + + # Becomes "$XDG_RUNTIME_DIR/tailscaled/tailscaled.sock" at runtime. + socketFile = "%t/tailscaled/tailscaled.sock"; +in +{ + options.custom.tailscale-daemon = { + enable = lib.mkEnableOption '' + Tailscale daemon (tailscaled) running as a systemd user service with + userspace networking. For non-NixOS hosts without TUN device drivers. + ''; + + acceptRoutes = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + If true, run `tailscale up --accept-routes` automatically on first + start. Otherwise you'll need to authenticate manually once. + ''; + }; + + acceptDns = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to accept DNS settings from the coordination server."; + }; + }; + + config = lib.mkIf cfg.enable { + # Create the persistent state dir up front so the systemd service can + # write to it immediately. + home.activation.createTailscaleDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + run mkdir -p ${lib.escapeShellArg stateDir} + run chmod 0700 ${lib.escapeShellArg stateDir} + ''; + + # Export the socket path (systemd-249-expanded) so any tool (systray, + # scripts) picks the user-scope daemon automatically. Note: this + # contains a literal `%t` which systemd will expand when the daemon + # or its consumers run. + home.sessionVariables.TAILSCALED_SOCKET = "/run/user/$(id -u)/tailscaled/tailscaled.sock"; + + home.packages = [ + pkgs.tailscale + + # Wrapper so the CLI client can find the user-scope socket without flags. + (pkgs.writeShellApplication { + name = "ts"; + runtimeInputs = [ pkgs.tailscale ]; + text = '' + exec tailscale --socket="/run/user/$(id -u)/tailscaled/tailscaled.sock" "$@" + ''; + }) + ]; + + systemd.user.services.tailscaled = { + Unit = { + Description = "Tailscale daemon (userspace networking, no TUN device)"; + # No "After=default.target" — it would deadlock since default.target + # is what brings us up. Just wait for the runtime dir to exist. + RequiresMountsFor = [ "%t" ]; + # Survive transient network blips / flap. + StartLimitIntervalSec = 60; + StartLimitBurst = 5; + }; + Service = { + Type = "notify"; + ExecStart = lib.escapeShellArgs [ + "${pkgs.tailscale}/bin/tailscaled" + "--tun=userspace-networking" + "--state=${stateFile}" + "--socket=${socketFile}" + ]; + # systemd-managed runtime dir: + # $XDG_RUNTIME_DIR/tailscaled (RuntimeDirectory, mode 0700) + # State is in $HOME/.local/state/tailscale and managed by us, not + # via StateDirectory, so we don't need to fight protectHome. + RuntimeDirectory = "tailscaled"; + RuntimeDirectoryMode = "0700"; + Restart = "on-failure"; + RestartSec = 5; + # Light sandboxing. systemd user services can't drop capabilities + # the way the system unit can, so we keep restrictions to a + # conservative subset that doesn't fight journald. + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = [ "%t/tailscaled" "${stateDir}" ]; + }; + Install.WantedBy = [ "default.target" "graphical-session.target" ]; + }; + + # One-shot: authenticate on first start if not already logged in. + # Idempotent: tailscale up is a no-op once you're logged in. + systemd.user.services.tailscale-up = { + Unit = { + Description = "Tailscale: authenticate and connect"; + After = [ "tailscaled.service" ]; + Requires = [ "tailscaled.service" ]; + }; + Service = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = lib.escapeShellArgs ([ + "${pkgs.tailscale}/bin/tailscale" + "--socket=${socketFile}" + "up" + (if cfg.acceptRoutes then "--accept-routes" else "--no-accept-routes") + (if cfg.acceptDns then "--accept-dns=true" else "--accept-dns=false") + ]); + # Don't fail the whole stack if auth isn't completed yet. + SuccessExitStatus = [ 0 1 ]; + }; + Install.WantedBy = [ "default.target" "graphical-session.target" ]; + }; + }; +} diff --git a/modules/home-manager/zshrc/default.nix b/modules/home-manager/zshrc/default.nix index e6e1fda..58f3b40 100644 --- a/modules/home-manager/zshrc/default.nix +++ b/modules/home-manager/zshrc/default.nix @@ -54,17 +54,31 @@ rot0 = "wlr-randr --output HDMI-A-1 --transform normal"; rotl = "wlr-randr --output HDMI-A-1 --transform 270"; rot180 = "wlr-randr --output HDMI-A-1 --transform 180"; + # Tailscale shortcuts (writter-deck only really needs these, but + # they don't hurt anyone else either). `ts` is the wrapper that + # points the CLI at the user-scope socket from the tailscale-daemon + # module. + ts = "tailscale --socket=/tmp/ts-$UID/tailscaled.sock"; + ts-status = "ts status"; + ts-ping = "ts ping -c 3 jpporta-nixos"; }; - initContent = '' - ${lib.optionalString config.custom.zsh.fastfetch "fastfetch"} - # Auto-rotate screen to portrait when running inside cage/foot - # on the writer-deck. Skipped over SSH and other non-Wayland sessions. - if [ -n "$WAYLAND_DISPLAY" ] && [ -n "$CAGE_RUNNING" ] && [ "$ROTATED" != "1" ]; then - export ROTATED=1 - wlr-randr --output HDMI-A-1 --transform 90 >/dev/null 2>&1 - fi - ''; + initContent = lib.mkMerge [ + (lib.mkOrder 700 '' + # Home Manager normally defines ZSH in .zshenv. Keep it available + # when .zshrc is sourced directly as well. + export ZSH="${config.programs.zsh.oh-my-zsh.package}/share/oh-my-zsh" + '') + (lib.mkOrder 1000 '' + ${lib.optionalString config.custom.zsh.fastfetch "fastfetch"} + # Auto-rotate screen to portrait when running inside cage/foot + # on the writer-deck. Skipped over SSH and other non-Wayland sessions. + if [ -n "$WAYLAND_DISPLAY" ] && [ -n "$CAGE_RUNNING" ] && [ "$ROTATED" != "1" ]; then + export ROTATED=1 + wlr-randr --output HDMI-A-1 --transform 90 >/dev/null 2>&1 + fi + '') + ]; }; }; } diff --git a/result b/result new file mode 120000 index 0000000..df23d83 --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/kldv63c3f6aqvk95rbwp7jf9ms7qjbb9-home-manager-generation \ No newline at end of file diff --git a/result-activation b/result-activation new file mode 120000 index 0000000..8f2f5d8 --- /dev/null +++ b/result-activation @@ -0,0 +1 @@ +/nix/store/z1w7hmcjbxlj4zif3lwb8ifkn66rjy60-home-manager-generation \ No newline at end of file