noconnorandClaude Opus 5.5 5c9797655e Capture omarchy-air's post-10-16 customizations into its layer
Everything on the Air that existed only on the machine:

- git/config and mimeapps.list, which the omarchy-air-host branch captured
  but profiles never picked up.
- NetBird: netbird-bin under [aur], the networkd drop-in it writes, and a
  setup.sh that runs `netbird service install` and enables the unit (the AUR
  package ships none). `netbird up` stays manual.
- ufw rules beyond Omarchy's stock set and the handoff step's 4242 rule:
  ssh and the ocovo fabric (7746/udp) from the home LAN, 7746 over wt0, and
  7790/tcp.

base gains omarchy-fix-qt-color and a setup.sh that runs it. Qt 6.12's
QtQuick Color singleton shadows the shell's qs.Commons Color, so the bar,
lock screen and polkit dialog lose every colour; the fix renames the
singleton to ShellColor. An omarchy upgrade restores the stock files, so it
is idempotent, skips Qt < 6.12, and no-ops once upstream renames it.
Verified: run against the pristine backup it reproduces the Air's
hand-patched /usr/share/omarchy/shell byte for byte.

Left out on purpose: ocovo-node.service (written by `ocovo service
install`) and environment.d/ohv.conf belong to those projects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 07:32:00 -05:00

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
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 includes 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:

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.

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%