50 Commits
Author SHA1 Message Date
Ryan HughesandGitHub 158e8cfb3a Merge pull request #8419 from AFOliveira/security/windows-vm-mount-boundary
[codex] Secure Windows VM host mounts
2026-08-29 19:31:39 -04:00
Ryan HughesandGitHub e229927671 Merge pull request #8934 from ErikMelton/security/plymouth-publication-race
Secure Plymouth and SDDM asset publication
2026-08-29 18:45:37 -04:00
Ryan Hughes 70e79c40ae Harden Plymouth and SDDM reset publication 2026-08-29 18:19:51 -04:00
Ryan HughesandGitHub c720f0b981 Merge pull request #8951 from omacom/cups-browsed-temporarily-removed
Temporarily remove automatic printer discovery
2026-08-29 15:38:23 -04:00
David Heinemeier HanssonandGitHub 5236f4426c Merge pull request #8203 from hjanuschka/fix-chromium-first-run-eula
Skip Chromium's new first-run EULA
2026-08-29 21:28:42 +02:00
Ryan Hughes 2002fb35ed Test installed CUPS state directly 2026-08-29 15:27:16 -04:00
David Heinemeier HanssonandClaude Opus 5 e47784be11 Name the dev-link authorization when it is the thing that failed
Validating /etc/omarchy.conf walks its parent chain to /, and that walk leaves
its own subject behind in the global failure_context. The comparison that
follows -- the one deciding whether the authorization actually names this
checkout -- sets no context of its own, so an unauthorized development tree
refused with:

  refusing to publish: directory / (must be root-owned and not group- or
  world-writable) failed validation

naming a directory that had just passed, and pointing the reader at a
filesystem problem that is not there. Seen on a worker VM running an
unauthorized checkout, where / is 0555 root-owned.

The hint line underneath was already right, which is what kept this from being
worse. Restore the context before the comparison so the first line agrees with
it, and assert both halves: that the refusal names the authorization, and that
it does not blame the root directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115LngksSpXLD9NSXBEP3ki
2026-08-29 21:03:02 +02:00
Ryan Hughes 96d5682460 Fix cups-browsed removal migration 2026-08-29 14:58:33 -04:00
David Heinemeier Hansson bf20c94ea0 Merge quattro into the Chromium first-run EULA branch
Quattro stopped making the Chromium managed-policy directory world-writable while this branch was open, and the block it deleted from the theme install leaf sat directly above the comment this branch rewrites, so the two edits landed in one hunk. The resolution keeps the hardening — the policy directory is set up through install/config/browser-policy.sh now — along with the first-run seed and the comment that names both things the seed does.
2026-08-29 20:55:09 +02:00
Ryan Hughes bb5b178e5f Remove unused cups-browsed install override 2026-08-29 14:24:05 -04:00
David Heinemeier HanssonandClaude Opus 5 1b92b7f5be Correct the dev-link help now that Plymouth follows the link
omarchy-dev-link told people /usr/share/plymouth was not covered by a link
and to reach for omarchy-dev-pkg-test. Since the publisher authorizes a
dev-linked checkout out of the root-owned /etc/omarchy.conf, the Plymouth and
SDDM themes are republished from the checkout like every other
$OMARCHY_PATH-resolved tree, so the help was left describing the old
behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 18:03:57 +02:00
Erik Melton aac1b009e4 Cover non-regular Plymouth logo descriptors 2026-08-29 17:47:42 +02:00
David Heinemeier HanssonandCodex XHigh 1dbc7d5bce Close each discovered queue before inspecting or removing it
Checking a queue for jobs and then deleting it leaves a window in between, and the sudo that does the deleting can sit at a password prompt for as long as someone takes to type. A job submitted in that window was cancelled by a deletion that had already decided the queue was empty. cupsreject closes the queue to new work first, which also stops more jobs piling onto one that is being left behind and can no longer route them. It comes from the cups package, which stays.

What a queue with jobs on it means is now said rather than implied: whatever is already at the printer finishes, whatever is still waiting cannot be routed with the daemon gone, and the person who owns them has to cancel what is left.

A queue that another administrator removed while this was running is the outcome wanted, not a failure to keep the package installed for, so a deletion that fails is checked against whether the destination is still there.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-29 17:44:31 +02:00
David Heinemeier HanssonandClaude Opus 5 d67a7c00bc Cover the ownership and mode checks the publisher rests on
Mutating each root-side control one at a time showed three that no test could
see: a destination directory root does not own, a single user-owned asset
inside an otherwise root-owned packaged directory, and an asset left group- or
world-writable by its own mode. Deleting any of the three left the suite green,
because the existing cases mark a whole tree untrusted and are caught by the
directory check before the per-file one is reached.

The harness already had the hook for the ownership pair: TEST_UNTRUSTED_SOURCE
makes the stat shim report a chosen prefix as uid 1000, so those two only need
it pointed at a destination directory and at a single file rather than at a
whole tree. A mode has to be real, so that case stages a copy of the packaged
tree the shim reports as root-owned and loosens one asset in it.

The empty logo is refused by the destination size bound rather than the
caller-side one, so that case pins the behaviour without isolating the check;
the two bounds are exactly redundant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115LngksSpXLD9NSXBEP3ki
2026-08-29 17:39:33 +02:00
David Heinemeier HanssonandCodex XHigh 24c18df5b7 Temporarily remove automatic printer discovery
cups-browsed is the daemon that watches the network and creates print queues by itself. Hardening it took a root daemon with a predictable cache down to a confined service account, but a daemon that turns anything advertising itself on the network into a print queue is a lot of exposure for a convenience, so it comes out of the default install while that is reworked. Only the discovery half: CUPS itself stays and printing keeps working, with each printer added by hand in Print Settings.

The migration disables the unit before removing the package because that is the only order that works: pacman deletes the unit file but not the enable symlink, and once the unit is gone systemd can no longer resolve it by name to clean that up.

It then removes the queues discovery generated. cups-browsed keeps those when it stops, since KeepGeneratedQueuesOnShutdown defaults to Yes, and they route through its own implicitclass backend, which goes with the package, so they cannot print again. Idle ones go. A queue with jobs on it is left alone and named: implicitclass only needs cups-browsed to choose a destination, so a job already past that point finishes on its own, and deleting the queue would abort it. One printer's job does not hold up the removal. A printer added by hand has an ipp:// or usb:// device and is left where it is.

A queue whose jobs cannot be asked about is left alone rather than assumed idle, including one named so that lpstat would misread it -- "all" is its word for every destination, and a leading dash or a comma reads as another option or a list.

Where CUPS does not answer at all, or a queue will not delete, discovery is still stopped but the package stays and no marker is written. omarchy-migrate records a migration for the user as soon as it exits zero, so that is where the machine stays until someone removes the package by hand, and the message says so rather than implying a retry.

The queue list is read under LC_ALL=C because lpstat translates "device for", and captured rather than piped, so a cupsd it cannot reach is reported instead of reading like a machine with nothing to clean up.

It removes with plain pacman -R rather than omarchy-pkg-drop, which passes -n and would discard /etc/cups/cups-browsed.conf instead of keeping it as a .pacsave. A removal meant to be temporary should not delete the machine's copy of its own configuration. Without -s either, so it only ever removes the package it names: sweeping newly unneeded dependencies is nothing today, but it is not a promise a rolling dependency graph can keep.

Queue names come off the network, since cups-browsed names its queues after what the printer advertised. CUPS allows every printable character but space, tab, / and #, and lpstat and lpadmin take a destination as an option value, so a name with a leading dash or a comma is reported rather than passed to them and guessed at.

Migration state is per user, so a machine-wide marker records the one removal. Without it, an account whose first migration run came after someone deliberately reinstalled discovery would quietly take it back out again.

The install-time override for cups-browsed.conf now waits for cups-browsed rather than for CUPS. Guarding it on a file CUPS still ships would write a configuration file for a package nothing installed, and pacman would later land the package's own copy beside it as a .pacnew.

The hardened configuration stays in the tree. omarchy-settings still ships the cups-browsed.conf override, the sysusers account and the service drop-in, so they are what discovery returns onto.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-29 17:13:11 +02:00
David Heinemeier HanssonandCodex XHigh e9ba17e52e Keep the explanation for the Chromium color scheme defaults
The comment above the seed was the only thing recording that color_scheme and color_scheme2 are both zero in order to follow system appearance rather than force dark. Generalizing it to "first-run defaults" left two magic numbers with nothing to explain them, so the next person touching an unrelated first-run setting has no way to tell that changing them regresses theme following. Name both things the seed does.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-29 17:13:06 +02:00
David Heinemeier HanssonandCodex XHigh eb76684c60 Retrofit the Chromium first-run EULA opt-out onto existing installs
Chromium 151 flipped MasterPrefs::eula_required from false to true, so a first run with no seed now stops on a blank terms-of-service dialog before the browser opens. The opt-out is written in two places that each run exactly once: the install leaf that runs during ISO finalization, and the one-time 3.x upgrade. A machine already on Quattro runs neither again, so it keeps the old seed and still meets the dialog the first time anyone launches Chromium, including from every user account created after the install.

The migration writes the same seed those two paths write. It compares before writing so the second user on a shared machine no-ops rather than repeating a machine-wide repair, and the literal is duplicated rather than sourced because a migration repairs the state of its own moment and must not drift when the seed later changes.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-29 17:13:06 +02:00
David Heinemeier HanssonandGitHub 56fbaf4689 Merge pull request #8162 from jmwall/fix/windows-vm-opacity
Keep the Windows VM display fully opaque
2026-08-29 17:10:14 +02:00
Erik Melton 363db1f569 Keep Plymouth publishing working in dev mode 2026-08-29 16:19:31 +02:00
Erik MeltonandAfonso Oliveira 11fa6b9809 Race Windows VM mount sources concurrently 2026-08-29 15:15:51 +01:00
Erik MeltonandAfonso Oliveira bf10b75150 Protect the Windows VM web console 2026-08-29 15:15:51 +01:00
079d116511 Cover the Plymouth parent-chain walk and guard its test harness
Deleting the whole ancestor walk from validate_trusted_directory and checking
only the immediate parent left the suite green, so the invariant the design
rests on had no coverage: a writable ancestor lets an attacker swap a validated
directory out from under the leaf. Reject a run whose destination grandparent
is world-writable while the destination itself is pristine.

The harness also rewrites the root script's fixed /usr/share paths by string
substitution. A drifted string silently no-ops that rewrite and would point the
simulation at the real system tree, so assert each substitution landed. Assert
the scratch directory exists too: a failed mktemp left it empty and the suite
carried on building paths from it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-29 16:01:01 +02:00
David Heinemeier HanssonandClaude Opus 5 d3b7810a76 Say why the privileged Plymouth transaction refused
Every check inside the root shell is a bare [[ ]] or (( )) assertion that
aborts under set -e, so a refusal exited with status 1 and no output at all.
The floating-terminal wrapper then printed its green "Done!" for any status
but 130, so a failed boot-theme change read as a success.

The refusal a working machine actually hits is omarchy dev link, which points
OMARCHY_PATH at a checkout the desktop user owns. Name that case outright and
point at omarchy dev unlink; report every other rejection through an ERR trap
that names what failed validation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 16:00:50 +02:00
Erik Melton 05fb36d3cc Build Plymouth assets in a root-owned stage 2026-08-29 15:05:13 +02:00
Erik Melton a465dfa681 Use the runtime path for Plymouth refresh 2026-08-29 14:45:17 +02:00
20a23b8c16 [Security] Complete Plymouth publication coverage
Keep SDDM color substitution collision-free for White themes, based on the fix proposed in #8469.

Co-authored-by: itz4blitz <itz4blitz@users.noreply.github.com>
2026-08-29 14:45:17 +02:00
Afonso OliveiraandErik Melton 0f15e2330f [Security] Harden Plymouth asset publication 2026-08-29 14:45:17 +02:00
Afonso OliveiraandErik Melton 2b91fdc0d3 [Security] Pin Plymouth asset reads before elevation 2026-08-29 14:44:37 +02:00
Ryan Hughes 0b3f1b7ead Merge pull request #8496 from Chessing234/security/webapp-http-only 2026-08-29 03:32:08 -04:00
Ryan Hughes f20bf0a21b Merge pull request #8473 from bastidotnet/fix/webapp-desktop-value-escaping 2026-08-29 03:27:39 -04:00
Ryan HughesandGitHub 9da8824098 Merge pull request #8416 from mdisec/theme-name-shell-syntax
Refuse a theme name that is shell syntax, and quote the one the unlock picker returns
2026-08-29 03:19:45 -04:00
Ryan HughesandGitHub 169ad00a84 Merge pull request #8627 from mdisec/security/harden-cups-browsed
Harden CUPS printer discovery
2026-08-29 02:19:53 -04:00
Ryan Hughes 74997fd523 Ship CUPS authorization through settings package 2026-08-29 02:18:44 -04:00
Afonso Oliveira 4fc14173b7 [Security] Add Windows VM boundary race regressions 2026-08-28 19:37:04 +01:00
Afonso Oliveira a165185a3f [Security] Complete Windows VM mount hardening 2026-08-27 23:53:44 +01:00
9382410026 Validate the web app URL before anything fetches it
The scheme check ran after the interactive branch, which had already handed the URL to curl: a refused `file://` or `ftp://` URL was dereferenced first, the whole string went out to Google's favicon endpoint in a `domain=` query, and a fetch that happened to succeed left an orphan icon and a rebuilt icon cache behind. Validating immediately after normalization puts the refusal ahead of every dereference in both branches.

A leading space also kept the URL out of the scheme test entirely. `normalize_webapp_url` saw no scheme, prefixed it, and ` file:///etc/passwd` became `https:// file:///etc/passwd`, which passes `^https?://`. The desktop `Exec` field is unquoted, so it splits back into two arguments, and `omarchy-launch-webapp` forwards everything after the first to the browser, where a bare argument is another URL to open. Refusing whitespace closes that without touching desktop-entry escaping.

Scheme comparison is case-insensitive because schemes are: `HTTPS://example.com` installed before this check existed and has no reason to stop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 20:10:15 +02:00
b07374f03c Reserve the cups-browsed account name at install
The username prompt already refuses the service accounts a desktop user must not claim, cups and lp among them. A user who took cups-browsed would get a primary group of that name, and the CUPS authorization written here puts that group in SystemGroup, handing that desktop user the passwordless administration the rest of this change removes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 19:55:36 +02:00
68fc0cf6e6 Match the CUPS directives being rewritten the way cupsd reads them
cupsd compares directive names with _cups_strcasecmp, so a hand-edited "systemgroup sys root wheel" is live configuration, but matching $1 against the canonical spelling skipped it and appended a second directive at the end of the file. parse_groups accumulates the groups of every SystemGroup directive it reads rather than replacing them, so both lines took effect and wheel kept the passwordless administration this is meant to remove, with the migration reporting success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 19:55:36 +02:00
David Heinemeier HanssonandCodex XHigh 2b923cf5bd Pin the theme name check to C, and read a colon before any slash as scp-style
A bracket range is collated, not ASCII, so `[a-z]` admits `é` under
en_US.UTF-8 and refuses it under C: the same theme URL installed on one
desktop and was refused on the next, and the manual's stated set was only
true in the C locale. Pinning the comparison makes the written set the
shipped one everywhere.

git reads a URL as scp-style when a colon appears before any slash, so the
path after it need not contain one. Requiring a slash left the prefix on
`git@host:omarchy-blue-theme.git` and derived the name
`git@host:omarchy-blue`, which the allowlist then refuses -- a repo that
clones fine could no longer be installed at all.

Co-Authored-By: Codex XHigh <codex@openai.com>
2026-08-27 19:52:37 +02:00
521f1ae9ac Resume cups-browsed on whether it is enabled, not on whether it was running
The migration recomputed whether cups-browsed was active at the start of every run, after an earlier run may already have stopped it. A run interrupted between that stop and the completion marker left the service down, and the retry that followed read it as inactive, skipped the restart, and wrote the marker anyway: printer discovery stayed off until the next reboot with no migration left to bring it back. Keying the restart to whether the unit is enabled survives the interruption, and reports not-enabled for a unit the user masked or disabled, which restarting would fail on and abort the migration short of its marker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 19:47:10 +02:00
c2587dff08 Escape .desktop values with parameter expansion, not sed
GNU sed's N auto-prints the pattern space and exits at end of input, so the
`:a;N;$!ba` slurp skipped every following s/// for a value with no newline in
it. That is every value except the injection attempt the escaping exists to
stop, so the Exec quoting's own backslashes were written to the file unescaped
and GLib refused to load the entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex GPT-5.6-sol (xhigh) <noreply@openai.com>
2026-08-27 19:45:45 +02:00
Mehmet InceandDaybreak Blue 5c336885d2 Harden CUPS printer discovery
Run cups-browsed as a locked service account with a dedicated cache and a focused systemd sandbox. Restrict automatic queues to driverless IPP printers, remove wheel from passwordless CUPS administration, replace cups-pdf with Polkit-backed setup, and migrate existing systems safely.

Reported-By: Erik Hunstad (Bad Sector Labs)

Co-Authored-By: Daybreak Blue <noreply@openai.com>
2026-08-27 18:00:12 +01:00
Taksh 877f1e96ef Keep web app launchers on http(s)
Chromium --app= will run javascript:, file:, and data: URLs. Prefix
schemeless input with https as before, then refuse anything else.
2026-08-27 07:17:44 +05:30
Mehmet InceandClaude Opus 5 3b0e899029 Let a theme name hold a plus or lead with an underscore, and document the set
The name a theme installs under is derived from its repo URL, and holding it to
an allowlist made that allowlist a naming convention nobody had written down. It
was also tighter than the harm it exists to stop: `+` is not shell syntax and a
leading `_` is neither the `..` climb nor the dash basename reads as an option,
so `omarchy-c++-theme` was refused for nothing.

Widen the set to those two and say what it is where a theme author is already
picking a name. The leading character stays out of `.` and `-`, which is the
part that does the work.

Reported-by: Luis Alvarez (lalvarezt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fd3RCHxwjEbMXoSYB9Aiso
2026-08-26 23:37:44 +01:00
bastidotnet 187c268d68 Escape webapp .desktop values per freedesktop spec
- omarchy-webapp-install wrote all substituted values raw into the
  generated .desktop (Name/Comment/Icon/MimeType/Exec), with no escaping.
- Adds two spec-level escapers: Desktop Entry string escaping on every
  field (a raw newline could inject a second key line / second Exec=),
  and Exec-argument quoting for the default Exec's URL (spaces, %, reserved
  chars). $CUSTOM_EXEC stays file-syntax-only — it is a full command line
  by design, not a single value.
- No known exploit path: untrusted input reaches these values today only
  via Omarchy literals, interactive gum, or direct CLI. This is
  defense-in-depth for a latent sink.
- Verified end-to-end: generated .desktop has one escaped key per field;
  gio launch passes the URL to omarchy-launch-webapp as a single unchanged
  argument (Sunshine literal and normal gum path both byte-identical).
2026-08-26 23:59:38 +02:00
Mehmet InceandClaude Opus 5 75e51f0b95 Refuse a theme name that is shell syntax, and quote the one the unlock picker returns
A theme installed from a git repo is named after the repo URL, and that name
becomes its directory name under ~/.config/omarchy/themes. Style > Unlock built
a command line out of the name the picker returned and handed it to
omarchy-launch-floating-terminal-with-presentation, which runs its argument as a
shell string -- so a theme directory called `a';id;'b` ran `id`. Themes are
already held to contributing colour and nothing that executes, which is why
omarchy-theme-set stages no .lua, terminal config, or vscode.json from one.

Hold the derived name to the characters a theme name needs, which stops it from
being dangerous at every place it lands rather than at the one found, and quote
it with printf %q on the way into the action for the names already on disk.

omarchy-theme-remove keeps its existing path-climb guard: its name reaches only
a quoted rm, and the same charset would strand a theme installed before this.

Reported-by: Luis Alvarez (lalvarezt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WFcUm5HWFyxaVYdwAeWPP
2026-08-26 16:22:17 +01:00
Afonso Oliveira c34d20ca14 [Security] Pin Windows VM mounts behind a root boundary 2026-08-25 18:39:00 +01:00
Helmut Januschka b91180a808 Skip Chromium EULA on first run 2026-08-25 14:48:44 +02:00
David Heinemeier HanssonandClaude Opus 5 12c350e404 Match the Windows VM title regardless of the launcher's shell quoting
The new assertion pinned /title:"Windows VM - Omarchy" with the quote sitting
immediately after the colon. That quote is incidental shell syntax, not the
title the Hyprland rule matches on. Open PR #7902 moves the RDP arguments into
an array, where the same flag reads "/title:Windows VM - Omarchy", so the
assertion would fail for whichever of the two pull requests merged second.

Allow an optional quote after the colon. The assertion still fails if the title
itself drifts, in either direction, which is the coupling it exists to protect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 08:45:50 +02:00
Jason Wall 8bee78bc63 Keep the Windows VM display fully opaque 2026-08-24 22:11:16 -07:00
203 changed files with 4759 additions and 8215 deletions
-24
View File
@@ -1,24 +0,0 @@
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
@@ -1,50 +0,0 @@
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,7 +2,3 @@
# Python bytecode (orchestrator)
__pycache__/
*.pyc
# Local working documents
OmarchyCN PRD.md
OmarchyCN-TASKS.md
+1 -20
View File
@@ -11,32 +11,13 @@ 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. Chinese user manual for the cn layer lives in `manual/zh-cn/`
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals
# Style
-11
View File
@@ -1,11 +0,0 @@
# 行为准则
参与 OmarchyCN 社区(issue、PR、讨论)时:
- 尊重他人,就事论事,不进行人身攻击、骚扰或歧视
- 欢迎新手提问,回答保持耐心
- 技术分歧用证据和代码说话
- 不发布垃圾信息、广告或与项目无关的内容
违反者由维护者视情节警告、删除内容或封禁账号。
举报渠道见 [SECURITY.md](SECURITY.md) 中的联系方式。
-23
View File
@@ -1,23 +0,0 @@
# 贡献指南
## 仓库结构
- 上游 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
@@ -1,14 +0,0 @@
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.
+64 -44
View File
@@ -1,59 +1,79 @@
# OmarchyCN
# Omarchy
面向中国开发者的 [Omarchy](https://omarchy.org) 下游发行版:完整保留 Omarchy 的 Arch + Hyprland 桌面体验,为中国网络环境、中文使用习惯与国内 AI 服务做系统级增强。
Omarchy is a beautiful, modern & opinionated Linux distribution by DHH.
当前版本 `4.0.0.alpha-cn.8`(基于上游 `quattro` 分支构建的基线 ISO)。
Read more at [omarchy.org](https://omarchy.org).
## 特性
## The Omarchy Manual
继承自 Omarchy
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.
- Hyprland 动态平铺桌面 + Quickshell 顶栏、菜单与系统面板,键盘驱动工作流
- `omarchy` CLI 与 `Super + Space` 系统菜单,CLI 与 GUI 同构
- 内置主题系统与一键换肤,终端 / 编辑器 / 桌面配色统一
- AI coding agent 桌面集成(Claude Code、Codex、OpenCode 等,`mise` 按需安装)
- Btrfs + Snapper 更新前快照与系统回滚
- archiso 离线安装镜像,支持无人值守安装与双系统引导
- [Welcome to Omarchy!](manual/01-welcome-to-omarchy.md)
- **中国镜像管理**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 配置
**The Basics**
## 下载与安装
- [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)
从 [Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 页面下载 ISO 与 `SHA256SUMS.txt`,校验后写入 U 盘,UEFI 启动安装:
**The Applications**
```bash
sha256sum -c SHA256SUMS.txt
```
- [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)
## 已有原版 Omarchy?一键转换
**Configuration**
包安装的原版 Omarchy(quattro)可直接转换,无需重装系统:
- [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)
- [Making your own theme](manual/43-making-your-own-theme.md)
```bash
curl -fsSL https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/bin/omarchy-cn-convert | bash
```
**The Rest**
转换内容:换装 OmarchyCN 构建的系统包(中文菜单/键位/更新界面、OMARCHY CN 品牌、AI 全家桶)、启用 [omarchy] 自建镜像与 [omarchycn] 仓库、zh_CN 语言与 Fcitx5+Rime 输入法、国内 Arch 镜像自动测速。重新登录后生效。
- [Mac support](manual/44-mac-support.md)
- [Troubleshooting](manual/45-troubleshooting.md)
- [FAQ](manual/46-faq.md)
- [System snapshots](manual/47-system-snapshots.md)
- [Security](manual/48-security.md)
- [Omarchy on...](manual/49-omarchy-on.md)
- [Dual Boot Install](manual/50-dual-boot-install.md)
- [Unattended Installs](manual/51-unattended-installs.md)
随时一键还原(按转换时记录的状态逐项回退):
## License
```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 原始版权声明。
Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT).
-19
View File
@@ -1,19 +0,0 @@
# 安全政策
## 报告漏洞
请勿在公开 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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,9 @@
[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
@@ -0,0 +1,8 @@
[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
@@ -0,0 +1,8 @@
[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.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

-1
View File
@@ -39,7 +39,6 @@ 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"
-24
View File
@@ -101,30 +101,6 @@ 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
-82
View File
@@ -1,82 +0,0 @@
#!/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
@@ -1,61 +0,0 @@
#!/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
@@ -1,52 +0,0 @@
#!/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
@@ -1,75 +0,0 @@
#!/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
@@ -1,30 +0,0 @@
#!/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
@@ -1,19 +0,0 @@
#!/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
@@ -1,104 +0,0 @@
#!/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
@@ -1,109 +0,0 @@
#!/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
@@ -1,94 +0,0 @@
#!/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
@@ -1,57 +0,0 @@
#!/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
@@ -1,15 +0,0 @@
#!/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
@@ -1,29 +0,0 @@
#!/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
@@ -1,107 +0,0 @@
#!/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
@@ -1,36 +0,0 @@
#!/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
@@ -1,32 +0,0 @@
#!/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
@@ -1,14 +0,0 @@
#!/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
@@ -1,20 +0,0 @@
#!/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
@@ -1,61 +0,0 @@
#!/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
@@ -1,124 +0,0 @@
#!/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
@@ -1,15 +0,0 @@
#!/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
@@ -1,36 +0,0 @@
#!/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
@@ -1,28 +0,0 @@
#!/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
@@ -1,63 +0,0 @@
#!/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
@@ -1,54 +0,0 @@
#!/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
@@ -1,148 +0,0 @@
#!/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
@@ -1,23 +0,0 @@
#!/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
@@ -1,22 +0,0 @@
#!/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
@@ -1,21 +0,0 @@
#!/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
@@ -1,36 +0,0 @@
#!/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
@@ -1,27 +0,0 @@
#!/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
@@ -1,9 +0,0 @@
#!/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
@@ -1,19 +0,0 @@
#!/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
@@ -1,22 +0,0 @@
#!/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
@@ -1,33 +0,0 @@
#!/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
@@ -1,31 +0,0 @@
#!/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
@@ -1,89 +0,0 @@
#!/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
@@ -1,120 +0,0 @@
#!/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
@@ -1,20 +0,0 @@
#!/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
@@ -1,72 +0,0 @@
#!/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
@@ -1,10 +0,0 @@
#!/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}"
+12 -29
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|kimi|deepcode|dim|dsh]
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent kimi
# 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
installing=false
if [[ ${1:-} == "--install" ]]; then
@@ -34,42 +34,25 @@ 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|kimi|deepcode|dim|dsh>"
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush>"
exit 1
;;
esac
agent_package=${agent_package:-$agent}
# 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 [[ $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
fi
exit 1
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
mkdir -p "$(dirname "$agent_file")"
+6 -2
View File
@@ -34,8 +34,12 @@ systemd, shell, or app-launcher environment; reboot to make every layer agree.
Affects only \$OMARCHY_PATH-resolved trees: bin/, default/, shell/,
themes/, applications/, config/. Files installed at fixed system paths
(/etc/, /usr/lib/systemd/, udev rule bodies, /etc/skel after user
creation, /usr/share/plymouth) are NOT covered — for those, use
omarchy-dev-pkg-test to build and install the package from the checkout.
creation) are NOT covered — for those, use omarchy-dev-pkg-test to build
and install the package from the checkout.
The Plymouth and SDDM themes under /usr/share are the exception: omarchy
plymouth set and omarchy refresh plymouth republish them from the checkout,
reading this link's authorization out of the root-owned /etc/omarchy.conf.
Also writes $sudoers_file so sudo resolves omarchy-*
from the checkout instead of the packaged copies. That part takes effect
+3
View File
@@ -12,10 +12,13 @@ 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 \
+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, /快捷键/)) 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, /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, /Launch apps/)) prio = 5
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, /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, /Audio controls/)) prio = 18
if (match(line, /Bluetooth controls/)) prio = 19
if (match(line, /Wifi controls/)) prio = 20
if (match(line, /表情/)) prio = 21
if (match(line, /取色器/)) prio = 22
if (match(line, /截图/)) prio = 23
if (match(line, /录屏/)) prio = 24
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, /Tmux/)) prio = 25
if (match(line, /Herdr/)) prio = 26
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, /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, /Cycle to/)) prio = 96
if (match(line, /置顶显示/)) prio = 97
if (match(line, /Reveal active/)) prio = 97
if (match(line, /Apple Display/)) prio = 98
if (match(line, /XF86/)) prio = 99
if (match(line, /Tmux 快捷键/)) prio = 100
if (match(line, /Herdr 快捷键/)) prio = 101
if (match(line, /Tmux keybindings/)) prio = 100
if (match(line, /Herdr keybindings/)) prio = 101
# print "priority<TAB>record"
printf "%d\t%s\n", prio, $0
-3
View File
@@ -96,9 +96,6 @@ 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.
+4 -2
View File
@@ -3,5 +3,7 @@
# omarchy:summary=Restore the default Omarchy Plymouth boot theme and SDDM login screen
# omarchy:requires-sudo=true
omarchy-refresh-plymouth
omarchy-refresh-sddm
set -euo pipefail
"$OMARCHY_PATH/bin/omarchy-refresh-plymouth"
"$OMARCHY_PATH/bin/omarchy-refresh-sddm"
+365 -76
View File
@@ -5,88 +5,377 @@
# omarchy:examples=omarchy plymouth set '#1d2021' '#ebdbb2' ~/.local/state/omarchy/current/theme/plymouth/logo.png
# omarchy:requires-sudo=true
# Configure the Plymouth boot theme with a custom background color, text color, and logo.
# Stages the change in a temp dir, then commits the staged files to /usr/share and
# rebuilds the initramfs. Also syncs the SDDM login screen (the post-logout
# screen) with the same colors and logo so boot/login stay visually unified.
set -euo pipefail
if (( $# != 3 )); then
# Build the authoritative theme in a root-owned directory, then publish each
# fixed destination atomically. The caller opens the selected logo before sudo,
# so the privileged process never resolves a user-controlled input path.
usage() {
echo "Usage: omarchy-plymouth-set <background-hex> <text-hex> <path-to-logo.png>" >&2
exit 1
fi
}
bg_hex="${1#\#}"
text_hex="${2#\#}"
logo_path="$3"
if ! [[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
echo "Invalid background color: $1 (expected #RRGGBB)" >&2
exit 1
fi
if ! [[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
echo "Invalid text color: $2 (expected #RRGGBB)" >&2
exit 1
fi
if [[ ! -f $logo_path ]]; then
echo "Logo file not found: $logo_path" >&2
exit 1
fi
# omarchy-plymouth-set-by-theme passes a theme's unlock.png straight from
# ~/.config/omarchy/themes, where an installed theme can make it a symlink to
# anything. The copies below land in world-readable /usr/share, so following one
# would republish whatever it points at.
if [[ -L $logo_path ]]; then
echo "Logo file is a symlink, which is not accepted: $logo_path" >&2
exit 1
fi
bg_r=$(awk -v n=$((16#${bg_hex:0:2})) 'BEGIN{printf "%.3f", n/255}')
bg_g=$(awk -v n=$((16#${bg_hex:2:2})) 'BEGIN{printf "%.3f", n/255}')
bg_b=$(awk -v n=$((16#${bg_hex:4:2})) 'BEGIN{printf "%.3f", n/255}')
theme_dir="/usr/share/plymouth/themes/omarchy"
staging_dir=$(mktemp -d)
trap 'rm -rf "$staging_dir"' EXIT
find "$OMARCHY_PATH/default/plymouth" -maxdepth 1 -type f -exec cp -t "$staging_dir/" {} +
cp "$logo_path" "$staging_dir/logo.png"
sed -i \
-e "s/^Window.SetBackgroundTopColor.*/Window.SetBackgroundTopColor($bg_r, $bg_g, $bg_b);/" \
-e "s/^Window.SetBackgroundBottomColor.*/Window.SetBackgroundBottomColor($bg_r, $bg_g, $bg_b);/" \
"$staging_dir/omarchy.script"
for asset in bullet.png entry.png lock.png progress_bar.png; do
magick "$staging_dir/$asset" -channel RGB +level-colors "#$text_hex","#$text_hex" "$staging_dir/$asset"
done
sudo cp -a --no-preserve=mode,ownership "$staging_dir/." "$theme_dir/"
sudo plymouth-set-default-theme omarchy
if omarchy-cmd-present limine-mkinitcpio; then
sudo limine-mkinitcpio
if (( $# == 3 )); then
mode=set
elif (( $# == 1 )); then
case "$1" in
--refresh-default)
mode=refresh-plymouth
;;
--refresh-sddm-default)
mode=refresh-sddm
;;
*)
usage
;;
esac
else
sudo mkinitcpio -P
usage
fi
# Sync the SDDM login screen with the same colors and logo.
sddm_dir="/usr/share/sddm/themes/omarchy"
sddm_template="$OMARCHY_PATH/default/sddm/omarchy/Main.qml"
if (( EUID == 0 )); then
echo "Error: run omarchy-plymouth-set as your user, not under sudo." >&2
exit 1
fi
sed \
-e "s/#1a1b26/#$bg_hex/g" \
-e "s/#ffffff/#$text_hex/g" \
"$sddm_template" | sudo tee "$sddm_dir/Main.qml" >/dev/null
logo_fd=
if [[ $mode != "set" ]]; then
bg_hex=
text_hex=
else
bg_hex="${1#\#}"
text_hex="${2#\#}"
logo_path="$3"
sudo cp "$staging_dir/logo.png" "$sddm_dir/logo.png"
for asset in bullet.png entry.png lock.png; do
sudo cp "$staging_dir/$asset" "$sddm_dir/$asset"
done
for asset in entry lock; do
magick "$staging_dir/$asset.png" -channel RGB +level-colors "#f7768e","#f7768e" "$staging_dir/$asset-failed.png"
sudo cp "$staging_dir/$asset-failed.png" "$sddm_dir/$asset-failed.png"
done
sudo rm -f "$sddm_dir/logo.svg"
if ! [[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
echo "Invalid background color: $1 (expected #RRGGBB)" >&2
exit 1
fi
if ! [[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]; then
echo "Invalid text color: $2 (expected #RRGGBB)" >&2
exit 1
fi
if [[ ! -f $logo_path ]]; then
echo "Logo file not found: $logo_path" >&2
exit 1
fi
if [[ -L $logo_path ]]; then
echo "Logo file is a symlink, which is not accepted: $logo_path" >&2
exit 1
fi
# Open the logo while still unprivileged. A replacement symlink to a root-only
# file therefore fails here instead of being followed after sudo starts.
if ! exec {logo_fd}<"$logo_path"; then
echo "Unable to open logo file as the current user: $logo_path" >&2
exit 1
fi
if [[ ! -f /proc/$$/fd/$logo_fd ]]; then
echo "Logo input is no longer a regular file: $logo_path" >&2
exit 1
fi
fi
run_root_transaction() {
sudo /bin/bash -c '
set -eEuo pipefail
PATH=/usr/bin:/bin
export PATH
umask 077
# Every check below is a bare assertion that aborts under set -e. Name the
# subject of each one so a refusal reaches the user instead of exiting mute.
failure_context="the privileged Plymouth transaction"
failure_reported=
report_failure() {
[[ -z $failure_reported ]] || return 0
failure_reported=1
printf "omarchy-plymouth-set: refusing to publish: %s failed validation\n" "$failure_context" >&2
if [[ -n ${failure_hint:-} ]]; then
printf "omarchy-plymouth-set: %s\n" "$failure_hint" >&2
fi
}
trap report_failure ERR
mode=$1
source_root=$2
bg_hex=$3
text_hex=$4
max_asset_size=$5
failure_context="the arguments of the privileged transaction"
[[ $mode == "set" || $mode == "refresh-plymouth" || $mode == "refresh-sddm" ]]
[[ $source_root == /* ]]
[[ $max_asset_size =~ ^[0-9]+$ ]]
(( max_asset_size > 0 ))
failure_context="the Omarchy source tree $source_root"
canonical_source_root=$(realpath -e -- "$source_root")
[[ $canonical_source_root == "$source_root" ]]
validate_trusted_directory() {
local directory=$1 canonical uid directory_mode
failure_context="directory $directory"
canonical=$(realpath -e -- "$directory")
[[ $canonical == "$directory" && -d $directory && ! -L $directory ]]
while :; do
failure_context="directory $directory (must be root-owned and not group- or world-writable)"
uid=$(stat -c %u -- "$directory")
directory_mode=$(stat -c %a -- "$directory")
(( uid == 0 ))
(( (8#$directory_mode & 0022) == 0 ))
[[ $directory == "/" ]] && break
directory=${directory%/*}
[[ -n $directory ]] || directory=/
done
}
validate_trusted_configuration_file() {
local configuration=$1 canonical uid configuration_mode size
failure_context="root configuration $configuration"
[[ -f $configuration && ! -L $configuration ]]
canonical=$(realpath -e -- "$configuration")
[[ $canonical == "$configuration" ]]
validate_trusted_directory "${configuration%/*}"
uid=$(stat -c %u -- "$configuration")
configuration_mode=$(stat -c %a -- "$configuration")
size=$(stat -c %s -- "$configuration")
(( uid == 0 ))
(( (8#$configuration_mode & 0022) == 0 ))
(( size > 0 && size <= 4096 ))
}
# A packaged tree must be root-owned. A development checkout is the one
# deliberate exception: omarchy dev link records its canonical path in a
# root-owned /etc/omarchy.conf. That is already an explicit decision to run
# privileged Omarchy commands from user-editable code in the checkout, so
# reading its packaged assets does not widen the development trust boundary.
development_source=false
source_root_uid=$(stat -c %u -- "$source_root")
if (( source_root_uid != 0 )); then
omarchy_conf=/etc/omarchy.conf
failure_context="$source_root is user-owned and $omarchy_conf must contain its trusted dev-link authorization; run omarchy dev link to authorize it"
failure_hint="$source_root is user-owned; run omarchy dev link to authorize this development checkout, or omarchy dev unlink to use the packaged tree"
validate_trusted_configuration_file "$omarchy_conf"
# validate_trusted_configuration_file walks /etc up to / and leaves its own
# subject behind in failure_context. Without restoring ours, a checkout
# that simply is not the authorized one refuses with "directory / must be
# root-owned and not group- or world-writable" -- naming a directory that
# passed, and sending the reader after a filesystem problem that is not
# there.
failure_context="the dev-link authorization in $omarchy_conf, which must name $source_root"
quoted_source_root=$source_root
quoted_source_root=${quoted_source_root//\\/\\\\}
quoted_source_root=${quoted_source_root//\"/\\\"}
quoted_source_root=${quoted_source_root//\$/\\\$}
quoted_source_root=${quoted_source_root//\`/\\\`}
expected_config_line="export OMARCHY_PATH=\"$quoted_source_root\""
mapfile -t omarchy_config_lines <"$omarchy_conf"
(( ${#omarchy_config_lines[@]} == 1 ))
[[ ${omarchy_config_lines[0]} == "$expected_config_line" ]]
development_source=true
failure_hint=
fi
if [[ $mode == "set" ]]; then
[[ $bg_hex =~ ^[0-9a-fA-F]{6}$ ]]
[[ $text_hex =~ ^[0-9a-fA-F]{6}$ ]]
fi
theme_dir=/usr/share/plymouth/themes/omarchy
sddm_dir=/usr/share/sddm/themes/omarchy
plymouth_theme_assets=(
bullet.png
entry.png
lock.png
logo.png
omarchy.plymouth
omarchy.script
preview-unlock.png
progress_bar.png
progress_box.png
)
plymouth_default_assets=("${plymouth_theme_assets[@]}" logos/oma.png)
sddm_theme_assets=(Main.qml bullet.png entry-failed.png entry.png lock-failed.png lock.png logo.png)
sddm_default_assets=("${sddm_theme_assets[@]}" metadata.desktop theme.conf)
plymouth_assets=()
sddm_assets=()
case "$mode" in
set)
plymouth_assets=("${plymouth_theme_assets[@]}")
sddm_assets=("${sddm_theme_assets[@]}")
;;
refresh-plymouth)
plymouth_assets=("${plymouth_default_assets[@]}")
;;
refresh-sddm)
sddm_assets=("${sddm_default_assets[@]}")
;;
esac
validate_trusted_file() {
local source=$1 canonical uid file_mode size
failure_context="packaged source file $source"
[[ -f $source && ! -L $source ]]
canonical=$(realpath -e -- "$source")
[[ $canonical == "$source" ]]
file_mode=$(stat -c %a -- "$source")
size=$(stat -c %s -- "$source")
(( size > 0 && size <= max_asset_size ))
if ! $development_source; then
validate_trusted_directory "${source%/*}"
uid=$(stat -c %u -- "$source")
(( uid == 0 ))
(( (8#$file_mode & 0022) == 0 ))
fi
}
copy_trusted_file() {
local source=$1 destination=$2
validate_trusted_file "$source"
install -o 0 -g 0 -m 0600 -- "$source" "$destination"
}
staging_dir=$(mktemp -d /tmp/omarchy-plymouth.XXXXXXXX)
temporary=
cleanup() {
[[ -z $temporary ]] || rm -f -- "$temporary"
rm -rf -- "$staging_dir"
}
trap cleanup EXIT HUP INT TERM
chown 0:0 -- "$staging_dir"
chmod 0700 -- "$staging_dir"
plymouth_stage=$staging_dir/plymouth
sddm_stage=$staging_dir/sddm
mkdir -m 0700 -p -- "$plymouth_stage/logos" "$sddm_stage"
for asset in "${plymouth_assets[@]}"; do
copy_trusted_file "$source_root/default/plymouth/$asset" "$plymouth_stage/$asset"
done
if [[ $mode == "set" ]]; then
# stdin was opened by the unprivileged caller. Read no more than the
# documented limit into the root-owned stage before doing other work.
failure_context="the selected logo (expected 1 to $max_asset_size bytes)"
head -c "$((max_asset_size + 1))" >"$plymouth_stage/logo.png"
logo_size=$(stat -c %s -- "$plymouth_stage/logo.png")
(( logo_size > 0 && logo_size <= max_asset_size ))
chown 0:0 -- "$plymouth_stage/logo.png"
chmod 0600 -- "$plymouth_stage/logo.png"
cp --reflink=never -- "$plymouth_stage/logo.png" "$sddm_stage/logo.png"
bg_r=$(awk -v n=$((16#${bg_hex:0:2})) "BEGIN{printf \"%.3f\", n/255}")
bg_g=$(awk -v n=$((16#${bg_hex:2:2})) "BEGIN{printf \"%.3f\", n/255}")
bg_b=$(awk -v n=$((16#${bg_hex:4:2})) "BEGIN{printf \"%.3f\", n/255}")
sed -i \
-e "s/^Window.SetBackgroundTopColor.*/Window.SetBackgroundTopColor($bg_r, $bg_g, $bg_b);/" \
-e "s/^Window.SetBackgroundBottomColor.*/Window.SetBackgroundBottomColor($bg_r, $bg_g, $bg_b);/" \
"$plymouth_stage/omarchy.script"
for asset in bullet.png entry.png lock.png progress_bar.png; do
magick "$plymouth_stage/$asset" -channel RGB +level-colors "#$text_hex","#$text_hex" "$plymouth_stage/$asset"
done
copy_trusted_file "$source_root/default/sddm/omarchy/Main.qml" "$sddm_stage/Main.qml"
sed -i \
-e "s/#1a1b26/#__OMARCHY_SDDM_BG__/g" \
-e "s/#ffffff/#__OMARCHY_SDDM_TEXT__/g" \
-e "s/#__OMARCHY_SDDM_BG__/#$bg_hex/g" \
-e "s/#__OMARCHY_SDDM_TEXT__/#$text_hex/g" \
"$sddm_stage/Main.qml"
for asset in bullet.png entry.png lock.png; do
cp --reflink=never -- "$plymouth_stage/$asset" "$sddm_stage/$asset"
done
for asset in entry lock; do
magick "$plymouth_stage/$asset.png" -channel RGB +level-colors "#f7768e","#f7768e" "$sddm_stage/$asset-failed.png"
done
chown -R 0:0 -- "$staging_dir"
find "$staging_dir" -type f -exec chmod 0600 -- {} +
elif (( ${#sddm_assets[@]} )); then
for asset in "${sddm_assets[@]}"; do
copy_trusted_file "$source_root/default/sddm/omarchy/$asset" "$sddm_stage/$asset"
done
fi
if (( ${#plymouth_assets[@]} )); then
validate_trusted_directory "$theme_dir"
if [[ $mode == "refresh-plymouth" ]]; then
validate_trusted_directory "$theme_dir/logos"
fi
fi
if (( ${#sddm_assets[@]} )); then
validate_trusted_directory "$sddm_dir"
fi
publish_asset() {
local source=$1 destination=$2 parent filename source_size copied_size
failure_context="destination $destination"
[[ -f $source && ! -L $source ]]
(( $(stat -c %u -- "$source") == 0 ))
source_size=$(stat -c %s -- "$source")
(( source_size > 0 && source_size <= max_asset_size ))
[[ $destination == /* && $destination != */ && $destination != *"/../"* ]]
parent=${destination%/*}
filename=${destination##*/}
[[ -n $parent && -n $filename && $filename != "." && $filename != ".." ]]
validate_trusted_directory "$parent"
temporary=$(mktemp --tmpdir="$parent" ".$filename.omarchy-new.XXXXXXXX")
install -o 0 -g 0 -m 0644 -- "$source" "$temporary"
copied_size=$(stat -c %s -- "$temporary")
(( copied_size == source_size ))
cmp -s -- "$source" "$temporary"
sync -f -- "$temporary"
mv --no-copy -fT -- "$temporary" "$destination"
temporary=
}
if (( ${#plymouth_assets[@]} )); then
for asset in "${plymouth_assets[@]}"; do
publish_asset "$plymouth_stage/$asset" "$theme_dir/$asset"
done
fi
if (( ${#sddm_assets[@]} )); then
for asset in "${sddm_assets[@]}"; do
publish_asset "$sddm_stage/$asset" "$sddm_dir/$asset"
done
validate_trusted_directory "$sddm_dir"
rm -f -- "$sddm_dir/logo.svg"
fi
' bash "$mode" "$OMARCHY_PATH" "$bg_hex" "$text_hex" "$((64 * 1024 * 1024))"
}
if [[ $mode == "set" ]]; then
run_root_transaction <&"$logo_fd"
else
run_root_transaction </dev/null
fi
if [[ $mode != "refresh-sddm" ]]; then
sudo plymouth-set-default-theme omarchy
if omarchy-cmd-present limine-mkinitcpio; then
sudo limine-mkinitcpio
else
sudo mkinitcpio -P
fi
fi
+1 -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 and videos with Kdenlive"
"Edit images with Pinta, videos with Kdenlive, docs with LibreOffice"
"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"
+2 -1
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, and install/user/all.sh.
default browser/mailto, and install/user/all.sh.
For shipped configs see /etc/skel (new users) and omarchy-reinstall-configs
(existing users explicitly resyncing).
@@ -110,6 +110,7 @@ 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 -8
View File
@@ -3,11 +3,6 @@
# omarchy:summary=Overwrite the user config for the Plymouth drive decryption and boot sequence with the Omarchy default and rebuild it.
# omarchy:requires-sudo=true
sudo cp -r "$OMARCHY_PATH/default/plymouth/." /usr/share/plymouth/themes/omarchy/
sudo plymouth-set-default-theme omarchy
if omarchy-cmd-present limine-mkinitcpio; then
sudo limine-mkinitcpio
else
sudo mkinitcpio -P
fi
# Reuse the fixed-file publisher so root never resolves the source checkout or
# follows a destination symlink while restoring the packaged assets.
exec "$OMARCHY_PATH/bin/omarchy-plymouth-set" --refresh-default
+3 -2
View File
@@ -3,5 +3,6 @@
# omarchy:summary=Refresh the SDDM theme from default
# omarchy:requires-sudo=true
sudo rm -rf /usr/share/sddm/themes/omarchy
sudo cp -r "$OMARCHY_PATH/default/sddm/omarchy" /usr/share/sddm/themes/omarchy
# Reuse the fixed-file publisher so root never resolves an untrusted source or
# follows a destination symlink while restoring the packaged SDDM theme.
exec "$OMARCHY_PATH/bin/omarchy-plymouth-set" --refresh-sddm-default
+3
View File
@@ -18,10 +18,13 @@ 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 \
+16 -6
View File
@@ -23,16 +23,26 @@ omarchy-git-url-check "$REPO_URL" || exit 1
THEMES_DIR="$HOME/.config/omarchy/themes"
# Strip user@host: prefix from scp-style SSH URLs so basename sees just the path
# Strip user@host: prefix from scp-style SSH URLs so basename sees just the path.
# git reads a URL as scp-style when a colon appears before any slash, so the path
# after it need not hold one: `git@host:omarchy-blue-theme.git` is a repo in that
# user's home, and leaving its prefix on names the theme after the whole URL.
REPO_PATH="$REPO_URL"
[[ $REPO_PATH != *"://"* && $REPO_PATH == *:*/* ]] && REPO_PATH="${REPO_PATH#*:}"
[[ $REPO_PATH != *"://"* && $REPO_PATH == *:* && ${REPO_PATH%%:*} != */* ]] && REPO_PATH="${REPO_PATH#*:}"
THEME_NAME=$(basename -- "$REPO_PATH" .git | sed -E 's/^omarchy-//; s/-theme$//' | tr '[:upper:]' '[:lower:]')
THEME_PATH="$THEMES_DIR/$THEME_NAME"
# The name comes from the URL and is joined into a path that is about to be
# removed, so a repo called `..` would take ~/.config/omarchy with it. A leading
# dot is refused with it: `host:-s/foo.git` leaves basename with `.git`.
if [[ -z $THEME_NAME || $THEME_NAME == .* || $THEME_NAME == */* ]]; then
# The name comes from the URL, is joined into a path that is about to be
# removed, and then names a directory the rest of Omarchy passes around by
# name: Style > Unlock builds a command line out of the one the picker
# returned. So it is held to the characters a theme name needs rather than
# screened for the harm of the day -- a repo called `..` would take
# ~/.config/omarchy with it, and one called `a';'id` would carry its own
# command into that picker. The leading character is kept out of `.` and `-`,
# which also covers `host:-s/foo.git` leaving basename with `.git`.
# A bracket range follows the locale's collation, not ASCII: `[a-z]` takes in
# `é` under en_US.UTF-8. Pin the locale so the set is the one written here.
if ! (LC_ALL=C; [[ $THEME_NAME =~ ^[a-z0-9_][a-z0-9._+-]*$ ]]); then
echo "Error: '$REPO_URL' does not give a usable theme name."
exit 1
fi
+6 -6
View File
@@ -3,16 +3,16 @@
# omarchy:summary=Prompt for confirmation before starting an update
gum style --border normal --padding "1 2" \
"准备好更新了吗?" \
"Ready to update?" \
"" \
"• 更新一旦开始就不能中止!" \
"• 请确保已接通电源或电量充足" \
"• You cannot stop the update once you start!" \
"• Make sure you're connected to power or have a full battery" \
"" \
"更新内容: https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases"
"What's new: https://github.com/basecamp/omarchy/releases/latest"
echo
if ! gum confirm "继续更新?"; then
echo "已取消更新"
if ! gum confirm "Continue with update?"; then
echo "Update cancelled"
exit 1
fi
+5 -2
View File
@@ -1328,7 +1328,7 @@ apply_system_transition() {
'
fi
as_root install -d -m 0755 /usr/lib/chromium
printf '%s\n' '{"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
printf '%s\n' '{"distribution":{"require_eula":false},"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
as_root tee /usr/lib/chromium/initial_preferences >/dev/null
# Deliberately do NOT add the user to the docker group. That group is
@@ -1364,7 +1364,6 @@ EOF
as_root systemctl disable docker.service >/dev/null 2>&1 || true
enable_system_service cups.service
enable_system_service cups-browsed.service
enable_system_service avahi-daemon.service
enable_system_service linux-modules-cleanup.service
enable_system_service docker.socket
@@ -2210,6 +2209,10 @@ fi
if command -v xdg-settings >/dev/null 2>&1; then
env -u BROWSER xdg-settings set default-web-browser chromium.desktop || true
fi
if command -v xdg-mime >/dev/null 2>&1; then
xdg-mime default HEY.desktop x-scheme-handler/mailto || true
fi
# This script is fetched from the branch and runs against whatever packaged
# tree the channel currently serves, so the packaged tree can be older than the
# script. The completion markers are written directly rather than through
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# omarchy:summary=Open HEY webmail and translate mailto links
# omarchy:args=[url]
url="$1"
web_url="https://app.hey.com"
# Handle mailto: URLs
if [[ $url =~ ^mailto: ]]; then
email=$(echo "$url" | sed 's/mailto://')
web_url="https://app.hey.com/messages/new?to=$email"
fi
exec omarchy-launch-webapp "$web_url"
+80 -14
View File
@@ -54,6 +54,34 @@ download_icon() {
[[ -s $2 && $(file -b --mime-type "$2") == image/* ]]
}
# Chromium --app= treats javascript:, file:, and data: as a document to
# run. Prefix schemeless input with https as before, then refuse anything
# that is not http(s).
normalize_webapp_url() {
local url=$1
if [[ ! $url =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
url="https://$url"
fi
printf '%s' "$url"
}
# Raw whitespace must be percent-encoded in a URL. Refuse it before serializing
# the desktop entry; before Exec argument quoting, it also split browser flags
# and additional URLs into separate arguments. Schemes are case-insensitive.
require_http_url() {
local url=$1
if [[ $url =~ [[:space:]] ]]; then
echo "Error: web app URL must not contain whitespace." >&2
exit 1
fi
if [[ ! ${url,,} =~ ^https?:// ]]; then
echo "Error: web app URL must be http or https." >&2
exit 1
fi
}
fetch_site_icon() {
local site_url="$1" dest="$2"
local origin page icon_url
@@ -77,14 +105,44 @@ fetch_site_icon() {
download_icon "https://www.google.com/s2/favicons?domain=${site_url}&sz=256" "$dest"
}
desktop_string_escape() {
# Desktop Entry "string" value (freedesktop Desktop Entry Spec, "Value types"):
# a raw newline would start a new key line and let a value inject a second
# Exec=. Escape backslash first, then tab/CR/LF and a leading space. Every value
# written into the .desktop file passes through here.
#
# Parameter expansion rather than sed: GNU sed's N auto-prints the pattern space
# and exits at end of input, so a `:a;N;$!ba` slurp skips every following s///
# for a value with no newline in it - which is every value except the injection
# attempt this exists to stop.
local value="$1"
value=${value//\\/\\\\}
value=${value//$'\t'/\\t}
value=${value//$'\r'/\\r}
value=${value//$'\n'/\\n}
[[ $value == " "* ]] && value="\\s${value# }"
printf '%s' "$value"
}
desktop_exec_arg() {
# One Exec argument, double-quoted per the freedesktop Exec spec: inside quotes
# " ` $ \ take a backslash and a literal % becomes %%. Only the default Exec's
# URL needs this; $CUSTOM_EXEC stays a whole command line (file-syntax only).
local escaped
escaped=$(printf '%s' "$1" \
| sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/`/\\`/g' -e 's/\$/\\$/g' -e 's/%/%%/g')
printf '"%s"' "$escaped"
}
if (( $# < 3 )); then
echo -e "\e[32mLet's create a new web app you can start with the app launcher.\n\e[0m"
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
require_plain_name "$APP_NAME"
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
APP_URL="https://$APP_URL"
fi
APP_URL=$(normalize_webapp_url "$APP_URL")
require_http_url "$APP_URL"
# Try to fetch the site's icon automatically first.
mkdir -p "$ICON_DIR"
@@ -101,10 +159,8 @@ if (( $# < 3 )); then
INTERACTIVE_MODE=true
else
APP_NAME="$1"
APP_URL="$2"
if [[ ! $APP_URL =~ ^[a-zA-Z][a-zA-Z0-9+.-]*: ]]; then
APP_URL="https://$APP_URL"
fi
APP_URL=$(normalize_webapp_url "$2")
require_http_url "$APP_URL"
ICON_REF="$3"
CUSTOM_EXEC="$4" # Optional custom exec command
MIME_TYPES="$5" # Optional mime types
@@ -143,29 +199,39 @@ else
ICON_VALUE=$(icon_name_from_ref "$ICON_REF")
fi
# Use custom exec if provided, otherwise default behavior
EXEC_COMMAND="${CUSTOM_EXEC:-omarchy-launch-webapp $APP_URL}"
# Default Exec quotes the URL as one Exec-spec argument; the whole line then gets
# the file-syntax escaping below (unescaped first at read time per spec, so the
# layers compose). $CUSTOM_EXEC is a full command line, so it gets file-syntax only.
if [[ -n $CUSTOM_EXEC ]]; then
EXEC_COMMAND=$CUSTOM_EXEC
else
EXEC_COMMAND="omarchy-launch-webapp $(desktop_exec_arg "$APP_URL")"
fi
# Create application .desktop file
DESKTOP_DIR="$HOME/.local/share/applications"
DESKTOP_FILE="$DESKTOP_DIR/$APP_NAME.desktop"
mkdir -p "$DESKTOP_DIR"
name_field=$(desktop_string_escape "$APP_NAME")
exec_field=$(desktop_string_escape "$EXEC_COMMAND")
icon_field=$(desktop_string_escape "$ICON_VALUE")
cat >"$DESKTOP_FILE" <<EOF
[Desktop Entry]
Version=1.0
Name=$APP_NAME
Comment=$APP_NAME
Exec=$EXEC_COMMAND
Name=$name_field
Comment=$name_field
Exec=$exec_field
Terminal=false
Type=Application
Icon=$ICON_VALUE
Icon=$icon_field
StartupNotify=true
EOF
# Add mime types if provided
if [[ -n $MIME_TYPES ]]; then
echo "MimeType=$MIME_TYPES" >>"$DESKTOP_FILE"
printf 'MimeType=%s\n' "$(desktop_string_escape "$MIME_TYPES")" >>"$DESKTOP_FILE"
fi
chmod +x "$DESKTOP_FILE"
+894 -125
View File
File diff suppressed because it is too large Load Diff
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
# OmarchyCN command center: routes to omarchy cn <command>
set -o pipefail
OMARCHY_BIN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
exec "$OMARCHY_BIN_DIR/omarchy" cn "$@"
-14
View File
@@ -1,14 +0,0 @@
{
"_verified": "2026-08-24, AUR RPC 实证在维护且未 out-of-date",
"apps": {
"wechat": { "name": "微信", "source": "aur", "package": "wechat-universal-bwrap", "license": "proprietary" },
"qq": { "name": "QQ", "source": "aur", "package": "linuxqq", "license": "proprietary" },
"feishu": { "name": "飞书", "source": "aur", "package": "feishu-bin", "license": "proprietary" },
"dingtalk": { "name": "钉钉", "source": "aur", "package": "dingtalk-bin", "license": "proprietary" },
"wemeet": { "name": "腾讯会议", "source": "aur", "package": "wemeet-bin", "license": "proprietary" },
"wps": { "name": "WPS Office", "source": "aur", "package": "wps-office-cn", "license": "proprietary" },
"tencent-docs": { "name": "腾讯文档", "source": "webapp", "url": "https://docs.qq.com", "icon": "https://docs.qq.com/favicon.ico", "license": "web" },
"yuque": { "name": "语雀", "source": "webapp", "url": "https://www.yuque.com/dashboard", "icon": "https://www.yuque.com/favicon.ico", "license": "web" },
"shimo": { "name": "石墨文档", "source": "webapp", "url": "https://shimo.im/desktop", "icon": "https://shimo.im/favicon.ico", "license": "web" }
}
}
-26
View File
@@ -1,26 +0,0 @@
{
"npm": {
"china": "https://registry.npmmirror.com",
"official": "https://registry.npmjs.org/"
},
"pip": {
"china": "https://pypi.tuna.tsinghua.edu.cn/simple",
"official": "https://pypi.org/simple"
},
"cargo": {
"china": "sparse+https://rsproxy.cn/index/",
"official": ""
},
"go": {
"china": "https://goproxy.cn,direct",
"official": "https://proxy.golang.org,direct"
},
"gem": {
"china": "https://mirrors.tuna.tsinghua.edu.cn/rubygems/",
"official": "https://rubygems.org/"
},
"docker": {
"china": "https://docker.m.daocloud.io",
"official": ""
}
}
-15
View File
@@ -1,15 +0,0 @@
[Groups/0]
Name=Default
Default Layout=us
DefaultIM=rime
[Groups/0/Items/0]
Name=keyboard-us
Layout=
[Groups/0/Items/1]
Name=rime
Layout=
[GroupOrder]
0=Default
-91
View File
@@ -1,91 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- 每条 lang 规则成对出现:50-omarchy.conf 先以 assign 把 generic 名整体替换成
Liberation Sans/Serif 与 JetBrainsMono Nerd Font(且先于本文件加载),
所以除 generic 名外还必须测试替换后的具体家族,否则规则静默失效(Issue #4 -->
<!-- 日文/韩文标签内容保持原生字形(仅约束 generic 请求,避免波及未标签路径) -->
<match target="pattern">
<test name="lang" compare="contains"><string>ja</string></test>
<test name="family"><string>sans-serif</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK JP</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>ja</string></test>
<test name="family"><string>Liberation Sans</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK JP</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>ko</string></test>
<test name="family"><string>sans-serif</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK KR</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>ko</string></test>
<test name="family"><string>Liberation Sans</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK KR</string></edit>
</match>
<!-- 简体中文内容强制简体字形 -->
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<test name="family"><string>sans-serif</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK SC</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<test name="family"><string>Liberation Sans</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans CJK SC</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<test name="family"><string>serif</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Serif CJK SC</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<test name="family"><string>Liberation Serif</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Serif CJK SC</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<test name="family"><string>monospace</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans Mono CJK SC</string></edit>
</match>
<match target="pattern">
<test name="lang" compare="contains"><string>zh-cn</string></test>
<test name="family"><string>JetBrainsMono Nerd Font</string></test>
<edit name="family" mode="prepend" binding="strong"><string>Noto Sans Mono CJK SC</string></edit>
</match>
<!-- 无语言标签的任意字体请求:汉字回退到 SC 而非 JPChromium/Electron 常见路径) -->
<match target="pattern">
<edit name="family" mode="append" binding="weak"><string>Noto Sans CJK SC</string></edit>
</match>
<!-- generic 家族展开时 SC 优先于其他 CJK 变体 -->
<alias>
<family>sans-serif</family>
<prefer>
<family>Noto Sans</family>
<family>Noto Sans CJK SC</family>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>serif</family>
<prefer>
<family>Noto Serif</family>
<family>Noto Serif CJK SC</family>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>Noto Sans Mono CJK SC</family>
<family>Noto Color Emoji</family>
</prefer>
</alias>
</fontconfig>
-32
View File
@@ -1,32 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
xsBNBGqMzPYBCADOcNGiPxC8AgP1kG1s6obLZi5NzfkVzlqkWH9/7JgSokRr//IO
L8L2eMQ5bvEou4Mc+eujPI+5Tm8TwfECkjhKWbEOk09yn9kUXVlc/+iROJXF9z4u
Cp6BIiZ5YzoadZysCqSLlYXejp38LGXdi4xyh5SfXII/VD/036MBwvwZkb6qoFsp
1AS4BRLcE0BgD2QjL5MIFU+yVblUZl8ss1r9AVNNgAllGzFpYHFt+PknnPDldj/j
dv5vihekC1Wy+w46w15vdBM04i6t1h72N3naFfuPkMJFuXo0NLWMNLOeDp+bsej8
Kwf31yn6Vz0XLw8wCmLBwUtBaahvE/TqYUJPABEBAAHNDyhBcmNoIFJlZ2lzdHJ5
KcLAuwQTAQgAbwWCaozM9gILBwkQvThgSIZ7M7Q1FAAAAAAAHAAQc2FsdEBub3Rh
dGlvbnMub3BlbnBncGpzLm9yZwbZDf7U+gBf+H+EPPm1SmYCFQgCFgACGQECmwMC
HgEWIQR03PV6zYErJNlZ8Ua9OGBIhnsztAAATuEH/2LQxWP9JXXjloS2HwsD/jBb
OX/LPP4inkbUmkxaPx9hzecKPIZttzRoIXK8FxviPxwifQYPRShD5t1UwI1vwTnY
BCWs4VUf+vMlcaKrmMhgL4z3Wfi1Cpn7GcEYaCB+9BPbNlVTy4KzQJKXk8RkrOPz
+r2YynBH2PynjoqVqVvgAnU1vc5gdRUdZIBQIt8RqpwF/Sc0+WwWf9bdW1VzB0Pc
QTvuCaNzQOtSTYgDko9XJSa5VNQJrYuwYg97DmTfx+PYm2dFBTeR8Ut9wWiynItZ
4ywSm6qE9w1OuBknUx80jev2LGlNNCD0/xBTWPokLf0ZsgLG9f302raZ+WY78GzO
wE0EaozM9gEIAN8JV920AR4+TY1v1FVenZbSq74nCe2UNtEHWf97cwZye8ivUbg2
mbDEJp+09asoLWRGwzDvD6rUicqihvTgAOB5cZQU1D9wxXfowfG5aB/czVA0MzPU
bj5BXS8lA0pN4zcTamCHnrfoQpCGk+eB48xwBPLfxxVtwttaBtvG2YAPdiwultJu
13IX4lsK/LS425NYwzuGw5m7Amy/rzamAUZiymIiG45RQ8y3/1UYRMW4yXOVMrtV
fmtaL/hzsG0sGVEyiuseR/ZPqZKZrveYeqzy3fCOyCGsPwWH9rtfDekGBGbJLoiY
wwA3ikMifFYPr1A1PMOBR5jAXYV/VG/pQQkAEQEAAcLArAQYAQgAYAWCaozM9gkQ
vThgSIZ7M7Q1FAAAAAAAHAAQc2FsdEBub3RhdGlvbnMub3BlbnBncGpzLm9yZyFs
5xT4PpfIzf6Y7ke/Br8CmwwWIQR03PV6zYErJNlZ8Ua9OGBIhnsztAAATqIH/26g
dx2lJNHIYXlS/Ld8JXCC42IctWPeESegQPmJp96GbvF4Z7dVB0e+kqy7YgNYY8dv
ibc7GicmQrAtsNDmm+fB1nmd316HxrtiVsBKgeNWTh7EN8cH79lNB4IqwWf6zkny
1A1Ntbtqv0tERg/fXEuY6y+9f67JBM5P20iWRij3NNODPHpy5Vr7Ftm7pQ7FgDSj
tntrOKMvAwKsWA+avGt55eQe9kbbFhbOqQEXfiqIgIQki0yg8o5VrIz5ZEQm7AXU
fYr6IxJPrD7KhBeXO2Z0/TdKjZ5c1kpWEC2W0x8TUH9s2v5q4UeTlyHg5fyFzte6
oAEZesYWyQ10m7ewZTg=
=9OK8
-----END PGP PUBLIC KEY BLOCK-----
-16
View File
@@ -1,16 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEaozDJhYJKwYBBAHaRw8BAQdA3l2d/6gj2z96cit0NGYg419BmDDFH6SYhkzL
3OMVCem0PU9tYXJjaHlDTiBSZWxlYXNlIFNpZ25pbmcgS2V5IDx6aGFuZ3lhbmdo
YWhhMDQwN0BvdXRsb29rLmNvbT6ImQQTFgoAQRYhBARJDwZfat0mKnJDUG7fe4YD
tdJHBQJqjMMmAhsBBQkDwmcABQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJ
EG7fe4YDtdJHuSMBAI3/samJVmhfdr/HYS8y266Xq82tof9lL9izImd/YibkAQCQ
Fjw97J4LPnPQEm93j/3kmHC+EGTQu3EDbXEoI/AUC7gzBGqMwyYWCSsGAQQB2kcP
AQEHQCRBOFrQ9UQVtv3XawZIMm93j3IV45K8kk5AGLg+JnMUiPUEGBYKACYWIQQE
SQ8GX2rdJipyQ1Bu33uGA7XSRwUCaozDJgIbAgUJA8JnAACBCRBu33uGA7XSR3Yg
BBkWCgAdFiEEUarKC/SJIll7SA5+IRubguF8+2cFAmqMwyYACgkQIRubguF8+2cP
FQD6A6Xgt7L5bXR/kSJXvDHEgjsQG5xfAEEaZ8/VZ0clWxwA/ikOgsx8VQkZhwrJ
Hb/3iG5mb138yHf3Ug00N1GwL6gOHn0A/3OSdXKvdmQhMEUf8TpZNf/ouRal2NsI
BB1FBGxZWCYGAQDJRR2W0FEYbEuzUj9XGCHb8O9TjabNN0QduQj78ZNNAQ==
=Awb4
-----END PGP PUBLIC KEY BLOCK-----
-175
View File
@@ -1,175 +0,0 @@
# shellcheck shell=bash
# Sourced helpers for omarchy-cn-ai-* commands
CN_AI_PROVIDERS="$OMARCHY_PATH/cn/registry/ai-providers.json"
CN_AI_HARNESSES="$OMARCHY_PATH/cn/registry/ai-harnesses.json"
CN_AI_COMPAT="$OMARCHY_PATH/cn/registry/ai-compatibility.json"
CN_AI_PROFILE_DIR="$HOME/.config/omarchycn/ai/profiles"
CN_AI_CURRENT="$HOME/.config/omarchycn/ai/current"
cn_ai_provider_ids() { jq -r '.providers | keys[]' "$CN_AI_PROVIDERS"; }
cn_ai_harness_ids() { jq -r '.harnesses | keys[]' "$CN_AI_HARNESSES"; }
cn_ai_endpoint() {
jq -re --arg p "$1" --arg proto "$2" '.providers[$p].endpoints[$proto]' "$CN_AI_PROVIDERS"
}
cn_ai_models() {
jq -r --arg p "$1" '.providers[$p].models[].id' "$CN_AI_PROVIDERS"
}
cn_ai_model_by_alias() {
jq -re --arg p "$1" --arg a "$2" \
'.providers[$p].models[] | select(.aliases // [] | index($a)) | .id' "$CN_AI_PROVIDERS"
}
cn_ai_fast_model() {
cn_ai_model_by_alias "$1" fast 2>/dev/null || cn_ai_model_by_alias "$1" default-coding
}
cn_ai_harness_field() {
jq -re --arg h "$1" --arg f "$2" '.harnesses[$h][$f]' "$CN_AI_HARNESSES"
}
# Providers with a fixed token (local servers) need no stored secret.
# `// empty` keeps stdout clean when absent (bare -re would print "null").
cn_ai_static_token() {
jq -re --arg p "$1" '.providers[$p].static_token // empty' "$CN_AI_PROVIDERS"
}
cn_ai_resolve_key() {
cn_ai_static_token "$1" 2>/dev/null || omarchy-cn-ai-secret get "$1"
}
# Dynamic-model providers list models at runtime, not in the registry
cn_ai_models_dynamic() {
jq -e --arg p "$1" '.providers[$p].models_dynamic == true' "$CN_AI_PROVIDERS" > /dev/null
}
cn_ai_combo_level() {
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].level // "unsupported"' "$CN_AI_COMPAT"
}
# Combo-specific model allowlist; empty output means no restriction
cn_ai_combo_models() {
jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].models // [] | .[]' "$CN_AI_COMPAT"
}
cn_ai_model_protocol() {
jq -r --arg p "$1" --arg m "$2" \
'.providers[$p].models[] | select(.id == $m) | .protocol // ""' "$CN_AI_PROVIDERS"
}
# Profile files are flat key="value" lines
cn_ai_profile_field() {
local file="$CN_AI_PROFILE_DIR/$1.toml" key="$2"
grep -E "^$key = " "$file" | head -1 | cut -d\" -f2
}
cn_ai_current_profile() {
if [[ ! -f $CN_AI_CURRENT ]]; then
echo "No default AI profile set (run: omarchycn ai profile use <name>)" >&2
return 1
fi
cat "$CN_AI_CURRENT"
}
# Print export statements for the profile's harness x provider combo.
# Secrets are resolved at launch time only; nothing is written to disk.
cn_ai_render_env() {
local harness="$1" provider="$2" model="$3" key="$4"
local base fast
case "$harness" in
claude-code)
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")
printf 'export ANTHROPIC_BASE_URL=%q\n' "$base"
# Docs vary between AUTH_TOKEN (deepseek/zai) and API_KEY (kimi): set both
printf 'export ANTHROPIC_AUTH_TOKEN=%q\n' "$key"
printf 'export ANTHROPIC_API_KEY=%q\n' "$key"
printf 'export ANTHROPIC_MODEL=%q\n' "$model"
printf 'export ANTHROPIC_DEFAULT_SONNET_MODEL=%q\n' "$model"
printf 'export ANTHROPIC_DEFAULT_OPUS_MODEL=%q\n' "$model"
printf 'export ANTHROPIC_DEFAULT_HAIKU_MODEL=%q\n' "$fast"
;;
opencode)
if [[ $provider != "deepseek" ]]; then
echo "opencode adapter renders only the deepseek combo (native provider)" >&2
return 1
fi
printf 'export DEEPSEEK_API_KEY=%q\n' "$key"
;;
*)
echo "No env renderer for harness: $harness" >&2
return 1
;;
esac
}
# Write ~/.codex/config.toml + models.json per DeepSeek's official codex
# integration contract (mirrors cdn.deepseek.com codex-deepseek-setup script)
cn_ai_render_codex_config() {
local provider="$1" model="$2" key="$3"
local base cfg="$HOME/.codex/config.toml"
if [[ $provider != "deepseek" ]]; then
echo "codex adapter renders only the deepseek combo (official integration)" >&2
return 1
fi
base="https://api.deepseek.com/"
mkdir -p "${cfg%/*}"
touch "$cfg"
# Strip our old blocks first, then check for an unmanaged provider table
rm -f "$cfg.omarchycn-tmp" "$cfg.omarchycn-new"
(
umask 077
awk '
/^# OmarchyCN ai (model|provider) begin$/ { skip = 1; next }
/^# OmarchyCN ai (model|provider) end$/ { skip = 0; next }
skip { next }
/^[[:space:]]*\[/ { in_section = 1 }
!in_section && /^[[:space:]]*(model|model_provider|model_catalog_json|preferred_auth_method|forced_login_method|model_reasoning_effort)[[:space:]]*=/ { next }
{ print }
' "$cfg" > "$cfg.omarchycn-tmp"
)
if grep -qE "^[[:space:]]*\[[[:space:]]*model_providers[[:space:]]*\.[[:space:]]*\"?$provider\"?[[:space:]]*\]" "$cfg.omarchycn-tmp"; then
rm -f "$cfg.omarchycn-tmp"
echo "codex: $cfg 已有非托管的 [model_providers.$provider],请手动清理后重试" >&2
return 1
fi
if [[ -s $cfg ]]; then
cp "$cfg" "$cfg.omarchycn-bak-$(date +%Y%m%d-%H%M%S)"
fi
cp "$OMARCHY_PATH/cn/registry/codex-deepseek-models.json" "$HOME/.codex/models.json"
(
umask 077
{
echo "# OmarchyCN ai model begin"
echo "model = \"$model\""
echo "model_provider = \"$provider\""
echo "model_catalog_json = \"~/.codex/models.json\""
echo "preferred_auth_method = \"apikey\""
echo "forced_login_method = \"api\""
echo "model_reasoning_effort = \"high\""
echo "# OmarchyCN ai model end"
cat "$cfg.omarchycn-tmp"
echo "# OmarchyCN ai provider begin"
echo "[model_providers.$provider]"
echo "name = \"$provider\""
echo "base_url = \"$base\""
echo "wire_api = \"responses\""
echo "experimental_bearer_token = \"$key\""
echo "# OmarchyCN ai provider end"
} > "$cfg.omarchycn-new"
)
mv "$cfg.omarchycn-new" "$cfg"
rm -f "$cfg.omarchycn-tmp"
}
-138
View File
@@ -1,138 +0,0 @@
# shellcheck shell=bash
# Sourced helpers for omarchy-cn-dev-mirror-* commands
CN_DEV_MIRRORS_JSON="$OMARCHY_PATH/cn/dev-mirrors.json"
# shellcheck disable=SC2034 # consumed by sourcing commands
CN_DM_TARGETS=(npm pip cargo go gem docker)
CN_DM_BACKUP_ROOT="$HOME/.local/state/omarchycn/backups/dev-mirror"
cn_dm_url() {
jq -re --arg t "$1" --arg p "$2" '.[$t][$p]' "$CN_DEV_MIRRORS_JSON"
}
cn_dm_backup() {
local file="$1"
local stamp="$2"
if [[ -f $file ]]; then
mkdir -p "$CN_DM_BACKUP_ROOT/$stamp"
cp "$file" "$CN_DM_BACKUP_ROOT/$stamp/${file##*/}"
fi
}
# Replace-or-append one key=value style line matched by a regex
cn_dm_set_line() {
local file="$1" match="$2" line="$3"
mkdir -p "${file%/*}"
touch "$file"
grep -vE "$match" "$file" > "$file.omarchycn-tmp" || true
printf '%s\n' "$line" >> "$file.omarchycn-tmp"
mv "$file.omarchycn-tmp" "$file"
}
cn_dm_get_npm() { grep -sE '^registry=' "$HOME/.npmrc" | cut -d= -f2- || true; }
cn_dm_set_npm() { cn_dm_set_line "$HOME/.npmrc" '^registry=' "registry=$1"; }
cn_dm_get_pip() {
python3 - <<'EOF'
import configparser, os
c = configparser.ConfigParser()
c.read(os.path.expanduser("~/.config/pip/pip.conf"))
print(c.get("global", "index-url", fallback=""))
EOF
}
cn_dm_set_pip() {
CN_DM_PIP_URL="$1" python3 - <<'EOF'
import configparser, os
path = os.path.expanduser("~/.config/pip/pip.conf")
c = configparser.ConfigParser()
c.read(path)
if not c.has_section("global"):
c.add_section("global")
c.set("global", "index-url", os.environ["CN_DM_PIP_URL"])
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
c.write(f)
EOF
}
cn_dm_get_cargo() {
local f="$HOME/.cargo/config.toml"
grep -sA1 '^\[source\.omarchycn\]' "$f" | grep -sE '^registry' | cut -d\" -f2 || true
}
cn_dm_set_cargo() {
local url="$1" f="$HOME/.cargo/config.toml"
mkdir -p "${f%/*}"
touch "$f"
sed -i '/^# OmarchyCN dev-mirror begin$/,/^# OmarchyCN dev-mirror end$/d' "$f"
if [[ -z $url ]]; then
return 0
fi
if grep -q '^\[source\.crates-io\]' "$f"; then
echo "cargo: $f already defines [source.crates-io]; edit it manually" >&2
return 1
fi
cat >> "$f" <<EOF
# OmarchyCN dev-mirror begin
[source.crates-io]
replace-with = "omarchycn"
[source.omarchycn]
registry = "$url"
# OmarchyCN dev-mirror end
EOF
}
cn_dm_get_go() { grep -sE '^GOPROXY=' "$HOME/.config/go/env" | cut -d= -f2- || true; }
cn_dm_set_go() { cn_dm_set_line "$HOME/.config/go/env" '^GOPROXY=' "GOPROXY=$1"; }
cn_dm_get_gem() { grep -sE '^- ' "$HOME/.gemrc" | head -1 | sed 's/^- //' || true; }
cn_dm_set_gem() {
local f="$HOME/.gemrc"
if [[ -s $f ]] && ! grep -q '^# OmarchyCN dev-mirror managed$' "$f"; then
echo "gem: $f has existing user content; set :sources: manually" >&2
return 1
fi
cat > "$f" <<EOF
# OmarchyCN dev-mirror managed
---
:sources:
- $1
EOF
}
cn_dm_get_docker() {
jq -re '."registry-mirrors"[0] // ""' /etc/docker/daemon.json 2>/dev/null || true
}
cn_dm_set_docker() {
local url="$1" current="{}"
sudo mkdir -p /etc/docker
if sudo test -f /etc/docker/daemon.json; then
current=$(sudo cat /etc/docker/daemon.json)
fi
if [[ -n $url ]]; then
jq --arg u "$url" '."registry-mirrors" = [$u]' <<<"$current" | sudo tee /etc/docker/daemon.json > /dev/null
else
jq 'del(."registry-mirrors")' <<<"$current" | sudo tee /etc/docker/daemon.json > /dev/null
fi
echo "docker: restart the docker daemon to take effect"
}
cn_dm_config_file() {
case "$1" in
npm) echo "$HOME/.npmrc" ;;
pip) echo "$HOME/.config/pip/pip.conf" ;;
cargo) echo "$HOME/.cargo/config.toml" ;;
go) echo "$HOME/.config/go/env" ;;
gem) echo "$HOME/.gemrc" ;;
docker) echo "/etc/docker/daemon.json" ;;
esac
}
-98
View File
@@ -1,98 +0,0 @@
# shellcheck shell=bash
# Sourced helpers for omarchy-cn-mirror-* commands
CN_MIRRORS_JSON="$OMARCHY_PATH/cn/mirrors.json"
CN_MIRRORLIST="/etc/pacman.d/mirrorlist"
CN_PROFILE_FILE="$HOME/.config/omarchycn/mirror-profile"
CN_OMARCHY_MIRROR="https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64"
# [omarchy] mirror line management (stable channel only); shared by the
# rollout migration and doctor --fix so refresh-pacman resets stay healable
cn_mirror_omarchy_repo_ok() {
grep -qxF "Server = $CN_OMARCHY_MIRROR" "${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
}
cn_mirror_omarchy_repo_fix() {
local conf="${OMARCHYCN_PACMAN_CONF:-/etc/pacman.conf}"
# [omarchy] requires signed packages; the mirror re-signs with the registry key
sudo pacman-key --add "$OMARCHY_PATH/cn/keys/omarchycn-registry.asc" > /dev/null || return 1
sudo pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4 > /dev/null || return 1
cn_mirror_omarchy_repo_ok && return 0
sudo sed -i "s|^Server = https://pkgs.omarchy.org/stable/|Server = $CN_OMARCHY_MIRROR\nServer = https://pkgs.omarchy.org/stable/|" "$conf"
}
cn_mirror_ids() {
jq -r '.mirrors[].id' "$CN_MIRRORS_JSON"
}
cn_mirror_url() {
jq -r --arg id "$1" '.mirrors[] | select(.id == $id) | .url' "$CN_MIRRORS_JSON"
}
cn_mirror_name() {
jq -r --arg id "$1" '.mirrors[] | select(.id == $id) | .name' "$CN_MIRRORS_JSON"
}
cn_mirror_ids_by_region() {
jq -r --arg r "$1" '.mirrors[] | select(.region == $r) | .id' "$CN_MIRRORS_JSON"
}
# Probe one mirror: prints "<speed_bytes_s> <ttfb_s> <sync_age_s|stale|unknown>"
cn_mirror_probe() {
local url="$1"
local out speed=0 ttfb=0 lastsync age="unknown" now
if out=$(curl -sSf -o /dev/null -m 12 --connect-timeout 5 \
-w '%{speed_download} %{time_starttransfer}' \
"$url/core/os/x86_64/core.db" 2>/dev/null); then
read -r speed ttfb <<<"$out"
speed="${speed%%.*}"
fi
if lastsync=$(curl -sSf -m 5 --connect-timeout 5 "$url/lastsync" 2>/dev/null); then
lastsync=$(tr -cd '0-9' <<<"$lastsync")
if [[ -n $lastsync ]]; then
now=$(date +%s)
age=$((now - lastsync))
if (( age > 86400 )); then
age="stale"
fi
fi
fi
echo "$speed $ttfb $age"
}
# Rank ids by probe speed, excluding dead (speed 0) and stale mirrors
cn_mirror_rank() {
local id url speed ttfb age
for id in "$@"; do
url=$(cn_mirror_url "$id")
read -r speed ttfb age < <(cn_mirror_probe "$url")
(( speed > 0 )) || continue
[[ $age == "stale" ]] && continue
echo "$speed $id"
done | sort -rn | awk '{print $2}'
}
# Write mirrorlist from mirror ids (first = primary), with timestamped backup
cn_mirror_write_list() {
local profile="$1"
shift
local stamp id url content=""
stamp=$(date +%Y%m%d-%H%M%S)
content="## OmarchyCN mirrorlist (profile: $profile, generated $stamp)\n"
for id in "$@"; do
url=$(cn_mirror_url "$id")
content+="## $id: $(cn_mirror_name "$id")\nServer = $url/\$repo/os/\$arch\n"
done
sudo cp "$CN_MIRRORLIST" "$CN_MIRRORLIST.omarchycn-bak-$stamp"
printf '%b' "$content" | sudo tee "$CN_MIRRORLIST" > /dev/null
mkdir -p "${CN_PROFILE_FILE%/*}"
echo "$profile" > "$CN_PROFILE_FILE"
echo "Mirrorlist written ($CN_MIRRORLIST), backup: $CN_MIRRORLIST.omarchycn-bak-$stamp"
}
-5
View File
@@ -1,5 +0,0 @@
echo "Trust the OmarchyCN registry key and put the [omarchy] mirror first"
# Only the stable channel is mirrored; edge/rc systems keep upstream directly
source "$OMARCHY_PATH/cn/lib/mirror.sh"
cn_mirror_omarchy_repo_fix
-8
View File
@@ -1,8 +0,0 @@
echo "Refresh the CJK fontconfig priority so zh-cn rules survive 50-omarchy.conf (Issue #4)"
# Only users who applied it before carry the broken copy
conf="$HOME/.config/fontconfig/conf.d/64-omarchycn-cjk.conf"
if [[ -f $conf ]]; then
cp "$OMARCHY_PATH/cn/fontconfig/64-omarchycn-cjk.conf" "$conf"
fc-cache -f > /dev/null
fi
-12
View File
@@ -1,12 +0,0 @@
{
"mirrors": [
{ "id": "tuna", "name": "清华大学 TUNA", "region": "cn", "url": "https://mirrors.tuna.tsinghua.edu.cn/archlinux" },
{ "id": "ustc", "name": "中国科学技术大学", "region": "cn", "url": "https://mirrors.ustc.edu.cn/archlinux" },
{ "id": "aliyun", "name": "阿里云", "region": "cn", "url": "https://mirrors.aliyun.com/archlinux" },
{ "id": "tencent", "name": "腾讯云", "region": "cn", "url": "https://mirrors.cloud.tencent.com/archlinux" },
{ "id": "netease", "name": "网易", "region": "cn", "url": "https://mirrors.163.com/archlinux" },
{ "id": "sjtu", "name": "上海交通大学", "region": "cn", "url": "https://mirror.sjtu.edu.cn/archlinux" },
{ "id": "geo", "name": "Arch 官方 Geo Mirror", "region": "global", "url": "https://geo.mirror.pkgbuild.com" },
{ "id": "worldwide", "name": "Arch 官方 Worldwide", "region": "global", "url": "https://mirror.rackspace.com/archlinux" }
]
}

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