feat(archiso): embed every installer script at /installer, with an offline repo snapshot

The ISO carried only the three entry scripts launch.sh dispatches to;
tui-install.sh, its modules/ and the desktopenvs/ configs it deploys existed
solely inside the repo the installer clones at run time. A live environment
without working networking therefore ran the whole install and only failed at
the very last step, on a missing /home/<user>/Dotfiles/setup/tui-install.sh.

build.sh now embeds the entire setup/ tree at /installer (so every installer
script is on the ISO), plus a full repo snapshot — .git included, so a seeded
checkout is a real repo — at /installer/dotfiles. launch.sh moves there too
and the overlay/motd/docs follow; profiledef's file_permissions list is
repointed, which matters because mkarchiso hard-errors on an entry whose path
does not exist.

Tree resolution in both installers is now: an existing checkout, else a fresh
clone (online installs still get what is newest), else the ISO snapshot. Each
candidate is accepted only if it actually contains setup/tui-install.sh — the
old check was a bare `-d .git`, which any leftover or half-finished checkout
satisfied.

Sudo can no longer stop the install to ask for a password:
  - before the chroot hands over to the TUI, the temporary NOPASSWD drop-in is
    proven with `sudo -n true` and `sudo -n -v` (both forms modules rely on).
    A rule that didn't apply now skips the TUI instead of stalling forever on a
    hidden prompt, and the drop-in is removed on that path too rather than
    being left behind as permanent passwordless sudo;
  - tui-install.sh's root sudo shim moves to the top of the file, ahead of the
    require_jq() bootstrap that shells out through sudo, so running it as root
    straight from /installer never needs a real sudo binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-07-29 19:16:45 +02:00
parent 72e0fa8edc
commit 96f15c82af
12 changed files with 255 additions and 105 deletions

View File

@ -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/`) 2. Applies the M-Archy overlay (`setup/archiso/overlay/`)
3. Replaces `profiledef.sh` with the M-Archy version 3. Replaces `profiledef.sh` with the M-Archy version
4. Adds extra packages from `packages.extra` 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 6. If `--preconf`: copies the answerfile to `/answerfile.json` in the ISO's airootfs
7. Runs `mkarchiso` to produce the final `.iso` 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) 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: 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/ setup/archiso/overlay/
├── airootfs/ ├── airootfs/
│ ├── etc/motd # Welcome message │ ├── etc/motd # Welcome message
│ ├── root/ │ ├── installer/
│ │ └── launch.sh # Installer entry point │ │ └── launch.sh # Installer entry point
│ ├── root/
│ │ └── .automated_script.sh # Login hook → /installer/launch.sh
│ └── usr/local/bin/ │ └── usr/local/bin/
│ └── install-arch # User-facing CLI command │ └── install-arch # User-facing CLI command
├── packages.extra # Additional live-system packages ├── packages.extra # Additional live-system packages
@ -198,11 +200,21 @@ setup/archiso/overlay/
The `build.sh` script also adds at build time: 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 ├── 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 ## Writing the ISO to USB

View File

@ -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. Interactive, dialog-based. Prompts for each setting with sensible defaults. Good for hands-on installs where you want to review each option.
```bash ```bash
bash ~/installer/archbaseos-guided-install.sh bash /installer/archbaseos-guided-install.sh
``` ```
### Auto Installer (`arch-autoinstall.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. Reads all settings from `/answerfile.json` if present; falls back to prompts for anything missing.
```bash ```bash
bash ~/installer/arch-autoinstall.sh bash /installer/arch-autoinstall.sh
``` ```
Both installers perform the same steps: Both installers perform the same steps:

View File

@ -584,38 +584,64 @@ fi
export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK KEYMAP RUN_TUI CLONE_SKEL_DOTFILES 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) — # The ISO embeds every installer script under /installer and a full repo
# tui-install.sh, its modules/, and desktopenvs/ configs exist only inside the # snapshot under /installer/dotfiles (see setup/tools/build.sh), so this step
# full repo. Clone it ONCE into the live environment whenever either RUN_TUI # is only about picking the best copy available, in order:
# or CLONE_SKEL_DOTFILES is YES, so both consumers below (the skel copy, and # 1. a checkout already sitting in the live environment (a re-run),
# the fallback copy straight into the new user's home when skel is skipped # 2. a fresh clone, so an install with working networking gets what is newest,
# but the TUI still needs to run) are served from one clone instead of each # 3. the ISO's own snapshot, so an offline install still gets a full tree.
# needing its own network fetch — and so a bare install genuinely fetches # Resolved ONCE, whenever either RUN_TUI or CLONE_SKEL_DOTFILES is YES, so both
# nothing at all. Done here (outside the chroot) because a clone made inside # consumers below (the skel copy, and the fallback copy straight into the new
# the chroot heredoc lives only under /mnt and can't be reused for the skel # user's home when skel is skipped but the TUI still needs to run) are served
# copy that must happen before useradd -m runs inside that same heredoc. # 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" LIVE_DOTFILES_DIR="$HOME/Dotfiles"
if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" || "${RUN_TUI^^}" == "YES" ]]; then # Snapshot embedded by setup/tools/build.sh. Overridable so the scripts can be
echo "Cloning dotfiles into the live environment ($LIVE_DOTFILES_DIR)..." # run from a checkout on a normal system, not just from the ISO.
_clone_ok=false ISO_DOTFILES="${ISO_DOTFILES:-/installer/dotfiles}"
while ! $_clone_ok; do
if [[ -d "$LIVE_DOTFILES_DIR/.git" ]]; then # A tree is only usable if the script the chroot step actually runs is in it.
_clone_ok=true # The old check was `-d $LIVE_DOTFILES_DIR/.git`, which a leftover or half-
elif git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "$LIVE_DOTFILES_DIR"; then # finished checkout satisfies — the install then went all the way to the last
_clone_ok=true # step before failing on a missing tui-install.sh.
else _dotfiles_tree_ok() { [[ -f "$1/setup/tui-install.sh" ]]; }
if $AF_MODE; then
echo "Warning: dotfiles clone failed — continuing without dotfiles." _clone_dotfiles() {
RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO" local url="https://git.abdelbaki.eu/The_miro/Dotfiles.git"
_clone_ok=true while :; do
else rm -rf "$LIVE_DOTFILES_DIR"
read -rp "Clone failed — retry? [y/N]: " _retry if git clone "$url" "$LIVE_DOTFILES_DIR" && _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then
[[ "${_retry,,}" == "y" ]] || { echo "Skipping dotfiles — clone manually after first boot."; RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO"; _clone_ok=true; } return 0
fi
fi 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 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 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 tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
arch-chroot /mnt chmod 0440 /etc/sudoers.d/99-setup-nopasswd 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 <cmd>` (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..." echo "Running tui-install.sh as ${USERNAME} inside chroot..."
# `runuser -u` switches to the unprivileged user inside the chroot so that # `runuser -u` switches to the unprivileged user inside the chroot so that
# AUR helpers and dotfiles are owned/built by the correct UID. # AUR helpers and dotfiles are owned/built by the correct UID.

View File

@ -560,36 +560,61 @@ if $AF_MODE; then
fi 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) — # The ISO embeds every installer script under /installer and a full repo
# tui-install.sh, its modules/, and desktopenvs/ configs exist only inside the # snapshot under /installer/dotfiles (see setup/tools/build.sh), so this step
# full repo. Clone it ONCE into the live environment whenever either RUN_TUI # is only about picking the best copy available, in order:
# or CLONE_SKEL_DOTFILES is YES, so both consumers below (the skel copy, and # 1. a checkout already sitting in the live environment (a re-run),
# the fallback copy straight into the new user's home when skel is skipped # 2. a fresh clone, so an install with working networking gets what is newest,
# but the TUI still needs to run) are served from one clone instead of each # 3. the ISO's own snapshot, so an offline install still gets a full tree.
# needing its own network fetch — and so a bare install genuinely fetches # Resolved ONCE, whenever either RUN_TUI or CLONE_SKEL_DOTFILES is YES, so both
# nothing at all. # 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" LIVE_DOTFILES_DIR="$HOME/Dotfiles"
if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" || "${RUN_TUI^^}" == "YES" ]]; then # Snapshot embedded by setup/tools/build.sh. Overridable so the scripts can be
echo "Cloning dotfiles into the live environment ($LIVE_DOTFILES_DIR)..." # run from a checkout on a normal system, not just from the ISO.
_clone_ok=false ISO_DOTFILES="${ISO_DOTFILES:-/installer/dotfiles}"
while ! $_clone_ok; do
if [[ -d "$LIVE_DOTFILES_DIR/.git" ]]; then # A tree is only usable if the script the chroot step actually runs is in it.
_clone_ok=true # The old check was `-d $LIVE_DOTFILES_DIR/.git`, which a leftover or half-
elif git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git "$LIVE_DOTFILES_DIR"; then # finished checkout satisfies — the install then went all the way to the last
_clone_ok=true # step before failing on a missing tui-install.sh.
else _dotfiles_tree_ok() { [[ -f "$1/setup/tui-install.sh" ]]; }
if $AF_MODE; then
echo "Warning: dotfiles clone failed — continuing without dotfiles." _clone_dotfiles() {
RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO" local url="https://git.abdelbaki.eu/The_miro/Dotfiles.git"
_clone_ok=true while :; do
else rm -rf "$LIVE_DOTFILES_DIR"
read -rp "Clone failed — retry? [y/N]: " _retry if git clone "$url" "$LIVE_DOTFILES_DIR" && _dotfiles_tree_ok "$LIVE_DOTFILES_DIR"; then
[[ "${_retry,,}" == "y" ]] || { echo "Skipping dotfiles — clone manually after first boot."; RUN_TUI="NO"; CLONE_SKEL_DOTFILES="NO"; _clone_ok=true; } return 0
fi
fi 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 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 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 tee /etc/sudoers.d/99-setup-nopasswd > /dev/null
arch-chroot /mnt chmod 0440 /etc/sudoers.d/99-setup-nopasswd 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 <cmd>` (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..." echo "Running tui-install.sh as ${USERNAME} inside chroot..."
# `runuser -u` switches to the unprivileged user inside the chroot so that # `runuser -u` switches to the unprivileged user inside the chroot so that
# AUR helpers and dotfiles are owned/built by the correct UID. # AUR helpers and dotfiles are owned/built by the correct UID.

View File

@ -9,7 +9,7 @@
install-arch guided installer (manual mode) install-arch guided installer (manual mode)
install-arch auto automated installer (requires answerfile) 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 Dotfiles: https://git.abdelbaki.eu/The_miro/Dotfiles

View File

@ -1,6 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail 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) # Keyboard layout (live environment)

View File

@ -40,9 +40,9 @@ ANSWERFILE="/answerfile.json"
if [[ -f "$ANSWERFILE" ]]; then if [[ -f "$ANSWERFILE" ]]; then
# Automated mode: pass "auto" as the first arg and the answerfile path as # Automated mode: pass "auto" as the first arg and the answerfile path as
# the second. launch.sh will forward these to arch-autoinstall.sh. # the second. launch.sh will forward these to arch-autoinstall.sh.
exec /root/launch.sh auto "$ANSWERFILE" exec /installer/launch.sh auto "$ANSWERFILE"
else else
# Guided mode: start the interactive installer. The user will be prompted # Guided mode: start the interactive installer. The user will be prompted
# for keyboard layout and installation action before anything happens. # for keyboard layout and installation action before anything happens.
exec /root/launch.sh guided exec /installer/launch.sh guided
fi fi

View File

@ -1,2 +1,2 @@
#!/usr/bin/env bash #!/usr/bin/env bash
exec /root/launch.sh "$@" exec /installer/launch.sh "$@"

View File

@ -19,8 +19,15 @@ file_permissions=(
["/root"]="0:0:750" ["/root"]="0:0:750"
["/root/.automated_script.sh"]="0:0:755" ["/root/.automated_script.sh"]="0:0:755"
["/root/.zlogin"]="0:0:644" ["/root/.zlogin"]="0:0:644"
["/root/launch.sh"]="0:0:755" # /installer holds every installer script (build.sh embeds the whole setup/
["/root/installer/archbaseos-guided-install.sh"]="0:0:755" # tree there, plus a full repo snapshot at /installer/dotfiles). Only the
["/root/installer/arch-autoinstall.sh"]="0:0:755" # entry points need an explicit mode here — build.sh chmods the rest, and
# everything below them is invoked as `bash <script>` anyway.
["/installer"]="0:0:755"
["/installer/launch.sh"]="0:0:755"
["/installer/archbaseos-guided-install.sh"]="0:0:755"
["/installer/arch-autoinstall.sh"]="0:0:755"
["/installer/reset-arch.sh"]="0:0:755"
["/installer/tui-install.sh"]="0:0:755"
["/usr/local/bin/install-arch"]="0:0:755" ["/usr/local/bin/install-arch"]="0:0:755"
) )

View File

@ -13,7 +13,7 @@
# Logo resolution order: # Logo resolution order:
# 1. $PLYMOUTH_LOGO_SRC env var — caller-supplied custom image (PNG or SVG) # 1. $PLYMOUTH_LOGO_SRC env var — caller-supplied custom image (PNG or SVG)
# 2. $DOTFILES_DIR/resources/bg-skull.svg — repo copy, always present # 2. $DOTFILES_DIR/resources/bg-skull.svg — repo copy, always present
# 3. /root/installer/resources/bg-skull.svg — archiso embedded copy # 3. /installer/dotfiles/resources/bg-skull.svg — archiso embedded snapshot
# #
# PNG inputs are used directly; SVG inputs are converted via rsvg-convert. # PNG inputs are used directly; SVG inputs are converted via rsvg-convert.
# #
@ -40,7 +40,7 @@ LOGO_SRC="${PLYMOUTH_LOGO_SRC:-}"
if [[ -z "$LOGO_SRC" ]]; then if [[ -z "$LOGO_SRC" ]]; then
for _candidate in \ for _candidate in \
"$DOTFILES_DIR/resources/bg-skull.svg" \ "$DOTFILES_DIR/resources/bg-skull.svg" \
"/root/installer/resources/bg-skull.svg" "/installer/dotfiles/resources/bg-skull.svg"
do do
if [[ -f "$_candidate" ]]; then if [[ -f "$_candidate" ]]; then
LOGO_SRC="$_candidate" LOGO_SRC="$_candidate"

View File

@ -16,7 +16,8 @@
# 3. Copy the upstream releng profile to a working directory. # 3. Copy the upstream releng profile to a working directory.
# 4. Apply the M-Archy overlay (custom scripts, profiledef, mkinitcpio config). # 4. Apply the M-Archy overlay (custom scripts, profiledef, mkinitcpio config).
# 5. Merge extra packages (packages.extra) into the releng package list. # 5. Merge extra packages (packages.extra) into the releng package list.
# 6. Embed the installer shell scripts into the ISO's /root/installer/. # 6. Embed the whole setup/ tree into the ISO's /installer/, plus a full repo
# snapshot at /installer/dotfiles/ so installs work with no network.
# 7. Optionally embed an answerfile.json for automated installs. # 7. Optionally embed an answerfile.json for automated installs.
# 8. Run mkarchiso to produce the final .iso and netboot tarball. # 8. Run mkarchiso to produce the final .iso and netboot tarball.
# 9. Optionally write an iPXE chainload script for netboot.xyz / WDS. # 9. Optionally write an iPXE chainload script for netboot.xyz / WDS.
@ -184,33 +185,37 @@ while IFS= read -r pkg || [[ -n "$pkg" ]]; do
grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64" grep -qxF "$pkg" "$PROFILE/packages.x86_64" || echo "$pkg" >> "$PROFILE/packages.x86_64"
done < "$OVERLAY_DIR/packages.extra" done < "$OVERLAY_DIR/packages.extra"
# ── Embed installer scripts ──────────────────────────────────────────────────── # ── Embed the installer scripts (the whole setup/ tree) ───────────────────────
# These three scripts live in the main setup/ directory of the dotfiles repo # EVERY installer script goes into /installer on the live ISO, not just the
# and implement the actual Arch Linux installation logic. They are placed in # three entry points launch.sh dispatches to. tui-install.sh and its modules/
# /root/installer/ on the live ISO so the auto-launch scripts can find them. # used to exist only inside the repo the installer clones at run time, so a live
echo "Embedding installer scripts..." # environment with no network — or a briefly unreachable Gitea — got all the way
mkdir -p "$PROFILE/airootfs/root/installer" # to "Running tui-install.sh ..." and then died on a missing file. Logs that
# Guided interactive installer — walks the user through partitioning, locale, etc. # happen to be committed under setup/ are skipped: build junk, not installer code.
cp "$DOTFILES_DIR/setup/archbaseos-guided-install.sh" "$PROFILE/airootfs/root/installer/" echo "Embedding installer scripts (setup/ → /installer)..."
# Automated unattended installer — reads /answerfile.json and installs silently. mkdir -p "$PROFILE/airootfs/installer"
cp "$DOTFILES_DIR/setup/arch-autoinstall.sh" "$PROFILE/airootfs/root/installer/" tar -C "$DOTFILES_DIR/setup" --exclude='*.log' -cf - . \
# Reset script — wipes and reinstalls the system while preserving /home. | tar -C "$PROFILE/airootfs/installer" -xf -
cp "$DOTFILES_DIR/setup/reset-arch.sh" "$PROFILE/airootfs/root/installer/"
echo "Embedding resources (branding assets used by post-install modules)..." # ── Embed a full snapshot of the repo ─────────────────────────────────────────
# resources/ contains shared assets (SVGs, etc.) referenced by installer modules # The installer scripts alone still aren't a complete offline install: the TUI
# such as the Plymouth splash logo. Embedding them here means the ISO carries # deploys desktopenvs/ configs, colors.conf, resources/ and friends out of the
# everything needed so post-install steps never require the user to supply files. # repo. Ship the whole thing (~31 MB, and mksquashfs deduplicates the setup/
mkdir -p "$PROFILE/airootfs/root/installer/resources" # copy above) so the installers can seed ~/Dotfiles from the ISO whenever the
cp -r "$DOTFILES_DIR/resources/." "$PROFILE/airootfs/root/installer/resources/" # clone can't run. .git is included on purpose — a seeded checkout is then a
# real repo that can `git pull` later, exactly like a cloned one.
echo "Embedding dotfiles snapshot (/installer/dotfiles)..."
mkdir -p "$PROFILE/airootfs/installer/dotfiles"
tar -C "$DOTFILES_DIR" --exclude='*.log' -cf - . \
| tar -C "$PROFILE/airootfs/installer/dotfiles" -xf -
# Make all scripts executable. The archiso tool preserves these bits in the # Make the scripts executable. The archiso tool preserves these bits in the
# squashfs, so they will be executable on the live system too. # squashfs, so they will be executable on the live system too.
chmod 755 \ chmod 755 \
"$PROFILE/airootfs/root/launch.sh" \ "$PROFILE/airootfs/installer/launch.sh" \
"$PROFILE/airootfs/root/.automated_script.sh" \ "$PROFILE/airootfs/root/.automated_script.sh" \
"$PROFILE/airootfs/usr/local/bin/install-arch" \ "$PROFILE/airootfs/usr/local/bin/install-arch"
"$PROFILE/airootfs/root/installer/"*.sh find "$PROFILE/airootfs/installer" -name '*.sh' -exec chmod 755 {} +
# ── Embed answerfile (--preconf) ─────────────────────────────────────────────── # ── Embed answerfile (--preconf) ───────────────────────────────────────────────
# An answerfile baked into the ISO lets machines boot and install completely # An answerfile baked into the ISO lets machines boot and install completely

View File

@ -23,6 +23,22 @@ TMP_D="$(mktemp -d)"
# '|| true' ensures the trap never returns a non-zero code, which would mask the real exit status. # '|| true' ensures the trap never returns a non-zero code, which would mask the real exit status.
trap 'rm -rf "$TMP_D"; tput reset 2>/dev/null || stty sane 2>/dev/null || true' EXIT INT TERM HUP trap 'rm -rf "$TMP_D"; tput reset 2>/dev/null || stty sane 2>/dev/null || true' EXIT INT TERM HUP
# ── sudo shim (root contexts) ─────────────────────────────────────────────────
# Module scripts call `sudo` for privileged work. When this script is already
# running as root — an archiso chroot, or a run straight out of /installer on
# the live ISO — there may be no sudo binary at all, and there is certainly
# nothing to authenticate against. Shim it as a plain passthrough so nothing
# ever stops to ask for a password. Installed this early (rather than in the
# preflight section further down) because require_jq() below already shells out
# through sudo.
if [[ $EUID -eq 0 ]]; then
mkdir -p "$TMP_D/bin"
printf '#!/bin/bash\nexec "$@"\n' > "$TMP_D/bin/sudo"
chmod +x "$TMP_D/bin/sudo"
# Prepend to PATH so this shim takes precedence over any real sudo.
export PATH="$TMP_D/bin:$PATH"
fi
# Allow the caller to inject a different answerfile path via the ANSWERFILE env var. # Allow the caller to inject a different answerfile path via the ANSWERFILE env var.
# The default /answerfile.json is a conventional location for CI/PXE boot images. # The default /answerfile.json is a conventional location for CI/PXE boot images.
ANSWERFILE="${ANSWERFILE:-/answerfile.json}" ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
@ -455,17 +471,7 @@ load_answerfile() {
} }
# ── Preflight ───────────────────────────────────────────────────────────────── # ── Preflight ─────────────────────────────────────────────────────────────────
if [[ $EUID -eq 0 ]]; then # (the root sudo shim lives at the top of this file — require_jq() needs it)
# Root context (e.g. archiso chroot): shim sudo as a passthrough
# Module scripts call 'sudo' for privilege operations; when we are already root
# there is no sudo binary (or it may be absent), so inject a fake one that is
# just 'exec "$@"' — transparently forwarding all arguments.
mkdir -p "$TMP_D/bin"
printf '#!/bin/bash\nexec "$@"\n' > "$TMP_D/bin/sudo"
chmod +x "$TMP_D/bin/sudo"
# Prepend to PATH so this shim takes precedence over any real sudo.
export PATH="$TMP_D/bin:$PATH"
fi
# Hard guard: pacman is the package manager used by every module; without it # Hard guard: pacman is the package manager used by every module; without it
# the installer cannot function on this OS. # the installer cannot function on this OS.
command -v pacman &>/dev/null || die "pacman not found — Arch Linux required." command -v pacman &>/dev/null || die "pacman not found — Arch Linux required."