diff --git a/README.md b/README.md index 1ec23ea..8dad9b2 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ These are the configurator's own output files, so the way to get a starting set | `authorized_keys` | No | SSH public keys in sshd's own format, one per line | | `tailscale_authkey` | No | Tailscale auth key; the machine joins your tailnet on first boot | -Both required files must be present or the installer falls back to the configurator. Generate the password hash for `user_credentials.json` with `openssl passwd -6 "yourpassword"`. +Both required files must be present or the installer falls back to the configurator. `user_configuration.json` may set `omarchy_install.region` to `global` (the default) or `china`, the region the configurator asks for on its first screen. Generate the password hash for `user_credentials.json` with `openssl passwd -6 "yourpassword"`. Encryption itself is configured by the `disk_encryption` block inside `user_configuration.json` — which carries the passphrase in plaintext, so treat a drive built from an encrypted install accordingly. The flag file must match it: it drives the encrypted install's SDDM autologin and the final boot validation, not the encryption. diff --git a/configs/airootfs/root/configurator b/configs/airootfs/root/configurator index 2960c06..13c7888 100644 --- a/configs/airootfs/root/configurator +++ b/configs/airootfs/root/configurator @@ -216,7 +216,10 @@ keyboard_form() { say --foreground 8 "Press Ctrl+C to prepare this machine for another owner." echo - omarchy_prompt_keyboard && status=0 || status=$? + omarchy_prompt_region && status=0 || status=$? + if ((status == 0)); then + omarchy_prompt_keyboard && status=0 || status=$? + fi ((status == 0)) && break # Esc means "back", and nothing precedes the first screen, so re-ask. @@ -298,7 +301,8 @@ Full name,${full_name:-[Skipped]} Email address,${email_address:-[Skipped]} Hostname,$hostname Timezone,$timezone -Keyboard,$keyboard" | +Keyboard,$keyboard +Region,${region^}" | gum table -s "," -p | sed "s/^/${PADDING_LEFT_SPACES}/" echo @@ -801,6 +805,7 @@ run_partition_execute() { "custom_commands": [], "omarchy_install": { "mode": "protected", + "region": "$region", "target_mount": "/mnt", "boot": { "esp_mount": "$esp_mount_in_target", @@ -1139,6 +1144,7 @@ cat <<-_EOF_ >user_configuration.json "custom_commands": [], "omarchy_install": { "mode": "full_disk", + "region": "$region", "defer_provisioning": $defer_provisioning, "target_mount": "/mnt", "boot": { diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py index 02494b0..be2d4c6 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py @@ -65,6 +65,12 @@ class InstallContext: defer_provisioning = bool(omarchy_install.get("defer_provisioning")) or defer_provisioning_marker is not None omarchy_install["defer_provisioning"] = defer_provisioning + # Autoinstall configs written before regions existed install Global. + region = omarchy_install.get("region", "global") + if region not in ("global", "china"): + raise RuntimeError(f"omarchy_install.region must be global or china, not {region!r}") + omarchy_install["region"] = region + if creds_path.exists(): user_credentials = json.loads(creds_path.read_text()) elif defer_provisioning: diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py index 1ebd4ce..2a64bc6 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py @@ -1098,6 +1098,7 @@ def _run_target_setup_command(ctx: InstallContext, cmd: list[str], *, user: str f"OMARCHY_USER_NAME={ctx.full_name}", f"OMARCHY_USER_EMAIL={ctx.email}", f"OMARCHY_MIRROR={mirror_channel}", + f"OMARCHY_REGION={ctx.omarchy_install['region']}", f"OMARCHY_ISO_REF={_iso_ref()}", f"OMARCHY_RUNTIME_PACKAGE={_omarchy_runtime_package()}", f"OMARCHY_SETTINGS_PACKAGE={_omarchy_settings_package()}", diff --git a/test/unit/test_region.py b/test/unit/test_region.py new file mode 100644 index 0000000..da5c95d --- /dev/null +++ b/test/unit/test_region.py @@ -0,0 +1,77 @@ +"""The configurator's region reaches the target setup as OMARCHY_REGION.""" + +import json +import os +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 context, phases_impl # noqa: E402 + + +def load_context(root: Path, omarchy_install: dict | None) -> context.InstallContext: + config = {"kernels": ["linux-omarchy"]} + if omarchy_install is not None: + config["omarchy_install"] = omarchy_install + config_path = root / "config.json" + creds_path = root / "creds.json" + config_path.write_text(json.dumps(config)) + creds_path.write_text(json.dumps({"users": [{"username": "test"}]})) + with mock.patch.dict(os.environ, { + "OMARCHY_INSTALL_CONFIG": str(config_path), + "OMARCHY_INSTALL_CREDS": str(creds_path), + "OMARCHY_INSTALL_STATE_DIR": str(root / "state"), + }, clear=True): + return context.InstallContext.from_env() + + +class RegionTest(unittest.TestCase): + def test_context_reads_region(self): + for omarchy_install, expected in [ + (None, "global"), + ({"mode": "full_disk"}, "global"), + ({"mode": "full_disk", "region": "global"}, "global"), + ({"mode": "full_disk", "region": "china"}, "china"), + ]: + with self.subTest(omarchy_install=omarchy_install), tempfile.TemporaryDirectory() as tmp: + ctx = load_context(Path(tmp), omarchy_install) + self.assertEqual(ctx.omarchy_install["region"], expected) + + def test_context_rejects_unknown_region(self): + for region in ("China", "mars", ""): + with self.subTest(region=region), tempfile.TemporaryDirectory() as tmp: + with self.assertRaisesRegex(RuntimeError, "omarchy_install.region must be global or china"): + load_context(Path(tmp), {"mode": "full_disk", "region": region}) + + def test_target_setup_receives_region(self): + for region in ("global", "china"): + with self.subTest(region=region), tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + ctx = types.SimpleNamespace( + target=root / "mnt", + log_path=root / "install.log", + omarchy_install={"region": region}, + username="test", + full_name="", + email="", + state={"omarchy_start_time": "now", "omarchy_start_epoch": 0, + "omarchy_finalizer_header_written": True}, + ) + with mock.patch.object(phases_impl, "_prepare_target_setup"), \ + mock.patch.object(phases_impl.subprocess, "run") as run: + phases_impl._run_target_setup_command(ctx, ["/usr/bin/omarchy-apply-system"]) + chroot = next(call.args[0] for call in run.call_args_list if call.args[0][0] == "arch-chroot") + self.assertIn(f"OMARCHY_REGION={region}", chroot) + + +if __name__ == "__main__": + unittest.main()