feat(archiso): support fully bare Arch installs and fix greetd/DM conflicts

Decouple the base installer's "run dotfiles TUI" choice from cloning the
dotfiles repo into /etc/skel and from the new user's default shell, so
declining both actually yields a bare system: no dotfiles, plain bash,
no DE, no display manager, and a working agetty login on tty1. Also add
an explicit toggle in the TUI for syncing the configured user's ~/.config
back into /etc/skel, previously unconditional.

Separately, core.sh's "svc" component unconditionally staged a greetd
config that launches Hyprland and enabled greetd.service — even when no
DE was selected, and even for DEs (GNOME, KDE Plasma, COSMIC, XFCE,
LXQt, Sway) that install their own display manager. That left greetd
racing the DE's native DM (or agetty, in the no-DE case) for tty1, and
Sway additionally got a greeter pointed at the wrong compositor. Gate
greetd on the DE selection and have every DE module disown getty/greetd
before enabling its own DM, matching the pattern hyprland.sh/niri.sh
already used; add a Sway-specific tuigreet config.
main
Amir Alexander Abdelbaki 2026-07-13 12:08:13 +02:00
parent bdecba0cf3
commit 3b67b23944
12 changed files with 280 additions and 61 deletions

View File

@ -0,0 +1,16 @@
[terminal]
# The VT to run the greeter on. Can be "next", "current" or a number
# designating the VT.
vt = 1
# The default session, also known as the greeter.
[default_session]
# `agreety` is the bundled agetty/login-lookalike. You can replace `/bin/sh`
# with whatever you want started, such as `sway`.
command = "tuigreet --cmd sway"
# The user to run the command as. The privileges this user must have depends
# on the greeter. A graphical greeter may for example require the user to be
# in the `video` group.
user = "greeter"

View File

@ -5,7 +5,12 @@
# all prompts are answered from it. Missing fields fall back to interactive prompts. # all prompts are answered from it. Missing fields fall back to interactive prompts.
# #
# Answerfile fields: drive, kernel, keymap, hostname, username, encrypt, fido2_root, # Answerfile fields: drive, kernel, keymap, hostname, username, encrypt, fido2_root,
# fido2_user, run_tui, password, luks_password # fido2_user, run_tui, clone_skel_dotfiles, password, luks_password
# "clone_skel_dotfiles" is independent of "run_tui": it controls only whether the
# dotfiles repo is cloned into /etc/skel. When omitted it defaults to run_tui's
# value, preserving the old coupled behaviour. Set both false for a fully bare
# install: no encryption (encrypt:false), no dotfiles, no DE, no modules, plain
# bash + agetty login.
# The user "password" (and "luks_password" for encrypted installs) may be supplied # The user "password" (and "luks_password" for encrypted installs) may be supplied
# in the answerfile for fully unattended deployment; when omitted they are prompted # in the answerfile for fully unattended deployment; when omitted they are prompted
# interactively. Storing secrets in the answerfile is a convenience/secret-handling # interactively. Storing secrets in the answerfile is a convenience/secret-handling
@ -261,6 +266,15 @@ if $AF_MODE; then
FIDO_ROOT=$(af_bool '.fido2_root') FIDO_ROOT=$(af_bool '.fido2_root')
FIDO_USER=$(af_bool '.fido2_user') FIDO_USER=$(af_bool '.fido2_user')
RUN_TUI=$(af_bool '.run_tui') RUN_TUI=$(af_bool '.run_tui')
# Independent of run_tui: whether to clone the raw dotfiles repo into
# /etc/skel for future users. Falls back to RUN_TUI when the answerfile
# omits the field, preserving the old coupled behaviour for existing files.
CLONE_SKEL_DOTFILES=$(jq -r '.clone_skel_dotfiles // empty' "$ANSWERFILE" 2>/dev/null || true)
case "$CLONE_SKEL_DOTFILES" in
true) CLONE_SKEL_DOTFILES="YES" ;;
false) CLONE_SKEL_DOTFILES="NO" ;;
*) CLONE_SKEL_DOTFILES="$RUN_TUI" ;;
esac
echo "Kernel: $KERNEL" echo "Kernel: $KERNEL"
echo "Hostname: $HOSTNAME" echo "Hostname: $HOSTNAME"
echo "Username: $USERNAME" echo "Username: $USERNAME"
@ -294,10 +308,21 @@ fi
# In interactive mode, decide now whether to run the dotfiles TUI inside chroot. # In interactive mode, decide now whether to run the dotfiles TUI inside chroot.
# AF_MODE already has RUN_TUI set from the answerfile. # AF_MODE already has RUN_TUI set from the answerfile.
if ! $AF_MODE; then if ! $AF_MODE; then
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _RUN_TUI_IN # Answering "no" also skips cloning dotfiles into /etc/skel and leaves the new
# user's shell as /bin/bash, producing a bare Arch install with no DE, no
# modules, and plain agetty login (a display manager only gets installed as a
# module inside the TUI, so skipping the TUI means none is ever installed).
read -rp "Run dotfiles TUI setup inside chroot now? (NO = bare Arch install: no dotfiles, no DE, plain bash + agetty) [YES/no]: " _RUN_TUI_IN
# Default to YES if the user presses Enter without typing anything. # Default to YES if the user presses Enter without typing anything.
_RUN_TUI_IN="${_RUN_TUI_IN:-YES}" _RUN_TUI_IN="${_RUN_TUI_IN:-YES}"
[[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO" [[ "${_RUN_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
# Independent toggle: cloning the raw dotfiles repo into /etc/skel doesn't
# require running the TUI, and declining it keeps a fully bare install even
# if the TUI does run.
read -rp "Clone dotfiles repo into /etc/skel for future users? [YES/no]: " _CLONE_IN
_CLONE_IN="${_CLONE_IN:-YES}"
[[ "${_CLONE_IN^^}" == "YES" ]] && CLONE_SKEL_DOTFILES="YES" || CLONE_SKEL_DOTFILES="NO"
fi fi
############################################ ############################################
@ -505,7 +530,7 @@ fi
# Variables must be exported so the arch-chroot heredoc inherits them. # Variables must be exported so the arch-chroot heredoc inherits them.
# The heredoc uses single-quote delimiter ('CHROOT_EOF') which prevents expansion # The heredoc uses single-quote delimiter ('CHROOT_EOF') which prevents expansion
# outside the chroot, so env vars must be in the environment, not inline. # outside the chroot, so env vars must be in the environment, not inline.
export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK KEYMAP export HOSTNAME USERNAME USERPASS ROOT_PART KERNEL FIDO_ROOT FIDO_USER ENCRYPT_DISK KEYMAP RUN_TUI CLONE_SKEL_DOTFILES
############################################ ############################################
# CHROOT CONFIGURATION # CHROOT CONFIGURATION
@ -541,15 +566,29 @@ systemctl enable sshd
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
echo "root:${USERPASS}" | chpasswd echo "root:${USERPASS}" | chpasswd
# Populate /etc/skel before user creation so useradd -m copies everything # Populate /etc/skel before user creation so useradd -m copies everything.
# Gated by its own toggle, independent of RUN_TUI: declining it means the new
# user gets a bare /etc/skel (system defaults) instead of a clone of the
# dotfiles repo sitting in their home directory.
if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" ]]; then
echo "Cloning dotfiles into /etc/skel..." echo "Cloning dotfiles into /etc/skel..."
git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /etc/skel/Dotfiles \ git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git /etc/skel/Dotfiles \
|| echo "Warning: dotfiles clone failed — clone manually after first boot." || echo "Warning: dotfiles clone failed — clone manually after first boot."
mkdir -p /etc/skel/{Desktop,Documents,Downloads,Music,Pictures,Public,Templates,Videos} mkdir -p /etc/skel/{Desktop,Documents,Downloads,Music,Pictures,Public,Templates,Videos}
else
echo "Skipping dotfiles clone — clone_skel_dotfiles=NO."
fi
# User # User
# -m: create home dir; -G wheel: add to sudoers group; -s /bin/zsh: default shell. # -m: create home dir (from /etc/skel above); -G wheel: add to sudoers group.
useradd -m -G wheel -s /bin/zsh "$USERNAME" # Default shell follows the same bare/configured choice as the skel clone: zsh
# only when the dotfiles TUI will run to set it up, plain bash otherwise.
if [[ "${RUN_TUI^^}" == "YES" ]]; then
NEW_USER_SHELL="/bin/zsh"
else
NEW_USER_SHELL="/bin/bash"
fi
useradd -m -G wheel -s "$NEW_USER_SHELL" "$USERNAME"
# chpasswd reads "user:pass" from stdin to set the password non-interactively. # chpasswd reads "user:pass" from stdin to set the password non-interactively.
echo "$USERNAME:$USERPASS" | chpasswd echo "$USERNAME:$USERPASS" | chpasswd
# Grant wheel group full sudo access (ALL:ALL covers any user/group runas context). # Grant wheel group full sudo access (ALL:ALL covers any user/group runas context).
@ -710,6 +749,9 @@ echo "FIDO2 root unlock: $FIDO_ROOT"
echo "FIDO2 user login: $FIDO_USER" echo "FIDO2 user login: $FIDO_USER"
[[ "$ENCRYPT_DISK" == "YES" ]] && echo "LUKS backup key: /_LUKS_BACKUP_KEY (in new system)" [[ "$ENCRYPT_DISK" == "YES" ]] && echo "LUKS backup key: /_LUKS_BACKUP_KEY (in new system)"
echo echo
echo "Run TUI: $RUN_TUI"
echo "Clone to skel: $CLONE_SKEL_DOTFILES"
echo
echo "Boot size: $BOOT_SIZE" echo "Boot size: $BOOT_SIZE"
echo "Root size: ${ROOT_GIB}GiB" echo "Root size: ${ROOT_GIB}GiB"
echo "Swap size: $SWAP_SIZE" echo "Swap size: $SWAP_SIZE"
@ -726,6 +768,11 @@ echo "Installation complete! Unmount and reboot:"
echo " umount -R /mnt && reboot" echo " umount -R /mnt && reboot"
if [[ "${RUN_TUI^^}" != "YES" ]]; then if [[ "${RUN_TUI^^}" != "YES" ]]; then
echo echo
if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" ]]; then
echo "After first boot, login as $USERNAME and run:" echo "After first boot, login as $USERNAME and run:"
echo " ~/Dotfiles/setup/tui-install.sh" echo " ~/Dotfiles/setup/tui-install.sh"
else
echo "Bare install — no dotfiles were cloned. To set up dotfiles/DE later, run:"
echo " git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git ~/Dotfiles && ~/Dotfiles/setup/tui-install.sh"
fi
fi fi

View File

@ -202,6 +202,15 @@ if $AF_MODE; then
ENABLE_FIDO_ROOT=$(af_bool '.fido2_root') ENABLE_FIDO_ROOT=$(af_bool '.fido2_root')
ENABLE_FIDO_USER=$(af_bool '.fido2_user') ENABLE_FIDO_USER=$(af_bool '.fido2_user')
RUN_TUI=$(af_bool '.run_tui') RUN_TUI=$(af_bool '.run_tui')
# Independent of run_tui: whether to clone the raw dotfiles repo into
# /etc/skel for future users. Falls back to RUN_TUI when the answerfile
# omits the field, preserving the old coupled behaviour for existing files.
CLONE_SKEL_DOTFILES=$(jq -r '.clone_skel_dotfiles // empty' "$ANSWERFILE" 2>/dev/null || true)
case "$CLONE_SKEL_DOTFILES" in
true) CLONE_SKEL_DOTFILES="YES" ;;
false) CLONE_SKEL_DOTFILES="NO" ;;
*) CLONE_SKEL_DOTFILES="$RUN_TUI" ;;
esac
KEYMAP=$(af_get '.keymap' 'us') KEYMAP=$(af_get '.keymap' 'us')
echo "Kernel: $KERNEL / Hostname: $HOSTNAME / Username: $USERNAME" echo "Kernel: $KERNEL / Hostname: $HOSTNAME / Username: $USERNAME"
echo "Encrypt: $ENCRYPT_DISK / FIDO2 root: $ENABLE_FIDO_ROOT / FIDO2 user: $ENABLE_FIDO_USER" echo "Encrypt: $ENCRYPT_DISK / FIDO2 root: $ENABLE_FIDO_ROOT / FIDO2 user: $ENABLE_FIDO_USER"
@ -240,9 +249,20 @@ else
# Whether to run the dotfiles TUI inside the chroot — asked now so the whole # Whether to run the dotfiles TUI inside the chroot — asked now so the whole
# back half of the install can proceed without further interaction. # back half of the install can proceed without further interaction.
read -rp "Run dotfiles TUI setup inside chroot after base install? [YES/no]: " _TUI_IN # Answering "no" also skips cloning dotfiles into /etc/skel and leaves the
# new user's shell as /bin/bash, producing a bare Arch install with no DE,
# no modules, and plain agetty login (no display manager gets installed
# since that only happens as a module inside the TUI).
read -rp "Run dotfiles TUI setup inside chroot after base install? (NO = bare Arch install: no dotfiles, no DE, plain bash + agetty) [YES/no]: " _TUI_IN
_TUI_IN="${_TUI_IN:-YES}" _TUI_IN="${_TUI_IN:-YES}"
[[ "${_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO" [[ "${_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
# Independent toggle: cloning the raw dotfiles repo into /etc/skel doesn't
# require running the TUI (e.g. staging it for later manual use), and
# declining it keeps a fully bare install even if the TUI does run.
read -rp "Clone dotfiles repo into /etc/skel for future users? [YES/no]: " _CLONE_IN
_CLONE_IN="${_CLONE_IN:-YES}"
[[ "${_CLONE_IN^^}" == "YES" ]] && CLONE_SKEL_DOTFILES="YES" || CLONE_SKEL_DOTFILES="NO"
fi fi
# ── Target drive ────────────────────────────────────────────────────────────── # ── Target drive ──────────────────────────────────────────────────────────────
@ -292,6 +312,7 @@ echo " Encrypt disk: $ENCRYPT_DISK"
echo " FIDO2 root: $ENABLE_FIDO_ROOT" echo " FIDO2 root: $ENABLE_FIDO_ROOT"
echo " FIDO2 user: $ENABLE_FIDO_USER" echo " FIDO2 user: $ENABLE_FIDO_USER"
echo " Run TUI: $RUN_TUI" echo " Run TUI: $RUN_TUI"
echo " Clone to skel: $CLONE_SKEL_DOTFILES"
echo " Target drive: $DRIVE" echo " Target drive: $DRIVE"
echo "──────────────────────────────────────────────" echo "──────────────────────────────────────────────"
echo " WARNING: ALL DATA on $DRIVE will be PERMANENTLY ERASED." echo " WARNING: ALL DATA on $DRIVE will be PERMANENTLY ERASED."
@ -491,11 +512,15 @@ if $AF_MODE; then
fi fi
############################################ ############################################
# DOTFILES CLONE TO SKEL (with retry) # DOTFILES CLONE TO SKEL (with retry, gated by its own toggle)
############################################ ############################################
# Independent of RUN_TUI: declining this means the new user gets a bare
# /etc/skel (system defaults) rather than a clone of the dotfiles repo sitting
# in their home directory.
# Clone before entering the chroot so useradd -m inside will copy Dotfiles to # Clone before entering the chroot so useradd -m inside will copy Dotfiles to
# the new user's home. Interactive retry is available so a transient network # the new user's home. Interactive retry is available so a transient network
# failure doesn't silently leave the system without dotfiles. # failure doesn't silently leave the system without dotfiles.
if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" ]]; then
echo "Cloning dotfiles into /mnt/etc/skel..." echo "Cloning dotfiles into /mnt/etc/skel..."
_clone_ok=false _clone_ok=false
while ! $_clone_ok; do while ! $_clone_ok; do
@ -512,6 +537,9 @@ while ! $_clone_ok; do
fi fi
fi fi
done done
else
echo "Skipping dotfiles clone — clone_skel_dotfiles=NO."
fi
############################################ ############################################
# CHROOT Configuration # CHROOT Configuration
@ -534,6 +562,7 @@ arch-chroot /mnt /usr/bin/env \
ROOT_UUID="$ROOT_UUID" \ ROOT_UUID="$ROOT_UUID" \
ROOT_PART="$ROOT_PART" \ ROOT_PART="$ROOT_PART" \
KEYMAP="$KEYMAP" \ KEYMAP="$KEYMAP" \
RUN_TUI="$RUN_TUI" \
/bin/bash <<'CHROOT_EOF' /bin/bash <<'CHROOT_EOF'
set -euo pipefail set -euo pipefail
@ -554,8 +583,15 @@ echo "$HOSTNAME" > /etc/hostname
# Enable NetworkManager at boot so the installed system has networking on first login. # Enable NetworkManager at boot so the installed system has networking on first login.
systemctl enable NetworkManager systemctl enable NetworkManager
# -m: create home from /etc/skel (dotfiles cloned before chroot); -G wheel: allow sudo; -s /bin/zsh: default shell. # -m: create home from /etc/skel (dotfiles cloned before chroot, gated by its own toggle above); -G wheel: allow sudo.
useradd -m -G wheel -s /bin/zsh "$USERNAME" # Default shell follows RUN_TUI, not the skel toggle: zsh only when the dotfiles
# TUI will actually run to set it up, plain bash (Arch's stock shell) otherwise.
if [[ "${RUN_TUI^^}" == "YES" ]]; then
NEW_USER_SHELL="/bin/zsh"
else
NEW_USER_SHELL="/bin/bash"
fi
useradd -m -G wheel -s "$NEW_USER_SHELL" "$USERNAME"
# chpasswd reads "user:pass" from stdin to set the password non-interactively. # chpasswd reads "user:pass" from stdin to set the password non-interactively.
echo "$USERNAME:$USERPASS" | chpasswd echo "$USERNAME:$USERPASS" | chpasswd
# Ensure all files under the new home dir are owned by the user (skel copy may be root-owned). # Ensure all files under the new home dir are owned by the user (skel copy may be root-owned).
@ -696,8 +732,13 @@ echo "Installation complete! Log saved to /mnt/boot/$(basename "$LOGFILE")"
echo " umount -R /mnt && reboot" echo " umount -R /mnt && reboot"
if [[ "${_DO_TUI^^}" != "YES" ]]; then if [[ "${_DO_TUI^^}" != "YES" ]]; then
echo echo
if [[ "${CLONE_SKEL_DOTFILES^^}" == "YES" ]]; then
echo "After first boot, login as ${USERNAME} and run:" echo "After first boot, login as ${USERNAME} and run:"
echo " ~/Dotfiles/setup/tui-install.sh" echo " ~/Dotfiles/setup/tui-install.sh"
else
echo "Bare install — no dotfiles were cloned. To set up dotfiles/DE later, run:"
echo " git clone https://git.abdelbaki.eu/The_miro/Dotfiles.git ~/Dotfiles && ~/Dotfiles/setup/tui-install.sh"
fi
fi fi
if [[ "$ENCRYPT_DISK" == "YES" ]]; then if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo echo

View File

@ -59,6 +59,16 @@ sudo pacman -S --noconfirm --needed \
log "Enabling services..." log "Enabling services..."
# getty@tty1 is the default text-mode login prompt; COSMIC's own greeter (or
# the SDDM fallback below) replaces it. '|| true' prevents abort if the unit
# is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# Disable greetd, which core.sh enables by default for every install. This DE
# uses its own display manager below, so leaving greetd enabled means two
# display managers both race to claim tty1.
disable_service greetd.service
# COSMIC ships its own display manager (cosmic-greeter) built with iced. # COSMIC ships its own display manager (cosmic-greeter) built with iced.
# Prefer it over SDDM for a cohesive look-and-feel; fall back to SDDM when # Prefer it over SDDM for a cohesive look-and-feel; fall back to SDDM when
# the package is not present (e.g. on systems where cosmic-greeter is still # the package is not present (e.g. on systems where cosmic-greeter is still

View File

@ -61,6 +61,15 @@ sudo pacman -S --noconfirm --needed \
log "Enabling services..." log "Enabling services..."
# getty@tty1 is the default text-mode login prompt; GDM replaces it.
# '|| true' prevents abort if the unit is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# Disable greetd, which core.sh enables by default for every install. GDM is
# this DE's own display manager, so leaving greetd enabled means two display
# managers both race to claim tty1.
disable_service greetd.service
# GDM (GNOME Display Manager) handles the login screen and session startup. # GDM (GNOME Display Manager) handles the login screen and session startup.
# It is bundled with the 'gnome' group and is the canonical choice for GNOME. # It is bundled with the 'gnome' group and is the canonical choice for GNOME.
sudo systemctl enable gdm.service sudo systemctl enable gdm.service

View File

@ -28,6 +28,15 @@ sudo pacman -S --noconfirm --needed \
flatpak flatpak
log "Enabling services..." log "Enabling services..."
# getty@tty1 is the default text-mode login prompt; SDDM replaces it.
# '|| true' prevents abort if the unit is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# Disable greetd, which core.sh enables by default for every install. SDDM is
# this DE's own display manager, so leaving greetd enabled means two display
# managers both race to claim tty1.
disable_service greetd.service
# sddm provides the graphical login screen; must be enabled before rebooting. # sddm provides the graphical login screen; must be enabled before rebooting.
sudo systemctl enable sddm.service sudo systemctl enable sddm.service
sudo systemctl enable NetworkManager.service sudo systemctl enable NetworkManager.service

View File

@ -17,6 +17,15 @@ sudo pacman -S --noconfirm --needed \
flatpak flatpak
log "Enabling services..." log "Enabling services..."
# getty@tty1 is the default text-mode login prompt; SDDM replaces it.
# '|| true' prevents abort if the unit is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# Disable greetd, which core.sh enables by default for every install. SDDM is
# this DE's own display manager, so leaving greetd enabled means two display
# managers both race to claim tty1.
disable_service greetd.service
sudo systemctl enable sddm.service sudo systemctl enable sddm.service
sudo systemctl enable NetworkManager.service sudo systemctl enable NetworkManager.service
sudo systemctl enable bluetooth.service sudo systemctl enable bluetooth.service

View File

@ -9,7 +9,7 @@ log "Installing required packages..."
sudo pacman -Syu --noconfirm --needed \ sudo pacman -Syu --noconfirm --needed \
brightnessctl btop dmenu foot glfw grim \ brightnessctl btop dmenu foot glfw grim \
greetd-tuigreet gst-plugin-pipewire imagemagick iwd libpulse \ greetd-tuigreet gst-plugin-pipewire imagemagick iwd libpulse \
libva-intel-driver libva-mesa-driver lightdm lightdm-gtk-greeter \ libva-intel-driver libva-mesa-driver \
networkmanager pavucontrol pipewire pipewire-alsa pipewire-jack pipewire-pulse \ networkmanager pavucontrol pipewire pipewire-alsa pipewire-jack pipewire-pulse \
slurp sway swaybg swayidle swaylock alacritty \ slurp sway swaybg swayidle swaylock alacritty \
ttf-jetbrains-mono vulkan-intel vulkan-radeon waybar wget \ ttf-jetbrains-mono vulkan-intel vulkan-radeon waybar wget \
@ -22,9 +22,16 @@ sudo pacman -Syu --noconfirm --needed \
log "Enabling services..." log "Enabling services..."
sudo systemctl enable NetworkManager.service sudo systemctl enable NetworkManager.service
# getty@tty1 is the default text-mode login prompt; greetd replaces it.
# '|| true' prevents abort if the unit is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# 3. greetd config # 3. greetd config
# Use the sway-specific tuigreet config (`--cmd sway`), not the Hyprland one —
# core.sh's default greeter launches Hyprland, which was never installed here.
log "Deploying greetd config..." log "Deploying greetd config..."
sudo cp -f ~/Dotfiles/desktopenvs/hyprland/greetd-tuigreet/config.toml /etc/greetd/config.toml sudo mkdir -p /etc/greetd
sudo cp -f ~/Dotfiles/desktopenvs/sway/greetd-tuigreet/config.toml /etc/greetd/config.toml
sudo systemctl enable greetd.service sudo systemctl enable greetd.service
# 4. Copy DE configs # 4. Copy DE configs

View File

@ -15,6 +15,15 @@ sudo pacman -S --noconfirm --needed \
flatpak flatpak
log "Enabling services..." log "Enabling services..."
# getty@tty1 is the default text-mode login prompt; LightDM replaces it.
# '|| true' prevents abort if the unit is already disabled or doesn't exist.
sudo systemctl disable getty@tty1.service || true
# Disable greetd, which core.sh enables by default for every install. LightDM
# is this DE's own display manager, so leaving greetd enabled means two
# display managers both race to claim tty1.
disable_service greetd.service
sudo systemctl enable lightdm.service sudo systemctl enable lightdm.service
sudo systemctl enable NetworkManager.service sudo systemctl enable NetworkManager.service
sudo systemctl enable bluetooth.service sudo systemctl enable bluetooth.service

View File

@ -48,6 +48,13 @@ enable_service cronie.service
# then enable the service. The config.toml specifies which greeter to run # then enable the service. The config.toml specifies which greeter to run
# and optionally auto-login settings. # and optionally auto-login settings.
# -f flag forces overwrite of any existing config. # -f flag forces overwrite of any existing config.
#
# MARCHY_DE is exported by tui-install.sh once the DE menu is answered. When no
# DE was selected ("none"), staging a greeter that launches Hyprland — which
# was never installed — leaves a broken login screen racing tty1's own agetty
# instead of a working plain-console login. Every DE module that actually wants
# this greeter also re-enables it itself, so it's safe to skip here entirely.
if [[ "${MARCHY_DE:-none}" != "none" ]]; then
log "Deploying greetd config..." log "Deploying greetd config..."
# greetd may not be installed yet at this point (the DE module pulls in # greetd may not be installed yet at this point (the DE module pulls in
# greetd-tuigreet later), so /etc/greetd does not exist and the cp would fail — # greetd-tuigreet later), so /etc/greetd does not exist and the cp would fail —
@ -57,6 +64,9 @@ log "Deploying greetd config..."
sudo mkdir -p /etc/greetd sudo mkdir -p /etc/greetd
sudo cp -f ~/Dotfiles/desktopenvs/hyprland/greetd-tuigreet/config.toml /etc/greetd/config.toml sudo cp -f ~/Dotfiles/desktopenvs/hyprland/greetd-tuigreet/config.toml /etc/greetd/config.toml
enable_service greetd.service enable_service greetd.service
else
skip "No desktop environment selected — leaving greetd disabled (plain agetty login)."
fi
# ── fail2ban ────────────────────────────────────────────────────────────────── # ── fail2ban ──────────────────────────────────────────────────────────────────
# WHY: Protects against brute-force attacks by monitoring log files and # WHY: Protects against brute-force attacks by monitoring log files and

View File

@ -191,8 +191,16 @@ dialog --backtitle "$BACKTITLE" \
# ── Run TUI installer after base install ────────────────────────────────────── # ── Run TUI installer after base install ──────────────────────────────────────
dialog --backtitle "$BACKTITLE" \ dialog --backtitle "$BACKTITLE" \
--title " Dotfiles Setup " \ --title " Dotfiles Setup " \
--yesno "\n Automatically run the dotfiles TUI installer\n inside the chroot after base install completes?\n" \ --yesno "\n Automatically run the dotfiles TUI installer\n inside the chroot after base install completes?\n\n No = no DE, no modules, no shell setup — installed\n user's default shell is plain bash + agetty login.\n" \
9 58 && AF_RUN_TUI="true" || AF_RUN_TUI="false" 12 60 && AF_RUN_TUI="true" || AF_RUN_TUI="false"
# ── Clone dotfiles repo into /etc/skel ──────────────────────────────────────────
# Independent of run_tui: staging the raw repo doesn't require running the TUI,
# and declining it keeps a fully bare install even if the TUI does run.
dialog --backtitle "$BACKTITLE" \
--title " Skel Dotfiles Clone " \
--yesno "\n Clone the dotfiles repo into /etc/skel so every\n user created on this machine gets a ~/Dotfiles\n checkout automatically?\n\n No = fully bare install, nothing dotfiles-related\n staged anywhere on disk.\n" \
12 60 && AF_CLONE_SKEL_DOTFILES="true" || AF_CLONE_SKEL_DOTFILES="false"
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# PART 2 — Dotfiles / TUI installer options # PART 2 — Dotfiles / TUI installer options
@ -201,6 +209,7 @@ dialog --backtitle "$BACKTITLE" \
AF_COMPONENTS="" AF_COMPONENTS=""
AF_DE="none" AF_DE="none"
AF_APPS="" AF_APPS=""
AF_SYNC_SKEL_CONFIG="true"
AF_COLOR_TEXT="" AF_COLOR_TEXT=""
AF_COLOR_BG="" AF_COLOR_BG=""
AF_COLOR_HIGHLIGHT="" AF_COLOR_HIGHLIGHT=""
@ -326,6 +335,12 @@ if [[ "$AF_RUN_TUI" == "true" ]]; then
3>&1 1>&2 2>&3) || AF_APPS="" 3>&1 1>&2 2>&3) || AF_APPS=""
# END GENERATED MODULES: module-checklist # END GENERATED MODULES: module-checklist
# ── Skel template sync ───────────────────────────────────────────────────────
dialog --backtitle "$BACKTITLE" \
--title " Skel Template " \
--yesno "\n Copy this user's configured ~/.config (and shell\n rc's, if selected) into /etc/skel once setup\n finishes, so users created later on this machine\n inherit the same setup automatically?\n" \
11 62 && AF_SYNC_SKEL_CONFIG="true" || AF_SYNC_SKEL_CONFIG="false"
# ── Colorway ────────────────────────────────────────────────────────────── # ── Colorway ──────────────────────────────────────────────────────────────
# Read defaults from repo colors.conf # Read defaults from repo colors.conf
declare -A _cdef declare -A _cdef
@ -386,7 +401,9 @@ SUMMARY=""
SUMMARY+=" Encrypt: $AF_ENCRYPT\n" SUMMARY+=" Encrypt: $AF_ENCRYPT\n"
SUMMARY+=" FIDO2 root: $AF_FIDO2_ROOT / FIDO2 user: $AF_FIDO2_USER\n" SUMMARY+=" FIDO2 root: $AF_FIDO2_ROOT / FIDO2 user: $AF_FIDO2_USER\n"
SUMMARY+=" Run TUI: $AF_RUN_TUI\n" SUMMARY+=" Run TUI: $AF_RUN_TUI\n"
SUMMARY+=" Clone skel: $AF_CLONE_SKEL_DOTFILES\n"
[[ -n "$AF_DE" && "$AF_DE" != "none" ]] && SUMMARY+=" DE: $AF_DE\n" [[ -n "$AF_DE" && "$AF_DE" != "none" ]] && SUMMARY+=" DE: $AF_DE\n"
[[ "$AF_RUN_TUI" == "true" ]] && SUMMARY+=" Sync skel: $AF_SYNC_SKEL_CONFIG\n"
[[ -n "$AF_COLOR_TEXT" ]] && SUMMARY+=" Colors: custom\n" [[ -n "$AF_COLOR_TEXT" ]] && SUMMARY+=" Colors: custom\n"
dialog --backtitle "$BACKTITLE" \ dialog --backtitle "$BACKTITLE" \
@ -426,9 +443,11 @@ mkdir -p "$(dirname "$OUTPUT")"
printf ' "fido2_root": %s,\n' "$AF_FIDO2_ROOT" printf ' "fido2_root": %s,\n' "$AF_FIDO2_ROOT"
printf ' "fido2_user": %s,\n' "$AF_FIDO2_USER" printf ' "fido2_user": %s,\n' "$AF_FIDO2_USER"
printf ' "run_tui": %s,\n' "$AF_RUN_TUI" printf ' "run_tui": %s,\n' "$AF_RUN_TUI"
printf ' "clone_skel_dotfiles": %s,\n' "$AF_CLONE_SKEL_DOTFILES"
printf ' "components": %s,\n' "$(_words_to_json_array "$AF_COMPONENTS")" printf ' "components": %s,\n' "$(_words_to_json_array "$AF_COMPONENTS")"
printf ' "desktop_environment": %s,\n' "$(json_str "$AF_DE")" printf ' "desktop_environment": %s,\n' "$(json_str "$AF_DE")"
printf ' "apps": %s' "$(_words_to_json_array "$AF_APPS")" printf ' "apps": %s,\n' "$(_words_to_json_array "$AF_APPS")"
printf ' "sync_skel_config": %s' "$AF_SYNC_SKEL_CONFIG"
if [[ -n "$AF_COLOR_TEXT" ]]; then if [[ -n "$AF_COLOR_TEXT" ]]; then
printf ',\n "colors": {\n' printf ',\n "colors": {\n'

View File

@ -426,6 +426,7 @@ count_steps() {
AF_COMPONENTS="" AF_COMPONENTS=""
AF_DE="none" AF_DE="none"
AF_APPS="" AF_APPS=""
AF_SYNC_SKEL_CONFIG="true"
AF_COLOR_TEXT="" AF_COLOR_TEXT=""
AF_COLOR_BG="" AF_COLOR_BG=""
AF_COLOR_HIGHLIGHT="" AF_COLOR_HIGHLIGHT=""
@ -442,6 +443,9 @@ load_answerfile() {
AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE") AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE")
AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE") AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE")
AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE") AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE")
# Default true: preserves the historical behaviour (always synced) for
# answerfiles written before this toggle existed.
AF_SYNC_SKEL_CONFIG=$(jq -r '.sync_skel_config // true' "$ANSWERFILE")
# Color values are optional; an empty string means "keep the repo default". # Color values are optional; an empty string means "keep the repo default".
AF_COLOR_TEXT=$(jq -r '.colors.COLOR_TEXT // ""' "$ANSWERFILE") AF_COLOR_TEXT=$(jq -r '.colors.COLOR_TEXT // ""' "$ANSWERFILE")
AF_COLOR_BG=$(jq -r '.colors.COLOR_BG // ""' "$ANSWERFILE") AF_COLOR_BG=$(jq -r '.colors.COLOR_BG // ""' "$ANSWERFILE")
@ -551,6 +555,10 @@ else
"lxqt" "LXQt — lightweight Qt X11 DE" \ "lxqt" "LXQt — lightweight Qt X11 DE" \
"none" "Skip DE installation") || DE="none" "none" "Skip DE installation") || DE="none"
fi fi
# Exported so modules launched via `bash "$script"` in run_module() (namely
# core.sh, which stages a default greeter) can see the DE choice — a plain
# subshell does not inherit unexported shell variables.
export MARCHY_DE="$DE"
# ── Apps selection ──────────────────────────────────────────────────────────── # ── Apps selection ────────────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then if $ANSWERFILE_MODE; then
@ -655,6 +663,23 @@ else
SHELL_RC="defaults" SHELL_RC="defaults"
fi fi
# ── Skel template preference ──────────────────────────────────────────────────
# Whether to copy this user's fully-configured ~/.config (and shell rc's, if
# SHELL_RC is "dotfiles") into /etc/skel once setup finishes, so any user
# created later on this machine inherits the same setup automatically. Asked
# separately from the dotfiles-clone/component choices above since it's a
# post-install propagation step, not a package/config decision for THIS user.
if $ANSWERFILE_MODE; then
SYNC_SKEL_CONFIG="$AF_SYNC_SKEL_CONFIG"
else
ui_yesno " Skel Template " "\
Copy this user's configured ~/.config (and shell rc's,\n\
if selected above) into /etc/skel once setup finishes?\n\n\
Any user created later on this machine will then inherit\n\
the same setup automatically." yes \
&& SYNC_SKEL_CONFIG="true" || SYNC_SKEL_CONFIG="false"
fi
# ── Confirmation (interactive mode only) ────────────────────────────────────── # ── Confirmation (interactive mode only) ──────────────────────────────────────
if ! $ANSWERFILE_MODE; then if ! $ANSWERFILE_MODE; then
# Build a human-readable summary of everything that will be installed so the # Build a human-readable summary of everything that will be installed so the
@ -666,8 +691,12 @@ if ! $ANSWERFILE_MODE; then
[[ " $COMPONENTS " == *" shell "* ]] && SUMMARY+=" ✦ Shell setup (plugins & programs)\n" [[ " $COMPONENTS " == *" shell "* ]] && SUMMARY+=" ✦ Shell setup (plugins & programs)\n"
[[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Personal shell rc's (.bashrc/.zshrc/.vimrc)\n" [[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Personal shell rc's (.bashrc/.zshrc/.vimrc)\n"
[[ "$DE" != "none" && "$DE" != "" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n" [[ "$DE" != "none" && "$DE" != "" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n"
[[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Shell rc's → /etc/skel for new users (dotfiles)\n" \ if [[ "$SYNC_SKEL_CONFIG" == "true" ]]; then
|| SUMMARY+=" ✦ Shell rc's → /etc/skel for new users (system defaults)\n" [[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ ~/.config + shell rc's → /etc/skel for new users (dotfiles)\n" \
|| SUMMARY+=" ✦ ~/.config → /etc/skel for new users (shell rc's stay system defaults)\n"
else
SUMMARY+=" ✦ /etc/skel left untouched — new users get system defaults\n"
fi
if [[ -n "$SELECTED_APPS" ]]; then if [[ -n "$SELECTED_APPS" ]]; then
SUMMARY+="\n Applications:\n" SUMMARY+="\n Applications:\n"
@ -1035,10 +1064,11 @@ else
fi fi
fi fi
# ── Sync user config to /etc/skel ───────────────────────────────────────────── # ── Sync user config to /etc/skel (gated by SYNC_SKEL_CONFIG) ─────────────────
# /etc/skel is the skeleton directory Linux copies to new user home dirs on creation. # /etc/skel is the skeleton directory Linux copies to new user home dirs on creation.
# Propagating the post-install config here means any new user added to this machine # Propagating the post-install config here means any new user added to this machine
# automatically gets the full dotfiles environment without a manual copy step. # automatically gets the full dotfiles environment without a manual copy step.
if [[ "$SYNC_SKEL_CONFIG" == "true" ]]; then
if [[ -d "$HOME/.config" ]]; then if [[ -d "$HOME/.config" ]]; then
printf "\n Syncing ~/.config to /etc/skel...\n" printf "\n Syncing ~/.config to /etc/skel...\n"
sudo mkdir -p /etc/skel/.config sudo mkdir -p /etc/skel/.config
@ -1053,6 +1083,9 @@ if [[ "$SHELL_RC" == "dotfiles" ]]; then
[[ -f "$HOME/.bashrc" ]] && sudo cp "$HOME/.bashrc" /etc/skel/.bashrc [[ -f "$HOME/.bashrc" ]] && sudo cp "$HOME/.bashrc" /etc/skel/.bashrc
[[ -f "$HOME/.vimrc" ]] && sudo cp "$HOME/.vimrc" /etc/skel/.vimrc [[ -f "$HOME/.vimrc" ]] && sudo cp "$HOME/.vimrc" /etc/skel/.vimrc
fi fi
else
printf "\n Skipping /etc/skel config sync (declined).\n"
fi
# ── Done ────────────────────────────────────────────────────────────────────── # ── Done ──────────────────────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then if $ANSWERFILE_MODE; then