Author SHA1 Message Date
ZacharyZhang-NYandClaude Fable 5 640c0d4291 Begin the 4.0.0.alpha-cn.8 release: cn/release to 8
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 23:37:31 -04:00
ZacharyZhang-NYandClaude Fable 5 ed9ef8bc04 Propagate key-trust failures and let doctor --fix heal a keyless mirror line
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 23:35:24 -04:00
ZacharyZhang-NYandClaude Fable 5 653656e439 Trust the registry key inside the mirror fix so Required never strands a client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 23:31:13 -04:00
ZacharyZhang-NY 8715c20af2 Merge remote-tracking branch 'origin/quattro' into quattro
# Conflicts:
#	default/pacman/pacman-stable.conf
2026-08-28 23:18:35 -04:00
ZacharyZhang-NYandClaude Fable 5 f9c4085982 Make CJK lang rules survive 50-omarchy.conf's generic-family assign (fixes #4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 23:12:03 -04:00
ZacharyZhang-NYandClaude Fable 5 074247c79a Restore the app catalog verification date to its actual audit day
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 23:09:26 -04:00
ZacharyZhang-NYandClaude Fable 5 dc0513a519 Remove WeCom from the China app catalog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 23:05:22 -04:00
ZacharyZhang-NYandClaude Fable 5 495b24cedf Point the site and README at the 4.0.0.alpha-cn.7 release
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 22:23:26 -04:00
ZacharyZhang-NYandClaude Fable 5 9ce9979ce5 Begin the 4.0.0.alpha-cn.7 release: cn/release to 7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 22:14:10 -04:00
Ryan HughesandGitHub f0672772d2 Merge pull request #8268 from Skeptomenos/fix/copy-url-python-shim-recursion
Avoid mise shim recursion in Copy URL migration test
2026-08-28 21:54:19 -04:00
Ryan HughesandGitHub 62eb5182d0 Merge pull request #8835 from basecamp/fix-browser-policy-exit-trap
Fix migration 1787515927 failing on Bash 5.3
2026-08-28 21:24:32 -04:00
ZacharyZhang-NYandClaude Fable 5 06174bbd06 Distribute release ISOs through the dl.zacharyzhang.com R2 host
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 21:14:10 -04:00
Ryan Hughes 6b10dbf191 Harden Firefox policy dirs even when the theme refresh fails 2026-08-28 19:00:03 -04:00
Ryan Hughes 5925929cb6 Stop the browser policy EXIT trap from reporting a clean run as failed 2026-08-28 19:00:03 -04:00
Ryan HughesandGitHub 7d58bb9a62 Merge pull request #7972 from acrogenesis/harden-browser-policy-dirs
Stop world-writable browser policy directories
2026-08-28 18:00:20 -04:00
David Heinemeier HanssonandGitHub 6dd9aa55cc Merge pull request #6919 from AksharP5/agent/resolve-dialog-pointer-focus
Stop DaVinci Resolve dialogs from recapturing pointer focus
2026-08-28 23:41:00 +02:00
Ryan HughesandGitHub 468b511249 Merge pull request #8397 from ErikMelton/unauthorized-http-get-requests-from-notifications
Require textFormat declaration for all Text elements
2026-08-28 15:50:19 -04:00
ZacharyZhang-NYandClaude Fable 5 101a3db393 Anchor the cn-convert style exemption to the exact command
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 14:15:02 -04:00
ZacharyZhang-NYandClaude Fable 5 64a531f999 Exempt the cn-convert bootstrap from the command-helper style rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 14:12:26 -04:00
ZacharyZhang-NYandClaude Fable 5 7f9236777f Harden the new cn assertions against false passes and fix overlay update wording
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 14:04:34 -04:00
ZacharyZhang-NYandClaude Fable 5 92ebed0f34 Assert the cn update wiring in tests and document the unified update path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:57:07 -04:00
ZacharyZhang-NYandClaude Fable 5 e62584b31f Add WeCom to the China app catalog and menu
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:51:58 -04:00
ZacharyZhang-NYandClaude Fable 5 39026fd41e Move the cn migration call into omarchy-migrate and guard downgrades onto pre-migrate trees
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:46:25 -04:00
ZacharyZhang-NYandClaude Fable 5 81c253b86e Restore executable bits on omarchy-cn-convert and omarchy-cn-revert
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:46:11 -04:00
ZacharyZhang-NYandClaude Fable 5 f4d5fa4013 Run cn migrations in the update pipeline and serve package installs from omarchy cn update
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:35:25 -04:00
ZacharyZhang-NYandClaude Fable 5 7d22dc14a5 Fix stale step number in sync SLA and stale README version
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:32:23 -04:00
ZacharyZhang-NYandClaude Fable 5 9f15d42c2c Make cn/release mean the current release, bumped as step one of a release
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kb7vDj6PHwymeDeSUUk1eX
2026-08-28 13:26:44 -04:00
David Heinemeier HanssonandGitHub 06e32d243d Merge pull request #8198 from bastidotnet/harden-apple-brightness-device-cache
Validate the cached Apple-display device path before use
2026-08-28 14:42:22 +02:00
ZacharyZhang-NYandClaude Fable 5 f9c9b3c364 Bring the landing site into the repo as site/
Vite 8 vanilla page for omarchycn.zacharyzhang.com: kami palette on a
12-col Swiss grid, self-hosted Fusion Pixel zh_hans (OFL shipped),
GSAP ScrollSmoother/ScrollTrigger with reduced-motion opt-out, reicon
icons, ISO download plus one-click convert command copy, real
installer screenshot. shots.mjs is the rendered-state harness; deploy
is npx wrangler deploy from site/ (custom domain + workers.dev).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 20:07:43 -04:00
ZacharyZhang-NYandClaude Fable 5 f72a48f747 One-click convert between vanilla Omarchy and OmarchyCN
omarchy-cn-convert turns a package-based install into OmarchyCN in one
curl-able run: registry key + [omarchycn] repo, --ask=4 swap onto the
cn-built packages (Chinese menu/bindings and OMARCHY CN branding ride
the settings tree), [omarchy] mirror line, zh_CN locale, fcitx5-rime
seeding, CN Arch mirrors, and a plymouth rebrand, recording pre-state
in /var/lib/omarchycn/convert-state. omarchy-cn-revert restores that
state symmetrically and survives user-removed packages, self-deletion
mid-swap, and cosmetic plymouth failures after the state is cleared.
Checkout installs are refused toward the overlay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 18:20:51 -04:00
ZacharyZhang-NYandClaude Fable 5 c51df9977b Publish cn-built packages to the omarchycn registry before each release
packages/publish-cn-packages.sh uploads the omarchy-dev and
omarchy-settings-dev builds (token via curl config on stdin), and the
release checklist runs it as step 8 ahead of the public Release so the
one-click convert never installs stale packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 18:20:51 -04:00
ZacharyZhang-NYandClaude Fable 5 1535e7871f Bump cn/release to 7 after publishing 4.0.0.alpha-cn.6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 18:20:51 -04:00
ZacharyZhang-NYandClaude Fable 5 c4dcd2b63a Document and test the [omarchy] mirror wiring
Static suite assertions pin the stable conf's server order and keep
edge/rc upstream-only; the guard test proves exact-line semantics.
docs/pacman-repo.md gains the mirror section with the honest
operational contract: staleness is invisible to pacman, fallback
fires only on retrieval failure, and omarchy-refresh-pacman's -Syyuu
downgrade wipes the cn layer while upstream ships older omarchy-dev.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 16:29:34 -04:00
ZacharyZhang-NYandClaude Fable 5 b183be2832 Put the OmarchyCN [omarchy] mirror first on the stable channel
The Gitea mirror db is registry-signed, so installs trust the registry
key right after the pacman.conf restore, and a cn migration does the
same on existing systems with an idempotent exact-line guard. The
insertion lives in cn/lib/mirror.sh so 'omarchycn doctor mirror --fix'
can restore the line after omarchy-refresh-pacman resets an overlay
system's conf. Only stable is mirrored: the Gitea repository name must
equal the pacman section name, and edge/rc stay on upstream directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 16:20:48 -04:00
ZacharyZhang-NYandClaude Fable 5 fc9afae2b6 Cover the ollama provider paths in the AI mock regression suite
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 16:02:06 -04:00
ZacharyZhang-NYandClaude Fable 5 111b857cda Add local Ollama as an AI Hub provider for claude-code
Ollama v0.14+ speaks the Anthropic Messages API natively, so the
registry drives it with a fixed 'ollama' token and runtime-listed
models: static_token skips secret storage everywhere keys resolve,
models_dynamic defers model validation to the live /api/tags list,
and the setup wizard installs the right GPU variant and starts the
service before listing local models. Doctor stops treating the
endpoint port as part of the hostname.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:55:15 -04:00
ZacharyZhang-NYandClaude Fable 5 4b280a1767 Add DSH to the agent roster and Install > AI
Official npm @deepseek-ai/dsh (bin: dsh) rides the mise path like Dim.
dsh has no TUI, so the interactive launch is the documented 'dsh web'
and one-shot prompts use 'dsh --profile headless'. Auth is dsh-managed,
so it stays out of the AI Hub registry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:42:46 -04:00
d1845245d3 Unquote the new variables inside [[ ]]
AGENTS.md asks for unquoted variables inside `[[ ]]`, with quotes reserved for string literals being compared. The three conditions added here quoted them.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 21:40:31 +02:00
9d8c0176d1 Make the cache tests fail when either check is removed
Neither half of the validation was covered. Dropping `&& -c $cached` from the wrapper left the whole file green: all three poison values fail on the pathname prefix, so none of them ever reached the character-device test. A path that matches the hiddev glob but is not a device now covers it, and it is the real case rather than a synthetic one -- the display replugs, the interface renumbers, and the cached node is gone. It is added only when the host has no such node, so a machine with the display attached cannot fail there spuriously.

The no-XDG_RUNTIME_DIR assertion had the same problem for the opposite reason: its decoy held a path the validation rejects on its own, so restoring the `${XDG_RUNTIME_DIR:-/tmp}` fallback left it passing. It asserts on the open now instead of on the contents -- a FIFO with no writer blocks whoever opens it, so a wrapper that consults the path hangs and one that ignores it exits. mkfifo is atomic and fails outright if the path is taken, so it still neither overwrites a file nor follows a symlink at the fixed path.

Clearing created_tmp_cache as soon as the decoy is removed keeps this run's EXIT trap from deleting a concurrent run's decoy at the same fixed path, which would have let that run pass against the old code.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 21:40:31 +02:00
ZacharyZhang-NYandClaude Fable 5 602a82621a Run the package repo mirror sync from CI every six hours
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:34:44 -04:00
ZacharyZhang-NYandClaude Fable 5 87b4cc4da1 Mirror the upstream stable package repo into the Gitea Arch registry
packages/sync-omarchy-repo.sh diffs the upstream omarchy.db against the
registry's own db by filename, streams missing packages through (409 =
already present), and deletes entries upstream no longer lists. Only a
404 counts as first sync; any other mirror-db failure aborts rather
than faking an empty mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
2026-08-27 15:34:01 -04:00
OmarchybotandGitHub 83881e979b Merge pull request #7984 from Chessing234/fix/webapp-name-slashes
Keep a web app name out of the launcher's directory structure
2026-08-27 19:40:46 +02:00
9ece53cede Prove the web app name guard, and reject before the icon is fetched
The slash guard was the only thing keeping a name out of the directory structure, and nothing tested it: deleting it left the suite green, because creating the launcher directly in the applications directory already makes the redirect fail on its own, with a raw bash error instead of the message. The assertion is on the message now, alongside the traversal case the guard actually closes -- on quattro a name of `../../../../escaped` writes its launcher clean outside the applications directory.

The interactive prompt read the name, fetched the favicon, wrote it and updated the icon cache before the name was ever checked, so a URL typed into the Name field left an icon behind on every attempt. Validating as soon as the name is read covers both paths from one place.

Removing by name also scanned unconditionally, so a machine with no applications directory printed a find error where omarchy-remove-gaming-xbox-cloud does not hide stderr.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 19:25:25 +02:00
David Heinemeier HanssonandGitHub 946704f309 Merge pull request #8224 from kevinmcconnell/once-requires-sudo
Run ONCE with sudo when installing
2026-08-27 17:36:31 +02:00
Ryan HughesandGitHub c5a5e14e99 Merge pull request #8072 from basecamp/require-omarchy-repo-signatures
Require signed packages from the Omarchy repository
2026-08-27 11:29:12 -04:00
4be440b501 Close six ways the textFormat scan reported success without checking
Each of these is a Text rendering external data with no textFormat, written in
a form that passed silently. None exists in this tree, so they were holes in
the guard rather than live exposures — but a guard is only worth what it
catches, and every one of them is a single line someone could plausibly write.

  Text /* why */ {          strip_noise knew // and not /* */, so a block
                            comment between the type name and its brace hid
                            the element from every rule at once
  QQ.Text { ... }           a namespaced import made the name compare unequal
                            to `Text`, and the element was skipped outright
  visible: textFormatEnabled  textFormat was matched as a substring, so a
                            lookalike property exempted the whole block
  component Info:           a component root with its Text on the next line;
    Text {                  the one-line form was covered and this was not
  an unreadable subdirectory  rglob() swallows a directory it cannot enter, so
                            a locked subtree scanned as though it were empty

The scan moves out of the heredoc into qml-text-format-scan.py, taking its root
as an argument, because nothing could run it over anything but the real tree —
and a scanner whose only input always passes cannot be shown to fail. The test
now runs it over nineteen fixtures, one per form above and one per form the
scan already handled, so a later edit that loosens it fails here instead of
going unnoticed until something renders a remote image.

Two limits stay open and are written down in the module docstring rather than
papered over: text assigned from elsewhere (a Binding element, PropertyChanges,
an onCompleted assignment, a property alias onto a child) is invisible to a
scanner that reads each element's own declaration, and a regex literal holding
a brace throws off the brace depth. Neither shape exists in this tree and both
need a QML parser, not another regex.

Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:28:13 +02:00
David Heinemeier Hansson 7026ede90b Strip image tags after the newline rewrite, not before it
The card binds the body Text to styledBody, which rewrites newlines to <br/>
*after* sanitizeBody has run. That rewrite inserts tag syntax into text the
stripper deliberately kept: a kept tag may hold a `<` of its own, and `<x`,
newline, `<img src="http://host/x.png">` is one tag named `x` to both the
stripper and Qt, so it survives whole — until the rewrite splits it into
`<x<br/>` and a live image tag the input never contained.

Measured against Qt 6.11.2 with an offscreen StyledText and a local HTTP
server: that body issues the GET after this branch's sanitizer and issues
nothing before it, because the one-pass /<img[^>]*>/gi it replaces deleted the
inner substring outright. The whole-tag bound is still the right trade — it is
what stops the stripper manufacturing tags — but it only holds if nothing edits
the string afterwards.

So move the rewrite into NotificationLogic, next to the reasoning it depends
on, and strip again after it. What Qt parses is then what was checked last. The
tests assert on styledBody for the same reason, since sanitizeBody's output is
no longer the string that reaches the renderer, and a regex assertion pins the
card's binding because no JavaScript assertion can see a QML property.
2026-08-27 16:53:55 +02:00
David Heinemeier HanssonandGitHub 9d02bb08f8 Merge pull request #8549 from basecamp/crash-notification-mute
Let a crash diagnosis mute that program's notifications
2026-08-27 13:19:46 +02:00
OmarchybotandDavid Heinemeier Hansson b68d4142d7 Cut the crash-mute section of the skill to what it instructs
The section had grown a paragraph per review round, each one explaining why the last was right, until one offer took a third of the file. Most of it was reassurance about what the command refuses rather than anything an agent has to do, and the command enforces that itself whatever the prose says.

What is left is the instruction: offer it and never run it unprompted, say how to lift it, which of the two names to pass and why the binary is the better one, quote it because the name is the crashed program's to choose, and name the interpreter collision before muting python or node on someone's behalf. Fifty-four lines to thirty-two, with nothing dropped that changes what the agent does.
2026-08-27 12:28:44 +02:00
ea6ee9440a Add omarchy-crash-mute to mute and unmute one program
The mute was reachable only as `omarchy-toggle crash-ignore/<program>`, which asks whoever runs it to know the flag layout, to reduce a binary's path to the name the watcher keys on, and to have read the rule that a name climbing out of that directory writes an unrelated toggle. All of that was carried in the skill's prose, which is the wrong place for a rule that has to hold: prose is advice, and the thing being advised about is a name the crashed program chose.

So it is a command now. `omarchy crash mute hyprland` silences that program, `off` lifts it, `toggle` flips it, and no argument lists what is muted. It takes the binary's path as readily as the name and reduces it the way the watcher does, so the `Executable:` line from `coredumpctl` can be handed straight to it; it refuses what is not one component of a name, so it cannot be talked into writing outside its own directory whatever it is given; and it re-reads the flag afterwards and reports what is now true rather than what was asked for. The listing counts only regular files, because that is all the watcher honours -- anything else in there would read as muted while the crashes kept arriving. A leading `--` is consumed so a program named `-h`, which the router would otherwise answer with its own help, can still be muted.

The watcher gained an unrelated fix that this uncovered. Its fields are read with `IFS=$'\t'`, and tab is IFS whitespace, so an empty field collapsed into the next delimiter and shifted every field after it along one: a crash whose comm was empty had a path read as its pid and was discarded as somebody else's. A process can set its comm to nothing, so that was reachable. Empty fields now arrive as a dash like missing ones, and a dash joins the empty and dot cases that fall back to `unknown`.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 11:26:28 +02:00
OmarchybotandDavid Heinemeier Hansson eeb4206c7b Say in the manual what the skill already says about quoting
The manual had single quotes covering "punctuation your shell would otherwise read as its own", which is more than they do: a name containing a single quote closes them, and the rest of it is read as shell. The skill states that correctly and the manual did not, so the one document a person reads before typing the command was the one making the claim that does not hold.
2026-08-27 10:38:18 +02:00
8d14869689 Let a crash diagnosis mute that program's notifications
A crash that is understood is not a crash that stops: an upstream bug waiting on a release, a program that dumps core every time it exits. The diagnosis explains it once and the toast keeps arriving, and the only answer Omarchy had was Crash Capture, which turns off every program's notifications in order to silence one.

The watcher already resolves a name to dedupe on and announces that same name in the toast, so the mute is keyed on it: a flag file under toggles/crash-ignore/, written by the existing omarchy-toggle and read by the existing omarchy-toggle-enabled. One flag per name rather than one list, so `on` mutes, `off` un-mutes, and `ls -A` shows what is muted, with no new file format and nothing to parse. It is the executable's basename wherever one was recorded, falling back to the process name, which the kernel truncates to fifteen characters -- muting the truncated form would match nothing, forever, while looking like it worked.

The name is not always a name, though, and the mute turns it into a path. A program picks its own comm and prctl takes anything, including slashes, and the watcher falls back to comm whenever a crash carries no absolute executable. So it is stripped to its last component first: without that, `a/../bar-off` is a legal comm aimed at an unrelated Omarchy flag, letting a crashing program suppress its own notification and letting a user who accepted the offered mute hide their bar instead. Stripping does not always leave a component either -- `/` leaves an empty string, which is no kind of array subscript and no kind of toast, and `.` or `..` names a directory that omarchy-toggle would touch and report success on, leaving a mute that never matches. Both fall back to `unknown`, the word omarchy-agent-crash already uses for a name it does not have, and which mutes like any other.

The skill offers this at the end of a diagnosis and never runs it unprompted, which makes it the single change a diagnosis may make to a system it otherwise only reads. It tells the agent to use the name it was handed rather than re-derive one, since the watcher resolved that name already and the two agree for ordinary names and not for strange ones; a diagnosis started by hand from `omarchy agent crash <pid>` is given no name and gets the derivation instead. It also says to treat the name as hostile text rather than as a word -- it is whatever the crashed program's author called a file, so a single quote inside one closes the quotes around it and the rest runs as the shell -- and to check the flag arrived rather than assume it.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 10:32:10 +02:00
Luke ParkeandGitHub eb7ecd13f3 Keep Ori interactive when launched with a prompt (#8455) 2026-08-27 09:21:07 +02:00
0260d2accb Stop the textFormat test from passing when it has not checked
The root rule matched only a file-level root Text, of which this tree has
exactly one. QML inline components are roots for the same reason — the
`text` of `component InfoValue: Text {` comes from every caller, so the
file it lives in never binds it — but they sit inside another element, so
the depth-1 test never saw them. Six went uncovered while the test
reported green, among them the network panel's InfoValue, which callers
bind to the IP address and gateway.

Six more ways to write a Text were read as clean rather than as unreadable:
an opening brace that is not last on its line, a brace on the line after
`Text`, a one-line block containing nested braces, a wrapped binding split
by a comment or a blank line before its `+` (which exempted a dynamic
binding as a literal), and a root Text indented from column zero. Require
the forms a line scanner can read instead of parsing QML; the tree already
writes every Text that way.

Last, a run that read no files reported success. A checkout with no shell/
QML now fails instead, since an all-clear from a scan that opened nothing
is the one answer this test must never give.

Each case is covered by a fixture that fails without its fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: OpenAI Codex (gpt-5, xhigh) <noreply@openai.com>
2026-08-26 17:42:07 +02:00
David Heinemeier HanssonandClaude Opus 5 e428dc2627 Strip image tags whose separator Qt skips but \s does not
QQuickStyledText skips the characters between `<` and the tag name with
QChar::isSpace(), which counts U+0085 NEL. JavaScript's `\s` does not, so
isImageTag() read no name at all from a tag written as `<`, U+0085, `img`,
kept it, and Qt then read `img` and issued the GET the stripper exists to
prevent. Measured against Qt 6.11.2 with an offscreen StyledText and a
local HTTP server.

Read the name by skipping everything that is not part of it rather than by
matching the separator, so the two definitions cannot drift apart again.
Over-skipping is the safe direction: it can only classify more runs as
images, and dropping a run never manufactures a tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 17:10:17 +02:00
Erik Melton 6e962b4466 Address review comments: Enforce stricter tag handling and image sanitation in notifications. 2026-08-26 16:36:12 +02:00
Erik Melton 3af7675a10 Require textFormat declaration for all Text elements. 2026-08-26 16:14:51 +02:00
ZacharyZhang-NYandClaude Fable 5 70895d4f1c Bump cn/release to 6 after publishing 4.0.0.alpha-cn.5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 22:36:59 -04:00
0ae1694830 Constrain the tzupdate sudoers rule to a single timezone argument (#8194)
The wildcard granted passwordless root for timedatectl set-timezone plus any trailing arguments, so -H/--host and -M/--machine reached the SSH and machine transports as root. Systemd 261 guards argv injection into ssh, but -H still drives root's SSH client at an attacker-chosen host, and the transport resolves its helper through PATH; only Defaults secure_path stands between that and a planted ssh running as root. Match the argument with an anchored POSIX ERE that admits exactly one timezone token (no whitespace, no leading-dash segment, no traversal component), so no second argument and no option can ever match. The sole caller, omarchy-menu-timezone, passes one list-timezones value and is unaffected.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-25 22:30:09 +02:00
77305ed3b9 Enable Dell XPS 13 sidecar speaker amplifiers (#7032)
The Dell XPS 13 DX13260 drives its two CS35L56 sidecar speaker amplifiers through a quirk that Linux only gains in 7.2, so until Arch ships that kernel the machine plays through one amplifier with no bass. The dell-xps13-sidecar-amps package selects the same driver path with a module override; this installs it on that exact machine and nowhere else.

The detector requires both the DX13260 product name and SKU 0E53, because the override forces a quirk value rather than merging into one, and a machine that gets it wrong loses whatever quirk the kernel would have chosen for itself.

Pacman registers a package even when its post_install scriptlet fails, so the leaf calls dell-xps13-sidecar-amps-apply itself instead of trusting the install to have applied: a failed cleanup or boot-image rebuild has to reach the caller rather than hide behind a package pacman considers installed. That is also why the migration marks reboot-required only after the apply succeeds — a migration that exits non-zero keeps no completion marker and retries the apply on the next run, even though pacman already has the package.

The leaf runs after intel/ptl-kernel.sh rather than beside the other Dell leaf at the top of install/hardware/all.sh, so its boot-image rebuild sees the Panther Lake kernel that step swaps in rather than the stock one it removes.

Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-25 22:29:27 +02:00
David Helmus 45749c5b68 test: cover Python shim bypass
Place a synthetic python3 shim first in PATH so CI catches any regression that resolves REAL_PYTHON through user-managed shims.
2026-08-25 21:09:45 +02:00
ZacharyZhang-NYandClaude Fable 5 aa87ed97c1 Dim prompts use the documented dim exec form
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 15:03:35 -04:00
acrogenesis bafc9a1000 Write browser theme colour through a passwordless helper
Managed policy dirs are enterprise trust roots, so they stay 0755 root:root. The menu path takes root for that one write through a sudoers glob of six hex digits, the same shape as omarchy-dns, and falls back to pkexec where the grant is not installed. Drop omarchy-browser-policy; a group member could plant any JSON, not just a colour.
2026-08-25 13:01:01 -06:00
ZacharyZhang-NYandClaude Fable 5 a3cd27dba3 Add Dim Agent CLI to the agent roster and Install > AI
npm dimcode (bin: dim) rides the mise path like Deep Code; entries
land alphabetically in the Default Agent and Install > AI menus.
CLI-only per dimagent.cn, login handled by the CLI itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 14:59:18 -04:00
acrogenesis 44a186afe4 Replace planted policy directory symlinks instead of following them
install -d follows a managed or distribution symlink and would chmod the target. Unlink those paths first, and treat a dangling symlink as a directory the migration still has to repair.
2026-08-25 12:14:52 -06:00
acrogenesis bebe19bc70 Harden browser policy parent directories and validate theme RGB
install -d follows a planted ancestor symlink, and a writable parent can rename the managed leaf aside. chromium.theme is user-installed, so only a 0-255 RGB triple becomes a colour.
2026-08-25 12:10:08 -06:00
acrogenesis 87dfa14c56 Keep a trusted Firefox policies.json when repairing the directory
A world-writable distribution dir failed the hardened check even when
policies.json was already root-owned, and setup then overwrote it.
2026-08-25 12:04:03 -06:00
acrogenesis b0e6611c70 Require root-owned Firefox policies before skipping repair
The hardened gate only looked at the distribution directory. A regular
policies.json planted under the old 777 mode would then be left in place
if the directory later looked 755/root.
2026-08-25 12:04:02 -06:00
acrogenesis 95b791af16 Stop world-writable browser policy directories
Chromium managed policy is mandatory for every profile. World-writable
dirs let any local uid plant policy, including force-installed
extensions. Write goes through the omarchy-browser-policy group at 2775
so theme colour still works without other-write.
2026-08-25 12:04:02 -06:00
ZacharyZhang-NYandClaude Fable 5 0812b28527 Detect the missing official repo dbs by core.db, not any db
pacstrap leaves the offline repo's db in the target's sync dir, so
the any-db glob skipped the first-run update path on real installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 13:50:11 -04:00
ZacharyZhang-NYandClaude Fable 5 9bf870449b Link kimi into ~/.local/bin after the official install
The installer only appends PATH to .bashrc, so the fresh binary was
invisible to the launching process and the login never started.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 13:46:23 -04:00
ZacharyZhang-NYandClaude Fable 5 9be4d24d6d App installs route first-time updates through omarchy-update
Direct pacman -Syu trips the update pacman guard once real upgrades
exist; the catalog installer now runs the upstream update pipeline
interactively and fails fast with instructions when non-interactive.
omarchy-cn-kimi-install gains its executable bit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 13:27:38 -04:00
ZacharyZhang-NYandClaude Fable 5 a317bfb36f Fix China app installs and wire the CN AI stack into Install > AI
Offline installs ship no pacman sync db, so yay resolved official
repo dependencies as AUR packages and every catalog install failed;
omarchy-cn-app-install now runs a full -Syu first (verified end to
end: WeChat builds and installs in the VM). Kimi Code gains a real
installer command reused by the harness registry, the default-agent
path, and new Install > AI entries (Kimi Code, Deep Code, AI Hub),
and the AI Hub wizard offers to launch native harnesses immediately
so their official CLI login runs, matching how Claude Code works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 13:17:52 -04:00
ZacharyZhang-NYandClaude Fable 5 0f888ffe31 Bump cn/release to 5 after publishing 4.0.0.alpha-cn.4
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 12:25:27 -04:00
ZacharyZhang-NY 41ddd9052a Update README.md 2026-08-25 16:14:20 +00:00
ZacharyZhang-NYandClaude Fable 5 68a994b223 Document the Chinese-first defaults in the OmarchyCN layer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 12:03:04 -04:00
ZacharyZhang-NYandClaude Fable 5 d7938dd10b Localize the fingerprint first-run notice
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:21:31 -04:00
ZacharyZhang-NYandClaude Fable 5 f338f6cb15 Localize update confirmation and first-run notifications
The update prompt speaks Chinese and points What's new at the
OmarchyCN releases page; the Wi-Fi, update, keybindings, dictation,
and default-agent first-run notices follow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:18:02 -04:00
ZacharyZhang-NYandClaude Fable 5 b85a998404 Keybindings menu test fixtures follow localization
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:16:37 -04:00
ZacharyZhang-NYandClaude Fable 5 e32f1eca8e Binding-conflicts test follows localized descriptions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:12:43 -04:00
Kevin McConnell 2c93e66b0c Run ONCE with sudo when installing
The install user is no longer in the docker group by default, so a bare
`once` cannot reach the Docker socket. Run with `sudo` instead.

The script already requires sudo to install the command and enable the
service, so we can safely use it for the initial command launch as well.
2026-08-25 16:12:10 +01:00
ZacharyZhang-NYandClaude Fable 5 6e3eb678b1 Bar panel test expectation follows localization
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:10:09 -04:00
ZacharyZhang-NYandClaude Fable 5 2fce4924e6 Remaining scratchpad test expectations follow localization
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:09:55 -04:00
ZacharyZhang-NYandClaude Fable 5 6e73f9f797 Scratchpad binding test follows the localized description
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:09:39 -04:00
ZacharyZhang-NYandClaude Fable 5 9e1bb767c0 Voxtype binding test follows localized descriptions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:09:23 -04:00
ZacharyZhang-NYandClaude Fable 5 2eff2a37b3 Localize dynamic bind templates and the Super+K matcher
Workspace, group-window, and bar-panel loops emit Chinese
descriptions; omarchy-menu-keybindings' merge list and priority
patterns follow them, so combined chords and curated ordering
survive localization. Affected tests updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:09:05 -04:00
Naeem MalikandGitHub 9301092404 Update Omarchy on Mac guide link to omarchy-mac repo (#8192) 2026-08-25 17:04:13 +02:00
ZacharyZhang-NYandClaude Fable 5 e673e63baa Localize keybinding descriptions for the Super+K cheatsheet
All 176 bind descriptions across the shipped Hyprland binding files
switch to Simplified Chinese; brand names stay English.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:03:38 -04:00
ZacharyZhang-NYandClaude Fable 5 907f7b81aa Fix kimi install error masking, prompt flag, and setup default hop
The official installer downloads to a temp file so curl failures
cannot be masked by the pipe; Kimi prompts use --prompt per the CLI
reference; the AI Hub wizard now offers omarchy-cn-ai-default for
claude-code/codex profiles so the combo really becomes the default
agent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 11:01:50 -04:00
ZacharyZhang-NYandClaude Fable 5 8ce169f7fa Keep the agent menu alphabetical with the CN additions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:56:16 -04:00
ZacharyZhang-NYandClaude Fable 5 0778554984 Add Kimi Code and Deep Code to the default agent roster
kimi installs through its official script rather than mise;
deepcode rides the existing mise path as npm:@vegamo/deepcode-cli.
Both gain launch branches and Default Agent menu entries, plus an
AI Hub entry for provider combos (Kimi/DeepSeek/Z.AI/MiniMax over
Claude Code) via the omarchycn setup wizard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:55:45 -04:00
ZacharyZhang-NYandClaude Fable 5 6b491d52be Add MiniMax as an Anthropic-compatible AI provider
MiniMax-M3[1m] via api.minimaxi.com/anthropic per the official
Claude Code token-plan docs; the claude-code combo enters the
compatibility matrix as stable with mock regression coverage,
including the Haiku-slot fallback to the single model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:49:53 -04:00
ZacharyZhang-NYandClaude Fable 5 e57d2a3424 Plugin menu test expects the localized labels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:38:21 -04:00
ZacharyZhang-NYandClaude Fable 5 b7dd8c7125 Menu search test follows the localized labels
The exact-label ranking case now queries in Chinese, and a new case
pins English reachability through the leaf id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:37:59 -04:00
ZacharyZhang-NYandClaude Fable 5 f182234796 Localize the Omarchy menu and add AI Hub and China apps sections
All category and action labels switch to Simplified Chinese with
brand names kept in English per the PRD terminology rule. The
OmarchyCN section gains an AI Hub submenu (wizard, profiles, test,
doctor) and a China apps submenu backed by the cn/apps.json catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:36:42 -04:00
ZacharyZhang-NYandClaude Fable 5 b7aa5d9d7d Drop mailto handler for removed HEY launcher from mimeapps
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:33:49 -04:00
ZacharyZhang-NYandClaude Fable 5 43f274ce63 Docs: stop claiming removed apps ship by default
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:32:51 -04:00
ZacharyZhang-NYandClaude Fable 5 763ebb83c3 Purge remaining references to removed western apps
The quattro upgrade script no longer registers the HEY mailto
handler, installer tips stop naming LibreOffice, and the hotkeys,
GUIs, and web-apps manuals drop the entries for removed apps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:30:28 -04:00
ZacharyZhang-NYandClaude Fable 5 6d8acfca9f Drop western default apps from the CN default set
Basecamp, Discord, HEY, Google Contacts/Maps/Messages/Photos,
WhatsApp, and X lose their shipped launchers, webapp keybindings,
and the whatsapp-slim extension; aether, libreoffice-fresh, and
obs-studio leave the base package set. The HEY mailto handler and
its registration go with them. Preinstall add/remove lists, the
launcher hide list, and chromium flags stay in sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:23:52 -04:00
ZacharyZhang-NYandClaude Fable 5 7150f63544 Ship Chinese IME and font fallback by default on zh_CN systems
fcitx5-rime, fcitx5-chinese-addons, and fcitx5-configtool join the
base package set, and a provisioning step seeds the Rime profile and
CJK fontconfig for users on systems whose locale is zh_CN, so Chinese
input works at first boot without omarchycn setup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:14:00 -04:00
4cd8a081cb Fix Codex usage collection on 0.149 (#7649)
* Fix Codex usage collector approval policy

* Capture codex argv with boundaries in the scanner test

The stub joined its arguments with "$*", so the assertion compared one
flattened string and could not tell five arguments from fewer containing
spaces. Passing "-s read-only" and "-a on-request" as single arguments --
which codex rejects as an unexpected argument -- passed the test. NUL
separation and an array comparison keep the boundaries the assertion is
about.

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

---------

Co-authored-by: Omabot <omabot@omarchy.org>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-25 16:07:17 +02:00
ZacharyZhang-NYandClaude Fable 5 e71a9de012 Install zh_CN.UTF-8 as system language for Chinese installs
The configurator wrote en_US.UTF-8 unconditionally, so a Chinese
install booted into an English system. sys_lang now follows the
installer language, and both zh_CN and en_US locales are always
generated in the target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:00:14 -04:00
ZacharyZhang-NYandClaude Fable 5 960e0a6211 Bump cn/release to 4 after publishing 4.0.0.alpha-cn.3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 08:57:39 -04:00
ZacharyZhang-NYandClaude Fable 5 e0b21ff8fb Fix shell-quoting leak in setup-form English strings
Three literals carried ''' escape sequences verbatim, so sourcing
the English branch executed stray words and aborted the first-boot
owner form under set -e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 08:37:33 -04:00
bastidotnet e53548fae2 Harden the test's temp-file handling against a symlink race
The /tmp-fallback case did check-then-create on a fixed /tmp name, a
TOCTOU/symlink race, and the EXIT trap only cleaned $TMPDIR. Create the decoy
atomically with noclobber (O_EXCL) so it refuses to overwrite an existing file
or follow a symlink at that path, and remove it on exit only when this test
created it. The fixed path is required (it is exactly the path the old code
would form), so a random mktemp name cannot replace it. Addresses the Copilot
review on #8198; the wrapper fix is unchanged.
2026-08-25 13:51:39 +02:00
bastidotnet fe56d68e90 Validate the cached Apple-display device path before use
The cached device path was trusted for merely existing, not for being a hiddev node, and fell back to a predictable /tmp path when XDG_RUNTIME_DIR was unset. Validate the cache shape (hiddev char device) and cache only under the user-private runtime dir; asdcontrol already gates non-Apple devices downstream, so this is defense-in-depth in the layer Omarchy owns.
2026-08-25 13:24:00 +02:00
23dab9ec4d [Security] Stop the FIDO2 setup staging its authfile at a predictable /tmp path (#7904)
* [Security] Stop the FIDO2 setup staging its authfile at a predictable /tmp path

pamu2fcfg wrote to /tmp/fido2 and the registration was then moved into place
with `sudo mv`. Any other local user can pre-create /tmp/fido2, and rename(2)
does not dereference the final component, so the privileged move installed the
attacker's symlink itself as pam_u2f's global authfile -- a file consulted by
`sufficient` lines in /etc/pam.d/sudo and /etc/pam.d/polkit-1.

The same move also carried the staged file's ownership into /etc, so on every
install to date /etc/fido2/fido2 is owned by the invoking user at mode 0644.
That needs no attacker: anything running as that uid can add its own credential
and satisfy the machine's sudo prompt without root.

Stage under mktemp and hand the bytes to `install` instead, so the authfile is
always a fresh root-owned regular file rather than an inode a non-root user
still controls. Guard the already-registered check with -L, which -f would
otherwise follow, and reject a symlinked /etc/fido2 in the remove path for the
same reason. A migration takes ownership of authfiles left behind by the old
code; it reports a symlink rather than repairing one, since chown would follow
it and removing it would strip sudo from anyone whose only credential is the
token.

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

* Install the FIDO2 authfile with -T and refuse a non-regular path

`install SRC DEST` without -T treats an existing directory at DEST as a
destination directory: it drops the credential inside as
/etc/fido2/fido2/tmp.XXXX, exits 0, and setup reports a successful registration
while PAM goes on reading a path that is not a file. -T makes that an error.

The already-registered check has the same blind spot from the other side. -f
follows symlinks, so it reads a symlinked authfile as a registration and leaves
it in place, and is false for a directory, so it tries to register over one.
Only a regular file is a pam_u2f authfile; anything else is now refused with the
same advice to remove it and set FIDO2 up again.

The test deleted every staged path that fell outside its scratch directory,
taking the path from the script under test and already resolved through any
symlink -- so a script staging through one would have named a file of the user's
and had it unlinked. It now unlinks only a file its own stub wrote into.

On a machine that already has /etc/fido2/fido2 the staging assertions cannot run
at all, and the file used to pass without exercising one of them. That branch now
asserts what the host state promises instead: a regular authfile still has to be
recognised as a registration and left alone, and anything else has to be refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Replace the FIDO2 authfile inode rather than chowning it in place

Permission is checked at open(2), not at write(2), so a descriptor the
registering user opened on the authfile while it was still theirs stays writable
through chown and chmod alike. pam_u2f resolves /etc/fido2/fido2 to that same
inode, so the repair left the account it authenticates able to append a
credential it controls -- the exact state the migration exists to end, now
recorded as migrated and never revisited.

Installing a fresh root-owned copy and renaming it over the path leaves any such
descriptor writing to a file nothing reads. Credit to #7703, which reached the
same conclusion independently. An interrupted run heals: the staged copy is
root-owned 600 and inert, no marker is written, and the next run replaces it.

A directory or device at the authfile path is no more ours to rewrite than a
symlink is, and chmod 600 on a directory would only make it untraversable, so
both are now reported rather than repaired.

The repair had no test, because it names an absolute path no unprivileged suite
can write. It is exercised through a scratch copy with that one literal
retargeted, rather than by reading the path from the environment: the migration
hands `install` and `mv` root, and an operand the caller can choose is a
privileged write to anywhere. The copy is only as honest as the substitution, so
the test fails if the migration stops naming the path exactly once.

Covered: the no-op on a machine that never registered a key, which must not cost
a password prompt; the repair itself; the new inode; the absence of a staged copy
afterwards; a second account finding it done; and the symlink and non-regular
cases. Each assertion was checked against a mutation that defeats it -- notably a
repair with the right install call, mode, content and cleanup that writes through
the old inode, which only the inode assertion catches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Finish hardening FIDO2 authfile installation

* Guard the FIDO2 directory and the stage path the setup writes through

install -d follows a symlink at /etc/fido2 and applies the mode and ownership to whatever it points at, so the credential would be staged and published inside the link target and that directory silently reopened to root:root 755. The leaf guard above it only covered fido2 itself, and this is the same threat omarchy-remove-security-fido2 already names on its side.

mktemp's output is an operand for a privileged tee, chmod, mv and rm. The migration validates it before any of them run; the setup did not, so take only the name it asked for there too.

The suite was guarded on the host's own /etc/fido2/fido2 and exited early when one existed, which meant the staging assertions asserted nothing on exactly the machines that use FIDO2. Drive a retargeted copy the way the migration suite already does, so every branch is a fixture and all of them run everywhere.

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

* Stop the FIDO2 migration recording a repair it never made

omarchy-migrate writes the per-user completion marker on any zero exit, so the two states this migration cannot repair got one line in the update terminal and were then silenced for good: no login notice, no re-run, the migration recorded as done having repaired nothing. Those are precisely the machines where the authfile may already be under someone else's control, so raise them through omarchy-notification-send as well, where they outlive the scrollback. Delivery is best-effort: a machine with no user bus or no notification server must not abort the migration and take every later one with it.

The early exit had the same shape of problem. It read the authfile unprivileged, and the old setup created /etc/fido2 with `sudo mkdir -p`, which took the union of the caller's umask and sudoers' 0022 — so registering under `umask 077` left the directory mode 0700 with the user-owned authfile still inside. Absence and "cannot look" are the same answer to those tests, and the migration exited 0 and marked itself complete. Ask root whether a registration is actually behind an untraversable directory before reopening it, so an aborted setup that left an empty directory, or one an administrator keeps private, does not have its mode widened and its group and special bits discarded for a repair it does not need. A machine that never set FIDO2 up has no directory here and still reaches exit 0 without a password prompt.

The notification assertion checks argument shape rather than a substring of the command line. The glyph is a private-use codepoint, and losing it shifts every argument left: -g swallows the headline, the body becomes the title, and the message goes out with no description — which a substring match reads as a pass.

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

* Cover the FIDO2 removal's symlink guard

The -d to -e || -L change is load-bearing for the threat its own comment names — a dangling link at /etc/fido2 that -d reads as absent, left for the next setup to install an authfile through — and it was the one part of this work with no test behind it. Name the directory once so the suite can retarget a copy, the same seam the setup and migration suites use, and assert both halves: the link goes, and the directory it pointed at does not.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-25 12:09:17 +02:00
9285b19d6a [Security] Stop USB device names from being executed as Hyprland Lua (#8129)
* Stop device names from being executed as Hyprland Lua

Hyprland input-device and monitor names come from USB descriptors and
hyprctl output, so they are attacker-influenceable, yet the toggle and
monitor commands interpolated them straight into hyprctl eval and into
generated Lua that Hyprland re-executes on every reload. The input-device
toggle keys are bound with locked = true, so a malicious USB name reached
Lua code execution from the lock screen; a persisted disable made it run
on every start. This closes that class everywhere it appeared.

- The touchpad/touchscreen disable is now the device name in a plain-text
  sidecar file, read back by a packaged Lua module on reload, never a
  generated Lua file. hyprctl eval Lua-quotes the name and control
  characters are rejected outright.
- Dropped the shipped *-disabled.lua templates so nothing seeds a
  disabled state to /etc/skel, making the name file the single source of
  truth read from a hardcoded ~/.local/state to match the sibling tools.
- The reload loader excludes those two legacy filenames, so a leftover
  generated *-disabled.lua on a not-yet-migrated install can never be
  sourced as code again; a migration then recovers the device name from
  it and deletes it, sanitizing installs that ran the vulnerable version.
- All four monitor scripts (internal, mirror, clamshell, scaling) now
  validate an output name against a plain-connector-name pattern before
  writing it as Lua, closing the same latent pattern in the siblings.
- paths.lua treats a set-but-empty XDG_STATE_HOME as unset, matching the
  bash side so state is never read from the filesystem root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0144ZDt44vtxjyF8j9Y88NrM

* Let a failing Lua assertion fail the test

lua discards the status of a chunk read from stdin, so a blown assert printed its traceback and still exited 0: the surrounding `set -euo pipefail` never fired and the following `pass` printed `ok`. Every Lua block in these two files was unenforced, including the assertion that a quoted `hyprctl eval` cannot reach `os.execute` and the negative control that proves the test can detect the injection at all. Passing the chunk as a script argument makes lua report the failure.

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

* Re-apply a recovered input-device disable to the running session

The package hook reloads Hyprland during `omarchy-update-system-pkgs`, before `omarchy-migrate` runs, and at that reload the generated Lua is already excluded while the name file does not exist yet — so a touchpad or touchscreen the user had switched off comes back on, and stays on until their next login. Reload once more once the name has been recovered, which is the same path a login already takes to read it.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Omarchybot <omabot@omarchy.org>
Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-25 11:03:12 +02:00
4637735aa2 Pin trusted PATH in privileged DNS helper (#8172)
* Pin PATH to trusted dirs when omarchy-dns holds root

A dev link prepends a user-writable checkout bin/ to sudo's secure_path,
so the passwordless `omarchy-dns Cloudflare` sudoers rule lets root
resolve a bare helper (dirname, install, tee, nmcli, ...) out of that
checkout — turning checkout-write access into arbitrary root execution.
Pin PATH to trusted system directories once EUID is 0, leaving the
unprivileged wrapper phase free to locate sudo/pkexec on the caller's
PATH.

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

* Assert the trusted-PATH pin is gated on root, not merely present

The EUID assertion matched `(( EUID == 0 ))` anywhere in the file, and require_root has carried that exact test since long before the pin existed. Deleting the pin left the assertion passing, so it stood for nothing: a run with the pin neutered reached the behavioural probe with both greps green. Anchor on the unindented guard and require the pin to be the line it opens, which no other construct in the script satisfies.

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

* Skip the DNS trusted-PATH probe where user namespaces are unavailable

`fail` ends the file, so a sandbox or hardened kernel that refuses unprivileged user namespaces did not just lose the probe — it took the two elevation assertions below it down as well, reporting a product defect where there was only a missing capability. The non-graphical suites are meant to run on any machine and treat a skip as a passing test, the way require_compositor and plugin-add-test.sh already do. Gate the probe on the namespace it needs and say so when it is absent; the static checks above and the elevation checks below run either way.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-25 09:37:55 +02:00
68ab12f77d Share the git URL check, and refuse the transports Omarchy does not clone from (#8174)
* Share the git URL check between theme-install and plugin-add

Both commands clone a URL a stranger can choose, and each carried its own copy of the rule that refuses a git option or a `<helper>::<address>` transport helper before cloning. Two copies of a security check drift: the second one arrived four months after the first, and only because someone went looking for it.

The rule now lives in omarchy-git-url-check and the callers ask it. Its absence refuses the URL rather than waving it through, since the callers read a non-zero status as a refusal and a missing command exits 127.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* Refuse a git URL naming a transport Omarchy does not clone from

`<helper>::<address>` is only one of the two ways a URL reaches a remote helper. git also resolves git-remote-<scheme> for `<scheme>://<address>` whenever the scheme is not one it connects itself, so `ext::sh -c id` and `ext://sh -c id` arrive at the same helper while only the first was refused.

That shape cannot be refused outright, because it is also how every legitimate URL arrives, so the scheme is checked against the transports git still connects itself. `git+ssh` and `ssh+git` are on that list: they are spelled like a helper and read as plain ssh, and leaving them off would refuse a URL that clones today. `ext` and `fd` are off it deliberately -- git ships a helper for each, and `ext` runs whatever command the URL carries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-25 09:10:20 +02:00
BastiandGitHub 30471bf35a Guard plugin-add against git transport-helper URLs (match theme-install) (#8067)
* Guard plugin-add against git transport-helper URLs

omarchy-plugin-add cloned a user-supplied git URL without the
transport-helper guard that omarchy-theme-install already applies
(added in #7884, which did not touch plugin-add). Port that guard
(reject ext::/fd:: and leading-dash forms, keep https/ssh/scp-style
incl. IPv6) and add a regression test. Stock systems are unaffected
(git default protocol.ext.allow=never); this removes the silent
dependency on that default and aligns the two install paths.

* Test the plugin-add guard's leading-dash arm via the gum input path

The prior leading-dash cases only exercised the argv option parser, not
the guard (removing the guard's -* arm left them green). Drive a dash
value through the interactive gum prompt under a pty so the post-input
guard is actually covered; skip cleanly where util-linux script is
unavailable.
2026-08-25 08:29:48 +02:00
ZacharyZhang-NYandClaude Fable 5 ba9a265e4a Fix GUI fallback misfire and untranslated failure-menu case patterns
The cage wrapper treated the installer session exit code as cage
availability, so an installer abort re-ran the English VT flow. A
gui-started marker now separates the two. Dashboard failure-menu
cases for shell/poweroff now match the localized choice strings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 02:13:27 -04:00
ZacharyZhang-NYandClaude Fable 5 1245b749af Logo word gap; translate welcome, cfdisk, and partitioning progress strings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:43:21 -04:00
ZacharyZhang-NYandClaude Fable 5 84514840f5 Chinese installer onboarding: bilingual setup form, vendored ISO patch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:33:35 -04:00
ZacharyZhang-NYandClaude Fable 5 48218b2d5f Brand boot splash and plymouth wordmark as OMARCHY CN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:26:05 -04:00
ZacharyZhang-NYandClaude Fable 5 6ce3bbf8d6 Merge upstream sync b86d4505 (README resolved toward CN rewrite)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:18:23 -04:00
ZacharyZhang-NYandClaude Fable 5 0bb9a96612 Begin cn.3 development cycle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:25:41 -04:00
ZacharyZhang-NYandClaude Fable 5 9546fe98b9 Update: refuse pre-channel downgrades; doc docker restore scope
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:24:39 -04:00
ZacharyZhang-NYandClaude Fable 5 2f5f02d3e5 Update: stable-channel empty tag list fails with message, not silently
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:21:11 -04:00
ZacharyZhang-NYandClaude Fable 5 eb149f30c4 M5 review fixes: key download step, honest update paths, ordered checklist, channel semantics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:19:26 -04:00
ZacharyZhang-NYandClaude Fable 5 9145679261 Document the OmarchyCN layer in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:17:32 -04:00
ZacharyZhang-NYandClaude Fable 5 786544edf3 Add release checklist, sync SLA, security response, channel command
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:17:06 -04:00
ZacharyZhang-NYandClaude Fable 5 708651fa8e Add Chinese user manual (install, mirrors, IME, AI, recovery)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:16:34 -04:00
ZacharyZhang-NYandClaude Fable 5 79d3a65441 Remove temp diagnostics workflow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:15:24 -04:00
ZacharyZhang-NYandClaude Fable 5 17e0beb7a6 Temp host diagnostics workflow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:09:10 -04:00
Akshar Patel 597f57a198 Allow Resolve Voiceover to release focus 2026-08-24 22:00:42 -04:00
ZacharyZhang-NYandClaude Fable 5 6330c2b0a7 Vendor omarchy-pkgs patch shipping the cn data layer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:30:59 -04:00
ZacharyZhang-NYandClaude Fable 5 9f78e1530f cn commands: use omarchy-cmd helpers per bin style rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:16:54 -04:00
ZacharyZhang-NYandClaude Fable 5 0aaa770561 Wizard: explicit step error propagation, honest final diagnosis
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:08:58 -04:00
ZacharyZhang-NYandClaude Fable 5 35e4d42682 Wizard: full PRD step set with explicit skip; apps via AUR helper
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:07:29 -04:00
ZacharyZhang-NYandClaude Fable 5 16b95790aa Add China app catalog and first-run wizard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:03:35 -04:00
ZacharyZhang-NYandClaude Fable 5 327442a1e7 AI tests: restricted PATH guarantees installer branch coverage
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:01:54 -04:00
ZacharyZhang-NYandClaude Fable 5 1cf6d455d7 AI tests: responses-only codex mock, exercised installer branch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:00:29 -04:00
ZacharyZhang-NYandClaude Fable 5 bf0d2a4e2a AI default/tests: env-passed key, secure creation, harness install, isolated strict mocks
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:58:35 -04:00
ZacharyZhang-NYandClaude Fable 5 bc2a3c09db Add AI mock regression suite; grant stable to regression-covered combos
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:55:34 -04:00
ZacharyZhang-NYandClaude Fable 5 cd80471307 Add AI default-agent mapping via harness-native config persistence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:54:42 -04:00
ZacharyZhang-NYandClaude Fable 5 e2a473481d Wizard: preserve prior profile until new config passes its test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:53:12 -04:00
ZacharyZhang-NYandClaude Fable 5 cf15c6083c Wizard: adapter-backed combos only, protocol-filtered models, test-gated activation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:52:09 -04:00
ZacharyZhang-NYandClaude Fable 5 d0c4c3a0b3 AI test: codex Responses API branch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:50:37 -04:00
ZacharyZhang-NYandClaude Fable 5 78909cbfd5 AI wizard/test/doctor: native-auth handling, Responses API test, safe temp, stale profile
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:50:11 -04:00
ZacharyZhang-NYandClaude Fable 5 a942500712 AI test: correct http code capture on connection failure
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:46:03 -04:00
ZacharyZhang-NYandClaude Fable 5 870ee40c7a Add AI test, doctor, and interactive setup wizard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:45:47 -04:00
ZacharyZhang-NYandClaude Fable 5 3760ac46e2 Codex adapter: remove stale temp files before secure recreation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:44:36 -04:00
ZacharyZhang-NYandClaude Fable 5 51ba414009 Codex adapter: full auth contract, robust TOML detection, 0600 temp files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:43:29 -04:00
ZacharyZhang-NYandClaude Fable 5 717ba305bf AI: harness-aware model validation, official codex contract, launch fail-fast
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:38:43 -04:00
ZacharyZhang-NYandClaude Fable 5 02d0d5c991 Add AI profiles and harness adapters (claude-code, codex, opencode, native)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:29:48 -04:00
ZacharyZhang-NYandClaude Fable 5 8c3ef3d884 Secret: decryption-free pass existence check for purge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:27:49 -04:00
ZacharyZhang-NYandClaude Fable 5 f9f25119d0 Secret: purge failure fails set, unreachable service fails rm
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:26:38 -04:00
ZacharyZhang-NYandClaude Fable 5 bfef3e5c1e Secret: purge stale lower-tier copies on set, warn on unreachable rm, fresh 0600
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:24:55 -04:00
ZacharyZhang-NYandClaude Fable 5 6cb9065dcf Secret: handle empty stdin read, explicit dir perms
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:21:13 -04:00
ZacharyZhang-NYandClaude Fable 5 28a9de1a46 Add AI secret backend with three-tier storage fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:20:54 -04:00
ZacharyZhang-NYandClaude Fable 5 676fcfc9ea Compatibility levels: official-doc until mock regression grants stable
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:20:02 -04:00
ZacharyZhang-NYandClaude Fable 5 86ff275910 Add AI registry: providers, harnesses, compatibility matrix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:09:49 -04:00
ZacharyZhang-NYandClaude Fable 5 c8279fc993 cn tests: assert fontconfig rule content and required menu entries
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:06:36 -04:00
ZacharyZhang-NYandClaude Fable 5 9baecc928d cn tests: strict mode, positive assertions before removals
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:05:48 -04:00
ZacharyZhang-NYandClaude Fable 5 bc84b808fe Add OmarchyCN shell test suite
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:03:09 -04:00
ZacharyZhang-NYandClaude Fable 5 5bab9b81ac Overlay: comments-only extension template counts as replaceable
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:01:53 -04:00
ZacharyZhang-NYandClaude Fable 5 a8683ae8a7 Menu: full scale presets; overlay ships extracted menu extension
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:00:27 -04:00
ZacharyZhang-NYandClaude Fable 5 ca0130c674 Add OmarchyCN menu with mirrors, Chinese env, display, diagnostics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:51:54 -04:00
ZacharyZhang-NYandClaude Fable 5 94b9797211 Display scale: session detection via Hyprland env, executable recovery hint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:49:57 -04:00
ZacharyZhang-NYandClaude Fable 5 3cdb3fa61c Display scale: verify both lines, fail on reload error, honest non-TTY path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:48:35 -04:00
ZacharyZhang-NYandClaude Fable 5 fff49e747c Add display scale presets with revert confirmation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:46:26 -04:00
ZacharyZhang-NYandClaude Fable 5 4a762052ca IME hotkey: also unbind Apps menu key before menu migration
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:45:36 -04:00
ZacharyZhang-NYandClaude Fable 5 14b14f24fa Add IME hotkey command with menu-key migration and conflict detection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:43:35 -04:00
ZacharyZhang-NYandClaude Fable 5 b7ced3b1b0 IME: dependency-free profile comparison
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:41:33 -04:00
ZacharyZhang-NYandClaude Fable 5 0a3cff60eb IME: content-diff backup condition, DefaultIM assertion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:40:15 -04:00
ZacharyZhang-NYandClaude Fable 5 7b00f37c59 Add Fcitx5 Rime setup with default profile and status checks
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:37:29 -04:00
ZacharyZhang-NYandClaude Fable 5 2b799d4388 Font status: assert untagged CJK fallback reaches SC
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:35:01 -04:00
ZacharyZhang-NYandClaude Fable 5 92b0289dbb Fontconfig: restore SC-first generic alias preferences
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:34:32 -04:00
ZacharyZhang-NYandClaude Fable 5 19f7fff4de Fontconfig: scope ja/ko overrides to generic families
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:34:02 -04:00
ZacharyZhang-NYandClaude Fable 5 ae1083039b Fontconfig: untagged-request SC fallback, ja/ko native variants
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:33:22 -04:00
ZacharyZhang-NYandClaude Fable 5 d057c6f894 Locale apply: escaped matching, explicit generation assertions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:26:31 -04:00
ZacharyZhang-NYandClaude Fable 5 843769bf77 Add CJK fonts, fontconfig priority, and locale commands
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:25:53 -04:00
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
Sameer PokharelandGitHub b86d4505c1 Renumber the README manual TOC after Extra themes was dropped (#8089)
Dropping manual/43-extra-themes.md shifted every chapter after it down
by one. The TOC rode along on the old numbers, so its last ten links
404.
2026-08-24 21:59:17 +02:00
06a3dbca42 Offer to reboot when toggling sudoless Docker; show only the relevant menu entry (#8098)
* Offer to reboot when toggling sudoless Docker; show only the relevant menu entry

Group membership only takes effect on a fresh session, and in practice a logout
or newgrp isn't enough — only a reboot reliably applies it. So the setup/remove
commands now flag the reboot and offer to do it now with a gum confirm (like the
GPU toggle), and the notices say "after a reboot" instead of pointing at logout
or newgrp. The existing-user migration passes OMARCHY_DEFER_REBOOT so it does not
prompt mid-update — omarchy-update-restart still handles the reboot once the whole
update finishes.

The Setup > Security menu also showed Sudoless Docker under both Setup and
Remove. Condition the Setup entry on the group being absent (Remove already
conditions on it being present), so only the applicable one appears.

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

* Ask omarchy-sudo-docker whether Docker needs sudo

Every place that chooses between talking to Docker directly and elevating was
testing group membership by hand, and the menu guards tested the wrong thing:
they read the running session's groups, which do not change until the reboot,
so after enabling sudoless Docker the menu still offered Setup — the one action
that could no longer do anything — while Remove stayed hidden.

Add omarchy-sudo-docker as the single answer, with the two questions that
actually differ in that window. By default it asks whether this session can
reach the socket, which is what decides if a command must elevate: lazydocker
and the Windows VM keep prompting until the reboot lands. With --configured it
asks whether the account is set up for sudoless Docker, which is what the menu
and the toggles need, so the menu switches to the action that can change state
as soon as the group is written.

Also correct a comment: nothing surfaces reboot-required in the bar; it is
omarchy-update-restart that reads it during a later update.

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>
2026-08-24 21:58:27 +02: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
Ryan Hughes e66c27f1e7 Require signed packages from the Omarchy repository 2026-08-24 12:53:24 -04:00
David Helmus 44b00a4e80 test: avoid mise shim recursion 2026-08-24 08:52:25 +02:00
Taksh 7c896d3521 Keep a web app name out of the launcher's directory structure
The app name becomes a filename, and omarchy-webapp-install ran
`mkdir -p "$(dirname "$DESKTOP_FILE")"` over it, so every slash turned into a
directory level. Typing a URL into the Name field -- the reported way in --
wrote the launcher to
`~/.local/share/applications/http:/127.0.0.1:4000/.desktop`.

Removal could then never reach it. The picker lists the file but displays a
name derived from the path, and the removal rebuilt a flat
`$DESKTOP_DIR/$APP_NAME.desktop` from that name, so `rm -f` deleted nothing and
the app stayed in the launcher with no error.

Refuse a name containing a slash rather than silently renaming what the user
typed, and delete the file the scan actually found instead of a path rebuilt
from its display name. The second half also clears up whatever earlier versions
nested, which a reconstructed path cannot address.
2026-08-24 07:16:53 +05:30
Akshar Patel 94a7b70ed4 Explain Resolve pointer focus override 2026-08-14 23:50:12 -04:00
Akshar Patel ec9da050ee Stop Resolve dialogs from recapturing pointer focus 2026-08-14 23:47:31 -04:00
298 changed files with 13549 additions and 2839 deletions
+24
View File
@@ -0,0 +1,24 @@
name: pkg-repo-sync
on:
schedule:
- cron: "17 */6 * * *"
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: quattro
- name: Mirror upstream [omarchy] stable into the Gitea Arch registry
env:
PKG_SYNC_TOKEN: ${{ secrets.PKG_SYNC_TOKEN }}
run: |
set -euo pipefail
if ! command -v zstd > /dev/null; then
apt-get update -qq && apt-get install -y -qq zstd
fi
bash packages/sync-omarchy-repo.sh
+50
View File
@@ -0,0 +1,50 @@
name: upstream-sync
on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: quattro
fetch-depth: 0
- name: Fetch upstream and open sync PR
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
API: ${{ github.server_url }}/api/v1
REPO: ${{ github.repository }}
run: |
set -euo pipefail
git config user.name "omarchycn-sync"
git config user.email "sync@noreply.git.zacharyzhang.com"
git remote add upstream https://github.com/basecamp/omarchy.git
git fetch upstream quattro
UP=$(git rev-parse upstream/quattro)
echo "upstream quattro: $UP"
if git merge-base --is-ancestor "$UP" HEAD; then
echo "Already up to date with upstream"
exit 0
fi
BR="sync/upstream-${UP:0:8}"
if git ls-remote --exit-code --heads origin "$BR" > /dev/null; then
echo "Sync branch $BR already exists, PR pending review"
exit 0
fi
# Trial merge only to report conflict status in the PR body
MERGE="clean"
if ! git merge --no-commit --no-ff "upstream/quattro" > /dev/null 2>&1; then
MERGE="CONFLICTS (resolve manually)"
fi
git merge --abort 2> /dev/null || true
# Branch points at upstream HEAD so the PR always gets created
git push origin "$UP:refs/heads/$BR"
curl -sS --fail-with-body -X POST \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
"$API/repos/$REPO/pulls" \
-d "{\"base\":\"quattro\",\"head\":\"$BR\",\"title\":\"Sync upstream omarchy ${UP:0:8}\",\"body\":\"Automated sync of basecamp/omarchy quattro @ $UP. Trial merge: $MERGE. Review, run ./test/all, then merge.\"}"
+4
View File
@@ -2,3 +2,7 @@
# Python bytecode (orchestrator)
__pycache__/
*.pyc
# Local working documents
OmarchyCN PRD.md
OmarchyCN-TASKS.md
+20 -1
View File
@@ -11,13 +11,32 @@ matching guide before starting:
- [`agents/skills/visual-verification.md`](agents/skills/visual-verification.md) - verifying any change with a visual effect in the running UI
- [`agents/skills/migrations.md`](agents/skills/migrations.md) - creating or changing migrations under `migrations/`
# OmarchyCN Layer
This fork carries the OmarchyCN China-integration layer on top of upstream `basecamp/omarchy`:
- `bin/omarchycn` routes to `omarchy cn <command>`; all cn commands are `bin/omarchy-cn-*` and follow the upstream bin conventions (metadata, helpers, `$OMARCHY_PATH` — bootstrap exceptions: the overlay installer resolves its own checkout, and `omarchy-cn-convert` / `omarchy-cn-revert` are curl-able standalone scripts that default `OMARCHY_PATH` because they run before/while the cn tree exists)
- Convert/revert: `omarchy-cn-convert` turns a vanilla package-based Omarchy into OmarchyCN (registry key + [omarchycn] repo, `--ask=4` package swap to the cn-built omarchy-dev/omarchy-settings-dev published by `packages/publish-cn-packages.sh`, [omarchy] mirror line, zh_CN locale, fcitx5 seeding, plymouth rebrand) recording pre-state in `/var/lib/omarchycn/convert-state`; `omarchy-cn-revert` restores that state; checkout installs are refused toward the overlay
- `cn/` holds the data layer: `mirrors.json`, `dev-mirrors.json`, `apps.json`, `registry/` (AI providers/harnesses/compatibility), `fcitx5/`, `fontconfig/`, `keys/`, `lib/` (sourced helpers), `release` (cn release number)
- cn migrations live in `cn/migrations/*.sh`, run by `omarchy-cn-migrate` with per-file completion markers under `~/.local/state/omarchycn/`; `bin/omarchy-migrate` calls it after the upstream migrations, and `omarchy-cn-update` serves both install forms (overlay: git pull + reinstall + migrate; package: exec `omarchy-update`)
- Packaging: `packages/omarchy-pkgs-cn.patch` must be applied to the sibling `omarchy-pkgs` checkout so `omarchy-dev` ships `cn/`; keyring in `packages/omarchycn-keyring/`
- Upstream repo mirror: `packages/sync-omarchy-repo.sh` + `.gitea/workflows/pkg-repo-sync.yml` mirror the upstream `[omarchy]` stable channel into the Gitea Arch registry every 6h; `pacman-stable.conf` lists the mirror first (upstream fallback), clients must trust the registry key (install import + cn migration), `omarchycn doctor mirror --fix` re-heals the line; see `docs/pacman-repo.md`
- ISO: `packages/omarchy-iso-cn.patch` must be applied to the sibling `omarchy-iso` checkout — Chinese installer (cage+foot graphical console with English VT fallback) and live-env packages
- Release process: `docs/release-checklist.md`; signing: `docs/release-signing.md`; pacman repo: `docs/pacman-repo.md`; ISO distribution: Cloudflare R2 bucket behind `dl.zacharyzhang.com` via `packages/upload-release-r2.sh` (Gitea releases carry only the small artifacts)
- Site: `site/` is the omarchycn.zacharyzhang.com landing page (Vite 8 vanilla + GSAP + self-hosted Fusion Pixel + reicon, kami palette on a 12-col grid); deploy with `npx wrangler deploy` from `site/`, rendered-state checks via `node shots.mjs` against a preview or the live URL
- Chinese-first defaults: OmarchyCN ships Simplified Chinese as the default UX. User-visible strings in `default/omarchy/omarchy-menu.jsonc` (all labels), `default/hypr/bindings/*.lua` (bind descriptions, including the generated workspace/group/panel loops), `bin/omarchy-menu-keybindings` (its merge list and priority patterns must match the shipped Chinese descriptions), `bin/omarchy-update-confirm`, and `install/user/first-run/*` notifications are Chinese with brand names kept in English; menu search stays reachable in English through leaf ids. When syncing upstream, translate new strings in these files and resolve string conflicts toward our Chinese text.
- Default set changes vs upstream: `install/omarchy-base.packages` adds fcitx5-rime/chinese-addons/configtool and drops aether/libreoffice-fresh/obs-studio; the Basecamp/Discord/HEY/Google/WhatsApp/X launchers, their webapp keybindings, the whatsapp-slim extension, and the HEY mailto handler are removed (preinstall add/remove lists, launcher.hides, chromium flags, and mimeapps stay in sync); `install/user/cn-chinese.sh` seeds IME/font config on zh_CN systems
- AI: `cn/registry/` drives providers (DeepSeek/Kimi/Z.AI/MiniMax/Ollama-local) with zero-code adapters; Ollama uses a fixed `static_token` and runtime-listed models (`models_dynamic`); `omarchy-default-agent` also accepts kimi (official installer script, not mise), deepcode, dim, and dsh (mise npm); the Default Agent menu carries them plus an AI Hub combo entry
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal and inventoried (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the `omarchy-cn-migrate` line in `bin/omarchy-migrate`, the localized menu/bindings/update/first-run surfaces above, the western-app removals above, the cn agent roster in `bin/omarchy-default-agent` / `bin/omarchy-agent` (kimi/deepcode/dim/dsh), the `[omarchy]` mirror line in `default/pacman/pacman-stable.conf`, the registry-key import in `install/post-install/pacman.sh`, a rewritten `README.md` (known recurring sync conflict, resolve toward ours), `AGENTS.md` additions, and two `.gitignore` lines)
- cn tests: `test/shell.d/omarchycn-test.sh`, `test/shell.d/omarchycn-ai-test.sh`; localized UI expectations live in the upstream suites (menu, keybindings-menu, hyprland-default-config, binding-conflicts, clock, screenrecording, update-disk-space)
# Documentation Layout
Three documentation trees, split by genre and audience:
- `agents/skills/` - task procedure ("do this when doing X"), for anyone working on the codebase
- `docs/` - reference on how the system is shaped (file layout, update pipeline, theming, shell architecture), for anyone working on the codebase; skills link here for depth
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals. Chinese user manual for the cn layer lives in `manual/zh-cn/`
# Style
+11
View File
@@ -0,0 +1,11 @@
# 行为准则
参与 OmarchyCN 社区(issue、PR、讨论)时:
- 尊重他人,就事论事,不进行人身攻击、骚扰或歧视
- 欢迎新手提问,回答保持耐心
- 技术分歧用证据和代码说话
- 不发布垃圾信息、广告或与项目无关的内容
违反者由维护者视情节警告、删除内容或封禁账号。
举报渠道见 [SECURITY.md](SECURITY.md) 中的联系方式。
+23
View File
@@ -0,0 +1,23 @@
# 贡献指南
## 仓库结构
- 上游 Omarchy 代码尽量不动;OmarchyCN 改动集中在 `bin/omarchy-cn-*``bin/omarchycn``cn/``install/cn/`
- 上游代码定期与 `basecamp/omarchy` 同步:同步 PR 由维护者或 CI 工作流发起,人工审查合并
## 提交规范
- 遵循 [AGENTS.md](AGENTS.md) 的全部代码与命令约定(Bash 5、`[[ ]]`/`(( ))`、两空格缩进、`#!/bin/bash`
- 提交保持原子:一个提交只做一件事
- 涉及系统修改的命令必须支持 `--dry-run`,写配置前先备份
## 测试
- CLI 与 shell 改动跑 `./test/all`
- OmarchyCN 新增测试放在 `test/shell.d/*-test.sh`
## 提交流程
1. Fork 或在 issue 中讨论
2. 提交 PR 到 `quattro` 分支,说明动机与验证方式
3. 通过 CI 与代码审查后合并
+14
View File
@@ -0,0 +1,14 @@
OmarchyCN
Copyright (c) 2026 Zachary Zhang / OmarchyCN Community
本发行版基于 Omarchyhttps://github.com/basecamp/omarchy),
Copyright (c) 2025 David Heinemeier HanssonMIT License。
上游文件保留其原始版权与许可证声明(见 LICENSE)。
OmarchyCN 原创代码(bin/omarchy-cn-*、cn/、install/cn/ 及相关文档)
采用 MIT License 发布。
OmarchyCN is an independent community distribution based on Omarchy.
OmarchyCN is not affiliated with or endorsed by Basecamp, 37signals,
or the Omarchy maintainers.
-1747
View File
File diff suppressed because it is too large Load Diff
-37
View File
@@ -1,37 +0,0 @@
# OmarchyCN 执行任务清单
进度真相文件。按顺序执行,禁止跳项。每项完成后勾选并记录结果。
范围裁剪:本轮交付 = 用户目标三项(公开仓库 + 基线 ISO + Gitea Release),即 PRD M0 中的
Canonical Repo 与「未修改功能的基线 ISO」两项。M0 其余项(上游同步 MR 自动化、Release Key
与签名体系、法务/治理文档)以及 M1–M5 功能章节均不在本轮范围。本轮校验产物为 SHA-256
checksum,不含 PGP 签名(PRD §15.1 签名体系属后续 M0 收尾项)。
- [x] T1 复核 PRD 全文(§1–28),确认本轮交付物 = 公开仓库 + 基线 ISO + ReleaseM0 部分交付)
- [x] T2 探测 git.zacharyzhang.com Gitea API:版本、token 身份、release 附件大小上限
- [x] T3 通过 API 创建公开仓库 omarchycn
- [x] T4 提交 PRD 与本清单到 quattro 分支,推送全部历史到 Gitea
- [x] T5 准备 WSL 构建环境:克隆 omacom-io/omarchy-iso 与 omacom-io/omarchy-pkgs,按其 bin 脚本要求装依赖
- [x] T6 构建基线 ISO./bin/omarchy-iso-make --no-boot-offer --local-source ../omarchy ../omarchy-pkgs
- [x] T7 校验 ISO:生成 SHA-256WSL 有 /dev/kvm,尽可能做 QEMU UEFI 启动冒烟测试
- [ ] T8 创建 Gitea Releasetag 4.0.0.alpha-cn.1):ISO 传 generic package registryrelease 附 sha256 与下载链接
- [ ] T9 输出最终核对表:仓库 URL、Release URL、逐条验收结果
## 执行记录
(每项完成后在此追加一行:任务号、结果、产物路径/URL)
- T1 完成:PRD §1–28 全文已复核。本轮交付=M0 部分交付(Canonical Repo + 基线 ISO + SHA-256 校验产物)。
- T2 完成:Gitea 1.27.1token=ZacharyZhang-NY(admin)。附件限制 100MB 且不允许 .isorelease asset API 无 external_url。
已验证 generic package registry 可上传(201)。策略:ISO 传 generic registryrelease 附 sha256(.txt) 与下载链接。
如需 ISO 直接作为 release 附件,需管理员改服务器 app.ini 的 [attachment] ALLOWED_TYPES/MAX_SIZE。
- T3 完成:https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn public,默认分支 quattro)。
- T4 完成:quattro 全历史已推送,HEAD=2c8a6874(含 PRD 与本清单)。
- T5 完成:WSL Ubuntu 24.04 装 docker.io 29.1.3systemd 托管,active);~/omarchycn-build 下
三个 checkoutomarchyquattro 5fa312e0,克隆自本地)、omarchy-isoquattro)、omarchy-pkgsmaster)。
构建命令按 omarchy-iso-make 实际要求:--keep-pkg-cache 跳过交互 sudo--no-boot-offer 跳过 gum。
- T6 完成:release/omarchy-2026.08.24-x86_64-local.iso5.8G,含离线镜像仓库),mkarchiso DoneEXIT=0。
- T7 完成:SHA-256=1d5421d1427cf97c0950c766778380c0543f180c9062e0a7c06f99b457dd8300.sha256 已落盘)。
QEMU 8.2 + OVMF + KVM 冒烟测试通过:UEFI 启动进入安装器欢迎屏(Press Return to Start Install),
QMP screendump 三张截图确认。曾出现两次环境故障并已修根因:WSL 随最后一个客户端退出导致 nohup QEMU
被杀(改为前台任务保活);/dev/kvm 权限(用户加入 kvm 组)。
+44 -65
View File
@@ -1,80 +1,59 @@
# Omarchy
# OmarchyCN
Omarchy is a beautiful, modern & opinionated Linux distribution by DHH.
面向中国开发者的 [Omarchy](https://omarchy.org) 下游发行版:完整保留 Omarchy 的 Arch + Hyprland 桌面体验,为中国网络环境、中文使用习惯与国内 AI 服务做系统级增强。
Read more at [omarchy.org](https://omarchy.org).
当前版本 `4.0.0.alpha-cn.7`(基于上游 `quattro` 分支构建的基线 ISO)。
## The Omarchy Manual
## 特性
The manual lives in [`manual/`](manual/), which is its authoritative source. It's
mirrored to [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual), where
its screenshots are also hosted.
继承自 Omarchy
- [Welcome to Omarchy!](manual/01-welcome-to-omarchy.md)
- Hyprland 动态平铺桌面 + Quickshell 顶栏、菜单与系统面板,键盘驱动工作流
- `omarchy` CLI 与 `Super + Space` 系统菜单,CLI 与 GUI 同构
- 内置主题系统与一键换肤,终端 / 编辑器 / 桌面配色统一
- AI coding agent 桌面集成(Claude Code、Codex、OpenCode 等,`mise` 按需安装)
- Btrfs + Snapper 更新前快照与系统回滚
- archiso 离线安装镜像,支持无人值守安装与双系统引导
**The Basics**
- **中国镜像管理**Arch / npm / pip / Cargo / Go 等软件源测速、自动选择与故障切换
- **中文环境开箱即用**zh_CN locale、思源黑体 / 宋体字体栈、高分屏分数缩放预设
- **中文输入法**Fcitx5 + Rime 预配置,Wayland / GTK / Qt / Electron 全栈兼容,快捷键冲突自动处理
- **AI Hub**Kimi、DeepSeek、Z.AI/GLM、MiniMax 与本地 Ollama 的一等 Provider 支持,与 Claude Code、Codex、OpenCode、Kimi Code、Deep Code 等 Harness 的统一配置向导、凭据安全存储与连接诊断;另有 Dim、DSH 等自管登录的 Agent CLI 一键安装
- **国内应用中心**:微信、QQ、飞书、钉钉、腾讯会议、WPS 等应用的可信安装入口
- **一键转换**:原版 Omarchy(包安装)一条命令转成 OmarchyCN,logo/菜单/键位/输入法/软件源全套切换,`omarchy cn revert` 一键还原
- **Overlay 安装器**:在现有 Omarchy checkout 上叠加 OmarchyCN,全程可逆、可卸载
- **统一诊断**`omarchycn doctor` 覆盖网络、镜像、输入法、显示与 AI 配置
- [Getting Started](manual/02-getting-started.md)
- [Coming From Mac or Windows](manual/03-coming-from-mac-or-windows.md)
- [Navigation](manual/04-navigation.md)
- [The top bar](manual/05-the-top-bar.md)
- [Themes](manual/06-themes.md)
- [Hotkeys](manual/07-hotkeys.md)
- [Unified Clipboard & History](manual/08-unified-clipboard-history.md)
- [Reminders](manual/09-reminders.md)
- [Notices](manual/10-notices.md)
- [Text Extraction & Dictation](manual/11-text-extraction-dictation.md)
- [Screenshots & Recording](manual/12-screenshots-recording.md)
- [Toggles, idle & screensaver](manual/13-toggles-idle-screensaver.md)
- [Omarchy CLI](manual/14-omarchy-cli.md)
## 下载与安装
**The Applications**
从 [Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 页面下载 ISO 与 `SHA256SUMS.txt`,校验后写入 U 盘,UEFI 启动安装:
- [Terminal](manual/15-terminal.md)
- [Neovim](manual/16-neovim.md)
- [AI](manual/17-ai.md)
- [Development Tools](manual/18-development-tools.md)
- [Shell Tools](manual/19-shell-tools.md)
- [Shell Functions](manual/20-shell-functions.md)
- [TUIs](manual/21-tuis.md)
- [GUIs](manual/22-guis.md)
- [Browsers](manual/23-browsers.md)
- [Commercial apps/services](manual/24-commercial-apps-services.md)
- [Web Apps](manual/25-web-apps.md)
- [Gaming](manual/26-gaming.md)
- [Filling out PDFs](manual/27-filling-out-pdfs.md)
- [Windows VM](manual/28-windows-vm.md)
- [Other Packages](manual/29-other-packages.md)
```bash
sha256sum -c SHA256SUMS.txt
```
**Configuration**
## 已有原版 Omarchy?一键转换
- [Updates](manual/30-updates.md)
- [Dotfiles](manual/31-dotfiles.md)
- [Shell plugins](manual/32-shell-plugins.md)
- [Monitors](manual/33-monitors.md)
- [Keyboard, Mouse, Trackpad](manual/34-keyboard-mouse-trackpad.md)
- [Networking](manual/35-networking.md)
- [System sleep](manual/36-system-sleep.md)
- [Hardware authentication](manual/37-hardware-authentication.md)
- [Fonts](manual/38-fonts.md)
- [Backgrounds](manual/39-backgrounds.md)
- [Prompt](manual/40-prompt.md)
- [Branding](manual/41-branding.md)
- [Common tweaks](manual/42-common-tweaks.md)
- [Extra themes](manual/43-extra-themes.md)
- [Making your own theme](manual/44-making-your-own-theme.md)
包安装的原版 Omarchy(quattro)可直接转换,无需重装系统:
**The Rest**
```bash
curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash
```
- [Mac support](manual/45-mac-support.md)
- [Troubleshooting](manual/46-troubleshooting.md)
- [FAQ](manual/47-faq.md)
- [System snapshots](manual/48-system-snapshots.md)
- [Security](manual/49-security.md)
- [Omarchy on...](manual/50-omarchy-on.md)
- [Dual Boot Install](manual/51-dual-boot-install.md)
- [Unattended Installs](manual/52-unattended-installs.md)
转换内容:换装 OmarchyCN 构建的系统包(中文菜单/键位/更新界面、OMARCHY CN 品牌、AI 全家桶)、启用 [omarchy] 自建镜像与 [omarchycn] 仓库、zh_CN 语言与 Fcitx5+Rime 输入法、国内 Arch 镜像自动测速。重新登录后生效。
## License
随时一键还原(按转换时记录的状态逐项回退):
Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT).
```bash
omarchy cn revert
```
## 上游文档
Omarchy 完整英文手册在 [`manual/`](manual/) 目录,亦见 [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual)。
## 声明与许可证
OmarchyCN is an independent community distribution based on Omarchy. OmarchyCN is not affiliated with or endorsed by Basecamp, 37signals, or the Omarchy maintainers.
代码沿用上游 [MIT License](LICENSE),保留 Omarchy 原始版权声明。
+19
View File
@@ -0,0 +1,19 @@
# 安全政策
## 报告漏洞
请勿在公开 issue 中披露安全漏洞。发送邮件至:
**zhangyanghaha0407@outlook.com**(主题注明 [OmarchyCN Security]
请附:影响版本、复现步骤、影响面评估。我们在 72 小时内确认,修复后在
Release Notes 中致谢(除非你要求匿名)。
## 范围
- OmarchyCN 软件包、ISO、安装与更新脚本、Registry 与签名链路
- 上游 Omarchy / Arch / Hyprland 的漏洞请报给对应上游项目
## 签名验证
Release 产物的 PGP 公钥与验证方法见 [docs/release-signing.md](docs/release-signing.md)。
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Basecamp
Exec=omarchy-launch-webapp https://launchpad.37signals.com
Terminal=false
Type=Application
Icon=basecamp
StartupNotify=true
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Discord
Exec=omarchy-launch-webapp https://discord.com/channels/@me
Terminal=false
Type=Application
Icon=omarchy-discord
StartupNotify=true
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Google Contacts
Exec=omarchy-launch-webapp https://contacts.google.com/
Terminal=false
Type=Application
Icon=google-contacts
StartupNotify=true
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Google Maps
Exec=omarchy-launch-webapp https://maps.google.com
Terminal=false
Type=Application
Icon=google-maps
StartupNotify=true
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Google Messages
Exec=omarchy-launch-webapp https://messages.google.com/web/conversations
Terminal=false
Type=Application
Icon=google-messages
StartupNotify=true
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=Google Photos
Exec=omarchy-launch-webapp https://photos.google.com/
Terminal=false
Type=Application
Icon=google-photos
StartupNotify=true
-9
View File
@@ -1,9 +0,0 @@
[Desktop Entry]
Version=1.0
Name=HEY
Exec=omarchy-webapp-handler-hey %u
Terminal=false
Type=Application
Icon=hey
StartupNotify=true
MimeType=x-scheme-handler/mailto
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=WhatsApp
Exec=omarchy-launch-webapp https://web.whatsapp.com/
Terminal=false
Type=Application
Icon=whatsapp
StartupNotify=true
-8
View File
@@ -1,8 +0,0 @@
[Desktop Entry]
Version=1.0
Name=X
Exec=omarchy-launch-webapp https://x.com/
Terminal=false
Type=Application
Icon=x
StartupNotify=true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

+2
View File
@@ -39,7 +39,9 @@ GROUP_DESCRIPTIONS[capture]="Screenshots and screen recording"
GROUP_DESCRIPTIONS[channel]="Omarchy release channel management"
GROUP_DESCRIPTIONS[clipboard]="Clipboard helpers"
GROUP_DESCRIPTIONS[cmd]="Command and shortcut helpers"
GROUP_DESCRIPTIONS[cn]="OmarchyCN China environment integration"
GROUP_DESCRIPTIONS[config]="System configuration helpers"
GROUP_DESCRIPTIONS[crash]="Crash notification controls"
GROUP_DESCRIPTIONS[debug]="Diagnostics and support logs"
GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
GROUP_DESCRIPTIONS[default]="Default application selection"
+27 -1
View File
@@ -92,13 +92,39 @@ omp)
;;
ori)
# Ori is a harness launcher, and `ori code` is the agent it runs itself.
# A prompt alone means one headless turn there, printed after the turn ends,
# so --interactive is what seeds the session with it and keeps the window.
command=(ori code)
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
[[ -n ${prompt:-} ]] && command+=(--interactive --prompt "$prompt")
;;
pi)
command=(pi)
[[ -n ${prompt:-} ]] && command+=("$prompt")
;;
kimi)
command=(kimi)
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
;;
deepcode)
command=(deepcode)
[[ -n ${prompt:-} ]] && command+=("$prompt")
;;
dim)
# `dim exec` is the documented one-shot prompt form
if [[ -n ${prompt:-} ]]; then
command=(dim exec "$prompt")
else
command=(dim)
fi
;;
dsh)
# dsh has no TUI: `dsh web` is the interactive form, headless the one-shot
if [[ -n ${prompt:-} ]]; then
command=(dsh --profile headless "$prompt")
else
command=(dsh web)
fi
;;
*)
echo "Unsupported default agent: $agent" >&2
exit 1
+1 -1
View File
@@ -528,7 +528,7 @@ def fetch_codex_rpc():
try:
proc = subprocess.Popen(
[codex, "-s", "read-only", "-a", "untrusted", "app-server"],
[codex, "-s", "read-only", "-a", "on-request", "app-server"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
+17 -4
View File
@@ -4,7 +4,13 @@
# omarchy:args=[--no-osd] [+N%|N%-|N%]
# omarchy:examples=omarchy brightness display apple | omarchy brightness display apple +5% | omarchy brightness display apple --no-osd 50%
device_cache="${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display-apple.device"
# Only cache under the user-private runtime dir. With no XDG_RUNTIME_DIR we skip
# caching (detect every run) rather than fall back to a predictable, world-writable
# /tmp path another user could pre-create.
device_cache=""
if [[ -n ${XDG_RUNTIME_DIR:-} ]]; then
device_cache="$XDG_RUNTIME_DIR/omarchy-brightness-display-apple.device"
fi
no_osd=0
if [[ ${1:-} == "--no-osd" ]]; then
no_osd=1
@@ -28,9 +34,14 @@ find_apple_display_device() {
local cached=""
local device=""
if [[ -r $device_cache ]]; then
if [[ -n $device_cache && -r $device_cache ]]; then
read -r cached <"$device_cache" || true
if [[ -n $cached && -e $cached ]]; then
# Trust a cached value only if it still names a hiddev character device. A
# stale or unexpected cache (a regular file, a non-hiddev node) is ignored and
# we re-detect instead of handing an arbitrary path to asdcontrol. The globs
# are left unquoted on purpose: [[ ]] pattern-matches an unquoted right side,
# and quoting them would turn the match into a literal string comparison.
if [[ ( $cached == /dev/hiddev* || $cached == /dev/usb/hiddev* ) && -c $cached ]]; then
printf '%s\n' "$cached"
return 0
fi
@@ -39,7 +50,9 @@ find_apple_display_device() {
device="$(detect_apple_display_device)" || return 1
[[ -n $device ]] || return 1
printf '%s\n' "$device" >"$device_cache"
if [[ -n $device_cache ]]; then
printf '%s\n' "$device" >"$device_cache"
fi
printf '%s\n' "$device"
}
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# omarchy:summary=Map an AI profile onto Omarchy's default agent and hotkey
# omarchy:args=[profile]
# omarchy:examples=omarchycn ai default | omarchycn ai default work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
profile="${1:-$(cn_ai_current_profile)}"
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
echo "No such profile: $profile" >&2
exit 1
fi
harness=$(cn_ai_profile_field "$profile" harness)
provider=$(cn_ai_profile_field "$profile" provider)
model=$(cn_ai_profile_field "$profile" model)
# Only harnesses whose provider config persists in their own files can back
# the upstream hotkey; opencode is session-env only, natives self-auth
case "$harness" in
claude-code) agent="claude" ;;
codex) agent="codex" ;;
*)
echo "$harness 无法映射为上游默认 Agent;用 omarchycn ai launch $profile 启动" >&2
exit 1
;;
esac
command=$(cn_ai_harness_field "$harness" command)
if omarchy-cmd-missing "$command"; then
install=$(cn_ai_harness_field "$harness" install)
echo "Installing $harness: $install"
$install
fi
key=$(cn_ai_resolve_key "$provider")
case "$harness" in
claude-code)
# Persist provider env in Claude Code's own settings so the upstream
# Super+Shift+Ctrl+A -> omarchy-agent path launches fully configured
settings="$HOME/.claude/settings.json"
mkdir -p "${settings%/*}"
base=$(cn_ai_endpoint "$provider" anthropic)
# Dynamic-model providers have no fast alias: every tier maps to the model
fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model")
current="{}"
if [[ -s $settings ]]; then
current=$(cat "$settings")
cp "$settings" "$settings.omarchycn-bak-$(date +%Y%m%d-%H%M%S)"
fi
# Key reaches jq via environment, never the argument list
rm -f "$settings.omarchycn-new"
(
umask 077
CN_AI_KEY="$key" jq --arg base "$base" --arg model "$model" --arg fast "$fast" \
'.env = (.env // {}) + {
ANTHROPIC_BASE_URL: $base,
ANTHROPIC_AUTH_TOKEN: env.CN_AI_KEY,
ANTHROPIC_API_KEY: env.CN_AI_KEY,
ANTHROPIC_MODEL: $model,
ANTHROPIC_DEFAULT_SONNET_MODEL: $model,
ANTHROPIC_DEFAULT_OPUS_MODEL: $model,
ANTHROPIC_DEFAULT_HAIKU_MODEL: $fast
}' <<<"$current" > "$settings.omarchycn-new"
)
mv "$settings.omarchycn-new" "$settings"
;;
codex)
cn_ai_render_codex_config "$provider" "$model" "$key"
;;
esac
agent_file="$HOME/.config/omarchy/defaults/agent"
mkdir -p "${agent_file%/*}"
printf '%s\n' "$agent" > "$agent_file"
echo "默认 Agent: $agent$profile: $provider x $model"
echo "快捷键 Super+Shift+Ctrl+A 或 'omarchy agent' 将以该配置启动"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# omarchy:summary=Check AI harness installation, credentials, and endpoint health
# omarchy:examples=omarchycn ai doctor
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
failures=0
profile=$(cn_ai_current_profile 2>/dev/null || true)
if [[ -z $profile ]]; then
echo "INFO ai: 未设置默认 Profile (omarchycn ai profile use <name>)"
exit 0
fi
echo "INFO ai: current profile $profile"
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
echo "FAIL ai: 默认 Profile $profile 的文件不存在 (omarchycn ai profile list)"
exit 1
fi
harness=$(cn_ai_profile_field "$profile" harness)
provider=$(cn_ai_profile_field "$profile" provider)
command=$(cn_ai_harness_field "$harness" command)
proto=$(cn_ai_harness_field "$harness" protocol)
if omarchy-cmd-present "$command"; then
echo "PASS ai: harness $harness installed ($command)"
else
install=$(cn_ai_harness_field "$harness" install)
if [[ $install == doc:* ]]; then
echo "WARN ai: harness $harness 未安装,需手动安装: ${install#doc:}"
else
echo "WARN ai: harness $harness 未安装,首次 launch 时自动安装"
fi
fi
if [[ $proto == "native" ]]; then
echo "INFO ai: $harness 自管鉴权,跳过 OmarchyCN Key 检查"
elif cn_ai_static_token "$provider" > /dev/null 2>&1; then
echo "PASS ai: $provider 使用内置 token,无需存储 Key"
elif omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
echo "PASS ai: $provider API Key stored"
else
echo "FAIL ai: $provider API Key 未存储 (omarchycn ai secret set $provider)"
failures=$((failures + 1))
fi
if [[ $proto != "native" ]]; then
base=$(cn_ai_endpoint "$provider" "$proto")
host=$(sed -E 's|https?://([^/:]+).*|\1|' <<<"$base")
if getent hosts "$host" > /dev/null 2>&1; then
echo "PASS ai: endpoint DNS resolves ($host)"
else
echo "FAIL ai: endpoint DNS 无法解析 ($host)"
failures=$((failures + 1))
fi
fi
exit $((failures > 0 ? 1 : 0))
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# omarchy:summary=Launch the AI harness configured by a profile
# omarchy:args=[profile]
# omarchy:examples=omarchycn ai launch | omarchycn ai launch work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
profile="${1:-$(cn_ai_current_profile)}"
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
echo "No such profile: $profile" >&2
exit 1
fi
harness=$(cn_ai_profile_field "$profile" harness)
provider=$(cn_ai_profile_field "$profile" provider)
model=$(cn_ai_profile_field "$profile" model)
command=$(cn_ai_harness_field "$harness" command)
config_method=$(cn_ai_harness_field "$harness" config_method)
if omarchy-cmd-missing "$command"; then
install=$(cn_ai_harness_field "$harness" install)
if [[ $install == doc:* ]]; then
echo "$harness 未安装,安装方法见官方文档: ${install#doc:}" >&2
exit 1
fi
echo "Installing $harness: $install"
$install
fi
case "$config_method" in
env)
key=$(cn_ai_resolve_key "$provider")
if [[ $harness == "codex" ]]; then
cn_ai_render_codex_config "$provider" "$model" "$key"
exec "$command"
fi
env_exports=$(cn_ai_render_env "$harness" "$provider" "$model" "$key")
eval "$env_exports"
exec "$command"
;;
native)
# Native harnesses (kimi-code, deep-code) manage their own auth
exec "$command"
;;
*)
echo "Unknown config method: $config_method" >&2
exit 1
;;
esac
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
# omarchy:summary=Create an AI profile binding a harness, provider, and model
# omarchy:args=<name> <harness> <provider> <model|default-coding|fast>
# omarchy:examples=omarchycn ai profile create work claude-code deepseek default-coding
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
name="${1:?usage: omarchycn ai profile create <name> <harness> <provider> <model>}"
harness="${2:?missing harness}"
provider="${3:?missing provider}"
model="${4:?missing model}"
if [[ ! $name =~ ^[a-z0-9-]+$ ]]; then
echo "Profile name must be lowercase alphanumeric/dashes: $name" >&2
exit 1
fi
cn_ai_harness_field "$harness" command > /dev/null || {
echo "Unknown harness: $harness (known: $(cn_ai_harness_ids | tr '\n' ' '))" >&2
exit 1
}
jq -e --arg p "$provider" '.providers[$p]' "$CN_AI_PROVIDERS" > /dev/null || {
echo "Unknown provider: $provider (known: $(cn_ai_provider_ids | tr '\n' ' '))" >&2
exit 1
}
level=$(cn_ai_combo_level "$harness" "$provider")
if [[ $level == "unsupported" ]]; then
echo "Combo $harness x $provider is not in the compatibility matrix" >&2
exit 1
fi
allowlist=$(cn_ai_combo_models "$harness" "$provider")
if cn_ai_models_dynamic "$provider"; then
: # runtime-listed models; omarchycn ai test validates against the live server
elif [[ -n $allowlist ]]; then
# Combo-restricted model set (e.g. codex uses its own catalog slugs)
if ! grep -qxF "$model" <<<"$allowlist"; then
echo "Model $model not supported for $harness x $provider" >&2
echo "Supported: $(tr '\n' ' ' <<<"$allowlist")" >&2
exit 1
fi
else
resolved=$(cn_ai_model_by_alias "$provider" "$model" 2>/dev/null || true)
if [[ -n $resolved ]]; then
model="$resolved"
elif ! cn_ai_models "$provider" | grep -qxF "$model"; then
echo "Unknown model for $provider: $model" >&2
echo "Available: $(cn_ai_models "$provider" | tr '\n' ' ')" >&2
exit 1
fi
model_proto=$(cn_ai_model_protocol "$provider" "$model")
harness_proto=$(cn_ai_harness_field "$harness" protocol)
if [[ -n $model_proto && $model_proto != "$harness_proto" ]]; then
echo "Model $model speaks $model_proto, but $harness needs $harness_proto" >&2
exit 1
fi
fi
mkdir -p "$CN_AI_PROFILE_DIR"
cat > "$CN_AI_PROFILE_DIR/$name.toml" <<EOF
schema_version = 1
name = "$name"
harness = "$harness"
provider = "$provider"
model = "$model"
secret_ref = "omarchycn://ai/$provider"
EOF
echo "Profile $name: $harness x $provider x $model (compat: $level)"
if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then
echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider"
fi
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# omarchy:summary=List AI profiles and the current default
# omarchy:examples=omarchycn ai profile list
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
current=""
if [[ -f $CN_AI_CURRENT ]]; then
current=$(<"$CN_AI_CURRENT")
fi
found="false"
for f in "$CN_AI_PROFILE_DIR"/*.toml; do
[[ -f $f ]] || continue
found="true"
name="${f##*/}"
name="${name%.toml}"
marker=" "
[[ $name == "$current" ]] && marker="*"
printf '%s %-14s %s x %s x %s\n' "$marker" "$name" \
"$(cn_ai_profile_field "$name" harness)" \
"$(cn_ai_profile_field "$name" provider)" \
"$(cn_ai_profile_field "$name" model)"
done
if [[ $found == "false" ]]; then
echo "No profiles (create: omarchycn ai profile create <name> <harness> <provider> <model>)"
fi
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# omarchy:summary=Set the default AI profile
# omarchy:args=<name>
# omarchy:examples=omarchycn ai profile use work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
name="${1:?usage: omarchycn ai profile use <name>}"
if [[ ! -f $CN_AI_PROFILE_DIR/$name.toml ]]; then
echo "No such profile: $name (see: omarchycn ai profile list)" >&2
exit 1
fi
mkdir -p "${CN_AI_CURRENT%/*}"
echo "$name" > "$CN_AI_CURRENT"
echo "Default AI profile: $name"
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
# omarchy:summary=Store, read, or delete AI provider API keys
# omarchy:args=<set|get|rm> <provider>
# omarchy:examples=omarchycn ai secret set deepseek | omarchycn ai secret get deepseek
set -euo pipefail
action="${1:?usage: omarchycn ai secret <set|get|rm> <provider>}"
provider="${2:?usage: omarchycn ai secret <set|get|rm> <provider>}"
if [[ ! $provider =~ ^[a-z0-9-]+$ ]]; then
echo "Invalid provider name: $provider" >&2
exit 1
fi
FILE_DIR="$HOME/.local/state/omarchycn/secrets"
FILE_PATH="$FILE_DIR/$provider"
secret_service_ok() {
omarchy-cmd-present secret-tool && secret-tool search service omarchycn > /dev/null 2>&1
}
pass_ok() {
omarchy-cmd-present pass && pass ls > /dev/null 2>&1
}
case "$action" in
set)
if [[ -t 0 ]]; then
read -rs -p "API key for $provider: " key
echo
else
IFS= read -r key || true
fi
if [[ -z $key ]]; then
echo "Empty key refused" >&2
exit 1
fi
if secret_service_ok; then
printf '%s' "$key" | secret-tool store --label "OmarchyCN AI: $provider" service omarchycn key "ai/$provider"
# Purge stale copies in lower-priority backends so get never falls
# through to an outdated key; a failed purge fails the set.
# Existence check is decryption-free (store file on disk).
if pass_ok && [[ -f ${PASSWORD_STORE_DIR:-$HOME/.password-store}/omarchycn/ai/$provider.gpg ]]; then
if ! pass rm -f "omarchycn/ai/$provider" > /dev/null; then
echo "存储成功但 pass 中的旧副本清除失败,请手动执行: pass rm omarchycn/ai/$provider" >&2
exit 1
fi
fi
rm -f "$FILE_PATH"
echo "Stored in Secret Service (secret-tool)"
elif pass_ok; then
printf '%s\n' "$key" | pass insert -m -f "omarchycn/ai/$provider" > /dev/null
rm -f "$FILE_PATH"
echo "Stored in pass (omarchycn/ai/$provider)"
else
mkdir -p "$FILE_DIR"
chmod 700 "$FILE_DIR"
rm -f "$FILE_PATH"
(umask 177 && printf '%s' "$key" > "$FILE_PATH")
echo "Stored in $FILE_PATH (0600 file fallback — 安装 libsecret 或 pass 可获得更安全的存储)"
fi
;;
get)
if secret_service_ok && secret-tool lookup service omarchycn key "ai/$provider" 2>/dev/null; then
exit 0
fi
if pass_ok && pass show "omarchycn/ai/$provider" 2>/dev/null; then
exit 0
fi
if [[ -f $FILE_PATH ]]; then
cat "$FILE_PATH"
exit 0
fi
echo "No secret stored for $provider (run: omarchycn ai secret set $provider)" >&2
exit 1
;;
rm)
removed=0
if secret_service_ok && secret-tool clear service omarchycn key "ai/$provider" 2>/dev/null; then
removed=1
fi
if pass_ok && pass rm -f "omarchycn/ai/$provider" > /dev/null 2>&1; then
removed=1
fi
if [[ -f $FILE_PATH ]]; then
rm -f "$FILE_PATH"
removed=1
fi
if omarchy-cmd-present secret-tool && ! secret_service_ok; then
echo "Secret Service 不可达,无法确认其中的副本已清除;服务恢复后重新执行 rm" >&2
exit 1
fi
if (( removed == 0 )); then
echo "No secret stored for $provider" >&2
exit 1
fi
echo "Secret for $provider removed"
;;
*)
echo "Unknown action: $action (expected set, get, or rm)" >&2
exit 1
;;
esac
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
# omarchy:summary=Interactive wizard: pick harness, provider, model, store key, test
# omarchy:examples=omarchycn ai setup
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
if [[ ! -t 0 ]]; then
echo "omarchycn ai setup 需要交互终端;非交互场景用 omarchycn ai profile create" >&2
exit 1
fi
harness=$(cn_ai_harness_ids | gum choose --header "选择 Harness")
# Only combos whose adapter actually renders at launch
mapfile -t providers < <(jq -r --arg h "$harness" \
'.combos[$h] | to_entries[] | select(.value.adapter) | .key' "$CN_AI_COMPAT")
if (( ${#providers[@]} == 0 )); then
echo "兼容矩阵中没有 $harness 的可用 Provider 组合" >&2
exit 1
fi
provider=$(printf '%s\n' "${providers[@]}" | gum choose --header "选择 Provider(兼容级别见 docs")
harness_proto=$(cn_ai_harness_field "$harness" protocol)
allowlist=$(cn_ai_combo_models "$harness" "$provider")
if cn_ai_models_dynamic "$provider"; then
base=$(cn_ai_endpoint "$provider" "$harness_proto")
if [[ $provider == "ollama" ]]; then
if omarchy-cmd-missing ollama; then
gum confirm "Ollama 未安装,现在安装本地服务?" || exit 1
if omarchy-cmd-present nvidia-smi; then
ollama_pkg=ollama-cuda
elif omarchy-cmd-present rocminfo; then
ollama_pkg=ollama-rocm
else
ollama_pkg=ollama
fi
omarchy-pkg-add "$ollama_pkg"
fi
if ! curl -sf --connect-timeout 3 "$base/api/tags" > /dev/null; then
sudo systemctl enable --now ollama
fi
fi
models=$(curl -sf --connect-timeout 3 "$base/api/tags" | jq -r '.models[].name' || true)
if [[ -z $models ]]; then
echo "无法列出本地模型(服务未运行或尚未拉取):先 ollama pull <模型>(如 qwen3-coder)再重试" >&2
exit 1
fi
model=$(gum choose --header "选择本地模型" <<<"$models")
elif [[ -n $allowlist ]]; then
model=$(gum choose --header "选择模型" <<<"$allowlist")
else
# Hide models bound to a different wire protocol than the harness speaks
model=$(jq -r --arg p "$provider" --arg hp "$harness_proto" \
'.providers[$p].models[] | select((.protocol // $hp) == $hp) | .id' \
"$CN_AI_PROVIDERS" | gum choose --header "选择模型")
fi
default_name="$harness-$provider"
name=$(gum input --header "Profile 名称" --value "$default_name")
proto=$(cn_ai_harness_field "$harness" protocol)
if [[ $proto == "native" ]]; then
omarchy-cn-ai-profile-create "$name" "$harness" "$provider" "$model"
omarchy-cn-ai-profile-use "$name"
echo "$harness 自管鉴权:首次启动时在 CLI 内完成官方登录"
if gum confirm "立即启动并完成官方登录?"; then
exec omarchy-cn-ai-launch "$name"
fi
echo "完成。启动: omarchycn ai launch"
exit 0
fi
if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then
key=$(gum input --password --header "$provider API Key(获取: $(jq -r --arg p "$provider" '.providers[$p].key_url' "$CN_AI_PROVIDERS")")
printf '%s' "$key" | omarchy-cn-ai-secret set "$provider"
fi
# Keep any existing profile intact until the new config proves itself
profile_file="$CN_AI_PROFILE_DIR/$name.toml"
if [[ -f $profile_file ]]; then
cp "$profile_file" "$profile_file.omarchycn-prev"
fi
omarchy-cn-ai-profile-create "$name" "$harness" "$provider" "$model"
if gum confirm "运行连接测试?"; then
if ! omarchy-cn-ai-test "$name"; then
if [[ -f $profile_file.omarchycn-prev ]]; then
mv "$profile_file.omarchycn-prev" "$profile_file"
echo "测试未通过,已恢复原 Profile $name" >&2
else
rm -f "$profile_file"
echo "测试未通过,Profile $name 未保留" >&2
fi
exit 1
fi
fi
rm -f "$profile_file.omarchycn-prev"
omarchy-cn-ai-profile-use "$name"
if [[ $harness == "claude-code" || $harness == "codex" ]]; then
if gum confirm "设为系统默认 AgentSuper+Shift+Ctrl+A)?"; then
omarchy-cn-ai-default "$name"
fi
fi
echo "完成。启动: omarchycn ai launch"
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
# omarchy:summary=Test connectivity, auth, and model availability for an AI profile
# omarchy:args=[profile]
# omarchy:examples=omarchycn ai test | omarchycn ai test work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
profile="${1:-$(cn_ai_current_profile)}"
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
echo "No such profile: $profile" >&2
exit 1
fi
harness=$(cn_ai_profile_field "$profile" harness)
provider=$(cn_ai_profile_field "$profile" provider)
model=$(cn_ai_profile_field "$profile" model)
proto=$(cn_ai_harness_field "$harness" protocol)
# Codex speaks the Responses API (wire_api=responses), not chat/completions
[[ $harness == "codex" ]] && proto="openai-responses"
failures=0
if [[ $proto == "native" ]]; then
echo "INFO ai-test: $harness 自管鉴权,本测试仅覆盖 env/config 型 Harness"
exit 0
fi
# Endpoint override for mock-server tests
base="${OMARCHYCN_AI_TEST_BASE_URL:-$(cn_ai_endpoint "$provider" "${proto%-responses}")}"
echo "INFO ai-test: $profile ($harness x $provider x $model) -> $base"
if ! key=$(cn_ai_resolve_key "$provider" 2>/dev/null); then
echo "FAIL ai-test: 未存储 $provider 的 API Key (omarchycn ai secret set $provider)"
exit 1
fi
body=$(mktemp)
trap 'rm -f "$body"' EXIT
case "$proto" in
anthropic)
code=$(curl -sS -o "$body" -w '%{http_code}' -m 30 --connect-timeout 8 \
-X POST "${base%/}/v1/messages" \
-H "x-api-key: $key" -H "authorization: Bearer $key" \
-H "anthropic-version: 2023-06-01" -H "content-type: application/json" \
-d "{\"model\":\"$model\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" || true)
;;
openai)
code=$(curl -sS -o "$body" -w '%{http_code}' -m 30 --connect-timeout 8 \
-X POST "${base%/}/chat/completions" \
-H "Authorization: Bearer $key" -H "content-type: application/json" \
-d "{\"model\":\"$model\",\"max_tokens\":8,\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}" || true)
;;
openai-responses)
code=$(curl -sS -o "$body" -w '%{http_code}' -m 30 --connect-timeout 8 \
-X POST "${base%/}/responses" \
-H "Authorization: Bearer $key" -H "content-type: application/json" \
-d "{\"model\":\"$model\",\"input\":\"ping\",\"max_output_tokens\":16}" || true)
;;
*)
echo "FAIL ai-test: unknown protocol $proto"
exit 1
;;
esac
[[ -z $code ]] && code=000
case "$code" in
200)
echo "PASS ai-test: 连接、鉴权与模型调用成功 (HTTP 200)"
;;
401 | 403)
echo "FAIL ai-test: 鉴权失败 (HTTP $code),检查 API Key"
failures=$((failures + 1))
;;
404 | 400 | 422)
echo "FAIL ai-test: 端点可达但请求被拒 (HTTP $code),可能是模型名问题"
head -c 200 "$body" 2>/dev/null && echo
failures=$((failures + 1))
;;
000)
echo "FAIL ai-test: 无法连接 $base (DNS/TLS/网络)"
failures=$((failures + 1))
;;
*)
echo "FAIL ai-test: HTTP $code"
head -c 200 "$body" 2>/dev/null && echo
failures=$((failures + 1))
;;
esac
rm -f $body
exit $((failures > 0 ? 1 : 0))
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
# omarchy:summary=Install a China app from the catalog with source confirmation
# omarchy:args=<app-id> [--yes]
# omarchy:examples=omarchycn app install wechat | omarchycn app install tencent-docs
set -euo pipefail
APPS_JSON="$OMARCHY_PATH/cn/apps.json"
app="${1:?usage: omarchycn app install <app-id> (see: omarchycn app list)}"
yes="${2:-}"
entry=$(jq -e --arg a "$app" '.apps[$a]' "$APPS_JSON") || {
echo "Unknown app: $app (see: omarchycn app list)" >&2
exit 1
}
name=$(jq -r '.name' <<<"$entry")
source_type=$(jq -r '.source' <<<"$entry")
license=$(jq -r '.license' <<<"$entry")
if [[ $license == "proprietary" && $yes != "--yes" ]]; then
pkg=$(jq -r '.package' <<<"$entry")
echo "$name 为专有软件,来源: AUR/$pkg(构建脚本公开,二进制来自厂商)"
if [[ -t 0 ]]; then
gum confirm "确认安装?" || exit 1
else
echo "非交互环境需显式加 --yes 确认专有软件安装" >&2
exit 1
fi
fi
case "$source_type" in
aur)
# Offline installs leave only offline.db in sync; without the official
# repo dbs yay treats repo deps as AUR. Upgrades must go through the
# omarchy-update pipeline (pacman guard).
if [[ ! -f /var/lib/pacman/sync/core.db ]]; then
if [[ ! -t 0 ]]; then
echo "首次安装应用前请先完成一次系统更新: omarchy-update" >&2
exit 1
fi
echo "首次安装应用前需要完成一次系统更新。"
omarchy-update
fi
omarchy-pkg-aur-add "$(jq -r '.package' <<<"$entry")"
;;
webapp)
omarchy-webapp-install "$name" "$(jq -r '.url' <<<"$entry")" "$(jq -r '.icon' <<<"$entry")"
;;
*)
echo "Unknown source type: $source_type" >&2
exit 1
;;
esac
echo "$name 安装完成"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# omarchy:summary=List China app catalog entries and their sources
# omarchy:examples=omarchycn app list
set -euo pipefail
APPS_JSON="$OMARCHY_PATH/cn/apps.json"
printf '%-14s %-10s %-8s %-12s %s\n' "ID" "NAME" "SOURCE" "LICENSE" "PACKAGE/URL"
jq -r '.apps | to_entries[] |
[.key, .value.name, .value.source, .value.license, (.value.package // .value.url)] | @tsv' \
"$APPS_JSON" |
while IFS=$'\t' read -r id name source license ref; do
printf '%-14s %-10s %-8s %-12s %s\n' "$id" "$name" "$source" "$license" "$ref"
done
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# omarchy:summary=Show or set the OmarchyCN release channel
# omarchy:args=[stable|beta|nightly]
# omarchy:examples=omarchycn channel | omarchycn channel beta
set -euo pipefail
CHANNEL_FILE="$HOME/.config/omarchycn/channel"
if (( $# == 0 )); then
if [[ -f $CHANNEL_FILE ]]; then
cat "$CHANNEL_FILE"
else
echo "beta (default)"
fi
exit 0
fi
case "$1" in
stable | beta | nightly)
mkdir -p "${CHANNEL_FILE%/*}"
echo "$1" > "$CHANNEL_FILE"
echo "Channel: $1"
;;
*)
echo "Unknown channel: $1 (stable|beta|nightly)" >&2
exit 1
;;
esac
+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
# omarchy:summary=Convert a vanilla package-based Omarchy install into OmarchyCN
# omarchy:examples=omarchy cn convert
# Curl-able bootstrap (like the overlay installer): everything before the
# package swap must run without the cn tree, so no $OMARCHY_PATH until then.
set -euo pipefail
GITEA=${GITEA:-https://git.zacharyzhang.com}
OWNER=${OWNER:-ZacharyZhang-NY}
REGISTRY="$GITEA/api/packages/$OWNER/arch"
REG_KEY_FPR=74DCF57ACD812B24D959F146BD386048867B33B4
STATE_DIR=/var/lib/omarchycn
STATE=$STATE_DIR/convert-state
# Bootstrap exception: curl|bash runs outside a session, so default the path
export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}
main() {
command -v pacman > /dev/null || { echo "需要 Arch Linux(未找到 pacman" >&2; exit 1; }
if (( EUID == 0 )); then
echo "请以普通用户运行(脚本内部按需 sudo" >&2
exit 1
fi
local prev_omarchy prev_settings
prev_omarchy=$(pacman -Q omarchy 2> /dev/null || pacman -Q omarchy-dev 2> /dev/null || true)
prev_settings=$(pacman -Q omarchy-settings 2> /dev/null || pacman -Q omarchy-settings-dev 2> /dev/null || true)
if [[ -z $prev_omarchy || -z $prev_settings ]]; then
if [[ -d $HOME/.local/share/omarchy/.git ]]; then
echo "检测到 checkout 安装(非包安装):请改用 overlaybin/omarchycn-install-overlay" >&2
else
echo "未检测到 Omarchy 包安装(需要 omarchy 与 omarchy-settings" >&2
fi
exit 1
fi
[[ -f $STATE ]] && echo "已转换过($STATE 在案):本次仅同步更新,不覆盖回退状态"
echo "==> 信任 OmarchyCN registry 公钥并配置 [omarchycn] 仓库"
local tmpkey
tmpkey=$(mktemp)
curl -fsSL "$REGISTRY/repository.key" -o "$tmpkey"
sudo pacman-key --add "$tmpkey"
sudo pacman-key --lsign-key "$REG_KEY_FPR"
rm -f "$tmpkey"
if ! grep -q '^\[omarchycn\]' /etc/pacman.conf; then
printf '\n[omarchycn]\nSigLevel = Required DatabaseOptional\nServer = %s/omarchycn/x86_64\n' \
"$REGISTRY" | sudo tee -a /etc/pacman.conf > /dev/null
fi
if [[ ! -f $STATE ]]; then
echo "==> 记录转换前状态(供 omarchy cn revert 还原)"
sudo mkdir -p "$STATE_DIR"
if [[ -f /etc/locale.conf ]]; then
sudo cp /etc/locale.conf "$STATE_DIR/locale.conf.pre"
fi
sudo cp /etc/pacman.d/mirrorlist "$STATE_DIR/mirrorlist.pre"
local p new_pkgs=""
for p in fcitx5-rime fcitx5-chinese-addons fcitx5-configtool omarchycn-keyring; do
pacman -Q "$p" > /dev/null 2>&1 || new_pkgs+="$p "
done
# Values are quoted because revert sources this file; publish atomically
{
echo "prev_omarchy=\"${prev_omarchy%% *}\""
echo "prev_settings=\"${prev_settings%% *}\""
echo "new_pkgs=\"${new_pkgs% }\""
echo "converted_at=\"$(date +%s)\""
} | sudo tee "$STATE.new" > /dev/null
sudo mv "$STATE.new" "$STATE"
fi
echo "==> 系统更新并换装 OmarchyCN 软件包(替换 ${prev_omarchy%% *}/${prev_settings%% *}"
sudo pacman -Syu --noconfirm
sudo pacman -S --noconfirm omarchycn/omarchycn-keyring
# --ask=4 auto-confirms removing the conflicting upstream omarchy packages
sudo pacman -S --noconfirm --ask=4 omarchycn/omarchy-dev omarchycn/omarchy-settings-dev
sudo pacman -S --needed --noconfirm fcitx5-rime fcitx5-chinese-addons fcitx5-configtool
echo "==> [omarchy] 仓库启用自建镜像(Cloudflare 上游兜底)"
source /usr/share/omarchy/cn/lib/mirror.sh
cn_mirror_omarchy_repo_fix
echo "==> 系统语言 zh_CN.UTF-8"
grep -q '^zh_CN.UTF-8 UTF-8' /etc/locale.gen ||
echo 'zh_CN.UTF-8 UTF-8' | sudo tee -a /etc/locale.gen > /dev/null
sudo locale-gen > /dev/null
if grep -q '^LANG=' /etc/locale.conf 2> /dev/null; then
sudo sed -i 's/^LANG=.*/LANG=zh_CN.UTF-8/' /etc/locale.conf
else
echo 'LANG=zh_CN.UTF-8' | sudo tee -a /etc/locale.conf > /dev/null
fi
echo "==> 中文输入法与字体(用户级)"
OMARCHY_PATH=/usr/share/omarchy bash /usr/share/omarchy/install/user/cn-chinese.sh
systemctl --user restart omarchy-fcitx5.service 2> /dev/null || true
echo "==> Arch 官方仓库切换国内镜像(自动测速,失败则保留现状)"
omarchy-cn-mirror-apply china || echo "国内镜像不可达,保留当前 mirrorlist"
echo "==> OMARCHY CN 开机画面"
omarchy-refresh-plymouth
omarchy-notification-send "OmarchyCN" "转换完成:重新登录后菜单/键位/输入法全中文" 2> /dev/null || true
echo
echo "转换完成(重新登录生效)。回退: omarchy cn revert"
}
main "$@"
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# omarchy:summary=Apply china or official registry profile to development tools
# omarchy:args=<china|official> [--target npm,pip,cargo,go,gem,docker]
# omarchy:examples=omarchycn dev-mirror apply china | omarchycn dev-mirror apply official --target npm,pip
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
profile="${1:?usage: omarchycn dev-mirror apply <china|official> [--target a,b]}"
shift
if [[ $profile != "china" && $profile != "official" ]]; then
echo "Unknown profile: $profile (expected china or official)" >&2
exit 1
fi
targets=("${CN_DM_TARGETS[@]}")
if [[ ${1:-} == "--target" ]]; then
IFS=',' read -ra targets <<<"${2:?--target needs a comma-separated list}"
fi
stamp=$(date +%Y%m%d-%H%M%S)
for target in "${targets[@]}"; do
if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then
echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2
exit 1
fi
url=$(cn_dm_url "$target" "$profile")
cn_dm_backup "$(cn_dm_config_file "$target")" "$stamp"
"cn_dm_set_$target" "$url"
echo "$target -> ${url:-<default>}"
done
echo "Backups (if any): $CN_DM_BACKUP_ROOT/$stamp"
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# omarchy:summary=Check reachability of each configured development registry
# omarchy:examples=omarchycn dev-mirror doctor
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
failures=0
for target in "${CN_DM_TARGETS[@]}"; do
current=$("cn_dm_get_$target")
if [[ -z $current ]]; then
echo "INFO $target: using tool default"
continue
fi
# Reduce to a probe-able https URL
probe="${current#sparse+}"
probe="${probe%%,*}"
if curl -sSf -o /dev/null -m 8 --connect-timeout 5 "$probe" 2>/dev/null; then
echo "PASS $target: $current"
elif curl -sS -o /dev/null -m 8 --connect-timeout 5 -w '%{http_code}' "$probe" 2>/dev/null | grep -qE '^[34]'; then
# Registry roots often answer 3xx/404 to bare GET while the service works
echo "PASS $target: $current"
else
echo "FAIL $target: $current unreachable"
failures=$((failures + 1))
fi
done
exit $((failures > 0 ? 1 : 0))
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# omarchy:summary=Show configured registry for each development ecosystem
# omarchy:examples=omarchycn dev-mirror list
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
printf '%-8s %-52s %s\n' "TARGET" "CURRENT" "CONFIG"
for target in "${CN_DM_TARGETS[@]}"; do
current=$("cn_dm_get_$target")
printf '%-8s %-52s %s\n' "$target" "${current:-<default>}" "$(cn_dm_config_file "$target")"
done
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# omarchy:summary=Point one development ecosystem at a custom registry URL
# omarchy:args=<target> <url>
# omarchy:examples=omarchycn dev-mirror set npm https://registry.example.com
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
target="${1:?usage: omarchycn dev-mirror set <target> <url>}"
url="${2:?usage: omarchycn dev-mirror set <target> <url>}"
if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then
echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2
exit 1
fi
cn_dm_backup "$(cn_dm_config_file "$target")" "$(date +%Y%m%d-%H%M%S)"
"cn_dm_set_$target" "$url"
echo "$target -> $url"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# omarchy:summary=Apply a display scale preset with 15s revert confirmation
# omarchy:args=<1.0|1.25|1.5|1.6|1.75|2.0>
# omarchy:examples=omarchycn display scale 1.6
set -euo pipefail
preset="${1:?usage: omarchycn display scale <1.0|1.25|1.5|1.6|1.75|2.0>}"
case "$preset" in
1.0 | 1.25 | 1.5 | 1.6 | 1.75 | 2.0) ;;
*)
echo "Unknown preset: $preset (supported: 1.0 1.25 1.5 1.6 1.75 2.0)" >&2
exit 1
;;
esac
monitors="$HOME/.config/hypr/monitors.lua"
if [[ ! -f $monitors ]]; then
omarchy-refresh-config hypr/monitors.lua
fi
gdk=$(printf '%.0f' "$preset")
backup="$monitors.omarchycn-prev"
cp "$monitors" "$backup"
sed -i -E \
-e "s|^local omarchy_monitor_scale = .*|local omarchy_monitor_scale = $preset|" \
-e "s|^local omarchy_gdk_scale = .*|local omarchy_gdk_scale = $gdk|" \
"$monitors"
if ! grep -q "omarchy_monitor_scale = $preset" "$monitors" ||
! grep -q "omarchy_gdk_scale = $gdk" "$monitors"; then
mv "$backup" "$monitors"
echo "monitors.lua lacks the omarchy scale lines; reverted, edit it manually" >&2
exit 1
fi
echo "Monitor scale: $preset, GDK scale: $gdk"
if [[ -z ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
rm -f "$backup"
echo "当前不在 Hyprland 会话内,重登录后生效"
elif ! hyprctl reload > /dev/null 2>&1; then
mv "$backup" "$monitors"
echo "hyprctl reload 失败,已恢复原配置" >&2
exit 1
elif [[ -t 0 ]]; then
echo "15 秒内按 y 保留新缩放,超时或按其他键自动恢复"
if read -r -t 15 -n 1 answer && [[ $answer == "y" ]]; then
rm -f "$backup"
echo "已保留"
else
mv "$backup" "$monitors"
hyprctl reload > /dev/null 2>&1
echo "已恢复原缩放"
fi
else
omarchy-notification-send "OmarchyCN" "缩放已改为 $preset;如异常执行: mv $backup $monitors && hyprctl reload" 2> /dev/null || true
echo "无终端交互:已应用;恢复命令: mv $backup $monitors && hyprctl reload"
fi
+124
View File
@@ -0,0 +1,124 @@
#!/bin/bash
# omarchy:summary=Check network, pacman mirror, and dev registries; --fix fails over mirrors
# omarchy:args=[network|mirror|dev-mirror] [--fix]
# omarchy:examples=omarchycn doctor | omarchycn doctor mirror --fix
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
module="all"
fix="false"
for arg in "$@"; do
case "$arg" in
all | network | mirror | dev-mirror | ime) module="$arg" ;;
--fix) fix="true" ;;
*)
echo "Unknown argument: $arg" >&2
exit 2
;;
esac
done
failures=0
check_network() {
local host
for host in mirrors.tuna.tsinghua.edu.cn archlinux.org; do
if getent hosts "$host" > /dev/null 2>&1; then
echo "PASS network: DNS resolves $host"
else
echo "FAIL network: cannot resolve $host"
failures=$((failures + 1))
fi
done
if curl -sSf -o /dev/null -m 8 --connect-timeout 5 https://www.baidu.com 2>/dev/null; then
echo "PASS network: HTTPS reachable (baidu.com)"
else
echo "FAIL network: HTTPS unreachable (baidu.com)"
failures=$((failures + 1))
fi
}
check_mirror() {
local primary speed ttfb age
primary=$(grep -sE '^Server' "$CN_MIRRORLIST" | head -1 | sed -E 's/^Server = //; s|/\$repo/os/\$arch/?$||' || true)
if [[ -z $primary ]]; then
echo "FAIL mirror: no Server entries in $CN_MIRRORLIST"
failures=$((failures + 1))
return 0
fi
echo "INFO mirror: primary $primary"
read -r speed ttfb age < <(cn_mirror_probe "$primary")
if (( speed > 0 )) && [[ $age != "stale" ]]; then
echo "PASS mirror: primary healthy ($((speed / 1024)) KB/s, sync age ${age}s)"
return 0
fi
echo "FAIL mirror: primary unhealthy (speed=$speed age=$age)"
failures=$((failures + 1))
if [[ $fix == "true" && -f $CN_PROFILE_FILE && $(<"$CN_PROFILE_FILE") == "china" ]]; then
echo "INFO mirror: re-applying china profile (auto-failover)"
if omarchy-cn-mirror-apply china; then
failures=$((failures - 1))
omarchy-notification-send "OmarchyCN" "pacman 镜像已自动切换" 2> /dev/null || true
fi
fi
}
# refresh-pacman restores the checkout's template, which on overlay installs
# has no OmarchyCN mirror line; this check makes that loss visible and fixable
check_omarchy_repo() {
local conf="${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
if ! grep -q "^Server = https://pkgs.omarchy.org/stable/" "$conf"; then
echo "INFO omarchy-repo: 非 stable 通道,跳过 OmarchyCN 镜像检查"
return 0
fi
# --fix runs the helper even when the line is present: it also heals a missing key
if [[ $fix == "true" ]]; then
if cn_mirror_omarchy_repo_fix && cn_mirror_omarchy_repo_ok; then
echo "PASS omarchy-repo: OmarchyCN 镜像行与 registry 公钥在位"
return 0
fi
elif cn_mirror_omarchy_repo_ok; then
echo "PASS omarchy-repo: OmarchyCN 镜像行在位"
return 0
fi
echo "FAIL omarchy-repo: [omarchy] 缺 OmarchyCN 镜像行(omarchycn doctor mirror --fix 可修复)"
failures=$((failures + 1))
}
check_dev_mirror() {
omarchy-cn-dev-mirror-doctor || failures=$((failures + 1))
}
check_ime() {
omarchy-cn-ime-status || failures=$((failures + 1))
}
case "$module" in
network) check_network ;;
mirror)
check_mirror
check_omarchy_repo
;;
dev-mirror) check_dev_mirror ;;
ime) check_ime ;;
all)
check_network
check_mirror
check_omarchy_repo
check_dev_mirror
check_ime
;;
esac
if (( failures > 0 )); then
echo "Doctor: $failures failure(s)"
exit 1
fi
echo "Doctor: all checks passed"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# omarchy:summary=Install CJK fonts and the OmarchyCN fontconfig priority
# omarchy:examples=omarchycn font apply
set -euo pipefail
omarchy-pkg-add noto-fonts-cjk noto-fonts-emoji
conf_dir="$HOME/.config/fontconfig/conf.d"
mkdir -p "$conf_dir"
cp "$OMARCHY_PATH/cn/fontconfig/64-omarchycn-cjk.conf" "$conf_dir/"
fc-cache -f > /dev/null
echo "CJK fonts installed, fontconfig priority applied ($conf_dir/64-omarchycn-cjk.conf)"
omarchy-cn-font-status
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# omarchy:summary=Check Chinese font availability and fallback correctness
# omarchy:examples=omarchycn font status
set -euo pipefail
failures=0
for family in "Noto Sans CJK SC" "Noto Serif CJK SC" "Noto Sans Mono CJK SC"; do
matched=$(fc-match --format '%{family}' "$family")
if [[ $matched == *"$family"* ]]; then
echo "PASS font: $family"
else
echo "FAIL font: $family missing (got: $matched)"
failures=$((failures + 1))
fi
done
zh_sans=$(fc-match --format '%{family}' sans-serif:lang=zh-cn)
if [[ $zh_sans == *"CJK SC"* ]]; then
echo "PASS fallback: zh-cn sans-serif -> $zh_sans"
else
echo "FAIL fallback: zh-cn sans-serif -> $zh_sans (expected SC variant)"
failures=$((failures + 1))
fi
# Untagged requests (Chromium/Electron path) must reach SC before JP/KR
untagged=$(fc-match --sort sans-serif | grep -m1 -oE 'CJK (SC|JP|TC|KR|HK)' || true)
if [[ $untagged == "CJK SC" ]]; then
echo "PASS fallback: untagged CJK -> SC"
else
echo "FAIL fallback: untagged CJK -> ${untagged:-none} (expected SC)"
failures=$((failures + 1))
fi
exit $((failures > 0 ? 1 : 0))
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# omarchy:summary=Install Fcitx5 Rime and Chinese addons with a default profile
# omarchy:examples=omarchycn ime apply
set -euo pipefail
omarchy-pkg-add fcitx5-rime fcitx5-chinese-addons fcitx5-configtool
profile_dir="$HOME/.config/fcitx5"
mkdir -p "$profile_dir"
if [[ -f $profile_dir/profile && "$(<"$profile_dir/profile")" != "$(<"$OMARCHY_PATH/cn/fcitx5/profile")" ]]; then
backup_dir="$HOME/.local/state/omarchycn/backups/ime/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
cp "$profile_dir/profile" "$backup_dir/profile"
echo "Existing profile backed up to $backup_dir"
fi
cp "$OMARCHY_PATH/cn/fcitx5/profile" "$profile_dir/profile"
if systemctl --user is-active omarchy-fcitx5.service > /dev/null 2>&1; then
systemctl --user restart omarchy-fcitx5.service
echo "Fcitx5 restarted with Rime profile"
else
echo "Fcitx5 profile installed (takes effect at next graphical session)"
fi
omarchy-cn-ime-status
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# omarchy:summary=Set the input method toggle key, migrating the menu key if needed
# omarchy:args=<ctrl-space|super-space>
# omarchy:examples=omarchycn ime hotkey ctrl-space | omarchycn ime hotkey super-space
set -euo pipefail
choice="${1:?usage: omarchycn ime hotkey <ctrl-space|super-space>}"
fcitx_config="$HOME/.config/fcitx5/config"
bindings="$HOME/.config/hypr/bindings.lua"
# Replace or append the [Hotkey/TriggerKeys] section in fcitx5 global config
set_trigger() {
local key="$1"
mkdir -p "${fcitx_config%/*}"
touch "$fcitx_config"
awk '
/^\[Hotkey\/TriggerKeys\]$/ { skip = 1; next }
/^\[/ { skip = 0 }
!skip { print }
' "$fcitx_config" > "$fcitx_config.omarchycn-tmp"
printf '[Hotkey/TriggerKeys]\n0=%s\n' "$key" >> "$fcitx_config.omarchycn-tmp"
mv "$fcitx_config.omarchycn-tmp" "$fcitx_config"
}
remove_menu_migration() {
if [[ -f $bindings ]]; then
sed -i '/^-- OmarchyCN ime hotkey begin$/,/^-- OmarchyCN ime hotkey end$/d' "$bindings"
fi
}
case "$choice" in
ctrl-space)
set_trigger "Control+space"
remove_menu_migration
echo "输入法切换键: Ctrl+SpaceOmarchy 菜单保持 Super+Space"
;;
super-space)
set_trigger "Super+space"
mkdir -p "${bindings%/*}"
touch "$bindings"
remove_menu_migration
cat >> "$bindings" <<'EOF'
-- OmarchyCN ime hotkey begin
hl.unbind("SUPER + SPACE")
hl.unbind("SUPER + ALT + SPACE")
o.bind("SUPER + ALT + SPACE", "Omarchy menu", "omarchy-menu toggle")
-- OmarchyCN ime hotkey end
EOF
echo "输入法切换键: Super+Space"
echo "Omarchy 菜单已迁移至 Super+Alt+Space(写入 $bindings"
echo "原 Super+Alt+Space 的 Apps 菜单让位,可从根菜单进入或自行改绑"
;;
*)
echo "Unknown hotkey choice: $choice (expected ctrl-space or super-space)" >&2
exit 1
;;
esac
if systemctl --user is-active omarchy-fcitx5.service > /dev/null 2>&1; then
systemctl --user restart omarchy-fcitx5.service
fi
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# omarchy:summary=Check Fcitx5 Rime installation and configuration
# omarchy:examples=omarchycn ime status
set -euo pipefail
failures=0
for pkg in fcitx5 fcitx5-gtk fcitx5-qt fcitx5-rime fcitx5-chinese-addons; do
if pacman -Q "$pkg" > /dev/null 2>&1; then
echo "PASS ime: $pkg installed"
else
echo "FAIL ime: $pkg missing"
failures=$((failures + 1))
fi
done
if grep -sq '^Name=rime$' "$HOME/.config/fcitx5/profile"; then
echo "PASS ime: rime in fcitx5 profile"
else
echo "FAIL ime: rime not in fcitx5 profile (run: omarchycn ime apply)"
failures=$((failures + 1))
fi
if grep -sq '^DefaultIM=rime$' "$HOME/.config/fcitx5/profile"; then
echo "PASS ime: rime is the default input method"
else
echo "FAIL ime: DefaultIM is not rime"
failures=$((failures + 1))
fi
env_file="/usr/share/omarchy/default/environment.d/10-omarchy-fcitx.conf"
if [[ -f $env_file ]] || [[ -f $OMARCHY_PATH/default/environment.d/10-omarchy-fcitx.conf ]]; then
echo "PASS ime: fcitx environment file present"
else
echo "FAIL ime: fcitx environment file missing"
failures=$((failures + 1))
fi
trigger=$(grep -sA2 '^\[Hotkey/TriggerKeys\]' "$HOME/.config/fcitx5/config" | grep -sE '^0=' | cut -d= -f2- || true)
if [[ $trigger == "Super+space" ]] && ! grep -sq 'OmarchyCN ime hotkey begin' "$HOME/.config/hypr/bindings.lua"; then
echo "FAIL ime: Super+space 与 Omarchy 菜单冲突 (run: omarchycn ime hotkey super-space)"
failures=$((failures + 1))
else
echo "PASS ime: trigger key ${trigger:-Control+space (fcitx5 default)}"
fi
if pgrep -x fcitx5 > /dev/null 2>&1; then
echo "PASS ime: fcitx5 running"
else
echo "INFO ime: fcitx5 not running (headless session?)"
fi
exit $((failures > 0 ? 1 : 0))
+148
View File
@@ -0,0 +1,148 @@
#!/bin/bash
# omarchy:summary=Install or remove the OmarchyCN layer on an existing Omarchy
# omarchy:args=[--uninstall]
# omarchy:examples=omarchy-cn-install-overlay | omarchy-cn-install-overlay --uninstall
# Bootstraps onto a foreign OMARCHY_PATH, so it resolves its own checkout root.
set -euo pipefail
SRC=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
DEST="${OMARCHY_PATH:?OMARCHY_PATH must point at the target Omarchy install}"
MANIFEST="$HOME/.local/state/omarchycn/overlay-manifest"
run_in() {
local root="$1"
shift
if [[ -w $root ]]; then
"$@"
else
sudo "$@"
fi
}
valid_entry() {
[[ $1 == "cn" || $1 =~ ^bin/(omarchycn|omarchy-cn-[a-z0-9-]+)$ ]]
}
MENU_EXT="$HOME/.config/omarchy/extensions/omarchy-menu.jsonc"
MENU_MARKER="// OmarchyCN overlay menu extension"
# Comments-only files (upstream ships such a template) hold no user content
menu_ext_has_user_content() {
[[ -f $MENU_EXT ]] || return 1
grep -qF "$MENU_MARKER" "$MENU_EXT" && return 1
[[ -n $(grep -vE '^[[:space:]]*(//.*)?$' "$MENU_EXT" | tr -d '[:space:]{}') ]]
}
install_menu_extension() {
if menu_ext_has_user_content; then
echo "跳过菜单扩展:$MENU_EXT 已有用户内容,请手动合并 default/omarchy/omarchy-menu.jsonc 的 OmarchyCN 段"
return 0
fi
mkdir -p "${MENU_EXT%/*}"
{
echo "$MENU_MARKER"
echo "{"
sed -n '/^ \/\/ OmarchyCN$/,$p' "$SRC/default/omarchy/omarchy-menu.jsonc" | sed '$d'
echo "}"
} > "$MENU_EXT"
echo "OmarchyCN 菜单扩展已写入 $MENU_EXT"
}
remove_menu_extension() {
if [[ -f $MENU_EXT ]] && grep -qF "$MENU_MARKER" "$MENU_EXT"; then
rm -f "$MENU_EXT"
fi
}
uninstall() {
if [[ ! -f $MANIFEST ]]; then
echo "No overlay manifest at $MANIFEST" >&2
exit 1
fi
local dest="" rel=""
dest=$(grep -m1 '^dest=' "$MANIFEST" | cut -d= -f2-)
if [[ -z $dest || ! -d $dest ]]; then
echo "Manifest has no valid dest= record" >&2
exit 1
fi
# Validate every entry before deleting anything
while IFS= read -r rel; do
[[ $rel == source=* || $rel == dest=* ]] && continue
if ! valid_entry "$rel"; then
echo "Refusing suspicious manifest entry: $rel (nothing deleted)" >&2
exit 1
fi
done < "$MANIFEST"
while IFS= read -r rel; do
[[ $rel == source=* || $rel == dest=* ]] && continue
if [[ $rel == "cn" ]]; then
run_in "$dest" rm -rf "$dest/cn"
else
run_in "$dest" rm -f "$dest/$rel"
fi
done < "$MANIFEST"
remove_menu_extension
rm -f "$MANIFEST"
echo "OmarchyCN overlay removed from $dest"
}
install() {
if [[ ! -f $DEST/version || ! -x $DEST/bin/omarchy ]]; then
echo "No Omarchy install found at $DEST" >&2
exit 1
fi
if [[ $SRC == "$DEST" ]]; then
echo "Source checkout and target are the same tree; nothing to overlay" >&2
exit 1
fi
# Reinstall: remove the previous overlay first so upgrades leave no residue
if [[ -f $MANIFEST ]]; then
uninstall
fi
if [[ -e $DEST/cn ]]; then
echo "Refusing to take over unmanaged $DEST/cn" >&2
exit 1
fi
local f rel entries=()
for f in "$SRC/bin/omarchycn" "$SRC"/bin/omarchy-cn-*; do
rel="bin/${f##*/}"
if [[ -e $DEST/$rel ]]; then
echo "Refusing to overwrite unmanaged $DEST/$rel" >&2
exit 1
fi
entries+=("$rel")
done
# Manifest is the intent record, published BEFORE copying: if a copy fails
# midway, a rerun sees the manifest, uninstalls the partial state, retries.
mkdir -p "${MANIFEST%/*}"
{
echo "source=$SRC"
echo "dest=$DEST"
echo "cn"
printf '%s\n' "${entries[@]}"
} > "$MANIFEST"
run_in "$DEST" cp -r "$SRC/cn" "$DEST/cn"
for rel in "${entries[@]}"; do
run_in "$DEST" cp "$SRC/$rel" "$DEST/$rel"
done
install_menu_extension
echo "OmarchyCN overlay installed into $DEST (${#entries[@]} commands)"
echo "Verify: omarchy cn version"
}
if [[ ${1:-} == "--uninstall" ]]; then
uninstall
else
install
fi
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# omarchy:summary=Install Kimi Code with the official installer script
# omarchy:examples=omarchycn kimi install
set -euo pipefail
if omarchy-cmd-present kimi; then
echo "Kimi Code 已安装"
exit 0
fi
installer=$(mktemp)
trap 'rm -f "$installer"' EXIT
curl -fsSL https://code.kimi.com/kimi-code/install.sh -o "$installer"
bash "$installer"
# The official installer only touches .bashrc PATH; expose kimi now
if [[ ! -x $HOME/.kimi-code/bin/kimi ]]; then
echo "kimi binary missing after the official installer" >&2
exit 1
fi
mkdir -p "$HOME/.local/bin"
ln -sf "$HOME/.kimi-code/bin/kimi" "$HOME/.local/bin/kimi"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# omarchy:summary=Generate zh_CN.UTF-8 and en_US.UTF-8 locales
# omarchy:examples=omarchycn locale apply
# omarchy:requires-sudo=true
set -euo pipefail
for loc in zh_CN en_US; do
if ! grep -qE "^${loc}\.UTF-8 UTF-8" /etc/locale.gen; then
echo "${loc}.UTF-8 UTF-8" | sudo tee -a /etc/locale.gen > /dev/null
fi
done
sudo locale-gen
for loc in zh_CN en_US; do
if ! locale -a | grep -qiE "^${loc}\.utf-?8$"; then
echo "FAIL locale: ${loc}.UTF-8 not generated" >&2
exit 1
fi
echo "PASS locale: ${loc}.UTF-8"
done
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
# omarchy:summary=Run pending OmarchyCN migrations
# omarchy:examples=omarchycn migrate
set -euo pipefail
MIGRATIONS_DIR="$OMARCHY_PATH/cn/migrations"
DONE_DIR="$HOME/.local/state/omarchycn/migrations-done"
[[ -d $MIGRATIONS_DIR ]] || exit 0
mkdir -p "$DONE_DIR"
for m in "$MIGRATIONS_DIR"/*.sh; do
[[ -f $m ]] || continue
name="${m##*/}"
if [[ ! -f $DONE_DIR/$name ]]; then
echo "OmarchyCN migration: $name"
bash -euo pipefail "$m"
touch "$DONE_DIR/$name"
fi
done
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# omarchy:summary=Apply a mirror profile or pin one mirror for pacman
# omarchy:args=<china|official|mirror-id>
# omarchy:examples=omarchycn mirror apply china | omarchycn mirror apply official | omarchycn mirror apply tuna
# omarchy:requires-sudo=true
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
target="${1:?usage: omarchycn mirror apply <china|official|mirror-id>}"
case "$target" in
china)
echo "Benchmarking China mirrors..."
mapfile -t cn_ids < <(cn_mirror_ids_by_region cn)
mapfile -t ranked < <(cn_mirror_rank "${cn_ids[@]}")
if (( ${#ranked[@]} == 0 )); then
echo "No healthy China mirror reachable; keeping current mirrorlist" >&2
exit 1
fi
# Primary + up to two backups
cn_mirror_write_list china "${ranked[@]:0:3}"
;;
official)
cn_mirror_write_list official geo worldwide
;;
*)
url=$(cn_mirror_url "$target")
if [[ -z $url ]]; then
echo "Unknown mirror id: $target (see: omarchycn mirror benchmark)" >&2
exit 1
fi
cn_mirror_write_list "pin:$target" "$target"
;;
esac
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# omarchy:summary=Benchmark Arch mirrors for speed, latency, and sync freshness
# omarchy:examples=omarchycn mirror benchmark
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
printf '%-11s %-22s %12s %8s %10s\n' "ID" "NAME" "SPEED" "TTFB" "SYNC"
for id in $(cn_mirror_ids); do
url=$(cn_mirror_url "$id")
read -r speed ttfb age < <(cn_mirror_probe "$url")
if (( speed > 0 )); then
speed_h="$((speed / 1024)) KB/s"
else
speed_h="FAIL"
fi
case "$age" in
stale) sync_h="STALE" ;;
unknown) sync_h="?" ;;
*) sync_h="$((age / 60))m ago" ;;
esac
printf '%-11s %-22s %12s %7ss %10s\n' "$id" "$(cn_mirror_name "$id")" "$speed_h" "${ttfb:0:5}" "$sync_h"
done
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# omarchy:summary=Pin pacman to a single mirror
# omarchy:args=<mirror-id>
# omarchy:examples=omarchycn mirror pin tuna
# omarchy:requires-sudo=true
set -euo pipefail
exec omarchy-cn-mirror-apply "${1:?usage: omarchycn mirror pin <mirror-id>}"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# omarchy:summary=Restore the most recent mirrorlist backup
# omarchy:examples=omarchycn mirror restore
# omarchy:requires-sudo=true
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
backups=("$CN_MIRRORLIST".omarchycn-bak-*)
if [[ ! -e ${backups[0]} ]]; then
echo "No OmarchyCN mirrorlist backups found" >&2
exit 1
fi
latest="${backups[-1]}"
sudo cp "$latest" "$CN_MIRRORLIST"
rm -f "$CN_PROFILE_FILE"
echo "Restored $CN_MIRRORLIST from ${latest##*/}"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# omarchy:summary=Show current pacman mirror profile and servers
# omarchy:examples=omarchycn mirror status
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
if [[ -f $CN_PROFILE_FILE ]]; then
echo "Profile: $(<"$CN_PROFILE_FILE")"
else
echo "Profile: not managed by OmarchyCN"
fi
echo "Mirrorlist: $CN_MIRRORLIST"
echo "Servers:"
grep -E '^Server' "$CN_MIRRORLIST" | sed 's/^/ /'
backups=("$CN_MIRRORLIST".omarchycn-bak-*)
if [[ -e ${backups[0]} ]]; then
echo "Backups: ${#backups[@]} (latest: ${backups[-1]##*/})"
fi
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# omarchy:summary=Restore development registry configs from a backup stamp
# omarchy:args=<stamp>
# omarchy:examples=omarchycn restore config 20260824-184243
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
stamp="${1:?usage: omarchycn restore config <stamp> (see: omarchycn restore list)}"
dir="$CN_DM_BACKUP_ROOT/$stamp"
if [[ ! -d $dir ]]; then
echo "No backup at $dir" >&2
exit 1
fi
restored=0
for target in "${CN_DM_TARGETS[@]}"; do
dest=$(cn_dm_config_file "$target")
src="$dir/${dest##*/}"
if [[ -f $src && $dest != /etc/* ]]; then
mkdir -p "${dest%/*}"
cp "$src" "$dest"
echo "restored $target: $dest"
restored=$((restored + 1))
fi
done
if (( restored == 0 )); then
echo "Nothing restored from $dir (system files like docker are not auto-restored)" >&2
exit 1
fi
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# omarchy:summary=List OmarchyCN configuration backups
# omarchy:examples=omarchycn restore list
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
echo "Dev-mirror backups ($CN_DM_BACKUP_ROOT):"
if [[ -d $CN_DM_BACKUP_ROOT ]]; then
for dir in "$CN_DM_BACKUP_ROOT"/*/; do
[[ -d $dir ]] || continue
stamp="${dir%/}"
stamp="${stamp##*/}"
echo " $stamp: $(ls "$dir" | tr '\n' ' ')"
done
else
echo " (none)"
fi
echo "Mirrorlist backups (/etc/pacman.d):"
found="false"
for f in /etc/pacman.d/mirrorlist.omarchycn-bak-*; do
if [[ -e $f ]]; then
echo " ${f##*/}"
found="true"
fi
done
if [[ $found == "false" ]]; then
echo " (none)"
fi
+89
View File
@@ -0,0 +1,89 @@
#!/bin/bash
# omarchy:summary=Revert an OmarchyCN conversion back to vanilla Omarchy
# omarchy:examples=omarchy cn revert
# Curl-able and self-deleting-safe: main() is fully parsed before the package
# swap removes this file from the cn tree it may be running from.
set -euo pipefail
STATE_DIR=/var/lib/omarchycn
STATE=$STATE_DIR/convert-state
MIRROR_LINE="Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64"
REG_KEY_FPR=74DCF57ACD812B24D959F146BD386048867B33B4
# Bootstrap exception: curl|bash runs outside a session, so default the path
export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}
main() {
if [[ ! -f $STATE ]]; then
echo "未发现转换状态($STATE):本机不是由 omarchy cn convert 转换而来" >&2
exit 1
fi
if (( EUID == 0 )); then
echo "请以普通用户运行(脚本内部按需 sudo" >&2
exit 1
fi
local prev_omarchy="" prev_settings="" new_pkgs=""
# State is trusted root-owned key=value lines written by convert
source "$STATE"
[[ -n $prev_omarchy && -n $prev_settings ]] || { echo "转换状态损坏: $STATE" >&2; exit 1; }
echo "==> 清理转换时播种的用户配置(与 cn 层原件一致才删除)"
local seeded
for seeded in \
"$HOME/.config/fcitx5/profile:/usr/share/omarchy/cn/fcitx5/profile" \
"$HOME/.config/fontconfig/conf.d/64-omarchycn-cjk.conf:/usr/share/omarchy/cn/fontconfig/64-omarchycn-cjk.conf"; do
if cmp -s "${seeded%%:*}" "${seeded#*:}" 2> /dev/null; then
rm -f "${seeded%%:*}"
fi
done
echo "==> 还原 mirrorlist 与系统语言"
sudo cp "$STATE_DIR/mirrorlist.pre" /etc/pacman.d/mirrorlist
if [[ -f $STATE_DIR/locale.conf.pre ]]; then
sudo cp "$STATE_DIR/locale.conf.pre" /etc/locale.conf
fi
echo "==> pacman.conf 移除 [omarchycn] 与 [omarchy] 镜像行"
sudo sed -i "\|^$MIRROR_LINE\$|d" /etc/pacman.conf
# Delete the section header and body, but keep any section that follows
sudo sed -i '/^\[omarchycn\]$/,/^\[/{ /^\[omarchycn\]$/d; /^\[/!d; }' /etc/pacman.conf
echo "==> 换回上游软件包($prev_omarchy/$prev_settings"
sudo pacman -Sy
# --ask=4 auto-confirms removing the conflicting cn packages
sudo pacman -S --noconfirm --ask=4 "omarchy/$prev_omarchy" "omarchy/$prev_settings"
if [[ -n $new_pkgs ]]; then
# Only what is still installed: a user may have removed some already,
# and a -R failure here would strand the swap-completed system
local p still=""
for p in $new_pkgs; do
# grep without -q consumes the whole list: -q's early exit would
# SIGPIPE pacman and read as "not installed" under pipefail
pacman -Qq | grep -xF "$p" > /dev/null && still+="$p "
done
if [[ -n $still ]]; then
echo "==> 移除转换时新装的包: ${still% }"
# shellcheck disable=SC2086
sudo pacman -R --noconfirm ${still% }
fi
fi
echo "==> 移除 registry 公钥信任"
sudo pacman-key --delete "$REG_KEY_FPR" > /dev/null 2>&1 || true
# The system is vanilla again: clear state before the cosmetic tail so a
# plymouth failure cannot strand a reverted machine in "converted" state
systemctl --user restart omarchy-fcitx5.service 2> /dev/null || true
sudo rm -rf "$STATE_DIR"
echo "==> 还原上游开机画面"
if ! omarchy-refresh-plymouth; then
echo "开机画面还原失败:手动执行 omarchy-refresh-plymouth 即可" >&2
exit 1
fi
echo
echo "已回退为原版 Omarchy(重新登录生效)。再次转换: omarchy cn convert"
}
main "$@"
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# omarchy:summary=First-run wizard: language, timezone, scale, Chinese env, mirrors, apps, AI
# omarchy:examples=omarchycn setup
set -euo pipefail
if [[ ! -t 0 ]]; then
echo "omarchycn setup 需要交互终端" >&2
exit 1
fi
STATE_DIR="$HOME/.local/state/omarchycn/setup-done"
mkdir -p "$STATE_DIR"
run_step() {
local step="$1" title="$2"
shift 2
if [[ -f $STATE_DIR/$step ]]; then
gum confirm "「$title」已完成,重新运行?" --default=false || return 0
elif ! gum confirm "运行「$title」?(可跳过,稍后重进)"; then
return 0
fi
echo "==> $title"
if "$@"; then
touch "$STATE_DIR/$step"
else
gum confirm "「$title」失败,继续后面的步骤?" || exit 1
fi
}
step_language() {
local choice env_file="$HOME/.config/environment.d/90-omarchycn-lang.conf"
choice=$(gum choose --header "界面语言 LANG" "zh_CN.UTF-8" "en_US.UTF-8" "保持当前")
if [[ $choice == "保持当前" ]]; then
return 0
fi
mkdir -p "${env_file%/*}"
echo "LANG=$choice" > "$env_file"
echo "LANG=$choice 写入 $env_file(重登录生效)"
}
step_timezone() {
local choice
choice=$(gum choose --header "时区" "Asia/Shanghai" "保持当前" "手动输入")
if [[ $choice == "保持当前" ]]; then
return 0
fi
if [[ $choice == "手动输入" ]]; then
choice=$(timedatectl list-timezones | gum filter --header "选择时区")
fi
sudo timedatectl set-timezone "$choice"
echo "时区: $(timedatectl show -p Timezone --value)"
}
step_scale() {
local choice
choice=$(gum choose --header "显示缩放" "1.0" "1.25" "1.5" "1.6" "1.75" "2.0" "保持当前")
if [[ $choice == "保持当前" ]]; then
return 0
fi
omarchy-cn-display-scale "$choice"
}
step_hotkey() {
local choice
choice=$(gum choose --header "输入法切换键" "ctrl-space" "super-space")
omarchy-cn-ime-hotkey "$choice"
}
step_mirror() {
local choice
choice=$(gum choose --header "pacman 镜像策略" "china" "official")
omarchy-cn-mirror-apply "$choice"
}
step_dev_mirror() {
local choice
choice=$(gum choose --header "开发工具镜像(npm/pip/cargo/go/gem" "china" "official")
omarchy-cn-dev-mirror-apply "$choice" --target npm,pip,cargo,go,gem
}
step_apps() {
local catalog picks app rc=0
catalog=$(omarchy-cn-app-list | tail -n +2 | awk '{print $1}') || return 1
picks=$(gum choose --no-limit --header "选择要安装的国内应用(空格多选,回车确认)" <<<"$catalog" || true)
for app in $picks; do
omarchy-cn-app-install "$app" || rc=1
done
return $rc
}
step_privacy() {
echo "隐私状态(无可配置项,如实告知):"
echo " - OmarchyCN 不含遥测代码,默认无任何数据上报"
echo " - 诊断信息仅在你手动运行 doctor 时本地生成,不上传"
gum confirm "已了解" --affirmative "了解" --negative "" || true
}
run_step language "语言 Language" step_language
run_step timezone "时区 Timezone" step_timezone
run_step scale "显示缩放 Display Scale" step_scale
run_step locale "中文 Locale 生成" omarchy-cn-locale-apply
run_step fonts "中文字体与 fallback" omarchy-cn-font-apply
run_step ime "Fcitx5 + Rime 输入法" omarchy-cn-ime-apply
run_step hotkey "输入法切换键" step_hotkey
run_step mirror "pacman 镜像" step_mirror
run_step dev-mirror "开发工具镜像" step_dev_mirror
run_step apps "国内应用" step_apps
run_step ai "AI HubHarness/Provider/Key" omarchy-cn-ai-setup
run_step privacy "隐私与诊断" step_privacy
echo
echo "==> 最终检查"
if omarchy-cn-doctor all; then
echo "OmarchyCN 初始化完成。随时可用 omarchycn setup 重进任一步骤。"
else
echo "初始化步骤已执行,但诊断存在失败项(见上),修复后可重跑 omarchycn setup" >&2
exit 1
fi
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# omarchy:summary=Show OmarchyCN version, paths, and configured state
# omarchy:examples=omarchycn status
set -euo pipefail
echo "OmarchyCN $(omarchy-cn-version)"
echo "Omarchy upstream: $(<"$OMARCHY_PATH/version")"
echo "Root: $OMARCHY_PATH"
config="$HOME/.config/omarchycn"
state="$HOME/.local/state/omarchycn"
echo "User config: $config $([[ -d $config ]] && echo "(present)" || echo "(not created)")"
echo "State: $state $([[ -d $state ]] && echo "(present)" || echo "(not created)")"
if [[ -f $config/mirror-profile ]]; then
echo "Mirror profile: $(<"$config/mirror-profile")"
else
echo "Mirror profile: not configured"
fi
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
# omarchy:summary=Full OmarchyCN update: system, cn data, and migrations
# omarchy:examples=omarchycn update
set -euo pipefail
MANIFEST="$HOME/.local/state/omarchycn/overlay-manifest"
if [[ ! -f $MANIFEST ]]; then
# Package install: the pipeline brings the new cn tree and runs cn migrations
if [[ -d $OMARCHY_PATH/cn ]]; then
exec omarchy-update
fi
echo "未检测到 OmarchyCN$OMARCHY_PATH/cn 不存在,也没有 overlay manifest" >&2
exit 1
fi
src=$(grep -m1 '^source=' "$MANIFEST" | cut -d= -f2-)
if [[ ! -d $src/.git ]]; then
echo "Overlay source $src is not a git checkout" >&2
exit 1
fi
channel=$(omarchy-cn-channel)
channel=${channel%% *}
old=$(git -C "$src" rev-parse --short HEAD)
git -C "$src" fetch -q --tags origin
case "$channel" in
nightly)
git -C "$src" checkout -q quattro
git -C "$src" pull --ff-only origin quattro
;;
beta)
tag=$(git -C "$src" tag -l "*-cn.*" | sort -V | tail -1)
if [[ -z $tag ]]; then
echo "beta 通道无可用发布 tag" >&2
exit 1
fi
git -C "$src" checkout -q "$tag"
;;
stable)
tag=$(git -C "$src" tag -l "*-cn.*" | grep -vE "alpha|beta|rc" | sort -V | tail -1 || true)
if [[ -z $tag ]]; then
echo "当前尚无 stable 发布(omarchycn channel beta 可跟随预发布)" >&2
exit 1
fi
git -C "$src" checkout -q "$tag"
;;
*)
echo "Unknown channel: $channel" >&2
exit 1
;;
esac
# Never move onto a tree that predates the channel or migrate mechanism:
# it cannot switch back or finish this update's migration step
if [[ ! -f $src/bin/omarchy-cn-channel || ! -f $src/bin/omarchy-cn-migrate ]]; then
git -C "$src" checkout -q "$old"
echo "$channel 通道的目标发布早于当前更新机制,已回退;请使用 nightly 或更新的发布" >&2
exit 1
fi
new=$(git -C "$src" rev-parse --short HEAD)
echo "Channel: $channel, source: $old -> $new"
"$src/bin/omarchy-cn-install-overlay"
"$src/bin/omarchy-cn-migrate"
echo "OmarchyCN update complete ($(omarchy-cn-version 2>/dev/null || echo unknown))"
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# omarchy:summary=Show the OmarchyCN version
# omarchy:examples=omarchycn version
set -euo pipefail
upstream=$(<"$OMARCHY_PATH/version")
release=$(<"$OMARCHY_PATH/cn/release")
echo "${upstream}-cn.${release}"
+73
View File
@@ -0,0 +1,73 @@
#!/bin/bash
# omarchy:summary=Silence crash notifications for one program, or list what is silenced
# omarchy:args=[--] [<program>] [on|off|toggle]
# omarchy:examples=omarchy crash mute | omarchy crash mute hyprland | omarchy crash mute /usr/bin/hyprland | omarchy crash mute hyprland off
# The flag omarchy-crash-watch reads before announcing a crash. Muting is per
# program; Trigger > Toggle > Crash Capture is the switch for all of them.
set -uo pipefail
readonly MUTES="$HOME/.local/state/omarchy/toggles/crash-ignore"
usage() {
echo "Usage: omarchy crash mute [--] [<program>] [on|off|toggle]" >&2
}
# Only regular files, because that is all the watcher honours: anything else in
# there would be reported as muted while the crashes kept arriving. The dotted
# glob is for a program legitimately called .hidden, and `.` and `..` fail the
# same -f test that keeps them out.
list() {
local entry found=0
for entry in "$MUTES"/* "$MUTES"/.*; do
[[ -f $entry ]] || continue
printf '%s\n' "${entry##*/}"
found=1
done
((found)) || echo "No programs muted. Crashes all notify."
}
# A program may be named -h, and the router answers that with its own help
# before this ever runs. `omarchy crash mute -- -h` is the way through.
[[ ${1:-} == "--" ]] && shift
if (($# == 0)); then
list
exit 0
fi
program=$1
action=${2:-on}
# The watcher keys the mute on the executable's basename, so accept the path it
# reports as readily as the name, and reduce either the same way it does.
program=${program##*/}
if [[ -z $program || $program == "." || $program == ".." ]]; then
echo "Not a program name: $1" >&2
usage
exit 1
fi
case "$action" in
on|off|toggle) ;;
*)
echo "Not an action: $action" >&2
usage
exit 1
;;
esac
omarchy-toggle "crash-ignore/$program" "$action" || exit 1
# Report what is now true rather than what was asked for: the flag is what the
# watcher reads, and a toggle does not say which way it went.
if omarchy-toggle-enabled "crash-ignore/$program"; then
echo "Muted crash notifications for $program."
else
echo "Crash notifications for $program are back on."
fi
+28 -5
View File
@@ -48,12 +48,17 @@ announce() {
# -n 0 so a restart does not re-announce crashes already dealt with.
journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
while IFS= read -r entry; do
# A dash for a field that is empty as well as one that is missing: tab is
# IFS whitespace, so an empty field collapses into the next delimiter and
# every field after it shifts along one. A process can set its own comm to
# nothing, and that crash used to be read as somebody else's and dropped.
IFS=$'\t' read -r uid comm pid exe signal < <(
jq -r '[(._UID // "-"),
(.COREDUMP_COMM // "-"),
(.COREDUMP_PID // "-"),
(.COREDUMP_EXE // "-"),
(.COREDUMP_SIGNAL_NAME // "-")] | @tsv' <<<"$entry" 2>/dev/null
jq -r 'def field: if . == null or . == "" then "-" else . end;
[(._UID | field),
(.COREDUMP_COMM | field),
(.COREDUMP_PID | field),
(.COREDUMP_EXE | field),
(.COREDUMP_SIGNAL_NAME | field)] | @tsv' <<<"$entry" 2>/dev/null
)
[[ $pid =~ ^[0-9]+$ ]] || continue
@@ -71,11 +76,29 @@ journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
name=$comm
[[ $exe == /* ]] && name=${exe##*/}
# A process can set its own comm to anything prctl takes, slashes included,
# and a crash with no recorded executable falls back to it. The mute below
# turns this name into a path, so keep it one component: a crash must not
# reach a flag outside crash-ignore/, nor have a diagnosis write one there.
name=${name##*/}
# What that leaves is not always a name. "/" leaves nothing, which is no
# kind of array subscript and no kind of toast; a dot component names a
# directory rather than a flag, so a mute on it would touch that directory
# and then never match; and a dash is what the read above puts there when
# the crash recorded no name at all.
[[ -n $name && $name != "-" && $name != "." && $name != ".." ]] || name=unknown
[[ -n $ignore_pattern && $name =~ $ignore_pattern ]] && continue
# Never announce our own machinery, or it notifies about itself.
[[ $name == omarchy-crash-* || $name == omarchy-agent-* ]] && continue
# Muted at the end of a diagnosis, when the user was offered it and said
# yes. A flag per program rather than one list, so omarchy-crash-mute can
# lift one without reading, rewriting and re-parsing the rest.
omarchy-toggle-enabled "crash-ignore/$name" && continue
now=$EPOCHSECONDS
(((now - ${last_notified[$name]:-0}) < dedupe_seconds)) && continue
+29 -12
View File
@@ -1,8 +1,8 @@
#!/bin/bash
# omarchy:summary=Set and launch the default coding agent
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush]
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent claude
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim|dsh]
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent kimi
installing=false
if [[ ${1:-} == "--install" ]]; then
@@ -34,25 +34,42 @@ crush) agent="crush"; name="Crush" ;;
grok) agent="grok"; name="Grok"; agent_package="npm:@xai-official/grok" ;;
agy | antigravity | antigravity-cli | gemini | gemini-cli) agent="agy"; name="Antigravity"; agent_package="antigravity-cli" ;;
copilot | github-copilot) agent="copilot"; name="GitHub Copilot" ;;
kimi | kimi-code) agent="kimi"; name="Kimi Code" ;;
deepcode | deep-code) agent="deepcode"; name="Deep Code"; agent_package="npm:@vegamo/deepcode-cli" ;;
dim | dimcode | dim-agent) agent="dim"; name="Dim Agent"; agent_package="npm:dimcode" ;;
dsh | deepseek-harness) agent="dsh"; name="DSH"; agent_package="npm:@deepseek-ai/dsh" ;;
*)
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush>"
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|kimi|deepcode|dim|dsh>"
exit 1
;;
esac
agent_package=${agent_package:-$agent}
if [[ $installing == "false" ]] && ! mise where "$agent_package" &>/dev/null; then
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-agent --install "$agent"
fi
# Kimi Code installs via its official script, not mise
if [[ $agent == "kimi" ]]; then
if omarchy-cmd-missing kimi; then
if [[ $installing == "false" ]]; then
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-agent --install kimi
fi
if ! omarchy-cn-kimi-install; then
echo "Could not install $name with the official installer" >&2
exit 1
fi
fi
else
if [[ $installing == "false" ]] && ! mise where "$agent_package" &>/dev/null; then
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-agent --install "$agent"
fi
if ! mise use -g "$agent_package"; then
if [[ $installing == "true" ]]; then
echo "Could not install $name with mise" >&2
else
echo "Could not set $name as the default coding agent" >&2
if ! mise use -g "$agent_package"; then
if [[ $installing == "true" ]]; then
echo "Could not install $name with mise" >&2
else
echo "Could not set $name as the default coding agent" >&2
fi
exit 1
fi
exit 1
fi
mkdir -p "$(dirname "$agent_file")"
+12
View File
@@ -6,6 +6,18 @@
set -euo pipefail
# Whenever this runs as root — invoked directly through the passwordless
# sudoers rule, or re-execed by require_root below — sudo's secure_path decides
# where a bare helper resolves, and a dev link (etc/sudoers.d/omarchy-dev-path)
# prepends a user-writable checkout bin/ to it. Every helper this script calls
# by bare name (dirname, install, tee, rm, nmcli, systemctl, awk) is a system
# tool, never an omarchy-* command, so pin PATH to trusted system directories
# and keep root from resolving one out of that checkout. The unprivileged
# wrapper phase keeps the caller's PATH so it can still find sudo/pkexec.
if (( EUID == 0 )); then
export PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin
fi
NM_DNS_CONF=/etc/NetworkManager/conf.d/20-omarchy-dns.conf
provider_from_arg() {
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# omarchy:summary=Check that a git URL names a repository, not a transport helper
# omarchy:args=<git-url>
# omarchy:hidden=true
set -euo pipefail
# git picks a remote helper -- an executable it runs at clone time -- out of a URL
# in exactly two shapes, and no others: `<helper>::<address>`, and
# `<scheme>://<address>` for any scheme git does not handle itself. A single
# colon is always scp-style ssh, and a bare path is always a path; neither can
# reach a helper. So constraining those two shapes covers the whole surface.
#
# The `::` shape is refused outright, because no helper reachable that way is one
# a theme or plugin URL has business naming, and `ext::` runs a shell command.
# The `://` shape cannot be refused the same way, since it is also how every
# legitimate URL arrives -- so it is allowlisted instead. The list is the
# transports git still connects itself, `git+ssh` and `ssh+git` included: those
# two are spelled like a helper but are read as plain ssh. `ext` and `fd` are
# left out deliberately -- git ships a helper for each, and `ext` runs whatever
# command the URL carries.
TRANSPORTS=(ssh git git+ssh ssh+git http https ftp ftps file)
fail() {
echo "omarchy-git-url-check: $*" >&2
exit 1
}
url="${1-}"
if [[ -z $url ]]; then
fail "a git URL is required"
fi
if [[ $url == -* || $url =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*:: ]]; then
fail "'$url' names a git option or transport helper, not a repository."
fi
if [[ $url =~ ^([A-Za-z0-9][A-Za-z0-9+.-]*):// ]]; then
scheme="${BASH_REMATCH[1]}"
for transport in "${TRANSPORTS[@]}"; do
if [[ $scheme == "$transport" ]]; then
exit 0
fi
done
fail "'$url' names the '$scheme' transport, which Omarchy does not clone from."
fi
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
# omarchy:summary=Match the Dell XPS 13 DX13260 that requires the sidecar amplifier workaround.
product_sku="${OMARCHY_DMI_PRODUCT_SKU:-/sys/class/dmi/id/product_sku}"
omarchy-hw-match "DX13260" &&
grep -qix "0E53" "$product_sku" 2>/dev/null
+8
View File
@@ -11,6 +11,14 @@ MONITOR_LUA="$HOME/.config/hypr/monitors.lua"
INTERNAL=$(omarchy-hyprland-monitor-laptop)
# INTERNAL is written into generated Lua and hyprctl eval/dispatch below, so a
# name that is not a plain connector string could execute on the next reload.
# Names come from hyprctl; a user-created headless output can carry anything.
if [[ -n $INTERNAL && ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "Refusing unsafe internal monitor name" >&2
exit 1
fi
valid_scale() {
[[ $1 =~ ^[0-9]+([.][0-9]+)?$ ]]
}
+7
View File
@@ -28,6 +28,13 @@ off() {
exit 1
fi
# The name is written into generated Lua below, so only a plain connector
# name may pass; anything else could execute on the next reload.
if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
omarchy-notification-send -g 󰍹 "Refusing unsafe monitor name"
exit 1
fi
if ! omarchy-hyprland-monitor-external-active; then
omarchy-notification-send -g 󰍹 "Can't disable the only active display"
exit 1
@@ -22,6 +22,15 @@ on() {
exit 1
fi
# Both names are written into generated Lua below, so only plain connector
# names may pass; a user-created headless output can carry any name.
for output in "$INTERNAL" "$EXTERNAL"; do
if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then
omarchy-notification-send -g 󰍹 "Refusing unsafe monitor name"
exit 1
fi
done
omarchy-hyprland-toggle $DISABLE_TOGGLE off
if omarchy-hyprland-toggle-disabled $TOGGLE; then
+8
View File
@@ -80,6 +80,14 @@ set_scale() {
local width="$(echo "$monitor_info" | jq -r '.width')"
local height="$(echo "$monitor_info" | jq -r '.height')"
local refresh_rate="$(echo "$monitor_info" | jq -r '.refreshRate')"
# active_monitor is written into the Lua string eval'd below, so only a plain
# connector name may pass; a hostile output name could execute otherwise.
if [[ ! $active_monitor =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "Refusing unsafe monitor name" >&2
exit 1
fi
local new_scale="$(clean_scale "$requested_scale" "$width" "$height")"
# GTK only honors integer GDK_SCALE values, so persist the nearest whole
# factor even when the monitor scale itself is fractional.
+11 -17
View File
@@ -6,9 +6,10 @@
set -e
setup_policy_directory() {
sudo mkdir -p "$1"
sudo chmod a+rw "$1"
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
setup_chromium_policy_directory() {
browser_policy_setup_dir "$1"
}
announce_browser_installed() {
@@ -23,13 +24,6 @@ copy_chromium_flags() {
omarchy-install-chromium-ytdlp
}
setup_firefox_preferences() {
local distribution_dir="$1"
setup_policy_directory "$distribution_dir"
sudo cp -f "$OMARCHY_PATH/default/firefox/policies.json" "$distribution_dir/policies.json"
}
setup_firefox_wayland() {
mkdir -p ~/.config/environment.d
echo "MOZ_ENABLE_WAYLAND=1" > ~/.config/environment.d/omarchy-firefox-wayland.conf
@@ -40,7 +34,7 @@ chromium)
echo "Installing Chromium..."
omarchy-pkg-add chromium
setup_policy_directory /etc/chromium/policies/managed
setup_chromium_policy_directory /etc/chromium/policies/managed
copy_chromium_flags ~/.config/chromium-flags.conf
omarchy-theme-set-browser
announce_browser_installed "Chromium"
@@ -49,7 +43,7 @@ chrome)
echo "Installing Chrome..."
omarchy-pkg-aur-add google-chrome || exit 1
setup_policy_directory /etc/opt/chrome/policies/managed
setup_chromium_policy_directory /etc/opt/chrome/policies/managed
copy_chromium_flags ~/.config/chrome-flags.conf
omarchy-theme-set-browser
announce_browser_installed "Chrome"
@@ -58,7 +52,7 @@ edge)
echo "Installing Edge..."
omarchy-pkg-aur-add microsoft-edge-stable-bin || exit 1
setup_policy_directory /etc/opt/edge/policies/managed
setup_chromium_policy_directory /etc/opt/edge/policies/managed
copy_chromium_flags ~/.config/microsoft-edge-stable-flags.conf
omarchy-theme-set-browser
announce_browser_installed "Edge"
@@ -67,7 +61,7 @@ brave)
echo "Installing Brave..."
omarchy-pkg-aur-add brave-bin || exit 1
setup_policy_directory /etc/brave/policies/managed
setup_chromium_policy_directory /etc/brave/policies/managed
copy_chromium_flags ~/.config/brave-flags.conf
omarchy-theme-set-browser
announce_browser_installed "Brave"
@@ -76,7 +70,7 @@ brave-origin)
echo "Installing Brave Origin..."
omarchy-pkg-aur-add brave-origin-bin || exit 1
setup_policy_directory /etc/brave/policies/managed
setup_chromium_policy_directory /etc/brave/policies/managed
copy_chromium_flags ~/.config/brave-origin-flags.conf
omarchy-theme-set-browser
announce_browser_installed "Brave Origin"
@@ -85,7 +79,7 @@ firefox)
echo "Installing Firefox..."
omarchy-pkg-add firefox || exit 1
setup_firefox_preferences /usr/lib/firefox/distribution
browser_policy_setup_firefox_distribution /usr/lib/firefox/distribution
setup_firefox_wayland
announce_browser_installed "Firefox"
;;
@@ -93,7 +87,7 @@ zen)
echo "Installing Zen..."
omarchy-pkg-aur-add zen-browser-bin || exit 1
setup_firefox_preferences /opt/zen-browser/distribution
browser_policy_setup_firefox_distribution /opt/zen-browser/distribution
setup_firefox_wayland
announce_browser_installed "Zen"
;;
-3
View File
@@ -12,13 +12,10 @@ if gum confirm "Are you sure you want to restore all preinstalled web apps, TUI
# Mirrors the list in omarchy-remove-preinstalls; both track omarchy-base.packages
if ! omarchy-pkg-add \
aether \
cliamp \
libreoffice-fresh \
xournalpp \
pinta \
obsidian \
obs-studio \
kdenlive \
moonlight-qt \
lazydocker \
+1 -1
View File
@@ -10,4 +10,4 @@ echo "Enabling ONCE background service..."
sudo systemctl enable --now once-background.service
echo -e "\nLaunching ONCE..."
once
sudo once
+7 -5
View File
@@ -9,10 +9,12 @@
# no prompt. lazydocker needs the root-owned Docker socket, so when the group is
# absent, gate that access behind a polkit prompt. If the user has opted into
# sudoless Docker (omarchy-setup-security-sudoless-docker), the socket is already
# reachable, so run lazydocker directly. pkexec sanitizes the environment, so
# carry TERM through for the TUI to render and run lazydocker from root's PATH.
if id -nG 2>/dev/null | grep -qw docker; then
exec lazydocker
else
# reachable, so run lazydocker directly — omarchy-sudo-docker answers that for
# this session, so the prompt stays until the reboot that grants the group.
# pkexec sanitizes the environment, so carry TERM through for the TUI to render
# and run lazydocker from root's PATH.
if omarchy-sudo-docker; then
exec pkexec /usr/bin/env TERM="${TERM:-xterm-256color}" lazydocker
else
exec lazydocker
fi
+47 -47
View File
@@ -338,10 +338,10 @@ static_bindings() {
# for interchangeably belong on one row.
alternative_chord_actions() {
cat <<'ACTIONS'
Close window
Calculator
Toggle scratchpad
Move window to scratchpad
关闭窗口
计算器
切换便签区
窗口移入便签区
ACTIONS
}
@@ -442,58 +442,58 @@ prioritize_entries() {
sub(/ \/ [^→]*→/, " →", line)
prio = 50
if (match(line, /Keybindings/)) prio = 0
if (match(line, /Omarchy menu/)) prio = 1
if (match(line, /Terminal/)) prio = 2
if (match(line, /Browser/) && !match(line, /Browser[[:space:]]*\(/) && !match(line, /SUPER SHIFT.*\+.*B.*→.*Browser/)) prio = 3
if (match(line, /File manager/) && !match(line, /File manager \(cwd\)/)) prio = 4
if (match(line, /快捷键/)) prio = 0
if (match(line, /Omarchy 菜单/)) prio = 1
if (match(line, /终端/)) prio = 2
if (match(line, /浏览器/) && !match(line, /浏览器(/) && !match(line, /SUPER SHIFT.*\+.*B.*→.*浏览器/)) prio = 3
if (match(line, /文件管理器/) && !match(line, /文件管理器(当前目录)/)) prio = 4
if (match(line, /Launch apps/)) prio = 5
if (match(line, /System menu/)) prio = 6
if (match(line, /Theme menu/)) prio = 7
if (match(line, /Full screen/)) prio = 8
if (match(line, /Full width/)) prio = 9
if (match(line, /Close window/)) prio = 10
if (match(line, /Close all windows/)) prio = 11
if (match(line, /Lock system/)) prio = 12
if (match(line, /Toggle window floating/)) prio = 13
if (match(line, /Toggle window split/)) prio = 14
if (match(line, /Pop window/)) prio = 15
if (match(line, /Universal/)) prio = 16
if (match(line, /Clipboard/)) prio = 17
if (match(line, /系统菜单/)) prio = 6
if (match(line, /主题菜单/)) prio = 7
if (match(line, /全屏/)) prio = 8
if (match(line, /全宽/)) prio = 9
if (match(line, /关闭窗口/)) prio = 10
if (match(line, /关闭所有窗口/)) prio = 11
if (match(line, /锁定系统/)) prio = 12
if (match(line, /切换窗口浮动/)) prio = 13
if (match(line, /切换窗口分割/)) prio = 14
if (match(line, /弹出窗口/)) prio = 15
if (match(line, /通用/)) prio = 16
if (match(line, /剪贴板/)) prio = 17
if (match(line, /Audio controls/)) prio = 18
if (match(line, /Bluetooth controls/)) prio = 19
if (match(line, /Wifi controls/)) prio = 20
if (match(line, /Emojis/)) prio = 21
if (match(line, /Color picker/)) prio = 22
if (match(line, /Screenshot/)) prio = 23
if (match(line, /Screenrecording/)) prio = 24
if (match(line, /表情/)) prio = 21
if (match(line, /取色器/)) prio = 22
if (match(line, /截图/)) prio = 23
if (match(line, /录屏/)) prio = 24
if (match(line, /Tmux/)) prio = 25
if (match(line, /Herdr/)) prio = 26
if (match(line, /SUPER SHIFT.*\+.*B.*→.*Browser/)) prio = 27
if (match(line, /File manager \(cwd\)/)) prio = 28
if (match(line, /(Switch|Next|Former|Previous).*workspace/)) prio = 29
if (match(line, /Move window to workspace/)) prio = 30
if (match(line, /Move window silently to workspace/)) prio = 31
if (match(line, /Swap window/)) prio = 32
if (match(line, /Focus/)) prio = 33
if (match(line, /Move window$/)) prio = 34
if (match(line, /Resize window/)) prio = 35
if (match(line, /Expand window/)) prio = 36
if (match(line, /Shrink window/)) prio = 37
if (match(line, /scratchpad/)) prio = 38
if (match(line, /notification/)) prio = 39
if (match(line, /Toggle window transparency/)) prio = 40
if (match(line, /Toggle workspace gaps/)) prio = 41
if (match(line, /Toggle nightlight/)) prio = 42
if (match(line, /Toggle locking/)) prio = 43
if (match(line, /group/)) prio = 94
if (match(line, /Scroll active workspace/)) prio = 95
if (match(line, /SUPER SHIFT.*\+.*B.*→.*浏览器/)) prio = 27
if (match(line, /文件管理器(当前目录)/)) prio = 28
if (match(line, /(切换到|下一|上一|上次)工作区/)) prio = 29
if (match(line, /窗口移到工作区/)) prio = 30
if (match(line, /窗口静默移到工作区/)) prio = 31
if (match(line, /互换/)) prio = 32
if (match(line, /聚焦/)) prio = 33
if (match(line, /移动窗口$/)) prio = 34
if (match(line, /调整窗口大小/)) prio = 35
if (match(line, /扩展窗口/)) prio = 36
if (match(line, /收缩窗口/)) prio = 37
if (match(line, /便签区/)) prio = 38
if (match(line, /通知/)) prio = 39
if (match(line, /切换窗口透明/)) prio = 40
if (match(line, /切换窗口间距/)) prio = 41
if (match(line, /切换夜间模式/)) prio = 42
if (match(line, /切换空闲锁定/)) prio = 43
if (match(line, /组内|分组/)) prio = 94
if (match(line, /工作区向(前|后)滚动/)) prio = 95
if (match(line, /Cycle to/)) prio = 96
if (match(line, /Reveal active/)) prio = 97
if (match(line, /置顶显示/)) prio = 97
if (match(line, /Apple Display/)) prio = 98
if (match(line, /XF86/)) prio = 99
if (match(line, /Tmux keybindings/)) prio = 100
if (match(line, /Herdr keybindings/)) prio = 101
if (match(line, /Tmux 快捷键/)) prio = 100
if (match(line, /Herdr 快捷键/)) prio = 101
# print "priority<TAB>record"
printf "%d\t%s\n", prio, $0
+3
View File
@@ -96,6 +96,9 @@ while IFS=$'\t' read -r name file marker; do
fi
done < <(migration_entries)
# Here (not omarchy-update) so the first update from a pre-cn package runs them
OMARCHY_PATH="$OMARCHY_PATH" omarchy-cn-migrate
# Clear a login-time notification the user left sitting there and then resolved
# by running migrations some other way. The substring matches both the current
# and legacy notification titles.
+7
View File
@@ -93,6 +93,13 @@ if [[ -z $url ]]; then
[[ -n $url ]] || fail "a git URL is required"
fi
# Refuse a URL that names a git option or a transport helper before cloning, so
# an untrusted URL cannot run a command before the plugin is validated or
# enabled. The check is shared with omarchy-theme-install and explains itself; a
# missing checker leaves this non-zero, which refuses the URL rather than
# cloning it.
omarchy-git-url-check "$url" || exit 1
if (( ! ASSUME_YES )); then
cat >&2 <<WARN
+7 -1
View File
@@ -186,7 +186,7 @@ tips=(
"Share files with phones and laptops using LocalSend"
"Turn any website into an app with Install > Web App in the menu"
"Super + Return opens a terminal, Super + Shift + Return the browser"
"Edit images with Pinta, videos with Kdenlive, docs with LibreOffice"
"Edit images with Pinta and videos with Kdenlive"
"Super + Ctrl + Print grabs text off the screen with OCR"
"Print takes a screenshot, Alt + Print records the screen"
"Set a reminder with Super + Ctrl + R"
@@ -742,6 +742,12 @@ create_user() {
# for specific commands), and a duplicate grant is harmless.
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
chmod 440 /etc/sudoers.d/00-omarchy-wheel
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
[[ -d $dir || -L $dir ]] || continue
browser_policy_setup_dir "$dir"
done
}
install_authorized_keys() {
+1 -2
View File
@@ -11,7 +11,7 @@ Usage: omarchy finalize user [--force] [--first-install]
Runs the per-user setup steps that /etc/skel can't seed:
dev-aware skill symlinks, xdg-user-dirs + gtk bookmarks (need \$HOME),
default browser/mailto, and install/user/all.sh.
default browser, and install/user/all.sh.
For shipped configs see /etc/skel (new users) and omarchy-reinstall-configs
(existing users explicitly resyncing).
@@ -110,7 +110,6 @@ source "$OMARCHY_INSTALL/user/all.sh"
omarchy-refresh-applications
env -u BROWSER xdg-settings set default-web-browser chromium.desktop
xdg-mime default HEY.desktop x-scheme-handler/mailto
if (( first_install )); then
mkdir -p "$state_dir/migrations"
-3
View File
@@ -18,13 +18,10 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w
~/.local/bin/omp ~/.local/bin/ori ~/.local/bin/grok ~/.local/bin/crush ~/.local/bin/ghui ~/.local/bin/hunk
omarchy-pkg-drop \
aether \
cliamp \
libreoffice-fresh \
xournalpp \
pinta \
obsidian \
obs-studio \
kdenlive \
moonlight-qt \
lazydocker \
+7 -2
View File
@@ -24,9 +24,14 @@ echo -e "\e[32mRemoving FIDO2 device from authentication.\n\e[0m"
remove_pam_config
if [[ -d /etc/fido2 ]]; then
authdir=/etc/fido2
# -d follows symlinks, so a dangling link at /etc/fido2 would survive this and
# a later setup would install the authfile through it. rm -rf on a symlink
# removes the link itself, never the directory it points at.
if [[ -e $authdir || -L $authdir ]]; then
echo "Removing FIDO2 configuration..."
sudo rm -rf /etc/fido2
sudo rm -rf "$authdir"
fi
echo "Removing FIDO2 packages..."
+17 -8
View File
@@ -5,7 +5,10 @@
set -e
if ! id -nG "$USER" 2>/dev/null | grep -qw docker; then
# Ask about the configured groups, not this session's: right after enabling,
# sudoless Docker is on for the account even though the running session still
# needs a prompt, and this command is what turns it back off.
if omarchy-sudo-docker --configured; then
echo "Sudoless Docker is not enabled: $USER is not in the docker group."
exit 0
fi
@@ -13,13 +16,19 @@ fi
echo "Removing $USER from the docker group..."
sudo gpasswd -d "$USER" docker >/dev/null
# Group membership is fixed at login, so the running session keeps its docker
# access until it ends. Flag a reboot so omarchy-update-restart prompts for one
# (and the bar shows it pending); a plain log out and back in works too.
# Group membership is only re-read by a fresh session, and in practice logging
# out or newgrp isn't enough — only a reboot reliably applies it. Record it so a
# later `omarchy update` still prompts (omarchy-update-restart reads this), then
# offer to do it now.
omarchy-state set reboot-required
echo ""
echo "Sudoless Docker DISABLED. Reboot (or log out and back in) for the change to take effect."
echo "Docker access now goes through a polkit/sudo prompt again: the Docker TUI"
echo "(Super + Shift + D) and the Windows VM will ask when they need it, and the"
echo "plain 'docker' CLI runs under sudo."
echo "Sudoless Docker DISABLED. Docker access goes through a polkit/sudo prompt"
echo "again: the Docker TUI (Super + Shift + D) and the Windows VM ask when they"
echo "need it, and the plain 'docker' CLI runs under sudo. It takes effect after a reboot."
echo ""
# The migration reuses this command during 'omarchy update' and defers the
# reboot to omarchy-update-restart, so it doesn't cut the update short.
if [[ -z ${OMARCHY_DEFER_REBOOT:-} ]] && gum confirm "Reboot now to apply?"; then
omarchy-system-reboot
fi
+71 -4
View File
@@ -4,6 +4,7 @@
# omarchy:requires-sudo=true
set -e
set -o pipefail
check_fido2_hardware() {
@@ -50,13 +51,79 @@ if ! check_fido2_hardware; then
fi
# Create the pamu2fcfg file
if [[ ! -f /etc/fido2/fido2 ]]; then
sudo mkdir -p /etc/fido2
authdir=/etc/fido2
authfile=/etc/fido2/fido2
# install -d follows a symlink here and applies the mode and ownership to
# whatever it points at, so the credential would be staged and published inside
# the link target and that directory reopened to root:root 755. This is the
# threat omarchy-remove-security-fido2 already names on its side.
if [[ -L $authdir || ( -e $authdir && ! -d $authdir ) ]]; then
echo -e "\e[31m\n$authdir is not a FIDO2 configuration directory.\e[0m"
echo "Run omarchy-remove-security-fido2 first, then set FIDO2 up again."
exit 1
fi
# -f follows symlinks, so the already-registered check below reads a symlinked
# authfile as a registration and leaves it in place, and is false for a
# directory, so it tries to register over one. Only a regular file is a valid
# pam_u2f authfile.
if [[ -L $authfile || ( -e $authfile && ! -f $authfile ) ]]; then
echo -e "\e[31m\n$authfile is not a FIDO2 registration file.\e[0m"
echo "Run omarchy-remove-security-fido2 first, then set FIDO2 up again."
exit 1
fi
if [[ ! -f $authfile ]]; then
sudo install -d -m 755 -o root -g root "$authdir"
echo -e "\e[32m\nLet's setup your device by confirming on the device now.\e[0m"
echo -e "Touch your FIDO2 key when it lights up...\n"
if pamu2fcfg >/tmp/fido2; then
sudo mv /tmp/fido2 /etc/fido2/fido2
# A unique sibling created by root cannot be replaced by another process
# running as this user. Stream pamu2fcfg into it instead of asking root to
# reopen a caller-owned path: an observed temporary name could otherwise be
# replaced with a symlink before the privileged copy. The final rename is
# atomic, and -T refuses a directory at the destination. Mode 644 keeps the
# root-owned global authfile readable when pam_u2f uses openasuser; only root
# can still rewrite it.
stage=""
# mktemp's output is an operand for four privileged commands below, one of
# them an rm. Take only the name this script asked for rather than whatever
# came back on stdout.
safe_stage_path() {
local candidate=$1
local prefix="$authfile.new."
local suffix
[[ $candidate == "$prefix"* ]] || return 1
suffix=${candidate#"$prefix"}
[[ $suffix =~ ^[[:alnum:]]{6}$ ]]
}
cleanup_stage() {
local status=$?
if safe_stage_path "$stage"; then
sudo rm -f -- "$stage" || true
fi
return "$status"
}
trap cleanup_stage EXIT
stage=$(sudo mktemp "$authfile.new.XXXXXX")
if ! safe_stage_path "$stage" || [[ ! -f $stage || -L $stage ]]; then
echo -e "\e[31m\nCould not create a safe staging file beside $authfile.\e[0m"
exit 1
fi
if pamu2fcfg | sudo tee "$stage" >/dev/null && [[ -s $stage ]]; then
sudo chmod 644 "$stage"
sudo mv -Tf "$stage" "$authfile"
stage=""
trap - EXIT
echo -e "\e[32mFIDO2 device registered successfully!\e[0m"
else
echo -e "\e[31m\nFIDO2 registration failed. Please try again.\e[0m"
+15 -7
View File
@@ -5,7 +5,9 @@
set -e
if id -nG "$USER" 2>/dev/null | grep -qw docker; then
# Ask about the configured groups, not this session's: once enabled it stays
# enabled for the account even before the reboot that lets this session use it.
if ! omarchy-sudo-docker --configured; then
echo "Sudoless Docker is already enabled: $USER is in the docker group."
echo "To disable it again, run: omarchy-remove-security-sudoless-docker"
exit 0
@@ -28,14 +30,20 @@ echo ""
if gum confirm "Enable sudoless Docker? This gives anything running as you passwordless root."; then
sudo usermod -aG docker "$USER"
# Group membership is fixed at login, so docker won't be reachable without a
# prompt until the session restarts. Flag a reboot so omarchy-update-restart
# prompts for one (and the bar shows it pending).
# A new docker group membership is only picked up by a fresh session, and in
# practice logging out or newgrp isn't enough — only a reboot reliably applies
# it. Record it so a later `omarchy update` still prompts
# (omarchy-update-restart reads this), then offer to do it now.
omarchy-state set reboot-required
echo ""
echo "Sudoless Docker ENABLED. Reboot, or log out and back in (or run 'newgrp docker'),"
echo "for the new group membership to take effect."
echo "To disable it again, run: omarchy-remove-security-sudoless-docker"
echo "Sudoless Docker ENABLED. It takes effect after a reboot."
echo "To disable it again: Setup > Security > Sudoless Docker."
echo ""
# The migration reuses this command during 'omarchy update' and defers the
# reboot to omarchy-update-restart, so it doesn't cut the update short.
if [[ -z ${OMARCHY_DEFER_REBOOT:-} ]] && gum confirm "Reboot now to apply?"; then
omarchy-system-reboot
fi
else
echo "Aborted. No changes made. Docker access still goes through a prompt."
fi
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
# omarchy:summary=Succeed when Docker needs sudo, fail when it can be used directly
# omarchy:args=[--configured]
# omarchy:examples=omarchy-sudo-docker && echo "needs sudo" | omarchy-sudo-docker --configured
# omarchy:hidden=true
# The docker group is root-equivalent, so Omarchy leaves users out of it by
# default and reaches the daemon through a prompt instead. Everything that has
# to make that choice asks here rather than testing group membership itself.
#
# Two questions, because they have different answers between toggling sudoless
# Docker and the reboot that applies it (group membership is fixed when the
# session is created):
#
# (default) Does Docker need sudo *right now*? Answered by whether this
# process can actually reach the socket, which is what decides
# if a command must elevate. Still true in the window after
# sudoless Docker is enabled but before the reboot.
# --configured Will it need sudo once the account's groups take effect?
# Answered from the account's configured groups, so the menu
# offers the toggle that can actually change state.
#
# Succeeds (exit 0) when sudo is needed, so it reads as `if omarchy-sudo-docker`.
DOCKER_SOCKET="${OMARCHY_DOCKER_SOCKET:-/var/run/docker.sock}"
case "${1:-}" in
--configured)
# An account in the docker group will not need sudo after the next login.
id -nG "$USER" 2>/dev/null | grep -qw docker && exit 1
exit 0
;;
"")
# A socket we can write is a daemon we can drive without elevating. A missing
# socket counts as needing sudo: reaching it means starting it as root anyway.
[[ -w $DOCKER_SOCKET ]] && exit 1
exit 0
;;
*)
echo "Usage: omarchy-sudo-docker [--configured]" >&2
exit 2
;;
esac
+4 -8
View File
@@ -16,14 +16,10 @@ if [[ -z $REPO_URL ]]; then
exit 1
fi
# git reads a leading dash as an option, and `<helper>::<address>` as a remote
# helper to run. The helper name is a bare word at the very start, which is what
# this matches; an scp-style IPv6 host such as git@[2001:db8::1]:org/repo.git
# carries `::` too and must still clone.
if [[ $REPO_URL == -* || $REPO_URL =~ ^[A-Za-z0-9][A-Za-z0-9+.-]*:: ]]; then
echo "Error: '$REPO_URL' names a git option or transport helper, not a repository."
exit 1
fi
# Refuse a URL that names a git option or a transport helper before cloning. The
# check is shared with omarchy-plugin-add and explains itself; a missing checker
# leaves this non-zero, which refuses the URL rather than cloning it.
omarchy-git-url-check "$REPO_URL" || exit 1
THEMES_DIR="$HOME/.config/omarchy/themes"
+9 -19
View File
@@ -3,23 +3,15 @@
# omarchy:summary=Apply the current theme color to Chromium, Chrome, Edge, and Brave
# omarchy:hidden=true
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
CHROMIUM_THEME=$HOME/.local/state/omarchy/current/theme/chromium.theme
THEME_HEX_COLOR=$BROWSER_POLICY_DEFAULT_COLOR
if [[ -f $CHROMIUM_THEME ]]; then
THEME_RGB_COLOR=$(<$CHROMIUM_THEME)
THEME_HEX_COLOR=$(printf '#%02x%02x%02x' ${THEME_RGB_COLOR//,/ })
else
# Use a default, neutral grey if theme doesn't have a color
THEME_HEX_COLOR="#1c2027"
THEME_HEX_COLOR=$(browser_policy_theme_hex "$(<$CHROMIUM_THEME)")
fi
set_browser_policy() {
local policy_dir="$1"
[[ -d $policy_dir ]] || return
echo "{\"BrowserThemeColor\": \"$THEME_HEX_COLOR\", \"BrowserColorScheme\": \"device\"}" | tee "$policy_dir/color.json" >/dev/null
}
refresh_running_browser() {
local process="$1"
local command="$2"
@@ -30,17 +22,15 @@ refresh_running_browser() {
fi
}
set_browser_policy /etc/chromium/policies/managed
failed=0
omarchy-theme-set-browser-policy "${THEME_HEX_COLOR#\#}" || failed=1
refresh_running_browser chromium chromium
set_browser_policy /etc/opt/chrome/policies/managed
refresh_running_browser chrome google-chrome-stable || refresh_running_browser chrome google-chrome
set_browser_policy /etc/opt/edge/policies/managed
refresh_running_browser msedge microsoft-edge-stable
set_browser_policy /etc/brave/policies/managed
refresh_running_browser brave brave
# Match on the binary path: the running process is named plain "brave", and a
# bare -f brave-origin pattern would also match the installer's own terminal.
refresh_running_browser /opt/brave-origin-bin/ brave-origin -f
exit "$failed"

Some files were not shown because too many files have changed in this diff Show More