diff --git a/config/default.nix b/config/default.nix index df57d38a1..8d4f03db2 100644 --- a/config/default.nix +++ b/config/default.nix @@ -17,6 +17,7 @@ in ./git-ai ./gomi ./ghostty + ./input-leap ./iterm2 ./hammerspoon ./jj diff --git a/config/input-leap/default.nix b/config/input-leap/default.nix new file mode 100644 index 000000000..9c251dcdb --- /dev/null +++ b/config/input-leap/default.nix @@ -0,0 +1,16 @@ +{ config, pkgs, ... }: +let + inherit (pkgs) lib; + inherit (pkgs.stdenv) isDarwin isLinux; +in +{ + # Server config on galactica (macOS) + home.file."Library/Application Support/InputLeap/InputLeap.conf" = lib.mkIf isDarwin { + source = ./server.conf; + }; + + # Server config on galactica (XDG fallback) + xdg.configFile."InputLeap/InputLeap.conf" = lib.mkIf isDarwin { + source = ./server.conf; + }; +} diff --git a/config/input-leap/server.conf b/config/input-leap/server.conf new file mode 100644 index 000000000..9fa812ebd --- /dev/null +++ b/config/input-leap/server.conf @@ -0,0 +1,16 @@ +section: screens + galactica: + matic: +end + +section: links + galactica: + right = matic + matic: + left = galactica +end + +section: options + keystroke(super+shift+left) = switchInDirection(left) + keystroke(super+shift+right) = switchInDirection(right) +end diff --git a/docs/INPUT_LEAP.md b/docs/INPUT_LEAP.md new file mode 100644 index 000000000..5a9ade7fc --- /dev/null +++ b/docs/INPUT_LEAP.md @@ -0,0 +1,107 @@ +# Input Leap + +Input Leap shares keyboard and mouse between machines over the network. Traffic flows over Tailscale (WireGuard-encrypted), so Input Leap's own TLS is disabled. + +## Architecture + +| Machine | Role | Service type | +|---|---|---| +| galactica | Server (shares keyboard/mouse) | launchd agent | +| matic | Client (receives input) | systemd user service | +| kyber | Not configured (headless server) | N/A | + +## Config files + +| File | Purpose | +|---|---| +| `config/input-leap/server.conf` | Screen layout and hotkeys | +| `config/input-leap/default.nix` | Deploys server config to galactica | +| `home-manager/services/input-leap/default.nix` | Server (launchd) and client (systemd) services | + +## Screen layout + +``` +[ galactica ] --right--> [ matic ] +[ matic ] --left--> [ galactica ] +``` + +Defined in `config/input-leap/server.conf`. Edit `right`/`left`/`up`/`down` to match physical monitor positions. + +## Hotkeys + +- `Super+Shift+Left` — switch to left screen +- `Super+Shift+Right` — switch to right screen + +## Setup + +### 1. Deploy + +**galactica:** +```sh +make switch-galactica +``` + +**matic:** +```sh +make build HOST=matic && make switch HOST=matic +``` + +### 2. Verify Tailscale connectivity + +Both machines must be on the same tailnet: + +```sh +tailscale ping galactica # from matic +tailscale ping matic # from galactica +``` + +### 3. Services start automatically + +- **galactica**: launchd starts `input-leaps` on `:24800` +- **matic**: systemd starts `input-leapc` connecting to `galactica:24800` + +## Troubleshooting + +### Check server logs (galactica) + +```sh +cat /tmp/input-leap-server.log +cat /tmp/input-leap-server.error.log +``` + +### Check client logs (matic) + +```sh +journalctl --user -u input-leap-client -f +``` + +### Restart services + +**galactica:** +```sh +launchctl kickstart -k gui/$(id -u)/org.nix-community.home.input-leap-server +``` + +**matic:** +```sh +systemctl --user restart input-leap-client +``` + +### MagicDNS not resolving + +If `galactica` doesn't resolve from matic, use the Tailscale IP: + +```sh +# Get galactica's Tailscale IP +tailscale ip -4 galactica +``` + +Then update `home-manager/services/input-leap/default.nix` to use the IP instead of the hostname. + +### macOS accessibility permissions + +Input Leap needs Accessibility permissions on macOS to control keyboard/mouse. On first run, macOS will prompt to grant access in System Settings > Privacy & Security > Accessibility. + +### Wayland/X11 + +The client on matic runs under Hyprland (Wayland). Input Leap's Wayland support requires `libei`. If input doesn't work, check that `input-leap` was built with `libei` support. diff --git a/docs/TAILSCALE.md b/docs/TAILSCALE.md new file mode 100644 index 000000000..1eba1c25f --- /dev/null +++ b/docs/TAILSCALE.md @@ -0,0 +1,105 @@ +# Tailscale + +Tailscale VPN connects all machines via WireGuard tunnels with MagicDNS. + +## Machines + +| Machine | OS | Method | Config location | +|---|---|---|---| +| galactica | macOS (aarch64-darwin) | Homebrew cask `tailscale-app` | `nix-darwin/config/homebrew.nix` | +| matic | NixOS (x86_64-linux) | `services.tailscale.enable` | `named-hosts/matic/default.nix` | +| kyber | Ubuntu (x86_64-linux) | Home-manager module `modules.tailscale` | `named-hosts/kyber/default.nix` | + +## galactica (macOS) + +Installed as a Homebrew cask (`tailscale-app`). The CLI is also available via `home-manager/packages/default.nix` (`tailscale`). + +Managed through the macOS menu bar app. No nix service configuration. + +### Setup + +1. `make switch-galactica` installs the app +2. Open Tailscale from Applications and sign in + +## matic (NixOS) + +Uses the native NixOS Tailscale module: + +```nix +# named-hosts/matic/default.nix +services.tailscale.enable = true; +``` + +This creates and enables the `tailscaled` systemd service automatically. + +### Setup + +1. `make build HOST=matic && make switch HOST=matic` +2. Authenticate: + +```sh +sudo tailscale login +``` + +3. Verify: + +```sh +tailscale status +``` + +## kyber (Ubuntu) + +Uses the custom home-manager module (`home-manager/modules/tailscale/`), which installs a system-level `tailscaled` service via `installSystemService`: + +```nix +# named-hosts/kyber/default.nix +modules.tailscale = { + enable = true; + installSystemService = true; + extraUpArgs = [ + "--reset" + "--accept-dns=false" + ]; +}; +``` + +DNS acceptance is disabled (`--accept-dns=false`) on kyber. + +### Setup + +The initial bootstrap script (`named-hosts/kyber/setup.sh`) handles Tailscale installation: + +```sh +curl -fsSL https://tailscale.com/install.sh | sh +sudo systemctl enable --now tailscaled +sudo tailscale up +``` + +After the initial setup, subsequent rebuilds are managed via home-manager. + +## MagicDNS + +With all machines on the same tailnet, they resolve each other by hostname: + +```sh +ping galactica +ping matic +ping kyber +``` + +If MagicDNS isn't working, use Tailscale IPs directly: + +```sh +tailscale ip -4 galactica +tailscale ip -4 matic +tailscale ip -4 kyber +``` + +## Useful commands + +```sh +tailscale status # list connected nodes +tailscale ping # check latency to a node +tailscale ip -4 # get Tailscale IPv4 address +tailscale netcheck # diagnose connectivity +``` diff --git a/home-manager/default.nix b/home-manager/default.nix index cc0b3190c..9b9df8530 100644 --- a/home-manager/default.nix +++ b/home-manager/default.nix @@ -16,7 +16,12 @@ let sources = { }; }; services = import ./services { - inherit config lib pkgs; + inherit + config + inputs + lib + pkgs + ; }; in { diff --git a/home-manager/services/default.nix b/home-manager/services/default.nix index e6b6d7223..982f78599 100644 --- a/home-manager/services/default.nix +++ b/home-manager/services/default.nix @@ -1,5 +1,6 @@ { config, + inputs, lib, pkgs, ... @@ -12,6 +13,7 @@ let docker = import ./docker { inherit lib pkgs; }; dockerPostgres = import ./docker-postgres { inherit pkgs; }; dotfilesUpdater = import ./dotfiles-updater { inherit pkgs; }; + inputLeap = import ./input-leap { inherit inputs pkgs; }; makeUpdater = import ./make-updater { inherit pkgs; }; neversslKeepalive = import ./neverssl-keepalive { inherit pkgs; }; ollama = import ./ollama { inherit pkgs; }; @@ -28,6 +30,7 @@ in docker dockerPostgres dotfilesUpdater + inputLeap makeUpdater neversslKeepalive ollama diff --git a/home-manager/services/input-leap/default.nix b/home-manager/services/input-leap/default.nix new file mode 100644 index 000000000..a7ff47c9a --- /dev/null +++ b/home-manager/services/input-leap/default.nix @@ -0,0 +1,48 @@ +{ inputs, pkgs, ... }: +let + inherit (pkgs) lib; + inherit (pkgs.stdenv) isDarwin; + inherit (inputs.host) isMatic; +in +{ + # macOS: launchd agent for input-leap server + launchd.agents.input-leap-server = lib.mkIf isDarwin { + enable = true; + config = { + ProgramArguments = [ + "${pkgs.input-leap}/bin/input-leaps" + "--no-daemon" + "--config" + "${../../../config/input-leap/server.conf}" + "--address" + ":24800" + "--disable-crypto" + ]; + KeepAlive = true; + RunAtLoad = true; + StandardOutPath = "/tmp/input-leap-server.log"; + StandardErrorPath = "/tmp/input-leap-server.error.log"; + }; + }; + + # Only on matic (not kyber) + systemd.user.services.input-leap-client = lib.mkIf isMatic { + Unit = { + Description = "Input Leap client (connects to galactica)"; + After = [ + "graphical-session.target" + "network-online.target" + ]; + Requires = [ "graphical-session.target" ]; + }; + Service = { + Type = "simple"; + ExecStart = "${pkgs.input-leap}/bin/input-leapc --no-daemon --name matic --disable-crypto galactica:24800"; + Restart = "on-failure"; + RestartSec = 5; + }; + Install = { + WantedBy = [ "graphical-session.target" ]; + }; + }; +} diff --git a/named-hosts/matic/default.nix b/named-hosts/matic/default.nix index 9e3f3fdf1..5cb048335 100644 --- a/named-hosts/matic/default.nix +++ b/named-hosts/matic/default.nix @@ -93,6 +93,9 @@ inputs.nixpkgs.lib.nixosSystem { initialPassword = "changemeow"; # Change this after first login with: passwd }; + # Tailscale VPN + services.tailscale.enable = true; + # Docker virtualisation.docker.enable = true;