529 lines
19 KiB
Bash
Executable file
529 lines
19 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
readonly REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
DO_PACKAGES=0 DO_CONFIGS=0 DO_TOOLS=0 DO_SERVICES=0 DRY_RUN=0
|
|
BACKUP_DIR='' TEMP_DIR='' BENEFICIARY='' MACHINE_NAME='' DISPLAY_USER=''
|
|
CURRENT_STAGE='précontrôles' CURRENT_ACTION=''
|
|
COMPLETED=() APPLIED=() OFFICIAL_PACKAGES=() FOREIGN_PACKAGES=()
|
|
SYSTEM_SERVICES=() SYSTEM_TIMERS=() ON_DEMAND_SERVICES=()
|
|
|
|
readonly CONFIG_SOURCES=(
|
|
rootfs/etc/pacman.conf rootfs/etc/makepkg.conf rootfs/etc/mkinitcpio.conf
|
|
rootfs/etc/nftables.conf rootfs/etc/limine-snapper-sync.conf
|
|
rootfs/etc/tlp.d/10-archasp.conf rootfs/etc/systemd/zram-generator.conf
|
|
rootfs/etc/snapper/configs/root
|
|
)
|
|
readonly CONFIG_TARGETS=(
|
|
/etc/pacman.conf /etc/makepkg.conf /etc/mkinitcpio.conf
|
|
/etc/nftables.conf /etc/limine-snapper-sync.conf
|
|
/etc/tlp.d/10-archasp.conf /etc/systemd/zram-generator.conf
|
|
/etc/snapper/configs/root
|
|
)
|
|
readonly HELPER_NAMES=(batlimit-set vmware-stack)
|
|
readonly SUDOERS_NAMES=(batlimit vmware-stack)
|
|
|
|
# Le fragment privilégié est fixe : aucun argument n'est interprété comme du
|
|
# code. find termine avec 0 et émet un état même si le composant est absent ;
|
|
# toute erreur d'inspection fait échouer la commande au lieu de signifier ABSENT.
|
|
readonly INSPECT_CODE='
|
|
target="$1"
|
|
[[ "$target" == /* && "$target" != / ]] || exit 2
|
|
rest="${target#/}"
|
|
cursor=/
|
|
while [[ -n "$rest" ]]; do
|
|
name="${rest%%/*}"
|
|
[[ -n "$name" && "$name" != . && "$name" != .. ]] || exit 2
|
|
if [[ "$rest" == */* ]]; then rest="${rest#*/}"; last=0
|
|
else rest=""; last=1; fi
|
|
kind="$(find "$cursor" -mindepth 1 -maxdepth 1 -name "$name" -printf "%y\n" -quit)" || exit 3
|
|
case "$kind" in
|
|
"") printf "ABSENT\n"; exit 0 ;;
|
|
l) printf "LINK\n"; exit 0 ;;
|
|
d) if (( last )); then printf "INVALID\n"; exit 0; fi ;;
|
|
f) if (( last )); then printf "EXISTING\n"; else printf "INVALID\n"; fi; exit 0 ;;
|
|
*) printf "INVALID\n"; exit 0 ;;
|
|
esac
|
|
if [[ "$cursor" == / ]]; then cursor="/$name"; else cursor="$cursor/$name"; fi
|
|
done
|
|
exit 2
|
|
'
|
|
|
|
usage() {
|
|
cat <<'HELP'
|
|
Usage : deploy.sh [--dry-run] (--packages|--configs|--tools|--services|--all) ...
|
|
--packages Installe les listes de paquets natifs et étrangers (yay requis)
|
|
--configs Restaure les configurations système suivies
|
|
--tools Installe les helpers root et les règles sudoers
|
|
--services Active les services et timers système voulus
|
|
--all Exécute les quatre étapes dans cet ordre
|
|
--dry-run Affiche un plan statique, sans privilèges ni application
|
|
-h, --help Affiche cette aide
|
|
Codes : 0 = plan valide, annulation ou application terminée ; 1 = précondition/application
|
|
échouée ; 2 = usage invalide ; 130/143 = interruption INT/TERM.
|
|
HELP
|
|
}
|
|
|
|
fatal() {
|
|
printf '[!] Échec (%s) : %s\n' "$CURRENT_STAGE" "$1" >&2
|
|
if (( ${#COMPLETED[@]} )); then
|
|
printf '[!] Étapes achevées : %s\n' "${COMPLETED[*]}" >&2
|
|
else
|
|
printf '[!] Aucune étape achevée.\n' >&2
|
|
fi
|
|
if (( ${#APPLIED[@]} )); then
|
|
printf '[!] Actions déjà appliquées : %s\n' "${APPLIED[*]}" >&2
|
|
fi
|
|
[[ -z "$CURRENT_ACTION" ]] ||
|
|
printf '[!] Action échouée, effets à vérifier : %s\n' "$CURRENT_ACTION" >&2
|
|
[[ -z "$BACKUP_DIR" ]] ||
|
|
printf '[!] Sauvegardes et marqueurs de cibles absentes : %s\n' "$BACKUP_DIR" >&2
|
|
printf '[!] Pas de rollback global : inspecter les changements avant reprise.\n' >&2
|
|
exit 1
|
|
}
|
|
|
|
run() {
|
|
local label="$1"
|
|
shift
|
|
CURRENT_ACTION="$label"
|
|
"$@" || fatal "$label"
|
|
CURRENT_ACTION=''
|
|
}
|
|
|
|
interrupt() {
|
|
local signal="$1" status="$2"
|
|
trap - INT TERM
|
|
printf '[!] Interruption %s (%s), phase : %s\n' "$signal" "$status" "$CURRENT_STAGE" >&2
|
|
if (( ${#COMPLETED[@]} )); then
|
|
printf '[!] Étapes achevées : %s\n' "${COMPLETED[*]}" >&2
|
|
else
|
|
printf '[!] Aucune étape achevée.\n' >&2
|
|
fi
|
|
if (( ${#APPLIED[@]} )); then
|
|
printf '[!] Actions confirmées : %s\n' "${APPLIED[*]}" >&2
|
|
else
|
|
printf '[!] Aucune action confirmée.\n' >&2
|
|
fi
|
|
[[ -z "$CURRENT_ACTION" ]] ||
|
|
printf '[!] Action interrompue, effets à vérifier : %s\n' "$CURRENT_ACTION" >&2
|
|
[[ -z "$BACKUP_DIR" ]] ||
|
|
printf '[!] Sauvegardes et marqueurs conservés : %s\n' "$BACKUP_DIR" >&2
|
|
exit "$status"
|
|
}
|
|
|
|
cleanup() {
|
|
local status="$1"
|
|
if [[ -n "$TEMP_DIR" ]]; then
|
|
# Supprimer uniquement les deux fichiers créés par cette invocation.
|
|
rm -f -- "$TEMP_DIR/batlimit" "$TEMP_DIR/vmware-stack" ||
|
|
printf '[!] Nettoyage des sudoers temporaires incomplet : %s\n' "$TEMP_DIR" >&2
|
|
rmdir -- "$TEMP_DIR" 2>/dev/null || true
|
|
fi
|
|
exit "$status"
|
|
}
|
|
trap 'cleanup $?' EXIT
|
|
trap 'interrupt INT 130' INT
|
|
trap 'interrupt TERM 143' TERM
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--packages) DO_PACKAGES=1 ;;
|
|
--configs) DO_CONFIGS=1 ;;
|
|
--tools) DO_TOOLS=1 ;;
|
|
--services) DO_SERVICES=1 ;;
|
|
--all) DO_PACKAGES=1; DO_CONFIGS=1; DO_TOOLS=1; DO_SERVICES=1 ;;
|
|
--dry-run) DRY_RUN=1 ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) printf 'Option inconnue : %s\n' "$1" >&2; usage; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
if (( DO_PACKAGES + DO_CONFIGS + DO_TOOLS + DO_SERVICES == 0 )); then
|
|
usage
|
|
exit 2
|
|
fi
|
|
|
|
require_source() {
|
|
[[ -f "$1" && -r "$1" && -s "$1" && ! -L "$1" ]] ||
|
|
fatal "source obligatoire absente, vide, illisible ou liée : $1"
|
|
}
|
|
|
|
# Une liste présente et vide est volontairement vide. Une substitution de
|
|
# processus cacherait une erreur de lecture : lire directement et vérifier.
|
|
read_list() {
|
|
local file="$1" kind="$2" output_name="$3" line item
|
|
local -n output="$output_name"
|
|
local -A seen=()
|
|
[[ -f "$file" && -r "$file" && ! -L "$file" ]] ||
|
|
fatal "liste/manifest absent, illisible ou lié : $file"
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
item="${line#"${line%%[![:space:]]*}"}"
|
|
item="${item%"${item##*[![:space:]]}"}"
|
|
[[ -z "$item" || "$item" == \#* ]] && continue
|
|
case "$kind" in
|
|
package)
|
|
[[ "$item" =~ ^[a-zA-Z0-9@][a-zA-Z0-9@._+-]*$ ]] ||
|
|
fatal "nom de paquet invalide dans $file : $item" ;;
|
|
service)
|
|
[[ "$item" =~ ^[a-zA-Z0-9@_.-]+\.service$ ]] ||
|
|
fatal "unité service invalide dans $file : $item" ;;
|
|
timer)
|
|
[[ "$item" =~ ^[a-zA-Z0-9@_.-]+\.timer$ ]] ||
|
|
fatal "unité timer invalide dans $file : $item" ;;
|
|
esac
|
|
[[ ! -v "seen[$item]" ]] || fatal "doublon dans $file : $item"
|
|
seen["$item"]=1
|
|
output+=("$item")
|
|
done < "$file" || fatal "lecture échouée : $file"
|
|
}
|
|
|
|
require_command() {
|
|
command -v "$1" >/dev/null 2>&1 || fatal "commande requise absente : $1"
|
|
}
|
|
|
|
read_machine_name() {
|
|
[[ -r /etc/hostname ]] || fatal 'nom de machine indisponible : /etc/hostname illisible ou absent'
|
|
IFS= read -r MACHINE_NAME < /etc/hostname ||
|
|
[[ -n "$MACHINE_NAME" ]] || fatal 'lecture du nom de machine échouée'
|
|
[[ "$MACHINE_NAME" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*$ ]] ||
|
|
fatal 'nom de machine invalide dans /etc/hostname'
|
|
}
|
|
|
|
check_beneficiary() {
|
|
local uid
|
|
# Ne jamais utiliser USER/SUDO_USER comme autorité pour sudoers ou AUR.
|
|
(( EUID != 0 )) || fatal 'compte root refusé pour cette invocation'
|
|
BENEFICIARY="$(id -un)" || fatal 'identité du compte indisponible'
|
|
uid="$(id -u "$BENEFICIARY")" || fatal 'compte bénéficiaire inexistant'
|
|
[[ "$BENEFICIARY" =~ ^[a-z_][a-z0-9_-]*\$?$ && "$uid" =~ ^[0-9]+$ ]] ||
|
|
fatal 'nom de compte bénéficiaire invalide'
|
|
(( uid > 0 )) || fatal 'compte bénéficiaire root refusé'
|
|
}
|
|
|
|
check_target_local() {
|
|
local parent="${1%/*}"
|
|
while [[ "$parent" != / ]]; do
|
|
[[ ! -L "$parent" ]] || fatal "répertoire cible lié refusé : $parent"
|
|
[[ ! -e "$parent" || -d "$parent" ]] ||
|
|
fatal "parent non répertoire refusé : $parent"
|
|
parent="${parent%/*}"
|
|
[[ -n "$parent" ]] || parent=/
|
|
done
|
|
[[ ! -L "$1" ]] || fatal "cible liée refusée : $1"
|
|
[[ ! -e "$1" || -f "$1" ]] || fatal "cible non régulière refusée : $1"
|
|
}
|
|
|
|
check_unit() {
|
|
systemctl cat "$1" >/dev/null 2>&1 || fatal "unité requise absente : $1"
|
|
}
|
|
|
|
check_units() {
|
|
local unit
|
|
if (( DO_SERVICES )); then
|
|
for unit in "${SYSTEM_SERVICES[@]}" "${SYSTEM_TIMERS[@]}"; do
|
|
check_unit "$unit"
|
|
done
|
|
fi
|
|
if (( DO_TOOLS || DO_SERVICES )); then
|
|
for unit in "${ON_DEMAND_SERVICES[@]}"; do
|
|
check_unit "$unit"
|
|
done
|
|
fi
|
|
}
|
|
|
|
preflight_static() {
|
|
local i name
|
|
if (( DO_PACKAGES )); then
|
|
read_list "$REPO_DIR/pkglist/pacman.txt" package OFFICIAL_PACKAGES
|
|
read_list "$REPO_DIR/pkglist/aur.txt" package FOREIGN_PACKAGES
|
|
require_command pacman
|
|
if (( ${#FOREIGN_PACKAGES[@]} )); then
|
|
require_command yay
|
|
check_beneficiary
|
|
fi
|
|
fi
|
|
if (( DO_CONFIGS )); then
|
|
for i in "${!CONFIG_SOURCES[@]}"; do
|
|
require_source "$REPO_DIR/${CONFIG_SOURCES[i]}"
|
|
check_target_local "${CONFIG_TARGETS[i]}"
|
|
done
|
|
if (( ! DO_PACKAGES )); then require_command nft; fi
|
|
require_command systemctl
|
|
fi
|
|
if (( DO_TOOLS )); then
|
|
check_beneficiary
|
|
for name in "${HELPER_NAMES[@]}"; do
|
|
require_source "$REPO_DIR/rootfs/usr/local/sbin/$name"
|
|
check_target_local "/usr/local/sbin/$name"
|
|
done
|
|
for name in "${SUDOERS_NAMES[@]}"; do
|
|
require_source "$REPO_DIR/templates/sudoers.d/$name"
|
|
grep -Fq '@USER@' "$REPO_DIR/templates/sudoers.d/$name" ||
|
|
fatal "placeholder @USER@ absent : $name"
|
|
check_target_local "/etc/sudoers.d/$name"
|
|
done
|
|
require_command visudo
|
|
fi
|
|
if (( DO_SERVICES )); then
|
|
read_list "$REPO_DIR/manifests/system-services.txt" service SYSTEM_SERVICES
|
|
read_list "$REPO_DIR/manifests/system-timers.txt" timer SYSTEM_TIMERS
|
|
fi
|
|
if (( DO_TOOLS || DO_SERVICES )); then
|
|
read_list "$REPO_DIR/manifests/on-demand-system-services.txt" service ON_DEMAND_SERVICES
|
|
require_command systemctl
|
|
fi
|
|
if (( ! DO_PACKAGES )); then
|
|
check_units
|
|
fi
|
|
if (( ! DRY_RUN )); then
|
|
require_command sudo
|
|
require_command install
|
|
require_command mktemp
|
|
require_command find
|
|
read_machine_name
|
|
DISPLAY_USER="$(id -un)" || fatal 'identité du compte indisponible'
|
|
fi
|
|
}
|
|
|
|
show_plan() {
|
|
local i name unit
|
|
printf '[PLAN] Aucun changement appliqué ; validations privilégiées non exécutées.\n'
|
|
if (( DO_PACKAGES )); then
|
|
printf '[PLAN] %s -> pacman -Syu --needed (%s entrées)\n' \
|
|
"$REPO_DIR/pkglist/pacman.txt" "${#OFFICIAL_PACKAGES[@]}"
|
|
printf '[PLAN] %s -> yay -S --needed (%s entrées étrangères)\n' \
|
|
"$REPO_DIR/pkglist/aur.txt" "${#FOREIGN_PACKAGES[@]}"
|
|
fi
|
|
if (( DO_CONFIGS )); then
|
|
printf '[PLAN] Validation nftables privilégiée différée : %s\n' \
|
|
"$REPO_DIR/rootfs/etc/nftables.conf"
|
|
for i in "${!CONFIG_SOURCES[@]}"; do
|
|
printf '[PLAN] %s -> %s\n' \
|
|
"$REPO_DIR/${CONFIG_SOURCES[i]}" "${CONFIG_TARGETS[i]}"
|
|
done
|
|
printf '[PLAN] systemctl daemon-reload ; tlp start si présent.\n'
|
|
fi
|
|
if (( DO_TOOLS )); then
|
|
for name in "${HELPER_NAMES[@]}"; do
|
|
printf '[PLAN] %s -> /usr/local/sbin/%s\n' \
|
|
"$REPO_DIR/rootfs/usr/local/sbin/$name" "$name"
|
|
done
|
|
for name in "${SUDOERS_NAMES[@]}"; do
|
|
printf '[PLAN] %s -> /etc/sudoers.d/%s (visudo différé, compte %s)\n' \
|
|
"$REPO_DIR/templates/sudoers.d/$name" "$name" "$BENEFICIARY"
|
|
done
|
|
fi
|
|
if (( DO_SERVICES )); then
|
|
for unit in "${SYSTEM_SERVICES[@]}" "${SYSTEM_TIMERS[@]}"; do
|
|
printf '[PLAN] Activer : %s\n' "$unit"
|
|
done
|
|
fi
|
|
if (( DO_TOOLS || DO_SERVICES )); then
|
|
for unit in "${ON_DEMAND_SERVICES[@]}"; do
|
|
printf '[PLAN] Désactiver et arrêter si nécessaire : %s\n' "$unit"
|
|
done
|
|
fi
|
|
if (( DO_PACKAGES )); then
|
|
printf '[PLAN] Unités fournies par les paquets : contrôle différé après installation.\n'
|
|
fi
|
|
printf '[PLAN] Plan statique valide, déploiement non effectué.\n'
|
|
}
|
|
|
|
preflight_privileged() {
|
|
local name
|
|
run 'autorisation sudo' sudo -v
|
|
if (( DO_CONFIGS )); then
|
|
if command -v nft >/dev/null 2>&1; then
|
|
run 'validation nftables' sudo nft -c -f "$REPO_DIR/rootfs/etc/nftables.conf"
|
|
elif (( ! DO_PACKAGES )); then
|
|
fatal 'nft absent avant la phase configurations'
|
|
fi
|
|
fi
|
|
if (( DO_TOOLS )); then
|
|
TEMP_DIR="$(mktemp -d)" || fatal 'création du répertoire temporaire sudoers'
|
|
for name in "${SUDOERS_NAMES[@]}"; do
|
|
sed "s/@USER@/$BENEFICIARY/g" "$REPO_DIR/templates/sudoers.d/$name" \
|
|
> "$TEMP_DIR/$name" || fatal "rendu sudoers $name"
|
|
run "validation sudoers $name" sudo visudo -cf "$TEMP_DIR/$name"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Seul un résultat strict, produit avec un code de succès, décrit la cible.
|
|
# Un échec sudo/find, y compris avec une sortie plausible, reste indéterminé.
|
|
check_target_privileged() {
|
|
local target="$1" state
|
|
if state="$(sudo /usr/bin/bash -c "$INSPECT_CODE" -- "$target")"; then
|
|
:
|
|
else
|
|
fatal "contrôle privilégié impossible : $target"
|
|
fi
|
|
case "$state" in
|
|
EXISTING) return 0 ;;
|
|
ABSENT) return 1 ;;
|
|
LINK) fatal "cible ou parent lié refusé : $target" ;;
|
|
INVALID) fatal "cible ou parent non régulier refusé : $target" ;;
|
|
*) fatal "réponse de contrôle invalide pour $target" ;;
|
|
esac
|
|
}
|
|
|
|
ensure_backup_dir() {
|
|
[[ -n "$BACKUP_DIR" ]] && return 0
|
|
# mktemp crée atomiquement un répertoire root privé, sans collision
|
|
# entre deux invocations commencées dans la même seconde.
|
|
BACKUP_DIR="$(sudo mktemp -d /root/arch-system-restore.XXXXXXXX)" ||
|
|
fatal 'création du répertoire privé de sauvegarde'
|
|
[[ "$BACKUP_DIR" == /root/arch-system-restore.* ]] ||
|
|
fatal 'chemin de sauvegarde inattendu'
|
|
}
|
|
|
|
backup_target() {
|
|
local target="$1"
|
|
local exists=0
|
|
if check_target_privileged "$target"; then exists=1; fi
|
|
ensure_backup_dir
|
|
if (( exists )); then
|
|
run "répertoire de sauvegarde $target" sudo mkdir -p \
|
|
"$BACKUP_DIR/existing$(dirname "$target")"
|
|
run "sauvegarde $target" sudo cp -a -- "$target" \
|
|
"$BACKUP_DIR/existing$target"
|
|
else
|
|
# Un marqueur distinct d'une copie permet de retirer manuellement
|
|
# une cible créée par ce déploiement sans inventer un ancien contenu.
|
|
run "marqueur de cible absente $target" sudo install -D -m 0600 \
|
|
/dev/null "$BACKUP_DIR/absent$target"
|
|
fi
|
|
}
|
|
|
|
install_target() {
|
|
local source="$1" target="$2" mode="$3"
|
|
backup_target "$target"
|
|
# Réexaminer la cible après la copie et avant le remplacement ; un
|
|
# changement concurrent ne doit pas transformer la destination en lien.
|
|
if check_target_privileged "$target"; then :; fi
|
|
run "installation $target" sudo install -D -m "$mode" "$source" "$target"
|
|
APPLIED+=("installation:$target")
|
|
}
|
|
|
|
unit_state() {
|
|
local unit="$1" property="$2" result
|
|
if [[ "$property" == enabled ]]; then
|
|
result="$(systemctl is-enabled "$unit" 2>/dev/null)" || :
|
|
else
|
|
result="$(systemctl is-active "$unit" 2>/dev/null)" || :
|
|
fi
|
|
[[ -n "$result" ]] || fatal "état systemd indéterminé : $unit"
|
|
printf '%s' "$result"
|
|
}
|
|
|
|
disable_on_demand() {
|
|
local unit enabled active
|
|
for unit in "${ON_DEMAND_SERVICES[@]}"; do
|
|
check_unit "$unit"
|
|
enabled="$(unit_state "$unit" enabled)"
|
|
active="$(unit_state "$unit" active)"
|
|
if [[ "$enabled" == disabled && "$active" == inactive ]]; then
|
|
printf '[*] Déjà désactivé/inactif : %s\n' "$unit"
|
|
continue
|
|
fi
|
|
[[ "$enabled" =~ ^(enabled|enabled-runtime|disabled|masked)$ &&
|
|
"$active" =~ ^(active|activating|inactive|failed)$ ]] ||
|
|
fatal "état systemd non pris en charge : $unit ($enabled/$active)"
|
|
run "désactivation $unit" sudo systemctl disable --now "$unit"
|
|
APPLIED+=("désactivation:$unit")
|
|
done
|
|
}
|
|
|
|
enable_units() {
|
|
local unit
|
|
for unit in "${SYSTEM_SERVICES[@]}" "${SYSTEM_TIMERS[@]}"; do
|
|
check_unit "$unit"
|
|
run "activation $unit" sudo systemctl enable "$unit"
|
|
APPLIED+=("activation:$unit")
|
|
done
|
|
}
|
|
|
|
preflight_static
|
|
if (( DRY_RUN )); then
|
|
show_plan
|
|
exit 0
|
|
fi
|
|
|
|
read -rp "Déployer arch-system sur ${MACHINE_NAME} pour ${DISPLAY_USER} ? [y/N] " confirm ||
|
|
fatal 'confirmation indisponible'
|
|
[[ "$confirm" =~ ^[yY]$ ]] || { printf '[*] Déploiement annulé.\n'; exit 0; }
|
|
|
|
# Les validations privilégiées précèdent tout changement durable ; les unités
|
|
# fournies par les paquets demandés restent nécessairement vérifiées après eux.
|
|
preflight_privileged
|
|
|
|
if (( DO_PACKAGES )); then
|
|
CURRENT_STAGE='paquets'
|
|
if (( ${#OFFICIAL_PACKAGES[@]} )); then
|
|
run 'installation des paquets natifs' sudo pacman -Syu --needed \
|
|
"${OFFICIAL_PACKAGES[@]}"
|
|
APPLIED+=(paquets-natifs)
|
|
else
|
|
printf '[*] Liste native présente mais vide.\n'
|
|
fi
|
|
if (( ${#FOREIGN_PACKAGES[@]} )); then
|
|
run 'installation des paquets étrangers' yay -S --needed \
|
|
"${FOREIGN_PACKAGES[@]}"
|
|
APPLIED+=(paquets-étrangers)
|
|
else
|
|
printf '[*] Liste étrangère présente mais vide.\n'
|
|
fi
|
|
COMPLETED+=(paquets)
|
|
if (( DO_CONFIGS )) && ! command -v nft >/dev/null 2>&1; then
|
|
fatal 'nft reste absent après installation des paquets'
|
|
fi
|
|
if (( DO_CONFIGS )); then
|
|
run 'validation nftables différée' sudo nft -c -f \
|
|
"$REPO_DIR/rootfs/etc/nftables.conf"
|
|
fi
|
|
if (( DO_TOOLS || DO_SERVICES )); then
|
|
CURRENT_STAGE='contrôle différé des unités'
|
|
check_units
|
|
fi
|
|
fi
|
|
|
|
if (( DO_CONFIGS )); then
|
|
CURRENT_STAGE='configurations'
|
|
for i in "${!CONFIG_SOURCES[@]}"; do
|
|
install_target "$REPO_DIR/${CONFIG_SOURCES[i]}" "${CONFIG_TARGETS[i]}" 0644
|
|
done
|
|
run 'rechargement systemd' sudo systemctl daemon-reload
|
|
APPLIED+=(rechargement-systemd)
|
|
if command -v tlp >/dev/null 2>&1; then
|
|
run 'démarrage TLP' sudo tlp start
|
|
APPLIED+=(démarrage-tlp)
|
|
fi
|
|
COMPLETED+=(configurations)
|
|
printf '[!] fstab, Limine et Secure Boot restent à finaliser manuellement.\n'
|
|
fi
|
|
|
|
if (( DO_TOOLS )); then
|
|
CURRENT_STAGE='outils root'
|
|
for name in "${HELPER_NAMES[@]}"; do
|
|
install_target "$REPO_DIR/rootfs/usr/local/sbin/$name" \
|
|
"/usr/local/sbin/$name" 0755
|
|
done
|
|
for name in "${SUDOERS_NAMES[@]}"; do
|
|
install_target "$TEMP_DIR/$name" "/etc/sudoers.d/$name" 0440
|
|
done
|
|
disable_on_demand
|
|
COMPLETED+=(outils)
|
|
fi
|
|
|
|
if (( DO_SERVICES )); then
|
|
CURRENT_STAGE='services système'
|
|
enable_units
|
|
disable_on_demand
|
|
COMPLETED+=(services)
|
|
fi
|
|
|
|
printf '[+] Déploiement sélectionné terminé : %s\n' "${COMPLETED[*]}"
|
|
[[ -z "$BACKUP_DIR" ]] || printf '[+] Sauvegardes et marqueurs : %s\n' "$BACKUP_DIR"
|
|
printf '[!] Aucune transaction globale ni rollback automatique.\n'
|
|
printf '[!] Vérifier fstab, Limine, mkinitcpio, Snapper, nftables et sbctl avant reboot.\n'
|