noconnorandClaude Opus 5 b3f3f46b98 Make install.sh --handoff self-sufficient for a stock machine
A fresh generic install wants input sharing without the packages, config,
themes or plugins steps dragging personalizations along, so the handoff
step now stands alone: it installs lan-mouse and rust, drops in the
wrapper, builds the patch, installs and enables the unit, and opens the
firewall. Everything is idempotent, so a later full run does no extra
work. Verified by running it twice on omarchy-air -- second pass is all
skips, and the daemon comes back armed with a real barrier.

Drops python-evdev from the required set. packages.txt calls it a
lan-mouse dependency, but omarchy-air runs this entire setup without it
installed, so it is not needed for sharing and a minimal install should
not pull it.

The ufw section now degrades instead of half-applying: if sudo needs a
password and there is no terminal to prompt on, it prints the exact
commands to run and says what breaks without them, rather than emitting
raw sudo errors.

Hardware note for whoever reads this next: Omarchy's own installer covers
most of what packages.txt lists under hardware. install/hardware has
per-vendor trees (apple, asus, framework, intel, lenovo) plus vulkan.sh,
zram.sh, fix-fkeys.sh, sof-firmware.sh and thermald.sh, and the installer
prompts for keyboard, user, hostname and timezone. So vulkan-intel,
sof-firmware, thermald, zram-generator and the hid_apple fnmode drop-in
are all redundant on a fresh install, and the intel-* entries are wrong
on non-Intel hardware. Apply the hardware block only on the machine it
was captured from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LrgrMhPG8CTo9XZF326rjZ
2026-10-10 17:38:39 -04:00

omarchy-setup

Every customization this laptop has on top of a stock Omarchy install, in one place, so a second machine can be brought to the same state.

Captured from Omarchy 4.0.4-1 on 2026-10-10.

git clone <this repo> ~/omarchy-setup
cd ~/omarchy-setup
./install.sh --dry-run    # see what it would change
./install.sh              # do it

install.sh is idempotent and backs up anything it overwrites as <file>.bak.<epoch>. capture.sh runs the other way, pulling live changes back into the repo so this stays the source of truth.

What is in here

Path What
config/ Mirrors ~/.config. Only files that differ from Omarchy's shipped defaults.
themes/ The three hand-built themes, wallpapers included.
plugins.txt The 10 Omarchy shell plugins, each pinned to a commit.
plugin-patches/ Local plugin work that exists on no remote (see below).
bin/ omarchy-handoff and fix-stuck-displays.
handoff/ The lan-mouse source patch that fixes pointer feel, plus example config.tomls. Not a ~/.config mirror.
systemd/user/ lan-mouse.service, voxtype.service.
system/ /etc drop-ins: the ARP sysctl and hid_apple fnmode.
packages.txt Packages beyond the Omarchy baseline, each with a reason.

The customizations, and why

Displays (config/hypr/monitors.lua) are the fiddliest part. The file is the single source of truth for monitor rules on purpose — overlapping rules spread across several files is what previously made external displays unrecoverable. Three outputs are named by EDID description rather than connector, so they match whichever port they land on: the built-in Chimei panel at 1.25x, the LG IPS236 on HDMI at 1x, and the LG TV through the Anker dock. The TV is deliberately left on preferred (3840x2160@30) — the dock negotiates DP alt-mode with only two lanes, so forcing 4K60 produced a "monitor has no mode" loop. GDK_SCALE=1 with a 1.25 monitor scale; the stock pairing is 2 and auto.

hyprland.lua sources hyprmoncfg-monitors.lua last so the hyprmoncfg daemon's generated layout wins over anything above it.

bin/fix-stuck-displays recovers the related kernel-side failure: after suspend/resume i915 can leave an unplugged connector "disconnected" but still "enabled" on a pipe Hyprland has forgotten, which blocks every other output from modesetting. It forces the connector on, disables it through Hyprland to release the pipe, then returns it to auto-detect. It needs passwordless sudo for tee /sys/class/drm/*/status.

Keyboard/mouse sharing (bin/omarchy-handoff, systemd/user/lan-mouse.service) drives lan-mouse to share input with the other Omarchy laptop, bound to SUPER+SHIFT+K (SUPER+K is already Keybindings). The script's header comment is the real documentation — it records the non-obvious facts that cost hours: capture_backend must be layer-shell (the portal backend is a silent no-op on Hyprland 0.56.2), [authorized_fingerprints] is keyed by fingerprint and the reversed form fails silently, and the log variable is LAN_MOUSE_LOG_LEVEL, not RUST_LOG. It runs as a user unit rather than from autostart.lua because o.launch_on_start sends both streams to /dev/null, hiding every pairing failure.

Three further facts were only established after the above was first written, and they are what actually made it work:

  • Fingerprints are case-sensitive. lan-mouse generates them lowercase and compares with a plain map lookup — no normalisation — while openssl prints uppercase and cli authorize-key stores whatever it is handed. An uppercase digest is the correct digest, valid TOML, non-empty, passes every guard, and never matches: the DTLS handshake dies bad_certificate, indistinguishable from a wrong key or a dropped packet. This was the root cause of a full day of dead crossings. omarchy-handoff fixcase repairs it; doctor now fails on it.
  • Authorization is receiver-side only, not mutual. connect.rs sets insecure_skip_verify, so the connecting side never checks the peer's certificate — only the listener consults authorized_fingerprints. Each direction therefore depends solely on the receiver's list, two-way control needs both lists, and a sender cannot tell whether it is talking to the real peer. Input goes to whatever answers on that IP:port.
  • ufw is default-deny inbound, and a dropped inbound 4242 presents identically to a bad certificate. install.sh --handoff opens it for the peer's addresses. Check it first: journalctl -k -g 'UFW BLOCK'.

handoff/patch/ carries a two-line source patch so the daemon forwards the accelerated deltas, which is the only way to make the sender's sensitivity and accel_profile affect remote pointer feel at all; it also adds LAN_MOUSE_POINTER_SCALE for the residual difference in logical pixel density between the two screens. install.sh builds it per-machine — the two laptops are different architectures — and systemd/user/lan-mouse.service runs the result.

system/sysctl.d/99-omarchy-handoff-arp.conf goes with it: Wi-Fi and the dock's ethernet both sit on 192.168.1.0/24, and with the default ARP settings the subnet's mapping collapses onto whichever NIC replied last.

Bar and shell (config/omarchy/shell.json) adds omarchy.active-window to the left, five plugin widgets, and indie.herdr-hud as a non-bar plugin. Idle is stretched a long way past stock — screensaver at 20 minutes, lock at 30, against 2.5 and 5.

Keybindings (config/hypr/bindings.lua) rebinds SUPER+SHIFT+A from the stock ChatGPT webapp to Claude, and adds four Herdr HUD bindings plus Handoff.

Input (config/hypr/input.lua) enables the three-finger horizontal workspace gesture that ships commented out, and flattens pointer acceleration for unknown-device*.

That second rule is a no-op, and the reasoning originally given for it was wrong. It was added believing lan-mouse forwards deltas the sending compositor had already accelerated, which the receiver then amplified a second time. Upstream does the opposite: it forwards the unaccelerated deltas and discards the accelerated pair in the same event (handoff/patch/REBUILD.md). And the rule cannot act either way — hl.device({name="unknown-device"}) does match the virtual pointer (provable by toggling scroll_factor), but sensitivity and accel_profile are libinput pointer-acceleration settings and a wlr-virtual-pointer is not a libinput device, so they have nothing to act on. Measured 2026-10-10 on both machines with lan-mouse test-emulation + hyprctl cursorpos: travel was 398x198px at sensitivity = 0 and identical at -0.9, exactly 1:1 with the raw deltas. The lines are kept because they are harmless and the unknown-device naming note is worth preserving; the real fix is handoff/patch/. Do not add sensitivity or accel_profile rules here expecting them to change remote pointer feel.

Terminals are all dropped to a smaller font (9 -> 8) except ghostty, which went the other way to 12. shell.toml sets the bar font base size to 10.

Branding (config/omarchy/branding/) replaces the about and screensaver ASCII art.

hid_apple fnmode=2 makes the F-keys act as F-keys on an Apple keyboard. It only takes effect after sudo mkinitcpio -P or a module reload.

Local plugin work

indie.herdr-hud is the one piece that exists on no remote. Ten commits live on its local branch — touchpad scroll feel, agent ordering, context-size display — and they stay off the upstream PR deliberately. plugin-patches/ carries them as a git bundle plus a patch for the uncommitted working tree, and install.sh replays both after cloning. io.github.tallsam.navbar-cat has a one-file working tree change captured the same way.

capture.sh plugins re-bundles and re-diffs these, so local work keeps flowing back into the repo.

Not in here, on purpose

  • Secrets. ~/.config/lan-mouse/ holds a private key and per-machine fingerprints; ~/.config/novnc/auth.env, SSH keys and the git credential helpers are likewise machine-local. Start from handoff/config/config.toml.example-{air,laptop} (which carry the capture_backend, edge and lowercase-fingerprint rules), then pair with omarchy-handoff pair <peer> <lowercase-sha256> and omarchy-handoff doctor. A fresh install regenerates the certificate, so this machine's fingerprint changes and the peer has to re-pair. Back up lan-mouse.pem beforehand if you would rather keep it stable — somewhere private, not here.
  • Shell, git and dev tooling. That is already its own repo, dotfiles (git.nic-oconnor.com/public/dotfiles), which handles zsh, mise, 1Password and the ~/.local/bin tool shims. The only overlap avoided here is ~/.config/git/config, whose customizations are identity and credential helpers — dotfiles territory.
  • The remote-access stack. wayvnc, novnc, sunshine, freerdp, remmina and omarchy-remote-desktop are installed on this laptop but scoped out. kokd.remote-desktop is still in plugins.txt since it is a bar plugin, but it is not placed in shell.json.
  • Work tooling. The keycloak helpers (kcadm, kclogin, wren-keycloak) and paperclipai.service.
  • aether-managed themes. pink-gt3rs, porsche-cayman and porsche-rain carry a .aether-managed marker and are reinstallable with aether, so their ~18MB of wallpapers and previews stay out.
  • btop.conf. Its only diff against stock is v1.4.7 default drift, not a real customization.
  • Backup files. Fifteen .bak.* and .prefix.* files are sitting in ~/.config/hypr and ~/.config/omarchy from earlier editing sessions. They are not tracked and are safe to delete.

Adding a file

capture.sh only refreshes what the repo already tracks, so start it by hand:

cp ~/.config/foo/bar.conf config/foo/bar.conf   # path mirrors ~/.config
git add config/foo/bar.conf

install.sh config and capture.sh config both walk config/ recursively, so nothing else needs changing. Check first that the file actually differs from stock — diff /usr/share/omarchy/config/foo/bar.conf ~/.config/foo/bar.conf — or you will be tracking a file Omarchy already ships.

S
Description
Omarchy desktop customizations for this laptop: hypr, shell, themes, plugins and system drop-ins, with install/capture scripts
Readme
19 MiB
Languages
Shell 45.7%
QML 35.6%
Lua 10.1%
Python 8.6%