Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
595d38cb04 |
No files matched your search
@@ -28,20 +28,6 @@ Despite the local folder name, the first argument is the Omarchy source checkout
|
|||||||
|
|
||||||
Use `--dev` or `--rc` to build against those package channels. Both `--dev` and `--edge` select the dev packages from the edge mirror.
|
Use `--dev` or `--rc` to build against those package channels. Both `--dev` and `--edge` select the dev packages from the edge mirror.
|
||||||
|
|
||||||
### Regional builds
|
|
||||||
|
|
||||||
`--region cn` adds China bootstrap defaults independently of the channel. The default, `--region global`, preserves the existing package set, mirrors, cache paths, and artifact names. Supported country profiles use lowercase ISO 3166-1 alpha-2 codes; currently only `cn` is implemented.
|
|
||||||
|
|
||||||
For an unpublished regional runtime, use matching local source and package-recipe checkouts:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./bin/omarchy-iso-make --region cn --no-boot-offer --local-source /path/to/omarchy /path/to/omarchy-pkgs
|
|
||||||
```
|
|
||||||
|
|
||||||
China builds keep the channel mirror first, append the USTC Arch mirror, and add ArchLinuxCN with its verified keyring. These are a practical bridge until Omarchy runs its own mirror in China; USTC is rolling, not a matching stable/RC snapshot, so fallback version compatibility is not guaranteed. The region architecture also stages optional new-user defaults before user creation, including deferred setup, without touching existing homes. The China profile ships repository defaults and preselects `Asia/Shanghai` as the timezone; language and input-method defaults come as separate changes. See `docs/regions.md` in the companion runtime checkout.
|
|
||||||
|
|
||||||
Non-global cache and ISO names gain a `-cn` suffix. A runtime without the selected profile fails the build. These builds require an x86_64 Arch container environment; packaging checks on another architecture do not substitute for a fresh build, offline installation, and graphical acceptance run.
|
|
||||||
|
|
||||||
## Autoinstall
|
## Autoinstall
|
||||||
|
|
||||||
The shipped ISO installs itself with no keyboard when it finds its configuration on a second drive. Attach a drive labeled `cidata` alongside the ISO and the installer copies the config off it and skips the configurator; with no such drive, nothing changes and the wizard runs as usual. No rebuild, no extra boot entry.
|
The shipped ISO installs itself with no keyboard when it finds its configuration on a second drive. Attach a drive labeled `cidata` alongside the ISO and the installer copies the config off it and skips the configurator; with no such drive, nothing changes and the wizard runs as usual. No rebuild, no extra boot entry.
|
||||||
|
|||||||
+8
-36
@@ -6,17 +6,8 @@ NO_CACHE=""
|
|||||||
KEEP_PKG_CACHE=""
|
KEEP_PKG_CACHE=""
|
||||||
LOCAL_OMARCHY_PATH=""
|
LOCAL_OMARCHY_PATH=""
|
||||||
LOCAL_PKGS_PATH=""
|
LOCAL_PKGS_PATH=""
|
||||||
OMARCHY_REGION="${OMARCHY_REGION:-global}"
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--region)
|
|
||||||
OMARCHY_REGION="${2:-}"
|
|
||||||
if [[ $OMARCHY_REGION != "global" && $OMARCHY_REGION != "cn" ]]; then
|
|
||||||
echo "Error: --region requires global or cn (ISO 3166-1 alpha-2)." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--no-cache)
|
--no-cache)
|
||||||
NO_CACHE=1
|
NO_CACHE=1
|
||||||
shift
|
shift
|
||||||
@@ -66,17 +57,12 @@ while [[ $# -gt 0 ]]; do
|
|||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown option: $1"
|
echo "Unknown option: $1"
|
||||||
echo "Usage: $0 [--no-cache] [--keep-pkg-cache] [--no-boot-offer] [--debug] [--edge] [--dev|--rc] [--region global|cn] [--local-source <omarchy-checkout> <pkgs-checkout>]"
|
echo "Usage: $0 [--no-cache] [--keep-pkg-cache] [--no-boot-offer] [--debug] [--edge] [--dev|--rc] [--local-source <omarchy-checkout> <pkgs-checkout>]"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $OMARCHY_REGION != "global" && $OMARCHY_REGION != "cn" ]]; then
|
|
||||||
echo "Error: unsupported region '$OMARCHY_REGION'. Expected global or cn." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n $LOCAL_OMARCHY_PATH ]]; then
|
if [[ -n $LOCAL_OMARCHY_PATH ]]; then
|
||||||
[[ -d $LOCAL_OMARCHY_PATH ]] || { echo "Error: Omarchy checkout not found at $LOCAL_OMARCHY_PATH" >&2; exit 1; }
|
[[ -d $LOCAL_OMARCHY_PATH ]] || { echo "Error: Omarchy checkout not found at $LOCAL_OMARCHY_PATH" >&2; exit 1; }
|
||||||
[[ -d $LOCAL_PKGS_PATH ]] || { echo "Error: pkgs checkout not found at $LOCAL_PKGS_PATH" >&2; exit 1; }
|
[[ -d $LOCAL_PKGS_PATH ]] || { echo "Error: pkgs checkout not found at $LOCAL_PKGS_PATH" >&2; exit 1; }
|
||||||
@@ -130,10 +116,6 @@ BUILD_SCRIPT="builder/build-iso.sh"
|
|||||||
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
|
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
|
||||||
BUILD_RELEASE_PATH="$BUILD_ROOT/release"
|
BUILD_RELEASE_PATH="$BUILD_ROOT/release"
|
||||||
mkdir -p "$BUILD_RELEASE_PATH"
|
mkdir -p "$BUILD_RELEASE_PATH"
|
||||||
# Each build writes into its own directory, so concurrent builds (such as a
|
|
||||||
# global and a regional one) never pick up and rename each other's ISO.
|
|
||||||
BUILD_OUT=$(mktemp -d "$BUILD_RELEASE_PATH/.build-XXXXXX")
|
|
||||||
trap 'rm -rf "$BUILD_OUT"' EXIT
|
|
||||||
|
|
||||||
OMARCHY_ISO_REF="${OMARCHY_ISO_REF:-quattro}"
|
OMARCHY_ISO_REF="${OMARCHY_ISO_REF:-quattro}"
|
||||||
OMARCHY_MIRROR="${OMARCHY_MIRROR:-stable}"
|
OMARCHY_MIRROR="${OMARCHY_MIRROR:-stable}"
|
||||||
@@ -143,11 +125,10 @@ DOCKER_ARGS=(
|
|||||||
--privileged
|
--privileged
|
||||||
-e "OMARCHY_ISO_REF=$OMARCHY_ISO_REF"
|
-e "OMARCHY_ISO_REF=$OMARCHY_ISO_REF"
|
||||||
-e "OMARCHY_MIRROR=$OMARCHY_MIRROR"
|
-e "OMARCHY_MIRROR=$OMARCHY_MIRROR"
|
||||||
-e "OMARCHY_REGION=$OMARCHY_REGION"
|
|
||||||
-e "OMARCHY_INSTALL_DEBUG=${OMARCHY_INSTALL_DEBUG:-}"
|
-e "OMARCHY_INSTALL_DEBUG=${OMARCHY_INSTALL_DEBUG:-}"
|
||||||
-e "HOST_UID=$(id -u)"
|
-e "HOST_UID=$(id -u)"
|
||||||
-e "HOST_GID=$(id -g)"
|
-e "HOST_GID=$(id -g)"
|
||||||
-v "$BUILD_OUT/:/out/"
|
-v "$BUILD_RELEASE_PATH/:/out/"
|
||||||
-v "$BUILD_ROOT/$BUILD_SCRIPT:/$BUILD_SCRIPT:ro"
|
-v "$BUILD_ROOT/$BUILD_SCRIPT:/$BUILD_SCRIPT:ro"
|
||||||
-v "$BUILD_ROOT/archiso:/archiso:ro"
|
-v "$BUILD_ROOT/archiso:/archiso:ro"
|
||||||
-v "$BUILD_ROOT/builder:/builder:ro"
|
-v "$BUILD_ROOT/builder:/builder:ro"
|
||||||
@@ -165,14 +146,10 @@ if [[ -d /var/cache/pacman/pkg ]]; then
|
|||||||
DOCKER_ARGS+=(-v "/var/cache/pacman/pkg:/var/cache/pacman/pkg")
|
DOCKER_ARGS+=(-v "/var/cache/pacman/pkg:/var/cache/pacman/pkg")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Channels and regions must not share an offline mirror cache. Keep the
|
# Mount the build cache directory where packages are downloaded. Channels use
|
||||||
# existing global cache path and artifact suffix unchanged.
|
# different package snapshots and must never share an offline mirror cache.
|
||||||
region_suffix=""
|
|
||||||
if [[ $OMARCHY_REGION != "global" ]]; then
|
|
||||||
region_suffix="-$OMARCHY_REGION"
|
|
||||||
fi
|
|
||||||
if [[ -z "$NO_CACHE" ]]; then
|
if [[ -z "$NO_CACHE" ]]; then
|
||||||
OFFLINE_REPO_BUILD_CACHE_DIR="$HOME/.cache/omarchy/iso_${OMARCHY_MIRROR}${region_suffix}/airootfs/var/cache/omarchy"
|
OFFLINE_REPO_BUILD_CACHE_DIR="$HOME/.cache/omarchy/iso_${OMARCHY_MIRROR}/airootfs/var/cache/omarchy"
|
||||||
mkdir -p "$OFFLINE_REPO_BUILD_CACHE_DIR"
|
mkdir -p "$OFFLINE_REPO_BUILD_CACHE_DIR"
|
||||||
DOCKER_ARGS+=(-v "$OFFLINE_REPO_BUILD_CACHE_DIR:/var/cache/airootfs/var/cache/omarchy")
|
DOCKER_ARGS+=(-v "$OFFLINE_REPO_BUILD_CACHE_DIR:/var/cache/airootfs/var/cache/omarchy")
|
||||||
else
|
else
|
||||||
@@ -191,14 +168,9 @@ fi
|
|||||||
|
|
||||||
"${DOCKER[@]}" run "${DOCKER_ARGS[@]}" archlinux/archlinux:latest /$BUILD_SCRIPT
|
"${DOCKER[@]}" run "${DOCKER_ARGS[@]}" archlinux/archlinux:latest /$BUILD_SCRIPT
|
||||||
|
|
||||||
built_isos=("$BUILD_OUT"/*.iso)
|
latest_iso=$(\ls -t "$BUILD_RELEASE_PATH"/*.iso | head -n1)
|
||||||
if (( ${#built_isos[@]} != 1 )) || [[ ! -f ${built_isos[0]} ]]; then
|
iso_ref="${latest_iso%.*}-$OMARCHY_ISO_REF.iso"
|
||||||
echo "Error: expected exactly one ISO in $BUILD_OUT" >&2
|
mv -f "$latest_iso" "$iso_ref"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
built_iso_name=$(basename "${built_isos[0]}" .iso)
|
|
||||||
iso_ref="$BUILD_RELEASE_PATH/$built_iso_name-${OMARCHY_ISO_REF}${region_suffix}.iso"
|
|
||||||
mv -f "${built_isos[0]}" "$iso_ref"
|
|
||||||
|
|
||||||
if [[ -z "${NO_BOOT_OFFER:-}" ]]; then
|
if [[ -z "${NO_BOOT_OFFER:-}" ]]; then
|
||||||
echo
|
echo
|
||||||
|
|||||||
+69
-5
@@ -13,6 +13,9 @@
|
|||||||
#
|
#
|
||||||
# Usage: omarchy-iso-test [release/omarchy.iso] [options]
|
# Usage: omarchy-iso-test [release/omarchy.iso] [options]
|
||||||
#
|
#
|
||||||
|
# --keyboard LABEL English (US/UK), Japanese, Japanese (US keyboard), Korean,
|
||||||
|
# or Chinese (Simplified, Pinyin)/(Traditional, Zhuyin).
|
||||||
|
# OMARCHY_PATH must be the source tree used to build the ISO.
|
||||||
# --encrypt Drive the encrypted install flow (default: unencrypted)
|
# --encrypt Drive the encrypted install flow (default: unencrypted)
|
||||||
# --provision Drive the deferred-provisioning install flow: no user during install, the
|
# --provision Drive the deferred-provisioning install flow: no user during install, the
|
||||||
# first boot runs omarchy-provision-owner and creates it there
|
# first boot runs omarchy-provision-owner and creates it there
|
||||||
@@ -47,12 +50,14 @@ NO_PREVIEW=false
|
|||||||
SYNC_DIR=""
|
SYNC_DIR=""
|
||||||
SYNC_ALL=false
|
SYNC_ALL=false
|
||||||
|
|
||||||
|
KEYBOARD_LABEL="English (US)"
|
||||||
GUEST_USER="omarchy"
|
GUEST_USER="omarchy"
|
||||||
GUEST_PASSWORD="omarchy"
|
GUEST_PASSWORD="omarchy"
|
||||||
GUEST_HOSTNAME="omarchy-test"
|
GUEST_HOSTNAME="omarchy-test"
|
||||||
|
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--keyboard) KEYBOARD_LABEL="$2"; shift ;;
|
||||||
--encrypt) ENCRYPT=true ;;
|
--encrypt) ENCRYPT=true ;;
|
||||||
--provision) PROVISION=true ;;
|
--provision) PROVISION=true ;;
|
||||||
--reuse-base) REUSE_BASE=true ;;
|
--reuse-base) REUSE_BASE=true ;;
|
||||||
@@ -84,12 +89,20 @@ if [[ ! -f ${ISO:-} ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
case "$KEYBOARD_LABEL" in
|
||||||
|
"English (US)" | "English (UK)" | "Japanese" | "Japanese (US keyboard)" | "Korean" | "Chinese (Simplified, Pinyin)" | "Chinese (Traditional, Zhuyin)") ;;
|
||||||
|
*) echo "The VM harness cannot type credentials with keyboard: $KEYBOARD_LABEL" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
omarchy-pkg-add qemu-full edk2-ovmf socat imagemagick tesseract tesseract-data-eng
|
omarchy-pkg-add qemu-full edk2-ovmf socat imagemagick tesseract tesseract-data-eng
|
||||||
|
|
||||||
OVMF_CODE="/usr/share/edk2/x64/OVMF_CODE.4m.fd"
|
OVMF_CODE="/usr/share/edk2/x64/OVMF_CODE.4m.fd"
|
||||||
OVMF_VARS_TEMPLATE="/usr/share/edk2/x64/OVMF_VARS.4m.fd"
|
OVMF_VARS_TEMPLATE="/usr/share/edk2/x64/OVMF_VARS.4m.fd"
|
||||||
|
|
||||||
BASE_NAME="$(basename "$ISO" .iso)"
|
BASE_NAME="$(basename "$ISO" .iso)"
|
||||||
|
if [[ $KEYBOARD_LABEL != "English (US)" ]]; then
|
||||||
|
BASE_NAME+="-$(printf '%s' "$KEYBOARD_LABEL" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g')"
|
||||||
|
fi
|
||||||
$ENCRYPT && BASE_NAME+="-encrypted"
|
$ENCRYPT && BASE_NAME+="-encrypted"
|
||||||
$PROVISION && BASE_NAME+="-provision"
|
$PROVISION && BASE_NAME+="-provision"
|
||||||
BASE_DIR="$REPO_DIR/test-runs/$BASE_NAME"
|
BASE_DIR="$REPO_DIR/test-runs/$BASE_NAME"
|
||||||
@@ -610,6 +623,35 @@ audio-panel|meta_l-ctrl-a|omarchy-keyboard-panel|omarchy.audio'
|
|||||||
|
|
||||||
# ----------------------------------------------------- driving the installer
|
# ----------------------------------------------------- driving the installer
|
||||||
|
|
||||||
|
select_keyboard() {
|
||||||
|
local label="$1" index direction="down"
|
||||||
|
# gum choose uses arrows, not a searchable text field. Read the same ordered
|
||||||
|
# choices the ISO vendors from the runtime rather than hard-code row numbers.
|
||||||
|
index=$(OMARCHY_TEST_KEYBOARD="$label" python - <<'PYTHON'
|
||||||
|
import os, re
|
||||||
|
from pathlib import Path
|
||||||
|
source = Path(os.environ.get("OMARCHY_PATH", "")) / "install/provisioning/setup-form.sh"
|
||||||
|
if not source.is_file():
|
||||||
|
raise SystemExit("Set OMARCHY_PATH to the source tree used to build this ISO")
|
||||||
|
text = source.read_text()
|
||||||
|
rows = re.search(r"OMARCHY_KEYBOARD_LAYOUTS=\$'(.*?)'\n", text, re.S).group(1).splitlines()
|
||||||
|
labels = [row.split("|")[0] for row in rows]
|
||||||
|
label = os.environ["OMARCHY_TEST_KEYBOARD"]
|
||||||
|
if label not in labels:
|
||||||
|
raise SystemExit(f"Keyboard choice is absent from the ISO source: {label}")
|
||||||
|
print(labels.index(label) - labels.index("English (US)"))
|
||||||
|
PYTHON
|
||||||
|
) || return 1
|
||||||
|
if (( index < 0 )); then
|
||||||
|
direction="up"
|
||||||
|
index=$((-index))
|
||||||
|
fi
|
||||||
|
for (( i = 0; i < index; i++ )); do
|
||||||
|
press "$direction"
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
drive_configurator() {
|
drive_configurator() {
|
||||||
log "Driving the installer wizard"
|
log "Driving the installer wizard"
|
||||||
|
|
||||||
@@ -651,7 +693,11 @@ drive_configurator() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
press ret # English (US) is preselected
|
if [[ $KEYBOARD_LABEL != "English (US)" ]]; then
|
||||||
|
select_keyboard "$KEYBOARD_LABEL"
|
||||||
|
capture_console "success-installer-01-selected-keyboard"
|
||||||
|
fi
|
||||||
|
press ret
|
||||||
|
|
||||||
# Normal flow: user step comes before disk selection.
|
# Normal flow: user step comes before disk selection.
|
||||||
wait_for_screen "Username" 120
|
wait_for_screen "Username" 120
|
||||||
@@ -675,7 +721,15 @@ drive_configurator() {
|
|||||||
press ret
|
press ret
|
||||||
|
|
||||||
wait_for_screen "Email address" 60
|
wait_for_screen "Email address" 60
|
||||||
type_text "test@omarchy.org"
|
type_text "test"
|
||||||
|
if [[ $KEYBOARD_LABEL == "Japanese" ]]; then
|
||||||
|
press bracket_left
|
||||||
|
elif [[ $KEYBOARD_LABEL == "English (UK)" ]]; then
|
||||||
|
press shift-apostrophe
|
||||||
|
else
|
||||||
|
press shift-2
|
||||||
|
fi
|
||||||
|
type_text "omarchy.org"
|
||||||
capture_console "success-installer-06-email"
|
capture_console "success-installer-06-email"
|
||||||
press ret
|
press ret
|
||||||
|
|
||||||
@@ -736,8 +790,12 @@ drive_provision_owner() {
|
|||||||
press ret
|
press ret
|
||||||
|
|
||||||
wait_for_screen "keyboard layout" 120
|
wait_for_screen "keyboard layout" 120
|
||||||
press down
|
if [[ $KEYBOARD_LABEL == "English (US)" ]]; then
|
||||||
capture_console "success-provision-01-keyboard-layout-uk"
|
press down # preserve the harness's default UK provisioning coverage
|
||||||
|
else
|
||||||
|
select_keyboard "$KEYBOARD_LABEL"
|
||||||
|
fi
|
||||||
|
capture_console "success-provision-01-keyboard-layout"
|
||||||
press ret
|
press ret
|
||||||
|
|
||||||
wait_for_screen "Username" 120
|
wait_for_screen "Username" 120
|
||||||
@@ -763,7 +821,13 @@ drive_provision_owner() {
|
|||||||
# Shift+2 as on US); send that keycode so the typed email is correct under
|
# Shift+2 as on US); send that keycode so the typed email is correct under
|
||||||
# the new layout — itself further proof the deferred keymap took effect.
|
# the new layout — itself further proof the deferred keymap took effect.
|
||||||
type_text "test"
|
type_text "test"
|
||||||
press shift-apostrophe
|
if [[ $KEYBOARD_LABEL == "English (US)" || $KEYBOARD_LABEL == "English (UK)" ]]; then
|
||||||
|
press shift-apostrophe
|
||||||
|
elif [[ $KEYBOARD_LABEL == "Japanese" ]]; then
|
||||||
|
press bracket_left
|
||||||
|
else
|
||||||
|
press shift-2
|
||||||
|
fi
|
||||||
type_text "omarchy.org"
|
type_text "omarchy.org"
|
||||||
capture_console "success-provision-04-email"
|
capture_console "success-provision-04-email"
|
||||||
press ret
|
press ret
|
||||||
|
|||||||
+2
-27
@@ -4,11 +4,6 @@ set -e
|
|||||||
|
|
||||||
OMARCHY_ISO_REF="${OMARCHY_ISO_REF:-quattro}"
|
OMARCHY_ISO_REF="${OMARCHY_ISO_REF:-quattro}"
|
||||||
OMARCHY_MIRROR="${OMARCHY_MIRROR:-stable}"
|
OMARCHY_MIRROR="${OMARCHY_MIRROR:-stable}"
|
||||||
OMARCHY_REGION="${OMARCHY_REGION:-global}"
|
|
||||||
case "$OMARCHY_REGION" in
|
|
||||||
global|cn) ;;
|
|
||||||
*) echo "Unsupported region: $OMARCHY_REGION" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Edge, dev, and local-source ISOs install the dev packages explicitly. Those
|
# Edge, dev, and local-source ISOs install the dev packages explicitly. Those
|
||||||
# package recipes track the quattro branch. This avoids relying on pacman's
|
# package recipes track the quattro branch. This avoids relying on pacman's
|
||||||
@@ -72,7 +67,6 @@ cp -r /configs/* "$build_cache_dir/"
|
|||||||
mkdir -p "$build_cache_dir/airootfs/usr/share/omarchy-iso"
|
mkdir -p "$build_cache_dir/airootfs/usr/share/omarchy-iso"
|
||||||
echo "$OMARCHY_MIRROR" > "$build_cache_dir/airootfs/root/omarchy_mirror"
|
echo "$OMARCHY_MIRROR" > "$build_cache_dir/airootfs/root/omarchy_mirror"
|
||||||
echo "$OMARCHY_ISO_REF" > "$build_cache_dir/airootfs/root/omarchy_iso_ref"
|
echo "$OMARCHY_ISO_REF" > "$build_cache_dir/airootfs/root/omarchy_iso_ref"
|
||||||
echo "$OMARCHY_REGION" > "$build_cache_dir/airootfs/root/omarchy_region"
|
|
||||||
cat > "$build_cache_dir/airootfs/usr/share/omarchy-iso/package-targets" <<EOF
|
cat > "$build_cache_dir/airootfs/usr/share/omarchy-iso/package-targets" <<EOF
|
||||||
OMARCHY_RUNTIME_PACKAGE=$OMARCHY_RUNTIME_PACKAGE
|
OMARCHY_RUNTIME_PACKAGE=$OMARCHY_RUNTIME_PACKAGE
|
||||||
OMARCHY_SETTINGS_PACKAGE=$OMARCHY_SETTINGS_PACKAGE
|
OMARCHY_SETTINGS_PACKAGE=$OMARCHY_SETTINGS_PACKAGE
|
||||||
@@ -86,7 +80,6 @@ if [[ ${OMARCHY_INSTALL_DEBUG:-} == "1" ]]; then
|
|||||||
echo "built_at=$(date -Is)"
|
echo "built_at=$(date -Is)"
|
||||||
echo "ref=$OMARCHY_ISO_REF"
|
echo "ref=$OMARCHY_ISO_REF"
|
||||||
echo "mirror=$OMARCHY_MIRROR"
|
echo "mirror=$OMARCHY_MIRROR"
|
||||||
echo "region=$OMARCHY_REGION"
|
|
||||||
echo "runtime_package=$OMARCHY_RUNTIME_PACKAGE"
|
echo "runtime_package=$OMARCHY_RUNTIME_PACKAGE"
|
||||||
echo "settings_package=$OMARCHY_SETTINGS_PACKAGE"
|
echo "settings_package=$OMARCHY_SETTINGS_PACKAGE"
|
||||||
echo "nvim_package=$OMARCHY_NVIM_PACKAGE"
|
echo "nvim_package=$OMARCHY_NVIM_PACKAGE"
|
||||||
@@ -146,7 +139,6 @@ sed -i -E '/^(linux|broadcom-wl)$/d' "$build_cache_dir/packages.x86_64"
|
|||||||
# already in the mirror and we filter them out below. Without it, pacman -Syw
|
# already in the mirror and we filter them out below. Without it, pacman -Syw
|
||||||
# pulls the published omarchy* from the network mirror like any other package.
|
# pulls the published omarchy* from the network mirror like any other package.
|
||||||
if [[ -d /omarchy-source ]]; then
|
if [[ -d /omarchy-source ]]; then
|
||||||
runtime_root=/omarchy-source
|
|
||||||
base_pkg_lists=(/omarchy-source/install/omarchy-base.packages /omarchy-source/install/omarchy-other.packages)
|
base_pkg_lists=(/omarchy-source/install/omarchy-base.packages /omarchy-source/install/omarchy-other.packages)
|
||||||
setup_form=/omarchy-source/install/provisioning/setup-form.sh
|
setup_form=/omarchy-source/install/provisioning/setup-form.sh
|
||||||
else
|
else
|
||||||
@@ -163,14 +155,6 @@ else
|
|||||||
fi
|
fi
|
||||||
mkdir -p /tmp/omarchy-pkglists
|
mkdir -p /tmp/omarchy-pkglists
|
||||||
bsdtar -xf "$omarchy_pkg" -C /tmp/omarchy-pkglists usr/share/omarchy/install/omarchy-base.packages usr/share/omarchy/install/omarchy-other.packages
|
bsdtar -xf "$omarchy_pkg" -C /tmp/omarchy-pkglists usr/share/omarchy/install/omarchy-base.packages usr/share/omarchy/install/omarchy-other.packages
|
||||||
runtime_root=/tmp/omarchy-pkglists/usr/share/omarchy
|
|
||||||
if [[ $OMARCHY_REGION != "global" ]]; then
|
|
||||||
if ! bsdtar -xf "$omarchy_pkg" -C /tmp/omarchy-pkglists \
|
|
||||||
"usr/share/omarchy/default/regions/$OMARCHY_REGION" usr/share/omarchy/bin/omarchy-apply-pacman; then
|
|
||||||
echo "ERROR: runtime lacks region support; publish a matching runtime or use --local-source." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
base_pkg_lists=(/tmp/omarchy-pkglists/usr/share/omarchy/install/omarchy-base.packages /tmp/omarchy-pkglists/usr/share/omarchy/install/omarchy-other.packages)
|
base_pkg_lists=(/tmp/omarchy-pkglists/usr/share/omarchy/install/omarchy-base.packages /tmp/omarchy-pkglists/usr/share/omarchy/install/omarchy-other.packages)
|
||||||
# Extracted on its own, tolerating a miss: bsdtar exits non-zero for a member
|
# Extracted on its own, tolerating a miss: bsdtar exits non-zero for a member
|
||||||
# it can't find, so asking for this alongside the package lists would abort the
|
# it can't find, so asking for this alongside the package lists would abort the
|
||||||
@@ -184,15 +168,6 @@ mkdir -p "$build_cache_dir/airootfs/usr/share/omarchy-iso"
|
|||||||
cp "${base_pkg_lists[0]}" "$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-base.packages"
|
cp "${base_pkg_lists[0]}" "$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-base.packages"
|
||||||
cp "${base_pkg_lists[1]}" "$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-other.packages"
|
cp "${base_pkg_lists[1]}" "$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-other.packages"
|
||||||
|
|
||||||
# Work on writable copies, leaving the mounted runtime and ISO configs intact.
|
|
||||||
online_config=/tmp/omarchy-pacman-online.conf
|
|
||||||
cp "/configs/pacman-online-${OMARCHY_MIRROR}.conf" "$online_config"
|
|
||||||
bash /builder/prepare-region.sh "$OMARCHY_REGION" "$runtime_root" "$build_cache_dir/airootfs" "$online_config"
|
|
||||||
base_pkg_lists=(
|
|
||||||
"$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-base.packages"
|
|
||||||
"$build_cache_dir/airootfs/usr/share/omarchy-iso/omarchy-other.packages"
|
|
||||||
)
|
|
||||||
|
|
||||||
# The configurator's setup form comes from the runtime this ISO bundles, so the
|
# The configurator's setup form comes from the runtime this ISO bundles, so the
|
||||||
# installer and the first-boot setup that finishes a deferred install can never
|
# installer and the first-boot setup that finishes a deferred install can never
|
||||||
# disagree. A runtime predating the split ships no such file, which would leave
|
# disagree. A runtime predating the split ships no such file, which would leave
|
||||||
@@ -257,7 +232,7 @@ fi
|
|||||||
|
|
||||||
mkdir -p /tmp/offlinedb
|
mkdir -p /tmp/offlinedb
|
||||||
download_offline_packages() {
|
download_offline_packages() {
|
||||||
pacman --config "$online_config" --noconfirm -Syw \
|
pacman --config /configs/pacman-online-${OMARCHY_MIRROR}.conf --noconfirm -Syw \
|
||||||
"${all_packages[@]}" --cachedir "$offline_mirror_dir/" --dbpath /tmp/offlinedb --needed
|
"${all_packages[@]}" --cachedir "$offline_mirror_dir/" --dbpath /tmp/offlinedb --needed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +250,7 @@ fi
|
|||||||
# newest version of every cached package name) removes packages that have left
|
# newest version of every cached package name) removes packages that have left
|
||||||
# the lists or dependency closure, such as an old Electron major version.
|
# the lists or dependency closure, such as an old Electron major version.
|
||||||
if ! resolved_package_files="$(
|
if ! resolved_package_files="$(
|
||||||
pacman --config "$online_config" --noconfirm \
|
pacman --config "/configs/pacman-online-${OMARCHY_MIRROR}.conf" --noconfirm \
|
||||||
--dbpath /tmp/offlinedb -S --print --print-format '%f' "${all_packages[@]}"
|
--dbpath /tmp/offlinedb -S --print --print-format '%f' "${all_packages[@]}"
|
||||||
)"; then
|
)"; then
|
||||||
echo "ERROR: could not resolve the package files required by the offline mirror" >&2
|
echo "ERROR: could not resolve the package files required by the offline mirror" >&2
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Add the selected runtime's regional payload to the ISO. Repository defaults
|
|
||||||
# stay in the runtime package, not in a second set of ISO-specific templates.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
region="$1"
|
|
||||||
runtime="$2"
|
|
||||||
iso_root="$3"
|
|
||||||
online_config="$4"
|
|
||||||
|
|
||||||
case "$region" in
|
|
||||||
global) exit 0 ;;
|
|
||||||
cn) ;;
|
|
||||||
*) echo "Unsupported region: $region" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
profile="$runtime/default/regions/$region"
|
|
||||||
if [[ ! -f $runtime/bin/omarchy-apply-pacman || ! -f $profile/packages ||
|
|
||||||
! -f $profile/pacman/pacman.conf.append || ! -f $profile/pacman/mirrorlist.append ]]; then
|
|
||||||
echo "Runtime does not support region '$region'; publish a matching runtime or use --local-source." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This is the target package list used both for download resolution and by the
|
|
||||||
# installer. Merely putting extra packages in the cache would not install them.
|
|
||||||
payload="$iso_root/usr/share/omarchy-iso"
|
|
||||||
cp -a "$profile" "$payload/region"
|
|
||||||
printf '\n' >> "$payload/omarchy-base.packages"
|
|
||||||
cat "$profile/packages" >> "$payload/omarchy-base.packages"
|
|
||||||
|
|
||||||
# Only the community repository is needed for regional package downloads.
|
|
||||||
# Keep the channel's build mirrors intact; target mirrors are restored later.
|
|
||||||
printf '\n' >> "$online_config"
|
|
||||||
cat "$profile/pacman/pacman.conf.append" >> "$online_config"
|
|
||||||
|
|
||||||
# The keyring package must be signed by a key already trusted by Arch. Do not
|
|
||||||
# bootstrap trust from an unchecked keyserver response or disable signatures.
|
|
||||||
# https://github.com/archlinuxcn/repo#usage
|
|
||||||
pacman --config "$online_config" --noconfirm -Sy --needed archlinuxcn-keyring
|
|
||||||
pacman-key --populate archlinuxcn
|
|
||||||
@@ -24,16 +24,6 @@ if [[ ! -r $SETUP_FORM ]]; then
|
|||||||
fi
|
fi
|
||||||
source "$SETUP_FORM"
|
source "$SETUP_FORM"
|
||||||
|
|
||||||
# A regional ISO carries its profile here (builder/prepare-region.sh copies it);
|
|
||||||
# the form preselects the profile's timezone when there is no geo guess, which
|
|
||||||
# is every offline install.
|
|
||||||
REGION_TIMEZONE="${REGION_TIMEZONE:-/usr/share/omarchy-iso/region/timezone}"
|
|
||||||
region_timezone() {
|
|
||||||
if [[ -r $REGION_TIMEZONE ]]; then
|
|
||||||
head -n 1 "$REGION_TIMEZONE"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Partition creation helpers, shared with test/partition-numbering-test.sh so
|
# Partition creation helpers, shared with test/partition-numbering-test.sh so
|
||||||
# the numbering logic can be exercised without a real disk.
|
# the numbering logic can be exercised without a real disk.
|
||||||
DISK_PARTITIONING="${DISK_PARTITIONING:-/usr/share/omarchy-iso/disk-partitioning.sh}"
|
DISK_PARTITIONING="${DISK_PARTITIONING:-/usr/share/omarchy-iso/disk-partitioning.sh}"
|
||||||
@@ -280,7 +270,7 @@ user_form() {
|
|||||||
|
|
||||||
omarchy_prompt_identity || return $?
|
omarchy_prompt_identity || return $?
|
||||||
omarchy_prompt_hostname || return $?
|
omarchy_prompt_hostname || return $?
|
||||||
omarchy_prompt_timezone "$(region_timezone)" || return $?
|
omarchy_prompt_timezone || return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
# Returns 0 on confirmed account details, or as soon as the keyboard screen
|
# Returns 0 on confirmed account details, or as soon as the keyboard screen
|
||||||
@@ -810,6 +800,8 @@ run_partition_execute() {
|
|||||||
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
|
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
|
||||||
"custom_commands": [],
|
"custom_commands": [],
|
||||||
"omarchy_install": {
|
"omarchy_install": {
|
||||||
|
"input_method": "${keyboard_input_method:-none}",
|
||||||
|
"input_xkb_layout": "${keyboard_xkb_layout:-}",
|
||||||
"mode": "protected",
|
"mode": "protected",
|
||||||
"target_mount": "/mnt",
|
"target_mount": "/mnt",
|
||||||
"boot": {
|
"boot": {
|
||||||
@@ -1066,6 +1058,8 @@ if $defer_provisioning; then
|
|||||||
# their real layout in the OOBE, which re-keys the console and, on encrypted
|
# their real layout in the OOBE, which re-keys the console and, on encrypted
|
||||||
# installs, rebuilds the initramfs so the LUKS prompt matches.
|
# installs, rebuilds the initramfs so the LUKS prompt matches.
|
||||||
keyboard="us"
|
keyboard="us"
|
||||||
|
keyboard_input_method="none"
|
||||||
|
keyboard_xkb_layout=""
|
||||||
username=""
|
username=""
|
||||||
password=""
|
password=""
|
||||||
password_hash=""
|
password_hash=""
|
||||||
@@ -1148,6 +1142,8 @@ cat <<-_EOF_ >user_configuration.json
|
|||||||
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
|
"bootloader_config": { "bootloader": "Limine", "uki": false, "removable": false },
|
||||||
"custom_commands": [],
|
"custom_commands": [],
|
||||||
"omarchy_install": {
|
"omarchy_install": {
|
||||||
|
"input_method": "${keyboard_input_method:-none}",
|
||||||
|
"input_xkb_layout": "${keyboard_xkb_layout:-}",
|
||||||
"mode": "full_disk",
|
"mode": "full_disk",
|
||||||
"defer_provisioning": $defer_provisioning,
|
"defer_provisioning": $defer_provisioning,
|
||||||
"target_mount": "/mnt",
|
"target_mount": "/mnt",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ The canonical call sequence (mirrored from archinstall.scripts.guided.py) is:
|
|||||||
# configs do their own mounting before the Installer context opens.
|
# configs do their own mounting before the Installer context opens.
|
||||||
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
if disk_config.config_type != DiskLayoutType.Pre_mount:
|
||||||
inst.mount_ordered_layout()
|
inst.mount_ordered_layout()
|
||||||
inst.sanity_check(offline=, skip_ntp=, skip_wkd=)
|
sanity_check(inst)
|
||||||
inst.generate_key_files() # encrypted only
|
inst.generate_key_files() # encrypted only
|
||||||
inst.set_mirrors(handler, mirror_config, on_target=False)
|
inst.set_mirrors(handler, mirror_config, on_target=False)
|
||||||
inst.minimal_installation(...) # base + linux pacstrap
|
inst.minimal_installation(...) # base + linux pacstrap
|
||||||
@@ -214,6 +214,14 @@ def _method_accepts(method, name: str) -> bool:
|
|||||||
return name in (*positional, *kwonly)
|
return name in (*positional, *kwonly)
|
||||||
|
|
||||||
|
|
||||||
|
def sanity_check(installer) -> None:
|
||||||
|
"""Archinstall 4.5 removed offline; retain it for older releases."""
|
||||||
|
options = {"skip_ntp": True, "skip_wkd": True}
|
||||||
|
if _method_accepts(installer.sanity_check, "offline"):
|
||||||
|
options["offline"] = True
|
||||||
|
installer.sanity_check(**options)
|
||||||
|
|
||||||
|
|
||||||
def _method_accepts_users(method) -> bool:
|
def _method_accepts_users(method) -> bool:
|
||||||
return _method_accepts(method, "users")
|
return _method_accepts(method, "users")
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ from dataclasses import dataclass, field
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from .keyboard import validate_input_selection
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InstallContext:
|
class InstallContext:
|
||||||
@@ -57,6 +59,8 @@ class InstallContext:
|
|||||||
kernel = (omarchy_install.get("storage") or {}).get("kernel") or _default_kernel()
|
kernel = (omarchy_install.get("storage") or {}).get("kernel") or _default_kernel()
|
||||||
user_configuration["kernels"] = [kernel]
|
user_configuration["kernels"] = [kernel]
|
||||||
|
|
||||||
|
validate_input_selection(omarchy_install.get("input_method"), omarchy_install.get("input_xkb_layout", ""))
|
||||||
|
|
||||||
# Deferred provisioning: the whole system installs but user creation is deferred to
|
# Deferred provisioning: the whole system installs but user creation is deferred to
|
||||||
# first boot. Selected by the configurator (omarchy_install.defer_provisioning) or by
|
# first boot. Selected by the configurator (omarchy_install.defer_provisioning) or by
|
||||||
# an `defer-provisioning` marker file on an autoinstall drive, which also replaces the
|
# an `defer-provisioning` marker file on an autoinstall drive, which also replaces the
|
||||||
|
|||||||
@@ -16,21 +16,31 @@ from pathlib import Path
|
|||||||
from .command import capture
|
from .command import capture
|
||||||
|
|
||||||
|
|
||||||
def configure_keyboard(target: Path, language: str) -> bool:
|
def configure_keyboard(target: Path, language: str, input_method: str | None = None, xkb_layout: str = "") -> bool:
|
||||||
"""Write the console keymap into a mounted target without booting it.
|
"""Write the console keymap into a mounted target without booting it.
|
||||||
|
|
||||||
Returns False for layouts localectl doesn't know, matching archinstall:
|
Returns False for layouts localectl doesn't know, matching archinstall:
|
||||||
warn and keep the default. Every layout the configurator offers is known;
|
warn and keep the default. Every layout the configurator offers is known;
|
||||||
the guard is for the kb_layout an autoinstall drive can name freely.
|
the guard is for the kb_layout an autoinstall drive can name freely.
|
||||||
"""
|
"""
|
||||||
|
validate_input_selection(input_method, xkb_layout)
|
||||||
if not language.strip():
|
if not language.strip():
|
||||||
return True
|
if input_method is None:
|
||||||
|
return True
|
||||||
|
language = "us"
|
||||||
|
|
||||||
|
# Input engines are separate from real console keymaps. The shared picker
|
||||||
|
# uses US for Chinese/Korean and carries its preference in omarchy_install.
|
||||||
|
if input_method is None:
|
||||||
|
input_method = "mozc" if language == "jp106" else "none"
|
||||||
|
|
||||||
result = capture(["localectl", "--no-pager", "list-keymaps"])
|
result = capture(["localectl", "--no-pager", "list-keymaps"])
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
detail = result.stderr.strip() or "localectl returned an error"
|
detail = result.stderr.strip() or "localectl returned an error"
|
||||||
raise RuntimeError(f"Unable to list keyboard layouts: {detail}")
|
raise RuntimeError(f"Unable to list keyboard layouts: {detail}")
|
||||||
if language.lower() not in {layout.lower() for layout in result.stdout.splitlines()}:
|
if language.lower() not in {layout.lower() for layout in result.stdout.splitlines()}:
|
||||||
|
if input_method != "none":
|
||||||
|
raise ValueError(f"Unknown keyboard language: {language}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# systemd-firstboot --force rewrites vconsole.conf wholesale, dropping the
|
# systemd-firstboot --force rewrites vconsole.conf wholesale, dropping the
|
||||||
@@ -51,4 +61,15 @@ def configure_keyboard(target: Path, language: str) -> bool:
|
|||||||
if font:
|
if font:
|
||||||
vconsole_path.write_text(vconsole_path.read_text() + font + "\n")
|
vconsole_path.write_text(vconsole_path.read_text() + font + "\n")
|
||||||
|
|
||||||
|
preference = target / "etc/omarchy/input-method"
|
||||||
|
preference.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
preference.write_text(f"INPUT_METHOD={input_method}\nXKB_LAYOUT={xkb_layout}\n")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_input_selection(input_method: str | None, xkb_layout: str) -> None:
|
||||||
|
if input_method is not None and input_method not in {"none", "mozc", "hangul", "pinyin", "chewing"}:
|
||||||
|
raise ValueError(f"Unknown input method: {input_method}")
|
||||||
|
if xkb_layout not in {"", "kr"}:
|
||||||
|
raise ValueError(f"Unknown input keyboard layout: {xkb_layout}")
|
||||||
@@ -39,36 +39,6 @@ from .keyboard import configure_keyboard
|
|||||||
from .ui import error, info
|
from .ui import error, info
|
||||||
|
|
||||||
|
|
||||||
ISO_REGION_FILE = Path("/root/omarchy_region")
|
|
||||||
ISO_REGION_PAYLOAD = Path("/usr/share/omarchy-iso/region")
|
|
||||||
|
|
||||||
|
|
||||||
def _iso_region() -> str:
|
|
||||||
region = ISO_REGION_FILE.read_text().strip() if ISO_REGION_FILE.exists() else "global"
|
|
||||||
if region not in {"global", "cn"}:
|
|
||||||
raise RuntimeError(f"Unsupported ISO region: {region}")
|
|
||||||
return region
|
|
||||||
|
|
||||||
|
|
||||||
def _stage_region_defaults(target: Path) -> None:
|
|
||||||
"""Seed the target before useradd, including deferred first-boot users.
|
|
||||||
|
|
||||||
Region is a bootstrap default, not a locale or a user-level policy.
|
|
||||||
Existing home directories are never rewritten by this step.
|
|
||||||
"""
|
|
||||||
region = _iso_region()
|
|
||||||
if region != "global":
|
|
||||||
if not ISO_REGION_PAYLOAD.is_dir():
|
|
||||||
raise RuntimeError(f"Missing ISO payload for region: {region}")
|
|
||||||
skel = ISO_REGION_PAYLOAD / "skel"
|
|
||||||
if skel.is_dir():
|
|
||||||
shutil.copytree(skel, target / "etc/skel", dirs_exist_ok=True)
|
|
||||||
marker = target / "etc/omarchy/region"
|
|
||||||
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
marker.write_text(region + "\n")
|
|
||||||
marker.chmod(0o644)
|
|
||||||
|
|
||||||
|
|
||||||
# Package targets are written by builder/build-iso.sh. Stable ISOs use the
|
# Package targets are written by builder/build-iso.sh. Stable ISOs use the
|
||||||
# stable package names, while dev/local-source ISOs install the dev package
|
# stable package names, while dev/local-source ISOs install the dev package
|
||||||
# names explicitly instead of relying on provides=omarchy resolution.
|
# names explicitly instead of relying on provides=omarchy resolution.
|
||||||
@@ -265,11 +235,7 @@ def arch_install_system(ctx: InstallContext) -> None:
|
|||||||
if not pre_mounted:
|
if not pre_mounted:
|
||||||
installer.mount_ordered_layout()
|
installer.mount_ordered_layout()
|
||||||
|
|
||||||
installer.sanity_check(
|
arch.sanity_check(installer)
|
||||||
offline=True,
|
|
||||||
skip_ntp=True,
|
|
||||||
skip_wkd=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not pre_mounted and arch.is_encrypted(config):
|
if not pre_mounted and arch.is_encrypted(config):
|
||||||
installer.generate_key_files()
|
installer.generate_key_files()
|
||||||
@@ -304,7 +270,11 @@ def arch_install_system(ctx: InstallContext) -> None:
|
|||||||
# use the target kernel instead of the live ISO's kernel.
|
# use the target kernel instead of the live ISO's kernel.
|
||||||
installer.add_additional_packages([f"{kernel}-headers" for kernel in config.kernels])
|
installer.add_additional_packages([f"{kernel}-headers" for kernel in config.kernels])
|
||||||
|
|
||||||
if not configure_keyboard(installer.target, kb_layout):
|
if not configure_keyboard(
|
||||||
|
installer.target, kb_layout,
|
||||||
|
ctx.omarchy_install.get("input_method"),
|
||||||
|
ctx.omarchy_install.get("input_xkb_layout", ""),
|
||||||
|
):
|
||||||
error(f"Invalid keyboard language specified: {kb_layout}")
|
error(f"Invalid keyboard language specified: {kb_layout}")
|
||||||
|
|
||||||
if config.mirror_config:
|
if config.mirror_config:
|
||||||
@@ -317,7 +287,6 @@ def arch_install_system(ctx: InstallContext) -> None:
|
|||||||
_install_early_packages(installer)
|
_install_early_packages(installer)
|
||||||
_configure_limine_boot(ctx, installer, config)
|
_configure_limine_boot(ctx, installer, config)
|
||||||
|
|
||||||
_stage_region_defaults(ctx.target)
|
|
||||||
info("› creating user (with /etc/skel populated)")
|
info("› creating user (with /etc/skel populated)")
|
||||||
if config.auth_config and config.auth_config.users:
|
if config.auth_config and config.auth_config.users:
|
||||||
installer.create_users(config.auth_config.users)
|
installer.create_users(config.auth_config.users)
|
||||||
@@ -1166,13 +1135,6 @@ def _run_target_setup_command(ctx: InstallContext, cmd: list[str], *, user: str
|
|||||||
|
|
||||||
|
|
||||||
def run_system_finalizer(ctx: InstallContext) -> None:
|
def run_system_finalizer(ctx: InstallContext) -> None:
|
||||||
# The runtime package transaction has installed the regional keyring from
|
|
||||||
# the ISO. Initialize the target's trust database before enabling the online
|
|
||||||
# community repository; the build container's GPG state is not inherited.
|
|
||||||
if _iso_region() == "cn":
|
|
||||||
_run_target_setup_command(ctx, ["pacman-key", "--init"])
|
|
||||||
_run_target_setup_command(ctx, ["pacman-key", "--populate", "archlinux", "archlinuxcn"])
|
|
||||||
|
|
||||||
if ctx.defer_provisioning:
|
if ctx.defer_provisioning:
|
||||||
cmd = ["/usr/bin/omarchy-apply-system", "--defer-provisioning", "--first-install"]
|
cmd = ["/usr/bin/omarchy-apply-system", "--defer-provisioning", "--first-install"]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)
|
||||||
|
work=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
mkdir -p "$work/install/provisioning"
|
||||||
|
cat > "$work/install/provisioning/setup-form.sh" <<'FORM'
|
||||||
|
OMARCHY_KEYBOARD_LAYOUTS=$'Chinese (Simplified, Pinyin)|us|pinyin
|
||||||
|
English (US)|us
|
||||||
|
Japanese|jp106|mozc'
|
||||||
|
FORM
|
||||||
|
export OMARCHY_PATH="$work"
|
||||||
|
eval "$(sed -n '/^select_keyboard() {$/,/^}$/p' "$ROOT/bin/omarchy-iso-test")"
|
||||||
|
press() { pressed+=("$1"); }
|
||||||
|
sleep() { :; }
|
||||||
|
|
||||||
|
pressed=()
|
||||||
|
select_keyboard "Japanese"
|
||||||
|
[[ ${pressed[*]} == "down" ]]
|
||||||
|
pressed=()
|
||||||
|
select_keyboard "Chinese (Simplified, Pinyin)"
|
||||||
|
[[ ${pressed[*]} == "up" ]]
|
||||||
|
pressed=()
|
||||||
|
select_keyboard "English (US)"
|
||||||
|
(( ${#pressed[@]} == 0 ))
|
||||||
|
if (set -e; select_keyboard "Missing") 2>"$work/error"; then
|
||||||
|
echo "Unknown keyboard unexpectedly succeeded" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
grep -q 'Keyboard choice is absent' "$work/error"
|
||||||
|
echo "ok - VM keyboard choices are relative to the default and reject absent rows"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Exercise the compatibility boundary without importing host archinstall."""
|
||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
import types
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
source = ROOT / "configs/airootfs/usr/share/omarchy-iso/orchestrator/archinstall_adapter.py"
|
||||||
|
tree = ast.parse(source.read_text())
|
||||||
|
namespace = {}
|
||||||
|
functions = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name in {"_method_accepts", "sanity_check"}]
|
||||||
|
exec(compile(ast.Module(body=functions, type_ignores=[]), str(source), "exec"), namespace)
|
||||||
|
|
||||||
|
|
||||||
|
class SanityCheckTest(unittest.TestCase):
|
||||||
|
def test_legacy_archinstall_stays_offline(self):
|
||||||
|
calls = []
|
||||||
|
def check(offline=False, skip_ntp=False, skip_wkd=False):
|
||||||
|
calls.append((offline, skip_ntp, skip_wkd))
|
||||||
|
namespace["sanity_check"](types.SimpleNamespace(sanity_check=check))
|
||||||
|
self.assertEqual(calls, [(True, True, True)])
|
||||||
|
|
||||||
|
def test_archinstall_45_does_not_receive_removed_argument(self):
|
||||||
|
calls = []
|
||||||
|
class Installer:
|
||||||
|
def sanity_check(self, skip_ntp=False, skip_wkd=False):
|
||||||
|
calls.append((skip_ntp, skip_wkd))
|
||||||
|
namespace["sanity_check"](Installer())
|
||||||
|
self.assertEqual(calls, [(True, True)])
|
||||||
@@ -93,7 +93,6 @@ class KernelSelectionTest(unittest.TestCase):
|
|||||||
events = []
|
events = []
|
||||||
installer = mock.MagicMock()
|
installer = mock.MagicMock()
|
||||||
installer.minimal_installation.side_effect = lambda **kwargs: events.append("base")
|
installer.minimal_installation.side_effect = lambda **kwargs: events.append("base")
|
||||||
installer.create_users.side_effect = lambda users: events.append("user")
|
|
||||||
|
|
||||||
def install_packages(packages):
|
def install_packages(packages):
|
||||||
events.append(packages)
|
events.append(packages)
|
||||||
@@ -103,12 +102,12 @@ class KernelSelectionTest(unittest.TestCase):
|
|||||||
installer.add_additional_packages.side_effect = install_packages
|
installer.add_additional_packages.side_effect = install_packages
|
||||||
config = types.SimpleNamespace(
|
config = types.SimpleNamespace(
|
||||||
kernels=kernels, locale_config=None, mirror_config=None,
|
kernels=kernels, locale_config=None, mirror_config=None,
|
||||||
swap=None, auth_config=types.SimpleNamespace(users=["alice"]), app_config=None, timezone=None,
|
swap=None, auth_config=None, app_config=None, timezone=None,
|
||||||
ntp=False, hostname="test", pacman_config="/etc/pacman.conf",
|
ntp=False, hostname="test", pacman_config="/etc/pacman.conf",
|
||||||
)
|
)
|
||||||
ctx = types.SimpleNamespace(
|
ctx = types.SimpleNamespace(
|
||||||
state={"arch_config_handler": types.SimpleNamespace(config=config), "mirror_handler": None},
|
state={"arch_config_handler": types.SimpleNamespace(config=config), "mirror_handler": None},
|
||||||
target=Path("/unused"), tailscale_authkey_path=None,
|
target=Path("/unused"), tailscale_authkey_path=None, omarchy_install={},
|
||||||
)
|
)
|
||||||
for name in ("_mount_offline_package_cache", "_mask_mkinitcpio_pacman_hooks",
|
for name in ("_mount_offline_package_cache", "_mask_mkinitcpio_pacman_hooks",
|
||||||
"_configure_limine_boot", "_write_pre_mounted_fstab"):
|
"_configure_limine_boot", "_write_pre_mounted_fstab"):
|
||||||
@@ -117,10 +116,10 @@ class KernelSelectionTest(unittest.TestCase):
|
|||||||
unmount = stack.enter_context(mock.patch.object(phases_impl, "_unmount_offline_package_cache"))
|
unmount = stack.enter_context(mock.patch.object(phases_impl, "_unmount_offline_package_cache"))
|
||||||
stack.enter_context(mock.patch.object(phases_impl, "configure_keyboard", return_value=True))
|
stack.enter_context(mock.patch.object(phases_impl, "configure_keyboard", return_value=True))
|
||||||
stack.enter_context(mock.patch.object(phases_impl, "_install_early_packages", side_effect=lambda inst: events.append("early")))
|
stack.enter_context(mock.patch.object(phases_impl, "_install_early_packages", side_effect=lambda inst: events.append("early")))
|
||||||
stack.enter_context(mock.patch.object(phases_impl, "_stage_region_defaults", side_effect=lambda target: events.append("region")))
|
|
||||||
stack.enter_context(mock.patch.object(phases_impl, "_runtime_package_list", return_value=["omarchy"]))
|
stack.enter_context(mock.patch.object(phases_impl, "_runtime_package_list", return_value=["omarchy"]))
|
||||||
stack.enter_context(mock.patch.object(phases_impl.arch, "is_pre_mount", return_value=True, create=True))
|
stack.enter_context(mock.patch.object(phases_impl.arch, "is_pre_mount", return_value=True, create=True))
|
||||||
stack.enter_context(mock.patch.object(phases_impl.arch, "root_user", return_value=None, create=True))
|
stack.enter_context(mock.patch.object(phases_impl.arch, "root_user", return_value=None, create=True))
|
||||||
|
stack.enter_context(mock.patch.object(phases_impl.arch, "sanity_check", create=True))
|
||||||
opened = stack.enter_context(mock.patch.object(phases_impl.arch, "open_installer", create=True))
|
opened = stack.enter_context(mock.patch.object(phases_impl.arch, "open_installer", create=True))
|
||||||
opened.return_value.__enter__.return_value = installer
|
opened.return_value.__enter__.return_value = installer
|
||||||
if fail_headers:
|
if fail_headers:
|
||||||
@@ -130,7 +129,7 @@ class KernelSelectionTest(unittest.TestCase):
|
|||||||
phases_impl.arch_install_system(ctx)
|
phases_impl.arch_install_system(ctx)
|
||||||
expected = ["base", [f"{kernel}-headers" for kernel in kernels]]
|
expected = ["base", [f"{kernel}-headers" for kernel in kernels]]
|
||||||
if not fail_headers:
|
if not fail_headers:
|
||||||
expected += ["early", "region", "user", ["omarchy"]]
|
expected += ["early", ["omarchy"]]
|
||||||
self.assertEqual(events, expected)
|
self.assertEqual(events, expected)
|
||||||
unmask.assert_called_once_with(ctx)
|
unmask.assert_called_once_with(ctx)
|
||||||
unmount.assert_called_once_with(ctx)
|
unmount.assert_called_once_with(ctx)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
@@ -17,6 +18,7 @@ from orchestrator import keyboard as KEYBOARD # noqa: E402
|
|||||||
# wherever this checkout can see a runtime, and skip the coverage test rather
|
# wherever this checkout can see a runtime, and skip the coverage test rather
|
||||||
# than fail when none is around (a bare CI checkout of just this repo).
|
# than fail when none is around (a bare CI checkout of just this repo).
|
||||||
SETUP_FORM_CANDIDATES = (
|
SETUP_FORM_CANDIDATES = (
|
||||||
|
Path(os.environ.get("OMARCHY_PATH", "/nonexistent")) / "install/provisioning/setup-form.sh",
|
||||||
Path("/omarchy-source/install/provisioning/setup-form.sh"),
|
Path("/omarchy-source/install/provisioning/setup-form.sh"),
|
||||||
ROOT.parent / "omarchy/install/provisioning/setup-form.sh",
|
ROOT.parent / "omarchy/install/provisioning/setup-form.sh",
|
||||||
Path("/usr/share/omarchy/install/provisioning/setup-form.sh"),
|
Path("/usr/share/omarchy/install/provisioning/setup-form.sh"),
|
||||||
@@ -31,7 +33,7 @@ def supported_keymaps():
|
|||||||
r"OMARCHY_KEYBOARD_LAYOUTS=\$'(.*?)'\n", candidate.read_text(), re.DOTALL
|
r"OMARCHY_KEYBOARD_LAYOUTS=\$'(.*?)'\n", candidate.read_text(), re.DOTALL
|
||||||
)
|
)
|
||||||
assert block, f"no layout list in {candidate}"
|
assert block, f"no layout list in {candidate}"
|
||||||
return [line.split("|", 1)[1] for line in block.group(1).splitlines()]
|
return [line.split("|")[1] for line in block.group(1).splitlines()]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -79,6 +81,36 @@ class KeyboardConfigurationTest(unittest.TestCase):
|
|||||||
"KEYMAP=us\nFONT=default8x16\n",
|
"KEYMAP=us\nFONT=default8x16\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_input_choice_survives_while_console_keymap_stays_real(self):
|
||||||
|
for method, keymap, layout in (("mozc", "jp106", ""), ("mozc", "us", ""), ("hangul", "us", "kr"), ("pinyin", "us", ""), ("chewing", "us", "")):
|
||||||
|
with self.subTest(method=method), tempfile.TemporaryDirectory() as directory:
|
||||||
|
target = self.target(directory)
|
||||||
|
self.assertTrue(KEYBOARD.configure_keyboard(target, keymap, method, layout))
|
||||||
|
self.assertIn(f"KEYMAP={keymap}", (target / "etc/vconsole.conf").read_text())
|
||||||
|
if keymap == "jp106":
|
||||||
|
self.assertIn("XKBLAYOUT=jp", (target / "etc/vconsole.conf").read_text())
|
||||||
|
self.assertEqual((target / "etc/omarchy/input-method").read_text(), f"INPUT_METHOD={method}\nXKB_LAYOUT={layout}\n")
|
||||||
|
|
||||||
|
def test_input_choice_with_no_console_map_defaults_to_us(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
target = self.target(directory)
|
||||||
|
self.assertTrue(KEYBOARD.configure_keyboard(target, "", "pinyin"))
|
||||||
|
self.assertIn("INPUT_METHOD=pinyin", (target / "etc/omarchy/input-method").read_text())
|
||||||
|
|
||||||
|
def test_unknown_keyboard_does_not_silently_drop_an_input_choice(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
target = self.target(directory)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
KEYBOARD.configure_keyboard(target, "unknown", "pinyin")
|
||||||
|
|
||||||
|
def test_invalid_input_preferences_are_rejected_without_writes(self):
|
||||||
|
for method, layout in (("unknown", ""), ("hangul", "kr\nKEYMAP=ru")):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
target = self.target(directory)
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
KEYBOARD.configure_keyboard(target, "us", method, layout)
|
||||||
|
self.assertFalse((target / "etc/omarchy/input-method").exists())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
"""Region bootstrap contracts without Docker, pacman, or a running guest."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import types
|
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
|
||||||
sys.path.insert(0, str(ROOT / "configs/airootfs/usr/share/omarchy-iso"))
|
|
||||||
sys.modules.setdefault("orchestrator.archinstall_adapter", types.ModuleType("orchestrator.archinstall_adapter"))
|
|
||||||
from orchestrator import phases_impl
|
|
||||||
|
|
||||||
|
|
||||||
class RegionTargetTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
tmp = tempfile.TemporaryDirectory()
|
|
||||||
self.addCleanup(tmp.cleanup)
|
|
||||||
self.root = Path(tmp.name)
|
|
||||||
self.target = self.root / "target"
|
|
||||||
self.marker = self.root / "omarchy_region"
|
|
||||||
self.payload = self.root / "payload"
|
|
||||||
for name, value in (("ISO_REGION_FILE", self.marker), ("ISO_REGION_PAYLOAD", self.payload)):
|
|
||||||
patch = mock.patch.object(phases_impl, name, value)
|
|
||||||
patch.start()
|
|
||||||
self.addCleanup(patch.stop)
|
|
||||||
|
|
||||||
def test_global_does_not_need_a_profile_or_change_user_defaults(self):
|
|
||||||
phases_impl._stage_region_defaults(self.target)
|
|
||||||
self.assertEqual((self.target / "etc/omarchy/region").read_text(), "global\n")
|
|
||||||
self.assertFalse((self.target / "etc/skel").exists())
|
|
||||||
|
|
||||||
def test_cn_seeds_skel_without_touching_existing_users_or_locale(self):
|
|
||||||
self.marker.write_text("cn\n")
|
|
||||||
profile = self.payload / "skel/.config/example/defaults"
|
|
||||||
profile.parent.mkdir(parents=True)
|
|
||||||
profile.write_text("regional defaults\n")
|
|
||||||
existing = self.target / "home/alice/.config/example/defaults"
|
|
||||||
existing.parent.mkdir(parents=True)
|
|
||||||
existing.write_text("personal settings\n")
|
|
||||||
skel = self.target / "etc/skel"
|
|
||||||
skel.mkdir(parents=True)
|
|
||||||
(skel / ".bashrc").write_text("original\n")
|
|
||||||
(self.target / "etc/locale.conf").write_text("LANG=en_US.UTF-8\n")
|
|
||||||
|
|
||||||
phases_impl._stage_region_defaults(self.target)
|
|
||||||
self.assertEqual((skel / ".config/example/defaults").read_text(), "regional defaults\n")
|
|
||||||
self.assertEqual((skel / ".bashrc").read_text(), "original\n")
|
|
||||||
self.assertEqual(existing.read_text(), "personal settings\n")
|
|
||||||
self.assertEqual((self.target / "etc/locale.conf").read_text(), "LANG=en_US.UTF-8\n")
|
|
||||||
self.assertEqual((self.target / "etc/omarchy/region").read_text(), "cn\n")
|
|
||||||
|
|
||||||
def test_bad_region_and_missing_payload_fail_before_target_changes(self):
|
|
||||||
for region in ("CN", "chn", "zz", "../cn", "", "cn"):
|
|
||||||
with self.subTest(region=region):
|
|
||||||
self.marker.write_text(region)
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
phases_impl._stage_region_defaults(self.target)
|
|
||||||
self.assertFalse(self.target.exists())
|
|
||||||
|
|
||||||
def test_cn_keyring_is_initialized_before_finalization_for_both_install_modes(self):
|
|
||||||
self.marker.write_text("cn\n")
|
|
||||||
for deferred in (False, True):
|
|
||||||
ctx = types.SimpleNamespace(target=self.target, username="alice", defer_provisioning=deferred)
|
|
||||||
with mock.patch.object(phases_impl, "_run_target_setup_command") as run, \
|
|
||||||
mock.patch.object(phases_impl, "_mask_mkinitcpio_pacman_hooks"), \
|
|
||||||
mock.patch.object(phases_impl, "_unmask_mkinitcpio_pacman_hooks"):
|
|
||||||
phases_impl.run_system_finalizer(ctx)
|
|
||||||
commands = [call.args[1] for call in run.call_args_list]
|
|
||||||
self.assertEqual(commands[:2], [["pacman-key", "--init"], ["pacman-key", "--populate", "archlinux", "archlinuxcn"]])
|
|
||||||
expected = ["--defer-provisioning"] if deferred else ["--install-user", "alice"]
|
|
||||||
self.assertEqual(commands[2], ["/usr/bin/omarchy-apply-system", *expected, "--first-install"])
|
|
||||||
|
|
||||||
def test_keyring_failure_prevents_enabling_online_repositories(self):
|
|
||||||
self.marker.write_text("cn\n")
|
|
||||||
ctx = types.SimpleNamespace(target=self.target, username="alice", defer_provisioning=False)
|
|
||||||
with mock.patch.object(phases_impl, "_run_target_setup_command", side_effect=RuntimeError("keyring failed")) as run:
|
|
||||||
with self.assertRaisesRegex(RuntimeError, "keyring failed"):
|
|
||||||
phases_impl.run_system_finalizer(ctx)
|
|
||||||
self.assertEqual(run.call_count, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class RegionBuildTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
tmp = tempfile.TemporaryDirectory()
|
|
||||||
self.addCleanup(tmp.cleanup)
|
|
||||||
self.root = Path(tmp.name)
|
|
||||||
self.runtime = self.root / "runtime"
|
|
||||||
self.profile = self.runtime / "default/regions/cn"
|
|
||||||
(self.profile / "pacman").mkdir(parents=True)
|
|
||||||
(self.runtime / "bin").mkdir()
|
|
||||||
(self.runtime / "bin/omarchy-apply-pacman").touch()
|
|
||||||
(self.profile / "packages").write_text("archlinuxcn-keyring\nexample-regional-package\n")
|
|
||||||
(self.profile / "pacman/pacman.conf.append").write_text("[archlinuxcn]\nServer = https://mirrors.ustc.edu.cn/archlinuxcn/$arch\n")
|
|
||||||
(self.profile / "pacman/mirrorlist.append").write_text("Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch\n")
|
|
||||||
(self.profile / "timezone").write_text("Asia/Shanghai\n")
|
|
||||||
self.iso = self.root / "iso"
|
|
||||||
self.payload = self.iso / "usr/share/omarchy-iso"
|
|
||||||
self.payload.mkdir(parents=True)
|
|
||||||
self.packages = self.payload / "omarchy-base.packages"
|
|
||||||
self.packages.write_text("base\n")
|
|
||||||
self.config = self.root / "pacman.conf"
|
|
||||||
shutil.copyfile(ROOT / "configs/pacman-online-stable.conf", self.config)
|
|
||||||
self.original_config = self.config.read_bytes()
|
|
||||||
self.bin = self.root / "bin"
|
|
||||||
self.bin.mkdir()
|
|
||||||
self.log = self.root / "commands"
|
|
||||||
for command in ("pacman", "pacman-key"):
|
|
||||||
stub = self.bin / command
|
|
||||||
stub.write_text('#!/bin/bash\nprintf "%s %s\\n" "${0##*/}" "$*" >> "$TEST_LOG"\nif [[ ${0##*/} == pacman ]]; then exit "${TEST_PACMAN_STATUS:-0}"; fi\n')
|
|
||||||
stub.chmod(0o755)
|
|
||||||
|
|
||||||
def prepare(self, region, **env):
|
|
||||||
return subprocess.run(
|
|
||||||
["bash", str(ROOT / "builder/prepare-region.sh"), region, str(self.runtime), str(self.iso), str(self.config)],
|
|
||||||
env={**os.environ, "PATH": f"{self.bin}:{os.environ['PATH']}", "TEST_LOG": str(self.log), **env},
|
|
||||||
capture_output=True, text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_cn_adds_real_install_targets_and_verifies_keyring_first(self):
|
|
||||||
result = self.prepare("cn")
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(self.packages.read_text(), "base\n\narchlinuxcn-keyring\nexample-regional-package\n")
|
|
||||||
self.assertEqual(self.config.read_bytes(), self.original_config + b"\n[archlinuxcn]\nServer = https://mirrors.ustc.edu.cn/archlinuxcn/$arch\n")
|
|
||||||
self.assertEqual((self.payload / "region/packages").read_text(), "archlinuxcn-keyring\nexample-regional-package\n")
|
|
||||||
self.assertEqual((self.payload / "region/timezone").read_text(), "Asia/Shanghai\n")
|
|
||||||
self.assertEqual(self.log.read_text().splitlines(), [f"pacman --config {self.config} --noconfirm -Sy --needed archlinuxcn-keyring", "pacman-key --populate archlinuxcn"])
|
|
||||||
with mock.patch.object(phases_impl, "Path", return_value=self.packages), \
|
|
||||||
mock.patch.object(phases_impl, "_package_targets", return_value={"runtime": "omarchy-dev", "settings": "omarchy-settings-dev", "nvim": "omarchy-nvim"}):
|
|
||||||
self.assertEqual(phases_impl._runtime_package_list(None), ["omarchy-dev", "base", "archlinuxcn-keyring", "example-regional-package"])
|
|
||||||
|
|
||||||
def test_global_is_unchanged_without_region_support_in_runtime(self):
|
|
||||||
shutil.rmtree(self.runtime)
|
|
||||||
result = self.prepare("global")
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertEqual(self.packages.read_text(), "base\n")
|
|
||||||
self.assertEqual(self.config.read_bytes(), self.original_config)
|
|
||||||
self.assertFalse(self.log.exists())
|
|
||||||
self.assertFalse((self.payload / "region").exists())
|
|
||||||
|
|
||||||
def test_missing_runtime_support_fails_before_package_operations(self):
|
|
||||||
(self.runtime / "bin/omarchy-apply-pacman").unlink()
|
|
||||||
result = self.prepare("cn")
|
|
||||||
self.assertNotEqual(result.returncode, 0)
|
|
||||||
self.assertIn("--local-source", result.stderr)
|
|
||||||
self.assertFalse(self.log.exists())
|
|
||||||
self.assertEqual(self.config.read_bytes(), self.original_config)
|
|
||||||
|
|
||||||
def test_profile_without_user_defaults_is_supported(self):
|
|
||||||
result = self.prepare("cn")
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertFalse((self.payload / "region/skel").exists())
|
|
||||||
|
|
||||||
def test_untrusted_keyring_aborts_without_importing_keys(self):
|
|
||||||
result = self.prepare("cn", TEST_PACMAN_STATUS="1")
|
|
||||||
self.assertNotEqual(result.returncode, 0)
|
|
||||||
self.assertNotIn("pacman-key", self.log.read_text())
|
|
||||||
|
|
||||||
|
|
||||||
class RegionMakeTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
tmp = tempfile.TemporaryDirectory()
|
|
||||||
self.addCleanup(tmp.cleanup)
|
|
||||||
self.root = Path(tmp.name)
|
|
||||||
(self.root / "bin").mkdir()
|
|
||||||
shutil.copyfile(ROOT / "bin/omarchy-iso-make", self.root / "bin/omarchy-iso-make")
|
|
||||||
for directory in ("configs/airootfs/root", "configs/airootfs/usr/local/bin", "stubs"):
|
|
||||||
(self.root / directory).mkdir(parents=True)
|
|
||||||
(self.root / "configs/profiledef.sh").touch()
|
|
||||||
self.log = self.root / "docker-args"
|
|
||||||
stubs = {
|
|
||||||
"git": '#!/bin/bash\nexit 0\n',
|
|
||||||
"sudo": '#!/bin/bash\necho "unexpected sudo" >&2\nexit 99\n',
|
|
||||||
"docker": '''#!/bin/bash
|
|
||||||
if [[ $1 == version ]]; then exit 0; fi
|
|
||||||
printf '%s\\n' "$@" > "$TEST_LOG"
|
|
||||||
for arg in "$@"; do
|
|
||||||
if [[ $arg == *:/out/ ]]; then touch "${arg%:/out/}/omarchy-test-x86_64.iso"; fi
|
|
||||||
done
|
|
||||||
''',
|
|
||||||
}
|
|
||||||
for name, content in stubs.items():
|
|
||||||
stub = self.root / "stubs" / name
|
|
||||||
stub.write_text(content)
|
|
||||||
stub.chmod(0o755)
|
|
||||||
|
|
||||||
def make(self, *args):
|
|
||||||
env = {key: value for key, value in os.environ.items() if not key.startswith("OMARCHY_")}
|
|
||||||
return subprocess.run(
|
|
||||||
["bash", "bin/omarchy-iso-make", "--keep-pkg-cache", "--no-boot-offer", *args],
|
|
||||||
cwd=self.root, env={**env, "HOME": str(self.root), "PATH": f"{self.root / 'stubs'}:{env['PATH']}", "TEST_LOG": str(self.log), "TEST_ROOT": str(self.root)},
|
|
||||||
capture_output=True, text=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_region_is_independent_of_channel_and_names_cache_and_artifact(self):
|
|
||||||
for flags, channel, ref in (([], "stable", "quattro"), (["--rc"], "rc", "rc"), (["--edge"], "edge", "edge"), (["--dev"], "edge", "dev")):
|
|
||||||
with self.subTest(channel=ref):
|
|
||||||
release = self.root / "release"
|
|
||||||
if release.exists():
|
|
||||||
shutil.rmtree(release)
|
|
||||||
result = self.make(*flags, "--region", "cn")
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
args = self.log.read_text().splitlines()
|
|
||||||
self.assertIn("OMARCHY_REGION=cn", args)
|
|
||||||
self.assertIn(f"OMARCHY_MIRROR={channel}", args)
|
|
||||||
self.assertIn(f"OMARCHY_ISO_REF={ref}", args)
|
|
||||||
self.assertIn(f"{self.root}/.cache/omarchy/iso_{channel}-cn/airootfs/var/cache/omarchy:/var/cache/airootfs/var/cache/omarchy", args)
|
|
||||||
self.assertTrue((release / f"omarchy-test-x86_64-{ref}-cn.iso").exists())
|
|
||||||
|
|
||||||
def test_default_preserves_existing_global_cache_and_name(self):
|
|
||||||
result = self.make()
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertIn("OMARCHY_REGION=global", self.log.read_text().splitlines())
|
|
||||||
self.assertIn("/iso_stable/", self.log.read_text())
|
|
||||||
self.assertTrue((self.root / "release/omarchy-test-x86_64-quattro.iso").exists())
|
|
||||||
|
|
||||||
def test_build_renames_only_its_own_iso(self):
|
|
||||||
release = self.root / "release"
|
|
||||||
release.mkdir()
|
|
||||||
other = release / "omarchy-other-x86_64.iso"
|
|
||||||
other.touch()
|
|
||||||
result = self.make("--region", "cn")
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
self.assertTrue(other.exists())
|
|
||||||
self.assertTrue((release / "omarchy-test-x86_64-quattro-cn.iso").exists())
|
|
||||||
self.assertEqual(list(release.glob(".build-*")), [])
|
|
||||||
|
|
||||||
def test_local_source_keeps_region_and_mounts_both_checkouts(self):
|
|
||||||
for name in ("runtime", "packages"):
|
|
||||||
(self.root / name).mkdir()
|
|
||||||
result = self.make("--region", "cn", "--local-source", str(self.root / "runtime"), str(self.root / "packages"))
|
|
||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
|
||||||
args = self.log.read_text().splitlines()
|
|
||||||
self.assertIn("OMARCHY_REGION=cn", args)
|
|
||||||
self.assertIn("OMARCHY_ISO_REF=local", args)
|
|
||||||
self.assertIn(f"{self.root}/runtime:/omarchy-source:ro", args)
|
|
||||||
self.assertIn(f"{self.root}/packages:/omarchy-pkgs:ro", args)
|
|
||||||
self.assertTrue((self.root / "release/omarchy-test-x86_64-local-cn.iso").exists())
|
|
||||||
|
|
||||||
def test_invalid_regions_fail_without_starting_build(self):
|
|
||||||
for flags in (("--region",), ("--region", "chn"), ("--region", "CN"), ("--region", "zz"), ("--region", "../cn")):
|
|
||||||
with self.subTest(flags=flags):
|
|
||||||
result = self.make(*flags)
|
|
||||||
self.assertNotEqual(result.returncode, 0)
|
|
||||||
self.assertFalse(self.log.exists())
|
|
||||||
self.assertFalse((self.root / "release").exists())
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConfiguratorRegionTimezoneTest(unittest.TestCase):
|
|
||||||
"""The configurator hands the ISO's regional timezone to the setup form."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
configurator = (ROOT / "configs/airootfs/root/configurator").read_text()
|
|
||||||
helper = re.search(r"^region_timezone\(\) \{\n.*?^\}", configurator, re.M | re.S)
|
|
||||||
self.assertIsNotNone(helper)
|
|
||||||
self.helper = helper.group(0)
|
|
||||||
self.configurator = configurator
|
|
||||||
tmp = tempfile.TemporaryDirectory()
|
|
||||||
self.addCleanup(tmp.cleanup)
|
|
||||||
self.file = Path(tmp.name) / "timezone"
|
|
||||||
|
|
||||||
def region_timezone(self):
|
|
||||||
return subprocess.run(
|
|
||||||
["bash", "-c", f"set -euo pipefail\n{self.helper}\nregion_timezone"],
|
|
||||||
env={**os.environ, "REGION_TIMEZONE": str(self.file)},
|
|
||||||
capture_output=True, text=True, check=True,
|
|
||||||
).stdout
|
|
||||||
|
|
||||||
def test_global_iso_offers_no_timezone(self):
|
|
||||||
self.assertEqual(self.region_timezone(), "")
|
|
||||||
|
|
||||||
def test_regional_iso_offers_its_profile_timezone(self):
|
|
||||||
self.file.write_text("Asia/Shanghai\n")
|
|
||||||
self.assertEqual(self.region_timezone(), "Asia/Shanghai\n")
|
|
||||||
|
|
||||||
def test_user_form_passes_it_to_the_timezone_prompt(self):
|
|
||||||
self.assertIn('omarchy_prompt_timezone "$(region_timezone)"', self.configurator)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
Reference in new issue
Block a user