4.5.0rc2 replaced the Agents panel with a compact one: a meter per limit, the percentage only on hover, no tokens by day or by model. noconnor.agents is a clone of omarchy.agents with Panel.qml, Main.qml and README.md from upstream v4.0.4, vendored as plain files in personal-desktop's config/ so the config step installs and captures it. capture.sh's plugin step skips plugins vendored that way instead of warning they aren't git checkouts. shell.json is synced from the live HP: the bar uses noconnor.agents, plus the 4.5 centerBegin/centerEnd layout, elsewhen and the antigravity plugin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
350 lines
19 KiB
Markdown
350 lines
19 KiB
Markdown
# omarchy-setup
|
|
|
|
Omarchy customizations on top of a stock install, as **profiles**: one per
|
|
machine, each built from shared **layers** and generic **roles**.
|
|
|
|
| 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 https://git.nic-oconnor.com/public/omarchy-setup.git ~/omarchy-setup
|
|
cd ~/omarchy-setup
|
|
./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
|
|
`<file>.bak.<epoch>`. 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.
|
|
|
|
## How profiles are built
|
|
|
|
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/` | `~/.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 `<unit>.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) |
|
|
|
|
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.
|
|
|
|
| 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 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-<layer>.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).
|
|
|
|
**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.
|
|
|
|
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 <chosen> --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:
|
|
|
|
- **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
|
|
addresses in the profile's `handoff_peer_ips`. Check it first: `journalctl -k
|
|
-g 'UFW BLOCK'`.
|
|
|
|
`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.
|
|
|
|
`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** (`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** 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** (`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
|
|
(`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** (`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. 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--<name>`.
|
|
|
|
`capture.sh plugins` re-bundles and re-diffs these, so local work keeps flowing
|
|
back into the repo.
|
|
|
|
`noconnor.agents` is the Agents bar widget held at the Omarchy 4.0.4 panel. 4.5
|
|
replaced it with a compact one (a meter per limit, the percentage only on
|
|
hover, no tokens by day or by model). It is a clone of `omarchy.agents`
|
|
(`omarchy plugin clone`) with `Panel.qml`, `Main.qml` and `README.md` taken from
|
|
upstream tag `v4.0.4`; `Agent.qml` and `assets/` stay at 4.5, and the usage
|
|
data still comes from the system's `omarchy-agent-usage-*` collectors. It has
|
|
no git history of its own, so it is carried as plain files in
|
|
`profiles/personal-desktop/config/omarchy/plugins/noconnor.agents/` and the
|
|
config step installs it and pulls it back. The 4.5 panel's account controls
|
|
(Use, Autoswitch, adding an account) are not in it. After installing it on a
|
|
running shell, `omarchy restart shell`: until then the built-in instance keeps
|
|
the `omarchy.agents` IPC target.
|
|
|
|
## 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
|
|
`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 <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`.
|
|
- **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.
|
|
|
|
## Adding things
|
|
|
|
**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 profiles/machines/hp-laptop/config/foo/bar.conf
|
|
git add profiles/machines/hp-laptop/config/foo/bar.conf
|
|
```
|
|
|
|
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-<layer>.lua`, not in `bindings.lua` or
|
|
`looknfeel.lua`.
|
|
|
|
**A role.** `mkdir profiles/roles/<name>`, give it a `profile` (`description=`,
|
|
optionally `dotfiles=(...)`) and a `packages.txt`, plus
|
|
`config/hypr/layers/2N-<name>.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/<name>` 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
|
|
<name> --dry-run` on it, then `./capture.sh` after any hand-tuning.
|