Rename OEM vocabulary to provisioning (ISO)
Matches the runtime rename. The deferred-provisioning install mode replaces
'OEM' everywhere:
omarchy_install.oem → omarchy_install.defer_provisioning
--oem / --oem-file → --defer-provisioning / --defer-provisioning-file
OMARCHY_INSTALL_OEM_FILE → OMARCHY_INSTALL_DEFER_PROVISIONING_FILE
cidata 'oem' marker → 'defer-provisioning'
/var/lib/omarchy/oem/ → /var/lib/omarchy/provisioning/
/etc/omarchy/oem.key → /etc/omarchy/provisioning.key
install/oem/omarchy-oem-setup.service → install/provisioning/omarchy-provision-owner.service
stage_oem_state phase → stage_provisioning_state ('Staging provisioning')
Orchestrator internals (OEM_STATE_DIR, _validate_oem_state, ctx.oem, …),
the configurator, cidata-load, the dashboard phase band, the harness
(--oem → --provision), and the tests all move together.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c196f8d12
commit
5f2f336aba
12 files changed
+263
-263
No files matched your search
@@ -118,4 +118,4 @@ rm -f /run/omarchy-install/state.json
|
||||
--encrypt-file /root/user_encrypt_installation.txt \
|
||||
--authorized-keys-file /root/authorized_keys \
|
||||
--tailscale-authkey-file /root/tailscale_authkey \
|
||||
--oem-file /root/oem
|
||||
--defer-provisioning-file /root/defer-provisioning
|
||||
@@ -75,13 +75,13 @@ notice() {
|
||||
keyboard_form() {
|
||||
# The Ctrl+C hint sits directly under the header (no blank line between),
|
||||
# matching the disk-overwrite screen's unencrypted offer. Ctrl+C on the first
|
||||
# screen is the hidden entry into OEM mode; $1 is "true" only for that first
|
||||
# invocation, so the review-loop re-edit can't flip a user install into OEM.
|
||||
local allow_oem="${1:-false}"
|
||||
# screen is the hidden entry into deferred provisioning; $1 is "true" only for that first
|
||||
# invocation, so the review-loop re-edit can't flip a user install into it.
|
||||
local allow_defer_provisioning="${1:-false}"
|
||||
clear_logo
|
||||
echo
|
||||
say "Let's setup your machine..."
|
||||
if $allow_oem; then
|
||||
if $allow_defer_provisioning; then
|
||||
say --foreground 8 "Press Ctrl+C to prepare this machine for another owner."
|
||||
fi
|
||||
echo
|
||||
@@ -139,9 +139,9 @@ Ukrainian|ua'
|
||||
choice=$(printf '%s\n' "$keyboards" | cut -d'|' -f1 | gum choose --height 10 --selected "English (US)" --header "Select keyboard layout")
|
||||
local status=$?
|
||||
|
||||
if $allow_oem && (( status == 130 )); then
|
||||
if $allow_defer_provisioning && (( status == 130 )); then
|
||||
if confirm_prepare_for_another_owner; then
|
||||
oem_install=true
|
||||
defer_provisioning=true
|
||||
return 0
|
||||
fi
|
||||
keyboard_form true # declined: back to the keyboard picker
|
||||
@@ -171,9 +171,9 @@ confirm_prepare_for_another_owner() {
|
||||
"Prepare this machine for another owner?"
|
||||
}
|
||||
|
||||
# The user step. OEM installs skip it entirely — the machine's first owner
|
||||
# The user step. Deferred-provisioning installs skip it entirely — the machine's first owner
|
||||
# picks their keyboard and creates their user in the first-boot setup, so the
|
||||
# OEM operator sets nothing user-specific.
|
||||
# operator sets nothing user-specific.
|
||||
|
||||
user_form() {
|
||||
step "Let's setup your user account..."
|
||||
@@ -386,14 +386,14 @@ wait_for_device() {
|
||||
|
||||
# Save the configurator answers the installer consumes: git identity and
|
||||
# encryption choice as plain files, account details as archinstall credentials.
|
||||
# OEM installs defer the user to first boot: no credentials, and on encrypted
|
||||
# Deferred-provisioning installs defer the user to first boot: no credentials, and on encrypted
|
||||
# targets the orchestrator generates a throwaway LUKS passphrase itself.
|
||||
write_user_files() {
|
||||
echo "$full_name" >user_full_name.txt
|
||||
echo "$email_address" >user_email_address.txt
|
||||
printf '%s\n' "$encrypt_installation" >user_encrypt_installation.txt
|
||||
|
||||
if $oem_install; then
|
||||
if $defer_provisioning; then
|
||||
cat <<-_EOF_ >user_credentials.json
|
||||
{
|
||||
"users": []
|
||||
@@ -895,7 +895,7 @@ confirm_disk_overwrite() {
|
||||
|
||||
# Decide what and how to install: sets install_target (full_disk|free_space),
|
||||
# install_target and encrypt_installation. Nothing is written to disk yet —
|
||||
# the user step runs between this and the execution below. (OEM mode never
|
||||
# the user step runs between this and the execution below. (deferred provisioning never
|
||||
# reaches here; it's armed by Ctrl+C on the first screen and goes straight to
|
||||
# disk selection.)
|
||||
select_installation() {
|
||||
@@ -939,15 +939,15 @@ select_installation() {
|
||||
done
|
||||
}
|
||||
|
||||
# STEP 1: KEYBOARD (Ctrl+C here arms OEM mode and jumps to disk selection)
|
||||
# STEP 1: KEYBOARD (Ctrl+C here arms deferred provisioning and jumps to disk selection)
|
||||
|
||||
oem_install=false
|
||||
defer_provisioning=false
|
||||
install_target="full_disk"
|
||||
|
||||
keyboard_form true
|
||||
|
||||
if $oem_install; then
|
||||
# OEM: no user setup, and the keyboard is deferred to the machine's first
|
||||
if $defer_provisioning; then
|
||||
# Deferred provisioning: no user setup, and the keyboard is deferred to the machine's first
|
||||
# boot. A defined default keymap so it boots before then; the owner picks
|
||||
# their real layout in the OOBE, which re-keys the console and, on encrypted
|
||||
# installs, rebuilds the initramfs so the LUKS prompt matches.
|
||||
@@ -961,7 +961,7 @@ if $oem_install; then
|
||||
timezone="UTC"
|
||||
|
||||
# Jump straight to disk selection + overwrite confirmation (encrypted by
|
||||
# default, Ctrl+C toggles unencrypted). OEM is always a full-disk install.
|
||||
# default, Ctrl+C toggles unencrypted). deferred provisioning is always a full-disk install.
|
||||
disk_form
|
||||
until confirm_disk_overwrite; do
|
||||
disk_form
|
||||
@@ -1005,9 +1005,9 @@ main_partition_size=$((disk_size_in_mib - main_partition_start - gpt_backup_rese
|
||||
kernel_choice=$(detect_kernel)
|
||||
|
||||
if [[ $encrypt_installation == true ]]; then
|
||||
# OEM installs carry no password: the orchestrator generates a throwaway
|
||||
# Deferred-provisioning installs carry no password: the orchestrator generates a throwaway
|
||||
# LUKS passphrase and stages it for the first-boot re-key.
|
||||
if $oem_install; then
|
||||
if $defer_provisioning; then
|
||||
encryption_password_line=""
|
||||
else
|
||||
encryption_password_line=",
|
||||
@@ -1037,7 +1037,7 @@ cat <<-_EOF_ >user_configuration.json
|
||||
"custom_commands": [],
|
||||
"omarchy_install": {
|
||||
"mode": "full_disk",
|
||||
"oem": $oem_install,
|
||||
"defer_provisioning": $defer_provisioning,
|
||||
"target_mount": "/mnt",
|
||||
"boot": {
|
||||
"esp_mount": "/boot",
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
# Anything less than the pair means this isn't an autoinstall drive, and the
|
||||
# caller falls back to the wizard.
|
||||
#
|
||||
# An `oem` marker file stands in for user_credentials.json: OEM installs defer
|
||||
# user creation to first boot, so imaging rigs ship no credentials at all.
|
||||
# A `defer-provisioning` marker file stands in for user_credentials.json: deferred-provisioning
|
||||
# installs defer user creation to first boot, so imaging rigs ship no credentials.
|
||||
#
|
||||
# The optional argument prefixes every path the script touches; tests use it to
|
||||
# run against a sandbox instead of the live system.
|
||||
@@ -39,17 +39,17 @@ mkdir -p "$mnt"
|
||||
mount -o ro "$device" "$mnt" || exit 1
|
||||
|
||||
# Clear any inputs a previous load in this live session left in /root before
|
||||
# copying the current drive: a stale `oem` marker or credentials file would
|
||||
# otherwise force a fresh drive's config back into OEM mode or hand it an old
|
||||
# copying the current drive: a stale `defer-provisioning` marker or credentials file would
|
||||
# otherwise force a fresh drive back into deferred provisioning or hand it an old
|
||||
# LUKS passphrase. The interactive wizard writes these same names, but a
|
||||
# cidata load always precedes it, so nothing of the user's is discarded here.
|
||||
optional_inputs=(user_credentials.json oem user_full_name.txt user_email_address.txt user_encrypt_installation.txt authorized_keys tailscale_authkey)
|
||||
optional_inputs=(user_credentials.json defer-provisioning user_full_name.txt user_email_address.txt user_encrypt_installation.txt authorized_keys tailscale_authkey)
|
||||
for file in user_configuration.json "${optional_inputs[@]}"; do
|
||||
rm -f "$dest/$file"
|
||||
done
|
||||
|
||||
loaded=1
|
||||
if [[ -f $mnt/user_configuration.json ]] && [[ -f $mnt/user_credentials.json || -f $mnt/oem ]]; then
|
||||
if [[ -f $mnt/user_configuration.json ]] && [[ -f $mnt/user_credentials.json || -f $mnt/defer-provisioning ]]; then
|
||||
files=("$mnt"/user_configuration.json)
|
||||
for file in "${optional_inputs[@]}"; do
|
||||
[[ -f $mnt/$file ]] && files+=("$mnt/$file")
|
||||
|
||||
@@ -321,7 +321,7 @@ install_progress() {
|
||||
"Installing Arch + Omarchy") lo=65 hi=700 tau=150 pkg_signal=1 ;;
|
||||
"Configuring hibernation") lo=700 hi=720 tau=10 ;;
|
||||
"Configuring system") lo=720 hi=850 tau=120 ;;
|
||||
"Staging OEM state") lo=850 hi=855 tau=5 ;;
|
||||
"Staging provisioning") lo=850 hi=855 tau=5 ;;
|
||||
"Finalizing Limine boot") lo=855 hi=950 tau=90 ;;
|
||||
"Finalizing user") lo=950 hi=978 tau=30 ;;
|
||||
"Configuring login") lo=978 hi=988 tau=5 ;;
|
||||
|
||||
@@ -18,7 +18,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--encrypt-file) export OMARCHY_INSTALL_ENCRYPT_FILE="$2"; shift 2 ;;
|
||||
--authorized-keys-file) export OMARCHY_INSTALL_AUTHORIZED_KEYS_FILE="$2"; shift 2 ;;
|
||||
--tailscale-authkey-file) export OMARCHY_INSTALL_TAILSCALE_AUTHKEY_FILE="$2"; shift 2 ;;
|
||||
--oem-file) export OMARCHY_INSTALL_OEM_FILE="$2"; shift 2 ;;
|
||||
--defer-provisioning-file) export OMARCHY_INSTALL_DEFER_PROVISIONING_FILE="$2"; shift 2 ;;
|
||||
*) echo "omarchy-iso-install: unknown arg $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -26,7 +26,7 @@ class InstallContext:
|
||||
user_credentials: dict
|
||||
arch_config_path: Path
|
||||
omarchy_install: dict[str, Any]
|
||||
oem: bool = False
|
||||
defer_provisioning: bool = False
|
||||
|
||||
target: Path = Path("/mnt")
|
||||
omarchy_path: Path = Path("/usr/share/omarchy")
|
||||
@@ -50,23 +50,23 @@ class InstallContext:
|
||||
user_configuration = json.loads(config_path.read_text())
|
||||
omarchy_install = user_configuration.get("omarchy_install") or _default_omarchy_install(user_configuration)
|
||||
|
||||
# OEM mode: the whole system installs but user creation is deferred to
|
||||
# first boot. Selected by the configurator (omarchy_install.oem) or by
|
||||
# an `oem` marker file on an autoinstall drive, which also replaces the
|
||||
# Deferred provisioning: the whole system installs but user creation is deferred to
|
||||
# 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
|
||||
# user_credentials.json requirement.
|
||||
oem_marker = _optional_path(os.environ.get("OMARCHY_INSTALL_OEM_FILE"))
|
||||
oem = bool(omarchy_install.get("oem")) or oem_marker is not None
|
||||
omarchy_install["oem"] = oem
|
||||
defer_provisioning_marker = _optional_path(os.environ.get("OMARCHY_INSTALL_DEFER_PROVISIONING_FILE"))
|
||||
defer_provisioning = bool(omarchy_install.get("defer_provisioning")) or defer_provisioning_marker is not None
|
||||
omarchy_install["defer_provisioning"] = defer_provisioning
|
||||
|
||||
if creds_path.exists():
|
||||
user_credentials = json.loads(creds_path.read_text())
|
||||
elif oem:
|
||||
elif defer_provisioning:
|
||||
user_credentials = {"users": []}
|
||||
else:
|
||||
raise RuntimeError(f"credentials file missing: {creds_path}")
|
||||
|
||||
if oem:
|
||||
# OEM promises no account exists until first boot. A supplied
|
||||
if defer_provisioning:
|
||||
# Deferred provisioning promises no account until first boot. A supplied
|
||||
# credentials file (a rig handing over its LUKS passphrase) must
|
||||
# not smuggle in users or a root password.
|
||||
encryption_password = user_credentials.get("encryption_password")
|
||||
@@ -77,7 +77,7 @@ class InstallContext:
|
||||
arch_configuration = dict(user_configuration)
|
||||
arch_configuration.pop("omarchy_install", None)
|
||||
|
||||
if oem:
|
||||
if defer_provisioning:
|
||||
# The userless invariant covers the archinstall config too: a
|
||||
# combined/legacy config could carry account authentication that
|
||||
# archinstall would still act on (users, a root password). Strip
|
||||
@@ -87,14 +87,14 @@ class InstallContext:
|
||||
state_dir = Path(os.environ.get("OMARCHY_INSTALL_STATE_DIR", "/run/omarchy-install"))
|
||||
state_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if oem:
|
||||
# Encrypted OEM installs have no user password to protect LUKS
|
||||
if defer_provisioning:
|
||||
# Encrypted deferred-provisioning installs have no user password to protect LUKS
|
||||
# with. Generate a throwaway passphrase (staged for first-boot
|
||||
# re-key by the stage_oem_state phase) and hand it to archinstall
|
||||
# re-key by the stage_provisioning_state phase) and hand it to archinstall
|
||||
# through both places it may look: the disk_encryption block and
|
||||
# the credentials file.
|
||||
_inject_oem_encryption_password(arch_configuration, user_credentials)
|
||||
creds_path = state_dir / "oem-user_credentials.json"
|
||||
_inject_provisioning_encryption_password(arch_configuration, user_credentials)
|
||||
creds_path = state_dir / "provisioning-user_credentials.json"
|
||||
creds_path.write_text(json.dumps(user_credentials, indent=2) + "\n")
|
||||
creds_path.chmod(0o600)
|
||||
|
||||
@@ -113,7 +113,7 @@ class InstallContext:
|
||||
user_credentials=user_credentials,
|
||||
arch_config_path=arch_config_path,
|
||||
omarchy_install=omarchy_install,
|
||||
oem=oem,
|
||||
defer_provisioning=defer_provisioning,
|
||||
state_dir=state_dir,
|
||||
)
|
||||
disk_config = user_configuration.get("disk_config", {})
|
||||
@@ -127,7 +127,7 @@ class InstallContext:
|
||||
users = self.user_credentials.get("users") or []
|
||||
if users:
|
||||
return users[0]["username"]
|
||||
if self.oem:
|
||||
if self.defer_provisioning:
|
||||
return ""
|
||||
raise RuntimeError("user_credentials.json contains no users")
|
||||
|
||||
@@ -145,7 +145,7 @@ class InstallContext:
|
||||
|
||||
def _strip_account_fields(arch_configuration: dict) -> None:
|
||||
"""Remove every field archinstall reads to create users or set a root
|
||||
password, so an OEM install cannot ship a hidden provisioning account.
|
||||
password, so it cannot ship a hidden provisioning account.
|
||||
Encryption material lives elsewhere (disk_config.disk_encryption) and is
|
||||
untouched."""
|
||||
for key in ("!users", "!root-password", "root_enc_password", "users"):
|
||||
@@ -157,12 +157,12 @@ def _strip_account_fields(arch_configuration: dict) -> None:
|
||||
auth.pop(key, None)
|
||||
|
||||
|
||||
def _inject_oem_encryption_password(arch_configuration: dict, user_credentials: dict) -> None:
|
||||
"""Ensure an encrypted OEM install has a LUKS passphrase archinstall can
|
||||
def _inject_provisioning_encryption_password(arch_configuration: dict, user_credentials: dict) -> None:
|
||||
"""Ensure an encrypted deferred-provisioning install has a LUKS passphrase archinstall can
|
||||
use. Reuse one the input already carries (an autoinstall rig may supply
|
||||
its own); otherwise generate a throwaway. Mutates the disk_encryption
|
||||
block in place — arch_configuration shares it with user_configuration, so
|
||||
the stage_oem_state phase can read the effective passphrase back."""
|
||||
the stage_provisioning_state phase can read the effective passphrase back."""
|
||||
disk_encryption = (arch_configuration.get("disk_config") or {}).get("disk_encryption")
|
||||
if disk_encryption is None:
|
||||
return
|
||||
|
||||
@@ -34,7 +34,7 @@ def build_phases(ctx: InstallContext):
|
||||
arch_install_system,
|
||||
configure_hibernation,
|
||||
run_system_finalizer,
|
||||
stage_oem_state,
|
||||
stage_provisioning_state,
|
||||
finalize_limine_boot,
|
||||
run_chroot_finalizer,
|
||||
configure_dns_resolver,
|
||||
@@ -51,9 +51,9 @@ def build_phases(ctx: InstallContext):
|
||||
("Installing Arch + Omarchy", arch_install_system),
|
||||
("Configuring hibernation", configure_hibernation),
|
||||
("Configuring system", run_system_finalizer),
|
||||
# Before finalize_limine_boot: the OEM cryptkey drop-in and keyfile
|
||||
# Before finalize_limine_boot: the deferred-provisioning cryptkey drop-in and keyfile
|
||||
# must be in place for the final UKI build.
|
||||
("Staging OEM state", stage_oem_state),
|
||||
("Staging provisioning", stage_provisioning_state),
|
||||
("Finalizing Limine boot", finalize_limine_boot),
|
||||
("Finalizing user", run_chroot_finalizer),
|
||||
("Configuring login", configure_login),
|
||||
@@ -72,7 +72,7 @@ def main() -> int:
|
||||
error(f"Configuration error: {e}")
|
||||
return 2
|
||||
|
||||
who = ctx.username or "OEM (user created at first boot)"
|
||||
who = ctx.username or "deferred provisioning (user created at first boot)"
|
||||
info(f"Installing Omarchy for {who} → {ctx.target}")
|
||||
|
||||
from .phases_impl import (
|
||||
|
||||
@@ -1136,8 +1136,8 @@ def _run_target_setup_command(ctx: InstallContext, cmd: list[str], *, user: str
|
||||
|
||||
|
||||
def run_system_finalizer(ctx: InstallContext) -> None:
|
||||
if ctx.oem:
|
||||
cmd = ["/usr/bin/omarchy-setup-system", "--oem", "--first-install"]
|
||||
if ctx.defer_provisioning:
|
||||
cmd = ["/usr/bin/omarchy-setup-system", "--defer-provisioning", "--first-install"]
|
||||
else:
|
||||
cmd = ["/usr/bin/omarchy-setup-system", "--install-user", ctx.username, "--first-install"]
|
||||
|
||||
@@ -1149,66 +1149,66 @@ def run_system_finalizer(ctx: InstallContext) -> None:
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# stage_oem_state: produce the on-disk "OEM state" the runtime's first-boot
|
||||
# setup (omarchy-oem-setup) and factory reset (omarchy-system-factory-reset) consume.
|
||||
# stage_provisioning_state: produce the on-disk "provisioning state" the runtime's first-boot
|
||||
# setup (omarchy-provision-owner) and factory reset (omarchy-system-factory-reset) consume.
|
||||
#
|
||||
# Every install stashes the bundled Node tarball in /var/lib/omarchy/oem/
|
||||
# so a later factory reset can finalize the new owner's user offline. OEM-mode
|
||||
# Every install stashes the bundled Node tarball in /var/lib/omarchy/provisioning/
|
||||
# so a later factory reset can finalize the new owner's user offline. deferred-provisioning
|
||||
# installs additionally arm the first-boot setup service and, on encrypted
|
||||
# targets, stage the throwaway LUKS passphrase: the keyfile embedded in the
|
||||
# initramfs auto-unlocks boot during the OEM window, and first-boot setup
|
||||
# initramfs auto-unlocks boot during the provisioning window, and first-boot setup
|
||||
# re-keys the volume to the owner's password and removes it.
|
||||
#
|
||||
# Runs before finalize_limine_boot so the cryptkey cmdline drop-in and the
|
||||
# keyfile land in the final UKI build.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
OEM_STATE_DIR = "var/lib/omarchy/oem"
|
||||
OEM_KEYFILE = "etc/omarchy/oem.key"
|
||||
PROVISION_STATE_DIR = "var/lib/omarchy/provisioning"
|
||||
PROVISION_KEYFILE = "etc/omarchy/provisioning.key"
|
||||
NODE_PACKAGES_DIR = Path("/opt/packages")
|
||||
|
||||
|
||||
def stage_oem_state(ctx: InstallContext) -> None:
|
||||
def stage_provisioning_state(ctx: InstallContext) -> None:
|
||||
# World-readable: first-boot finalization reads the Node tarball as the
|
||||
# new user. The only secret inside (luks-key) is itself 0600 root.
|
||||
oem_dir = ctx.target / OEM_STATE_DIR
|
||||
oem_dir.mkdir(parents=True, exist_ok=True)
|
||||
oem_dir.chmod(0o755)
|
||||
provisioning_dir = ctx.target / PROVISION_STATE_DIR
|
||||
provisioning_dir.mkdir(parents=True, exist_ok=True)
|
||||
provisioning_dir.chmod(0o755)
|
||||
|
||||
_stage_node_tarball(ctx, oem_dir)
|
||||
_stage_node_tarball(ctx, provisioning_dir)
|
||||
|
||||
if not ctx.oem:
|
||||
if not ctx.defer_provisioning:
|
||||
return
|
||||
|
||||
service_src = ctx.target / "usr/share/omarchy/install/oem/omarchy-oem-setup.service"
|
||||
setup_bin = ctx.target / "usr/bin/omarchy-oem-setup"
|
||||
service_src = ctx.target / "usr/share/omarchy/install/provisioning/omarchy-provision-owner.service"
|
||||
setup_bin = ctx.target / "usr/bin/omarchy-provision-owner"
|
||||
if not service_src.exists() or not setup_bin.exists():
|
||||
raise RuntimeError(
|
||||
"OEM install requested, but the installed Omarchy runtime does not ship "
|
||||
"first-boot setup (omarchy-oem-setup + install/oem/omarchy-oem-setup.service). "
|
||||
"Update the runtime package this ISO bundles before installing in OEM mode."
|
||||
"deferred-provisioning install requested, but the installed Omarchy runtime does not ship "
|
||||
"first-boot setup (omarchy-provision-owner + install/provisioning/omarchy-provision-owner.service). "
|
||||
"Update the runtime package this ISO bundles before installing in deferred provisioning."
|
||||
)
|
||||
|
||||
info("› arming first-boot OEM setup")
|
||||
(oem_dir / "pending").touch()
|
||||
info("› arming first-boot setup")
|
||||
(provisioning_dir / "pending").touch()
|
||||
|
||||
unit_dst = ctx.target / "etc/systemd/system/omarchy-oem-setup.service"
|
||||
unit_dst = ctx.target / "etc/systemd/system/omarchy-provision-owner.service"
|
||||
unit_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(service_src, unit_dst)
|
||||
wants_dir = ctx.target / "etc/systemd/system/multi-user.target.wants"
|
||||
wants_dir.mkdir(parents=True, exist_ok=True)
|
||||
link = wants_dir / "omarchy-oem-setup.service"
|
||||
link = wants_dir / "omarchy-provision-owner.service"
|
||||
link.unlink(missing_ok=True)
|
||||
link.symlink_to("/etc/systemd/system/omarchy-oem-setup.service")
|
||||
link.symlink_to("/etc/systemd/system/omarchy-provision-owner.service")
|
||||
|
||||
if _oem_install_encrypted(ctx):
|
||||
_stage_oem_luks_unlock(ctx, oem_dir)
|
||||
if _provision_install_encrypted(ctx):
|
||||
_stage_provisioning_luks_unlock(ctx, provisioning_dir)
|
||||
|
||||
|
||||
def _stage_node_tarball(ctx: InstallContext, oem_dir) -> None:
|
||||
def _stage_node_tarball(ctx: InstallContext, provisioning_dir) -> None:
|
||||
tarballs = sorted(NODE_PACKAGES_DIR.glob("node-v*-linux-x64.tar.gz"))
|
||||
if not tarballs:
|
||||
# Hard error on every install, not just OEM: the stash is what lets a
|
||||
# Hard error on every install, not just deferred-provisioning installs: the stash is what lets a
|
||||
# later factory reset finalize the next owner offline, and an ISO
|
||||
# build always bundles the tarball — its absence means a broken build.
|
||||
raise RuntimeError(
|
||||
@@ -1216,7 +1216,7 @@ def _stage_node_tarball(ctx: InstallContext, oem_dir) -> None:
|
||||
"and factory reset could not finalize a user offline"
|
||||
)
|
||||
|
||||
packages_dir = oem_dir / "packages"
|
||||
packages_dir = provisioning_dir / "packages"
|
||||
packages_dir.mkdir(parents=True, exist_ok=True)
|
||||
target_tarball = packages_dir / tarballs[0].name
|
||||
if not target_tarball.exists():
|
||||
@@ -1224,7 +1224,7 @@ def _stage_node_tarball(ctx: InstallContext, oem_dir) -> None:
|
||||
shutil.copy2(tarballs[0], target_tarball)
|
||||
|
||||
|
||||
def _oem_install_encrypted(ctx: InstallContext) -> bool:
|
||||
def _provision_install_encrypted(ctx: InstallContext) -> bool:
|
||||
if _storage_intent(ctx).get("luks_uuid"):
|
||||
return True
|
||||
disk_encryption = (ctx.user_configuration.get("disk_config") or {}).get("disk_encryption")
|
||||
@@ -1233,43 +1233,43 @@ def _oem_install_encrypted(ctx: InstallContext) -> bool:
|
||||
return ctx.encrypt
|
||||
|
||||
|
||||
def _oem_encryption_password(ctx: InstallContext) -> str | None:
|
||||
def _provision_encryption_password(ctx: InstallContext) -> str | None:
|
||||
disk_encryption = (ctx.user_configuration.get("disk_config") or {}).get("disk_encryption") or {}
|
||||
return disk_encryption.get("encryption_password") or ctx.user_credentials.get("encryption_password")
|
||||
|
||||
|
||||
def _stage_oem_luks_unlock(ctx: InstallContext, oem_dir) -> None:
|
||||
password = _oem_encryption_password(ctx)
|
||||
def _stage_provisioning_luks_unlock(ctx: InstallContext, provisioning_dir) -> None:
|
||||
password = _provision_encryption_password(ctx)
|
||||
if not password:
|
||||
# Full-disk OEM installs get a generated passphrase injected by
|
||||
# Full-disk deferred-provisioning installs get a generated passphrase injected by
|
||||
# InstallContext; only a pre-mounted (rig-partitioned) LUKS target can
|
||||
# land here, and it must hand over the passphrase it formatted with.
|
||||
raise RuntimeError(
|
||||
"OEM install on a pre-encrypted target requires the LUKS passphrase "
|
||||
"deferred-provisioning install on a pre-encrypted target requires the LUKS passphrase "
|
||||
"in user_credentials.json (encryption_password) so first boot can re-key"
|
||||
)
|
||||
|
||||
info("› staging LUKS auto-unlock for the OEM window")
|
||||
info("› staging LUKS auto-unlock for the provisioning window")
|
||||
|
||||
# Byte-for-byte the slot passphrase: no trailing newline anywhere.
|
||||
luks_key = oem_dir / "luks-key"
|
||||
luks_key = provisioning_dir / "luks-key"
|
||||
luks_key.write_text(password)
|
||||
luks_key.chmod(0o600)
|
||||
|
||||
keyfile = ctx.target / OEM_KEYFILE
|
||||
keyfile = ctx.target / PROVISION_KEYFILE
|
||||
keyfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
keyfile.write_text(password)
|
||||
keyfile.chmod(0o600)
|
||||
|
||||
cmdline_dropin = ctx.target / "etc/limine-entry-tool.d/99-omarchy-oem-unlock.conf"
|
||||
cmdline_dropin = ctx.target / "etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf"
|
||||
cmdline_dropin.parent.mkdir(parents=True, exist_ok=True)
|
||||
cmdline_dropin.write_text(
|
||||
'KERNEL_CMDLINE[default]+=" cryptkey=rootfs:/etc/omarchy/oem.key"\n'
|
||||
'KERNEL_CMDLINE[default]+=" cryptkey=rootfs:/etc/omarchy/provisioning.key"\n'
|
||||
)
|
||||
|
||||
files_dropin = ctx.target / "etc/mkinitcpio.conf.d/99-omarchy-oem-key.conf"
|
||||
files_dropin = ctx.target / "etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf"
|
||||
files_dropin.parent.mkdir(parents=True, exist_ok=True)
|
||||
files_dropin.write_text("FILES+=(/etc/omarchy/oem.key)\n")
|
||||
files_dropin.write_text("FILES+=(/etc/omarchy/provisioning.key)\n")
|
||||
|
||||
|
||||
def finalize_limine_boot(ctx: InstallContext) -> None:
|
||||
@@ -1365,8 +1365,8 @@ def _limine_kernel_cmdline(config_text: str) -> str:
|
||||
|
||||
|
||||
def run_chroot_finalizer(ctx: InstallContext) -> None:
|
||||
if ctx.oem:
|
||||
info("› OEM install: user finalization deferred to first boot")
|
||||
if ctx.defer_provisioning:
|
||||
info("› deferred-provisioning install: user finalization deferred to first boot")
|
||||
return
|
||||
|
||||
_run_target_setup_command(
|
||||
@@ -1415,18 +1415,18 @@ def configure_login(ctx: InstallContext) -> None:
|
||||
)
|
||||
|
||||
autologin_conf = sddm_dir / "autologin.conf"
|
||||
if ctx.encrypt and not ctx.oem:
|
||||
if ctx.encrypt and not ctx.defer_provisioning:
|
||||
autologin_conf.write_text(
|
||||
"[Autologin]\n"
|
||||
f"User={ctx.username}\n"
|
||||
"Session=omarchy.desktop\n"
|
||||
)
|
||||
else:
|
||||
# OEM installs have no user yet; omarchy-oem-setup writes autologin
|
||||
# deferred-provisioning installs have no user yet; omarchy-provision-owner writes autologin
|
||||
# and SDDM state at first boot once the owner exists.
|
||||
autologin_conf.unlink(missing_ok=True)
|
||||
|
||||
if not ctx.oem:
|
||||
if not ctx.defer_provisioning:
|
||||
state_dir = ctx.target / "var" / "lib" / "sddm"
|
||||
state_dir.mkdir(parents=True, exist_ok=True)
|
||||
(state_dir / "state.conf").write_text(
|
||||
@@ -1460,14 +1460,14 @@ def configure_ssh_access(ctx: InstallContext) -> None:
|
||||
|
||||
keys = _authorized_keys(ctx.authorized_keys_path)
|
||||
|
||||
if ctx.oem:
|
||||
# No user to authorize yet. Stage the keys in OEM state for
|
||||
# omarchy-oem-setup to install once first boot creates the owner, and
|
||||
if ctx.defer_provisioning:
|
||||
# No user to authorize yet. Stage the keys in provisioning state for
|
||||
# omarchy-provision-owner to install once first boot creates the owner, and
|
||||
# still open the door (sshd + ufw) below.
|
||||
info(f"› staging {len(keys)} SSH key(s) for the first-boot user")
|
||||
oem_dir = ctx.target / OEM_STATE_DIR
|
||||
oem_dir.mkdir(parents=True, exist_ok=True)
|
||||
staged = oem_dir / "authorized_keys"
|
||||
provisioning_dir = ctx.target / PROVISION_STATE_DIR
|
||||
provisioning_dir.mkdir(parents=True, exist_ok=True)
|
||||
staged = provisioning_dir / "authorized_keys"
|
||||
staged.write_text("".join(f"{key}\n" for key in keys))
|
||||
staged.chmod(0o600)
|
||||
else:
|
||||
@@ -1695,33 +1695,33 @@ def validate_boot(ctx: InstallContext) -> None:
|
||||
if ctx.is_protected:
|
||||
_validate_pre_mounted_filesystems(ctx)
|
||||
|
||||
if ctx.oem:
|
||||
_validate_oem_state(ctx)
|
||||
if ctx.defer_provisioning:
|
||||
_validate_provisioning_state(ctx)
|
||||
|
||||
|
||||
def _validate_oem_state(ctx: InstallContext) -> None:
|
||||
"""An OEM install that boots without a working first-boot setup is a
|
||||
def _validate_provisioning_state(ctx: InstallContext) -> None:
|
||||
"""An deferred-provisioning install that boots without a working first-boot setup is a
|
||||
user-less brick; insist the armed state is complete before reboot."""
|
||||
oem_dir = ctx.target / OEM_STATE_DIR
|
||||
if not (oem_dir / "pending").exists():
|
||||
raise RuntimeError(f"OEM install but {oem_dir / 'pending'} is missing")
|
||||
provisioning_dir = ctx.target / PROVISION_STATE_DIR
|
||||
if not (provisioning_dir / "pending").exists():
|
||||
raise RuntimeError(f"deferred-provisioning install but {provisioning_dir / 'pending'} is missing")
|
||||
|
||||
link = ctx.target / "etc/systemd/system/multi-user.target.wants/omarchy-oem-setup.service"
|
||||
link = ctx.target / "etc/systemd/system/multi-user.target.wants/omarchy-provision-owner.service"
|
||||
if not link.is_symlink():
|
||||
raise RuntimeError("OEM install but omarchy-oem-setup.service is not enabled")
|
||||
raise RuntimeError("deferred-provisioning install but omarchy-provision-owner.service is not enabled")
|
||||
|
||||
if not list((oem_dir / "packages").glob("node-v*.tar.gz")):
|
||||
raise RuntimeError(f"OEM install but no Node tarball staged in {oem_dir / 'packages'}")
|
||||
if not list((provisioning_dir / "packages").glob("node-v*.tar.gz")):
|
||||
raise RuntimeError(f"deferred-provisioning install but no Node tarball staged in {provisioning_dir / 'packages'}")
|
||||
|
||||
if _oem_install_encrypted(ctx):
|
||||
for required in (oem_dir / "luks-key", ctx.target / OEM_KEYFILE):
|
||||
if _provision_install_encrypted(ctx):
|
||||
for required in (provisioning_dir / "luks-key", ctx.target / PROVISION_KEYFILE):
|
||||
if not required.exists():
|
||||
raise RuntimeError(f"encrypted OEM install but {required} is missing")
|
||||
raise RuntimeError(f"encrypted deferred-provisioning install but {required} is missing")
|
||||
limine_conf_text = (
|
||||
ctx.target / _boot_intent(ctx)["esp_mount"].lstrip("/") / "limine.conf"
|
||||
).read_text()
|
||||
if "cryptkey=rootfs:" not in limine_conf_text:
|
||||
raise RuntimeError("encrypted OEM install but limine.conf has no cryptkey= for auto-unlock")
|
||||
raise RuntimeError("encrypted deferred-provisioning install but limine.conf has no cryptkey= for auto-unlock")
|
||||
|
||||
|
||||
def _assert_boot_hooks_restored(ctx: InstallContext) -> None:
|
||||
@@ -1835,15 +1835,15 @@ def create_factory_snapshot(ctx: InstallContext) -> None:
|
||||
# Provisioning credentials staged for THIS deployment's first boot must not
|
||||
# survive into the factory image: a reset years later would otherwise hand the
|
||||
# next owner the original deployment's SSH keys or rejoin its tailnet, and a
|
||||
# stale OEM LUKS key (dead after the first re-key) has no business lingering.
|
||||
# stale LUKS key (dead after the first re-key) has no business lingering.
|
||||
# The mkinitcpio/cmdline drop-ins go with the keyfile — a reset rebuild would
|
||||
# otherwise fail on FILES pointing at a scrubbed path.
|
||||
FACTORY_SCRUB_PATHS = (
|
||||
"var/lib/omarchy/oem/authorized_keys",
|
||||
"var/lib/omarchy/oem/luks-key",
|
||||
"etc/omarchy/oem.key",
|
||||
"etc/limine-entry-tool.d/99-omarchy-oem-unlock.conf",
|
||||
"etc/mkinitcpio.conf.d/99-omarchy-oem-key.conf",
|
||||
"var/lib/omarchy/provisioning/authorized_keys",
|
||||
"var/lib/omarchy/provisioning/luks-key",
|
||||
"etc/omarchy/provisioning.key",
|
||||
"etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf",
|
||||
"etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf",
|
||||
"etc/tailscale/authkey",
|
||||
"etc/systemd/system/omarchy-tailscale-join.service",
|
||||
"etc/systemd/system/multi-user.target.wants/omarchy-tailscale-join.service",
|
||||
|
||||
Reference in new issue
Block a user