feat(installer): sshd opt-in, gsettings replay, live-medium motd stamp
Tests / tests (push) Successful in 2m50s
Tests / tests (push) Successful in 2m50s
- alvin-install: new sshd screen (default on) + install step. openssh is guaranteed into the pacstrap list when requested, and the final banner reports whether the enable actually succeeded (SSHD_APPLIED) instead of what was selected. The prompt warns that password login uses the same password as LUKS, and only mentions the GitHub key import when a username was given. - alvin-install: replay the `gsettings set` commands end-4 logged during its setup run with a clean env and a throwaway session bus as the target user, then read each key back in a second session to confirm it persisted (gvariant_unescape normalizes both '...' and "..." GVariant output). The venv check now walks the uv bin/python symlink chain, re-rooting absolute hops under $TARGET, since that link dangles on the live medium. - build: append the build stamp to the live /etc/motd (keeps the installed system's motd untouched) and drop alvin-install's install_motd step. - docs: installer sections in README/AGENTS.md, sshd notes, corrected line counts.
This commit is contained in:
@@ -54,13 +54,50 @@ install them into the target without the network.
|
||||
| `bootstrap_packages` | Minimal build-time packages (`base`, `arch-install-scripts`) |
|
||||
| `airootfs/root/customize_airootfs.sh` | Runs inside chroot during build: keyrings, skel dotfiles (incl. end4-pC), qsConfig patch, end-4 `install-deps`, package harvest, uv/font pre-warm |
|
||||
| `airootfs/root/.live-pacman.conf` | `/etc/pacman.conf` of the built image (adds `[local-aur]` = `file:///opt/localrepo`) |
|
||||
| `airootfs/usr/local/bin/alvin-install` | Installer: partitions, pacstrap, LUKS (systemd initramfs **or** experimental unl0kr + optional TPM2 unlock), user, git identity + GitHub SSH keys, login manager (none/greetd/sddm/gdm) + autologin, end-4 dotfiles (`--skip-alldeps`), build-stamp motd, boot |
|
||||
| `airootfs/usr/local/bin/alvin-install` | Installer: partitions, pacstrap, LUKS (systemd initramfs **or** experimental unl0kr + optional TPM2 unlock), user, git identity + GitHub SSH keys, login manager (none/greetd/sddm/gdm) + autologin, optional sshd (default on), end-4 dotfiles (`--skip-alldeps`), boot |
|
||||
| `localrepo/build-end4-deps.sh` | Non-interactive AUR builds for end-4 dependencies |
|
||||
| `localrepo/update.sh` | Regenerates localrepo DB, syncs names to packages.x86_64, syncs `airootfs/opt/localrepo/` |
|
||||
|
||||
## alvin-install (installer inside the ISO)
|
||||
|
||||
Source: `airootfs/usr/local/bin/alvin-install` (~1,880 lines, **no `.sh` extension**).
|
||||
The airootfs overlay ships it as `/usr/local/bin/alvin-install` (mode `0755` forced in
|
||||
`profiledef.sh` `file_permissions`), and `/etc/motd` advertises it on the live medium.
|
||||
|
||||
- **Invoke**: as root from the live session. Hard requirements: `gum` and UEFI —
|
||||
`check_prereqs` exits if `/sys/firmware/efi` is missing. Log: `/var/log/alvin-install.log`
|
||||
(copied into the target at the end; `ERR` trap prints the failing line).
|
||||
- **Structure**: `main()` runs 16 `screen_*` TUI functions (collect answers: welcome, keymap,
|
||||
network, disk, luks, unl0kr, filesystem, username, password, hostname, git, timezone,
|
||||
locale, login manager, sshd, summary) then a fixed order of `install_*` steps
|
||||
(partition → LUKS → format/mount → pacstrap → fstab → repos → timezone → locale →
|
||||
hostname → network → sshd → user → git/SSH → end-4 dots → mkinitcpio → bootloader →
|
||||
TPM/unl0kr → login manager → autologin → cleanup). Screens and install steps are
|
||||
independent — edit one without
|
||||
touching the other. An `EXIT` trap unmounts `$TARGET` and closes `cryptroot` on any exit.
|
||||
- **Package list**: `install_pacstrap` builds the *installed* system's list from the medium's
|
||||
`/run/archiso/bootmnt/arch/pkglist.x86_64.txt` (i.e. `pacman -Q` of the live ISO), so
|
||||
`packages.x86_64` changes flow into installed systems. It drops `PKGS_EXCLUDE`
|
||||
(`archiso mkinitcpio-archiso mkinitcpio-nfs-utils syslinux memtest86+`) plus names not
|
||||
resolvable in the live repos, then always appends hardcoded essentials
|
||||
(`base linux linux-firmware linux-cachyos networkmanager grub efibootmgr sudo git …`).
|
||||
- **CI never lints it**: the shellcheck step globs only `*.sh`. Check manually after edits:
|
||||
`shellcheck -x -s bash airootfs/usr/local/bin/alvin-install`
|
||||
- end-4 quirks (`--skip-alldeps`, `yes i`, end4-pC re-copy) are in [Gotchas](#gotchas) below.
|
||||
|
||||
## Verify a single change locally (mirrors CI)
|
||||
|
||||
```bash
|
||||
shellcheck -x -s bash <script>.sh # plus the alvin-install command above
|
||||
yamllint -d "{extends: relaxed, rules: {document-start: disable, line-length: disable, new-line-at-end-of-file: disable}}" <file>.yaml
|
||||
```
|
||||
|
||||
`packages.x86_64` syntax: one package per line, `[a-zA-Z0-9._@+\-]+` only, no trailing
|
||||
whitespace.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- The profile root is the **repo root** (not a `releng/` subdirectory). The CI build-iso workflow uses `mkarchiso -v -w work -o out/ releng` but this is a naming convention — the actual profile files live at the repo root.
|
||||
- The profile root is the **repo root** (not a `releng/` subdirectory). CI builds with `mkarchiso -v -w "$PWD/work" -o "$PWD/out" -- "$PWD"` (`.gitea/workflows/build-iso.yaml`), and the local build command above does the same — never pass a `releng` path.
|
||||
- `customize_airootfs.sh` clones git repos (oh-my-zsh, lazy.nvim, plugins, end-4 dots) into `/etc/skel` and `/opt` during the ISO build. These require network access at build time but make the live ISO work offline.
|
||||
- `customize_airootfs.sh` runs `./setup install-deps -f -s --skip-plasmaintg` as the throwaway user `ii-builder` (end-4's scripts refuse to run as root) and aborts the build if it fails. `--skip-plasmaintg` is deliberate: that optional Firefox widget pulls 410 packages / 526 MiB of KDE.
|
||||
- The `[local-aur]` repo path in `pacman.conf` (`file:///home/alvin/alvinarch/localrepo`) is for local builds only. The CI does not use this path.
|
||||
@@ -72,4 +109,4 @@ install them into the target without the network.
|
||||
- End-4 metapackages are built from `localrepo/end4-pkgbuilds/` (quickshell-git, bibata-modern-classic-bin, microtex-git).
|
||||
- `packages.x86_64` is sorted alphabetically. Keep it sorted when adding/removing entries.
|
||||
- `airootfs/opt/localrepo/` is a build artifact (gitignored) — never commit it.
|
||||
- **Build stamp flow**: `profiledef.sh` (sourced by mkarchiso) stages the gitignored `airootfs/root/.iso-build-stamp` when a `.git` dir exists **or** `CI`/`GITEA_ACTIONS` is set — line reads `AlvinArch build <date> | commit <date> | <4-char hash>` (falls back to `commit unknown` if `git -c safe.directory` can't read the repo; without `.git`/CI no file is staged). `customize_airootfs.sh` installs it as `/etc/alvin-build-stamp` on the live medium, and `alvin-install`'s `install_motd` copies the live `/etc/motd` banner to the target and appends the stamp. If the medium has no stamp, the installed system gets no motd from us.
|
||||
- **Build stamp flow**: `profiledef.sh` (sourced by mkarchiso) stages the gitignored `airootfs/root/.iso-build-stamp` when a `.git` dir exists **or** `CI`/`GITEA_ACTIONS` is set — line reads `AlvinArch build <date> | commit <date> | <4-char hash>` (falls back to `commit unknown` if `git -c safe.directory` can't read the repo; without `.git`/CI no file is staged). `customize_airootfs.sh` keeps it as `/etc/alvin-build-stamp` on the live medium **and appends it to the live `/etc/motd`**, so the booted ISO identifies its build. The installed system's motd is never touched by us — `alvin-install` has no motd step (an earlier `install_motd` that copied the banner + stamp into the target was removed on purpose).
|
||||
|
||||
@@ -37,9 +37,10 @@ AlvinArch/
|
||||
6. [System Services & Networking](#system-services--networking)
|
||||
7. [Local Repository (localrepo/)](#local-repository-localrepo)
|
||||
8. [Boot Configuration](#boot-configuration)
|
||||
9. [Build & Test](#build--test)
|
||||
10. [Planned Additions](#planned-additions)
|
||||
11. [Troubleshooting](#troubleshooting)
|
||||
9. [Automated Installer (alvin-install)](#automated-installer-alvin-install)
|
||||
10. [Build & Test](#build--test)
|
||||
11. [Planned Additions](#planned-additions)
|
||||
12. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
@@ -50,7 +51,7 @@ AlvinArch/
|
||||
### Key Features
|
||||
|
||||
- **Hyprland + end-4** — Wayland compositor with illogical-impulse dotfiles (quickshell, bibata cursors, microtex)
|
||||
- **Automated installer (`alvin-install`)** — gum TUI: LUKS with systemd initramfs or experimental touchscreen unl0kr, optional TPM2 auto-unlock, GitHub SSH key import, login manager choice (none/greetd/sddm/gdm), autologin, and a build stamp in `/etc/motd`
|
||||
- **Automated installer (`alvin-install`)** — gum TUI: LUKS with systemd initramfs or experimental touchscreen unl0kr, optional TPM2 auto-unlock, GitHub SSH key import, login manager choice (none/greetd/sddm/gdm), optional sshd (on by default), autologin; the build stamp identifies the live ISO via its `/etc/motd`
|
||||
- **LazyVim** — Pre-configured Neovim with lazy.nvim and curated plugins
|
||||
- **Fish + Zsh + Starship** — Dual shell setup with oh-my-zsh and a cross-shell prompt
|
||||
- **Local AUR repo** — Pre-built packages (end-4 deps, wallust, grub-customizer, etc.) that aren't in official repos
|
||||
@@ -138,7 +139,7 @@ AlvinArch/
|
||||
│ │ └── mkinitcpio.conf.d/ # archiso initramfs config
|
||||
│ ├── usr/
|
||||
│ │ ├── lib/os-release # usr-merge os-release
|
||||
│ │ ├── local/bin/ # choose-mirror, Installation_guide, livecd-sound
|
||||
│ │ ├── local/bin/ # alvin-install, choose-mirror, Installation_guide, livecd-sound
|
||||
│ │ ├── local/share/livecd-sound/ # ALSA config (asound.conf.in)
|
||||
│ │ └── share/pacman/keyrings/ # CachyOS GPG keyring + trust data
|
||||
│ └── root/
|
||||
@@ -305,6 +306,66 @@ Legacy BIOS boot with custom splash screen. Includes PXE network boot config.
|
||||
|
||||
---
|
||||
|
||||
## Automated Installer (alvin-install)
|
||||
|
||||
The ISO ships an interactive installer at `/usr/local/bin/alvin-install` (source:
|
||||
`airootfs/usr/local/bin/alvin-install`, ~1,880 lines of bash; the live `/etc/motd`
|
||||
advertises it). It partitions, encrypts, installs, and fully configures a system —
|
||||
including the end-4 dotfiles — without network access to this repo.
|
||||
|
||||
### Running it
|
||||
|
||||
```bash
|
||||
alvin-install # as root on the live medium (root autologins on tty1)
|
||||
```
|
||||
|
||||
Requirements: **UEFI boot** (it exits if `/sys/firmware/efi` is missing — BIOS/CSM
|
||||
is not supported by the installer) and `gum`. A network connection is required and
|
||||
verified by pinging `archlinux.org` before any disk is touched; the network screen
|
||||
can open `nmtui` for WiFi. Every step is logged to `/var/log/alvin-install.log`,
|
||||
which is copied into the installed system.
|
||||
|
||||
### What it asks (gum TUI screens)
|
||||
|
||||
Keymap → network → target disk (the live USB is hidden from the list) → LUKS
|
||||
encryption (optional; **the LUKS passphrase is the same as your login password**)
|
||||
→ experimental unl0kr touchscreen unlock → filesystem (`ext4`/`btrfs`) →
|
||||
username/password/hostname → git identity + GitHub username (both optional) →
|
||||
timezone/locale → login manager → SSH server (enabled by default) → summary.
|
||||
|
||||
### What it does
|
||||
|
||||
1. GPT partitioning (1 GiB ESP) on the chosen disk, optional LUKS2 on the rest,
|
||||
format + mount at `/mnt`
|
||||
2. `pacstrap` with the package list derived from the medium's
|
||||
`pkglist.x86_64.txt` (`pacman -Q` of the live ISO) — `archiso`, `syslinux`,
|
||||
`memtest86+` etc. are dropped, hardcoded essentials (kernel, GRUB,
|
||||
NetworkManager, cryptsetup, microcode) always appended
|
||||
3. User account, sudo, git config, GitHub SSH keys (`ssh-import-id`, falling
|
||||
back to `github.com/<user>.keys`)
|
||||
4. end-4 dotfiles from the staged `/opt/dots-hyprland` via
|
||||
`./setup install -f -s --skip-alldeps`
|
||||
5. Initramfs in one of two modes: **Mode A** — systemd initramfs + `sd-encrypt`
|
||||
(default); **Mode B** — busybox hooks + unl0kr touchscreen unlock
|
||||
(experimental, mutually exclusive with TPM2; falls back to Mode A on failure)
|
||||
6. Optional **TPM2 auto-unlock** (`systemd-cryptenroll`, PCR 0+7; passphrase
|
||||
slot kept as fallback; warns if Secure Boot is off)
|
||||
7. GRUB (UEFI, boot entry `ALVIN`), login manager — `none` (plain tty, Hyprland
|
||||
autostarts), `greetd`+tuigreet, `sddm`, or `gdm` — falls back to `none` only
|
||||
if the **package** can't be resolved/installed; a failed `systemctl enable`
|
||||
keeps `getty@tty1` as the login path (the summary still reports the chosen
|
||||
manager). Optional autologin (forced off when TPM2 is enrolled), and sshd
|
||||
enabled at boot when selected (default: on) — Arch's stock config allows
|
||||
**password login with the same password as LUKS**, so only accept the default
|
||||
on networks you trust (or harden `sshd_config` afterwards). The final banner
|
||||
reports whether the enable actually succeeded, not just what was selected
|
||||
|
||||
On failure an `EXIT` trap unmounts `/mnt` and closes the `cryptroot` mapping, so
|
||||
re-running is safe. If the end-4 step reports "incomplete", see the repository's
|
||||
`AGENTS.md` Gotchas for the chroot/systemd limitations that cause it.
|
||||
|
||||
---
|
||||
|
||||
## Build & Test
|
||||
|
||||
### Building the ISO
|
||||
|
||||
@@ -28,9 +28,13 @@ install -m 0644 /root/.live-pacman.conf /etc/pacman.conf
|
||||
rm -f /root/.live-pacman.conf
|
||||
|
||||
# Build stamp staged by profiledef.sh (absent for non-git, non-CI builds):
|
||||
# keep it where alvin-install looks for it, drop the staging copy.
|
||||
# keep the stamp file itself as a reference, drop the staging copy, and
|
||||
# stamp the live medium's motd so the booted ISO identifies its build (the
|
||||
# installed system keeps the stock package motd - alvin-install never
|
||||
# writes /etc/motd).
|
||||
if [[ -f /root/.iso-build-stamp ]]; then
|
||||
install -m 0644 /root/.iso-build-stamp /etc/alvin-build-stamp
|
||||
cat /etc/alvin-build-stamp >> /etc/motd
|
||||
rm -f /root/.iso-build-stamp
|
||||
fi
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ UNL0KR="false"
|
||||
TPM_STATUS=""
|
||||
LOGIN_MANAGER="none"
|
||||
AUTOLOGIN=""
|
||||
# SSHD is the choice made on the sshd screen; SSHD_APPLIED is only flipped once
|
||||
# the target's sshd.service has actually been enabled, so the final banner can
|
||||
# report reality instead of the request.
|
||||
SSHD="true"
|
||||
SSHD_APPLIED="false"
|
||||
TPM_DISCLAIMER=""
|
||||
TARGET_DB_SYNCED="false"
|
||||
|
||||
@@ -76,6 +81,40 @@ target_owner() {
|
||||
arch-chroot "$TARGET" sh -c "id -u '$USERNAME' && id -g '$USERNAME'" | paste -sd:
|
||||
}
|
||||
|
||||
# `gsettings get` prints GVariant text: '...' normally, "..." when the value
|
||||
# contains an apostrophe, with \n \t \\ \' \" escapes inside. Reduce that to
|
||||
# the plain string the original `gsettings set` was called with, so a read-back
|
||||
# can be compared against the replayed command. One left-to-right pass: doing
|
||||
# global replacements instead would corrupt sequences like \\n (literal
|
||||
# backslash + n must not become a newline).
|
||||
gvariant_unescape() {
|
||||
local s=$1 out="" i n c
|
||||
if [[ "$s" == \'*\' || "$s" == \"*\" ]]; then
|
||||
s=${s:1:${#s}-2}
|
||||
n=${#s}
|
||||
for ((i = 0; i < n; i++)); do
|
||||
c=${s:i:1}
|
||||
if [[ "$c" == "\\" && $((i + 1)) -lt $n ]]; then
|
||||
i=$((i + 1))
|
||||
case "${s:i:1}" in
|
||||
n) out+=$'\n' ;;
|
||||
t) out+=$'\t' ;;
|
||||
r) out+=$'\r' ;;
|
||||
a) out+=$'\a' ;;
|
||||
b) out+=$'\b' ;;
|
||||
f) out+=$'\f' ;;
|
||||
v) out+=$'\v' ;;
|
||||
*) out+="${s:i:1}" ;;
|
||||
esac
|
||||
else
|
||||
out+=$c
|
||||
fi
|
||||
done
|
||||
s=$out
|
||||
fi
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
# Refresh the target's package databases once before the first target-side
|
||||
# pacman/yay call (cachyos is appended to the target's pacman.conf after
|
||||
# pacstrap, so its db is not there yet; keyrings are re-populated as a
|
||||
@@ -366,6 +405,35 @@ screen_login_manager() {
|
||||
log "Login manager: $LOGIN_MANAGER"
|
||||
}
|
||||
|
||||
screen_sshd() {
|
||||
clear
|
||||
header "SSH Server"
|
||||
echo ""
|
||||
local -a ask_lines=(
|
||||
"Enable the SSH server (sshd) in the installed system?"
|
||||
""
|
||||
" - Starts at boot (systemctl enable sshd)"
|
||||
" - Arch's default config allows password login, and this password"
|
||||
" is the same as your LUKS passphrase - enable it only on networks"
|
||||
" you trust, or harden sshd afterwards (keys-only auth, firewall)."
|
||||
)
|
||||
# The GitHub import is optional (screen_git), so only mention it when the
|
||||
# user actually gave a username - and it happens later, during install.
|
||||
if [[ -n "$GH_USERNAME" ]]; then
|
||||
ask_lines+=(" - GitHub SSH keys for ${USERNAME} are imported during install")
|
||||
fi
|
||||
gum style --foreground 15 "${ask_lines[@]}"
|
||||
echo ""
|
||||
# --default pre-selects the affirmative so Enter accepts the default.
|
||||
if gum confirm --default --affirmative "Enable sshd" --negative "Skip" \
|
||||
"Enable the SSH server?"; then
|
||||
SSHD="true"
|
||||
else
|
||||
SSHD="false"
|
||||
fi
|
||||
log "SSH server: ${SSHD}"
|
||||
}
|
||||
|
||||
screen_summary() {
|
||||
clear
|
||||
header "Installation Summary"
|
||||
@@ -398,6 +466,7 @@ screen_summary() {
|
||||
"Locale: ${LOCALE}" \
|
||||
"Timezone: ${TIMEZONE}" \
|
||||
"Login mgr: ${LOGIN_MANAGER}" \
|
||||
"SSH server: $([[ "$SSHD" == "true" ]] && echo enabled || echo skipped)" \
|
||||
"SSH keys: ${ssh_line}" \
|
||||
"Git: ${git_line}"
|
||||
|
||||
@@ -528,6 +597,17 @@ install_pacstrap() {
|
||||
$found || pkgs+=("$p")
|
||||
done
|
||||
|
||||
# sshd was requested but openssh is not in the medium's pkglist (or was
|
||||
# filtered out above) — without this, install_sshd's enable would fail.
|
||||
if [[ "$SSHD" == "true" ]]; then
|
||||
local found_ssh=false
|
||||
for existing in "${pkgs[@]+"${pkgs[@]}"}"; do
|
||||
[[ "$existing" == "openssh" ]] && found_ssh=true && break
|
||||
done
|
||||
$found_ssh || pkgs+=(openssh)
|
||||
log "Pacstrap: openssh guaranteed in package list (sshd requested)"
|
||||
fi
|
||||
|
||||
info "Installing ${#pkgs[@]} packages (this may take a while)..."
|
||||
log "Pacstrap: ${#pkgs[@]} packages, ${#dropped[@]} skipped"
|
||||
pacstrap "$TARGET" "${pkgs[@]}" --noconfirm 2>>"$LOG"
|
||||
@@ -607,6 +687,25 @@ install_network() {
|
||||
ok "NetworkManager enabled."
|
||||
}
|
||||
|
||||
install_sshd() {
|
||||
[[ "$SSHD" != "true" ]] && return 0
|
||||
|
||||
info "Enabling SSH server..."
|
||||
if [[ ! -x "${TARGET}/usr/bin/sshd" ]]; then
|
||||
warn "openssh is missing from the target - cannot enable sshd."
|
||||
log "SSH server: enable skipped (sshd binary missing in target)"
|
||||
return 0
|
||||
fi
|
||||
if arch-chroot "$TARGET" systemctl enable sshd.service >>"$LOG" 2>&1; then
|
||||
SSHD_APPLIED="true"
|
||||
log "SSH server: enabled"
|
||||
ok "sshd enabled (starts at boot)."
|
||||
else
|
||||
warn "Could not enable sshd.service (see $LOG)."
|
||||
log "SSH server: enable failed"
|
||||
fi
|
||||
}
|
||||
|
||||
install_user() {
|
||||
info "Creating user..."
|
||||
|
||||
@@ -832,6 +931,73 @@ install_end4_dots() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# end-4's gsettings calls run under its sudo session inside this chroot,
|
||||
# so dconf sees root's environment (/run/user/0, /root/.local/...) and no
|
||||
# session bus: every write fails with "failed to commit changes to
|
||||
# dconf" while gsettings still exits 0 and reports "finished". Replay any
|
||||
# gsettings set this run logged, with a clean environment and a
|
||||
# throwaway session bus as the target user. gsettings exits 0 even when
|
||||
# the commit fails, so read the key back in a second session — only a
|
||||
# matching value counts as persisted.
|
||||
if ((log_mark > 0)) \
|
||||
&& [[ -x "${TARGET}/usr/bin/dbus-run-session" && -x "${TARGET}/usr/bin/gsettings" ]]; then
|
||||
local gs_line gs_schema gs_key gs_val gs_got gs_norm gs_raw
|
||||
# Snapshot and parse first (the loop below appends to $LOG), keeping
|
||||
# log order: a key set twice replays chronologically so the last
|
||||
# write wins, exactly as end-4 ran it. Dedupe identical repeats only.
|
||||
local -a gs_cmds=()
|
||||
mapfile -t gs_cmds < <(
|
||||
# Same ANSI-strip as the ignored-list parse below; the setup log
|
||||
# shows each command once as its "Next command" line (the
|
||||
# "…finished." echoes carry a prefix and never match ^gsettings).
|
||||
sed -n "$((log_mark + 1)),\$p" "$LOG" | sed $'s/\x1b\\[[0-9;]*m//g' |
|
||||
sed -n 's/^\(gsettings set .*\)$/\1/p' | awk '!seen[$0]++'
|
||||
)
|
||||
log "gsettings replay: ${#gs_cmds[@]} command(s) parsed from log"
|
||||
# Parsing is coupled to end-4's v() echo format: if upstream changes
|
||||
# it, the replay silently does nothing and the original dconf failure
|
||||
# returns - so distinguish "none ran" from "none recognized".
|
||||
if ((${#gs_cmds[@]} == 0)); then
|
||||
gs_raw=$(sed -n "$((log_mark + 1)),\$p" "$LOG" | sed $'s/\x1b\\[[0-9;]*m//g' |
|
||||
grep -c 'gsettings set' || true)
|
||||
if ((gs_raw > 0)); then
|
||||
warn "setup log mentions gsettings ${gs_raw}x but none matched the replay parser (format drift?) - see $LOG"
|
||||
fi
|
||||
fi
|
||||
for gs_line in "${gs_cmds[@]}"; do
|
||||
[[ "$gs_line" =~ ^gsettings\ set\ ([^[:space:]]+)\ ([^[:space:]]+)\ (.+)$ ]] || continue
|
||||
gs_schema=${BASH_REMATCH[1]}
|
||||
gs_key=${BASH_REMATCH[2]}
|
||||
gs_val=${BASH_REMATCH[3]}
|
||||
# env -i: root's env is exactly what broke dconf; rebuild it for
|
||||
# the target user with only what gsettings/dbus need.
|
||||
if arch-chroot "$TARGET" runuser -u "$USERNAME" -- \
|
||||
env -i HOME="$home_in_target" USER="$USERNAME" LOGNAME="$USERNAME" \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \
|
||||
XDG_CONFIG_HOME="${home_in_target}/.config" \
|
||||
dbus-run-session -- \
|
||||
gsettings set "$gs_schema" "$gs_key" "$gs_val" >>"$LOG" 2>&1; then
|
||||
gs_got=$(arch-chroot "$TARGET" runuser -u "$USERNAME" -- \
|
||||
env -i HOME="$home_in_target" USER="$USERNAME" LOGNAME="$USERNAME" \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin \
|
||||
XDG_CONFIG_HOME="${home_in_target}/.config" \
|
||||
dbus-run-session -- \
|
||||
gsettings get "$gs_schema" "$gs_key" 2>>"$LOG") || gs_got=""
|
||||
# Normalize the GVariant read-back: strings come back quoted
|
||||
# ('...' normally, "..." when the value contains an apostrophe)
|
||||
# with \n \\ \' \" escapes, bools/ints bare ('true' vs true).
|
||||
gs_norm=$(gvariant_unescape "$gs_got")
|
||||
if [[ "$gs_norm" == "$gs_val" ]]; then
|
||||
log "gsettings persisted: ${gs_schema} ${gs_key} = ${gs_val}"
|
||||
else
|
||||
warn "gsettings ${gs_schema} ${gs_key} did not persist (read back: ${gs_got:-nothing}, see $LOG)."
|
||||
fi
|
||||
else
|
||||
warn "Could not replay gsettings ${gs_schema} ${gs_key} (see $LOG)."
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Everything end-4 answered "i" to. systemctl/gsettings are the lines known
|
||||
# to be unusable in this chroot; anything else it had to skip is worth
|
||||
# telling the user about.
|
||||
@@ -902,8 +1068,24 @@ install_end4_dots() {
|
||||
if $configs_ok; then
|
||||
[[ -x "${TARGET}/usr/bin/hyprctl" && -x "${TARGET}/usr/bin/quickshell" ]] \
|
||||
|| configs_ok=false
|
||||
[[ -x "${TARGET}${home_in_target}/.local/state/quickshell/.venv/bin/python" ]] \
|
||||
|| configs_ok=false
|
||||
|
||||
# uv writes bin/python as an absolute symlink into the chroot
|
||||
# (/home/.../uv/python/...). Tested verbatim from the live medium
|
||||
# that link dangles - /home/$USERNAME exists only under $TARGET -
|
||||
# so walk the chain here and re-root absolute hops under $TARGET
|
||||
# (relative hops stay inside the venv and need no repointing).
|
||||
local venv_probe="${TARGET}${home_in_target}/.local/state/quickshell/.venv/bin/python"
|
||||
local venv_link hops=0
|
||||
while [[ -L "$venv_probe" && $hops -lt 10 ]]; do
|
||||
venv_link=$(readlink "$venv_probe")
|
||||
if [[ "$venv_link" == /* ]]; then
|
||||
venv_probe="${TARGET}${venv_link}"
|
||||
else
|
||||
venv_probe="$(dirname "$venv_probe")/$venv_link"
|
||||
fi
|
||||
hops=$((hops + 1))
|
||||
done
|
||||
[[ -x "$venv_probe" ]] || configs_ok=false
|
||||
fi
|
||||
|
||||
if [[ -d /etc/skel/.config/hypr/custom ]]; then
|
||||
@@ -1614,21 +1796,7 @@ EOF
|
||||
ok "Login set: manager=${LOGIN_MANAGER}, autologin=${AUTOLOGIN}."
|
||||
}
|
||||
|
||||
# ── Build stamp ───────────────────────────────────────────────────────────────
|
||||
|
||||
install_motd() {
|
||||
# Only stamped media carry a stamp; without one (local non-git build)
|
||||
# the installed system keeps whatever motd the target packages shipped.
|
||||
local stamp="/etc/alvin-build-stamp"
|
||||
if [[ ! -f "$stamp" ]]; then
|
||||
log "motd: no build stamp on this medium - target motd left as-is"
|
||||
return 0
|
||||
fi
|
||||
cp /etc/motd "${TARGET}/etc/motd" 2>/dev/null || true
|
||||
cat "$stamp" >> "${TARGET}/etc/motd"
|
||||
log "motd: appended build stamp: $(tr -d '\n' < "$stamp")"
|
||||
ok "Build stamp appended to the installed /etc/motd."
|
||||
}
|
||||
# ── Cleanup ───────────────────────────────────────────────────────────────────
|
||||
|
||||
install_cleanup() {
|
||||
info "Cleaning up..."
|
||||
@@ -1637,10 +1805,21 @@ install_cleanup() {
|
||||
log "Installation complete."
|
||||
ok "Installation complete!"
|
||||
echo ""
|
||||
# Report what actually happened, not what was asked for: the enable step
|
||||
# only warns when it cannot work (openssh missing / systemctl failed).
|
||||
local ssh_state
|
||||
if [[ "$SSHD" != "true" ]]; then
|
||||
ssh_state="disabled"
|
||||
elif [[ "$SSHD_APPLIED" == "true" ]]; then
|
||||
ssh_state="enabled (starts at boot)"
|
||||
else
|
||||
ssh_state="NOT enabled - see ${LOG}"
|
||||
fi
|
||||
gum style --foreground 10 --align center \
|
||||
"Arch Linux installed successfully." \
|
||||
"" \
|
||||
"Login manager: ${LOGIN_MANAGER} Autologin: ${AUTOLOGIN:-disabled}" \
|
||||
"SSH server: ${ssh_state}" \
|
||||
"TPM2 auto-unlock: ${TPM_STATUS:-n/a} unl0kr: ${UNL0KR}"
|
||||
if [[ -n "${TPM_DISCLAIMER}" ]]; then
|
||||
echo ""
|
||||
@@ -1671,6 +1850,7 @@ main() {
|
||||
screen_timezone
|
||||
screen_locale
|
||||
screen_login_manager
|
||||
screen_sshd
|
||||
screen_summary
|
||||
|
||||
install_partition
|
||||
@@ -1683,6 +1863,7 @@ main() {
|
||||
install_locale
|
||||
install_hostname
|
||||
install_network
|
||||
install_sshd
|
||||
install_user
|
||||
install_git_ssh
|
||||
install_end4_dots
|
||||
@@ -1692,7 +1873,6 @@ main() {
|
||||
install_unl0kr
|
||||
install_login_manager
|
||||
install_autologin
|
||||
install_motd
|
||||
install_cleanup
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -27,8 +27,9 @@ file_permissions=(
|
||||
|
||||
# ── Build stamp (staged file, never committed) ────────────────────────────────
|
||||
# airootfs/root/.iso-build-stamp lands at /root/.iso-build-stamp on the ISO;
|
||||
# customize_airootfs.sh installs it as /etc/alvin-build-stamp, and alvin-install
|
||||
# appends it to the installed system's /etc/motd. Only stamp builds from a git
|
||||
# customize_airootfs.sh keeps it as /etc/alvin-build-stamp and appends it to
|
||||
# the live medium's /etc/motd (the installed system's motd is left alone).
|
||||
# Only stamp builds from a git
|
||||
# checkout or CI — a random source-tarball build would record a bogus commit.
|
||||
# Sourced by mkarchiso under `set -eu`, hence every expansion is defaulted.
|
||||
_stamp_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
Reference in New Issue
Block a user