omarchy-cn-convert turns a package-based install into OmarchyCN in one curl-able run: registry key + [omarchycn] repo, --ask=4 swap onto the cn-built packages (Chinese menu/bindings and OMARCHY CN branding ride the settings tree), [omarchy] mirror line, zh_CN locale, fcitx5-rime seeding, CN Arch mirrors, and a plymouth rebrand, recording pre-state in /var/lib/omarchycn/convert-state. omarchy-cn-revert restores that state symmetrically and survives user-removed packages, self-deletion mid-swap, and cosmetic plymouth failures after the state is cleared. Checkout installs are refused toward the overlay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
153 lines
7.7 KiB
Bash
Executable File
153 lines
7.7 KiB
Bash
Executable File
#!/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"
|
|
|
|
# [omarchy] mirror wiring: stable is mirror-first with upstream fallback
|
|
mapfile -t stable_servers < <(sed -n '/^\[omarchy\]/,$p' "$ROOT/default/pacman/pacman-stable.conf" | grep '^Server = ')
|
|
[[ ${stable_servers[0]:-} == "Server = https://git.zacharyzhang.com/api/packages/ZacharyZhang-NY/arch/omarchy/x86_64" ]] ||
|
|
fail "stable conf lists the OmarchyCN mirror first" "${stable_servers[0]:-none}"
|
|
[[ ${stable_servers[1]:-} == 'Server = https://pkgs.omarchy.org/stable/$arch' ]] ||
|
|
fail "stable conf keeps the upstream fallback second" "${stable_servers[1]:-none}"
|
|
for ch in edge rc; do
|
|
if grep -q git.zacharyzhang.com "$ROOT/default/pacman/pacman-$ch.conf"; then
|
|
fail "$ch conf must stay upstream-only (mirror carries stable only)"
|
|
fi
|
|
done
|
|
bash -n "$ROOT/cn/migrations/1787861071.sh" || fail "mirror migration parses"
|
|
grep -q "pacman-key --add" "$ROOT/install/post-install/pacman.sh" || fail "install imports the registry key"
|
|
pass "[omarchy] mirror wiring: stable mirror-first, edge/rc untouched, key import present"
|
|
|
|
# Mirror-line guard: comments and substrings must not count as present
|
|
tmpconf=$(mktemp)
|
|
printf 'Server = https://pkgs.omarchy.org/stable/$arch\n' > "$tmpconf"
|
|
(
|
|
source "$ROOT/cn/lib/mirror.sh"
|
|
OMARCHYCN_PACMAN_CONF=$tmpconf cn_mirror_omarchy_repo_ok && exit 1
|
|
printf '# Server = %s\n' "$CN_OMARCHY_MIRROR" >> "$tmpconf"
|
|
OMARCHYCN_PACMAN_CONF=$tmpconf cn_mirror_omarchy_repo_ok && exit 1
|
|
printf 'Server = %s\n' "$CN_OMARCHY_MIRROR" >> "$tmpconf"
|
|
OMARCHYCN_PACMAN_CONF=$tmpconf cn_mirror_omarchy_repo_ok
|
|
) || fail "omarchy repo guard exact-line semantics"
|
|
rm -f "$tmpconf"
|
|
pass "omarchy mirror guard ignores comments and matches only the exact line"
|
|
|
|
# Convert/revert bootstrap pair: parse, self-delete guard, no constant drift
|
|
for s in omarchy-cn-convert omarchy-cn-revert; do
|
|
bash -n "$ROOT/bin/$s" || fail "$s parses"
|
|
grep -q '^main "\$@"$' "$ROOT/bin/$s" || fail "$s wraps work in main() against self-deletion"
|
|
done
|
|
grep -q -- '--ask=4' "$ROOT/bin/omarchy-cn-convert" || fail "convert auto-confirms the conflict swap"
|
|
lib_mirror=$(grep -m1 '^CN_OMARCHY_MIRROR=' "$ROOT/cn/lib/mirror.sh" | cut -d'"' -f2)
|
|
revert_mirror=$(grep -m1 '^MIRROR_LINE=' "$ROOT/bin/omarchy-cn-revert" | cut -d'"' -f2)
|
|
[[ "Server = $lib_mirror" == "$revert_mirror" ]] ||
|
|
fail "revert MIRROR_LINE drifted from cn/lib/mirror.sh" "$revert_mirror"
|
|
pass "convert/revert pair: parse, main() guard, ask=4, mirror constant in sync"
|
|
|
|
# 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"
|