From 87a31858fdc0cadb57ce4cb24f6ff0fc4010b3aa Mon Sep 17 00:00:00 2001 From: Nic O'Connor Date: Fri, 16 Oct 2026 15:59:48 -0400 Subject: [PATCH] Profiles: machines, roles and shared layers The repo was one machine's capture. It is now a set of profiles built from layers under profiles/: machines/ hp-laptop, macbook-air-m2, coder-desktop roles/ developer, devops, ai-agents features/ handoff (lan-mouse) shared base, personal, personal-desktop A profile resolves to its includes (depth first), any --roles, then itself; for a path several layers provide, the last wins. install.sh --show prints the layers and which one owns each file. Hyprland settings no longer share a file: base's hyprland.lua loads ~/.config/hypr/layers/*.lua with Omarchy's own require_all, and bindings.lua stays stock so nothing binds twice. Verified with Hyprland 0.56.2 --verify-config for all three profiles, plus a broken probe layer to prove the directory is loaded. packages.txt gains per-line arch (Arch Linux ARM lacks opencode, codex, mise, shellcheck) and cmd= (skip when dotfiles' ~/.local/bin already provides the command), and an [ignore] section that replaces capture.sh's hard-coded list. Tab-separated records would have collapsed empty fields under read, so package records use the unit separator. hp-laptop: re-captured. monitors.lua catch-all scale is now 1, shell.json has four new plugins, fix-stuck-displays has its connector-name fix, navbar-cat is gone, plugin-manager and omarchy-phone are pinned, the herdr-hud working tree is re-diffed and bin/herdr-web rides along. Its handoff peer is now the Air (192.168.1.49); the old default list was the HP's own addresses, written from the Air's side. macbook-air-m2: captured read-only over SSH as it is (no themes, plugins or bar changes). Its LAN_MOUSE_POINTER_SCALE=0.7 moves from the shared unit into a lan-mouse.service.d drop-in in its own layer. coder-desktop: the Omarchy settings covert-cloud's image used to patch in by hand (Virtual-1 at 1920x1080, no animations, stay-awake, Retro 82 once), for the image to apply at a pinned commit. Roles reach it per workspace via install.sh --roles ... --no-clobber. capture.sh writes each file back into the layer that owns it, and refuses to run before the first layered install unless --force, since the old file shapes would land in shared layers. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0153egowV1Xw2RNjZnSyZicc --- README.md | 373 +++++++++---- capture.sh | 271 +++++---- install.sh | 515 ++++++++++++------ lib/layers.sh | 183 +++++++ packages.txt | 59 -- ...o.github.tallsam.navbar-cat-worktree.patch | 48 -- .../base/config}/hypr/bindings.lua | 16 +- .../base/config}/hypr/hyprland.lua | 6 + profiles/base/profile | 4 + .../features/handoff/bin}/omarchy-handoff | 0 .../handoff/config/hypr/layers/50-handoff.lua | 4 + .../handoff}/config/config.toml.example-air | 0 .../config/config.toml.example-laptop | 0 .../handoff/handoff}/patch/REBUILD.md | 0 .../handoff}/patch/accelerated-deltas.patch | 0 .../features/handoff/handoff}/patch/build.sh | 0 profiles/features/handoff/packages.txt | 4 + profiles/features/handoff/profile | 4 + .../handoff/systemd}/user/lan-mouse.service | 8 +- .../config/hypr/layers/80-coder-desktop.lua | 5 + .../coder-desktop/config/hypr/monitors.lua | 20 + profiles/machines/coder-desktop/profile | 15 + profiles/machines/coder-desktop/setup.sh | 42 ++ .../hp-laptop/bin}/fix-stuck-displays | 2 +- .../config}/alacritty/alacritty.toml | 0 .../machines/hp-laptop/config}/foot/foot.ini | 0 .../machines/hp-laptop/config}/ghostty/config | 0 .../hp-laptop/config}/hypr/autostart.lua | 0 .../machines/hp-laptop/config}/hypr/input.lua | 0 .../hp-laptop/config}/hypr/monitors.lua | 2 +- .../hp-laptop/config}/kitty/kitty.conf | 0 profiles/machines/hp-laptop/packages.txt | 48 ++ profiles/machines/hp-laptop/profile | 8 + .../system}/modprobe.d/hid_apple.conf | 0 .../sysctl.d/99-omarchy-handoff-arp.conf | 0 .../macbook-air-m2/config/herdr/config.toml | 96 ++++ .../macbook-air-m2/config/hypr/input.lua | 83 +++ .../macbook-air-m2/config/hypr/monitors.lua | 22 + .../config/opencode/opencode.json | 5 + .../macbook-air-m2/config/voxtype/config.toml | 100 ++++ profiles/machines/macbook-air-m2/packages.txt | 35 ++ profiles/machines/macbook-air-m2/profile | 11 + .../system/modprobe.d/asahi-notch.conf | 1 + .../system/modprobe.d/hid_apple.conf | 1 + .../lan-mouse.service.d/pointer-scale.conf | 6 + .../hypr/layers/40-personal-desktop.lua | 11 + .../omarchy/ilyazar.btop/settings.toml | 0 .../config}/omarchy/shell.json | 22 +- .../config}/omarchy/shell.toml | 0 profiles/personal-desktop/packages.txt | 10 + .../indie.herdr-hud--bin--herdr-agents | 0 .../indie.herdr-hud--bin--herdr-web | 191 +++++++ .../indie.herdr-hud-worktree.patch | 223 +++++++- .../plugin-patches}/indie.herdr-hud.bundle | Bin .../personal-desktop/plugins.txt | 3 +- profiles/personal-desktop/profile | 6 + .../themes}/ferrari/CREDITS.md | 0 .../ferrari/backgrounds/1-giallo-studio.jpg | Bin .../ferrari/backgrounds/2-london-nights.jpg | Bin .../ferrari/backgrounds/3-light-trails.jpg | Bin .../ferrari/backgrounds/4-at-speed.jpg | Bin .../themes}/ferrari/colors.toml | 0 .../themes}/ferrari/icons.theme | 0 .../themes}/porsche/CREDITS.md | 0 .../porsche/backgrounds/0-718-cayman-s.jpg | Bin .../porsche/backgrounds/1-rainy-evening.jpg | Bin .../porsche/backgrounds/2-gulf-917.jpg | Bin .../themes}/porsche/backgrounds/3-night.jpg | Bin .../porsche/backgrounds/4-carrera-rs.jpg | Bin .../themes}/porsche/colors.toml | 0 .../themes}/porsche/icons.theme | 0 .../themes}/simpsons/CREDITS.md | 0 .../backgrounds/1-springfield-wasteland.jpg | Bin .../simpsons/backgrounds/2-bart-sunset.jpg | Bin .../simpsons/backgrounds/3-duff-wall.jpg | Bin .../themes}/simpsons/btop.theme | 0 .../themes}/simpsons/chromium.theme | 0 .../themes}/simpsons/colors.toml | 0 .../themes}/simpsons/hyprland.lua | 0 .../themes}/simpsons/icons.theme | 0 .../themes}/simpsons/keyboard.rgb | 0 .../themes}/simpsons/neovim.lua | 0 .../themes}/simpsons/preview.png | Bin .../themes}/simpsons/shell.lock.toml | 0 .../personal/config}/herdr/config.toml | 0 .../config}/omarchy/branding/about.txt | 0 .../config}/omarchy/branding/screensaver.txt | 0 .../personal/config}/omarchy/defaults/agent | 0 .../personal/config}/opencode/opencode.json | 0 profiles/personal/packages.txt | 3 + profiles/personal/profile | 3 + .../personal/systemd}/user/voxtype.service | 0 profiles/roles/ai-agents/packages.txt | 7 + profiles/roles/ai-agents/profile | 9 + profiles/roles/developer/packages.txt | 13 + profiles/roles/developer/profile | 8 + profiles/roles/devops/packages.txt | 28 + profiles/roles/devops/profile | 8 + 98 files changed, 2010 insertions(+), 532 deletions(-) create mode 100644 lib/layers.sh delete mode 100644 packages.txt delete mode 100644 plugin-patches/io.github.tallsam.navbar-cat-worktree.patch rename {config => profiles/base/config}/hypr/bindings.lua (56%) rename {config => profiles/base/config}/hypr/hyprland.lua (76%) create mode 100644 profiles/base/profile rename {bin => profiles/features/handoff/bin}/omarchy-handoff (100%) create mode 100644 profiles/features/handoff/config/hypr/layers/50-handoff.lua rename {handoff => profiles/features/handoff/handoff}/config/config.toml.example-air (100%) rename {handoff => profiles/features/handoff/handoff}/config/config.toml.example-laptop (100%) rename {handoff => profiles/features/handoff/handoff}/patch/REBUILD.md (100%) rename {handoff => profiles/features/handoff/handoff}/patch/accelerated-deltas.patch (100%) rename {handoff => profiles/features/handoff/handoff}/patch/build.sh (100%) create mode 100644 profiles/features/handoff/packages.txt create mode 100644 profiles/features/handoff/profile rename {systemd => profiles/features/handoff/systemd}/user/lan-mouse.service (81%) create mode 100644 profiles/machines/coder-desktop/config/hypr/layers/80-coder-desktop.lua create mode 100644 profiles/machines/coder-desktop/config/hypr/monitors.lua create mode 100644 profiles/machines/coder-desktop/profile create mode 100755 profiles/machines/coder-desktop/setup.sh rename {bin => profiles/machines/hp-laptop/bin}/fix-stuck-displays (98%) rename {config => profiles/machines/hp-laptop/config}/alacritty/alacritty.toml (100%) rename {config => profiles/machines/hp-laptop/config}/foot/foot.ini (100%) rename {config => profiles/machines/hp-laptop/config}/ghostty/config (100%) rename {config => profiles/machines/hp-laptop/config}/hypr/autostart.lua (100%) rename {config => profiles/machines/hp-laptop/config}/hypr/input.lua (100%) rename {config => profiles/machines/hp-laptop/config}/hypr/monitors.lua (97%) rename {config => profiles/machines/hp-laptop/config}/kitty/kitty.conf (100%) create mode 100644 profiles/machines/hp-laptop/packages.txt create mode 100644 profiles/machines/hp-laptop/profile rename {system => profiles/machines/hp-laptop/system}/modprobe.d/hid_apple.conf (100%) rename {system => profiles/machines/hp-laptop/system}/sysctl.d/99-omarchy-handoff-arp.conf (100%) create mode 100644 profiles/machines/macbook-air-m2/config/herdr/config.toml create mode 100644 profiles/machines/macbook-air-m2/config/hypr/input.lua create mode 100644 profiles/machines/macbook-air-m2/config/hypr/monitors.lua create mode 100644 profiles/machines/macbook-air-m2/config/opencode/opencode.json create mode 100644 profiles/machines/macbook-air-m2/config/voxtype/config.toml create mode 100644 profiles/machines/macbook-air-m2/packages.txt create mode 100644 profiles/machines/macbook-air-m2/profile create mode 100644 profiles/machines/macbook-air-m2/system/modprobe.d/asahi-notch.conf create mode 100644 profiles/machines/macbook-air-m2/system/modprobe.d/hid_apple.conf create mode 100644 profiles/machines/macbook-air-m2/systemd/user/lan-mouse.service.d/pointer-scale.conf create mode 100644 profiles/personal-desktop/config/hypr/layers/40-personal-desktop.lua rename {config => profiles/personal-desktop/config}/omarchy/ilyazar.btop/settings.toml (100%) rename {config => profiles/personal-desktop/config}/omarchy/shell.json (83%) rename {config => profiles/personal-desktop/config}/omarchy/shell.toml (100%) create mode 100644 profiles/personal-desktop/packages.txt rename {plugin-patches => profiles/personal-desktop/plugin-patches}/indie.herdr-hud--bin--herdr-agents (100%) create mode 100755 profiles/personal-desktop/plugin-patches/indie.herdr-hud--bin--herdr-web rename {plugin-patches => profiles/personal-desktop/plugin-patches}/indie.herdr-hud-worktree.patch (81%) rename {plugin-patches => profiles/personal-desktop/plugin-patches}/indie.herdr-hud.bundle (100%) rename plugins.txt => profiles/personal-desktop/plugins.txt (81%) create mode 100644 profiles/personal-desktop/profile rename {themes => profiles/personal-desktop/themes}/ferrari/CREDITS.md (100%) rename {themes => profiles/personal-desktop/themes}/ferrari/backgrounds/1-giallo-studio.jpg (100%) rename {themes => profiles/personal-desktop/themes}/ferrari/backgrounds/2-london-nights.jpg (100%) rename {themes => profiles/personal-desktop/themes}/ferrari/backgrounds/3-light-trails.jpg (100%) rename {themes => profiles/personal-desktop/themes}/ferrari/backgrounds/4-at-speed.jpg (100%) rename {themes => profiles/personal-desktop/themes}/ferrari/colors.toml (100%) rename {themes => profiles/personal-desktop/themes}/ferrari/icons.theme (100%) rename {themes => profiles/personal-desktop/themes}/porsche/CREDITS.md (100%) rename {themes => profiles/personal-desktop/themes}/porsche/backgrounds/0-718-cayman-s.jpg (100%) rename {themes => profiles/personal-desktop/themes}/porsche/backgrounds/1-rainy-evening.jpg (100%) rename {themes => profiles/personal-desktop/themes}/porsche/backgrounds/2-gulf-917.jpg (100%) rename {themes => profiles/personal-desktop/themes}/porsche/backgrounds/3-night.jpg (100%) rename {themes => profiles/personal-desktop/themes}/porsche/backgrounds/4-carrera-rs.jpg (100%) rename {themes => profiles/personal-desktop/themes}/porsche/colors.toml (100%) rename {themes => profiles/personal-desktop/themes}/porsche/icons.theme (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/CREDITS.md (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/backgrounds/1-springfield-wasteland.jpg (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/backgrounds/2-bart-sunset.jpg (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/backgrounds/3-duff-wall.jpg (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/btop.theme (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/chromium.theme (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/colors.toml (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/hyprland.lua (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/icons.theme (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/keyboard.rgb (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/neovim.lua (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/preview.png (100%) rename {themes => profiles/personal-desktop/themes}/simpsons/shell.lock.toml (100%) rename {config => profiles/personal/config}/herdr/config.toml (100%) rename {config => profiles/personal/config}/omarchy/branding/about.txt (100%) rename {config => profiles/personal/config}/omarchy/branding/screensaver.txt (100%) rename {config => profiles/personal/config}/omarchy/defaults/agent (100%) rename {config => profiles/personal/config}/opencode/opencode.json (100%) create mode 100644 profiles/personal/packages.txt create mode 100644 profiles/personal/profile rename {systemd => profiles/personal/systemd}/user/voxtype.service (100%) create mode 100644 profiles/roles/ai-agents/packages.txt create mode 100644 profiles/roles/ai-agents/profile create mode 100644 profiles/roles/developer/packages.txt create mode 100644 profiles/roles/developer/profile create mode 100644 profiles/roles/devops/packages.txt create mode 100644 profiles/roles/devops/profile diff --git a/README.md b/README.md index 993e134..7fa206f 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,185 @@ # 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. +Omarchy customizations on top of a stock install, as **profiles**: one per +machine, each built from shared **layers** and generic **roles**. -Captured from Omarchy **4.0.4-1** on **2026-10-10**. +| Profile | Machine | +|---|---| +| `hp-laptop` | The HP (`omarchy-laptop`): Intel, Anker dock, LG displays. Where this repo started. | +| `macbook-air-m2` | The Air (`omarchy-air`): Asahi Linux on Arch Linux ARM, aarch64. | +| `coder-desktop` | The streamed Coder desktops, built by covert-cloud's image pipeline. | + +| Role | Adds | +|---|---| +| `developer` | gh, delta, uv, just, direnv, shfmt/shellcheck, httpie, mise, VS Code | +| `devops` | kubectl, helm, k9s, kubectx, stern, kustomize, talosctl, cilium, tofu, terragrunt, packer, ansible, go-task, yq, sops/age, dig/mtr/nmap/tcpdump | +| `ai-agents` | Claude Code, OpenCode, Codex, Gemini CLI, Copilot CLI | ```bash -git clone ~/omarchy-setup +git clone https://git.nic-oconnor.com/public/omarchy-setup.git ~/omarchy-setup cd ~/omarchy-setup -./install.sh --dry-run # see what it would change -./install.sh # do it +./install.sh --profiles # what exists +./install.sh --profile hp-laptop --show # its layers, and which layer owns each file +./install.sh --dry-run # see what it would change (profile picked by hostname) +./install.sh # do it +./install.sh --roles devops,ai-agents # add roles on top of the profile ``` `install.sh` is idempotent and backs up anything it overwrites as -`.bak.`. `capture.sh` runs the other way, pulling live changes back -into the repo so this stays the source of truth. +`.bak.`. It records the profile and roles it applied in +`~/.config/omarchy-setup/profile`, so later runs (and `capture.sh`) need no +flags. `capture.sh` runs the other way, pulling live changes back into the +layer that owns each file, so the repo stays the source of truth. -## What is in here +## How profiles are built -| Path | What | +Everything lives under `profiles/`. A layer is a directory with a `profile` +file (bash: `description`, `include=(...)`, and for machines `hostnames`, +`arch`, `handoff_peer_ips`) and any of the same pieces this repo always had: + +| Path in a layer | Installed to | |---|---| -| `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.toml`s. 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. | +| `config/` | `~/.config`, mirrored | +| `themes/` | `~/.config/omarchy/themes` | +| `plugins.txt`, `plugin-patches/` | `~/.config/omarchy/plugins`, pinned; local work replayed | +| `bin/` | `~/.local/bin` | +| `systemd/user/` | user units, plus `.service.d/*.conf` drop-ins | +| `system/` | `/etc`, mirrored (sudo) | +| `handoff/` | the lan-mouse patch and example configs (`install.sh handoff`) | +| `packages.txt` | `omarchy pkg add` / `omarchy pkg aur add` | +| `setup.sh` | run last, for state outside `~/.config` (markers, gsettings, a first-run theme) | -## The customizations, and why +A profile resolves to an ordered list of layers: its `include`s first (depth +first, each once), then any `--roles`, then the profile itself. **For a path +several layers provide, the last one wins**, so a machine overrides the roles +and shared layers beneath it. `./install.sh --show` prints the result. -**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`. +| Layer | Used by | What | +|---|---|---| +| `base` | all | `hyprland.lua` with the `hypr/layers/` loader; `bindings.lua` kept at stock | +| `personal` | both laptops | branding, default agent (`claude`), herdr, opencode, voxtype | +| `personal-desktop` | HP | the three hand-built themes, the shell plugins, bar layout, Claude/Herdr bindings | +| `features/handoff` | both laptops | `omarchy-handoff` + patched lan-mouse, its unit and binding | +| `roles/*` | per profile, or `--roles` | packages, and any bindings the role needs | +| `machines/*` | — | displays, input, hardware packages, `/etc` drop-ins, per-machine overrides | -`hyprland.lua` sources `hyprmoncfg-monitors.lua` last so the `hyprmoncfg` daemon's -generated layout wins over anything above it. +**Hyprland settings never share a file.** `base`'s `hyprland.lua` loads every +`~/.config/hypr/layers/*.lua` in name order (Omarchy's own `require_all`, with +reload), after `monitors`/`input`/`bindings`/`looknfeel`/`autostart`. Each +layer ships its own `NN-.lua` there — roles 20s, `personal-desktop` 40, +features 50, machines 80 — and `bindings.lua` stays stock so nothing is bound +twice. `install.sh` warns about a file in `hypr/layers/` that no layer of the +current profile provides (a role you dropped). -`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`. +**Packages** lines are `name [arch...] [cmd=a,b] # why`, in `[repo]`, `[aur]`, +`[optional]` or `[ignore]` sections. An arch limits a line to `x86_64` or +`aarch64` (Arch Linux ARM lacks `opencode`, `openai-codex`, `mise` and +`shellcheck`). `cmd=` skips the package when one of those commands is already +on PATH — on the laptops, `dotfiles`' `bin/` puts most devops and agent CLIs in +`~/.local/bin`, and the Arch package would only duplicate them; on a Coder +desktop, which has no `dotfiles`, the package is the tool. `[ignore]` is for +packages a machine has on purpose but this repo does not manage, so +`capture.sh packages` stays quiet about them. -**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. +Roles name the `dotfiles` modules they pair with (`dotfiles=(...)`), and +`install.sh` prints them when `~/.dotfiles` is absent. It never runs +`dotfiles` itself: that needs a 1Password token, which a Coder desktop does +not have. + +## Coder desktops + +The image is built by `covert-cloud` +(`platform/workspaces/omarchy-desktop/image/`). Its `provision/user.sh` clones +this repo at a **pinned commit** into `~/.local/share/omarchy-setup` and runs +`install.sh --profile coder-desktop`, which bakes in `base`, `developer` and +the `coder-desktop` layer: + +- one fixed `Virtual-1` monitor at 1920x1080, GDK scale 1 — **must match** + `selkies.service`'s `--manual-width/--manual-height`; the build fails if the + line is missing; +- no window animations, and GTK reduced motion: the VM has no GPU, so every + frame is drawn on the CPU; +- Omarchy's stay-awake mode: the desktop user has no password, so a lock + screen could never be unlocked; the build checks for the marker; +- Retro 82 as the default theme, set once only, so a theme the developer picks + survives restarts. (The image itself installs the Covert Cloud themes.) + +The plumbing stays in `covert-cloud`: Selkies, the Coder agent, the Super key +stand-in, the start page, autologin, and the lockdown (no SSH, no file +transfer, locked password). Nothing from `personal*` or `handoff` goes on a +desktop that other people use. + +**Roles per workspace.** The Coder template has a **Roles** parameter +(multi-select: `devops`, `ai-agents`). At every workspace start it runs +`install.sh --profile coder-desktop --roles --no-clobber`: +idempotent, so an unchanged choice costs a few seconds, and `--no-clobber` +keeps any file the developer has edited. Deselecting a role stops managing +it but does not uninstall its packages; its `hypr/layers/` file is left in place +and reported. + +**Changing what the desktops get** is a commit here plus a pin bump there: +`OMARCHY_SETUP_REF` in `covert-cloud`'s `provision/user.sh` (rebuild the image) +and in the template's `main.tf` (template push; reaches running desktops at +their next start). + +## The MacBook Air M2 + +Captured as it was on 2026-10-16 (Omarchy 4.0.3rc4), read-only over SSH: no +hand-built themes, no shell plugins, stock bar and terminal fonts, so it +includes `personal` but not `personal-desktop`. Add that to its `include=` to +give it the HP's desktop. What is its own: + +- `monitors.lua`: monitor scale 1.33333, GDK scale 1, no named outputs. +- `input.lua`: stock gestures (no three-finger workspace swipe), and the + measured note on why `sensitivity`/`accel_profile` rules are inert for the + lan-mouse virtual pointer. +- `/etc/modprobe.d`: `hid_apple fnmode=1` (the built-in keyboard; the HP uses 2 + for an external Apple keyboard) and `appledrm show_notch=1`. +- `lan-mouse.service.d/pointer-scale.conf`: `LAN_MOUSE_POINTER_SCALE=0.7`, for + the Air's 140 logical ppi against the LG's ~96. +- Its own `herdr/config.toml` (no agent-row customization yet), `opencode.json` + (`"theme": "system"`) and `voxtype/config.toml`. +- The Asahi platform packages, listed so `capture.sh` notices drift. + +Left out: `~/.config/opencode/opencode.gateway.json` (holds the LiteLLM key; +`dotfiles`' `mcp-config` writes it), `~/.config/lan-mouse/`, and the +post-update hooks and `omarchy-asahi-mic.service`, which Omarchy installs +itself. + +## The HP laptop: its customizations, and why + +**Displays** (`profiles/machines/hp-laptop/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 catch-all monitor scale of 1 (the +built-in panel and the TV set 1.25 in their own rules); 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. + +`profiles/machines/hp-laptop/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** (`profiles/features/handoff/`: `bin/omarchy-handoff`, +`systemd/user/lan-mouse.service`) drives `lan-mouse` to share input with the +other Omarchy laptop (the Air), 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: @@ -72,7 +190,8 @@ they are what actually made it work: 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. + 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 @@ -80,65 +199,73 @@ they are what actually made it work: 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'`. + *identically* to a bad certificate. `install.sh handoff` opens it for the + addresses in the profile's `handoff_peer_ips`. 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. +`profiles/features/handoff/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 `lan-mouse.service` runs the result. +The Air's `LAN_MOUSE_POINTER_SCALE=0.7` is a systemd drop-in in its own layer +(`lan-mouse.service.d/pointer-scale.conf`), so the shared unit stays at 1.0. -`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. +`profiles/machines/hp-laptop/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. +**Bar and shell** (`profiles/personal-desktop/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. +**Keybindings** live in two layer files. +`profiles/personal-desktop/config/hypr/layers/40-personal-desktop.lua` rebinds +`SUPER+SHIFT+A` from the stock ChatGPT webapp to Claude and adds four Herdr HUD +bindings; the handoff feature's `50-handoff.lua` adds Handoff. -**Input** (`config/hypr/input.lua`) enables the three-finger horizontal -workspace gesture that ships commented out, and flattens pointer acceleration for -`unknown-device*`. +**Input** (`profiles/machines/hp-laptop/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. +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 +(`profiles/features/handoff/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. +**Branding** (`profiles/personal/config/omarchy/branding/`, shared with the Air) +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. +`indie.herdr-hud` is the one piece that exists on no remote. Its `local` branch +carries commits — touchpad scroll feel, agent ordering, context-size display — +and they stay off the upstream PR deliberately. +`profiles/personal-desktop/plugin-patches/` carries them as a git bundle plus a +patch for the uncommitted working tree, and `install.sh` replays both after +cloning. Two untracked scripts in the same plugin, `bin/herdr-agents` and +`bin/herdr-web`, ride along as `indie.herdr-hud--bin--`. `capture.sh plugins` re-bundles and re-diffs these, so local work keeps flowing back into the repo. @@ -148,42 +275,62 @@ back into the repo. - **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 ` 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. + `profiles/features/handoff/handoff/config/config.toml.example-{air,laptop}` + (which carry the `capture_backend`, edge and lowercase-fingerprint rules), + then pair with `omarchy-handoff pair ` 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 + (`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`. +- **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`. +- **Two plugins with no remote yet.** `niccoconnor.mantel` and + `niccoconnor.keyhints` are installed on the HP and placed in `shell.json`, but + neither has an `origin`, so `plugins.txt` cannot pin them (`capture.sh` says + so). Until they are pushed, another machine gets a bar with those two slots + empty. +- **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. + `~/.config/hypr` and `~/.config/omarchy` from earlier editing sessions. They + are not tracked and are safe to delete. -## Adding a file +## Adding things -`capture.sh` only refreshes what the repo already tracks, so start it by hand: +**A file.** `capture.sh` only refreshes what the repo already tracks, so start +it by hand, in the layer it belongs to: ```bash -cp ~/.config/foo/bar.conf config/foo/bar.conf # path mirrors ~/.config -git add config/foo/bar.conf +cp ~/.config/foo/bar.conf profiles/machines/hp-laptop/config/foo/bar.conf +git add profiles/machines/hp-laptop/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. +Check first that it 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. A Hyprland setting goes in that +layer's `config/hypr/layers/NN-.lua`, not in `bindings.lua` or +`looknfeel.lua`. + +**A role.** `mkdir profiles/roles/`, give it a `profile` (`description=`, +optionally `dotfiles=(...)`) and a `packages.txt`, plus +`config/hypr/layers/2N-.lua` if it needs bindings. Use `cmd=` for any +package `dotfiles` might already provide, and an arch for anything Arch Linux +ARM lacks. To offer it on Coder desktops, add it to the template's Roles +parameter in `covert-cloud`. + +**A machine.** `mkdir profiles/machines/` with a `profile` naming +`hostnames=`, `arch=` and `include=(base ...)`, then its `monitors.lua`, +`input.lua`, `packages.txt` and `/etc` drop-ins. Run `./install.sh --profile + --dry-run` on it, then `./capture.sh` after any hand-tuning. diff --git a/capture.sh b/capture.sh index 86e8b70..1454cbe 100755 --- a/capture.sh +++ b/capture.sh @@ -2,11 +2,14 @@ # Pull the live machine's state back into this repo - the reverse of install.sh. # # Run it after customizing something by hand, then review `git diff` and commit. -# It only refreshes paths the repo already tracks; it never invents new ones, so -# adding a newly customized file is a deliberate act (see README, "Adding a file"). +# Each live file goes back into the layer that OWNS it for this machine's +# profile (the last layer providing that path; `./install.sh --show` lists +# them). It only refreshes paths the repo already tracks, so adding a newly +# customized file is a deliberate act (see README, "Adding a file"). # -# ./capture.sh # refresh everything -# ./capture.sh config # only the named steps +# ./capture.sh # this machine's profile, every step +# ./capture.sh --profile hp-laptop # name it explicitly +# ./capture.sh config # only the named steps # ./capture.sh --list # # Steps: config themes plugins bin services system packages @@ -16,15 +19,19 @@ set -uo pipefail REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" STEPS=(config themes plugins bin services system packages) -c_ch=$'\033[33m'; c_skip=$'\033[2m'; c_warn=$'\033[33m'; c_hd=$'\033[1m'; c_off=$'\033[0m' -[[ -t 1 ]] || { c_ch=; c_skip=; c_warn=; c_hd=; c_off=; } +c_ch=$'\033[33m'; c_skip=$'\033[2m'; c_warn=$'\033[33m'; c_err=$'\033[31m'; c_hd=$'\033[1m'; c_off=$'\033[0m' +[[ -t 1 ]] || { c_ch=; c_skip=; c_warn=; c_err=; c_hd=; c_off=; } say() { printf '%s\n' "$*"; } head_() { printf '\n%s==> %s%s\n' "$c_hd" "$*" "$c_off"; } chg() { printf ' %sM %s%s\n' "$c_ch" "$*" "$c_off"; } skip() { printf ' %s= %s%s\n' "$c_skip" "$*" "$c_off"; } warn() { printf ' %s! %s%s\n' "$c_warn" "$*" "$c_off"; } +err() { printf ' %sx %s%s\n' "$c_err" "$*" "$c_off"; } gone() { printf ' %s? %s%s\n' "$c_warn" "$*" "$c_off"; } +# shellcheck source=lib/layers.sh +source "$REPO/lib/layers.sh" + # pull_back