Author SHA1 Message Date
ZacharyZhang-NYandClaude Fable 5 e0b21ff8fb Fix shell-quoting leak in setup-form English strings
Three literals carried ''' escape sequences verbatim, so sourcing
the English branch executed stray words and aborted the first-boot
owner form under set -e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 08:37:33 -04:00
ZacharyZhang-NYandClaude Fable 5 ba9a265e4a Fix GUI fallback misfire and untranslated failure-menu case patterns
The cage wrapper treated the installer session exit code as cage
availability, so an installer abort re-ran the English VT flow. A
gui-started marker now separates the two. Dashboard failure-menu
cases for shell/poweroff now match the localized choice strings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 02:13:27 -04:00
ZacharyZhang-NYandClaude Fable 5 1245b749af Logo word gap; translate welcome, cfdisk, and partitioning progress strings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:43:21 -04:00
ZacharyZhang-NYandClaude Fable 5 84514840f5 Chinese installer onboarding: bilingual setup form, vendored ISO patch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:33:35 -04:00
ZacharyZhang-NYandClaude Fable 5 48218b2d5f Brand boot splash and plymouth wordmark as OMARCHY CN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:26:05 -04:00
ZacharyZhang-NYandClaude Fable 5 6ce3bbf8d6 Merge upstream sync b86d4505 (README resolved toward CN rewrite)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 01:18:23 -04:00
ZacharyZhang-NYandClaude Fable 5 0bb9a96612 Begin cn.3 development cycle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:25:41 -04:00
ZacharyZhang-NYandClaude Fable 5 9546fe98b9 Update: refuse pre-channel downgrades; doc docker restore scope
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:24:39 -04:00
ZacharyZhang-NYandClaude Fable 5 2f5f02d3e5 Update: stable-channel empty tag list fails with message, not silently
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:21:11 -04:00
ZacharyZhang-NYandClaude Fable 5 eb149f30c4 M5 review fixes: key download step, honest update paths, ordered checklist, channel semantics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 23:19:26 -04:00
ZacharyZhang-NYandClaude Fable 5 9145679261 Document the OmarchyCN layer in AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:17:32 -04:00
ZacharyZhang-NYandClaude Fable 5 786544edf3 Add release checklist, sync SLA, security response, channel command
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:17:06 -04:00
ZacharyZhang-NYandClaude Fable 5 708651fa8e Add Chinese user manual (install, mirrors, IME, AI, recovery)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:16:34 -04:00
ZacharyZhang-NYandClaude Fable 5 79d3a65441 Remove temp diagnostics workflow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:15:24 -04:00
ZacharyZhang-NYandClaude Fable 5 17e0beb7a6 Temp host diagnostics workflow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 22:09:10 -04:00
ZacharyZhang-NYandClaude Fable 5 6330c2b0a7 Vendor omarchy-pkgs patch shipping the cn data layer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:30:59 -04:00
ZacharyZhang-NYandClaude Fable 5 9f78e1530f cn commands: use omarchy-cmd helpers per bin style rule
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:16:54 -04:00
ZacharyZhang-NYandClaude Fable 5 0aaa770561 Wizard: explicit step error propagation, honest final diagnosis
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:08:58 -04:00
ZacharyZhang-NYandClaude Fable 5 35e4d42682 Wizard: full PRD step set with explicit skip; apps via AUR helper
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:07:29 -04:00
ZacharyZhang-NYandClaude Fable 5 16b95790aa Add China app catalog and first-run wizard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:03:35 -04:00
ZacharyZhang-NYandClaude Fable 5 327442a1e7 AI tests: restricted PATH guarantees installer branch coverage
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:01:54 -04:00
ZacharyZhang-NYandClaude Fable 5 1cf6d455d7 AI tests: responses-only codex mock, exercised installer branch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 21:00:29 -04:00
ZacharyZhang-NYandClaude Fable 5 bf0d2a4e2a AI default/tests: env-passed key, secure creation, harness install, isolated strict mocks
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:58:35 -04:00
ZacharyZhang-NYandClaude Fable 5 bc2a3c09db Add AI mock regression suite; grant stable to regression-covered combos
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:55:34 -04:00
ZacharyZhang-NYandClaude Fable 5 cd80471307 Add AI default-agent mapping via harness-native config persistence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:54:42 -04:00
ZacharyZhang-NYandClaude Fable 5 e2a473481d Wizard: preserve prior profile until new config passes its test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:53:12 -04:00
ZacharyZhang-NYandClaude Fable 5 cf15c6083c Wizard: adapter-backed combos only, protocol-filtered models, test-gated activation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:52:09 -04:00
ZacharyZhang-NYandClaude Fable 5 d0c4c3a0b3 AI test: codex Responses API branch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:50:37 -04:00
ZacharyZhang-NYandClaude Fable 5 78909cbfd5 AI wizard/test/doctor: native-auth handling, Responses API test, safe temp, stale profile
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:50:11 -04:00
ZacharyZhang-NYandClaude Fable 5 a942500712 AI test: correct http code capture on connection failure
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:46:03 -04:00
ZacharyZhang-NYandClaude Fable 5 870ee40c7a Add AI test, doctor, and interactive setup wizard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:45:47 -04:00
ZacharyZhang-NYandClaude Fable 5 3760ac46e2 Codex adapter: remove stale temp files before secure recreation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:44:36 -04:00
ZacharyZhang-NYandClaude Fable 5 51ba414009 Codex adapter: full auth contract, robust TOML detection, 0600 temp files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:43:29 -04:00
ZacharyZhang-NYandClaude Fable 5 717ba305bf AI: harness-aware model validation, official codex contract, launch fail-fast
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:38:43 -04:00
ZacharyZhang-NYandClaude Fable 5 02d0d5c991 Add AI profiles and harness adapters (claude-code, codex, opencode, native)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:29:48 -04:00
ZacharyZhang-NYandClaude Fable 5 8c3ef3d884 Secret: decryption-free pass existence check for purge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:27:49 -04:00
ZacharyZhang-NYandClaude Fable 5 f9f25119d0 Secret: purge failure fails set, unreachable service fails rm
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:26:38 -04:00
ZacharyZhang-NYandClaude Fable 5 bfef3e5c1e Secret: purge stale lower-tier copies on set, warn on unreachable rm, fresh 0600
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:24:55 -04:00
ZacharyZhang-NYandClaude Fable 5 6cb9065dcf Secret: handle empty stdin read, explicit dir perms
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:21:13 -04:00
ZacharyZhang-NYandClaude Fable 5 28a9de1a46 Add AI secret backend with three-tier storage fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:20:54 -04:00
ZacharyZhang-NYandClaude Fable 5 676fcfc9ea Compatibility levels: official-doc until mock regression grants stable
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:20:02 -04:00
ZacharyZhang-NYandClaude Fable 5 86ff275910 Add AI registry: providers, harnesses, compatibility matrix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:09:49 -04:00
ZacharyZhang-NYandClaude Fable 5 c8279fc993 cn tests: assert fontconfig rule content and required menu entries
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:06:36 -04:00
ZacharyZhang-NYandClaude Fable 5 9baecc928d cn tests: strict mode, positive assertions before removals
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:05:48 -04:00
ZacharyZhang-NYandClaude Fable 5 bc84b808fe Add OmarchyCN shell test suite
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:03:09 -04:00
ZacharyZhang-NYandClaude Fable 5 5bab9b81ac Overlay: comments-only extension template counts as replaceable
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:01:53 -04:00
ZacharyZhang-NYandClaude Fable 5 a8683ae8a7 Menu: full scale presets; overlay ships extracted menu extension
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 20:00:27 -04:00
ZacharyZhang-NYandClaude Fable 5 ca0130c674 Add OmarchyCN menu with mirrors, Chinese env, display, diagnostics
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:51:54 -04:00
ZacharyZhang-NYandClaude Fable 5 94b9797211 Display scale: session detection via Hyprland env, executable recovery hint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:49:57 -04:00
ZacharyZhang-NYandClaude Fable 5 3cdb3fa61c Display scale: verify both lines, fail on reload error, honest non-TTY path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:48:35 -04:00
ZacharyZhang-NYandClaude Fable 5 fff49e747c Add display scale presets with revert confirmation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:46:26 -04:00
ZacharyZhang-NYandClaude Fable 5 4a762052ca IME hotkey: also unbind Apps menu key before menu migration
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:45:36 -04:00
ZacharyZhang-NYandClaude Fable 5 14b14f24fa Add IME hotkey command with menu-key migration and conflict detection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:43:35 -04:00
ZacharyZhang-NYandClaude Fable 5 b7ced3b1b0 IME: dependency-free profile comparison
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:41:33 -04:00
ZacharyZhang-NYandClaude Fable 5 0a3cff60eb IME: content-diff backup condition, DefaultIM assertion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:40:15 -04:00
ZacharyZhang-NYandClaude Fable 5 7b00f37c59 Add Fcitx5 Rime setup with default profile and status checks
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:37:29 -04:00
ZacharyZhang-NYandClaude Fable 5 2b799d4388 Font status: assert untagged CJK fallback reaches SC
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:35:01 -04:00
ZacharyZhang-NYandClaude Fable 5 92b0289dbb Fontconfig: restore SC-first generic alias preferences
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:34:32 -04:00
ZacharyZhang-NYandClaude Fable 5 19f7fff4de Fontconfig: scope ja/ko overrides to generic families
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:34:02 -04:00
ZacharyZhang-NYandClaude Fable 5 ae1083039b Fontconfig: untagged-request SC fallback, ja/ko native variants
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:33:22 -04:00
ZacharyZhang-NYandClaude Fable 5 d057c6f894 Locale apply: escaped matching, explicit generation assertions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:26:31 -04:00
ZacharyZhang-NYandClaude Fable 5 843769bf77 Add CJK fonts, fontconfig priority, and locale commands
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:25:53 -04:00
ZacharyZhang-NYandClaude Fable 5 320ba5118a Update: strict-mode migrations, per-file completion markers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:24:12 -04:00
ZacharyZhang-NYandClaude Fable 5 b44380db1d Add omarchycn update for overlay installs with migration runner
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:12:46 -04:00
ZacharyZhang-NYandClaude Fable 5 1ef2743f9e Overlay: manifest-as-intent before copy, two-pass validated uninstall
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:11:34 -04:00
ZacharyZhang-NYandClaude Fable 5 88b933d2bc Overlay: validated manifest with dest record, ownership checks, clean reinstall
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:09:29 -04:00
ZacharyZhang-NYandClaude Fable 5 e3ca9ee493 Add overlay installer for existing Omarchy systems
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:06:29 -04:00
ZacharyZhang-NYandClaude Fable 5 fe42c049ff Use full -Syu transactions, no partial upgrades
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:05:24 -04:00
ZacharyZhang-NYandClaude Fable 5 afd75160b5 Document [omarchycn] pacman repository setup
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:02:23 -04:00
ZacharyZhang-NYandClaude Fable 5 738393851c Keyring: merge keys via gpg import/export, pkgrel 2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:01:38 -04:00
ZacharyZhang-NYandClaude Fable 5 b7ff3a32c7 Keyring build: .key source names so makepkg does not treat them as sigs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:00:49 -04:00
ZacharyZhang-NYandClaude Fable 5 a867157978 Add omarchycn-keyring package and container build script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 19:00:28 -04:00
ZacharyZhang-NYandClaude Fable 5 04b2d8836e Doctor: accept all module arg, survive empty mirrorlist
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:58:27 -04:00
ZacharyZhang-NYandClaude Fable 5 386bf49758 Doctor: silence best-effort notification in headless runs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:56:09 -04:00
ZacharyZhang-NYandClaude Fable 5 dd3b893137 Add cn doctor with mirror auto-failover and restore commands
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:55:08 -04:00
ZacharyZhang-NYandClaude Fable 5 d1a5ddfd02 dev-mirror: fix cargo official/duplicate handling, sed-free line set, gemrc safety
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:47:40 -04:00
ZacharyZhang-NYandClaude Fable 5 e48264373d dev-mirror docker: create /etc/docker before writing daemon.json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:43:19 -04:00
ZacharyZhang-NYandClaude Fable 5 15bf72dfe3 dev-mirror lib: shellcheck directive for cross-file array
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:42:41 -04:00
ZacharyZhang-NYandClaude Fable 5 8dab891155 Add dev-mirror manager for npm, pip, cargo, go, gem, docker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:42:29 -04:00
ZacharyZhang-NYandClaude Fable 5 aabe95b887 Mirror probe: guard failures under set -e, reject HTTP errors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:40:27 -04:00
ZacharyZhang-NYandClaude Fable 5 434f848dd8 Mirror manager: shellcheck fixes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:35:11 -04:00
ZacharyZhang-NYandClaude Fable 5 2bcf0a20f0 Add Arch mirror manager: benchmark, apply, pin, restore, status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:34:55 -04:00
ZacharyZhang-NYandClaude Fable 5 18041c7537 cn commands: use OMARCHY_PATH per runtime convention
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:32:51 -04:00
ZacharyZhang-NYandClaude Fable 5 d4002897e3 Add omarchycn CLI entry and cn command group
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:29:01 -04:00
ZacharyZhang-NYandClaude Fable 5 4f06b2364f Sync workflow: always open PR, report trial-merge status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:24:38 -04:00
ZacharyZhang-NYandClaude Fable 5 2fd24589c2 Add automated upstream sync workflow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:22:44 -04:00
ZacharyZhang-NYandClaude Fable 5 9ce9f300ae Add OmarchyCN release signing public key and verification docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:18:38 -04:00
ZacharyZhang-NYandClaude Fable 5 db5c7db39a Fix CONTRIBUTING sync wording to match current state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:17:40 -04:00
ZacharyZhang-NYandClaude Fable 5 c1b9d655bb Add NOTICE and governance docs for OmarchyCN
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:15:43 -04:00
ZacharyZhang-NYandClaude Fable 5 743ae05acc Feature-focused Chinese README; move PRD and task list to local-only
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 18:06:40 -04:00
ZacharyZhang-NYandClaude Fable 5 8125f24531 Rewrite README in Chinese with OmarchyCN vs Omarchy comparison
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 15:58:00 -04:00
ZacharyZhang-NYandClaude Fable 5 50b6798467 Record T8-T9: release published, all tasks complete
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 15:26:33 -04:00
ZacharyZhang-NYandClaude Fable 5 3b0f02968a Record T5-T7: build env, baseline ISO build, checksum and boot smoke test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 15:10:19 -04:00
ZacharyZhang-NYandClaude Fable 5 5fa312e0ca Record T1-T4 completion and review-corrected M0 partial scope
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 14:34:15 -04:00
ZacharyZhang-NYandClaude Fable 5 2c8a687434 Add OmarchyCN PRD and M0 execution task list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-24 14:21:55 -04:00
81 changed files with 4302 additions and 88 deletions
+50
View File
@@ -0,0 +1,50 @@
name: upstream-sync
on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: quattro
fetch-depth: 0
- name: Fetch upstream and open sync PR
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
API: ${{ github.server_url }}/api/v1
REPO: ${{ github.repository }}
run: |
set -euo pipefail
git config user.name "omarchycn-sync"
git config user.email "sync@noreply.git.zacharyzhang.com"
git remote add upstream https://github.com/basecamp/omarchy.git
git fetch upstream quattro
UP=$(git rev-parse upstream/quattro)
echo "upstream quattro: $UP"
if git merge-base --is-ancestor "$UP" HEAD; then
echo "Already up to date with upstream"
exit 0
fi
BR="sync/upstream-${UP:0:8}"
if git ls-remote --exit-code --heads origin "$BR" > /dev/null; then
echo "Sync branch $BR already exists, PR pending review"
exit 0
fi
# Trial merge only to report conflict status in the PR body
MERGE="clean"
if ! git merge --no-commit --no-ff "upstream/quattro" > /dev/null 2>&1; then
MERGE="CONFLICTS (resolve manually)"
fi
git merge --abort 2> /dev/null || true
# Branch points at upstream HEAD so the PR always gets created
git push origin "$UP:refs/heads/$BR"
curl -sS --fail-with-body -X POST \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
"$API/repos/$REPO/pulls" \
-d "{\"base\":\"quattro\",\"head\":\"$BR\",\"title\":\"Sync upstream omarchy ${UP:0:8}\",\"body\":\"Automated sync of basecamp/omarchy quattro @ $UP. Trial merge: $MERGE. Review, run ./test/all, then merge.\"}"
+4
View File
@@ -2,3 +2,7 @@
# Python bytecode (orchestrator)
__pycache__/
*.pyc
# Local working documents
OmarchyCN PRD.md
OmarchyCN-TASKS.md
+14 -1
View File
@@ -11,13 +11,26 @@ 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` — sole exception: the overlay installer bootstraps by resolving its own checkout)
- `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-update` with per-file completion markers under `~/.local/state/omarchycn/`
- 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/`
- 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`
- Upstream sync: `.gitea/workflows/upstream-sync.yml` opens a PR per upstream change; keep upstream file edits minimal (currently: one `GROUP_DESCRIPTIONS[cn]` line in `bin/omarchy`, the OmarchyCN section in `default/omarchy/omarchy-menu.jsonc`, 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`
# Documentation Layout
Three documentation trees, split by genre and audience:
- `agents/skills/` - task procedure ("do this when doing X"), for anyone working on the codebase
- `docs/` - reference on how the system is shaped (file layout, update pipeline, theming, shell architecture), for anyone working on the codebase; skills link here for depth
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals. Chinese user manual for the cn layer lives in `manual/zh-cn/`
# Style
+11
View File
@@ -0,0 +1,11 @@
# 行为准则
参与 OmarchyCN 社区(issue、PR、讨论)时:
- 尊重他人,就事论事,不进行人身攻击、骚扰或歧视
- 欢迎新手提问,回答保持耐心
- 技术分歧用证据和代码说话
- 不发布垃圾信息、广告或与项目无关的内容
违反者由维护者视情节警告、删除内容或封禁账号。
举报渠道见 [SECURITY.md](SECURITY.md) 中的联系方式。
+23
View File
@@ -0,0 +1,23 @@
# 贡献指南
## 仓库结构
- 上游 Omarchy 代码尽量不动;OmarchyCN 改动集中在 `bin/omarchy-cn-*``bin/omarchycn``cn/``install/cn/`
- 上游代码定期与 `basecamp/omarchy` 同步:同步 PR 由维护者或 CI 工作流发起,人工审查合并
## 提交规范
- 遵循 [AGENTS.md](AGENTS.md) 的全部代码与命令约定(Bash 5、`[[ ]]`/`(( ))`、两空格缩进、`#!/bin/bash`
- 提交保持原子:一个提交只做一件事
- 涉及系统修改的命令必须支持 `--dry-run`,写配置前先备份
## 测试
- CLI 与 shell 改动跑 `./test/all`
- OmarchyCN 新增测试放在 `test/shell.d/*-test.sh`
## 提交流程
1. Fork 或在 issue 中讨论
2. 提交 PR 到 `quattro` 分支,说明动机与验证方式
3. 通过 CI 与代码审查后合并
+14
View File
@@ -0,0 +1,14 @@
OmarchyCN
Copyright (c) 2026 Zachary Zhang / OmarchyCN Community
本发行版基于 Omarchyhttps://github.com/basecamp/omarchy),
Copyright (c) 2025 David Heinemeier HanssonMIT License。
上游文件保留其原始版权与许可证声明(见 LICENSE)。
OmarchyCN 原创代码(bin/omarchy-cn-*、cn/、install/cn/ 及相关文档)
采用 MIT License 发布。
OmarchyCN is an independent community distribution based on Omarchy.
OmarchyCN is not affiliated with or endorsed by Basecamp, 37signals,
or the Omarchy maintainers.
+29 -64
View File
@@ -1,79 +1,44 @@
# Omarchy
# OmarchyCN
Omarchy is a beautiful, modern & opinionated Linux distribution by DHH.
面向中国开发者的 [Omarchy](https://omarchy.org) 下游发行版:完整保留 Omarchy 的 Arch + Hyprland 桌面体验,为中国网络环境、中文使用习惯与国内 AI 服务做系统级增强。
Read more at [omarchy.org](https://omarchy.org).
当前版本 `4.0.0.alpha-cn.1`(基于上游 `quattro` 分支构建的基线 ISO)。
## The Omarchy Manual
## 特性
The manual lives in [`manual/`](manual/), which is its authoritative source. It's
mirrored to [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual), where
its screenshots are also hosted.
继承自 Omarchy
- [Welcome to Omarchy!](manual/01-welcome-to-omarchy.md)
- Hyprland 动态平铺桌面 + Quickshell 顶栏、菜单与系统面板,键盘驱动工作流
- `omarchy` CLI 与 `Super + Space` 系统菜单,CLI 与 GUI 同构
- 内置主题系统与一键换肤,终端 / 编辑器 / 桌面配色统一
- AI coding agent 桌面集成(Claude Code、Codex、OpenCode 等,`mise` 按需安装)
- Btrfs + Snapper 更新前快照与系统回滚
- archiso 离线安装镜像,支持无人值守安装与双系统引导
**The Basics**
OmarchyCN 增强(开发中,按路线图逐步交付):
- [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)
- **中国镜像管理**Arch / npm / pip / Cargo / Go 等软件源测速、自动选择与故障切换
- **中文环境开箱即用**zh_CN locale、思源黑体 / 宋体字体栈、高分屏分数缩放预设
- **中文输入法**Fcitx5 + Rime 预配置,Wayland / GTK / Qt / Electron 全栈兼容,快捷键冲突自动处理
- **AI Hub**Kimi、DeepSeek、Z.AI/GLM 一等 Provider 支持,与 Claude Code、Codex、OpenCode、Kimi Code、Deep Code 等 Harness 的统一配置向导、凭据安全存储与连接诊断
- **国内应用中心**:微信、QQ、飞书、钉钉、腾讯会议、WPS 等应用的可信安装入口
- **Overlay 安装器**:在现有 Omarchy 上叠加 OmarchyCN,全程可逆、可卸载
- **统一诊断**`omarchycn doctor` 覆盖网络、镜像、输入法、显示与 AI 配置
**The Applications**
## 下载与安装
- [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)
[Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 页面下载 ISO 与 `SHA256SUMS.txt`,校验后写入 U 盘,UEFI 启动安装:
**Configuration**
```bash
sha256sum -c SHA256SUMS.txt
```
- [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)
## 上游文档
**The Rest**
Omarchy 完整英文手册在 [`manual/`](manual/) 目录,亦见 [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual)。
- [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
OmarchyCN is an independent community distribution based on Omarchy. OmarchyCN is not affiliated with or endorsed by Basecamp, 37signals, or the Omarchy maintainers.
Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT).
代码沿用上游 [MIT License](LICENSE),保留 Omarchy 原始版权声明。
+19
View File
@@ -0,0 +1,19 @@
# 安全政策
## 报告漏洞
请勿在公开 issue 中披露安全漏洞。发送邮件至:
**zhangyanghaha0407@outlook.com**(主题注明 [OmarchyCN Security]
请附:影响版本、复现步骤、影响面评估。我们在 72 小时内确认,修复后在
Release Notes 中致谢(除非你要求匿名)。
## 范围
- OmarchyCN 软件包、ISO、安装与更新脚本、Registry 与签名链路
- 上游 Omarchy / Arch / Hyprland 的漏洞请报给对应上游项目
## 签名验证
Release 产物的 PGP 公钥与验证方法见 [docs/release-signing.md](docs/release-signing.md)。
+1
View File
@@ -39,6 +39,7 @@ 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[debug]="Diagnostics and support logs"
GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
+81
View File
@@ -0,0 +1,81 @@
#!/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=$(omarchy-cn-ai-secret get "$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)
fast=$(cn_ai_fast_model "$provider")
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' 将以该配置启动"
+59
View File
@@ -0,0 +1,59 @@
#!/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 omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
echo "PASS ai: $provider API Key stored"
else
echo "FAIL ai: $provider API Key 未存储 (omarchycn ai secret set $provider)"
failures=$((failures + 1))
fi
if [[ $proto != "native" ]]; then
base=$(cn_ai_endpoint "$provider" "$proto")
host=$(sed -E 's|https?://([^/]+).*|\1|' <<<"$base")
if getent hosts "$host" > /dev/null 2>&1; then
echo "PASS ai: endpoint DNS resolves ($host)"
else
echo "FAIL ai: endpoint DNS 无法解析 ($host)"
failures=$((failures + 1))
fi
fi
exit $((failures > 0 ? 1 : 0))
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# omarchy:summary=Launch the AI harness configured by a profile
# omarchy:args=[profile]
# omarchy:examples=omarchycn ai launch | omarchycn ai launch work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
profile="${1:-$(cn_ai_current_profile)}"
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
echo "No such profile: $profile" >&2
exit 1
fi
harness=$(cn_ai_profile_field "$profile" harness)
provider=$(cn_ai_profile_field "$profile" provider)
model=$(cn_ai_profile_field "$profile" model)
command=$(cn_ai_harness_field "$harness" command)
config_method=$(cn_ai_harness_field "$harness" config_method)
if omarchy-cmd-missing "$command"; then
install=$(cn_ai_harness_field "$harness" install)
if [[ $install == doc:* ]]; then
echo "$harness 未安装,安装方法见官方文档: ${install#doc:}" >&2
exit 1
fi
echo "Installing $harness: $install"
$install
fi
case "$config_method" in
env)
key=$(omarchy-cn-ai-secret get "$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
+73
View File
@@ -0,0 +1,73 @@
#!/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 [[ -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 ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then
echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider"
fi
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# omarchy:summary=List AI profiles and the current default
# omarchy:examples=omarchycn ai profile list
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
current=""
if [[ -f $CN_AI_CURRENT ]]; then
current=$(<"$CN_AI_CURRENT")
fi
found="false"
for f in "$CN_AI_PROFILE_DIR"/*.toml; do
[[ -f $f ]] || continue
found="true"
name="${f##*/}"
name="${name%.toml}"
marker=" "
[[ $name == "$current" ]] && marker="*"
printf '%s %-14s %s x %s x %s\n' "$marker" "$name" \
"$(cn_ai_profile_field "$name" harness)" \
"$(cn_ai_profile_field "$name" provider)" \
"$(cn_ai_profile_field "$name" model)"
done
if [[ $found == "false" ]]; then
echo "No profiles (create: omarchycn ai profile create <name> <harness> <provider> <model>)"
fi
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# omarchy:summary=Set the default AI profile
# omarchy:args=<name>
# omarchy:examples=omarchycn ai profile use work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
name="${1:?usage: omarchycn ai profile use <name>}"
if [[ ! -f $CN_AI_PROFILE_DIR/$name.toml ]]; then
echo "No such profile: $name (see: omarchycn ai profile list)" >&2
exit 1
fi
mkdir -p "${CN_AI_CURRENT%/*}"
echo "$name" > "$CN_AI_CURRENT"
echo "Default AI profile: $name"
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
# omarchy:summary=Store, read, or delete AI provider API keys
# omarchy:args=<set|get|rm> <provider>
# omarchy:examples=omarchycn ai secret set deepseek | omarchycn ai secret get deepseek
set -euo pipefail
action="${1:?usage: omarchycn ai secret <set|get|rm> <provider>}"
provider="${2:?usage: omarchycn ai secret <set|get|rm> <provider>}"
if [[ ! $provider =~ ^[a-z0-9-]+$ ]]; then
echo "Invalid provider name: $provider" >&2
exit 1
fi
FILE_DIR="$HOME/.local/state/omarchycn/secrets"
FILE_PATH="$FILE_DIR/$provider"
secret_service_ok() {
omarchy-cmd-present secret-tool && secret-tool search service omarchycn > /dev/null 2>&1
}
pass_ok() {
omarchy-cmd-present pass && pass ls > /dev/null 2>&1
}
case "$action" in
set)
if [[ -t 0 ]]; then
read -rs -p "API key for $provider: " key
echo
else
IFS= read -r key || true
fi
if [[ -z $key ]]; then
echo "Empty key refused" >&2
exit 1
fi
if secret_service_ok; then
printf '%s' "$key" | secret-tool store --label "OmarchyCN AI: $provider" service omarchycn key "ai/$provider"
# Purge stale copies in lower-priority backends so get never falls
# through to an outdated key; a failed purge fails the set.
# Existence check is decryption-free (store file on disk).
if pass_ok && [[ -f ${PASSWORD_STORE_DIR:-$HOME/.password-store}/omarchycn/ai/$provider.gpg ]]; then
if ! pass rm -f "omarchycn/ai/$provider" > /dev/null; then
echo "存储成功但 pass 中的旧副本清除失败,请手动执行: pass rm omarchycn/ai/$provider" >&2
exit 1
fi
fi
rm -f "$FILE_PATH"
echo "Stored in Secret Service (secret-tool)"
elif pass_ok; then
printf '%s\n' "$key" | pass insert -m -f "omarchycn/ai/$provider" > /dev/null
rm -f "$FILE_PATH"
echo "Stored in pass (omarchycn/ai/$provider)"
else
mkdir -p "$FILE_DIR"
chmod 700 "$FILE_DIR"
rm -f "$FILE_PATH"
(umask 177 && printf '%s' "$key" > "$FILE_PATH")
echo "Stored in $FILE_PATH (0600 file fallback — 安装 libsecret 或 pass 可获得更安全的存储)"
fi
;;
get)
if secret_service_ok && secret-tool lookup service omarchycn key "ai/$provider" 2>/dev/null; then
exit 0
fi
if pass_ok && pass show "omarchycn/ai/$provider" 2>/dev/null; then
exit 0
fi
if [[ -f $FILE_PATH ]]; then
cat "$FILE_PATH"
exit 0
fi
echo "No secret stored for $provider (run: omarchycn ai secret set $provider)" >&2
exit 1
;;
rm)
removed=0
if secret_service_ok && secret-tool clear service omarchycn key "ai/$provider" 2>/dev/null; then
removed=1
fi
if pass_ok && pass rm -f "omarchycn/ai/$provider" > /dev/null 2>&1; then
removed=1
fi
if [[ -f $FILE_PATH ]]; then
rm -f "$FILE_PATH"
removed=1
fi
if omarchy-cmd-present secret-tool && ! secret_service_ok; then
echo "Secret Service 不可达,无法确认其中的副本已清除;服务恢复后重新执行 rm" >&2
exit 1
fi
if (( removed == 0 )); then
echo "No secret stored for $provider" >&2
exit 1
fi
echo "Secret for $provider removed"
;;
*)
echo "Unknown action: $action (expected set, get, or rm)" >&2
exit 1
;;
esac
+76
View File
@@ -0,0 +1,76 @@
#!/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 [[ -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 自管鉴权:首次启动时按其官方流程登录/配置"
echo "完成。启动: omarchycn ai launch"
exit 0
fi
if ! omarchy-cn-ai-secret get "$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"
echo "完成。启动: omarchycn ai launch"
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
# omarchy:summary=Test connectivity, auth, and model availability for an AI profile
# omarchy:args=[profile]
# omarchy:examples=omarchycn ai test | omarchycn ai test work
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/ai.sh"
profile="${1:-$(cn_ai_current_profile)}"
if [[ ! -f $CN_AI_PROFILE_DIR/$profile.toml ]]; then
echo "No such profile: $profile" >&2
exit 1
fi
harness=$(cn_ai_profile_field "$profile" harness)
provider=$(cn_ai_profile_field "$profile" provider)
model=$(cn_ai_profile_field "$profile" model)
proto=$(cn_ai_harness_field "$harness" protocol)
# Codex speaks the Responses API (wire_api=responses), not chat/completions
[[ $harness == "codex" ]] && proto="openai-responses"
failures=0
if [[ $proto == "native" ]]; then
echo "INFO ai-test: $harness 自管鉴权,本测试仅覆盖 env/config 型 Harness"
exit 0
fi
# Endpoint override for mock-server tests
base="${OMARCHYCN_AI_TEST_BASE_URL:-$(cn_ai_endpoint "$provider" "${proto%-responses}")}"
echo "INFO ai-test: $profile ($harness x $provider x $model) -> $base"
if ! key=$(omarchy-cn-ai-secret get "$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))
+46
View File
@@ -0,0 +1,46 @@
#!/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)
omarchy-pkg-aur-add "$(jq -r '.package' <<<"$entry")"
;;
webapp)
omarchy-webapp-install "$name" "$(jq -r '.url' <<<"$entry")" "$(jq -r '.icon' <<<"$entry")"
;;
*)
echo "Unknown source type: $source_type" >&2
exit 1
;;
esac
echo "$name 安装完成"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# omarchy:summary=List China app catalog entries and their sources
# omarchy:examples=omarchycn app list
set -euo pipefail
APPS_JSON="$OMARCHY_PATH/cn/apps.json"
printf '%-14s %-10s %-8s %-12s %s\n' "ID" "NAME" "SOURCE" "LICENSE" "PACKAGE/URL"
jq -r '.apps | to_entries[] |
[.key, .value.name, .value.source, .value.license, (.value.package // .value.url)] | @tsv' \
"$APPS_JSON" |
while IFS=$'\t' read -r id name source license ref; do
printf '%-14s %-10s %-8s %-12s %s\n' "$id" "$name" "$source" "$license" "$ref"
done
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# omarchy:summary=Show or set the OmarchyCN release channel
# omarchy:args=[stable|beta|nightly]
# omarchy:examples=omarchycn channel | omarchycn channel beta
set -euo pipefail
CHANNEL_FILE="$HOME/.config/omarchycn/channel"
if (( $# == 0 )); then
if [[ -f $CHANNEL_FILE ]]; then
cat "$CHANNEL_FILE"
else
echo "beta (default)"
fi
exit 0
fi
case "$1" in
stable | beta | nightly)
mkdir -p "${CHANNEL_FILE%/*}"
echo "$1" > "$CHANNEL_FILE"
echo "Channel: $1"
;;
*)
echo "Unknown channel: $1 (stable|beta|nightly)" >&2
exit 1
;;
esac
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# omarchy:summary=Apply china or official registry profile to development tools
# omarchy:args=<china|official> [--target npm,pip,cargo,go,gem,docker]
# omarchy:examples=omarchycn dev-mirror apply china | omarchycn dev-mirror apply official --target npm,pip
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
profile="${1:?usage: omarchycn dev-mirror apply <china|official> [--target a,b]}"
shift
if [[ $profile != "china" && $profile != "official" ]]; then
echo "Unknown profile: $profile (expected china or official)" >&2
exit 1
fi
targets=("${CN_DM_TARGETS[@]}")
if [[ ${1:-} == "--target" ]]; then
IFS=',' read -ra targets <<<"${2:?--target needs a comma-separated list}"
fi
stamp=$(date +%Y%m%d-%H%M%S)
for target in "${targets[@]}"; do
if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then
echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2
exit 1
fi
url=$(cn_dm_url "$target" "$profile")
cn_dm_backup "$(cn_dm_config_file "$target")" "$stamp"
"cn_dm_set_$target" "$url"
echo "$target -> ${url:-<default>}"
done
echo "Backups (if any): $CN_DM_BACKUP_ROOT/$stamp"
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# omarchy:summary=Check reachability of each configured development registry
# omarchy:examples=omarchycn dev-mirror doctor
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
failures=0
for target in "${CN_DM_TARGETS[@]}"; do
current=$("cn_dm_get_$target")
if [[ -z $current ]]; then
echo "INFO $target: using tool default"
continue
fi
# Reduce to a probe-able https URL
probe="${current#sparse+}"
probe="${probe%%,*}"
if curl -sSf -o /dev/null -m 8 --connect-timeout 5 "$probe" 2>/dev/null; then
echo "PASS $target: $current"
elif curl -sS -o /dev/null -m 8 --connect-timeout 5 -w '%{http_code}' "$probe" 2>/dev/null | grep -qE '^[34]'; then
# Registry roots often answer 3xx/404 to bare GET while the service works
echo "PASS $target: $current"
else
echo "FAIL $target: $current unreachable"
failures=$((failures + 1))
fi
done
exit $((failures > 0 ? 1 : 0))
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# omarchy:summary=Show configured registry for each development ecosystem
# omarchy:examples=omarchycn dev-mirror list
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
printf '%-8s %-52s %s\n' "TARGET" "CURRENT" "CONFIG"
for target in "${CN_DM_TARGETS[@]}"; do
current=$("cn_dm_get_$target")
printf '%-8s %-52s %s\n' "$target" "${current:-<default>}" "$(cn_dm_config_file "$target")"
done
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# omarchy:summary=Point one development ecosystem at a custom registry URL
# omarchy:args=<target> <url>
# omarchy:examples=omarchycn dev-mirror set npm https://registry.example.com
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
target="${1:?usage: omarchycn dev-mirror set <target> <url>}"
url="${2:?usage: omarchycn dev-mirror set <target> <url>}"
if [[ ! " ${CN_DM_TARGETS[*]} " == *" $target "* ]]; then
echo "Unknown target: $target (known: ${CN_DM_TARGETS[*]})" >&2
exit 1
fi
cn_dm_backup "$(cn_dm_config_file "$target")" "$(date +%Y%m%d-%H%M%S)"
"cn_dm_set_$target" "$url"
echo "$target -> $url"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# omarchy:summary=Apply a display scale preset with 15s revert confirmation
# omarchy:args=<1.0|1.25|1.5|1.6|1.75|2.0>
# omarchy:examples=omarchycn display scale 1.6
set -euo pipefail
preset="${1:?usage: omarchycn display scale <1.0|1.25|1.5|1.6|1.75|2.0>}"
case "$preset" in
1.0 | 1.25 | 1.5 | 1.6 | 1.75 | 2.0) ;;
*)
echo "Unknown preset: $preset (supported: 1.0 1.25 1.5 1.6 1.75 2.0)" >&2
exit 1
;;
esac
monitors="$HOME/.config/hypr/monitors.lua"
if [[ ! -f $monitors ]]; then
omarchy-refresh-config hypr/monitors.lua
fi
gdk=$(printf '%.0f' "$preset")
backup="$monitors.omarchycn-prev"
cp "$monitors" "$backup"
sed -i -E \
-e "s|^local omarchy_monitor_scale = .*|local omarchy_monitor_scale = $preset|" \
-e "s|^local omarchy_gdk_scale = .*|local omarchy_gdk_scale = $gdk|" \
"$monitors"
if ! grep -q "omarchy_monitor_scale = $preset" "$monitors" ||
! grep -q "omarchy_gdk_scale = $gdk" "$monitors"; then
mv "$backup" "$monitors"
echo "monitors.lua lacks the omarchy scale lines; reverted, edit it manually" >&2
exit 1
fi
echo "Monitor scale: $preset, GDK scale: $gdk"
if [[ -z ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
rm -f "$backup"
echo "当前不在 Hyprland 会话内,重登录后生效"
elif ! hyprctl reload > /dev/null 2>&1; then
mv "$backup" "$monitors"
echo "hyprctl reload 失败,已恢复原配置" >&2
exit 1
elif [[ -t 0 ]]; then
echo "15 秒内按 y 保留新缩放,超时或按其他键自动恢复"
if read -r -t 15 -n 1 answer && [[ $answer == "y" ]]; then
rm -f "$backup"
echo "已保留"
else
mv "$backup" "$monitors"
hyprctl reload > /dev/null 2>&1
echo "已恢复原缩放"
fi
else
omarchy-notification-send "OmarchyCN" "缩放已改为 $preset;如异常执行: mv $backup $monitors && hyprctl reload" 2> /dev/null || true
echo "无终端交互:已应用;恢复命令: mv $backup $monitors && hyprctl reload"
fi
+97
View File
@@ -0,0 +1,97 @@
#!/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
}
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 ;;
dev-mirror) check_dev_mirror ;;
ime) check_ime ;;
all)
check_network
check_mirror
check_dev_mirror
check_ime
;;
esac
if (( failures > 0 )); then
echo "Doctor: $failures failure(s)"
exit 1
fi
echo "Doctor: all checks passed"
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
# omarchy:summary=Install CJK fonts and the OmarchyCN fontconfig priority
# omarchy:examples=omarchycn font apply
set -euo pipefail
omarchy-pkg-add noto-fonts-cjk noto-fonts-emoji
conf_dir="$HOME/.config/fontconfig/conf.d"
mkdir -p "$conf_dir"
cp "$OMARCHY_PATH/cn/fontconfig/64-omarchycn-cjk.conf" "$conf_dir/"
fc-cache -f > /dev/null
echo "CJK fonts installed, fontconfig priority applied ($conf_dir/64-omarchycn-cjk.conf)"
omarchy-cn-font-status
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# omarchy:summary=Check Chinese font availability and fallback correctness
# omarchy:examples=omarchycn font status
set -euo pipefail
failures=0
for family in "Noto Sans CJK SC" "Noto Serif CJK SC" "Noto Sans Mono CJK SC"; do
matched=$(fc-match --format '%{family}' "$family")
if [[ $matched == *"$family"* ]]; then
echo "PASS font: $family"
else
echo "FAIL font: $family missing (got: $matched)"
failures=$((failures + 1))
fi
done
zh_sans=$(fc-match --format '%{family}' sans-serif:lang=zh-cn)
if [[ $zh_sans == *"CJK SC"* ]]; then
echo "PASS fallback: zh-cn sans-serif -> $zh_sans"
else
echo "FAIL fallback: zh-cn sans-serif -> $zh_sans (expected SC variant)"
failures=$((failures + 1))
fi
# Untagged requests (Chromium/Electron path) must reach SC before JP/KR
untagged=$(fc-match --sort sans-serif | grep -m1 -oE 'CJK (SC|JP|TC|KR|HK)' || true)
if [[ $untagged == "CJK SC" ]]; then
echo "PASS fallback: untagged CJK -> SC"
else
echo "FAIL fallback: untagged CJK -> ${untagged:-none} (expected SC)"
failures=$((failures + 1))
fi
exit $((failures > 0 ? 1 : 0))
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# omarchy:summary=Install Fcitx5 Rime and Chinese addons with a default profile
# omarchy:examples=omarchycn ime apply
set -euo pipefail
omarchy-pkg-add fcitx5-rime fcitx5-chinese-addons fcitx5-configtool
profile_dir="$HOME/.config/fcitx5"
mkdir -p "$profile_dir"
if [[ -f $profile_dir/profile && "$(<"$profile_dir/profile")" != "$(<"$OMARCHY_PATH/cn/fcitx5/profile")" ]]; then
backup_dir="$HOME/.local/state/omarchycn/backups/ime/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
cp "$profile_dir/profile" "$backup_dir/profile"
echo "Existing profile backed up to $backup_dir"
fi
cp "$OMARCHY_PATH/cn/fcitx5/profile" "$profile_dir/profile"
if systemctl --user is-active omarchy-fcitx5.service > /dev/null 2>&1; then
systemctl --user restart omarchy-fcitx5.service
echo "Fcitx5 restarted with Rime profile"
else
echo "Fcitx5 profile installed (takes effect at next graphical session)"
fi
omarchy-cn-ime-status
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# omarchy:summary=Set the input method toggle key, migrating the menu key if needed
# omarchy:args=<ctrl-space|super-space>
# omarchy:examples=omarchycn ime hotkey ctrl-space | omarchycn ime hotkey super-space
set -euo pipefail
choice="${1:?usage: omarchycn ime hotkey <ctrl-space|super-space>}"
fcitx_config="$HOME/.config/fcitx5/config"
bindings="$HOME/.config/hypr/bindings.lua"
# Replace or append the [Hotkey/TriggerKeys] section in fcitx5 global config
set_trigger() {
local key="$1"
mkdir -p "${fcitx_config%/*}"
touch "$fcitx_config"
awk '
/^\[Hotkey\/TriggerKeys\]$/ { skip = 1; next }
/^\[/ { skip = 0 }
!skip { print }
' "$fcitx_config" > "$fcitx_config.omarchycn-tmp"
printf '[Hotkey/TriggerKeys]\n0=%s\n' "$key" >> "$fcitx_config.omarchycn-tmp"
mv "$fcitx_config.omarchycn-tmp" "$fcitx_config"
}
remove_menu_migration() {
if [[ -f $bindings ]]; then
sed -i '/^-- OmarchyCN ime hotkey begin$/,/^-- OmarchyCN ime hotkey end$/d' "$bindings"
fi
}
case "$choice" in
ctrl-space)
set_trigger "Control+space"
remove_menu_migration
echo "输入法切换键: Ctrl+SpaceOmarchy 菜单保持 Super+Space"
;;
super-space)
set_trigger "Super+space"
mkdir -p "${bindings%/*}"
touch "$bindings"
remove_menu_migration
cat >> "$bindings" <<'EOF'
-- OmarchyCN ime hotkey begin
hl.unbind("SUPER + SPACE")
hl.unbind("SUPER + ALT + SPACE")
o.bind("SUPER + ALT + SPACE", "Omarchy menu", "omarchy-menu toggle")
-- OmarchyCN ime hotkey end
EOF
echo "输入法切换键: Super+Space"
echo "Omarchy 菜单已迁移至 Super+Alt+Space(写入 $bindings"
echo "原 Super+Alt+Space 的 Apps 菜单让位,可从根菜单进入或自行改绑"
;;
*)
echo "Unknown hotkey choice: $choice (expected ctrl-space or super-space)" >&2
exit 1
;;
esac
if systemctl --user is-active omarchy-fcitx5.service > /dev/null 2>&1; then
systemctl --user restart omarchy-fcitx5.service
fi
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# omarchy:summary=Check Fcitx5 Rime installation and configuration
# omarchy:examples=omarchycn ime status
set -euo pipefail
failures=0
for pkg in fcitx5 fcitx5-gtk fcitx5-qt fcitx5-rime fcitx5-chinese-addons; do
if pacman -Q "$pkg" > /dev/null 2>&1; then
echo "PASS ime: $pkg installed"
else
echo "FAIL ime: $pkg missing"
failures=$((failures + 1))
fi
done
if grep -sq '^Name=rime$' "$HOME/.config/fcitx5/profile"; then
echo "PASS ime: rime in fcitx5 profile"
else
echo "FAIL ime: rime not in fcitx5 profile (run: omarchycn ime apply)"
failures=$((failures + 1))
fi
if grep -sq '^DefaultIM=rime$' "$HOME/.config/fcitx5/profile"; then
echo "PASS ime: rime is the default input method"
else
echo "FAIL ime: DefaultIM is not rime"
failures=$((failures + 1))
fi
env_file="/usr/share/omarchy/default/environment.d/10-omarchy-fcitx.conf"
if [[ -f $env_file ]] || [[ -f $OMARCHY_PATH/default/environment.d/10-omarchy-fcitx.conf ]]; then
echo "PASS ime: fcitx environment file present"
else
echo "FAIL ime: fcitx environment file missing"
failures=$((failures + 1))
fi
trigger=$(grep -sA2 '^\[Hotkey/TriggerKeys\]' "$HOME/.config/fcitx5/config" | grep -sE '^0=' | cut -d= -f2- || true)
if [[ $trigger == "Super+space" ]] && ! grep -sq 'OmarchyCN ime hotkey begin' "$HOME/.config/hypr/bindings.lua"; then
echo "FAIL ime: Super+space 与 Omarchy 菜单冲突 (run: omarchycn ime hotkey super-space)"
failures=$((failures + 1))
else
echo "PASS ime: trigger key ${trigger:-Control+space (fcitx5 default)}"
fi
if pgrep -x fcitx5 > /dev/null 2>&1; then
echo "PASS ime: fcitx5 running"
else
echo "INFO ime: fcitx5 not running (headless session?)"
fi
exit $((failures > 0 ? 1 : 0))
+148
View File
@@ -0,0 +1,148 @@
#!/bin/bash
# omarchy:summary=Install or remove the OmarchyCN layer on an existing Omarchy
# omarchy:args=[--uninstall]
# omarchy:examples=omarchy-cn-install-overlay | omarchy-cn-install-overlay --uninstall
# Bootstraps onto a foreign OMARCHY_PATH, so it resolves its own checkout root.
set -euo pipefail
SRC=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
DEST="${OMARCHY_PATH:?OMARCHY_PATH must point at the target Omarchy install}"
MANIFEST="$HOME/.local/state/omarchycn/overlay-manifest"
run_in() {
local root="$1"
shift
if [[ -w $root ]]; then
"$@"
else
sudo "$@"
fi
}
valid_entry() {
[[ $1 == "cn" || $1 =~ ^bin/(omarchycn|omarchy-cn-[a-z0-9-]+)$ ]]
}
MENU_EXT="$HOME/.config/omarchy/extensions/omarchy-menu.jsonc"
MENU_MARKER="// OmarchyCN overlay menu extension"
# Comments-only files (upstream ships such a template) hold no user content
menu_ext_has_user_content() {
[[ -f $MENU_EXT ]] || return 1
grep -qF "$MENU_MARKER" "$MENU_EXT" && return 1
[[ -n $(grep -vE '^[[:space:]]*(//.*)?$' "$MENU_EXT" | tr -d '[:space:]{}') ]]
}
install_menu_extension() {
if menu_ext_has_user_content; then
echo "跳过菜单扩展:$MENU_EXT 已有用户内容,请手动合并 default/omarchy/omarchy-menu.jsonc 的 OmarchyCN 段"
return 0
fi
mkdir -p "${MENU_EXT%/*}"
{
echo "$MENU_MARKER"
echo "{"
sed -n '/^ \/\/ OmarchyCN$/,$p' "$SRC/default/omarchy/omarchy-menu.jsonc" | sed '$d'
echo "}"
} > "$MENU_EXT"
echo "OmarchyCN 菜单扩展已写入 $MENU_EXT"
}
remove_menu_extension() {
if [[ -f $MENU_EXT ]] && grep -qF "$MENU_MARKER" "$MENU_EXT"; then
rm -f "$MENU_EXT"
fi
}
uninstall() {
if [[ ! -f $MANIFEST ]]; then
echo "No overlay manifest at $MANIFEST" >&2
exit 1
fi
local dest="" rel=""
dest=$(grep -m1 '^dest=' "$MANIFEST" | cut -d= -f2-)
if [[ -z $dest || ! -d $dest ]]; then
echo "Manifest has no valid dest= record" >&2
exit 1
fi
# Validate every entry before deleting anything
while IFS= read -r rel; do
[[ $rel == source=* || $rel == dest=* ]] && continue
if ! valid_entry "$rel"; then
echo "Refusing suspicious manifest entry: $rel (nothing deleted)" >&2
exit 1
fi
done < "$MANIFEST"
while IFS= read -r rel; do
[[ $rel == source=* || $rel == dest=* ]] && continue
if [[ $rel == "cn" ]]; then
run_in "$dest" rm -rf "$dest/cn"
else
run_in "$dest" rm -f "$dest/$rel"
fi
done < "$MANIFEST"
remove_menu_extension
rm -f "$MANIFEST"
echo "OmarchyCN overlay removed from $dest"
}
install() {
if [[ ! -f $DEST/version || ! -x $DEST/bin/omarchy ]]; then
echo "No Omarchy install found at $DEST" >&2
exit 1
fi
if [[ $SRC == "$DEST" ]]; then
echo "Source checkout and target are the same tree; nothing to overlay" >&2
exit 1
fi
# Reinstall: remove the previous overlay first so upgrades leave no residue
if [[ -f $MANIFEST ]]; then
uninstall
fi
if [[ -e $DEST/cn ]]; then
echo "Refusing to take over unmanaged $DEST/cn" >&2
exit 1
fi
local f rel entries=()
for f in "$SRC/bin/omarchycn" "$SRC"/bin/omarchy-cn-*; do
rel="bin/${f##*/}"
if [[ -e $DEST/$rel ]]; then
echo "Refusing to overwrite unmanaged $DEST/$rel" >&2
exit 1
fi
entries+=("$rel")
done
# Manifest is the intent record, published BEFORE copying: if a copy fails
# midway, a rerun sees the manifest, uninstalls the partial state, retries.
mkdir -p "${MANIFEST%/*}"
{
echo "source=$SRC"
echo "dest=$DEST"
echo "cn"
printf '%s\n' "${entries[@]}"
} > "$MANIFEST"
run_in "$DEST" cp -r "$SRC/cn" "$DEST/cn"
for rel in "${entries[@]}"; do
run_in "$DEST" cp "$SRC/$rel" "$DEST/$rel"
done
install_menu_extension
echo "OmarchyCN overlay installed into $DEST (${#entries[@]} commands)"
echo "Verify: omarchy cn version"
}
if [[ ${1:-} == "--uninstall" ]]; then
uninstall
else
install
fi
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# omarchy:summary=Generate zh_CN.UTF-8 and en_US.UTF-8 locales
# omarchy:examples=omarchycn locale apply
# omarchy:requires-sudo=true
set -euo pipefail
for loc in zh_CN en_US; do
if ! grep -qE "^${loc}\.UTF-8 UTF-8" /etc/locale.gen; then
echo "${loc}.UTF-8 UTF-8" | sudo tee -a /etc/locale.gen > /dev/null
fi
done
sudo locale-gen
for loc in zh_CN en_US; do
if ! locale -a | grep -qiE "^${loc}\.utf-?8$"; then
echo "FAIL locale: ${loc}.UTF-8 not generated" >&2
exit 1
fi
echo "PASS locale: ${loc}.UTF-8"
done
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
# omarchy:summary=Apply a mirror profile or pin one mirror for pacman
# omarchy:args=<china|official|mirror-id>
# omarchy:examples=omarchycn mirror apply china | omarchycn mirror apply official | omarchycn mirror apply tuna
# omarchy:requires-sudo=true
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
target="${1:?usage: omarchycn mirror apply <china|official|mirror-id>}"
case "$target" in
china)
echo "Benchmarking China mirrors..."
mapfile -t cn_ids < <(cn_mirror_ids_by_region cn)
mapfile -t ranked < <(cn_mirror_rank "${cn_ids[@]}")
if (( ${#ranked[@]} == 0 )); then
echo "No healthy China mirror reachable; keeping current mirrorlist" >&2
exit 1
fi
# Primary + up to two backups
cn_mirror_write_list china "${ranked[@]:0:3}"
;;
official)
cn_mirror_write_list official geo worldwide
;;
*)
url=$(cn_mirror_url "$target")
if [[ -z $url ]]; then
echo "Unknown mirror id: $target (see: omarchycn mirror benchmark)" >&2
exit 1
fi
cn_mirror_write_list "pin:$target" "$target"
;;
esac
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# omarchy:summary=Benchmark Arch mirrors for speed, latency, and sync freshness
# omarchy:examples=omarchycn mirror benchmark
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
printf '%-11s %-22s %12s %8s %10s\n' "ID" "NAME" "SPEED" "TTFB" "SYNC"
for id in $(cn_mirror_ids); do
url=$(cn_mirror_url "$id")
read -r speed ttfb age < <(cn_mirror_probe "$url")
if (( speed > 0 )); then
speed_h="$((speed / 1024)) KB/s"
else
speed_h="FAIL"
fi
case "$age" in
stale) sync_h="STALE" ;;
unknown) sync_h="?" ;;
*) sync_h="$((age / 60))m ago" ;;
esac
printf '%-11s %-22s %12s %7ss %10s\n' "$id" "$(cn_mirror_name "$id")" "$speed_h" "${ttfb:0:5}" "$sync_h"
done
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# omarchy:summary=Pin pacman to a single mirror
# omarchy:args=<mirror-id>
# omarchy:examples=omarchycn mirror pin tuna
# omarchy:requires-sudo=true
set -euo pipefail
exec omarchy-cn-mirror-apply "${1:?usage: omarchycn mirror pin <mirror-id>}"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# omarchy:summary=Restore the most recent mirrorlist backup
# omarchy:examples=omarchycn mirror restore
# omarchy:requires-sudo=true
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
backups=("$CN_MIRRORLIST".omarchycn-bak-*)
if [[ ! -e ${backups[0]} ]]; then
echo "No OmarchyCN mirrorlist backups found" >&2
exit 1
fi
latest="${backups[-1]}"
sudo cp "$latest" "$CN_MIRRORLIST"
rm -f "$CN_PROFILE_FILE"
echo "Restored $CN_MIRRORLIST from ${latest##*/}"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# omarchy:summary=Show current pacman mirror profile and servers
# omarchy:examples=omarchycn mirror status
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/mirror.sh"
if [[ -f $CN_PROFILE_FILE ]]; then
echo "Profile: $(<"$CN_PROFILE_FILE")"
else
echo "Profile: not managed by OmarchyCN"
fi
echo "Mirrorlist: $CN_MIRRORLIST"
echo "Servers:"
grep -E '^Server' "$CN_MIRRORLIST" | sed 's/^/ /'
backups=("$CN_MIRRORLIST".omarchycn-bak-*)
if [[ -e ${backups[0]} ]]; then
echo "Backups: ${#backups[@]} (latest: ${backups[-1]##*/})"
fi
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# omarchy:summary=Restore development registry configs from a backup stamp
# omarchy:args=<stamp>
# omarchy:examples=omarchycn restore config 20260824-184243
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
stamp="${1:?usage: omarchycn restore config <stamp> (see: omarchycn restore list)}"
dir="$CN_DM_BACKUP_ROOT/$stamp"
if [[ ! -d $dir ]]; then
echo "No backup at $dir" >&2
exit 1
fi
restored=0
for target in "${CN_DM_TARGETS[@]}"; do
dest=$(cn_dm_config_file "$target")
src="$dir/${dest##*/}"
if [[ -f $src && $dest != /etc/* ]]; then
mkdir -p "${dest%/*}"
cp "$src" "$dest"
echo "restored $target: $dest"
restored=$((restored + 1))
fi
done
if (( restored == 0 )); then
echo "Nothing restored from $dir (system files like docker are not auto-restored)" >&2
exit 1
fi
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# omarchy:summary=List OmarchyCN configuration backups
# omarchy:examples=omarchycn restore list
set -euo pipefail
source "$OMARCHY_PATH/cn/lib/dev-mirror.sh"
echo "Dev-mirror backups ($CN_DM_BACKUP_ROOT):"
if [[ -d $CN_DM_BACKUP_ROOT ]]; then
for dir in "$CN_DM_BACKUP_ROOT"/*/; do
[[ -d $dir ]] || continue
stamp="${dir%/}"
stamp="${stamp##*/}"
echo " $stamp: $(ls "$dir" | tr '\n' ' ')"
done
else
echo " (none)"
fi
echo "Mirrorlist backups (/etc/pacman.d):"
found="false"
for f in /etc/pacman.d/mirrorlist.omarchycn-bak-*; do
if [[ -e $f ]]; then
echo " ${f##*/}"
found="true"
fi
done
if [[ $found == "false" ]]; then
echo " (none)"
fi
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# omarchy:summary=First-run wizard: language, timezone, scale, Chinese env, mirrors, apps, AI
# omarchy:examples=omarchycn setup
set -euo pipefail
if [[ ! -t 0 ]]; then
echo "omarchycn setup 需要交互终端" >&2
exit 1
fi
STATE_DIR="$HOME/.local/state/omarchycn/setup-done"
mkdir -p "$STATE_DIR"
run_step() {
local step="$1" title="$2"
shift 2
if [[ -f $STATE_DIR/$step ]]; then
gum confirm "「$title」已完成,重新运行?" --default=false || return 0
elif ! gum confirm "运行「$title」?(可跳过,稍后重进)"; then
return 0
fi
echo "==> $title"
if "$@"; then
touch "$STATE_DIR/$step"
else
gum confirm "「$title」失败,继续后面的步骤?" || exit 1
fi
}
step_language() {
local choice env_file="$HOME/.config/environment.d/90-omarchycn-lang.conf"
choice=$(gum choose --header "界面语言 LANG" "zh_CN.UTF-8" "en_US.UTF-8" "保持当前")
if [[ $choice == "保持当前" ]]; then
return 0
fi
mkdir -p "${env_file%/*}"
echo "LANG=$choice" > "$env_file"
echo "LANG=$choice 写入 $env_file(重登录生效)"
}
step_timezone() {
local choice
choice=$(gum choose --header "时区" "Asia/Shanghai" "保持当前" "手动输入")
if [[ $choice == "保持当前" ]]; then
return 0
fi
if [[ $choice == "手动输入" ]]; then
choice=$(timedatectl list-timezones | gum filter --header "选择时区")
fi
sudo timedatectl set-timezone "$choice"
echo "时区: $(timedatectl show -p Timezone --value)"
}
step_scale() {
local choice
choice=$(gum choose --header "显示缩放" "1.0" "1.25" "1.5" "1.6" "1.75" "2.0" "保持当前")
if [[ $choice == "保持当前" ]]; then
return 0
fi
omarchy-cn-display-scale "$choice"
}
step_hotkey() {
local choice
choice=$(gum choose --header "输入法切换键" "ctrl-space" "super-space")
omarchy-cn-ime-hotkey "$choice"
}
step_mirror() {
local choice
choice=$(gum choose --header "pacman 镜像策略" "china" "official")
omarchy-cn-mirror-apply "$choice"
}
step_dev_mirror() {
local choice
choice=$(gum choose --header "开发工具镜像(npm/pip/cargo/go/gem" "china" "official")
omarchy-cn-dev-mirror-apply "$choice" --target npm,pip,cargo,go,gem
}
step_apps() {
local catalog picks app rc=0
catalog=$(omarchy-cn-app-list | tail -n +2 | awk '{print $1}') || return 1
picks=$(gum choose --no-limit --header "选择要安装的国内应用(空格多选,回车确认)" <<<"$catalog" || true)
for app in $picks; do
omarchy-cn-app-install "$app" || rc=1
done
return $rc
}
step_privacy() {
echo "隐私状态(无可配置项,如实告知):"
echo " - OmarchyCN 不含遥测代码,默认无任何数据上报"
echo " - 诊断信息仅在你手动运行 doctor 时本地生成,不上传"
gum confirm "已了解" --affirmative "了解" --negative "" || true
}
run_step language "语言 Language" step_language
run_step timezone "时区 Timezone" step_timezone
run_step scale "显示缩放 Display Scale" step_scale
run_step locale "中文 Locale 生成" omarchy-cn-locale-apply
run_step fonts "中文字体与 fallback" omarchy-cn-font-apply
run_step ime "Fcitx5 + Rime 输入法" omarchy-cn-ime-apply
run_step hotkey "输入法切换键" step_hotkey
run_step mirror "pacman 镜像" step_mirror
run_step dev-mirror "开发工具镜像" step_dev_mirror
run_step apps "国内应用" step_apps
run_step ai "AI HubHarness/Provider/Key" omarchy-cn-ai-setup
run_step privacy "隐私与诊断" step_privacy
echo
echo "==> 最终检查"
if omarchy-cn-doctor all; then
echo "OmarchyCN 初始化完成。随时可用 omarchycn setup 重进任一步骤。"
else
echo "初始化步骤已执行,但诊断存在失败项(见上),修复后可重跑 omarchycn setup" >&2
exit 1
fi
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# omarchy:summary=Show OmarchyCN version, paths, and configured state
# omarchy:examples=omarchycn status
set -euo pipefail
echo "OmarchyCN $(omarchy-cn-version)"
echo "Omarchy upstream: $(<"$OMARCHY_PATH/version")"
echo "Root: $OMARCHY_PATH"
config="$HOME/.config/omarchycn"
state="$HOME/.local/state/omarchycn"
echo "User config: $config $([[ -d $config ]] && echo "(present)" || echo "(not created)")"
echo "State: $state $([[ -d $state ]] && echo "(present)" || echo "(not created)")"
if [[ -f $config/mirror-profile ]]; then
echo "Mirror profile: $(<"$config/mirror-profile")"
else
echo "Mirror profile: not configured"
fi
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# omarchy:summary=Update an overlay install: pull source, reinstall, run migrations
# omarchy:examples=omarchycn update
set -euo pipefail
MANIFEST="$HOME/.local/state/omarchycn/overlay-manifest"
MIGRATION_DONE_DIR="$HOME/.local/state/omarchycn/migrations-done"
if [[ ! -f $MANIFEST ]]; then
echo "Not an overlay install (no $MANIFEST)." >&2
echo "Package-based installs update through 'omarchy update' / pacman." >&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 mechanism: its update
# command cannot switch back, stranding the install
if [[ ! -f $src/bin/omarchy-cn-channel ]]; 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"
# Run each not-yet-completed migration in filename order, one marker per file
if [[ -d $src/cn/migrations ]]; then
mkdir -p "$MIGRATION_DONE_DIR"
for m in "$src"/cn/migrations/*.sh; do
[[ -f $m ]] || continue
name="${m##*/}"
if [[ ! -f $MIGRATION_DONE_DIR/$name ]]; then
echo "Migration: $name"
bash -euo pipefail "$m"
touch "$MIGRATION_DONE_DIR/$name"
fi
done
fi
echo "OmarchyCN update complete ($(omarchy-cn-version 2>/dev/null || echo unknown))"
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# omarchy:summary=Show the OmarchyCN version
# omarchy:examples=omarchycn version
set -euo pipefail
upstream=$(<"$OMARCHY_PATH/version")
release=$(<"$OMARCHY_PATH/cn/release")
echo "${upstream}-cn.${release}"
Executable
+8
View File
@@ -0,0 +1,8 @@
#!/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
@@ -0,0 +1,14 @@
{
"_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
@@ -0,0 +1,26 @@
{
"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
@@ -0,0 +1,15 @@
[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
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<!-- 日文/韩文标签内容保持原生字形(仅约束 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>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>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>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>
<!-- 无语言标签的任意字体请求:汉字回退到 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
@@ -0,0 +1,32 @@
-----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
@@ -0,0 +1,16 @@
-----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-----
+159
View File
@@ -0,0 +1,159 @@
# 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"
}
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)
fast=$(cn_ai_fast_model "$provider")
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
@@ -0,0 +1,138 @@
# 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
}
+82
View File
@@ -0,0 +1,82 @@
# 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_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"
}
+12
View File
@@ -0,0 +1,12 @@
{
"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" }
]
}
+61
View File
@@ -0,0 +1,61 @@
{
"_verified": "2026-08-24. level=stable: adapter 渲染由 test/shell.d/omarchycn-ai-test.sh mock 回归覆盖; level=official-doc: 官方集成文档存在,native CLI 自管配置",
"combos": {
"claude-code": {
"deepseek": {
"level": "stable",
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code/",
"adapter": true
},
"kimi": {
"level": "stable",
"doc": "https://www.kimi.com/code/docs/en/third-party-tools/claude-code",
"adapter": true
},
"zai": {
"level": "stable",
"doc": "https://docs.z.ai/devpack/tool/claude",
"adapter": true
}
},
"codex": {
"deepseek": {
"level": "stable",
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/codex/",
"models": [
"deepseek-v4-flash",
"deepseek-v4-pro",
"deepseek-v4-flash-vision-exp"
],
"catalog": "codex-deepseek-models.json",
"adapter": true
}
},
"opencode": {
"deepseek": {
"level": "stable",
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/opencode/",
"adapter": true
},
"zai": {
"level": "official-doc",
"doc": "https://docs.z.ai/scenario-example/develop-tools/opencode",
"adapter": false
}
},
"kimi-code": {
"kimi": {
"level": "official-doc",
"doc": "https://www.kimi.com/code/docs/en/",
"adapter": true
}
},
"deep-code": {
"deepseek": {
"level": "official-doc",
"doc": "https://api-docs.deepseek.com/quick_start/agent_integrations/deepcode/",
"adapter": true
}
}
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"_verified": "2026-08-24",
"harnesses": {
"claude-code": {
"display_name": "Claude Code",
"command": "claude",
"install": "omarchy-mise-install claude",
"config_method": "env",
"protocol": "anthropic"
},
"codex": {
"display_name": "Codex",
"command": "codex",
"install": "omarchy-mise-install codex",
"config_method": "env",
"protocol": "openai"
},
"opencode": {
"display_name": "OpenCode",
"command": "opencode",
"install": "omarchy-mise-install opencode",
"config_method": "env",
"protocol": "anthropic"
},
"kimi-code": {
"display_name": "Kimi Code CLI",
"command": "kimi",
"install": "doc:https://www.kimi.com/code/docs/en/",
"config_method": "native",
"protocol": "native"
},
"deep-code": {
"display_name": "Deep Code",
"command": "deepcode",
"install": "omarchy-mise-install npm:@vegamo/deepcode-cli deepcode",
"config_method": "native",
"protocol": "native"
}
}
}
+45
View File
@@ -0,0 +1,45 @@
{
"_verified": "2026-08-24, from official docs (see OmarchyCN PRD §27)",
"providers": {
"deepseek": {
"display_name": "DeepSeek",
"auth": ["api_key"],
"key_url": "https://platform.deepseek.com/api_keys",
"endpoints": {
"anthropic": "https://api.deepseek.com/anthropic",
"openai": "https://api.deepseek.com"
},
"models": [
{ "id": "deepseek-v4-pro[1m]", "aliases": ["default-coding"], "capabilities": ["tools", "streaming", "reasoning"] },
{ "id": "deepseek-v4-flash", "aliases": ["fast"], "capabilities": ["tools", "streaming"] }
]
},
"kimi": {
"display_name": "Kimi (Moonshot)",
"auth": ["api_key"],
"key_url": "https://www.kimi.com/code",
"endpoints": {
"anthropic": "https://api.kimi.com/coding/",
"openai": "https://api.moonshot.cn/v1"
},
"models": [
{ "id": "kimi-for-coding", "aliases": ["default-coding"], "capabilities": ["tools", "streaming", "reasoning"] },
{ "id": "kimi-for-coding-highspeed", "aliases": ["fast"], "capabilities": ["tools", "streaming"] },
{ "id": "kimi-k2.6", "aliases": ["general"], "capabilities": ["tools", "streaming"], "protocol": "openai" }
]
},
"zai": {
"display_name": "Z.AI / GLM",
"auth": ["api_key"],
"key_url": "https://z.ai/manage-apikey/apikey-list",
"endpoints": {
"anthropic": "https://api.z.ai/api/anthropic"
},
"models": [
{ "id": "glm-5.2[1m]", "aliases": ["default-coding"], "capabilities": ["tools", "streaming", "reasoning"] },
{ "id": "GLM-5.3", "aliases": ["latest"], "capabilities": ["tools", "streaming", "reasoning"] },
{ "id": "GLM-4.7", "aliases": ["fast"], "capabilities": ["tools", "streaming"] }
]
}
}
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
3
+29
View File
@@ -365,4 +365,33 @@
"update.hardware.trackpad": {"icon":"󰟸","label":"Trackpad","action":"omarchy-launch-floating-terminal-with-presentation omarchy-restart-trackpad"},
"update.password.drive": {"icon":"","label":"Drive Encryption","action":"omarchy-launch-floating-terminal-with-presentation omarchy-drive-password"},
"update.password.user": {"icon":"","label":"User","action":"omarchy-launch-floating-terminal-with-presentation passwd"},
// OmarchyCN
"omarchycn": {"icon":"","label":"OmarchyCN"},
"omarchycn.mirrors": {"icon":"󰇧","label":"镜像 Mirrors"},
"omarchycn.mirrors.china": {"icon":"","label":"中国镜像 China","checked":"[[ \"$(cat ~/.config/omarchycn/mirror-profile 2>/dev/null)\" == china ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-mirror-apply china'"},
"omarchycn.mirrors.official": {"icon":"󰇩","label":"官方镜像 Official","checked":"[[ \"$(cat ~/.config/omarchycn/mirror-profile 2>/dev/null)\" == official ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-mirror-apply official'"},
"omarchycn.mirrors.benchmark": {"icon":"󰓅","label":"测速 Benchmark","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-mirror-benchmark"},
"omarchycn.mirrors.dev-china": {"icon":"","label":"开发工具国内源 Dev China","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-dev-mirror-apply china'"},
"omarchycn.mirrors.dev-official": {"icon":"","label":"开发工具官方源 Dev Official","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-dev-mirror-apply official'"},
"omarchycn.mirrors.restore": {"icon":"󰦛","label":"恢复 Restore","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-mirror-restore"},
"omarchycn.chinese": {"icon":"󰗊","label":"中文环境 Chinese"},
"omarchycn.chinese.ime": {"icon":"󰌌","label":"输入法 Fcitx5+Rime","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-ime-apply"},
"omarchycn.chinese.hotkey-ctrl": {"icon":"󰌏","label":"切换键 Ctrl+Space","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-ime-hotkey ctrl-space'"},
"omarchycn.chinese.hotkey-super": {"icon":"󰌏","label":"切换键 Super+Space","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-ime-hotkey super-space'"},
"omarchycn.chinese.fonts": {"icon":"","label":"中文字体 Fonts","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-font-apply"},
"omarchycn.chinese.locale": {"icon":"","label":"中文 Locale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-locale-apply"},
"omarchycn.display": {"icon":"󰍹","label":"显示缩放 Scale"},
"omarchycn.display.s10": {"icon":"󰍹","label":"1.0","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.0'"},
"omarchycn.display.s125": {"icon":"󰍹","label":"1.25","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.25'"},
"omarchycn.display.s15": {"icon":"󰍹","label":"1.5","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.5'"},
"omarchycn.display.s16": {"icon":"󰍹","label":"1.6","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.6'"},
"omarchycn.display.s175": {"icon":"󰍹","label":"1.75","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 1.75'"},
"omarchycn.display.s20": {"icon":"󰍹","label":"2.0","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-display-scale 2.0'"},
"omarchycn.diagnostics": {"icon":"󰨮","label":"诊断 Diagnostics"},
"omarchycn.diagnostics.doctor": {"icon":"󰨮","label":"System Doctor","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor all'"},
"omarchycn.diagnostics.mirror-fix": {"icon":"󰇧","label":"镜像修复 Mirror Fix","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-cn-doctor mirror --fix'"},
"omarchycn.diagnostics.ime": {"icon":"󰌌","label":"输入法状态 IME Status","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-ime-status"},
"omarchycn.update": {"icon":"","label":"更新 Update","when":"[[ -f ~/.local/state/omarchycn/overlay-manifest ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-update"},
"omarchycn.about": {"icon":"","label":"关于 About","action":"omarchy-launch-floating-terminal-with-presentation omarchy-cn-status"},
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

+41
View File
@@ -0,0 +1,41 @@
# [omarchycn] 软件仓库
OmarchyCN 的 pacman 仓库托管在 Gitea Arch package registry,每个上传的包由
registry 密钥自动签名,数据库同源生成。
## 接入
```bash
# 1. 导入并信任 registry 公钥(首次)
curl -sSf https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/repository.key -o /tmp/omarchycn-reg.key
sudo pacman-key --add /tmp/omarchycn-reg.key
sudo pacman-key --lsign-key 74DCF57ACD812B24D959F146BD386048867B33B4
# 2. 添加仓库
cat << 'EOF' | sudo tee -a /etc/pacman.conf
[omarchycn]
SigLevel = Required DatabaseOptional
Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchycn/x86_64
EOF
# 3. 安装 keyring(含 registry 与 release 两把公钥及信任配置)
sudo pacman -Syu omarchycn-keyring
```
## 密钥
| 用途 | 指纹 |
|---|---|
| Registry 包签名(Gitea 自动) | `74DCF57ACD812B24D959F146BD386048867B33B4` |
| Release 产物签名(ISO/清单) | `04490F065F6ADD262A7243506EDF7B8603B5D247`(见 [release-signing.md](release-signing.md) |
## 发布包
维护者构建与上传(keyring 示例):
```bash
./packages/build-keyring.sh
curl -X PUT -H "Authorization: token <token>" \
--upload-file packages/out/omarchycn-keyring-<ver>-any.pkg.tar.zst \
https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchycn
```
+30
View File
@@ -0,0 +1,30 @@
# Release Checklist
每次发布 `<upstream>-cn.<n>` 按序执行,任一步失败即停止:
1. `./test/all`:相对上游基线零新增失败;`test/shell.d/omarchycn-*.sh` 全绿
2.`--local-source` 重建 ISO`packages/omarchy-pkgs-cn.patch` 已应用到 omarchy-pkgs
3. 验证 omarchy-dev 包含 cn 层(`tar -tf … | grep usr/share/omarchy/cn/`
4. QEMU OVMF UEFI 冒烟:进入安装器欢迎屏
5. `sha256sum``SHA256SUMS.txt`;签名 SUMS 与 ISO(发布子钥)
6. SBOM 两份:syftlive airootfs+ 离线仓库 .PKGINFO 采集
7. `release.json`(版本、双向提交、包版本表、迁移列表、min_compatible、产物清单)→ 签名 release.json
8. 建 tag 与 Release,上传全部产物,Release Notes 写明上游基线与已知问题
9. 匿名回读已发布 ISO 并 sha256 复核 == 本地构建值
10. `cn/release` 数字 +1,提交
## 版本规则
`<omarchy-upstream-version>-cn.<n>``min_compatible` 只在有破坏性迁移时前移。
# 上游同步 SLA
- `upstream-sync.yml` 每日拉取 basecamp/omarchy quattro,自动开同步 PR(含试合并冲突标注)
- 常规变更:7 天内完成审查合并;上游安全修复:48 小时内
- 合并后必须重跑第 1 步测试门禁
# 安全响应
- 接报渠道见 `SECURITY.md`72 小时内确认
- 涉及发布密钥泄露:按 `docs/release-signing.md` 轮换流程处理,吊销并公告
- 修复发布走本清单完整流程,不走捷径
+35
View File
@@ -0,0 +1,35 @@
# Release 签名与验证
OmarchyCN 发布产物(ISO、SHA256SUMS、release.json)使用 PGP 签名。
## 当前密钥
| 用途 | 指纹 | 有效期 |
|---|---|---|
| 主钥(仅认证) | `04490F065F6ADD262A7243506EDF7B8603B5D247` | 2026-08-24 → 2028-08-23 |
| 发布签名子钥 | `51AACA0BF48922597B480E7E211B9B82E17CFB67` | 同上 |
公钥文件:[`cn/keys/omarchycn-release.asc`](../cn/keys/omarchycn-release.asc)
## 验证下载
```bash
gpg --import cn/keys/omarchycn-release.asc
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
```
确认签名者指纹与上表一致。
## 密钥保管
- 主钥私钥离线保存(不进服务器、不进 CI),仅用于签发/吊销子钥
- 日常签名只使用签名子钥
- 私钥与吊销证书不入 Git;仓库只含公钥
## 轮换流程
1. 用主钥吊销旧签名子钥,签发新子钥
2. 更新 `cn/keys/omarchycn-release.asc` 与本文档指纹表
3. 在下一个 Release Notes 中公告轮换
4. 主钥泄露时:发布吊销证书、启用全新主钥、公告并重签最近产物
+58 -15
View File
@@ -24,6 +24,49 @@
OMARCHY_FORM_BACK=1
OMARCHY_FORM_SIGNAL=130
# Prompt strings; the ISO's graphical console exports OMARCHY_INSTALL_LANG=zh
if [[ ${OMARCHY_INSTALL_LANG:-} == zh ]]; then
OMARCHY_L_KEYBOARD_HEADER="选择键盘布局"
OMARCHY_L_USERNAME_PROMPT="用户名> "
OMARCHY_L_USERNAME_HINT="仅限字母数字、不含空格(如 dhh)"
OMARCHY_L_USERNAME_RESERVED="该用户名已被系统保留"
OMARCHY_L_USERNAME_TAKEN="该用户名在本机已存在"
OMARCHY_L_USERNAME_INVALID="用户名必须为字母数字且不含空格"
OMARCHY_L_PASSWORD_PROMPT="密码> "
OMARCHY_L_PASSWORD_HINT="用于用户与 root,启用加密时也用于磁盘解锁"
OMARCHY_L_CONFIRM_PROMPT="确认密码> "
OMARCHY_L_CONFIRM_HINT="需与刚输入的密码一致"
OMARCHY_L_PASSWORD_BLANK="密码不能为空!"
OMARCHY_L_PASSWORD_MISMATCH="两次输入的密码不一致!"
OMARCHY_L_FULLNAME_PROMPT="姓名> "
OMARCHY_L_IDENTITY_HINT="用于 git 身份(回车跳过)"
OMARCHY_L_EMAIL_PROMPT="邮箱> "
OMARCHY_L_HOSTNAME_PROMPT="主机名> "
OMARCHY_L_HOSTNAME_HINT="字母、数字与连字符(回车默认 omarchy)"
OMARCHY_L_HOSTNAME_INVALID="主机名须为 1-63 位字母/数字/连字符,且不能以连字符开头或结尾"
OMARCHY_L_TIMEZONE_HEADER="时区"
else
OMARCHY_L_KEYBOARD_HEADER="Select keyboard layout"
OMARCHY_L_USERNAME_PROMPT="Username> "
OMARCHY_L_USERNAME_HINT="Alphanumeric without spaces (like dhh)"
OMARCHY_L_USERNAME_RESERVED="Username is reserved for system"
OMARCHY_L_USERNAME_TAKEN="That username already exists on this machine"
OMARCHY_L_USERNAME_INVALID="Username must be alphanumeric with no spaces"
OMARCHY_L_PASSWORD_PROMPT="Password> "
OMARCHY_L_PASSWORD_HINT="Used for user + root, and disk encryption when enabled"
OMARCHY_L_CONFIRM_PROMPT="Confirm> "
OMARCHY_L_CONFIRM_HINT="Must match the password you just typed"
OMARCHY_L_PASSWORD_BLANK="Your password can't be blank!"
OMARCHY_L_PASSWORD_MISMATCH="Passwords didn't match!"
OMARCHY_L_FULLNAME_PROMPT="Full name> "
OMARCHY_L_IDENTITY_HINT="Used for git authentication (hit return to skip)"
OMARCHY_L_EMAIL_PROMPT="Email address> "
OMARCHY_L_HOSTNAME_PROMPT="Hostname> "
OMARCHY_L_HOSTNAME_HINT="Letters, digits, and dashes (or return for 'omarchy')"
OMARCHY_L_HOSTNAME_INVALID="Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash"
OMARCHY_L_TIMEZONE_HEADER="Timezone"
fi
# The English layouts lead, then everything else alphabetically. gum choose
# paginates in --height-sized pages and jumps to the page holding --selected,
# so an alphabetical English (US) landed deep enough to sit alone at the edge
@@ -94,7 +137,7 @@ omarchy_username_taken() { return 1; }
omarchy_prompt_keyboard() {
local choice status
choice=$(printf '%s\n' "$OMARCHY_KEYBOARD_LAYOUTS" | cut -d'|' -f1 |
gum choose --height 10 --selected "English (US)" --header "Select keyboard layout") && status=0 || status=$?
gum choose --height 10 --selected "English (US)" --header "$OMARCHY_L_KEYBOARD_HEADER") && status=0 || status=$?
((status == 0)) || return $status
keyboard_label="$choice"
@@ -104,19 +147,19 @@ omarchy_prompt_keyboard() {
omarchy_prompt_username() {
local status
while true; do
username=$(gum input --placeholder "Alphanumeric without spaces (like dhh)" --prompt.foreground="#845DF9" --prompt "Username> ") && status=0 || status=$?
username=$(gum input --placeholder "$OMARCHY_L_USERNAME_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_USERNAME_PROMPT") && status=0 || status=$?
((status == 0)) || return $status
if [[ "$username" =~ $OMARCHY_USERNAME_PATTERN ]]; then
if [[ "$username" =~ $OMARCHY_RESERVED_USERNAMES ]]; then
notice "Username is reserved for system" 1
notice "$OMARCHY_L_USERNAME_RESERVED" 1
elif omarchy_username_taken "$username"; then
notice "That username already exists on this machine" 1
notice "$OMARCHY_L_USERNAME_TAKEN" 1
else
return 0
fi
else
notice "Username must be alphanumeric with no spaces" 1
notice "$OMARCHY_L_USERNAME_INVALID" 1
fi
done
}
@@ -124,17 +167,17 @@ omarchy_prompt_username() {
omarchy_prompt_password() {
local status
while true; do
password=$(gum input --placeholder "Used for user + root, and disk encryption when enabled" --prompt.foreground="#845DF9" --password --prompt "Password> ") && status=0 || status=$?
password=$(gum input --placeholder "$OMARCHY_L_PASSWORD_HINT" --prompt.foreground="#845DF9" --password --prompt "$OMARCHY_L_PASSWORD_PROMPT") && status=0 || status=$?
((status == 0)) || return $status
password_confirmation=$(gum input --placeholder "Must match the password you just typed" --prompt.foreground="#845DF9" --password --prompt "Confirm> ") && status=0 || status=$?
password_confirmation=$(gum input --placeholder "$OMARCHY_L_CONFIRM_HINT" --prompt.foreground="#845DF9" --password --prompt "$OMARCHY_L_CONFIRM_PROMPT") && status=0 || status=$?
((status == 0)) || return $status
if [[ -n "$password" && "$password" == "$password_confirmation" ]]; then
return 0
elif [[ -z "$password" ]]; then
notice "Your password can't be blank!" 1
notice "$OMARCHY_L_PASSWORD_BLANK" 1
else
notice "Passwords didn't match!" 1
notice "$OMARCHY_L_PASSWORD_MISMATCH" 1
fi
done
}
@@ -143,16 +186,16 @@ omarchy_prompt_password() {
# only Esc/Ctrl+C end the prompt early.
omarchy_prompt_identity() {
local status
full_name=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Full name> ") && status=0 || status=$?
full_name=$(gum input --placeholder "$OMARCHY_L_IDENTITY_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_FULLNAME_PROMPT") && status=0 || status=$?
((status == 0)) || return $status
email_address=$(gum input --placeholder "Used for git authentication (hit return to skip)" --prompt.foreground="#845DF9" --prompt "Email address> ") && status=0 || status=$?
email_address=$(gum input --placeholder "$OMARCHY_L_IDENTITY_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_EMAIL_PROMPT") && status=0 || status=$?
return $status
}
omarchy_prompt_hostname() {
local status
while true; do
hostname=$(gum input --placeholder "Letters, digits, and dashes (or return for 'omarchy')" --prompt.foreground="#845DF9" --prompt "Hostname> ") && status=0 || status=$?
hostname=$(gum input --placeholder "$OMARCHY_L_HOSTNAME_HINT" --prompt.foreground="#845DF9" --prompt "$OMARCHY_L_HOSTNAME_PROMPT") && status=0 || status=$?
((status == 0)) || return $status
if [[ -z $hostname ]]; then
@@ -161,7 +204,7 @@ omarchy_prompt_hostname() {
elif [[ "$hostname" =~ $OMARCHY_HOSTNAME_PATTERN ]]; then
return 0
else
notice "Hostname must be 1-63 letters, digits, or dashes, and cannot start or end with a dash" 1
notice "$OMARCHY_L_HOSTNAME_INVALID" 1
fi
done
}
@@ -173,9 +216,9 @@ omarchy_prompt_timezone() {
guess=$(tzupdate -p 2>/dev/null) || guess=""
if [[ -n $guess ]]; then
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$guess" --header "Timezone") && status=0 || status=$?
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$guess" --header "$OMARCHY_L_TIMEZONE_HEADER") && status=0 || status=$?
else
timezone=$(timedatectl list-timezones | gum filter --height 10 --header "Timezone") && status=0 || status=$?
timezone=$(timedatectl list-timezones | gum filter --height 10 --header "$OMARCHY_L_TIMEZONE_HEADER") && status=0 || status=$?
fi
((status == 0)) || return $status
+8 -8
View File
@@ -1,10 +1,10 @@
▄▄▄
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄ ▄███████ ▄█▄ ▄█▄
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███▄ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███ ███ █▀ ████▄ ███
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███ ███ ███▀█▄███
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███ ███ ███ ▀████
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███ ███ █▄ ███ ▀███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀ ███████▀ ▀█▀ ▀█▀
███ █▀
+35
View File
@@ -0,0 +1,35 @@
# 安装 OmarchyCN
## 下载与校验
从 [Releases](https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/releases) 下载最新 ISO 及 `SHA256SUMS.txt``SHA256SUMS.txt.asc`
```bash
curl -sSf https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn/raw/branch/quattro/cn/keys/omarchycn-release.asc | gpg --import
gpg --verify SHA256SUMS.txt.asc SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt
```
签名主钥指纹应为 `04490F065F6ADD262A7243506EDF7B8603B5D247`(详见 `docs/release-signing.md`)。
## 写盘与启动
Linux 用 `caligula``dd`Mac/Windows 用 balenaEtcher 写入 U 盘,UEFI 模式启动,按提示完成 Omarchy 安装(磁盘、用户、加密等流程与上游一致)。
## 首次进入桌面后
```bash
omarchycn setup
```
向导依次配置:语言、时区、显示缩放、中文 locale、中文字体、Fcitx5+Rime 输入法、输入法切换键、pacman 镜像、开发工具镜像、国内应用、AI Hub、隐私说明。每一步都可跳过,中断后重跑会从未完成的步骤继续。
## 现有 Omarchy 叠加安装(Overlay
```bash
git clone https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn.git
cd omarchycn && ./bin/omarchy-cn-install-overlay
omarchy cn version # 验证
```
卸载:`omarchy-cn-install-overlay --uninstall`。更新:`omarchycn update`
+37
View File
@@ -0,0 +1,37 @@
# 镜像管理
## pacman 镜像
```bash
omarchycn mirror benchmark # 测速:延迟、吞吐、同步新鲜度
omarchycn mirror apply china # 自动选最优中国镜像(1 主 + 2 备)
omarchycn mirror apply official # 回到官方全球源
omarchycn mirror pin tuna # 固定单一镜像
omarchycn mirror status # 当前策略与镜像列表
omarchycn mirror restore # 恢复最近一次备份
```
每次写入 `/etc/pacman.d/mirrorlist` 前自动生成带时间戳备份。候选列表在 `cn/mirrors.json`
主镜像失效时:
```bash
omarchycn doctor mirror --fix # 检测并自动切换到健康镜像
```
## 开发工具镜像
覆盖 npm、pip、cargo、go、gem、docker 六个生态:
```bash
omarchycn dev-mirror list # 当前各生态源
omarchycn dev-mirror apply china # 全部切国内
omarchycn dev-mirror apply official --target npm # 指定生态切官方
omarchycn dev-mirror set npm https://my.registry/ # 自定义
omarchycn dev-mirror doctor # 连通性检查
```
写入前备份到 `~/.local/state/omarchycn/backups/dev-mirror/<时间戳>/`
恢复:`omarchycn restore list` + `omarchycn restore config <时间戳>`(仅用户级文件)。
docker 目标写 `/etc/docker/daemon.json`(需 sudo,重启 docker 生效;系统文件不参与
自动恢复,回退用 `omarchycn dev-mirror apply official --target docker`)。
+34
View File
@@ -0,0 +1,34 @@
# 中文输入与字体
## 一键配置
```bash
omarchycn locale apply # 生成 zh_CN.UTF-8 / en_US.UTF-8
omarchycn font apply # 思源黑体/宋体 + emoji + fallback 优先级
omarchycn ime apply # Fcitx5 + Rime,默认拼音
```
字体配置保证中文优先落到简体字形(含无语言标签的 Chromium/Electron 路径),
日文/韩文标签内容仍使用各自原生变体。验证:`omarchycn font status`
## 切换键
默认 `Ctrl+Space`Fcitx5 原生)。改用 `Super+Space`
```bash
omarchycn ime hotkey super-space
```
此时 Omarchy 主菜单自动迁移到 `Super+Alt+Space`(原 Apps 菜单快捷键让位,
可从主菜单进入 Apps 或在 `~/.config/hypr/bindings.lua` 自行改绑)。
切回:`omarchycn ime hotkey ctrl-space`(自动移除迁移块)。
## 排查
```bash
omarchycn ime status # 包、profile、切换键冲突、进程状态
omarchycn doctor ime
```
上游已内置 fcitx 环境变量(`INPUT_METHOD/QT_IM_MODULE/XMODIFIERS/SDL_IM_MODULE`
`omarchy-fcitx5` 用户服务,OmarchyCN 不重复配置。
+36
View File
@@ -0,0 +1,36 @@
# AI Hub
Harness(工具)与 Provider(模型服务)分层管理,配置数据在 `cn/registry/`
## 快速开始
```bash
omarchycn ai setup # 向导:Harness → Provider → 模型 → API Key → 测试
omarchycn ai launch # 以默认 Profile 启动
```
支持组合(stable = 有 mock 回归覆盖的适配器):
| Harness | DeepSeek | Kimi | Z.AI/GLM |
|---|---|---|---|
| Claude Code | stable | stable | stable |
| Codex | stable | — | — |
| OpenCode | stable | — | — |
| Kimi Code | — | 官方 CLI 自管 | — |
| Deep Code | 官方 CLI 自管 | — | — |
## 手动操作
```bash
omarchycn ai secret set deepseek # 存 KeySecret Service → pass → 0600 文件)
omarchycn ai profile create work claude-code deepseek default-coding
omarchycn ai profile use work
omarchycn ai test work # 真实连通/鉴权/模型调用测试
omarchycn ai doctor # 安装、凭据、端点诊断
omarchycn ai default work # 映射到 Super+Shift+Ctrl+A / omarchy agent
```
`ai default` 把配置持久化进 Harness 自身文件(Claude Code 的 settings.json /
Codex 的 config.toml,均 0600 并保留你的其它设置),上游快捷键即以国内模型启动。
注意:`ai test` 消耗一次极小请求(max_tokens=8),需有效 API Key 与账户额度。
+36
View File
@@ -0,0 +1,36 @@
# 更新与恢复
## 更新
- ISO 安装:系统随上游 `omarchy update`;cn 层当前随新版 ISO 迭代([omarchycn] pacman 仓库暂只分发 keyringomarchy-dev 包上仓后将改为 pacman 更新)
- Overlay 安装:`omarchycn update`(拉取源码 → 重装 overlay → 执行未跑过的 cn 迁移)
## 备份位置
| 内容 | 位置 |
|---|---|
| pacman mirrorlist | `/etc/pacman.d/mirrorlist.omarchycn-bak-<时间戳>` |
| 开发工具镜像配置 | `~/.local/state/omarchycn/backups/dev-mirror/<时间戳>/` |
| 输入法 profile | `~/.local/state/omarchycn/backups/ime/<时间戳>/` |
| 显示缩放 | `~/.config/hypr/monitors.lua.omarchycn-prev` |
| Codex/Claude 配置 | 同目录 `*.omarchycn-bak-<时间戳>` |
## 恢复
```bash
omarchycn restore list # 列出全部备份
omarchycn restore config <时间戳> # 恢复开发工具镜像配置
omarchycn mirror restore # 恢复 pacman mirrorlist
```
系统级快照与回滚沿用上游 Btrfs + Snapper 机制(见上游手册 System snapshots)。
## 诊断
```bash
omarchycn doctor # network + mirror + dev-mirror + ime
omarchycn ai doctor
omarchycn status # 版本与配置概览
```
诊断只在本地输出,不上传任何数据。
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# Builds omarchycn-keyring in a clean archlinux container; output to packages/out/
set -euo pipefail
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
docker run --rm -v "$ROOT:/src:ro" -v "$ROOT/packages/out:/out" archlinux/archlinux:latest bash -c '
set -euo pipefail
pacman -Syu --noconfirm --quiet base-devel > /dev/null
useradd -m builder
cp -r /src/packages/omarchycn-keyring /home/builder/pkg
cp /src/cn/keys/omarchycn-registry.asc /home/builder/pkg/omarchycn-registry.key
cp /src/cn/keys/omarchycn-release.asc /home/builder/pkg/omarchycn-release.key
chown -R builder /home/builder/pkg
cd /home/builder/pkg
sudo -u builder makepkg --noconfirm
install -m644 omarchycn-keyring-*.pkg.tar.zst /out/
'
echo "Built: $(ls "$ROOT"/packages/out/omarchycn-keyring-*.pkg.tar.zst)"
+787
View File
@@ -0,0 +1,787 @@
diff --git a/builder/build-iso.sh b/builder/build-iso.sh
index db8daa2..5e4ca44 100755
--- a/builder/build-iso.sh
+++ b/builder/build-iso.sh
@@ -118,7 +118,7 @@ cp "/tmp/$NODE_FILENAME" "$build_cache_dir/airootfs/opt/packages/"
# The selected omarchy-settings package is needed here so its post_install hook
# drops Omarchy's plymouthd.conf into /etc/plymouth before mkarchiso builds the
# live initramfs.
-arch_packages=(linux-t2 git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted)
+arch_packages=(linux-t2 git gum jq openssl plymouth ttfx tzupdate omarchy-keyring "$OMARCHY_SETTINGS_PACKAGE" lvm2 cryptsetup parted cage foot noto-fonts-cjk)
printf '%s\n' "${arch_packages[@]}" >> "$build_cache_dir/packages.x86_64"
# The live ISO boots linux-t2 (see airootfs/etc/mkinitcpio.d/linux-t2.preset), so
diff --git a/configs/airootfs/root/.automated_script.sh b/configs/airootfs/root/.automated_script.sh
index 16904f8..f1ec36e 100644
--- a/configs/airootfs/root/.automated_script.sh
+++ b/configs/airootfs/root/.automated_script.sh
@@ -10,7 +10,29 @@
# - COLUMNS/LINES so gum picks up real terminal size
set -euo pipefail
-[[ $(tty) == /dev/tty1 ]] || exit 0
+[[ $(tty) == /dev/tty1 || -n ${OMARCHY_CN_GUI:-} ]] || exit 0
+
+# OmarchyCN: run the installer in a CJK-capable graphical console when KMS
+# allows; otherwise fall back to the original English VT flow.
+if [[ -z ${OMARCHY_CN_GUI:-} ]] && command -v cage > /dev/null 2>&1; then
+ export XDG_RUNTIME_DIR=/run/omarchy-gui
+ mkdir -p "$XDG_RUNTIME_DIR"
+ chmod 700 "$XDG_RUNTIME_DIR"
+ rm -f "$XDG_RUNTIME_DIR/gui-started"
+ gui_status=0
+ OMARCHY_CN_GUI=1 OMARCHY_INSTALL_LANG=zh WLR_RENDERER=pixman \
+ cage -- foot -F "$0" || gui_status=$?
+ if [[ -f $XDG_RUNTIME_DIR/gui-started ]]; then
+ # GUI 已运行:退出码属于安装器,不再回退英文流程
+ exit "$gui_status"
+ fi
+ echo "图形安装环境不可用,回退英文控制台 (falling back to the English VT installer)"
+ unset OMARCHY_INSTALL_LANG XDG_RUNTIME_DIR
+fi
+
+if [[ -n ${OMARCHY_CN_GUI:-} ]]; then
+ touch "$XDG_RUNTIME_DIR/gui-started"
+fi
export OMARCHY_MIRROR="$(cat /root/omarchy_mirror)"
if [[ -f /root/omarchy_iso_ref ]]; then
diff --git a/configs/airootfs/root/configurator b/configs/airootfs/root/configurator
index 4cd3db3..24a1bf8 100644
--- a/configs/airootfs/root/configurator
+++ b/configs/airootfs/root/configurator
@@ -9,6 +9,165 @@ OMARCHY_SETTINGS_PACKAGE="${OMARCHY_SETTINGS_PACKAGE:-omarchy-settings}"
LOGO_PATH="$OMARCHY_PATH/logo.txt"
+# Installer strings; the graphical console stage exports OMARCHY_INSTALL_LANG=zh
+if [[ ${OMARCHY_INSTALL_LANG:-} == zh ]]; then
+ L_TAGLINE='华丽、现代、观点鲜明的 Linux —— 中国版'
+ L_HINT='按回车开始安装'
+ L_ABORTED='安装已中止'
+ L_RETRY='稍后可重新运行: ./.automated_script.sh'
+ L_OWNER_PREP='这将把本机准备给另一位使用者。'
+ L_OWNER_PREP2='系统现在安装,但初始设置延迟到首次开机进行。'
+ L_OWNER_CONFIRM='为另一位使用者准备这台机器?'
+ L_OWNER_YES='是,为他人准备'
+ L_OWNER_NO='否,继续本人设置'
+ L_USER_STEP='开始设置你的用户账户……'
+ L_SUMMARY_OK='以上信息正确吗?'
+ L_SUMMARY_NO='不对,去修改'
+ L_F_FIELD='项目'
+ L_F_VALUE='值'
+ L_F_USER='用户名'
+ L_F_PASS='密码'
+ L_F_NAME='姓名'
+ L_F_EMAIL='邮箱'
+ L_F_HOST='主机名'
+ L_F_TZ='时区'
+ L_F_KB='键盘'
+ L_F_SKIP='[已跳过]'
+ L_NOSPACE_STEP='磁盘 %s 可用空间不足'
+ L_NOSPACE_1='%s 可用空间 %sOmarchy 至少需要 32GB。'
+ L_NOSPACE_2='请用分区工具在该盘腾出至少 32GB,然后重试。'
+ L_NOSPACE_CONFIRM='返回安装方式选择?'
+ L_BACK='返回'
+ L_OPEN_PT='打开分区工具'
+ L_BL_STEP='检查 %s 上的 BitLocker'
+ L_BL_1='检测到 %s 存在 BitLocker 签名。'
+ L_BL_2='请在 Windows 中关闭 BitLocker 并等待解密完成后重试。'
+ L_BL_3='仅暂停 BitLocker 不够——磁盘仍处于加密状态。'
+ L_BL_ABORT='已中止:该磁盘启用了 BitLocker。'
+ L_EFI_STEP='检查 %s 上已有的 EFI 分区'
+ L_EFI_WIN='发现 Windows ESP 位于 %s——保持原样不动。'
+ L_EFI_OWN='Omarchy 将在空闲空间中创建自己的 EFI 分区。'
+ L_EFI_NEW='将在空闲空间中新建 EFI 分区。'
+ L_FREE_STEP='分析 %s 的空闲空间'
+ L_MKPART_STEP='在 %s 上创建分区'
+ L_LUKS_STEP='在 %s 上配置 LUKS2 加密'
+ L_BTRFS_STEP='创建 Btrfs 文件系统与子卷'
+ L_FREESAY='在 %s 空闲空间中安装 Omarchy。'
+ L_CTRLC_UNENC='按 Ctrl+C 改为不加密安装。'
+ L_YES_INSTALL='是,开始安装'
+ L_YES_NOENC='是,不加密安装'
+ L_NO_CHANGE='否,去修改'
+ L_CONFIRM_ON='确认安装到 %s'
+ L_DISK_STEP='选择要安装 Omarchy 的磁盘……'
+ L_DISK_HEADER='选择安装磁盘'
+ L_PT_STEP='%s 的分区工具'
+ L_PT_1='为 Omarchy 腾出未分配空间,然后写入更改并退出。'
+ L_PT_2='不要在这里创建 Omarchy 分区——目标区域保持为空闲空间即可。'
+ L_MODE_FULL='整盘安装'
+ L_MODE_FREE='空闲空间安装(保留现有数据)'
+ L_MODE_OTHER='换一块磁盘'
+ L_MODE_STEP='选择 Omarchy 的安装方式……'
+ L_MODE_HEADER='选择 %s 上的安装方式'
+ L_WIPE_WARN='磁盘将被完全覆写,数据无法恢复。'
+ L_WIPE_CONFIRM='确认覆写 %s'
+ L_TAGLINE_W=38
+ L_HINT_W=14
+ L_WELCOME='开始设置你的机器……'
+ L_OWNER_HINT='按 Ctrl+C 可改为「为他人准备这台机器」。'
+ L_CFDISK_CONFIRM='打开 %s 的分区工具?'
+ L_OPEN_CFDISK='打开 cfdisk'
+ L_DS_GPT='初始化 %s 的 GPT'
+ L_DS_ESPFLAG='标记分区 %s 为 ESP'
+ L_DS_WIPE='清除 %s 的残留签名'
+ L_DS_MKBTRFS='在 %s 创建 Btrfs 文件系统'
+ L_DS_MOUNT='挂载 %s'
+ L_DS_SUBVOL='创建子卷 %s'
+ L_DS_MOUNTROOT='挂载目标根分区'
+ L_DS_MOUNTHOME='挂载 /home'
+ L_DS_MOUNTLOG='挂载 /var/log'
+ L_DS_MOUNTCACHE='挂载软件包缓存'
+ L_DS_MKESP='在 %s 创建 ESP 文件系统'
+ L_DS_MOUNTESP='挂载 ESP'
+else
+ L_TAGLINE='Beautiful, Modern & Opinionated Linux by DHH'
+ L_HINT='Press Return to Start Install'
+ L_ABORTED='Aborted installation'
+ L_RETRY='You can retry later by running: ./.automated_script.sh'
+ L_OWNER_PREP='This prepares the machine for another owner.'
+ L_OWNER_PREP2='The system installs now, but setup is delayed until first boot.'
+ L_OWNER_CONFIRM='Prepare this machine for another owner?'
+ L_OWNER_YES='Yes, prepare for another owner'
+ L_OWNER_NO='No, keep setting up'
+ L_USER_STEP='Let'\''s setup your user account...'
+ L_SUMMARY_OK='Does this look right?'
+ L_SUMMARY_NO='No, change it'
+ L_F_FIELD='Field'
+ L_F_VALUE='Value'
+ L_F_USER='Username'
+ L_F_PASS='Password'
+ L_F_NAME='Full name'
+ L_F_EMAIL='Email address'
+ L_F_HOST='Hostname'
+ L_F_TZ='Timezone'
+ L_F_KB='Keyboard'
+ L_F_SKIP='[Skipped]'
+ L_NOSPACE_STEP='Not enough free space on %s'
+ L_NOSPACE_1='%s has %s of usable free space; Omarchy needs at least 32GB.'
+ L_NOSPACE_2='Open the partition tool to free at least 32GB, then try again.'
+ L_NOSPACE_CONFIRM='Return to installation mode?'
+ L_BACK='Back'
+ L_OPEN_PT='Open partition tool'
+ L_BL_STEP='Checking for BitLocker on %s'
+ L_BL_1='BitLocker signature detected on %s.'
+ L_BL_2='Turn BitLocker off in Windows and wait for the drive to finish decrypting, then try again.'
+ L_BL_3='Suspending BitLocker is not enough — the drive stays encrypted.'
+ L_BL_ABORT='Aborted: BitLocker is enabled on this disk.'
+ L_EFI_STEP='Checking existing EFI partitions on %s'
+ L_EFI_WIN='Found a Windows ESP at %s — leaving it untouched.'
+ L_EFI_OWN='Omarchy will create its own dedicated EFI partition in free space.'
+ L_EFI_NEW='A new EFI partition will be created in free space.'
+ L_FREE_STEP='Analyzing free space on %s'
+ L_MKPART_STEP='Creating partitions on %s'
+ L_LUKS_STEP='Setting up LUKS2 on %s'
+ L_BTRFS_STEP='Creating Btrfs filesystem and subvolumes'
+ L_FREESAY='Install Omarchy in the %s of free space.'
+ L_CTRLC_UNENC='Press Ctrl+C for unencrypted install.'
+ L_YES_INSTALL='Yes, install'
+ L_YES_NOENC='Yes, install without encryption'
+ L_NO_CHANGE='No, change it'
+ L_CONFIRM_ON='Confirm installing on %s'
+ L_DISK_STEP='Let'\''s select where to install Omarchy...'
+ L_DISK_HEADER='Select install disk'
+ L_PT_STEP='Partition tool for %s'
+ L_PT_1='Create unallocated free space for Omarchy, then write changes and quit.'
+ L_PT_2='Do not create an Omarchy partition here — leave the target area as Free space.'
+ L_MODE_FULL='Full disk install'
+ L_MODE_FREE='Free space install (alongside existing data)'
+ L_MODE_OTHER='Choose a different disk'
+ L_MODE_STEP='Let'\''s select how to install Omarchy...'
+ L_MODE_HEADER='Select installation mode on %s'
+ L_WIPE_WARN='Everything will be overwritten. There is no recovery possible.'
+ L_WIPE_CONFIRM='Confirm overwriting %s'
+ L_TAGLINE_W=44
+ L_HINT_W=29
+ L_WELCOME="Let's setup your machine..."
+ L_OWNER_HINT='Press Ctrl+C to prepare this machine for another owner.'
+ L_CFDISK_CONFIRM='Open partition tool for %s?'
+ L_OPEN_CFDISK='Open cfdisk'
+ L_DS_GPT='initializing GPT on %s'
+ L_DS_ESPFLAG='flagging partition %s as ESP'
+ L_DS_WIPE='clearing stale signatures on %s'
+ L_DS_MKBTRFS='creating the Btrfs filesystem on %s'
+ L_DS_MOUNT='mounting %s'
+ L_DS_SUBVOL='creating subvolume %s'
+ L_DS_MOUNTROOT='mounting the target root'
+ L_DS_MOUNTHOME='mounting /home'
+ L_DS_MOUNTLOG='mounting /var/log'
+ L_DS_MOUNTCACHE='mounting the package cache'
+ L_DS_MKESP='creating the ESP filesystem on %s'
+ L_DS_MOUNTESP='mounting the ESP'
+fi
+
# The setup form — keyboard, account, hostname, and timezone questions, plus the
# rules their answers are checked against — shared verbatim with the first-boot
# owner setup that finishes a deferred install. build-iso.sh vendors it out of
@@ -122,12 +281,12 @@ greeter() {
printf '\033[%d;1H' "$logo_row"
gum style --foreground 2 --padding "0 0 0 $PADDING_LEFT" "$(<"$LOGO_PATH")"
- tagline="Beautiful, Modern & Opinionated Linux by DHH"
- tpad=$(((cols - ${#tagline}) / 2)); (( tpad < 0 )) && tpad=0
+ tagline="$L_TAGLINE"
+ tpad=$(((cols - L_TAGLINE_W) / 2)); (( tpad < 0 )) && tpad=0
printf '\033[%d;%dH%s' "$tagline_row" "$((tpad + 1))" "$tagline"
- hint="Press Return to Start Install"
- hpad=$(((cols - ${#hint}) / 2)); (( hpad < 0 )) && hpad=0
+ hint="$L_HINT"
+ hpad=$(((cols - L_HINT_W) / 2)); (( hpad < 0 )) && hpad=0
printf '\033[%d;%dH\033[2m%s\033[0m' "$hint_row" "$((hpad + 1))" "$hint"
# ColorShift the logo: a green base (indexed color 2) with a cyan accent (6)
@@ -176,9 +335,9 @@ greeter() {
}
abort() {
- gum style "${1:-Aborted installation}"
+ gum style "${1:-$L_ABORTED}"
echo
- gum style "You can retry later by running: ./.automated_script.sh"
+ gum style "$L_RETRY"
exit 1
}
@@ -212,8 +371,8 @@ keyboard_form() {
while true; do
clear_logo
echo
- say "Let's setup your machine..."
- say --foreground 8 "Press Ctrl+C to prepare this machine for another owner."
+ say "$L_WELCOME"
+ say --foreground 8 "$L_OWNER_HINT"
echo
omarchy_prompt_keyboard && status=0 || status=$?
@@ -245,11 +404,11 @@ keyboard_form() {
confirm_prepare_for_another_owner() {
clear_logo
echo
- say "This prepares the machine for another owner."
- say --foreground 8 "The system installs now, but setup is delayed until first boot."
+ say "$L_OWNER_PREP"
+ say --foreground 8 "$L_OWNER_PREP2"
echo
- gum confirm --affirmative "Yes, prepare for another owner" --negative "No, keep setting up" \
- "Prepare this machine for another owner?"
+ gum confirm --affirmative "$L_OWNER_YES" --negative "$L_OWNER_NO" \
+ "$L_OWNER_CONFIRM"
}
# The user step. Deferred-provisioning installs skip it entirely — the machine's first owner
@@ -257,7 +416,7 @@ confirm_prepare_for_another_owner() {
# operator sets nothing user-specific.
user_form() {
- step "Let's setup your user account..."
+ step "$L_USER_STEP"
# Each prompt reports 0 (set), OMARCHY_FORM_BACK (Esc), or OMARCHY_FORM_SIGNAL
# (Ctrl+C). Both non-zero cases unwind to user_step, which decides what they
@@ -291,18 +450,18 @@ user_step() {
fi
# Add manual padding since gum table -p doesn't respect padding
- echo -e "Field,Value
-Username,$username
-Password,$(printf "%${#password}s" | tr ' ' '*')
-Full name,${full_name:-[Skipped]}
-Email address,${email_address:-[Skipped]}
-Hostname,$hostname
-Timezone,$timezone
-Keyboard,$keyboard" |
+ echo -e "$L_F_FIELD,$L_F_VALUE
+$L_F_USER,$username
+$L_F_PASS,$(printf "%${#password}s" | tr ' ' '*')
+$L_F_NAME,${full_name:-$L_F_SKIP}
+$L_F_EMAIL,${email_address:-$L_F_SKIP}
+$L_F_HOST,$hostname
+$L_F_TZ,$timezone
+$L_F_KB,$keyboard" |
gum table -s "," -p | sed "s/^/${PADDING_LEFT_SPACES}/"
echo
- if gum confirm --negative "No, change it" "Does this look right?"; then
+ if gum confirm --negative "$L_SUMMARY_NO" "$L_SUMMARY_OK"; then
break
else
keyboard_form
@@ -504,11 +663,11 @@ print_dry_run_files() {
# action so users have a way to actually fix the situation.
not_enough_space() {
local available="${1:-0}"
- step "Not enough free space on $disk"
- say --foreground 1 "$disk has $(to_gb $available) of usable free space; Omarchy needs at least 32GB."
- say "Open the partition tool to free at least 32GB on $disk, then try again."
+ step "$(printf "$L_NOSPACE_STEP" "$disk")"
+ say --foreground 1 "$(printf "$L_NOSPACE_1" "$disk" "$(to_gb $available)")"
+ say "$L_NOSPACE_2"
echo
- if ! gum confirm --affirmative "Back" --negative "Open partition tool" "Return to installation mode?"; then
+ if ! gum confirm --affirmative "$L_BACK" --negative "$L_OPEN_PT" "$L_NOSPACE_CONFIRM"; then
open_partition_tool
fi
return 1
@@ -518,14 +677,14 @@ not_enough_space() {
# encryption confirm. Sets the globals run_partition_execute needs. Returns 1
# to fall back to the install-mode picker.
run_partition_decide() {
- step "Checking for BitLocker on $disk"
+ step "$(printf "$L_BL_STEP" "$disk")"
local bl_part
bl_part=$(detect_bitlocker || true)
if [[ -n "$bl_part" ]]; then
- say --foreground 1 "BitLocker signature detected on $bl_part."
- say "Turn BitLocker off in Windows and wait for the drive to finish decrypting, then try again."
- say "Suspending BitLocker is not enough — the drive stays encrypted."
- abort "Aborted: BitLocker is enabled on this disk."
+ say --foreground 1 "$(printf "$L_BL_1" "$bl_part")"
+ say "$L_BL_2"
+ say "$L_BL_3"
+ abort "$L_BL_ABORT"
fi
# Omarchy always creates its own dedicated ESP in free space and never
@@ -534,17 +693,17 @@ run_partition_decide() {
# Windows ESP is far too small for our Unified Kernel Images, and a shared
# ESP forces the install unencrypted. A separate Linux ESP keeps Windows
# and Omarchy fully isolated and keeps LUKS on the table.
- step "Checking existing EFI partitions on $disk"
+ step "$(printf "$L_EFI_STEP" "$disk")"
local detected_win_esp
detected_win_esp=$(detect_windows_esp || true)
if [[ -n "$detected_win_esp" ]]; then
- say "Found a Windows ESP at $detected_win_esp — leaving it untouched."
- say "Omarchy will create its own dedicated EFI partition in free space."
+ say "$(printf "$L_EFI_WIN" "$detected_win_esp")"
+ say "$L_EFI_OWN"
else
- say "A new EFI partition will be created in free space."
+ say "$L_EFI_NEW"
fi
- step "Analyzing free space on $disk"
+ step "$(printf "$L_FREE_STEP" "$disk")"
partprobe "$disk" 2>/dev/null || true
sleep 1
@@ -619,21 +778,21 @@ run_partition_decide() {
while true; do
clear_logo
echo
- say "Install Omarchy in the $(to_gb $INSTALL_MAX_B) of free space."
+ say "$(printf "$L_FREESAY" "$(to_gb $INSTALL_MAX_B)")"
case $mode in
encrypted)
- say --foreground 8 "Press Ctrl+C for unencrypted install."
- affirmative="Yes, install"
+ say --foreground 8 "$L_CTRLC_UNENC"
+ affirmative="$L_YES_INSTALL"
;;
unencrypted)
- affirmative="Yes, install without encryption"
+ affirmative="$L_YES_NOENC"
;;
esac
echo
- gum confirm --affirmative "$affirmative" --negative "No, change it" \
- "Confirm installing on $disk"
+ gum confirm --affirmative "$affirmative" --negative "$L_NO_CHANGE" \
+ "$(printf "$L_CONFIRM_ON" "$disk")"
confirm_status=$?
case $confirm_status in
@@ -691,9 +850,9 @@ run_partition_execute() {
say "[dry] partition numbers are assigned by parted at creation, then read back"
say "[dry] encrypted=$encrypt_installation kernel=$kernel_choice"
else
- step "Creating partitions on $disk"
+ step "$(printf "$L_MKPART_STEP" "$disk")"
if $needs_mklabel; then
- disk_step "initializing GPT on $disk" parted --script "$disk" mklabel gpt
+ disk_step "$(printf "$L_DS_GPT" "$disk")" parted --script "$disk" mklabel gpt
partprobe "$disk" 2>/dev/null || true
sleep 1
fi
@@ -709,7 +868,7 @@ run_partition_execute() {
disk_abort_hook "Could not create the root partition on $disk"
root_part_num="$created_partition_number"
- disk_step "flagging partition $efi_part_num as ESP" \
+ disk_step "$(printf "$L_DS_ESPFLAG" "$efi_part_num")" \
parted --script "$disk" set "$efi_part_num" esp on
efi_dev=$(partition_path "$disk" "$efi_part_num")
@@ -725,11 +884,11 @@ run_partition_execute() {
# Both partitions are ours and brand new, but the space they occupy may
# carry signatures from whatever was deleted to free it.
- disk_step "clearing stale signatures on $efi_dev" wipefs -af "$efi_dev"
- disk_step "clearing stale signatures on $root_partition_device" wipefs -af "$root_partition_device"
+ disk_step "$(printf "$L_DS_WIPE" "$efi_dev")" wipefs -af "$efi_dev"
+ disk_step "$(printf "$L_DS_WIPE" "$root_partition_device")" wipefs -af "$root_partition_device"
if [[ "$encrypt_installation" == "true" ]]; then
- step "Setting up LUKS2 on $root_partition_device"
+ step "$(printf "$L_LUKS_STEP" "$root_partition_device")"
# Kept as pipes rather than routed through disk_step: the passphrase must
# not become an argv the process table can show. Folding stderr into
# stdout still lands any error in the install log.
@@ -746,31 +905,31 @@ run_partition_execute() {
root_mapper="$root_partition_device"
fi
- step "Creating Btrfs filesystem and subvolumes"
- disk_step "creating the Btrfs filesystem on $root_mapper" \
+ step "$L_BTRFS_STEP"
+ disk_step "$(printf "$L_DS_MKBTRFS" "$root_mapper")" \
mkfs.btrfs -f -L OMARCHY "$root_mapper"
wait_for_device "$root_mapper" || disk_abort_hook "Root device $root_mapper never appeared"
mkdir -p /mnt/btrfs-root
- disk_step "mounting $root_mapper" mount "$root_mapper" /mnt/btrfs-root
+ disk_step "$(printf "$L_DS_MOUNT" "$root_mapper")" mount "$root_mapper" /mnt/btrfs-root
for subvol in @ @home @log @pkg; do
- disk_step "creating subvolume $subvol" btrfs subvolume create "/mnt/btrfs-root/$subvol"
+ disk_step "$(printf "$L_DS_SUBVOL" "$subvol")" btrfs subvolume create "/mnt/btrfs-root/$subvol"
done
umount /mnt/btrfs-root
rmdir /mnt/btrfs-root
- disk_step "mounting the target root" \
+ disk_step "$L_DS_MOUNTROOT" \
mount -o noatime,compress=zstd,subvol=@ "$root_mapper" /mnt
mkdir -p /mnt/home /mnt/var/log /mnt/var/cache/pacman/pkg /mnt"$esp_mount_in_target"
- disk_step "mounting /home" \
+ disk_step "$L_DS_MOUNTHOME" \
mount -o noatime,compress=zstd,subvol=@home "$root_mapper" /mnt/home
- disk_step "mounting /var/log" \
+ disk_step "$L_DS_MOUNTLOG" \
mount -o noatime,compress=zstd,subvol=@log "$root_mapper" /mnt/var/log
- disk_step "mounting the package cache" \
+ disk_step "$L_DS_MOUNTCACHE" \
mount -o noatime,compress=zstd,subvol=@pkg "$root_mapper" /mnt/var/cache/pacman/pkg
- disk_step "creating the ESP filesystem on $efi_dev" mkfs.fat -F32 -n OMARCHY_EFI "$efi_dev"
- disk_step "mounting the ESP" mount "$efi_dev" /mnt"$esp_mount_in_target"
+ disk_step "$(printf "$L_DS_MKESP" "$efi_dev")" mkfs.fat -F32 -n OMARCHY_EFI "$efi_dev"
+ disk_step "$L_DS_MOUNTESP" mount "$efi_dev" /mnt"$esp_mount_in_target"
# The orchestrator's first act is to verify this handoff. Check it here so
# a failure names the step that broke rather than surfacing later as
@@ -862,7 +1021,7 @@ _EOF_
}
disk_form() {
- step "Let's select where to install Omarchy..."
+ step "$L_DISK_STEP"
# Don't offer the install media as an option (Arch ISO mounts it here)
local boot_source
@@ -888,19 +1047,19 @@ disk_form() {
fi
done <<<"$available_disks"
- selected_display=$(echo "$disk_options" | gum choose --header "Select install disk") || abort
+ selected_display=$(echo "$disk_options" | gum choose --header "$L_DISK_HEADER") || abort
disk=$(echo "$selected_display" | awk '{print $1}')
}
# STEP 4: INSTALL MODE
open_partition_tool() {
- step "Partition tool for $disk"
- gum style "Create unallocated free space for Omarchy, then write changes and quit."
- gum style --foreground 8 "Do not create an Omarchy partition here — leave the target area as Free space."
+ step "$(printf "$L_PT_STEP" "$disk")"
+ gum style "$L_PT_1"
+ gum style --foreground 8 "$L_PT_2"
gum style --foreground 8 "Tip: free-space install needs at least 32GB unallocated, plus 2GB more if no ESP already exists."
echo
- gum confirm --affirmative "Open cfdisk" --negative "Back" "Open partition tool for $disk?" || return 0
+ gum confirm --affirmative "$L_OPEN_CFDISK" --negative "$L_BACK" "$(printf "$L_CFDISK_CONFIRM" "$disk")" || return 0
clear
cfdisk "$disk" || true
@@ -933,17 +1092,17 @@ requires_full_disk_install() {
}
install_mode_form() {
- local choices=("Full disk install")
+ local choices=("$L_MODE_FULL")
# The free-space/protected path registers an EFI boot entry and is UEFI-only.
# Full-disk installs still support BIOS through the orchestrator's BIOS branch.
if [[ -d /sys/firmware/efi ]] && ! $full_disk_only; then
- choices+=("Free space install (alongside existing data)")
+ choices+=("$L_MODE_FREE")
fi
- choices+=("Choose a different disk")
+ choices+=("$L_MODE_OTHER")
- step "Let's select how to install Omarchy..."
- install_mode=$(gum choose --header "Select installation mode on $disk" "${choices[@]}") || abort
+ step "$L_MODE_STEP"
+ install_mode=$(gum choose --header "$(printf "$L_MODE_HEADER" "$disk")" "${choices[@]}") || abort
}
confirm_disk_overwrite() {
@@ -953,17 +1112,17 @@ confirm_disk_overwrite() {
while true; do
clear_logo
echo
- say "Everything will be overwritten. There is no recovery possible."
+ say "$L_WIPE_WARN"
if [[ $mode == "encrypted" ]]; then
- say --foreground 8 "Press Ctrl+C for unencrypted install."
- affirmative="Yes, install"
+ say --foreground 8 "$L_CTRLC_UNENC"
+ affirmative="$L_YES_INSTALL"
else
- affirmative="Yes, install without encryption"
+ affirmative="$L_YES_NOENC"
fi
echo
- gum confirm --affirmative "$affirmative" --negative "No, change it" "Confirm overwriting ${disk}"
+ gum confirm --affirmative "$affirmative" --negative "No, change it" "$(printf "$L_WIPE_CONFIRM" "${disk}")"
confirm_status=$?
case $confirm_status in
@@ -1002,13 +1161,13 @@ select_installation() {
# With no free-space option to offer, a full-disk install is the only
# mode, so skip the picker and go straight to the overwrite confirm.
if $full_disk_only; then
- install_mode="Full disk install"
+ install_mode="$L_MODE_FULL"
else
install_mode_form
fi
case "$install_mode" in
- "Full disk install")
+ "$L_MODE_FULL")
if confirm_disk_overwrite; then
install_target="full_disk"
return 0
@@ -1017,13 +1176,13 @@ select_installation() {
# wants a different disk.
$full_disk_only && disk_form
;;
- "Free space install"*)
+ "$L_MODE_FREE")
if run_partition_decide; then
install_target="free_space"
return 0
fi
;;
- "Choose a different disk")
+ "$L_MODE_OTHER")
disk_form
;;
esac
diff --git a/configs/airootfs/usr/local/bin/omarchy-install-dashboard b/configs/airootfs/usr/local/bin/omarchy-install-dashboard
index 91e960b..54d331d 100755
--- a/configs/airootfs/usr/local/bin/omarchy-install-dashboard
+++ b/configs/airootfs/usr/local/bin/omarchy-install-dashboard
@@ -53,6 +53,44 @@ export GUM_CONFIRM_UNSELECTED_BACKGROUND="${GUM_CONFIRM_UNSELECTED_BACKGROUND:-0
child_pid=""
child_pgid=""
+if [[ ${OMARCHY_INSTALL_LANG:-} == zh ]]; then
+tips=(
+ "Super + Space 打开 Omarchy 菜单:应用、设置与更多"
+ "Super + K 查看全部快捷键"
+ "Super 就是键盘上的 Windows / Command 键"
+ "用 Xournal++ 给 PDF 签名"
+ "用 LocalSend 与手机、电脑互传文件"
+ "菜单 Install > Web App 可把任意网站变成应用"
+ "Super + Return 打开终端,Super + Shift + Return 打开浏览器"
+ "图片用 Pinta,视频用 Kdenlive,文档用 LibreOffice"
+ "Super + Ctrl + Print 用 OCR 抓取屏幕文字"
+ "Print 截屏,Alt + Print 录屏"
+ "Super + Ctrl + R 设置提醒"
+ "菜单 Style > Theme 一键换主题"
+ "双击顶栏可切换透明"
+ "菜单 Install > Windows 可运行完整 Windows 虚拟机"
+ "Super + Ctrl + V 打开剪贴板管理器"
+ "Super + 1 到 0 切换工作区,加 Shift 带窗口一起"
+ "Super + Print 拾取屏幕任意位置的颜色"
+ "菜单里的 Update 保持系统常新"
+)
+L_INSTALLING="正在安装 Omarchy CN"
+L_TIP="提示:"
+L_DONE_IN="安装完成,用时 %s"
+L_STOPPED="Omarchy 安装已停止"
+L_EXIT_STATUS="安装器退出,状态码 %s"
+L_ENTER="按回车继续……"
+L_REBOOT_NOW="立即重启"
+L_FAIL_HEADER="接下来做什么?"
+L_C_UPLOAD="上传日志求助"
+L_C_VIEWLOG="查看完整日志"
+L_C_SHELL="进入 Shell"
+L_C_REBOOT="重启"
+L_C_POWEROFF="关机"
+L_NO_UPLOADER="当前环境没有可用的日志上传工具。"
+L_LOG_AT="安装日志: %s"
+L_TARGET_LOG="目标系统日志: /mnt/var/log/omarchy-install.log"
+else
tips=(
"Super + Space opens the Omarchy menu for apps, settings, and more"
"Super + K shows all the key bindings"
@@ -73,6 +111,23 @@ tips=(
"Super + Print picks a color from anywhere on screen"
"Keep the system fresh with Update in the Omarchy menu"
)
+L_INSTALLING="Installing Omarchy"
+L_TIP="Tip:"
+L_DONE_IN="Installed Omarchy in %s"
+L_STOPPED="Omarchy installation stopped"
+L_EXIT_STATUS="Installer exited with status %s"
+L_ENTER="Press Enter to continue…"
+L_REBOOT_NOW="Reboot Now"
+L_FAIL_HEADER="What would you like to do?"
+L_C_UPLOAD="Upload log for support"
+L_C_VIEWLOG="View full log"
+L_C_SHELL="Drop to shell"
+L_C_REBOOT="Reboot"
+L_C_POWEROFF="Power off"
+L_NO_UPLOADER="No log uploader is available in this environment."
+L_LOG_AT="Install log: %s"
+L_TARGET_LOG="Target log: /mnt/var/log/omarchy-install.log"
+fi
cleanup() {
printf '%s%s' "$RESET" "$SHOW_CURSOR" >"$TTY_PATH" 2>/dev/null || true
@@ -452,13 +507,13 @@ render_dynamic() {
LAST_PM=$pm
printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW"
- center "Installing Omarchy" "$CONTENT_WIDTH"
+ center "$L_INSTALLING" "$CONTENT_WIDTH"
blank_line
line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 34) / 2 )) ""
progress_bar "$pm" 34
printf '\n'
blank_line
- center "${DIM}Tip:${RESET} ${GREEN}$(current_tip)${RESET}" "$CONTENT_WIDTH"
+ center "${DIM}${L_TIP}${RESET} ${GREEN}$(current_tip)${RESET}" "$CONTENT_WIDTH"
printf '%s' "$CLEAR_TO_END"
}
@@ -511,7 +566,7 @@ render_finish() {
printf '%s%s%s%d;1H' "$SHOW_CURSOR" "$CLEAR" "$CSI" "$FINISH_TOP_ROW"
render_logo
blank_line
- center "Installed Omarchy in ${duration}" "$CONTENT_WIDTH"
+ center "$(printf "$L_DONE_IN" "${duration}")" "$CONTENT_WIDTH"
blank_line
} >"$TTY_PATH"
@@ -556,7 +611,7 @@ reboot_prompt() {
--padding "0 0 0 $prompt_pad" \
--show-help=false \
--default \
- --affirmative "Reboot Now" \
+ --affirmative "$L_REBOOT_NOW" \
--negative "" \
"" <"$TTY_PATH" >"$TTY_PATH" 2>&1
}
@@ -617,7 +672,7 @@ find_log_uploader() {
}
prompt_enter() {
- printf '\nPress Enter to continue…' >"$TTY_PATH"
+ printf '\n%s' "$L_ENTER" >"$TTY_PATH"
IFS= read -r _ <"$TTY_PATH" || true
}
@@ -628,9 +683,9 @@ upload_failure_log() {
"$uploader" install >"$TTY_PATH" 2>&1 || true
else
{
- echo "No log uploader is available in this environment."
- echo "Install log: $LOG_FILE"
- [[ -f /mnt/var/log/omarchy-install.log ]] && echo "Target log: /mnt/var/log/omarchy-install.log"
+ echo "$L_NO_UPLOADER"
+ printf "$L_LOG_AT\n" "$LOG_FILE"
+ [[ -f /mnt/var/log/omarchy-install.log ]] && echo "$L_TARGET_LOG"
} >"$TTY_PATH"
fi
prompt_enter
@@ -696,8 +751,8 @@ render_failure() {
blank_line
render_logo
blank_line
- center "${RED}Omarchy installation stopped${RESET}" "$CONTENT_WIDTH"
- center "Installer exited with status $status" "$CONTENT_WIDTH"
+ center "${RED}${L_STOPPED}${RESET}" "$CONTENT_WIDTH"
+ center "$(printf "$L_EXIT_STATUS" "$status")" "$CONTENT_WIDTH"
render_media_diagnosis "$media_diagnosis"
@@ -730,40 +785,40 @@ failure_menu() {
if [[ -n $uploader ]]; then
choice=$(gum choose \
--height 6 \
- --header "What would you like to do?" \
- "Upload log for support" \
- "View full log" \
- "Drop to shell" \
- "Reboot" \
- "Power off" \
- <"$TTY_PATH" 2>"$TTY_PATH") || choice="Drop to shell"
+ --header "$L_FAIL_HEADER" \
+ "$L_C_UPLOAD" \
+ "$L_C_VIEWLOG" \
+ "$L_C_SHELL" \
+ "$L_C_REBOOT" \
+ "$L_C_POWEROFF" \
+ <"$TTY_PATH" 2>"$TTY_PATH") || choice="$L_C_SHELL"
else
choice=$(gum choose \
--height 5 \
- --header "What would you like to do?" \
- "View full log" \
- "Drop to shell" \
- "Reboot" \
- "Power off" \
- <"$TTY_PATH" 2>"$TTY_PATH") || choice="Drop to shell"
+ --header "$L_FAIL_HEADER" \
+ "$L_C_VIEWLOG" \
+ "$L_C_SHELL" \
+ "$L_C_REBOOT" \
+ "$L_C_POWEROFF" \
+ <"$TTY_PATH" 2>"$TTY_PATH") || choice="$L_C_SHELL"
fi
case "$choice" in
- "Upload log for support")
+ "$L_C_UPLOAD")
upload_failure_log
render_failure "${failure_status:-1}" "${failure_summary:-}" "${failure_media_diagnosis:-}" >"$TTY_PATH" 2>/dev/null || true
;;
- "View full log")
+ "$L_C_VIEWLOG")
view_failure_log
render_failure "${failure_status:-1}" "${failure_summary:-}" "${failure_media_diagnosis:-}" >"$TTY_PATH" 2>/dev/null || true
;;
- "Reboot")
+ "$L_C_REBOOT")
reboot 2>/dev/null || systemctl reboot 2>/dev/null || true
;;
- "Power off")
+ "$L_C_POWEROFF")
poweroff 2>/dev/null || systemctl poweroff 2>/dev/null || true
;;
- "Drop to shell"|"")
+ "$L_C_SHELL"|"")
return 0
;;
esac
+14
View File
@@ -0,0 +1,14 @@
diff --git a/pkgbuilds/omarchy-dev/PKGBUILD b/pkgbuilds/omarchy-dev/PKGBUILD
index fac61db..3fc43ef 100644
--- a/pkgbuilds/omarchy-dev/PKGBUILD
+++ b/pkgbuilds/omarchy-dev/PKGBUILD
@@ -131,6 +131,9 @@ package() {
# Versioned migrations runner reads from this tree.
cp -a migrations "$pkgdir/usr/share/omarchy/"
+ # OmarchyCN data layer (mirrors, registries, keys, fcitx5, fontconfig)
+ cp -a cn "$pkgdir/usr/share/omarchy/"
+
# Fresh users created after install are already on the package layout in this
# build, so seed migration markers through /etc/skel. Existing users keep
# their own state and still run pending migrations on update.
+23
View File
@@ -0,0 +1,23 @@
pkgname=omarchycn-keyring
pkgver=20260824
pkgrel=2
pkgdesc="OmarchyCN PGP keyring: package registry and release signing keys"
arch=(any)
url="https://git.zacharyzhang.com/ZacharyZhang-NY/omarchycn"
license=(MIT)
install=omarchycn-keyring.install
source=(omarchycn-registry.key omarchycn-release.key omarchycn-trusted)
sha256sums=(SKIP SKIP SKIP)
build() {
export GNUPGHOME="$srcdir/gnupg"
mkdir -p -m700 "$GNUPGHOME"
gpg --import omarchycn-registry.key omarchycn-release.key
gpg --export > omarchycn.gpg
}
package() {
install -Dm644 omarchycn.gpg "$pkgdir/usr/share/pacman/keyrings/omarchycn.gpg"
install -Dm644 omarchycn-trusted "$pkgdir/usr/share/pacman/keyrings/omarchycn-trusted"
install -Dm644 /dev/null "$pkgdir/usr/share/pacman/keyrings/omarchycn-revoked"
}
@@ -0,0 +1,7 @@
post_install() {
usr/bin/pacman-key --populate omarchycn
}
post_upgrade() {
usr/bin/pacman-key --populate omarchycn
}
@@ -0,0 +1,2 @@
74DCF57ACD812B24D959F146BD386048867B33B4:4:
04490F065F6ADD262A7243506EDF7B8603B5D247:4:
+164
View File
@@ -0,0 +1,164 @@
#!/bin/bash
set -euo pipefail
source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh"
require_command jq
require_command python3
export OMARCHY_PATH="$ROOT"
export PATH="$ROOT/bin:$PATH"
TEST_HOME=$(mktemp -d)
MOCK_PID=""
cleanup() {
[[ -n $MOCK_PID ]] && kill "$MOCK_PID" 2>/dev/null
rm -rf "$TEST_HOME"
}
trap cleanup EXIT
export HOME="$TEST_HOME"
# Force the file backend: production Secret Service / pass must never be touched
mkdir -p "$TEST_HOME/stubs"
printf '#!/bin/bash
exit 1
' > "$TEST_HOME/stubs/secret-tool"
printf '#!/bin/bash
exit 1
' > "$TEST_HOME/stubs/pass"
chmod +x "$TEST_HOME/stubs/secret-tool" "$TEST_HOME/stubs/pass"
export PATH="$TEST_HOME/stubs:$PATH"
# Registries parse and carry the P0 providers and harnesses
for reg in ai-providers ai-harnesses ai-compatibility; do
jq -e . "$ROOT/cn/registry/$reg.json" > /dev/null || fail "$reg.json parses"
done
for p in deepseek kimi zai; do
jq -e --arg p "$p" '.providers[$p].endpoints.anthropic // .providers[$p].endpoints.openai' \
"$ROOT/cn/registry/ai-providers.json" > /dev/null || fail "provider $p has an endpoint"
done
pass "AI registries parse with P0 providers"
# Secret file backend roundtrip with 0600
echo "sk-secret-1" | omarchy-cn-ai-secret set deepseek > /dev/null
[[ $(omarchy-cn-ai-secret get deepseek) == "sk-secret-1" ]] || fail "secret roundtrip"
[[ $(stat -c %a "$HOME/.local/state/omarchycn/secrets/deepseek") == 600 ]] || fail "secret file 0600"
pass "secret backend file fallback stores and returns the key at 0600"
# Profile validation matrix
omarchy-cn-ai-profile-create good claude-code deepseek default-coding > /dev/null
omarchy-cn-ai-profile-create cdx codex deepseek deepseek-v4-flash > /dev/null
omarchy-cn-ai-profile-create ock opencode deepseek default-coding > /dev/null
if omarchy-cn-ai-profile-create bad codex kimi fast > /dev/null 2>&1; then
fail "unsupported combo rejected"
fi
if omarchy-cn-ai-profile-create bad claude-code kimi kimi-k2.6 > /dev/null 2>&1; then
fail "protocol-mismatched model rejected"
fi
if omarchy-cn-ai-profile-create bad codex deepseek "deepseek-v4-pro[1m]" > /dev/null 2>&1; then
fail "codex rejects provider-namespace slug"
fi
pass "profile validation enforces combos, protocols, and allowlists"
# claude-code env render for all three providers, exact endpoints
source "$ROOT/cn/lib/ai.sh"
declare -A bases=(
[deepseek]="https://api.deepseek.com/anthropic"
[kimi]="https://api.kimi.com/coding/"
[zai]="https://api.z.ai/api/anthropic"
)
for p in deepseek kimi zai; do
out=$(cn_ai_render_env claude-code "$p" test-model sk-x)
grep -qF "ANTHROPIC_BASE_URL=${bases[$p]}" <<<"$out" || fail "claude-code $p base url" "$out"
grep -q "ANTHROPIC_AUTH_TOKEN=sk-x" <<<"$out" || fail "claude-code $p auth token"
grep -q "ANTHROPIC_API_KEY=sk-x" <<<"$out" || fail "claude-code $p api key"
done
out=$(cn_ai_render_env opencode deepseek test-model sk-x)
grep -q "DEEPSEEK_API_KEY=sk-x" <<<"$out" || fail "opencode deepseek env"
if cn_ai_render_env opencode zai m k > /dev/null 2>&1; then
fail "opencode zai refused"
fi
pass "env adapters render official endpoints for every stable combo"
# codex config render: official contract, user content preserved, idempotent
mkdir -p "$HOME/.codex"
printf '[mcp_servers.x]\ncommand = "npx"\n' > "$HOME/.codex/config.toml"
cn_ai_render_codex_config deepseek deepseek-v4-flash sk-x
for want in 'model = "deepseek-v4-flash"' 'model_catalog_json = "~/.codex/models.json"' \
'preferred_auth_method = "apikey"' 'forced_login_method = "api"' \
'wire_api = "responses"' '[model_providers.deepseek]'; do
grep -qF "$want" "$HOME/.codex/config.toml" || fail "codex contract: $want"
done
grep -q "mcp_servers.x" "$HOME/.codex/config.toml" || fail "codex user section preserved"
[[ -f $HOME/.codex/models.json ]] || fail "codex models catalog installed"
cn_ai_render_codex_config deepseek deepseek-v4-flash sk-x
[[ $(grep -c "OmarchyCN ai model begin" "$HOME/.codex/config.toml") == 1 ]] || fail "codex render idempotent"
pass "codex adapter implements the official DeepSeek contract"
# Mock server regression: anthropic 200/401 and codex responses path
port=$(python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()')
python3 - "$port" <<'PYEOF' &
import http.server, sys
class H(http.server.BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
key = self.headers.get("x-api-key") or (self.headers.get("Authorization") or "").replace("Bearer ", "")
if key != "sk-secret-1":
code = 401
elif self.path == "/v1/messages" and b"messages" in body and self.headers.get("anthropic-version"):
code = 200
elif self.path == "/responses" and b"input" in body and b"messages" not in body:
code = 200
else:
code = 404
self.send_response(code)
self.send_header("content-length", "2")
self.end_headers()
self.wfile.write(b"{}")
def log_message(self, *a):
pass
http.server.HTTPServer(("127.0.0.1", int(sys.argv[1])), H).serve_forever()
PYEOF
MOCK_PID=$!
for _ in $(seq 1 20); do
curl -sf -o /dev/null -X POST "http://127.0.0.1:$port/v1/messages" -H "x-api-key: sk-secret-1" && break
sleep 0.2
done
OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test good | grep -q "PASS" || fail "anthropic mock 200"
OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test cdx | grep -q "PASS" || fail "codex responses mock 200"
echo "wrong-key" | omarchy-cn-ai-secret set deepseek > /dev/null
if OMARCHYCN_AI_TEST_BASE_URL="http://127.0.0.1:$port" omarchy-cn-ai-test good > /dev/null 2>&1; then
fail "anthropic mock 401 fails the test"
fi
pass "mock regression: 200 pass, 401 fail, responses path exercised"
# ai-default merges into claude settings without clobbering user config.
# claude is absent; the install branch must run our omarchy-mise-install stub,
# which "installs" the claude stub and leaves a marker.
cat > "$TEST_HOME/stubs/omarchy-mise-install" <<STUB
#!/bin/bash
touch "$TEST_HOME/installer-ran"
printf '#!/bin/bash
exit 0
' > "$TEST_HOME/stubs/claude"
chmod +x "$TEST_HOME/stubs/claude"
STUB
chmod +x "$TEST_HOME/stubs/omarchy-mise-install"
echo "sk-secret-1" | omarchy-cn-ai-secret set deepseek > /dev/null
mkdir -p "$HOME/.claude"
printf '{"permissions":{"allow":["Bash"]},"env":{"KEEP":"1"}}' > "$HOME/.claude/settings.json"
# Restricted PATH guarantees claude is absent so the installer branch runs
restricted="$TEST_HOME/stubs:$ROOT/bin:/usr/bin:/bin"
if PATH="$restricted" command -v claude > /dev/null 2>&1; then
fail "precondition: claude must not be visible on the restricted PATH"
fi
PATH="$restricted" omarchy-cn-ai-default good > /dev/null
[[ $(jq -r '.env.KEEP' "$HOME/.claude/settings.json") == 1 ]] || fail "ai-default preserves user env"
[[ $(jq -r '.permissions.allow[0]' "$HOME/.claude/settings.json") == Bash ]] || fail "ai-default preserves permissions"
[[ $(jq -r '.env.ANTHROPIC_BASE_URL' "$HOME/.claude/settings.json") == "https://api.deepseek.com/anthropic" ]] || fail "ai-default env written"
[[ $(cat "$HOME/.config/omarchy/defaults/agent") == claude ]] || fail "upstream default agent set"
[[ $(stat -c %a "$HOME/.claude/settings.json") == 600 ]] || fail "claude settings 0600"
[[ -f $TEST_HOME/installer-ran ]] || fail "missing harness triggered installer"
pass "ai-default maps the profile onto the upstream agent entry"
+111
View File
@@ -0,0 +1,111 @@
#!/bin/bash
set -euo pipefail
source "$(dirname -- "${BASH_SOURCE[0]}")/base-test.sh"
export OMARCHY_PATH="$ROOT"
export PATH="$ROOT/bin:$PATH"
TEST_HOME=$(mktemp -d)
trap 'rm -rf "$TEST_HOME"' EXIT
export HOME="$TEST_HOME"
# Version format
version=$("$ROOT/bin/omarchy-cn-version")
[[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+.*-cn\.[0-9]+$ ]] || fail "cn version format" "$version"
pass "omarchy-cn-version reports <upstream>-cn.<release>"
# Mirror probe: dead host degrades to zeros without aborting under set -e
probe_out=$(
set -euo pipefail
source "$ROOT/cn/lib/mirror.sh"
cn_mirror_probe "https://invalid.invalid.example"
)
[[ $probe_out == "0 0 unknown" ]] || fail "dead mirror probes as 0 0 unknown" "$probe_out"
pass "mirror probe degrades cleanly on dead host"
# Dev-mirror: apply china writes npm registry, unrelated keys survive re-apply
echo "save-exact=true" > "$HOME/.npmrc"
omarchy-cn-dev-mirror-apply china --target npm > /dev/null
grep -q '^registry=https://registry.npmmirror.com$' "$HOME/.npmrc" || fail "npm china registry written"
grep -q '^save-exact=true$' "$HOME/.npmrc" || fail "unrelated npmrc keys preserved"
pass "dev-mirror apply china manages npm registry non-destructively"
# Dev-mirror: cargo china installs the block, official removes it completely
omarchy-cn-dev-mirror-apply china --target cargo > /dev/null
grep -q 'replace-with = "omarchycn"' "$HOME/.cargo/config.toml" || fail "cargo china writes source replacement"
grep -q 'rsproxy.cn' "$HOME/.cargo/config.toml" || fail "cargo china points at the registry URL"
omarchy-cn-dev-mirror-apply official --target cargo > /dev/null
if grep -q "crates-io" "$HOME/.cargo/config.toml"; then
fail "cargo official removes managed source block"
fi
pass "dev-mirror cargo china installs and official removes the block"
# IME: profile installs rime as default, hotkey surgery keeps other sections
mkdir -p "$HOME/.config/fcitx5"
cp "$ROOT/cn/fcitx5/profile" "$HOME/.config/fcitx5/profile"
grep -q '^DefaultIM=rime$' "$HOME/.config/fcitx5/profile" || fail "shipped profile defaults to rime"
printf '[Behavior]\nShareInputState=No\n\n[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n' > "$HOME/.config/fcitx5/config"
omarchy-cn-ime-hotkey super-space > /dev/null
grep -q '^ShareInputState=No$' "$HOME/.config/fcitx5/config" || fail "hotkey surgery preserves other sections"
grep -q '^0=Super+space$' "$HOME/.config/fcitx5/config" || fail "hotkey surgery sets trigger"
grep -q 'hl.unbind("SUPER + SPACE")' "$HOME/.config/hypr/bindings.lua" || fail "menu key unbound"
grep -q 'hl.unbind("SUPER + ALT + SPACE")' "$HOME/.config/hypr/bindings.lua" || fail "apps menu key unbound before rebind"
grep -q 'o.bind("SUPER + ALT + SPACE", "Omarchy menu"' "$HOME/.config/hypr/bindings.lua" || fail "menu rebound to Super+Alt+Space"
omarchy-cn-ime-hotkey ctrl-space > /dev/null
grep -q '^0=Control+space$' "$HOME/.config/fcitx5/config" || fail "ctrl-space restores Control+space trigger"
if grep -q 'OmarchyCN ime hotkey begin' "$HOME/.config/hypr/bindings.lua"; then
fail "ctrl-space removes migration block"
fi
pass "ime hotkey section surgery and menu migration round-trip"
# Fontconfig priority file is well-formed and carries the required rules
python3 - "$ROOT/cn/fontconfig/64-omarchycn-cjk.conf" <<'PYEOF' || fail "fontconfig rules incomplete"
import sys
import xml.etree.ElementTree as ET
tree = ET.parse(sys.argv[1])
text = ET.tostring(tree.getroot(), encoding="unicode")
required = [
"Noto Sans CJK SC", "Noto Serif CJK SC", "Noto Sans Mono CJK SC",
"Noto Sans CJK JP", "Noto Sans CJK KR", "zh-cn",
]
missing = [r for r in required if r not in text]
weak_appends = [
e for e in tree.getroot().iter("edit")
if e.get("mode") == "append" and e.get("binding") == "weak"
]
if missing or not weak_appends:
raise SystemExit(f"missing={missing} weak_appends={len(weak_appends)}")
PYEOF
pass "fontconfig priority file carries SC/JP/KR rules and weak fallback"
# Menu: OmarchyCN entries parse and reference only existing commands
node -e '
const fs = require("fs");
const src = fs.readFileSync(process.argv[1], "utf8")
.split("\n").filter(l => !l.trim().startsWith("//")).join("\n")
.replace(/,(\s*[}\]])/g, "$1");
const menu = JSON.parse(src);
const cn = Object.keys(menu).filter(k => k.startsWith("omarchycn"));
const required = [
"omarchycn", "omarchycn.mirrors.china", "omarchycn.mirrors.official",
"omarchycn.mirrors.benchmark", "omarchycn.mirrors.restore",
"omarchycn.chinese.ime", "omarchycn.chinese.fonts", "omarchycn.chinese.locale",
"omarchycn.chinese.hotkey-ctrl", "omarchycn.chinese.hotkey-super",
"omarchycn.display.s10", "omarchycn.display.s125", "omarchycn.display.s15",
"omarchycn.display.s16", "omarchycn.display.s175", "omarchycn.display.s20",
"omarchycn.diagnostics.doctor", "omarchycn.update", "omarchycn.about",
];
const gone = required.filter(k => !(k in menu));
if (gone.length) throw new Error("missing menu entries: " + gone.join(", "));
for (const key of cn) {
const action = menu[key].action || "";
const m = action.match(/omarchy-cn-[a-z-]+/);
if (m) {
fs.accessSync(process.argv[2] + "/" + m[0], fs.constants.X_OK);
}
}
' "$ROOT/default/omarchy/omarchy-menu.jsonc" "$ROOT/bin" ||
fail "OmarchyCN menu entries reference existing executable commands"
pass "OmarchyCN menu entries parse and reference real commands"