Merge reworked #13690: timezone-derived region and channel mirror prepends
Drop the .cn servers.sed twins, the USTC Arch fallback and the closed #14399 timezone preselection. region.sh follows the installer's marker.
This commit is contained in:
commit
4521dac023
25 files changed
+150
-206
No files matched your search
@@ -99,8 +99,8 @@ Exceptions are allowed for migration and package-helper scripts where the helper
|
||||
# Region
|
||||
|
||||
- `/etc/omarchy/region` holds `cn`; no file means Global. `omarchy-region` prints the region and switches it (`omarchy region cn|global`); the profile lives in `default/regions/cn/`, its contract in `docs/regions.md`, user-facing docs in `manual/52-china.md`.
|
||||
- `install/config/region.sh` is the one system leaf for both the ISO install (`OMARCHY_REGION`, from the configurator's `omarchy_install.region`) and the runtime switch: region file, the profile drop-ins it symlinks, the Rime packages, and the locale. `install/user/region.sh` runs `rime-ice-installer --asset-dir /opt/packages/rime-ice` only in the `iso-chroot` context, where the ISO bundles that data.
|
||||
- `omarchy-apply-pacman` renders pacman for every caller: the channel templates, then the profile's `pacman/servers.sed` (a `.omarchy.cn` twin ahead of every `.omarchy.org` server) and its `*.append` files. Nothing in `omarchy update` or migrations may change the region or `/etc/locale.conf`, and a migration that edits an `.omarchy.org` `Server` line must edit its `.cn` twin the same way.
|
||||
- `install/config/region.sh` is the one system leaf for both the ISO install (the `/etc/omarchy/region` marker the installer derives from the timezone) and the runtime switch (`OMARCHY_REGION`): region file, the profile drop-ins it symlinks, the Rime packages, and the locale. `install/user/region.sh` runs `rime-ice-installer --asset-dir /opt/packages/rime-ice` only in the `iso-chroot` context, where the ISO bundles that data.
|
||||
- `omarchy-apply-pacman` renders pacman for every caller: the channel templates, the profile's `pacman/mirrorlist-<channel>.prepend` ahead of the mirrorlist, and its `pacman.conf.append`. Nothing in `omarchy update` or migrations may change the region or `/etc/locale.conf`.
|
||||
- China-only menu rows carry `"when":"[[ \"$(omarchy-region)\" == \"China\" ]]"` on every row, not only the submenu, because search checks each row's own guard.
|
||||
- Chinese menu labels live in `default/omarchy/omarchy-menu.zh-cn.jsonc`, merged when `LANG` starts with `zh_CN`; give a new menu row with translatable words a label there.
|
||||
- Shell text goes through `I18n.tr("English %1", arg)` from `qs.Commons`, with its Chinese in `shell/Commons/I18nZh.js` keyed by the English. Notifications and OSD messages are worded by their sender with `omarchy-text "English %1" "$arg"` (Python: `text()`, QML: `I18n.tr`), which reads the same table; the shell shows them as given, so user text is never translated. `test/shell.d/shell-i18n-test.sh` fails on a missing entry.
|
||||
|
||||
+17
-17
@@ -53,25 +53,25 @@ else
|
||||
trap 'rm -rf "$staging"' EXIT
|
||||
fi
|
||||
cp "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" "$staging/pacman.conf"
|
||||
cp "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" "$staging/mirrorlist"
|
||||
if [[ $region != "global" ]]; then
|
||||
if [[ $region == "global" ]]; then
|
||||
cp "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" "$staging/mirrorlist"
|
||||
else
|
||||
profile="$OMARCHY_PATH/default/regions/$region/pacman"
|
||||
# A region's own copies of the channel servers go ahead of them.
|
||||
if [[ -r $profile/servers.sed ]]; then
|
||||
sed -i -f "$profile/servers.sed" "$staging/pacman.conf" "$staging/mirrorlist"
|
||||
fi
|
||||
printf '\n' >> "$staging/pacman.conf"
|
||||
cat "$profile/pacman.conf.append" >> "$staging/pacman.conf"
|
||||
cat "$profile/mirrorlist.append" >> "$staging/mirrorlist"
|
||||
# pacman tries servers in order, so the regional mirror leads and the
|
||||
# channel's own mirror stays as the fallback.
|
||||
cat "$profile/mirrorlist-$channel.prepend" "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" > "$staging/mirrorlist"
|
||||
fi
|
||||
[[ -z $render_dir ]] || exit 0
|
||||
|
||||
mkdir -p "$etc/pacman.d"
|
||||
for file in pacman.conf pacman.d/mirrorlist; do
|
||||
if [[ -e $etc/$file ]]; then
|
||||
cp -f "$etc/$file" "$etc/$file.bak"
|
||||
fi
|
||||
done
|
||||
# Copying over an existing file keeps its owner and mode, as refresh always has.
|
||||
cp -f "$staging/pacman.conf" "$etc/pacman.conf"
|
||||
cp -f "$staging/mirrorlist" "$etc/pacman.d/mirrorlist"
|
||||
if [[ -z $render_dir ]]; then
|
||||
mkdir -p "$etc/pacman.d"
|
||||
for file in pacman.conf pacman.d/mirrorlist; do
|
||||
if [[ -e $etc/$file ]]; then
|
||||
cp -f "$etc/$file" "$etc/$file.bak"
|
||||
fi
|
||||
done
|
||||
# Copying over an existing file keeps its owner and mode, as refresh always has.
|
||||
cp -f "$staging/pacman.conf" "$etc/pacman.conf"
|
||||
cp -f "$staging/mirrorlist" "$etc/pacman.d/mirrorlist"
|
||||
fi
|
||||
@@ -55,10 +55,15 @@ validate_dev_region() {
|
||||
[[ -f $region_file ]] && region=$(<"$region_file")
|
||||
[[ $region == "global" ]] && return 0
|
||||
|
||||
# The refresh after linking needs a runnable helper and both fragments, not
|
||||
# just a directory with the region's name.
|
||||
local profile="$checkout/default/regions/$region/pacman"
|
||||
if [[ ! -x $checkout/bin/omarchy-apply-pacman || ! -r $profile/pacman.conf.append || ! -r $profile/mirrorlist.append ]]; then
|
||||
# The refresh after linking needs a runnable helper and every fragment, not
|
||||
# just a directory with the region's name: dev refreshes edge, and leaving
|
||||
# dev later refreshes stable or rc with this checkout's tools.
|
||||
local profile="$checkout/default/regions/$region/pacman" fragment supported=true
|
||||
[[ -x $checkout/bin/omarchy-apply-pacman ]] || supported=false
|
||||
for fragment in pacman.conf.append mirrorlist-stable.prepend mirrorlist-rc.prepend mirrorlist-edge.prepend; do
|
||||
[[ -r $profile/$fragment ]] || supported=false
|
||||
done
|
||||
if ! $supported; then
|
||||
fail "$checkout does not support the '$region' region, so switching would drop its package repositories. Check out a branch with default/regions/$region, or remove $region_file to use global defaults."
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ set -euo pipefail
|
||||
PROVISIONING_DIR=/var/lib/omarchy/provisioning
|
||||
OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}"
|
||||
LOG_FILE=/var/log/omarchy-provision-owner.log
|
||||
REGION_MARKER=/etc/omarchy/region
|
||||
|
||||
if (( EUID != 0 )); then
|
||||
echo "Error: omarchy-provision-owner must run as root" >&2
|
||||
@@ -651,21 +650,7 @@ user_form() {
|
||||
omarchy_prompt_hostname || return $?
|
||||
|
||||
step "Let's set your timezone..."
|
||||
omarchy_prompt_timezone "$(region_timezone)" || return $?
|
||||
}
|
||||
|
||||
# The zone a region profile preselects when there is no geo guess, which is
|
||||
# every offline first boot. Reads the marker omarchy-apply-pacman reads; global,
|
||||
# missing, or malformed regions offer nothing.
|
||||
region_timezone() {
|
||||
local region=global profile
|
||||
if [[ -r $REGION_MARKER ]]; then
|
||||
region=$(<"$REGION_MARKER")
|
||||
fi
|
||||
profile="$OMARCHY_PATH/default/regions/$region/timezone"
|
||||
if [[ $region =~ ^[a-z]{2}$ && -r $profile ]]; then
|
||||
head -n 1 "$profile"
|
||||
fi
|
||||
omarchy_prompt_timezone || return $?
|
||||
}
|
||||
|
||||
confirm_form() {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Server = https://mirror.omarchy.cn/$repo/os/$arch
|
||||
@@ -0,0 +1 @@
|
||||
Server = https://rc-mirror.omarchy.cn/$repo/os/$arch
|
||||
@@ -0,0 +1 @@
|
||||
Server = https://stable-mirror.omarchy.cn/$repo/os/$arch
|
||||
@@ -1 +0,0 @@
|
||||
Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch
|
||||
@@ -1 +0,0 @@
|
||||
s|^Server = https://\([a-z-]*\)\.omarchy\.org/\(.*\)$|Server = https://\1.omarchy.cn/\2\n&|
|
||||
@@ -1 +0,0 @@
|
||||
Asia/Shanghai
|
||||
@@ -0,0 +1,9 @@
|
||||
# Installs in these timezones get this region. The installer's timezone list
|
||||
# includes tzdata's backward-compatible names, so they are listed too.
|
||||
Asia/Shanghai
|
||||
Asia/Urumqi
|
||||
Asia/Chongqing
|
||||
Asia/Chungking
|
||||
Asia/Harbin
|
||||
Asia/Kashgar
|
||||
PRC
|
||||
+21
-19
@@ -1,41 +1,43 @@
|
||||
# Regional ISO defaults
|
||||
# Regional defaults
|
||||
|
||||
Regions customize bootstrap defaults independently of update channels. Country identifiers are lowercase, officially assigned ISO 3166-1 alpha-2 codes. `global` is a reserved value for no country-specific additions. Only implemented profiles are accepted; currently `global` and `cn`.
|
||||
Regions customize bootstrap defaults independently of update channels and of the installer's language. Country identifiers are lowercase, officially assigned ISO 3166-1 alpha-2 codes. `global` is a reserved value for no country-specific additions. Only implemented profiles are accepted; currently `global` and `cn`.
|
||||
|
||||
## Runtime configuration
|
||||
|
||||
The ISO installer writes the selected region to `/etc/omarchy/region` before target finalization. It is plain text, not executable configuration. A missing file means `global`; an unsupported value is an error.
|
||||
The ISO installer writes the region it derived to `/etc/omarchy/region` before target finalization. It is plain text, not executable configuration, owned by root and world-readable (`0644`), because refresh renders as the calling user. A missing file means `global`; an unsupported value is an error.
|
||||
|
||||
`omarchy-apply-pacman <stable|rc|edge> [target-root]` restores the selected channel's two pacman templates, runs `default/regions/<region>/pacman/servers.sed` over both when the profile has one, and appends `default/regions/<region>/pacman/{pacman.conf.append,mirrorlist.append}`. Both files are generated before either live file is replaced. Existing files are backed up with `.bak`. Dev uses the edge pacman channel, as before.
|
||||
`omarchy-apply-pacman <stable|rc|edge> [target-root]` restores the selected channel's two pacman templates, appends the region's `pacman.conf.append` to `pacman.conf`, and puts the region's `mirrorlist-<channel>.prepend` ahead of the channel's mirrorlist. Both files are generated before either live file is replaced. Existing files are backed up with `.bak`. Dev uses the edge pacman channel, as before.
|
||||
|
||||
Both installation finalization and `omarchy-refresh-pacman` use this command. Explicit refreshes and channel changes restore regional defaults; ordinary updates do not enforce them. The existing `pre-refresh-pacman` user hook runs after regeneration and before the update, so users can maintain overrides there. Switching to the dev channel refuses a checkout that lacks this machine's region profile, because that checkout's own refresh would otherwise drop the regional repositories. Users can edit the files directly or set the region to `global` before a refresh to stop applying regional repository defaults. This does not uninstall packages or rewrite anything in existing home directories.
|
||||
|
||||
The China profile's `servers.sed` puts a `.omarchy.cn` twin ahead of every `.omarchy.org` server, including the `[omarchy]` package repository, so the channel's own snapshot stays first and `.org` stays the fallback. It then appends USTC's regular Arch mirror and adds the USTC ArchLinuxCN community repository. USTC's mirror, run by the University of Science and Technology of China, and ArchLinuxCN, a long-standing community repository, are the last fallback behind Omarchy's own mirrors in China. USTC is not a stable/RC snapshot replica: missing old packages and newer fallback databases remain compatibility risks.
|
||||
Refresh renders both files as the calling user and gives root only the finished copies, so the sudo boundary stays command-scoped. That boundary does not defend against other code already running as the same user: such code could change the rendered files while refresh waits for the password, as it could alter that user's own environment in other ways.
|
||||
|
||||
## Switching an installed machine
|
||||
The China profile puts Omarchy's China replica of the channel's Arch mirror first (`stable-mirror.omarchy.cn`, `rc-mirror.omarchy.cn`, or `mirror.omarchy.cn` for edge and dev) and keeps the channel's `.org` mirror as the fallback. pacman tries servers in order and moves to the next one when a download fails, including a 404. After three failed downloads from one server, it skips that server for the rest of the transaction. It also takes the package databases from the first server that answers, without comparing them, so the China replicas must carry complete, current metadata. The profile also adds the ArchLinuxCN community repository from USTC's mirror, run by the University of Science and Technology of China. It does not change the `[omarchy]` package repository. ArchLinuxCN is rolling and built against current Arch, while stable and RC are snapshots, so its packages can need newer libraries than a stable machine has.
|
||||
|
||||
`omarchy-region cn|global` switches at runtime. China installs the input method and runs `install/config/region.sh` (region file, developer-mirror drop-ins, locale). It then renders the cn pacman config to a scratch directory, installs the profile `packages` through it, populates the ArchLinuxCN keyring, and only then refreshes pacman on the current channel, so the refresh upgrade already trusts ArchLinuxCN. China also sets up Rime Ice in the person's home. Global undoes the drop-ins and the locale, refreshes pacman back to the channel defaults, and removes the Claude Code API settings from the home; it keeps Rime.
|
||||
## Profile layout
|
||||
|
||||
## ISO builder contract
|
||||
Each profile lives in `default/regions/<code>/`:
|
||||
|
||||
Every ISO carries the China profile, and the person picks the region on the installer's first screen. The companion `omarchy-iso` repository also accepts `--region global|cn`, which only preselects that choice, records it in `/root/omarchy_region`, and adds a region suffix to non-global artifact and offline-cache names. Existing global names remain unchanged.
|
||||
- `timezones`: the timezones that select this region, one per line, with blank lines and `#` comments allowed. List tzdata's backward-compatible names too, since the installer's timezone list includes them. A timezone may belong to at most one profile.
|
||||
- `packages`: additional target packages, in the same format. Every ISO carries every profile's packages in its offline repository; the installer installs them only on targets in that region. A package named `<name>-keyring` is the profile's trust anchor: the builder installs it through Arch's existing trust chain, and a regional target populates the `<name>` keyring.
|
||||
- `pacman/`: `pacman.conf.append`, added after the channel's `pacman.conf`, and one `mirrorlist-<channel>.prepend` per channel (`stable`, `rc`, `edge`), placed ahead of the channel's mirrorlist so the regional mirror is tried first.
|
||||
|
||||
Regional content comes from the same runtime used by the ISO: either the `--local-source` checkout or the downloaded runtime package. A build against a runtime without regional support fails rather than silently producing an ISO without the China choice.
|
||||
## ISO installer contract
|
||||
|
||||
- `packages`: additional target packages, one name per line, with blank lines and `#` comments allowed. The builder bundles them in the offline repository, and the installer installs them only when China is chosen. The source manifest is not modified.
|
||||
- `pacman/`: repository fragments used by the runtime finalizer. The builder also adds the community repository to its writable online config to obtain regional packages; it does not change its Arch channel mirrors.
|
||||
- `timezone` (optional): one zone name, such as `Asia/Shanghai`. The setup form preselects it in the timezone list when the network geo guess is unavailable, which is always the case offline. Both callers pass it: the ISO configurator from its copy of the profile, and first-boot setup from `default/regions/<region>/timezone` for the region in `/etc/omarchy/region`. A successful geo guess still wins, and the person can choose any other zone.
|
||||
- `environment.conf`, `cargo.toml`, `gemrc`: developer-mirror drop-ins that `install/config/region.sh` symlinks into `/etc` and `/.cargo`. `maven-settings.xml` stays in the profile; `MAVEN_ARGS` in `environment.conf` points at it.
|
||||
- `skel/` (optional): new-user defaults. The ISO installer overlays these onto target `/etc/skel` after the base settings packages and before user creation, including installations that defer user creation until first boot. It never overlays existing home directories.
|
||||
There is one ISO for every region. The companion `omarchy-iso` repository reads the profiles from the runtime it bundles, so a region and its repository defaults can never disagree.
|
||||
|
||||
For China, the builder installs `archlinuxcn-keyring` using the existing Arch trust chain before downloading other regional packages. Signature or keyring failures abort the build; no unverified keyserver trust or online signature bypass is added. The keyring is also installed on the target from the offline package set, then initialized and populated before online repositories are enabled. The ISO's existing offline repository verification policy is unchanged.
|
||||
- **Build:** every profile's `packages` go into the offline repository, but not into the base package list. The builder adds each profile's community repository to its writable online config to download them; it does not change its Arch channel mirrors.
|
||||
- **Install:** the installer maps the timezone the user picked to a region through the `timezones` files; a timezone no profile lists means `global`. It shows the result on the summary screen. Unattended installs can set the region explicitly instead. For a regional install it installs the profile's packages from the offline repository and writes `/etc/omarchy/region`; the runtime finalizer then applies the fragments.
|
||||
- **Deferred installs** have no timezone until first boot, so they are `global` unless the unattended configuration sets a region.
|
||||
|
||||
For China, the builder installs `archlinuxcn-keyring` using the existing Arch trust chain before downloading ArchLinuxCN packages. Signature or keyring failures abort the build; no unverified keyserver trust or online signature bypass is added. On a China target the installer installs the keyring from the offline repository, then initializes and populates it before the online repositories are enabled. Global targets never populate ArchLinuxCN's keys. The ISO's existing offline repository verification policy is unchanged.
|
||||
|
||||
## Scope of the China profile
|
||||
|
||||
The China profile provides repository and developer-mirror defaults and preselects `Asia/Shanghai` as the timezone. System language, the Rime input method and China Apps come from `install/config/region.sh` and `manual/52-china.md`, not from the profile files.
|
||||
The China profile provides repository defaults only. Language is a separate installer choice, not part of a region: a Chinese speaker outside China keeps global mirrors, and a China install in English still gets the regional ones.
|
||||
|
||||
## Remaining release validation
|
||||
|
||||
Unit tests cover region/channel independence, package-list augmentation, keyring failure handling, pre-user defaults, generated pacman contents, repeat application, and refresh ordering; refresh itself is exercised only inside the sudo-boundary sandbox. They do not establish live mirror availability, real package trust, or successful offline installation. Before distribution, build from matching local checkouts on an x86_64 builder and run the disposable-VM acceptance workflow in `agents/skills/acceptance-tests.md`.
|
||||
Unit tests cover region/channel independence, the timezone mapping, generated pacman contents, repeat application, and refresh ordering; refresh itself is exercised only inside the sudo-boundary sandbox. They do not establish live mirror availability, real package trust, or successful offline installation. Before distribution, build from matching local checkouts on an x86_64 builder and run the disposable-VM acceptance workflow in `agents/skills/acceptance-tests.md`.
|
||||
|
||||
The China acceptance run must install without network access, boot to a new user, including deferred first-boot user creation, and keep the regional mirrors, repository, marker, and keyring trust through `stable`, `rc`, and `edge` refreshes.
|
||||
The acceptance run must install without network access in both directions: a non-China timezone must produce pacman files byte for byte identical to the channel templates, and `Asia/Shanghai` must produce the China profile, boot to a new user, and keep the regional mirrors, repository, marker, and keyring trust through `stable`, `rc`, and `edge` refreshes.
|
||||
@@ -279,8 +279,6 @@ There is no version file at runtime. `omarchy-version` derives the version from
|
||||
linked checkout, and `omarchy-version-channel` sniffs the mirrorlist and
|
||||
pacman.conf to answer which channel is active.
|
||||
|
||||
On a China machine (`/etc/omarchy/region` holds `cn`), `omarchy-apply-pacman` runs `default/regions/cn/pacman/servers.sed` over the copied templates, which puts a `.omarchy.cn` twin ahead of every `.omarchy.org` server. The update never recopies these files, and a migration that edits a `Server` line must edit its `.cn` twin the same way, as `1788112314.sh` does, so the twins survive every `omarchy update`.
|
||||
|
||||
## Update-related binaries
|
||||
|
||||
This inventory is intentionally opinionated. Some commands are useful as stable
|
||||
|
||||
@@ -4,7 +4,14 @@ cn_links=(
|
||||
"gemrc /etc/gemrc"
|
||||
)
|
||||
|
||||
case ${OMARCHY_REGION:-global} in
|
||||
# The installer writes the marker; omarchy-region passes the region it switches to.
|
||||
region=global
|
||||
if [[ -e /etc/omarchy/region ]]; then
|
||||
region=$(</etc/omarchy/region)
|
||||
fi
|
||||
region=${OMARCHY_REGION:-$region}
|
||||
|
||||
case $region in
|
||||
global)
|
||||
rm -f /etc/omarchy/region
|
||||
sed -i 's/^LANG=zh_CN\.UTF-8$/LANG=en_US.UTF-8/' /etc/locale.conf
|
||||
@@ -41,7 +48,7 @@ cn)
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown OMARCHY_REGION: $OMARCHY_REGION" >&2
|
||||
echo "Unknown region: $region" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -216,13 +216,10 @@ omarchy_prompt_hostname() {
|
||||
}
|
||||
|
||||
# A fresh machine often hasn't joined a network yet, so the geo guess fails
|
||||
# often; guard it or a `set -e` caller dies before the filter fallback. Callers
|
||||
# may pass the zone to preselect when there is no guess (a region's default),
|
||||
# so an offline install does not open on the top of an alphabetical list.
|
||||
# often; guard it or a `set -e` caller dies before the filter fallback.
|
||||
omarchy_prompt_timezone() {
|
||||
local guess status
|
||||
guess=$(tzupdate -p 2>/dev/null) || guess=""
|
||||
[[ -n $guess ]] || guess="${1:-}"
|
||||
|
||||
if [[ -n $guess ]]; then
|
||||
timezone=$(timedatectl list-timezones | gum choose --height 10 --selected "$guess" --header "$(omarchy_text "Timezone")") && status=0 || status=$?
|
||||
|
||||
@@ -7,6 +7,6 @@ echo "Point rc-channel installs at the rc package repository"
|
||||
# shipped pairing: an administrator who chose another combination keeps it.
|
||||
if grep -q "https://rc-mirror.omarchy.org/" /etc/pacman.d/mirrorlist &&
|
||||
grep -q "^Server = https://pkgs.omarchy.org/edge/" /etc/pacman.conf; then
|
||||
sudo sed -i -E "s#^Server = https://pkgs\.omarchy\.(org|cn)/edge/#Server = https://pkgs.omarchy.\1/rc/#" /etc/pacman.conf
|
||||
sudo sed -i "s|^Server = https://pkgs.omarchy.org/edge/|Server = https://pkgs.omarchy.org/rc/|" /etc/pacman.conf
|
||||
echo "Switched the [omarchy] repository to the rc channel to match this machine's rc mirror."
|
||||
fi
|
||||
@@ -1,12 +0,0 @@
|
||||
echo "Rename the China region to cn and follow its moved profile"
|
||||
|
||||
[[ $(cat /etc/omarchy/region 2>/dev/null) == "china" ]] || exit 0
|
||||
|
||||
# Links first, so a failure leaves china for the retry.
|
||||
for link in /etc/environment.d/90-omarchy-china.conf /.cargo/config.toml /etc/gemrc; do
|
||||
old=$(readlink "$link" || true)
|
||||
if [[ $old == */default/region/china/* ]]; then
|
||||
sudo ln -sfn "$OMARCHY_PATH/default/regions/cn/${old##*/}" "$link"
|
||||
fi
|
||||
done
|
||||
echo cn | sudo tee /etc/omarchy/region >/dev/null
|
||||
@@ -213,7 +213,23 @@ 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"
|
||||
touch "$checkout/default/regions/cn/pacman/pacman.conf.append" "$checkout/default/regions/cn/pacman/mirrorlist.append"
|
||||
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"
|
||||
@@ -221,7 +237,7 @@ 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 an empty region profile or a helper that cannot run"
|
||||
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
|
||||
|
||||
@@ -40,9 +40,6 @@ PY
|
||||
copy_boundary_file bin/omarchy-security-functions
|
||||
copy_boundary_file bin/omarchy-update-pacman
|
||||
copy_boundary_file default/omarchy/sudo-no-update/sudo
|
||||
# The host's own region must not leak into the sandbox.
|
||||
printf '#!/bin/bash\necho Global\n' >"$SUDO_TEST_ROOT/bin/omarchy-region"
|
||||
chmod +x "$SUDO_TEST_ROOT/bin/omarchy-region"
|
||||
|
||||
cat >"$SUDO_TEST_ROOT/mock/sudo" <<'STUB'
|
||||
#!/bin/bash
|
||||
|
||||
@@ -27,24 +27,23 @@ 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.org ;;
|
||||
rc) host=rc-mirror.omarchy.org ;;
|
||||
edge) host=mirror.omarchy.org ;;
|
||||
stable) host=stable-mirror.omarchy ;;
|
||||
rc) host=rc-mirror.omarchy ;;
|
||||
edge) host=mirror.omarchy ;;
|
||||
esac
|
||||
apply "$channel"
|
||||
expected=$(printf 'Server = https://%s/$repo/os/$arch\nServer = https://%s/$repo/os/$arch\nServer = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch' "${host%.org}.cn" "$host")
|
||||
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"
|
||||
{
|
||||
sed -f "$ROOT/default/regions/cn/pacman/servers.sed" "$ROOT/default/pacman/pacman-$channel.conf"
|
||||
cat "$ROOT/default/pacman/pacman-$channel.conf"
|
||||
printf '\n[archlinuxcn]\nServer = https://mirrors.ustc.edu.cn/archlinuxcn/$arch\n'
|
||||
} > "$scratch/expected.conf"
|
||||
grep -qx "Server = https://pkgs.omarchy.cn/$channel/\$arch" "$scratch/expected.conf" || fail "$channel lists the .cn Omarchy repository"
|
||||
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 a .cn twin ahead of each channel server, appends the CN defaults, and is idempotent"
|
||||
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"
|
||||
@@ -76,8 +75,8 @@ 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\nServer = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch' ]] ||
|
||||
fail "render keeps the channel mirror first"
|
||||
[[ $(<"$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"
|
||||
|
||||
@@ -92,8 +91,8 @@ 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 scaffolding only. Locale, timezone,
|
||||
# and input defaults are separate changes built on the same mechanism.
|
||||
# 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
|
||||
@@ -104,3 +103,39 @@ 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"
|
||||
@@ -7,7 +7,7 @@ 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.append; do
|
||||
default/regions/cn/pacman/pacman.conf.append default/regions/cn/pacman/mirrorlist-rc.prepend; do
|
||||
copy_boundary_file "$file"
|
||||
done
|
||||
|
||||
@@ -63,7 +63,7 @@ PY
|
||||
printf '\n'
|
||||
cat "$ROOT/default/regions/cn/pacman/pacman.conf.append"
|
||||
} >"$boundary_tmp/expected-pacman.conf"
|
||||
cat "$ROOT/default/pacman/mirrorlist-rc" "$ROOT/default/regions/cn/pacman/mirrorlist.append" >"$boundary_tmp/expected-mirrorlist"
|
||||
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" ||
|
||||
|
||||
@@ -82,30 +82,3 @@ 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 to English and the global mirrors?' "$calls" &&
|
||||
grep -qx 'omarchy-system-logout ' "$calls" || fail "switching to Global offers to log out in English and logs out on yes" "$(<"$calls")"
|
||||
pass "a switch in a terminal offers to log out in the new region's language"
|
||||
|
||||
rc_migration="$test_tmp/1788112314.sh"
|
||||
rewrite "$ROOT/migrations/1788112314.sh" "$rc_migration"
|
||||
cp "$ROOT/default/pacman/mirrorlist-rc" "$root/etc/pacman.d/mirrorlist"
|
||||
sed -e 's|pkgs.omarchy.org/rc/|pkgs.omarchy.org/edge/|' "$ROOT/default/pacman/pacman-rc.conf" |
|
||||
sed -f "$ROOT/default/regions/cn/pacman/servers.sed" >"$root/etc/pacman.conf"
|
||||
bash -e "$rc_migration" >/dev/null
|
||||
[[ $(grep '^Server' "$root/etc/pacman.conf") == $'Server = https://pkgs.omarchy.cn/rc/$arch\nServer = https://pkgs.omarchy.org/rc/$arch' ]] ||
|
||||
fail "the rc repository migration moves the .cn twin with its .org server" "$(grep '^Server' "$root/etc/pacman.conf")"
|
||||
pass "the rc repository migration moves the .cn twin with its .org server"
|
||||
|
||||
rename_migration="$test_tmp/1791387144.sh"
|
||||
rewrite "$ROOT/migrations/1791387144.sh" "$rename_migration"
|
||||
rm -f "$root/etc/omarchy/region" "$root/etc/environment.d/90-omarchy-china.conf"
|
||||
mkdir -p "$root/etc/environment.d" "$root/etc/omarchy"
|
||||
echo china >"$root/etc/omarchy/region"
|
||||
ln -sfn /usr/share/omarchy/default/region/china/environment.conf "$root/etc/environment.d/90-omarchy-china.conf"
|
||||
printf '#!/bin/bash\nexit 1\n' >"$test_tmp/bin/ln" && chmod +x "$test_tmp/bin/ln"
|
||||
if bash -e "$rename_migration" >/dev/null 2>&1; then
|
||||
fail "a failed link repair fails the migration"
|
||||
fi
|
||||
[[ $(<"$root/etc/omarchy/region") == "china" ]] || fail "a failed link repair leaves china for the retry"
|
||||
rm "$test_tmp/bin/ln"
|
||||
bash -e "$rename_migration" >/dev/null
|
||||
[[ $(<"$root/etc/omarchy/region") == "cn" && $(readlink "$root/etc/environment.d/90-omarchy-china.conf") == "$sandbox/default/regions/cn/environment.conf" ]] ||
|
||||
fail "the rename migration records cn and repoints the links" "$(readlink "$root/etc/environment.d/90-omarchy-china.conf")"
|
||||
pass "the china rename migration repoints the links before it records cn, so a failure retries"
|
||||
+10
-33
@@ -7,35 +7,6 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
china_sed="$ROOT/default/regions/cn/pacman/servers.sed"
|
||||
|
||||
expected_stable_omarchy='Server = https://pkgs.omarchy.cn/stable/$arch
|
||||
Server = https://pkgs.omarchy.org/stable/$arch'
|
||||
actual=$(sed -f "$china_sed" "$ROOT/default/pacman/pacman-stable.conf" | sed -n '/^\[omarchy\]/,/^$/p' | grep '^Server')
|
||||
[[ $actual == "$expected_stable_omarchy" ]] || fail "stable [omarchy] lists pkgs.omarchy.cn before pkgs.omarchy.org" "$actual"
|
||||
pass "stable [omarchy] lists pkgs.omarchy.cn before pkgs.omarchy.org"
|
||||
|
||||
declare -A mirrors=([stable]=stable-mirror [rc]=rc-mirror [edge]=mirror)
|
||||
for channel in stable rc edge; do
|
||||
host=${mirrors[$channel]}
|
||||
expected="Server = https://$host.omarchy.cn/\$repo/os/\$arch
|
||||
Server = https://$host.omarchy.org/\$repo/os/\$arch"
|
||||
actual=$(sed -f "$china_sed" "$ROOT/default/pacman/mirrorlist-$channel")
|
||||
[[ $actual == "$expected" ]] || fail "$channel mirrorlist lists $host.omarchy.cn first" "$actual"
|
||||
pass "$channel mirrorlist lists $host.omarchy.cn first"
|
||||
|
||||
for file in "pacman-$channel.conf" "mirrorlist-$channel"; do
|
||||
original=$(<"$ROOT/default/pacman/$file")
|
||||
stripped=$(sed -f "$china_sed" "$ROOT/default/pacman/$file" | grep -v '^Server = https://[a-z-]*\.omarchy\.cn/')
|
||||
[[ $stripped == "$original" ]] || fail "$file only gains .cn servers" "$(diff <(echo "$original") <(echo "$stripped"))"
|
||||
|
||||
org_count=$(grep -c '^Server = https://[a-z-]*\.omarchy\.org/' "$ROOT/default/pacman/$file")
|
||||
cn_count=$(sed -f "$china_sed" "$ROOT/default/pacman/$file" | grep -c '^Server = https://[a-z-]*\.omarchy\.cn/')
|
||||
((org_count > 0 && org_count == cn_count)) || fail "$file gets one .cn twin per .org server" "org=$org_count cn=$cn_count"
|
||||
done
|
||||
pass "$channel templates only gain one .cn twin per .org server"
|
||||
done
|
||||
|
||||
region_bin="$test_tmp/omarchy-region"
|
||||
region_file="$test_tmp/region"
|
||||
sed "s|/etc/omarchy/region|$region_file|" "$ROOT/bin/omarchy-region" >"$region_bin"
|
||||
@@ -126,15 +97,21 @@ fi
|
||||
[[ ! -e $root/etc/omarchy/region && ! -e $root/etc/gemrc && $(<"$root/etc/locale.conf") == "LANG=en_US.UTF-8" ]] ||
|
||||
fail "a failed package install leaves the machine Global"
|
||||
pass "China installs its packages before it writes any region state"
|
||||
OMARCHY_REGION=cn bash -e "$region_setup"
|
||||
echo cn >"$root/etc/omarchy/region"
|
||||
: >"$calls"
|
||||
env -u OMARCHY_REGION bash -e "$region_setup"
|
||||
[[ ! -e $root/etc/omarchy/region ]] || fail "an installer that passes no region installs Global"
|
||||
pass "Global and an unset region leave no region file"
|
||||
[[ $(<"$root/etc/omarchy/region") == "cn" && $(readlink "$root/etc/gemrc") == "$ROOT/default/regions/cn/gemrc" ]] ||
|
||||
fail "install keeps the cn marker the installer wrote and links its drop-ins"
|
||||
pass "install follows the region marker the installer wrote"
|
||||
OMARCHY_REGION=global bash -e "$region_setup"
|
||||
env -u OMARCHY_REGION bash -e "$region_setup"
|
||||
[[ ! -e $root/etc/omarchy/region && ! -e $root/etc/gemrc ]] || fail "no marker installs Global"
|
||||
pass "no marker installs Global"
|
||||
|
||||
if output=$(OMARCHY_REGION=mars bash -e "$region_setup" 2>&1); then
|
||||
fail "install rejects an unknown region" "$output"
|
||||
fi
|
||||
[[ $output == "Unknown OMARCHY_REGION: mars" ]] || fail "install names the unknown region" "$output"
|
||||
[[ $output == "Unknown region: mars" ]] || fail "install names the unknown region" "$output"
|
||||
pass "install rejects an unknown region"
|
||||
|
||||
user_region="$ROOT/install/user/region.sh"
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
|
||||
zone=$(<"$ROOT/default/regions/cn/timezone")
|
||||
[[ $zone == "Asia/Shanghai" ]] || fail "China profile names Asia/Shanghai"
|
||||
[[ -f /usr/share/zoneinfo/$zone ]] || fail "China profile timezone exists in tzdata"
|
||||
pass "China profile defaults to an existing Asia/Shanghai zone"
|
||||
|
||||
# Load the real region_timezone() from the first-boot command.
|
||||
OMARCHY_PATH="$ROOT"
|
||||
REGION_MARKER="$scratch/region"
|
||||
eval "$(sed -n '/^region_timezone() {/,/^}/p' "$ROOT/bin/omarchy-provision-owner")"
|
||||
|
||||
[[ -z $(region_timezone) ]] || fail "missing region marker offers no timezone"
|
||||
for region in global CN ../cn zz ""; do
|
||||
printf '%s\n' "$region" >"$REGION_MARKER"
|
||||
[[ -z $(region_timezone) ]] || fail "region '$region' offers no timezone"
|
||||
done
|
||||
pass "global, unknown, and malformed regions offer no timezone"
|
||||
|
||||
printf 'cn\n' >"$REGION_MARKER"
|
||||
[[ $(region_timezone) == "Asia/Shanghai" ]] || fail "cn region offers Asia/Shanghai"
|
||||
pass "cn region offers its profile timezone"
|
||||
|
||||
grep -qF 'omarchy_prompt_timezone "$(region_timezone)"' "$ROOT/bin/omarchy-provision-owner" ||
|
||||
fail "first-boot setup passes the region timezone to the prompt"
|
||||
pass "first-boot setup preselects the region timezone"
|
||||
@@ -61,7 +61,7 @@ fi
|
||||
|
||||
trap 'if [[ ${FUNCNAME[0]:-} == "$PROMPT_FN" ]]; then printf "returned\n" >>"$MARKER"; fi' RETURN
|
||||
|
||||
"$PROMPT_FN" ${PROMPT_ARG:+"$PROMPT_ARG"}
|
||||
"$PROMPT_FN"
|
||||
|
||||
printf 'region=%s\n' "${region:-}"
|
||||
printf 'keyboard=%s\n' "${keyboard:-}"
|
||||
@@ -274,19 +274,6 @@ assert_status 0 "timezone prompt survives a failed geo guess"
|
||||
[[ $(head -n 1 "$GUM_ARGS") == filter* ]] || fail "timezone prompt filters when there is no geo guess"
|
||||
pass "timezone prompt falls back to filtering when the geo guess fails"
|
||||
|
||||
# A region's default stands in for the missing guess, so an offline China
|
||||
# install opens on Asia/Shanghai instead of the top of the list
|
||||
PROMPT_ARG=Asia/Shanghai run_prompt omarchy_prompt_timezone "0:Asia/Shanghai"
|
||||
assert_status 0 "timezone prompt accepts the caller's fallback"
|
||||
[[ $(field timezone) == "Asia/Shanghai" ]] || fail "timezone prompt keeps the preselected fallback"
|
||||
grep -qF -- '--selected Asia/Shanghai' "$GUM_ARGS" || fail "timezone prompt preselects the caller's fallback"
|
||||
[[ $(head -n 1 "$GUM_ARGS") == choose* ]] || fail "timezone prompt chooses from the list when it has a fallback"
|
||||
pass "timezone prompt preselects the caller's fallback when the geo guess fails"
|
||||
|
||||
TZ_GUESS=Europe/Copenhagen PROMPT_ARG=Asia/Shanghai run_prompt omarchy_prompt_timezone "0:Europe/Copenhagen"
|
||||
grep -qF -- '--selected Europe/Copenhagen' "$GUM_ARGS" || fail "a geo guess outranks the caller's fallback"
|
||||
pass "timezone prompt prefers the geo guess over the caller's fallback"
|
||||
|
||||
run_prompt omarchy_prompt_timezone "0:"
|
||||
assert_status 0 "timezone prompt accepts an empty selection"
|
||||
[[ $(field timezone) == "UTC" ]] || fail "timezone prompt falls back to UTC"
|
||||
|
||||
Reference in new issue
Block a user