diff --git a/docs/md/archiso.md b/docs/md/archiso.md index 632bbef..a96fae0 100644 --- a/docs/md/archiso.md +++ b/docs/md/archiso.md @@ -54,7 +54,7 @@ Build artefacts land in `~/m-archy-out/` by default. Override with the `OUT_DIR` 2. Applies the M-Archy overlay (`setup/archiso/overlay/`) 3. Replaces `profiledef.sh` with the M-Archy version 4. Adds extra packages from `packages.extra` -5. Embeds both installer scripts (`arch-autoinstall.sh`, `archbaseos-guided-install.sh`) into `/root/installer/` +5. Embeds the whole `setup/` tree into `/installer/` (every installer script, including `tui-install.sh` and its `modules/`), plus a full repo snapshot at `/installer/dotfiles/` 6. If `--preconf`: copies the answerfile to `/answerfile.json` in the ISO's airootfs 7. Runs `mkarchiso` to produce the final `.iso` @@ -94,7 +94,7 @@ install-arch guided # guided interactive install install-arch auto # automated mode (reads /answerfile.json) ``` -### `/root/launch.sh` +### `/installer/launch.sh` Internal dispatcher used by `install-arch`. After keymap selection, it prompts for one of two actions: @@ -188,8 +188,10 @@ For multi-machine deployments, the `hostname` field in the answerfile is combine setup/archiso/overlay/ ├── airootfs/ │ ├── etc/motd # Welcome message -│ ├── root/ +│ ├── installer/ │ │ └── launch.sh # Installer entry point +│ ├── root/ +│ │ └── .automated_script.sh # Login hook → /installer/launch.sh │ └── usr/local/bin/ │ └── install-arch # User-facing CLI command ├── packages.extra # Additional live-system packages @@ -198,11 +200,21 @@ setup/archiso/overlay/ The `build.sh` script also adds at build time: ``` -airootfs/root/installer/ +airootfs/installer/ # the whole setup/ tree +├── archbaseos-guided-install.sh ├── arch-autoinstall.sh -└── archbaseos-guided-install.sh +├── reset-arch.sh +├── tui-install.sh +├── modules/ # every DE/app/service module +└── dotfiles/ # full repo snapshot (incl. .git) ``` +Nothing the installer runs depends on network access any more: the guided and +automated installers still clone the repo when they can (so an online install +gets whatever is newest), but fall back to `/installer/dotfiles` when the clone +fails, and both verify the resulting tree actually contains +`setup/tui-install.sh` before they promise to run it. + --- ## Writing the ISO to USB diff --git a/docs/md/installation.md b/docs/md/installation.md index ca7d752..3b73f3a 100644 --- a/docs/md/installation.md +++ b/docs/md/installation.md @@ -142,7 +142,7 @@ Two scripts install Arch Linux itself (before the dotfiles step): Interactive, dialog-based. Prompts for each setting with sensible defaults. Good for hands-on installs where you want to review each option. ```bash -bash ~/installer/archbaseos-guided-install.sh +bash /installer/archbaseos-guided-install.sh ``` ### Auto Installer (`arch-autoinstall.sh`) @@ -150,7 +150,7 @@ bash ~/installer/archbaseos-guided-install.sh Reads all settings from `/answerfile.json` if present; falls back to prompts for anything missing. ```bash -bash ~/installer/arch-autoinstall.sh +bash /installer/arch-autoinstall.sh ``` Both installers perform the same steps: diff --git a/setup/arch-autoinstall.sh b/setup/arch-autoinstall.sh index e78f295..83e21c4 100755 --- a/setup/arch-autoinstall.sh +++ b/setup/arch-autoinstall.sh @@ -584,38 +584,64 @@ fi export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK KEYMAP RUN_TUI CLONE_SKEL_DOTFILES ############################################ -# DOTFILES CLONE (live environment, with retry) +# DOTFILES TREE (existing checkout → clone → ISO snapshot) ############################################ -# The live ISO only embeds the top-level installer scripts (see build.sh) — -# tui-install.sh, its modules/, and desktopenvs/ configs exist only inside the -# full repo. Clone it ONCE into the live environment whenever either RUN_TUI -# or CLONE_SKEL_DOTFILES is YES, so both consumers below (the skel copy, and -# the fallback copy straight into the new user's home when skel is skipped -# but the TUI still needs to run) are served from one clone instead of each -# needing its own network fetch — and so a bare install genuinely fetches -# nothing at all. Done here (outside the chroot) because a clone made inside -# the chroot heredoc lives only under /mnt and can't be reused for the skel -# copy that must happen before useradd -m runs inside that same heredoc. +# The ISO embeds every installer script under /installer and a full repo +# snapshot under /installer/dotfiles (see setup/tools/build.sh), so this step +# is only about picking the best copy available, in order: +# 1. a checkout already sitting in the live environment (a re-run), +# 2. a fresh clone, so an install with working networking gets what is newest, +# 3. the ISO's own snapshot, so an offline install still gets a full tree. +# Resolved ONCE, whenever either RUN_TUI or CLONE_SKEL_DOTFILES is YES, so both +# consumers below (the skel copy, and the fallback copy straight into the new +# user's home when skel is skipped but the TUI still needs to run) are served +# from it — and so a bare install genuinely fetches nothing at all. +# Done here (outside the chroot) because a tree materialised inside the chroot +# heredoc lives only under /mnt and can't be reused for the skel copy that must +# happen before useradd -m runs inside that same heredoc. LIVE_DOTFILES_DIR="$HOME/Dotfiles" -if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" || "${RUN_TUI^^}" == "YES" ]]; then - echo "Cloning dotfiles into the live environment ($LIVE_DOTFILES_DIR)..." - _clone_ok=false - while ! $_clone_ok; do - if [[ -d "$LIVE_DOTFILES_DIR/.git" ]]; then - _clone_ok=true - elif git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "$LIVE_DOTFILES_DIR"; then - _clone_ok=true - else - if $AF_MODE; then - echo "Warning: dotfiles clone failed — continuing without dotfiles." - RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO" - _clone_ok=true - else - read -rp "Clone failed — retry? [y/N]: " _retry - [[ "${_retry,,}" == "y" ]] || { echo "Skipping dotfiles — clone manually after first boot."; RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO"; _clone_ok=true; } - fi +# Snapshot embedded by setup/tools/build.sh. Overridable so the scripts can be +# run from a checkout on a normal system, not just from the ISO. +ISO_DOTFILES="${ISO_DOTFILES:-/installer/dotfiles}" + +# A tree is only usable if the script the chroot step actually runs is in it. +# The old check was `-d $LIVE_DOTFILES_DIR/.git`, which a leftover or half- +# finished checkout satisfies — the install then went all the way to the last +# step before failing on a missing tui-install.sh. +_dotfiles_tree_ok() { [[ -f "$1/setup/tui-install.sh" ]]; } + +_clone_dotfiles() { + local url="https://git.abdelbaki.eu/The_miro/Dotfiles.git" + while :; do + rm -rf "$LIVE_DOTFILES_DIR" + if git clone "$url" "$LIVE_DOTFILES_DIR" && _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then + return 0 fi + # Unattended, or the ISO carries a snapshot we can fall back to: don't + # sit at a prompt nobody is going to answer. + if $AF_MODE; then return 1; fi + if _dotfiles_tree_ok "$ISO_DOTFILES"; then return 1; fi + read -rp "Clone failed — retry? [y/N]: " _retry + if [[ "${_retry,,}" != "y" ]]; then return 1; fi done +} + +if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" || "${RUN_TUI^^}" == "YES" ]]; then + if _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then + echo "Using the dotfiles checkout already present at $LIVE_DOTFILES_DIR." + else + echo "Cloning dotfiles into the live environment ($LIVE_DOTFILES_DIR)..." + if ! _clone_dotfiles && _dotfiles_tree_ok "$ISO_DOTFILES"; then + echo "Clone unavailable — using the snapshot embedded in the ISO ($ISO_DOTFILES)." + rm -rf "$LIVE_DOTFILES_DIR" + cp -r "$ISO_DOTFILES" "$LIVE_DOTFILES_DIR" + fi + fi + if ! _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then + echo "Warning: no usable dotfiles tree (no clone, no ISO snapshot) — continuing without dotfiles." + echo " Clone manually after first boot: git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git ~/Dotfiles" + RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO" + fi fi ############################################ @@ -823,6 +849,39 @@ if [[ "${RUN_TUI^^}" == "YES" ]]; then | arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null arch-chroot /mnt chmod 0440 /etc/sudoers.d/99-setup-nopasswd + # Never hand control to a script that isn't there: without this the install + # ran to completion and only then reported a missing tui-install.sh. + if [[ ! -f "/mnt/home/${USERNAME}/Dotfiles/setup/tui-install.sh" ]]; then + echo "Warning: /home/${USERNAME}/Dotfiles/setup/tui-install.sh is missing — skipping the dotfiles TUI." + _DO_TUI="NO" + fi + + # Prove the drop-in above actually took effect before the TUI starts. `-n` + # makes sudo fail rather than prompt, so a rule that didn't apply surfaces + # here as a clean skip instead of a hidden "[sudo] password for ..." prompt + # that stalls the install forever — nobody is watching an unattended one. + # Both forms are tested: modules call plain `sudo ` (NOPASSWD rule) and + # third-party installers call `sudo -v` (the !authenticate default). + if [[ "${_DO_TUI^^}" == "YES" ]] \ + && ! arch-chroot /mnt runuser -u "${USERNAME}" -- sudo -n true 2>/dev/null; then + echo "Warning: passwordless sudo for ${USERNAME} is not in effect — skipping the TUI rather than stalling on a password prompt." + _DO_TUI="NO" + fi + if [[ "${_DO_TUI^^}" == "YES" ]] \ + && ! arch-chroot /mnt runuser -u "${USERNAME}" -- sudo -n -v 2>/dev/null; then + echo "Warning: 'sudo -v' still demands a password for ${USERNAME} — skipping the TUI rather than stalling on a password prompt." + _DO_TUI="NO" + fi + + # Skipping here means the run below never happens, so the drop-in never + # reaches its usual post-run removal — take it back out now rather than + # leaving the installed system with permanent passwordless sudo. + if [[ "${_DO_TUI^^}" != "YES" ]]; then + arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd + fi +fi + +if [[ "${_DO_TUI^^}" == "YES" ]]; then echo "Running tui-install.sh as ${USERNAME} inside chroot..." # `runuser -u` switches to the unprivileged user inside the chroot so that # AUR helpers and dotfiles are owned/built by the correct UID. diff --git a/setup/archbaseos-guided-install.sh b/setup/archbaseos-guided-install.sh index 460de23..5a6bfb8 100755 --- a/setup/archbaseos-guided-install.sh +++ b/setup/archbaseos-guided-install.sh @@ -560,36 +560,61 @@ if $AF_MODE; then fi ############################################ -# DOTFILES CLONE (live environment, with retry) +# DOTFILES TREE (existing checkout → clone → ISO snapshot) ############################################ -# The live ISO only embeds the top-level installer scripts (see build.sh) — -# tui-install.sh, its modules/, and desktopenvs/ configs exist only inside the -# full repo. Clone it ONCE into the live environment whenever either RUN_TUI -# or CLONE_SKEL_DOTFILES is YES, so both consumers below (the skel copy, and -# the fallback copy straight into the new user's home when skel is skipped -# but the TUI still needs to run) are served from one clone instead of each -# needing its own network fetch — and so a bare install genuinely fetches -# nothing at all. +# The ISO embeds every installer script under /installer and a full repo +# snapshot under /installer/dotfiles (see setup/tools/build.sh), so this step +# is only about picking the best copy available, in order: +# 1. a checkout already sitting in the live environment (a re-run), +# 2. a fresh clone, so an install with working networking gets what is newest, +# 3. the ISO's own snapshot, so an offline install still gets a full tree. +# Resolved ONCE, whenever either RUN_TUI or CLONE_SKEL_DOTFILES is YES, so both +# consumers below (the skel copy, and the fallback copy straight into the new +# user's home when skel is skipped but the TUI still needs to run) are served +# from it — and so a bare install genuinely fetches nothing at all. LIVE_DOTFILES_DIR="$HOME/Dotfiles" -if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" || "${RUN_TUI^^}" == "YES" ]]; then - echo "Cloning dotfiles into the live environment ($LIVE_DOTFILES_DIR)..." - _clone_ok=false - while ! $_clone_ok; do - if [[ -d "$LIVE_DOTFILES_DIR/.git" ]]; then - _clone_ok=true - elif git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "$LIVE_DOTFILES_DIR"; then - _clone_ok=true - else - if $AF_MODE; then - echo "Warning: dotfiles clone failed — continuing without dotfiles." - RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO" - _clone_ok=true - else - read -rp "Clone failed — retry? [y/N]: " _retry - [[ "${_retry,,}" == "y" ]] || { echo "Skipping dotfiles — clone manually after first boot."; RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO"; _clone_ok=true; } - fi +# Snapshot embedded by setup/tools/build.sh. Overridable so the scripts can be +# run from a checkout on a normal system, not just from the ISO. +ISO_DOTFILES="${ISO_DOTFILES:-/installer/dotfiles}" + +# A tree is only usable if the script the chroot step actually runs is in it. +# The old check was `-d $LIVE_DOTFILES_DIR/.git`, which a leftover or half- +# finished checkout satisfies — the install then went all the way to the last +# step before failing on a missing tui-install.sh. +_dotfiles_tree_ok() { [[ -f "$1/setup/tui-install.sh" ]]; } + +_clone_dotfiles() { + local url="https://git.abdelbaki.eu/The_miro/Dotfiles.git" + while :; do + rm -rf "$LIVE_DOTFILES_DIR" + if git clone "$url" "$LIVE_DOTFILES_DIR" && _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then + return 0 fi + # Unattended, or the ISO carries a snapshot we can fall back to: don't + # sit at a prompt nobody is going to answer. + if $AF_MODE; then return 1; fi + if _dotfiles_tree_ok "$ISO_DOTFILES"; then return 1; fi + read -rp "Clone failed — retry? [y/N]: " _retry + if [[ "${_retry,,}" != "y" ]]; then return 1; fi done +} + +if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" || "${RUN_TUI^^}" == "YES" ]]; then + if _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then + echo "Using the dotfiles checkout already present at $LIVE_DOTFILES_DIR." + else + echo "Cloning dotfiles into the live environment ($LIVE_DOTFILES_DIR)..." + if ! _clone_dotfiles && _dotfiles_tree_ok "$ISO_DOTFILES"; then + echo "Clone unavailable — using the snapshot embedded in the ISO ($ISO_DOTFILES)." + rm -rf "$LIVE_DOTFILES_DIR" + cp -r "$ISO_DOTFILES" "$LIVE_DOTFILES_DIR" + fi + fi + if ! _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then + echo "Warning: no usable dotfiles tree (no clone, no ISO snapshot) — continuing without dotfiles." + echo " Clone manually after first boot: git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git ~/Dotfiles" + RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO" + fi fi ############################################ @@ -793,6 +818,39 @@ if [[ "${_DO_TUI^^}" == "YES" ]]; then | arch-chroot /mnt tee /etc/sudoers.d/99-setup-nopasswd > /dev/null arch-chroot /mnt chmod 0440 /etc/sudoers.d/99-setup-nopasswd + # Never hand control to a script that isn't there: without this the install + # ran to completion and only then reported a missing tui-install.sh. + if [[ ! -f "/mnt/home/${USERNAME}/Dotfiles/setup/tui-install.sh" ]]; then + echo "Warning: /home/${USERNAME}/Dotfiles/setup/tui-install.sh is missing — skipping the dotfiles TUI." + _DO_TUI="NO" + fi + + # Prove the drop-in above actually took effect before the TUI starts. `-n` + # makes sudo fail rather than prompt, so a rule that didn't apply surfaces + # here as a clean skip instead of a hidden "[sudo] password for ..." prompt + # that stalls the install forever — nobody is watching an unattended one. + # Both forms are tested: modules call plain `sudo ` (NOPASSWD rule) and + # third-party installers call `sudo -v` (the !authenticate default). + if [[ "${_DO_TUI^^}" == "YES" ]] \ + && ! arch-chroot /mnt runuser -u "${USERNAME}" -- sudo -n true 2>/dev/null; then + echo "Warning: passwordless sudo for ${USERNAME} is not in effect — skipping the TUI rather than stalling on a password prompt." + _DO_TUI="NO" + fi + if [[ "${_DO_TUI^^}" == "YES" ]] \ + && ! arch-chroot /mnt runuser -u "${USERNAME}" -- sudo -n -v 2>/dev/null; then + echo "Warning: 'sudo -v' still demands a password for ${USERNAME} — skipping the TUI rather than stalling on a password prompt." + _DO_TUI="NO" + fi + + # Skipping here means the run below never happens, so the drop-in never + # reaches its usual post-run removal — take it back out now rather than + # leaving the installed system with permanent passwordless sudo. + if [[ "${_DO_TUI^^}" != "YES" ]]; then + arch-chroot /mnt rm -f /etc/sudoers.d/99-setup-nopasswd + fi +fi + +if [[ "${_DO_TUI^^}" == "YES" ]]; then echo "Running tui-install.sh as ${USERNAME} inside chroot..." # `runuser -u` switches to the unprivileged user inside the chroot so that # AUR helpers and dotfiles are owned/built by the correct UID. diff --git a/setup/archiso/overlay/airootfs/etc/motd b/setup/archiso/overlay/airootfs/etc/motd index ffe7d1b..d02cbac 100644 --- a/setup/archiso/overlay/airootfs/etc/motd +++ b/setup/archiso/overlay/airootfs/etc/motd @@ -9,7 +9,7 @@ install-arch guided installer (manual mode) install-arch auto automated installer (requires answerfile) - Scripts are also at: ~/installer/ + Scripts are also at: /installer/ Dotfiles: https://git.abdelbaki.eu/The_miro/Dotfiles diff --git a/setup/archiso/overlay/airootfs/root/launch.sh b/setup/archiso/overlay/airootfs/installer/launch.sh similarity index 86% rename from setup/archiso/overlay/airootfs/root/launch.sh rename to setup/archiso/overlay/airootfs/installer/launch.sh index 5212471..744b71d 100644 --- a/setup/archiso/overlay/airootfs/root/launch.sh +++ b/setup/archiso/overlay/airootfs/installer/launch.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -INSTALLER_DIR="$HOME/installer" +# Every installer script (this dispatcher, the guided/automated installers, +# tui-install.sh and its modules/) lives under /installer on the live ISO — +# see setup/tools/build.sh, which embeds the whole setup/ tree there. +INSTALLER_DIR="${INSTALLER_DIR:-/installer}" ############################################ # Keyboard layout (live environment) diff --git a/setup/archiso/overlay/airootfs/root/.automated_script.sh b/setup/archiso/overlay/airootfs/root/.automated_script.sh index 5654868..24a243d 100644 --- a/setup/archiso/overlay/airootfs/root/.automated_script.sh +++ b/setup/archiso/overlay/airootfs/root/.automated_script.sh @@ -40,9 +40,9 @@ ANSWERFILE="/answerfile.json" if [[ -f "$ANSWERFILE" ]]; then # Automated mode: pass "auto" as the first arg and the answerfile path as # the second. launch.sh will forward these to arch-autoinstall.sh. - exec /root/launch.sh auto "$ANSWERFILE" + exec /installer/launch.sh auto "$ANSWERFILE" else # Guided mode: start the interactive installer. The user will be prompted # for keyboard layout and installation action before anything happens. - exec /root/launch.sh guided + exec /installer/launch.sh guided fi diff --git a/setup/archiso/overlay/airootfs/usr/local/bin/install-arch b/setup/archiso/overlay/airootfs/usr/local/bin/install-arch index 45d2eda..59b15dc 100644 --- a/setup/archiso/overlay/airootfs/usr/local/bin/install-arch +++ b/setup/archiso/overlay/airootfs/usr/local/bin/install-arch @@ -1,2 +1,2 @@ #!/usr/bin/env bash -exec /root/launch.sh "$@" +exec /installer/launch.sh "$@" diff --git a/setup/archiso/overlay/profiledef.sh b/setup/archiso/overlay/profiledef.sh index 8a45852..cad83d5 100644 --- a/setup/archiso/overlay/profiledef.sh +++ b/setup/archiso/overlay/profiledef.sh @@ -19,8 +19,15 @@ file_permissions=( ["/root"]="0:0:750" ["/root/.automated_script.sh"]="0:0:755" ["/root/.zlogin"]="0:0:644" - ["/root/launch.sh"]="0:0:755" - ["/root/installer/archbaseos-guided-install.sh"]="0:0:755" - ["/root/installer/arch-autoinstall.sh"]="0:0:755" + # /installer holds every installer script (build.sh embeds the whole setup/ + # tree there, plus a full repo snapshot at /installer/dotfiles). Only the + # entry points need an explicit mode here — build.sh chmods the rest, and + # everything below them is invoked as `bash