Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ in
./git-ai
./gomi
./ghostty
./input-leap
./iterm2
./hammerspoon
./jj
Expand Down
16 changes: 16 additions & 0 deletions config/input-leap/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{ config, pkgs, ... }:
let
inherit (pkgs) lib;
inherit (pkgs.stdenv) isDarwin isLinux;
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

isLinux is inherited but unused in this module. Please remove the unused binding (and consider dropping/renaming other unused args like config if you want to keep the module lint-clean).

Suggested change
inherit (pkgs.stdenv) isDarwin isLinux;
inherit (pkgs.stdenv) isDarwin;

Copilot uses AI. Check for mistakes.
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;
};
Comment on lines +12 to +15
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 xdg.configFile block for isDarwin seems redundant. The standard configuration path for Input Leap on macOS is ~/Library/Application Support/InputLeap/, which is correctly configured above. The XDG path is more common on Linux. Having two Nix-managed config files pointing to the same source for the same OS can be confusing. It's recommended to remove this block to stick to the platform's standard.

}
16 changes: 16 additions & 0 deletions config/input-leap/server.conf
Original file line number Diff line number Diff line change
@@ -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
107 changes: 107 additions & 0 deletions docs/INPUT_LEAP.md
Original file line number Diff line number Diff line change
@@ -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.

Comment on lines +3 to +4
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

This doc states Input Leap TLS is disabled because traffic goes over Tailscale, but the current server setup binds to :24800 (all interfaces). Unless the port is restricted to the Tailscale interface/IP (or firewalled accordingly), plaintext Input Leap traffic could be reachable from the local network. Please document the binding/firewall requirement (or update the service to bind only to the Tailscale address).

Copilot uses AI. Check for mistakes.
## 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.
105 changes: 105 additions & 0 deletions docs/TAILSCALE.md
Original file line number Diff line number Diff line change
@@ -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 <host> # check latency to a node
tailscale ip -4 <host> # get Tailscale IPv4 address
tailscale netcheck # diagnose connectivity
```
7 changes: 6 additions & 1 deletion home-manager/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ let
sources = { };
};
services = import ./services {
inherit config lib pkgs;
inherit
config
inputs
lib
pkgs
;
};
in
{
Expand Down
3 changes: 3 additions & 0 deletions home-manager/services/default.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
config,
inputs,
lib,
pkgs,
...
Expand All @@ -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; };
Expand All @@ -28,6 +30,7 @@ in
docker
dockerPostgres
dotfilesUpdater
inputLeap
makeUpdater
neversslKeepalive
ollama
Expand Down
48 changes: 48 additions & 0 deletions home-manager/services/input-leap/default.nix
Original file line number Diff line number Diff line change
@@ -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}"
Comment on lines +15 to +16
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 hardcoded relative path to the config file is fragile. Following the suggestion to pass configFile to this module from home-manager/services/default.nix, you can make this more robust.

You'll also need to update the function signature at the top of this file to accept configFile:

{ pkgs, configFile, ... }:
...
        "--config"
        "${configFile}"

"--address"
":24800"
"--disable-crypto"
Comment on lines +17 to +19
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The server is started with --disable-crypto while binding to :24800 (all interfaces). Even if the intended transport is Tailscale, this exposes an unauthenticated/unencrypted Input Leap port on any non-Tailscale networks the host is connected to (e.g., LAN/Wi‑Fi). Consider either enabling crypto, or binding explicitly to the Tailscale IP/interface (or otherwise firewalling the port to tailscale0 only) so plaintext traffic isn't reachable off-tailnet.

Copilot uses AI. Check for mistakes.
];
KeepAlive = true;
RunAtLoad = true;
StandardOutPath = "/tmp/input-leap-server.log";
StandardErrorPath = "/tmp/input-leap-server.error.log";
Comment on lines +23 to +24
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

Storing log files in /tmp is not ideal as this directory is often cleared on reboot. A better practice is to use a standard logging location. On macOS, this would be ~/Library/Logs.

Following the suggestion to pass the config object to this module, you can define more robust log paths. You'll need to update the function signature to accept config:

{ config, pkgs, ... }:
...

With this change, also remember to update the log file paths in docs/INPUT_LEAP.md.

      StandardOutPath = "${config.home.homeDirectory}/Library/Logs/input-leap-server.log";
      StandardErrorPath = "${config.home.homeDirectory}/Library/Logs/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" ];
Comment on lines +32 to +36
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if network-online.target exists in user session context
# This target is typically system-level only

echo "=== Checking systemd user targets documentation ==="
# Search for any existing services in the repo using network-online.target in user context
rg -n "network-online.target" --type nix

Repository: shunkakinoki/dotfiles

Length of output: 754


🏁 Script executed:

cat -n home-manager/services/input-leap/default.nix

Repository: shunkakinoki/dotfiles

Length of output: 1721


🏁 Script executed:

cat -n home-manager/services/neverssl-keepalive/default.nix

Repository: shunkakinoki/dotfiles

Length of output: 1863


Add Wants declaration for network-online.target to match repository pattern.

The After declaration alone is insufficient for ordering dependencies in systemd. Other services in the repository (neverssl-keepalive, tailscale, openclaw) correctly pair Wants with After when depending on network-online.target. Add Wants to ensure proper dependency handling.

🔧 Suggested fix
     Unit = {
       Description = "Input Leap client (connects to galactica)";
+      Wants = [ "network-online.target" ];
       After = [
         "graphical-session.target"
         "network-online.target"
       ];
       Requires = [ "graphical-session.target" ];
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/services/input-leap/default.nix` around lines 31 - 35, Add a
Wants declaration for network-online.target to match the repository pattern: in
the systemd unit where you currently have After = [ "graphical-session.target"
"network-online.target" ] and Requires = [ "graphical-session.target" ], add
Wants = [ "network-online.target" ] so the unit both orders after and expresses
a weak dependency on the network-online.target (alongside the existing Requires
for graphical-session.target).

};
Service = {
Type = "simple";
ExecStart = "${pkgs.input-leap}/bin/input-leapc --no-daemon --name matic --disable-crypto galactica:24800";
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 server hostname galactica and client name matic are hardcoded in the ExecStart command. This reduces the reusability of this service definition. It's better to extract these into variables in the let block at the top of the file. This makes it easier to change them in the future.

# At the top of the file
let
  ...
  inputLeapServerHost = "galactica";
  inputLeapClientName = "matic";
in
...
      ExecStart = "${pkgs.input-leap}/bin/input-leapc --no-daemon --name ${inputLeapClientName} --disable-crypto ${inputLeapServerHost}:24800";

Restart = "on-failure";
RestartSec = 5;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
}
3 changes: 3 additions & 0 deletions named-hosts/matic/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading