Merge cn-region-switch (with omarchy #13690)
# Conflicts: # AGENTS.md # bin/omarchy-refresh-pacman # default/omarchy/omarchy-menu.jsonc # install/post-install/pacman.sh # shell/plugins/menu/MenuModel.js # test/shell.d/menu-test.sh
This commit is contained in:
35 files changed
+918
-7
No files matched your search
@@ -6,6 +6,7 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
source "$SHELL_TEST_DIR/fixtures/sudo-boundary-test.sh"
|
||||
copy_boundary_file bin/omarchy-channel-set
|
||||
copy_boundary_file bin/omarchy-refresh-pacman
|
||||
copy_boundary_file bin/omarchy-apply-pacman
|
||||
copy_boundary_file bin/omarchy-update
|
||||
export OMARCHY_UPDATE_LOGGED=1
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ run_channel() {
|
||||
: >"$log_file"
|
||||
OMARCHY_CHANNEL_TEST_LOG="$log_file" \
|
||||
OMARCHY_PATH="${OMARCHY_TEST_PATH:-$package_root}" \
|
||||
OMARCHY_REGION_FILE="${OMARCHY_TEST_REGION_FILE:-$test_tmp/no-region}" \
|
||||
HOME="$test_tmp/home" \
|
||||
PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||
"${OMARCHY_TEST_PATH:-$package_root}/bin/omarchy-channel-set" "$@"
|
||||
@@ -190,6 +191,58 @@ fi
|
||||
assert_log_line $'link\t'"$checkout"$'\t--no-reboot' "switching back to dev links ~/omarchy"
|
||||
pass "switching back to dev reuses the existing ~/omarchy checkout"
|
||||
|
||||
printf 'cn\n' >"$test_tmp/region"
|
||||
if OMARCHY_TEST_REGION_FILE="$test_tmp/region" run_channel dev >"$test_tmp/region.out" 2>"$test_tmp/region.err"; then
|
||||
fail "dev refuses a checkout without this machine's region"
|
||||
fi
|
||||
grep -q "does not support the 'cn' region" "$test_tmp/region.err" || fail "dev explains the missing region" "$(cat "$test_tmp/region.err")"
|
||||
if grep -Eq $'^(link|refresh)\t' "$log_file"; then
|
||||
fail "dev refuses before linking or refreshing pacman" "$(cat "$log_file")"
|
||||
fi
|
||||
pass "dev refuses a checkout without the region before touching pacman"
|
||||
|
||||
# A directory with the region's name, or a helper that cannot run, is not
|
||||
# support: the refresh after linking would fail with dev already linked.
|
||||
mkdir -p "$checkout/default/regions/cn"
|
||||
touch "$checkout/bin/omarchy-apply-pacman"
|
||||
chmod +x "$checkout/bin/omarchy-apply-pacman"
|
||||
if OMARCHY_TEST_REGION_FILE="$test_tmp/region" run_channel dev >"$test_tmp/region.out" 2>"$test_tmp/region.err"; then
|
||||
fail "dev refuses an empty region profile"
|
||||
fi
|
||||
if grep -Eq $'^(link|refresh)\t' "$log_file"; then
|
||||
fail "dev refuses an empty region profile before linking or refreshing pacman" "$(cat "$log_file")"
|
||||
fi
|
||||
mkdir -p "$checkout/default/regions/cn/pacman"
|
||||
cn_fragments=(pacman.conf.append mirrorlist-stable.prepend mirrorlist-rc.prepend mirrorlist-edge.prepend)
|
||||
for missing in "${cn_fragments[@]}"; do
|
||||
for fragment in "${cn_fragments[@]}"; do
|
||||
if [[ $fragment == "$missing" ]]; then
|
||||
rm -f "$checkout/default/regions/cn/pacman/$fragment"
|
||||
else
|
||||
touch "$checkout/default/regions/cn/pacman/$fragment"
|
||||
fi
|
||||
done
|
||||
if OMARCHY_TEST_REGION_FILE="$test_tmp/region" run_channel dev >"$test_tmp/region.out" 2>"$test_tmp/region.err"; then
|
||||
fail "dev refuses a region profile without $missing"
|
||||
fi
|
||||
if grep -Eq $'^(link|refresh)\t' "$log_file"; then
|
||||
fail "dev refuses a profile without $missing before linking or refreshing pacman" "$(cat "$log_file")"
|
||||
fi
|
||||
done
|
||||
touch "${cn_fragments[@]/#/$checkout/default/regions/cn/pacman/}"
|
||||
chmod -x "$checkout/bin/omarchy-apply-pacman"
|
||||
if OMARCHY_TEST_REGION_FILE="$test_tmp/region" run_channel dev >"$test_tmp/region.out" 2>"$test_tmp/region.err"; then
|
||||
fail "dev refuses a checkout whose pacman helper cannot run"
|
||||
fi
|
||||
if grep -Eq $'^(link|refresh)\t' "$log_file"; then
|
||||
fail "dev refuses incomplete region support before linking or refreshing pacman" "$(cat "$log_file")"
|
||||
fi
|
||||
pass "dev refuses a region profile missing any fragment or a helper that cannot run"
|
||||
|
||||
chmod +x "$checkout/bin/omarchy-apply-pacman"
|
||||
OMARCHY_TEST_REGION_FILE="$test_tmp/region" run_channel dev
|
||||
assert_log_line $'refresh\tedge' "dev proceeds with a checkout that supports the region"
|
||||
|
||||
current_channel() {
|
||||
OMARCHY_TEST_VERSION_CHANNEL="$1" \
|
||||
OMARCHY_TEST_PACKAGES="$2" \
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
require_command node
|
||||
|
||||
node - "$ROOT" <<'JS'
|
||||
const fs = require('fs')
|
||||
const root = process.argv[2]
|
||||
const model = require(`${root}/shell/plugins/menu/MenuModel.js`)
|
||||
const items = model.parseMenuJsonc(fs.readFileSync(`${root}/default/omarchy/omarchy-menu.jsonc`, 'utf8'))
|
||||
const china = items.filter(item => item.id === 'install.china' || item.id.startsWith('install.china.'))
|
||||
const guard = '[[ "$(omarchy-region)" == "China" ]]'
|
||||
const fail = message => { console.error(`not ok - ${message}`); process.exit(1) }
|
||||
|
||||
const aur = {
|
||||
wechat: 'wechat-universal-bwrap', qq: 'linuxqq', feishu: 'feishu-bin',
|
||||
dingtalk: 'dingtalk-bin', wemeet: 'wemeet-bin', wps: 'wps-office-cn',
|
||||
}
|
||||
const web = {
|
||||
'tencent-docs': ['腾讯文档', 'https://docs.qq.com'],
|
||||
yuque: ['语雀', 'https://www.yuque.com/dashboard'],
|
||||
shimo: ['石墨文档', 'https://shimo.im/desktop'],
|
||||
}
|
||||
|
||||
if (china.length !== 1 + Object.keys(aur).length + Object.keys(web).length) fail(`China Apps has exactly the expected entries: ${china.map(i => i.id)}`)
|
||||
if (!china.every(item => item.when === guard)) fail('every China Apps entry, including search hits, shows only in China')
|
||||
for (const [id, pkg] of Object.entries(aur)) {
|
||||
const item = china.find(i => i.id === `install.china.${id}`)
|
||||
if (!item || item.action !== `omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add ${pkg}'` || item.disabled !== `omarchy-pkg-present ${pkg}`)
|
||||
fail(`${id} installs and checks the AUR package ${pkg}`)
|
||||
}
|
||||
for (const [id, [name, url]] of Object.entries(web)) {
|
||||
const item = china.find(i => i.id === `install.china.${id}`)
|
||||
if (!item || item.action !== `omarchy-launch-floating-terminal-with-presentation 'omarchy-webapp-install ${name} ${url} ""'` || item.disabled !== `[[ -f "$HOME/.local/share/applications/${name}.desktop" ]]`)
|
||||
fail(`${id} installs and checks the ${name} web app`)
|
||||
}
|
||||
console.log('ok - China Apps lists the AUR packages and web apps, shown only in China')
|
||||
JS
|
||||
|
||||
aur_names="wechat-universal-bwrap linuxqq feishu-bin dingtalk-bin wemeet-bin wps-office-cn"
|
||||
if [[ ${OMARCHY_TEST_NETWORK:-} == "1" ]]; then
|
||||
query=$(printf '&arg[]=%s' $aur_names)
|
||||
found=$(curl -fsSL "https://aur.archlinux.org/rpc/v5/info?${query#&}" | jq -r '.results[].Name' | sort | tr '\n' ' ')
|
||||
[[ $found == "$(printf '%s\n' $aur_names | sort | tr '\n' ' ')" ]] || fail "every China Apps package exists in the AUR" "$found"
|
||||
pass "every China Apps package exists in the AUR"
|
||||
else
|
||||
skip "AUR existence check needs OMARCHY_TEST_NETWORK=1"
|
||||
fi
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
mkdir -p "$test_tmp/bin" "$test_tmp/home"
|
||||
cat >"$test_tmp/bin/curl" <<'SH'
|
||||
#!/bin/bash
|
||||
while (($#)); do [[ $1 == "-o" ]] && out=$2; shift; done
|
||||
[[ -n ${out:-} ]] && printf '\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0\x01\0\0\0\x01\x08\x06\0\0\0\x1f\x15\xc4\x89' >"$out"
|
||||
exit 0
|
||||
SH
|
||||
printf '#!/bin/bash\necho "the web app wizard opened" >&2\nexit 1\n' >"$test_tmp/bin/gum"
|
||||
chmod +x "$test_tmp/bin/curl" "$test_tmp/bin/gum"
|
||||
|
||||
grep -o "omarchy-launch-floating-terminal-with-presentation 'omarchy-webapp-install [^']*'" "$ROOT/default/omarchy/omarchy-menu.jsonc" |
|
||||
sed -e "s/^[^']*'//" -e "s/'$//" -e 's/\\"/"/g' >"$test_tmp/actions"
|
||||
(($(wc -l <"$test_tmp/actions") == 3)) || fail "the menu has three China web app actions"
|
||||
while read -r action; do
|
||||
HOME="$test_tmp/home" PATH="$test_tmp/bin:$ROOT/bin:$PATH" bash -c "$action" >/dev/null ||
|
||||
fail "the menu action installs its web app: $action"
|
||||
done <"$test_tmp/actions"
|
||||
for app in 腾讯文档 语雀 石墨文档; do
|
||||
[[ -f $test_tmp/home/.local/share/applications/$app.desktop ]] || fail "$app gets a launcher"
|
||||
done
|
||||
pass "each China web app action creates its launcher without the wizard"
|
||||
@@ -9,7 +9,11 @@ export SUDO_TEST_LOG="$boundary_tmp/events"
|
||||
export SUDO_TEST_CACHE="$boundary_tmp/cache"
|
||||
export OMARCHY_PATH="$SUDO_TEST_ROOT"
|
||||
export SUDO_TEST_HOME="$boundary_tmp/home"
|
||||
mkdir -p "$SUDO_TEST_HOME"
|
||||
# The machine the render reads its region marker from. It starts global; a test
|
||||
# writes etc/omarchy/region here to model a regional install, never the host's.
|
||||
export SUDO_TEST_TARGET="$boundary_tmp/target"
|
||||
export OMARCHY_REGION_FILE="$SUDO_TEST_TARGET/etc/omarchy/region"
|
||||
mkdir -p "$SUDO_TEST_HOME" "$SUDO_TEST_TARGET/etc"
|
||||
mkdir -p "$SUDO_TEST_ROOT/bin" "$SUDO_TEST_ROOT/mock" "$SUDO_TEST_ROOT/default/omarchy/sudo-no-update"
|
||||
: >"$SUDO_TEST_LOG"
|
||||
|
||||
@@ -24,6 +28,10 @@ s=(source/name).read_text().replace('$HOME', '$SUDO_TEST_HOME')
|
||||
for command in ['sudo','pkexec','pacman','omarchy-pkg-missing','systemd-inhibit','setpriv','snapper']:
|
||||
s=s.replace('/usr/bin/'+command, str(target/'mock'/command))
|
||||
s=s.replace('PATH=/usr/bin:/usr/sbin:/bin:/sbin', 'PATH="'+str(target/'bin')+':/usr/bin:/usr/sbin:/bin:/sbin"')
|
||||
if name.name == 'omarchy-apply-pacman':
|
||||
render_etc='etc="${4:-/}/etc"'
|
||||
assert s.count(render_etc)==1, 'render target lookup moved'
|
||||
s=s.replace(render_etc, 'etc="${4:-$SUDO_TEST_TARGET}/etc"')
|
||||
p.write_text(s)
|
||||
p.chmod((source/name).stat().st_mode & 0o777)
|
||||
PY
|
||||
|
||||
@@ -304,6 +304,14 @@ assert(
|
||||
&& defaultById['setup.language.zh'].action.includes('omarchy-language zh'),
|
||||
'menu offers English and Simplified Chinese under Setup > Language'
|
||||
)
|
||||
const regionEntries = defaultItems.filter(item => item.parent === 'setup.region')
|
||||
assert(
|
||||
regionEntries.map(item => item.label).join('\0') === 'Global\0China'
|
||||
&& regionEntries.every(item => item.checked.includes(`== \"${item.label}\"`) && !item.when)
|
||||
&& defaultById['setup.region.global'].action.includes('omarchy-region global')
|
||||
&& defaultById['setup.region.china'].action.includes('omarchy-region cn'),
|
||||
'menu offers Global and China under Setup > Region'
|
||||
)
|
||||
// Software you already have keeps its place in Install, dimmed rather than
|
||||
// dropped, so the list reads as a catalog of what Omarchy can install.
|
||||
// Chromium Account is the sole Install row with anything left to hide for. The
|
||||
@@ -314,7 +322,8 @@ const windowsGuard = 'omarchy-hw-x86'
|
||||
const x86OnlyInstalls = ['install.windows', 'install.browser.edge', 'install.service.dropbox', 'install.service.spotify', 'install.gaming.minecraft', 'install.gaming.heroic']
|
||||
assertDeepEqual(
|
||||
defaultItems
|
||||
.filter(item => item.id.startsWith('install.') && item.action && item.when)
|
||||
// China Apps rows hide by region, not because the software is there.
|
||||
.filter(item => item.id.startsWith('install.') && item.action && item.when && item.when !== '[[ "$(omarchy-region)" == "China" ]]')
|
||||
.map(item => item.id)
|
||||
.sort(),
|
||||
[...x86OnlyInstalls, 'install.service.chromium-account'].sort(),
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
export OMARCHY_PATH="$ROOT"
|
||||
export TARGET_ROOT="$scratch/target root"
|
||||
mkdir -p "$TARGET_ROOT/etc/omarchy"
|
||||
|
||||
apply() { "$ROOT/bin/omarchy-apply-pacman" "$1" "$TARGET_ROOT"; }
|
||||
|
||||
for channel in stable rc edge; do
|
||||
apply "$channel"
|
||||
cmp "$ROOT/default/pacman/pacman-$channel.conf" "$TARGET_ROOT/etc/pacman.conf"
|
||||
cmp "$ROOT/default/pacman/mirrorlist-$channel" "$TARGET_ROOT/etc/pacman.d/mirrorlist"
|
||||
done
|
||||
pass "missing region preserves all global channel templates byte for byte"
|
||||
|
||||
chmod 600 "$TARGET_ROOT/etc/pacman.conf"
|
||||
apply stable
|
||||
[[ $(stat -c %a "$TARGET_ROOT/etc/pacman.conf") == 600 ]] || fail "refresh keeps a customized pacman.conf mode"
|
||||
chmod 644 "$TARGET_ROOT/etc/pacman.conf"
|
||||
pass "refresh keeps existing config file modes"
|
||||
|
||||
printf 'cn\n' > "$TARGET_ROOT/etc/omarchy/region"
|
||||
for channel in stable rc edge; do
|
||||
case "$channel" in
|
||||
stable) host=stable-mirror.omarchy ;;
|
||||
rc) host=rc-mirror.omarchy ;;
|
||||
edge) host=mirror.omarchy ;;
|
||||
esac
|
||||
apply "$channel"
|
||||
expected=$(printf 'Server = https://%s.cn/$repo/os/$arch\nServer = https://%s.org/$repo/os/$arch' "$host" "$host")
|
||||
[[ $(<"$TARGET_ROOT/etc/pacman.d/mirrorlist") == "$expected" ]] || fail "$channel mirror order and variables"
|
||||
{
|
||||
cat "$ROOT/default/pacman/pacman-$channel.conf"
|
||||
printf '\n[archlinuxcn]\nServer = https://mirrors.ustc.edu.cn/archlinuxcn/$arch\n'
|
||||
} > "$scratch/expected.conf"
|
||||
cmp "$scratch/expected.conf" "$TARGET_ROOT/etc/pacman.conf"
|
||||
apply "$channel"
|
||||
cmp "$TARGET_ROOT/etc/pacman.conf.bak" "$TARGET_ROOT/etc/pacman.conf"
|
||||
cmp "$TARGET_ROOT/etc/pacman.d/mirrorlist.bak" "$TARGET_ROOT/etc/pacman.d/mirrorlist"
|
||||
done
|
||||
pass "China puts its channel mirror first, keeps the global one as fallback, and is idempotent"
|
||||
|
||||
cp "$TARGET_ROOT/etc/pacman.conf" "$scratch/before.conf"
|
||||
cp "$TARGET_ROOT/etc/pacman.d/mirrorlist" "$scratch/before.mirrors"
|
||||
for region in '' CN chn zz '../cn' '$(touch NEVER_EXECUTE)'; do
|
||||
printf '%s\n' "$region" > "$TARGET_ROOT/etc/omarchy/region"
|
||||
if apply stable > "$scratch/error" 2>&1; then
|
||||
fail "invalid region accepted: $region"
|
||||
fi
|
||||
cmp "$scratch/before.conf" "$TARGET_ROOT/etc/pacman.conf"
|
||||
cmp "$scratch/before.mirrors" "$TARGET_ROOT/etc/pacman.d/mirrorlist"
|
||||
done
|
||||
pass "unsupported and malformed regions fail before replacing either config"
|
||||
|
||||
printf 'cn\n' > "$TARGET_ROOT/etc/omarchy/region"
|
||||
mkdir -p "$scratch/incomplete/default/regions/cn/pacman"
|
||||
cp -a "$ROOT/default/pacman" "$scratch/incomplete/default/"
|
||||
cp "$ROOT/default/regions/cn/pacman/pacman.conf.append" "$scratch/incomplete/default/regions/cn/pacman/"
|
||||
if OMARCHY_PATH="$scratch/incomplete" apply stable > "$scratch/error" 2>&1; then
|
||||
fail "incomplete region profile accepted"
|
||||
fi
|
||||
cmp "$scratch/before.conf" "$TARGET_ROOT/etc/pacman.conf"
|
||||
cmp "$scratch/before.mirrors" "$TARGET_ROOT/etc/pacman.d/mirrorlist"
|
||||
pass "missing fragment cannot leave a half-generated config"
|
||||
|
||||
# Refresh renders these files unprivileged and hands root plain copies; its
|
||||
# privileged path is covered in the sudo-boundary sandbox, never on the host.
|
||||
cp "$TARGET_ROOT/etc/pacman.conf" "$scratch/before.conf"
|
||||
mkdir -p "$scratch/rendered"
|
||||
"$ROOT/bin/omarchy-apply-pacman" --render rc "$scratch/rendered" "$TARGET_ROOT"
|
||||
rg -qxF 'Server = https://pkgs.omarchy.org/rc/$arch' "$scratch/rendered/pacman.conf"
|
||||
rg -qxF '[archlinuxcn]' "$scratch/rendered/pacman.conf"
|
||||
[[ $(<"$scratch/rendered/mirrorlist") == $'Server = https://rc-mirror.omarchy.cn/$repo/os/$arch\nServer = https://rc-mirror.omarchy.org/$repo/os/$arch' ]] ||
|
||||
fail "render puts the regional channel mirror first"
|
||||
cmp "$scratch/before.conf" "$TARGET_ROOT/etc/pacman.conf"
|
||||
pass "render builds the regional channel files without touching the target"
|
||||
|
||||
if "$ROOT/bin/omarchy-apply-pacman" --render dev "$scratch/rendered" "$TARGET_ROOT" > "$scratch/error" 2>&1; then
|
||||
fail "render accepts a nonexistent dev pacman template"
|
||||
fi
|
||||
pass "render rejects channels without pacman templates"
|
||||
|
||||
printf 'global\n' > "$TARGET_ROOT/etc/omarchy/region"
|
||||
apply stable
|
||||
cmp "$ROOT/default/pacman/pacman-stable.conf" "$TARGET_ROOT/etc/pacman.conf"
|
||||
cmp "$ROOT/default/pacman/mirrorlist-stable" "$TARGET_ROOT/etc/pacman.d/mirrorlist"
|
||||
pass "users can opt out of regional repository defaults"
|
||||
|
||||
# The shipped China profile is repository defaults only. Language, locale, and
|
||||
# input methods follow the installer's language choice, not the region.
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
region = Path(os.environ["ROOT"]) / "default/regions/cn"
|
||||
packages = {line.strip() for line in (region / "packages").read_text().splitlines() if line.strip() and not line.startswith("#")}
|
||||
assert packages == {"archlinuxcn-keyring"}, packages
|
||||
assert not (region / "skel").exists()
|
||||
PY
|
||||
pass "China profile adds only its repository keyring"
|
||||
|
||||
# The installer derives the region from the chosen timezone, so every profile
|
||||
# needs a mapping, and no timezone may select two regions.
|
||||
python3 - <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
zoneinfo = Path("/usr/share/zoneinfo")
|
||||
owners = {}
|
||||
for profile in sorted((Path(os.environ["ROOT"]) / "default/regions").iterdir()):
|
||||
lines = (profile / "timezones").read_text().splitlines()
|
||||
zones = [line.strip() for line in lines if line.strip() and not line.startswith("#")]
|
||||
assert zones, f"{profile.name} maps no timezones"
|
||||
for zone in zones:
|
||||
assert zone not in owners, f"{zone} maps to {owners[zone]} and {profile.name}"
|
||||
owners[zone] = profile.name
|
||||
if zoneinfo.is_dir():
|
||||
assert (zoneinfo / zone).is_file(), f"{profile.name} lists unknown timezone {zone}"
|
||||
|
||||
# The installer offers tzdata's backward-compatible names too, so China
|
||||
# covers every name that links to its two zones and nothing else.
|
||||
# Without tzdata.zi (minimal containers) only the canonical zones can be
|
||||
# checked, so completeness is enforced where the host has the link table.
|
||||
china = {zone for zone, region in owners.items() if region == "cn"}
|
||||
expected = {"Asia/Shanghai", "Asia/Urumqi"}
|
||||
tzdata = zoneinfo / "tzdata.zi"
|
||||
if tzdata.is_file():
|
||||
for line in tzdata.read_text().splitlines():
|
||||
fields = line.split()
|
||||
if len(fields) == 3 and fields[0] == "L" and fields[1] in {"Asia/Shanghai", "Asia/Urumqi"}:
|
||||
expected.add(fields[2])
|
||||
assert china == expected, (china, expected)
|
||||
else:
|
||||
assert expected <= china, (china, expected)
|
||||
PY
|
||||
pass "region profiles map each timezone to at most one region"
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
source "$SHELL_TEST_DIR/fixtures/sudo-boundary-test.sh"
|
||||
copy_boundary_file bin/omarchy-refresh-pacman
|
||||
copy_boundary_file bin/omarchy-apply-pacman
|
||||
for file in default/pacman/pacman-rc.conf default/pacman/mirrorlist-rc \
|
||||
default/regions/cn/pacman/pacman.conf.append default/regions/cn/pacman/mirrorlist-rc.prepend; do
|
||||
copy_boundary_file "$file"
|
||||
done
|
||||
|
||||
# A sandboxed China machine: the render reads this region marker, never the
|
||||
# host's /etc/omarchy/region.
|
||||
mkdir -p "$SUDO_TEST_TARGET/etc/omarchy"
|
||||
printf 'cn\n' >"$SUDO_TEST_TARGET/etc/omarchy/region"
|
||||
|
||||
# cp still logs its step, but now really copies, so the rendered files exist.
|
||||
# Root's copies into /etc land in a capture directory instead of the host.
|
||||
export SUDO_TEST_CAPTURE="$boundary_tmp/capture"
|
||||
rm "$SUDO_TEST_ROOT/bin/cp"
|
||||
cat >"$SUDO_TEST_ROOT/bin/cp" <<'STUB'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
printf 'step:cp %s\n' "$*" >>"$SUDO_TEST_LOG"
|
||||
destination=${!#}
|
||||
if [[ $destination == /etc/* ]]; then
|
||||
source=${*: -2:1}
|
||||
[[ $source == /etc/* ]] && exit 0
|
||||
/usr/bin/mkdir -p "$SUDO_TEST_CAPTURE${destination%/*}"
|
||||
exec /usr/bin/cp "$source" "$SUDO_TEST_CAPTURE$destination"
|
||||
fi
|
||||
exec /usr/bin/cp "$@"
|
||||
STUB
|
||||
chmod +x "$SUDO_TEST_ROOT/bin/cp"
|
||||
|
||||
# Regional rendering must stay unprivileged: root only copies finished files,
|
||||
# exactly as the channel refresh did before regions existed.
|
||||
reset_boundary
|
||||
"$SUDO_TEST_ROOT/bin/omarchy-refresh-pacman" rc >"$boundary_tmp/output" 2>&1 || fail "refresh failed" "$(<"$boundary_tmp/output")"
|
||||
assert_boundary_cold "regional refresh"
|
||||
python3 - "$SUDO_TEST_LOG" "$SUDO_TEST_ROOT" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
events = open(sys.argv[1]).read().splitlines()
|
||||
root = sys.argv[2]
|
||||
render = [i for i, e in enumerate(events) if e.startswith('step:cp ' + root + '/default/pacman/pacman-rc.conf ')]
|
||||
assert len(render) == 1, events
|
||||
rendered = events[render[0]].split()[-1].rsplit('/', 1)[0]
|
||||
copies = [i for i, e in enumerate(events) if e.startswith('sudo -N cp ')]
|
||||
assert [events[i] for i in copies[2:]] == [
|
||||
f'sudo -N cp -f {rendered}/pacman.conf /etc/pacman.conf',
|
||||
f'sudo -N cp -f {rendered}/mirrorlist /etc/pacman.d/mirrorlist',
|
||||
], events
|
||||
assert not any(e.startswith('sudo ') and 'omarchy-apply-pacman' in e for e in events), events
|
||||
assert render[0] < copies[0], events
|
||||
transaction = next(i for i, e in enumerate(events) if e.startswith('step:pacman '))
|
||||
assert max(copies) < transaction, events
|
||||
PY
|
||||
{
|
||||
cat "$ROOT/default/pacman/pacman-rc.conf"
|
||||
printf '\n'
|
||||
cat "$ROOT/default/regions/cn/pacman/pacman.conf.append"
|
||||
} >"$boundary_tmp/expected-pacman.conf"
|
||||
cat "$ROOT/default/regions/cn/pacman/mirrorlist-rc.prepend" "$ROOT/default/pacman/mirrorlist-rc" >"$boundary_tmp/expected-mirrorlist"
|
||||
cmp -s "$boundary_tmp/expected-pacman.conf" "$SUDO_TEST_CAPTURE/etc/pacman.conf" ||
|
||||
fail "root installs the rendered China pacman.conf" "$(diff "$boundary_tmp/expected-pacman.conf" "$SUDO_TEST_CAPTURE/etc/pacman.conf" 2>&1)"
|
||||
cmp -s "$boundary_tmp/expected-mirrorlist" "$SUDO_TEST_CAPTURE/etc/pacman.d/mirrorlist" ||
|
||||
fail "root installs the rendered China mirrorlist" "$(diff "$boundary_tmp/expected-mirrorlist" "$SUDO_TEST_CAPTURE/etc/pacman.d/mirrorlist" 2>&1)"
|
||||
pass "refresh renders regional config unprivileged and gives root only plain copies"
|
||||
|
||||
reset_boundary
|
||||
if "$SUDO_TEST_ROOT/bin/omarchy-refresh-pacman" dev >"$boundary_tmp/output" 2>&1; then
|
||||
fail "refresh accepts an invalid channel"
|
||||
fi
|
||||
if grep -Eq '^(sudo -N cp |step:pacman )' "$SUDO_TEST_LOG"; then
|
||||
fail "invalid channel reached privileged work" "$(<"$SUDO_TEST_LOG")"
|
||||
fi
|
||||
pass "failed refresh does not proceed to privileged copies or a package update"
|
||||
|
||||
# A failed render still removes its scratch directory and exits cold.
|
||||
reset_boundary
|
||||
mv "$SUDO_TEST_ROOT/bin/omarchy-apply-pacman" "$boundary_tmp/omarchy-apply-pacman"
|
||||
cat >"$SUDO_TEST_ROOT/bin/omarchy-apply-pacman" <<'STUB'
|
||||
#!/bin/bash
|
||||
: >"$3/pacman.conf"
|
||||
exit 1
|
||||
STUB
|
||||
chmod +x "$SUDO_TEST_ROOT/bin/omarchy-apply-pacman"
|
||||
mkdir -p "$boundary_tmp/scratch"
|
||||
if TMPDIR="$boundary_tmp/scratch" "$SUDO_TEST_ROOT/bin/omarchy-refresh-pacman" rc >"$boundary_tmp/output" 2>&1; then
|
||||
fail "refresh succeeds after a failed render"
|
||||
fi
|
||||
mv "$boundary_tmp/omarchy-apply-pacman" "$SUDO_TEST_ROOT/bin/omarchy-apply-pacman"
|
||||
[[ -z $(ls -A "$boundary_tmp/scratch") ]] || fail "failed render leaves its scratch directory" "$(ls -AR "$boundary_tmp/scratch")"
|
||||
if grep -Eq '^(sudo -N cp |step:pacman )' "$SUDO_TEST_LOG"; then
|
||||
fail "failed render reached privileged work" "$(<"$SUDO_TEST_LOG")"
|
||||
fi
|
||||
assert_boundary_cold "failed render"
|
||||
pass "failed render removes its scratch directory and exits cold"
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
root="$test_tmp/root" sandbox="$test_tmp/omarchy" calls="$test_tmp/calls"
|
||||
mkdir -p "$root/etc/pacman.d" "$sandbox/bin" "$sandbox/install/config" "$sandbox/default/regions" "$test_tmp/bin" "$test_tmp/home"
|
||||
cp -r "$ROOT/default/regions/cn" "$sandbox/default/regions/"
|
||||
rewrite() { sed -e "s|/etc/|$root/etc/|g" -e "s| /\\.cargo/| $root/.cargo/|g" "$1" >"$2"; chmod +x "$2"; }
|
||||
rewrite "$ROOT/install/config/region.sh" "$sandbox/install/config/region.sh"
|
||||
rewrite "$ROOT/bin/omarchy-region" "$sandbox/bin/omarchy-region"
|
||||
|
||||
for command in omarchy-refresh-pacman omarchy-system-logout pacman-key pacman omarchy-apply-pacman; do
|
||||
printf '#!/bin/bash\necho "%s $*" >>"%s"\n' "$command" "$calls" >"$test_tmp/bin/$command"
|
||||
done
|
||||
printf '#!/bin/bash\necho "omarchy-update-pacman $*" >>"%s"\n[[ -z ${STUB_UPDATE_FAIL:-} ]]\n' "$calls" >"$test_tmp/bin/omarchy-update-pacman"
|
||||
printf '#!/bin/bash\necho "${STUB_CHANNEL:-stable}"\n' >"$test_tmp/bin/omarchy-version-channel"
|
||||
printf '#!/bin/bash\n[[ $1 == env ]] && shift\nexec env "$@"\n' >"$test_tmp/bin/sudo"
|
||||
printf '#!/bin/bash\necho "gum $*" >>"%s"\nexit "${GUM_ANSWER:-1}"\n' "$calls" >"$test_tmp/bin/gum"
|
||||
chmod +x "$test_tmp/bin"/*
|
||||
mkdir -p "$test_tmp/tmp"
|
||||
export PATH="$test_tmp/bin:$sandbox/bin:$PATH" OMARCHY_PATH="$sandbox" HOME="$test_tmp/home" TMPDIR="$test_tmp/tmp"
|
||||
switch_calls() { sed "s|$test_tmp/tmp/[^/ ]*|RENDERED|g" "$calls"; }
|
||||
|
||||
omarchy-region cn >/dev/null
|
||||
[[ $(omarchy-region) == "China" && $(<"$root/etc/omarchy/region") == "cn" ]] ||
|
||||
fail "switching to China records cn"
|
||||
[[ $(switch_calls) == $'omarchy-update-pacman -Syu --noconfirm\nomarchy-apply-pacman --render stable RENDERED\npacman --config RENDERED/pacman.conf -Sy --noconfirm --needed archlinuxcn-keyring\npacman-key --populate archlinuxcn\nomarchy-refresh-pacman stable' ]] ||
|
||||
fail "China upgrades, trusts ArchLinuxCN through the rendered cn config, then refreshes" "$(switch_calls)"
|
||||
[[ -z $(ls -A "$test_tmp/tmp") ]] || fail "the rendered config is removed"
|
||||
pass "switching to China trusts ArchLinuxCN before the refresh upgrades through it"
|
||||
|
||||
: >"$calls"
|
||||
STUB_CHANNEL=edge omarchy-region cn >/dev/null
|
||||
grep -qx 'omarchy-refresh-pacman edge' "$calls" || fail "the refresh keeps the machine's channel" "$(<"$calls")"
|
||||
pass "switching to China again keeps the channel"
|
||||
|
||||
: >"$calls"
|
||||
omarchy-region global >/dev/null
|
||||
[[ $(omarchy-region) == "Global" && ! -e $root/etc/omarchy/region ]] ||
|
||||
fail "switching to Global removes the region"
|
||||
[[ $(<"$calls") == 'omarchy-refresh-pacman stable' ]] ||
|
||||
fail "Global refreshes pacman back to the channel defaults" "$(<"$calls")"
|
||||
pass "switching to Global restores the channel's pacman defaults"
|
||||
|
||||
if output=$(omarchy-region mars 2>&1); then
|
||||
fail "an unknown region is refused" "$output"
|
||||
fi
|
||||
if output=$(STUB_CHANNEL="stable / edge" omarchy-region cn 2>&1); then
|
||||
fail "a mixed channel is refused" "$output"
|
||||
fi
|
||||
[[ $(omarchy-region) == "Global" ]] || fail "a refused switch changes nothing"
|
||||
pass "an unknown region or an unclear channel is refused"
|
||||
|
||||
: >"$calls"
|
||||
if output=$(STUB_UPDATE_FAIL=1 omarchy-region cn 2>&1); then
|
||||
fail "switching to China fails when the upgrade fails" "$output"
|
||||
fi
|
||||
[[ $(omarchy-region) == "Global" ]] && ! grep -q '^omarchy-refresh-pacman' "$calls" ||
|
||||
fail "a failed switch to China leaves the region Global and pacman untouched" "$(<"$calls")"
|
||||
pass "a failed switch to China leaves the machine Global"
|
||||
|
||||
grep -q '^gum' "$calls" && fail "a switch without a terminal asks nothing" "$(<"$calls")"
|
||||
: >"$calls"
|
||||
output=$(GUM_ANSWER=1 script -qec "omarchy-region cn" /dev/null)
|
||||
[[ $output == *"Log back in to switch the developer mirrors."* ]] && ! grep -q '^omarchy-system-logout' "$calls" ||
|
||||
fail "declining the logout leaves a reminder and the session" "$output"
|
||||
: >"$calls"
|
||||
GUM_ANSWER=0 script -qec "omarchy-region global" /dev/null >/dev/null
|
||||
grep -qxF 'gum confirm --affirmative Log out now --negative Later Log out now to switch the developer mirrors?' "$calls" &&
|
||||
grep -qx 'omarchy-system-logout ' "$calls" || fail "a terminal switch offers to log out and logs out on yes" "$(<"$calls")"
|
||||
pass "a switch in a terminal offers to log out"
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
region_setup="$test_tmp/region.sh"
|
||||
root="$test_tmp/root"
|
||||
sed -e "s|/etc/|$root/etc/|g" -e "s| /\.cargo/| $root/.cargo/|g" "$ROOT/install/config/region.sh" >"$region_setup"
|
||||
export OMARCHY_PATH="$ROOT"
|
||||
mkdir -p "$root/etc/omarchy"
|
||||
|
||||
bash -e "$region_setup"
|
||||
[[ -z $(find "$root" -type l) ]] || fail "no marker links nothing"
|
||||
pass "no marker installs Global"
|
||||
|
||||
echo cn >"$root/etc/omarchy/region"
|
||||
bash -e "$region_setup"
|
||||
for link in "environment.conf etc/environment.d/90-omarchy-china.conf" "cargo.toml .cargo/config.toml" "gemrc etc/gemrc"; do
|
||||
read -r source target <<<"$link"
|
||||
[[ $(readlink "$root/$target") == "$ROOT/default/regions/cn/$source" ]] || fail "China links /$target to the shipped $source"
|
||||
done
|
||||
bash -e "$region_setup"
|
||||
pass "the cn marker links the developer mirror drop-ins, again without error"
|
||||
|
||||
rm "$root/etc/gemrc"
|
||||
echo ":sources: []" >"$root/etc/gemrc"
|
||||
if output=$(bash -e "$region_setup" 2>&1); then
|
||||
fail "China refuses to replace an admin's /etc/gemrc" "$output"
|
||||
fi
|
||||
[[ $output == "Refusing to replace $root/etc/gemrc, which Omarchy did not create" && $(<"$root/etc/gemrc") == ":sources: []" ]] ||
|
||||
fail "China names the file it refuses to replace and leaves it" "$output"
|
||||
rm "$root/etc/gemrc"
|
||||
pass "region setup never replaces mirror config Omarchy did not create"
|
||||
|
||||
echo mars >"$root/etc/omarchy/region"
|
||||
if output=$(bash -e "$region_setup" 2>&1); then
|
||||
fail "an unknown region fails" "$output"
|
||||
fi
|
||||
[[ $output == "Unknown region: mars" ]] || fail "region setup names the unknown region" "$output"
|
||||
pass "region setup rejects an unknown region"
|
||||
|
||||
echo cn >"$root/etc/omarchy/region"
|
||||
bash -e "$region_setup"
|
||||
OMARCHY_REGION=global bash -e "$region_setup"
|
||||
[[ ! -e $root/etc/omarchy/region && -z $(find "$root" -type l) ]] || fail "Global removes the marker and the developer mirror links"
|
||||
ln -s /usr/share/somewhere/gemrc "$root/etc/gemrc"
|
||||
OMARCHY_REGION=global bash -e "$region_setup"
|
||||
[[ -L $root/etc/gemrc ]] || fail "Global keeps a gemrc link Omarchy did not create"
|
||||
rm "$root/etc/gemrc"
|
||||
pass "switching to Global removes only what Omarchy linked"
|
||||
|
||||
OMARCHY_REGION=cn bash -e "$region_setup"
|
||||
[[ $(<"$root/etc/omarchy/region") == "cn" && $(readlink "$root/etc/gemrc") == "$ROOT/default/regions/cn/gemrc" ]] ||
|
||||
fail "switching to China records cn and links the drop-ins"
|
||||
pass "switching to China records cn and links the drop-ins"
|
||||
|
||||
region_bin="$test_tmp/omarchy-region"
|
||||
sed "s|/etc/omarchy/region|$root/etc/omarchy/region|" "$ROOT/bin/omarchy-region" >"$region_bin"
|
||||
chmod +x "$region_bin"
|
||||
[[ $("$region_bin") == "China" ]] || fail "the cn marker reads as China"
|
||||
rm "$root/etc/omarchy/region"
|
||||
[[ $("$region_bin") == "Global" ]] || fail "no marker reads as Global"
|
||||
echo mars >"$root/etc/omarchy/region"
|
||||
if output=$("$region_bin" 2>&1); then
|
||||
fail "an unknown marker fails" "$output"
|
||||
fi
|
||||
[[ $output == "Unknown region in $root/etc/omarchy/region: mars" ]] || fail "the reader names the file and value" "$output"
|
||||
pass "omarchy region reads Global, China, and fails on anything else"
|
||||
|
||||
grep -qx 'run_logged "$OMARCHY_INSTALL/config/region.sh"' "$ROOT/install/config/all.sh" || fail "the install runs region setup"
|
||||
pass "the install runs region setup"
|
||||
@@ -6,6 +6,7 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
source "$SHELL_TEST_DIR/fixtures/sudo-boundary-test.sh"
|
||||
copy_boundary_file bin/omarchy-update
|
||||
copy_boundary_file bin/omarchy-refresh-pacman
|
||||
copy_boundary_file bin/omarchy-apply-pacman
|
||||
# Replace the step symlink, preserving the real fixture dispatcher.
|
||||
rm "$SUDO_TEST_ROOT/bin/omarchy-update-aur-pkgs"
|
||||
copy_boundary_file bin/omarchy-update-aur-pkgs
|
||||
|
||||
Reference in new issue
Block a user