Commit Graph
6186 Commits
Author SHA1 Message Date
ZacharyZhang-NYandClaude Fable 5 320ba5118a Update: strict-mode migrations, per-file completion markers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:24:12 -04:00
ZacharyZhang-NYandClaude Fable 5 b44380db1d Add omarchycn update for overlay installs with migration runner
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:12:46 -04:00
ZacharyZhang-NYandClaude Fable 5 1ef2743f9e Overlay: manifest-as-intent before copy, two-pass validated uninstall
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:11:34 -04:00
ZacharyZhang-NYandClaude Fable 5 88b933d2bc Overlay: validated manifest with dest record, ownership checks, clean reinstall
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:09:29 -04:00
ZacharyZhang-NYandClaude Fable 5 e3ca9ee493 Add overlay installer for existing Omarchy systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:06:29 -04:00
ZacharyZhang-NYandClaude Fable 5 fe42c049ff Use full -Syu transactions, no partial upgrades
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:05:24 -04:00
ZacharyZhang-NYandClaude Fable 5 afd75160b5 Document [omarchycn] pacman repository setup
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:02:23 -04:00
ZacharyZhang-NYandClaude Fable 5 738393851c Keyring: merge keys via gpg import/export, pkgrel 2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:01:38 -04:00
ZacharyZhang-NYandClaude Fable 5 b7ff3a32c7 Keyring build: .key source names so makepkg does not treat them as sigs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:00:49 -04:00
ZacharyZhang-NYandClaude Fable 5 a867157978 Add omarchycn-keyring package and container build script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:00:28 -04:00
ZacharyZhang-NYandClaude Fable 5 04b2d8836e Doctor: accept all module arg, survive empty mirrorlist
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:58:27 -04:00
ZacharyZhang-NYandClaude Fable 5 386bf49758 Doctor: silence best-effort notification in headless runs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:56:09 -04:00
ZacharyZhang-NYandClaude Fable 5 dd3b893137 Add cn doctor with mirror auto-failover and restore commands
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:55:08 -04:00
ZacharyZhang-NYandClaude Fable 5 d1a5ddfd02 dev-mirror: fix cargo official/duplicate handling, sed-free line set, gemrc safety
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:47:40 -04:00
ZacharyZhang-NYandClaude Fable 5 e48264373d dev-mirror docker: create /etc/docker before writing daemon.json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:43:19 -04:00
ZacharyZhang-NYandClaude Fable 5 15bf72dfe3 dev-mirror lib: shellcheck directive for cross-file array
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:42:41 -04:00
ZacharyZhang-NYandClaude Fable 5 8dab891155 Add dev-mirror manager for npm, pip, cargo, go, gem, docker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:42:29 -04:00
ZacharyZhang-NYandClaude Fable 5 aabe95b887 Mirror probe: guard failures under set -e, reject HTTP errors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:40:27 -04:00
ZacharyZhang-NYandClaude Fable 5 434f848dd8 Mirror manager: shellcheck fixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:35:11 -04:00
ZacharyZhang-NYandClaude Fable 5 2bcf0a20f0 Add Arch mirror manager: benchmark, apply, pin, restore, status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:34:55 -04:00
ZacharyZhang-NYandClaude Fable 5 18041c7537 cn commands: use OMARCHY_PATH per runtime convention
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:32:51 -04:00
ZacharyZhang-NYandClaude Fable 5 d4002897e3 Add omarchycn CLI entry and cn command group
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:29:01 -04:00
ZacharyZhang-NYandClaude Fable 5 4f06b2364f Sync workflow: always open PR, report trial-merge status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:24:38 -04:00
ZacharyZhang-NYandClaude Fable 5 2fd24589c2 Add automated upstream sync workflow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:22:44 -04:00
ZacharyZhang-NYandClaude Fable 5 9ce9f300ae Add OmarchyCN release signing public key and verification docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:18:38 -04:00
ZacharyZhang-NYandClaude Fable 5 db5c7db39a Fix CONTRIBUTING sync wording to match current state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:17:40 -04:00
ZacharyZhang-NYandClaude Fable 5 c1b9d655bb Add NOTICE and governance docs for OmarchyCN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:15:43 -04:00
ZacharyZhang-NYandClaude Fable 5 743ae05acc Feature-focused Chinese README; move PRD and task list to local-only
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:06:40 -04:00
ZacharyZhang-NYandClaude Fable 5 8125f24531 Rewrite README in Chinese with OmarchyCN vs Omarchy comparison
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 15:58:00 -04:00
ZacharyZhang-NYandClaude Fable 5 50b6798467 Record T8-T9: release published, all tasks complete
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 15:26:33 -04:00
ZacharyZhang-NYandClaude Fable 5 3b0f02968a Record T5-T7: build env, baseline ISO build, checksum and boot smoke test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
4.0.0.alpha-cn.1
2026-08-24 15:10:19 -04:00
ZacharyZhang-NYandClaude Fable 5 5fa312e0ca Record T1-T4 completion and review-corrected M0 partial scope
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 14:34:15 -04:00
ZacharyZhang-NYandClaude Fable 5 2c8a687434 Add OmarchyCN PRD and M0 execution task list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 14:21:55 -04:00
5d3299fb94 Wait for the keypress ourselves instead of asking gum to (#8082)
* Wait for the keypress ourselves instead of asking gum to

gum 2.0 runs a spun command without the terminal attached, so the
`gum spin -- read -n 1` that held the presentation terminal open returned
at once. Every menu command that ended in a failure took its window down
with it before the error could be read, which is how a failed update
looked like a terminal that just quit.

Read the key directly. gum's own terminal query replies are still sitting
on the tty when the spinner stops, so drain those first or they answer the
prompt on the user's behalf.

The green dot reads better than the globe did, so the provisioning notice
uses it too and drops its spinner along the way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Ask the terminal for itself before prompting on it

The /dev/tty node is there whether or not a terminal is behind it, so the
existence check passed on a headless run and left both reads failing with
"No such device or address". Open it instead.

Prompt on the terminal too, rather than stdout: a caller that redirects us
was sending the prompt to a file while the read waited on the terminal,
which looks like a hang with no instruction on screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 19:46:02 +02:00
1565919c87 Flag a reboot when the docker group changes (#8080)
Group membership is fixed at login, so removing (or adding) the docker group
does not take effect in the running session. The existing-user migration and the
Setup > Security toggles now call `omarchy-state set reboot-required`, so
omarchy-update-restart prompts for the reboot that actually applies the change
(and the bar shows it pending). A plain log out and back in still works.

The migration test now exercises the real removal command and omarchy-state
rather than a stub, asserting the reboot flag is set on removal and left alone
when the user is already out of the group.


Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 19:32:37 +02:00
b5ded31e2f Don't put the user in the docker group; make it opt-in (#8056)
* Don't put the user in the docker group; make it opt-in

The docker group is root-equivalent: anything in it can `docker run -v /:/host`
and rewrite the host as root with no password. On a single-user box that's not
an escalation (the owner is already a wheel/sudo user), but it hands any code
running as the user — a rogue plugin, a poisoned dependency — a silent, headless,
passwordless path to root that sudo's password prompt would otherwise gate.

Stop granting the docker group by default. The daemon still runs (docker.socket);
the Docker TUI and the Windows VM reach it through a polkit prompt, and the plain
`docker` CLI runs under sudo. Sudoless Docker is a warned opt-in via
Setup > Security (omarchy-setup-security-sudoless-docker).

No automatic path may re-grant it: install and first-boot provisioning never
record or apply the group (provisioning also filters a docker line left in an
older factory snapshot), and the Quattro upgrade no longer adds it.

The Windows VM keeps needing the root daemon for a privileged container (KVM,
NET_ADMIN), so it is reworked to run without the group and without becoming a new
way in:

- The compose lives in a root-owned dir and is only written by an elevated,
  input-validated writer. A root-invoked bring-up must never consume a file a
  user-process could rewrite to bind-mount / into the guest — the old
  ~/.config/windows compose was exactly that. Volume paths are rebuilt from
  $HOME on migration rather than trusted from the (user-writable) legacy file,
  path validation rejects traversal, and the privileged sub-action is checked
  against an allowlist before dispatch (a slash in it would otherwise run as a
  path).
- pkexec elevates a verified root-owned command path, not a PATH-resolved one,
  so an authorized prompt can't be redirected to an attacker's binary.
- The guest password is kept in a private 0600 per-user file for RDP instead of
  a world-readable compose, and a declined authorization is reported as such,
  never as a completed stop.

Existing installs auto-migrate the VM (no redownload) and refresh the stale
Docker launcher entry.

🤖 Generated by Opus 4.8 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <codex@openai.com>
Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

* Migrate existing installs off the docker group

The default flip only reaches new installs; existing users keep their docker
group membership and stay exposed. Extend the migration that already refreshes
the Docker launcher to also remove the current user from the group when present,
reusing omarchy-remove-security-sudoless-docker so there is one source of truth
for the change and its notice. It takes effect at next login (the current
session keeps working), and passwordless docker can be turned back on from
Setup > Security > Sudoless Docker.

Migrations run with sudo available — during `omarchy update`, or in the terminal
the pending-migrations notification opens — so the privileged removal does not
prompt at an unattended login. The no-op path (already out of the group) needs
no privilege.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

* Refuse symlinked VM mount sources; correct the docker CLI docs

Review follow-ups.

valid_path keeps a traversal string (/./, //, ..) out of the compose, but it is
a string check: a symlink planted at ~/.windows or ~/Windows redirects the
privileged bind mount exactly as traversal would, because docker follows it. So
verify the mount sources as root immediately before bringing the VM up — refuse
a source that is a symlink or resolves through one — which is where the string
check cannot help. A missing source stays fine (docker creates a plain dir).

Also correct the development-tools manual: the CLI is not transparently elevated
(there is no docker wrapper and `d` is still plain docker), so say plainly that
docker on the command line takes `sudo` until sudoless Docker is enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-24 18:56:00 +02:00
Ryan Hughes 5afc9e1495 Revert "Require signed packages from the Omarchy repository"
Reverts 39cffb8f, which landed on quattro through an accidental direct
push. The change returns for review as a pull request.
2026-08-24 12:53:17 -04:00
Ryan Hughes 39cffb8f51 Require signed packages from the Omarchy repository 2026-08-24 12:37:46 -04:00
4baae6bf2a Draw text as ASCII art in the Omarchy logo font (#8037)
* Add omarchy-ascii for drawing text in the logo font

Renders text as ASCII art in Delta Corps Priest 1, the FIGlet font the Omarchy wordmark itself is drawn in, so branding art can be words rather than a picture. The font is embedded in the script and the layout is done in awk, so the command adds nothing to the default package set.

The layout runs on one-byte stand-ins for the five block characters the font draws with. Column arithmetic over the characters themselves counts bytes in one locale and characters in another, and the stand-ins keep length() and substr() counting columns either way.

Delta Corps Priest 1 carries letters and spaces only, and every mirror of it ships the same file with the digit and punctuation glyphs empty. Anything else is dropped and named on stderr, and text with nothing drawable at all exits 1 rather than printing silence.

🤖 Generated by Opus 5 in Claude Code.

* Correct what the renderer did with input it could not draw

The route never ran on piped text. The metadata declared `<text...>` as required, so `omarchy ascii` with nothing on the command line resolved to the router's help while `omarchy-ascii` run directly worked, which is why the tests missed it: they all called the binary. The argument is optional now, and a test goes through the route.

Text reached awk as a command-line variable, where awk reads backslash escapes of its own, so `omarchy ascii 'A\nB'` drew two blocks instead of naming the backslash as a character the font lacks. A text longer than the argument list could not be passed at all. It arrives as awk's input now, with the font on a descriptor of its own.

A line with nothing drawable printed nothing at all, so a blank line between two words closed the gap up rather than keeping it. Every line draws its block now, blank ones included, which is what figlet does with a newline.

Placing a glyph scanned and copied the whole width of the art so far, costing the square of the line's length: four thousand characters took forty-six seconds. A row is now held without its trailing blanks, counted separately instead, so a glyph costs its own width and those four thousand characters take a tenth of a second.

A skipped control character was named on stderr by writing it out, which sends it to the terminal as a control character; those are named by code now. An unknown option was drawn as art rather than refused, so a mistyped `--width 40` quietly rendered the word "width".

figlet.c trims the column of blanks that every row of an `M` shares when it is the first glyph on a line, and figlet.js keeps it. asciiart.eu runs figlet.js, so the rendering follows figlet.js and a test pins that `M`, because the wordmark alone does not catch the difference and the next reader would have no way to tell the choice from an oversight.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Codex XHigh <codex@openai.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-24 17:58:52 +02:00
d99d4fc6de Remove the sudo lockout reset command (#8046)
Nothing in the repository called omarchy-sudo-reset, and its one line interpolated an environment-supplied $USER into a string handed to a root shell: `su -c "faillock --reset --user $USER"`. $USER is an environment variable rather than a kernel-supplied identity, so whatever set it before the command ran chose the rest of what root's shell executed. That is not a way past PAM on its own — su still has to authenticate — but the installer sets root's password to the user's own, so the prompt this raises is one the user answers by habit.

It bought little for that. Omarchy sets `deny=10 unlock_time=120` in /etc/pam.d/system-auth and in the lock screen's PAM stack, so a lockout takes ten wrong passwords to reach and clears itself two minutes later, and the manual documents the root-TTY reset for anyone who would rather not wait.

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-24 15:49:17 +02:00
20400badda Stop a psmouse quirk from failing every install (#7236)
* Stop a psmouse quirk from failing every install

install/hardware/fix-synaptic-touchpad.sh calls modprobe against the running
kernel. Under 4.0 the only thing that runs it is the ISO finalizer, inside
arch-chroot, where uname -r still names the live ISO's kernel while /lib/modules
holds the target's. The live ISO always boots linux-t2 and the configurator
gives every machine that is not a T2 Mac stock linux, so those two never match:
modprobe exits 1 with "Module psmouse not found in directory /lib/modules/<live
kernel>". run_logged returns that status and omarchy-apply-hardware runs under
set -euo pipefail, so a fresh install stops on the first machine with a device
named "synaptics" and no psmouse loaded -- reported from a ThinkPad in #6985,
but nothing about it is Lenovo-specific.

Skip the load when the running kernel's modules are not reachable, and warn
instead of failing when modprobe declines for any other reason. An optional
touchpad improvement should never be able to halt an install.

This does not make InterTouch reach the installed system: a module loaded into
the live kernel is gone at reboot, so on 4.0 this script has never applied
anything to an installed machine. Persisting it means writing options psmouse
synaptics_intertouch=1 to /etc/modprobe.d, which forces the SMBus transport past
the kernel's own allowlist on any touchpad merely named "synaptics" in
/proc/bus/input/devices. That is a hardware-behaviour change on a wide class of
machines, so it is left for a maintainer to decide separately.

Fixes #6985

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Ask modprobe whether psmouse resolves rather than guessing at /lib/modules

The reachability check ran through OMARCHY_SYNAPTIC_MODULES_DIR, an override
modprobe itself never saw: it decided whether the load was attempted but could
not change where modprobe looked, so the guard and the load consulted different
places and the seam read as though it configured module lookup. modprobe -qn
answers the same question directly -- it resolves psmouse against the running
kernel without loading it -- so the guard and the load now agree by
construction and the override goes away. The arch-chroot case that broke
installs is still skipped silently, for the same reason it always was: the live
kernel's modules are not the ones on disk.

Inline the remaining /proc/bus/input/devices override at its only use. These
leaves are sourced one after another into a single shell, so a variable left at
the top level outlives the script that set it.

Pin the wiring assertion to the run_logged call instead of any mention of the
path. A commented-out line satisfied the old grep, so the test could pass with
the quirk no longer running at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013L5zwTiZ2CsgazyxXBiPa8

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 15:33:03 +02:00
535d8f3485 Regenerate mise wrappers that still print mise's output to stdout (#8041)
omarchy-mise-install gained --quiet on its `mise use -g` line so a wrapper
no longer prints mise's "tools: pkg@version" ahead of the tool's own
output. That only changes wrappers written from then on, and the migration
that installed the current ones is already marked complete, so every
wrapper already on disk keeps polluting stdout: `claude --version` still
answers with two lines, and a wrapper for a protocol-speaking command
answers with a line its caller cannot parse.

Rewrite them through omarchy-mise-install so the template stays in one
place. Wrappers were written by four different generated forms over time
and only the ones a later migration happened to regenerate moved forward,
so all four are recognized. The whole file has to match one of them
exactly, which leaves a wrapper someone has added a line to alone and
makes a second run a no-op.


Claude-Session: https://claude.ai/code/session_01Mv1FyKG2VGZRdtknQ7YjU8

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 14:36:09 +02:00
Ryan HughesandGitHub 43bfe9b9d8 Merge pull request #7926 from basecamp/harden-notification-exec-argv
Run notification click actions as safe argv
2026-08-23 19:51:57 -04:00
Ryan Hughes 3b0d9b5734 Merge remote-tracking branch 'origin/quattro' into harden-notification-exec-argv
# Conflicts:
#	test/shell.d/notification-send-test.sh
2026-08-23 18:31:42 -04:00
Ryan Hughes 8f245e59dc Restore notify-send option parity and prove legacy fail-closed
The direct-Notify rewrite dropped notify-send options that callers rely on, which
the momus review caught: omarchy-display-text-size uses -r/-p to refresh one
toast in place, and the acceptance suite uses the --expire-time=15000 equals
form. Re-add -r/--replace-id (replaces_id), -p/--print-id (emit the returned id),
and the --flag=value form for every long option; a dash-leading description like
"-50% off" is now kept as body text rather than erroring, and --exec "" is
rejected.

Add a fixture proving the deliberate upgrade behavior: a popup persisted by a
pre-upgrade shell with a legacy `exec` shell string restores with an inert click
(execArgv empty, the old string never run) rather than executing it.
2026-08-23 17:18:58 -04:00
Ryan Hughes e3729a385b Send notifications via the Notify D-Bus method, never notify-send
omarchy-notification-send now calls org.freedesktop.Notifications.Notify
directly with `busctl --user`, instead of shelling out to notify-send. Each
value is one typed D-Bus parameter, so there is no argv/option layer that could
reinterpret a relayed headline like `--hint=…` or `-rf` as an option or a hint:
the summary and body are strings, and omarchy-exec-argv is built only from
--exec. A leading `--` keeps busctl's own getopt from reading a dash-leading
value as a busctl option.

Map -i to app_icon, -t to expire_timeout, and urgency to the byte hint; unknown
options are now a hard error rather than a silent pass-through. Route the unused
hypr o.notify helper and the sample hooks through the wrapper too, and tighten
the bin-style test so nothing under bin/ may call notify-send. The test stubs
busctl and trips if notify-send is invoked.
2026-08-23 17:02:29 -04:00
Ryan Hughes be63983d16 Describe parseExecArgv as a structural check, not intent
The comment claimed the validation kept a hostile hint from reaching a shell,
but it is purely structural: a well-formed ["bash","-c",…] passes. Say so,
and point at the separate sender-trust boundary.
2026-08-23 16:47:13 -04:00
7e469f962d Let a received Taildrop file wait to be answered (#7953)
* Let a received Taildrop file wait to be answered

A delivery can land hours after it was sent, and the toast announcing it was expiring after five seconds -- so a file that arrived while nobody was at the machine was gone from the screen before anyone could click it open. Critical urgency is what the shell reads as a popup that lives until it is clicked or dismissed, the same thing omarchy-crash-watch uses to keep its click-to-diagnose toast around.

The wrapper takes options after the headline and description as well as before, which is how this argument list is built. That path had no test, and it fails quietly rather than loudly: the wrapper appends its own default urgency last, so an urgency it stopped parsing would reach notify-send as `-u critical ... -u low` and the toast would go back to expiring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Say it in the commit message, not above the code

`-u critical` next to a line that builds a notification says what it does, and the five lines explaining why it is there were a recap of the change rather than something the code could not say. The reasoning stays where it belongs, in the commit that made the change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-23 22:12:07 +02:00
Claude Opus 5 (1M context)andCodex XHigh 1b15120d27 Keep relayed text out of notify-send's option parser
The argv click command closed injection through the hint's value, but the
sender still handed the headline and description to notify-send bare. A
value beginning with a dash is parsed there as flags, and one shaped like
`--hint=string:omarchy-exec-argv:[...]` is read as a hint of its own --
libnotify keys hints in a hash table, so the later of two replaces the
earlier and a forged headline outranks the vector --exec built.

That is reachable without any --exec in sight: omarchy-tailscale-send
passes a single file's basename verbatim as the description, so a file
named like the hint gives its click action to whoever chose the name.

Put the headline and description behind a `--` so notify-send reads them
as text, and refuse any pass-through word carrying omarchy-exec-argv --
--exec is the only thing that may build a click command.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-23 21:44:06 +02:00
Ryan Hughes 21cbbf8194 Recognize --exec only after the positionals
A greedy pre-scan matched the first --exec token anywhere on the line, so an
untrusted headline or description literally equal to "--exec" could be mistaken
for the delimiter. It always failed closed (the following token became a
dash-option argv[0] that parseExecArgv rejects), but it was a latent footgun
surfaced by an adversarial review.

Detect --exec only in the trailing-option loop, after the headline/description
positionals are captured. A headline that is the string "--exec" is now kept as
text and the real trailing --exec still wins. The residual — a description
exactly equal to "--exec" losing its click action — is an inherent, harmless CLI
ambiguity for a value identical to the delimiter.
2026-08-23 14:41:56 -04:00