416 lines
21 KiB
Bash
Executable file
416 lines
21 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
umask 077
|
|
|
|
readonly REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
# Contrôle privilégié fixe : sudo et find doivent réussir avant qu'un état
|
|
# soit utilisable. La cible est une donnée positionnelle, jamais du code.
|
|
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="$(/usr/bin/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
|
|
'
|
|
|
|
DRY_RUN=0 EXPLICIT_PARENT='' PARENT='' STAGE='' FINAL='' REVISION=''
|
|
STAGE_ID='' FINAL_ACQUIRED=0 FINAL_ANNOUNCED=0
|
|
STARTED='' CURRENT='' WARNINGS=0
|
|
declare -a RESULTS=() HASHES=()
|
|
|
|
usage() {
|
|
cat <<'HELP'
|
|
Usage : backup.sh [--output-dir REPERTOIRE_PARENT]
|
|
backup.sh --dry-run [--output-dir REPERTOIRE_PARENT]
|
|
backup.sh --help
|
|
Export système privé pour revue ; ni sauvegarde des données personnelles,
|
|
ni actualisation de la référence Git. --output-dir désigne un parent : chaque
|
|
collecte crée un sous-répertoire distinct. Codes : 0 = candidat finalisé ou
|
|
simulation ; 1 = contrôle/collecte/finalisation échoué ; 2 = usage invalide ;
|
|
130/143 = interruption INT/TERM.
|
|
HELP
|
|
}
|
|
fail() { printf '[!] Échec : %s\n' "$1" >&2; exit 1; }
|
|
interrupt() {
|
|
local signal="$1" code="$2"
|
|
trap - INT TERM
|
|
printf '[!] Interruption %s (%s) ; collecteur : %s\n' "$signal" "$code" "${CURRENT:-aucun}" >&2
|
|
reconcile_finalization
|
|
if (( FINAL_ACQUIRED )); then
|
|
printf '[!] Finalisation acquise, terminaison interrompue ; candidat conservé : %s\n' "$FINAL" >&2
|
|
FINAL_ANNOUNCED=1
|
|
fi
|
|
exit "$code"
|
|
}
|
|
# Le nom final seul ne suffit pas : une collision peut déjà l'occuper.
|
|
# Le couple périphérique/inode identifie le répertoire privé créé par mktemp.
|
|
reconcile_finalization() {
|
|
local current_id
|
|
(( FINAL_ACQUIRED == 0 )) || return 0
|
|
[[ -n "$FINAL" && -n "$STAGE_ID" && ! -e "$STAGE" && ! -L "$FINAL" && -d "$FINAL" ]] || return 0
|
|
current_id="$(stat -c '%d:%i' -- "$FINAL")" || return 0
|
|
[[ "$current_id" == "$STAGE_ID" ]] && FINAL_ACQUIRED=1
|
|
return 0
|
|
}
|
|
write_manifest() {
|
|
local status="$1" ended
|
|
[[ -n "$STAGE" && -d "$STAGE" ]] || return 0
|
|
ended="$(date -u +%Y-%m-%dT%H:%M:%SZ)" || return 1
|
|
{
|
|
printf 'format\t1\nstarted_utc\t%s\nended_utc\t%s\nsource_revision\t%s\nglobal_status\t%s\n' \
|
|
"$STARTED" "$ended" "$REVISION" "$status"
|
|
printf 'collector\tclass\tstatus\tdetail\n'
|
|
if (( ${#RESULTS[@]} )); then printf '%s\n' "${RESULTS[@]}"; fi
|
|
printf 'file\tsha256\n'
|
|
if (( ${#HASHES[@]} )); then printf '%s\n' "${HASHES[@]}"; fi
|
|
} > "$STAGE/.manifest.tmp" || return 1
|
|
mv -f -- "$STAGE/.manifest.tmp" "$STAGE/manifest.tsv"
|
|
}
|
|
on_exit() {
|
|
local code="$1"
|
|
trap - EXIT
|
|
reconcile_finalization
|
|
if (( FINAL_ACQUIRED )); then
|
|
if (( ! FINAL_ANNOUNCED )); then
|
|
printf '[!] Candidat de cette invocation déjà finalisé et conservé : %s\n' "$FINAL" >&2
|
|
fi
|
|
elif [[ -n "$STAGE" && -d "$STAGE" ]]; then
|
|
write_manifest INCOMPLETE || printf '[!] Manifeste incomplet non actualisé.\n' >&2
|
|
printf '[!] Export incomplet conservé : %s ; dernier collecteur : %s\n' \
|
|
"$STAGE" "${CURRENT:-aucun}" >&2
|
|
elif [[ -n "$FINAL" ]]; then
|
|
printf '[!] État de finalisation indéterminé ; inspecter : %s et %s\n' "$STAGE" "$FINAL" >&2
|
|
fi
|
|
exit "$code"
|
|
}
|
|
trap 'on_exit $?' EXIT
|
|
trap 'interrupt INT 130' INT
|
|
trap 'interrupt TERM 143' TERM
|
|
|
|
while (( $# )); do
|
|
case "$1" in
|
|
--help|-h) usage; exit 0 ;;
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--output-dir)
|
|
(( $# >= 2 )) || { usage >&2; exit 2; }
|
|
[[ -z "$EXPLICIT_PARENT" && -n "$2" && "$2" != --* ]] || { usage >&2; exit 2; }
|
|
EXPLICIT_PARENT="$2"; shift ;;
|
|
*) printf '[!] Option inconnue : %s\n' "$1" >&2; usage >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Deux normalisations protègent les alias du dépôt ; chaque composant lexical
|
|
# existant doit aussi être un vrai répertoire, sans traversée de lien.
|
|
choose_parent() {
|
|
local raw base component rest cursor=/ physical
|
|
if [[ -n "$EXPLICIT_PARENT" ]]; then raw="$EXPLICIT_PARENT"
|
|
else
|
|
base="${XDG_STATE_HOME:-}"
|
|
if [[ -n "$base" && "$base" != /* ]]; then
|
|
printf '[!] XDG_STATE_HOME relatif ignoré ; défaut sous HOME utilisé.\n' >&2
|
|
base=''
|
|
fi
|
|
if [[ -z "$base" ]]; then
|
|
[[ "${HOME:-}" == /* ]] || fail 'HOME absolu requis pour la destination par défaut'
|
|
base="$HOME/.local/state"
|
|
fi
|
|
raw="$base/arch-system/exports"
|
|
fi
|
|
[[ "$raw" == /* ]] || raw="$PWD/$raw"
|
|
PARENT="$(realpath -m -s -- "$raw")" || fail 'normalisation du parent impossible'
|
|
physical="$(realpath -m -- "$raw")" || fail 'résolution du parent impossible'
|
|
[[ "$PARENT" != / ]] || fail 'racine interdite comme parent'
|
|
[[ "$physical" != "$REPO_DIR" && "$physical" != "$REPO_DIR/"* ]] ||
|
|
fail 'destination dans le dépôt interdite'
|
|
rest="${PARENT#/}"
|
|
while [[ -n "$rest" ]]; do
|
|
component="${rest%%/*}"
|
|
if [[ "$cursor" == / ]]; then cursor="/$component"; else cursor="$cursor/$component"; fi
|
|
[[ ! -L "$cursor" ]] || fail "lien dans le parent refusé : $cursor"
|
|
[[ ! -e "$cursor" || -d "$cursor" ]] || fail "parent non répertoire : $cursor"
|
|
if [[ "$rest" == */* ]]; then rest="${rest#*/}"; else rest=''; fi
|
|
done
|
|
}
|
|
make_parent() {
|
|
local rest="${PARENT#/}" component cursor=/ uid mode physical
|
|
while [[ -n "$rest" ]]; do
|
|
component="${rest%%/*}"
|
|
if [[ "$cursor" == / ]]; then cursor="/$component"; else cursor="$cursor/$component"; fi
|
|
[[ ! -L "$cursor" ]] || fail "lien dans le parent refusé : $cursor"
|
|
if [[ ! -e "$cursor" ]]; then
|
|
mkdir -m 0700 -- "$cursor" 2>/dev/null ||
|
|
[[ -d "$cursor" && ! -L "$cursor" ]] || fail "création du parent impossible : $cursor"
|
|
fi
|
|
[[ -d "$cursor" && ! -L "$cursor" ]] || fail "parent invalide : $cursor"
|
|
if [[ "$rest" == */* ]]; then rest="${rest#*/}"; else rest=''; fi
|
|
done
|
|
uid="$(stat -c %u -- "$PARENT")" || fail 'propriétaire du parent indisponible'
|
|
mode="$(stat -c %a -- "$PARENT")" || fail 'permissions du parent indisponibles'
|
|
[[ "$uid" == "$EUID" ]] || fail 'parent final non détenu par le compte courant'
|
|
(( (8#$mode & 077) == 0 )) || fail 'parent final non privé'
|
|
physical="$(realpath -m -- "$PARENT")" || fail 'résolution du parent impossible'
|
|
[[ "$physical" != "$REPO_DIR" && "$physical" != "$REPO_DIR/"* ]] ||
|
|
fail 'destination dans le dépôt interdite'
|
|
}
|
|
record() {
|
|
RESULTS+=("$(printf '%s\t%s\t%s\t%s' "$1" "$2" "$3" "$4")")
|
|
case "$3" in SUCCESS|EMPTY) ;; *) (( WARNINGS += 1 )) ;; esac
|
|
}
|
|
hash_output() {
|
|
local relative="$1" digest
|
|
# L'entrée standard force le nom « - » dans la sortie de sha256sum : un
|
|
# antislash dans le parent ne peut plus préfixer le champ de somme.
|
|
if digest="$(sha256sum < "$STAGE/$relative")"; then :
|
|
else fail "empreinte impossible : $relative"; fi
|
|
[[ "$digest" =~ ^([0-9a-f]{64})[[:space:]][[:space:]]-$ ]] ||
|
|
fail "réponse d'empreinte invalide : $relative"
|
|
HASHES+=("$(printf '%s\t%s' "$relative" "${BASH_REMATCH[1]}")")
|
|
}
|
|
collect_command() {
|
|
local key="$1" class="$2" relative="$3" empty_ok="$4"; shift 4
|
|
local tmp="$STAGE/.work/$key" state=SUCCESS
|
|
CURRENT="$key"
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
record "$key" "$class" UNAVAILABLE 'commande_absente'
|
|
[[ "$class" == optional ]] || fail "collecteur obligatoire indisponible : $key"
|
|
return 0
|
|
fi
|
|
if "$@" > "$tmp"; then
|
|
# Les anciennes listes étaient triées ; garder cet ordre déterministe
|
|
# dans le candidat sans écrire directement dans la référence Git.
|
|
case "$key" in
|
|
pacman_*|system_enabled|system_running|system_timers|user_enabled|user_running|user_timers)
|
|
LC_ALL=C sort -u -- "$tmp" > "$tmp.sorted" || fail "tri impossible : $key"
|
|
mv -- "$tmp.sorted" "$tmp" || fail "tri impossible : $key" ;;
|
|
esac
|
|
if [[ ! -s "$tmp" ]]; then
|
|
if [[ "$empty_ok" == yes ]]; then state=EMPTY
|
|
else
|
|
rm -f -- "$tmp"
|
|
record "$key" "$class" COLLECT_ERROR 'sortie_vide_inattendue'
|
|
[[ "$class" == optional ]] || fail "sortie obligatoire vide : $key"
|
|
return 0
|
|
fi
|
|
fi
|
|
mkdir -p -- "$STAGE/${relative%/*}" || fail "répertoire de sortie impossible : $key"
|
|
mv -- "$tmp" "$STAGE/$relative" || fail "écriture de sortie impossible : $key"
|
|
hash_output "$relative"
|
|
record "$key" "$class" "$state" "$relative"
|
|
else
|
|
rm -f -- "$tmp"
|
|
record "$key" "$class" COLLECT_ERROR 'commande_en_echec_ou_selection_vide_indeterminee'
|
|
[[ "$class" == optional ]] || fail "collecte obligatoire échouée : $key"
|
|
fi
|
|
}
|
|
copy_source() {
|
|
local key="$1" class="$2" source="$3" relative="$4" state tmp
|
|
CURRENT="$key"
|
|
if state="$(sudo -n /usr/bin/bash -c "$INSPECT_CODE" -- "$source")"; then :
|
|
else
|
|
record "$key" "$class" CHECK_ERROR 'inspection_privilegiee_en_echec'
|
|
[[ "$class" == optional ]] || fail "contrôle obligatoire impossible : $key"
|
|
return 0
|
|
fi
|
|
case "$state" in
|
|
EXISTING) ;;
|
|
ABSENT)
|
|
record "$key" "$class" ABSENT 'absence_etablie'
|
|
[[ "$class" == optional ]] || fail "source obligatoire absente : $key"
|
|
return 0 ;;
|
|
*)
|
|
record "$key" "$class" CHECK_ERROR "etat_invalide_${state:-vide}"
|
|
[[ "$class" == optional ]] || fail "réponse de contrôle invalide : $key"
|
|
return 0 ;;
|
|
esac
|
|
tmp="$STAGE/.work/$key"
|
|
if sudo -n /usr/bin/cat -- "$source" > "$tmp"; then
|
|
mkdir -p -- "$STAGE/${relative%/*}" || fail "répertoire de sortie impossible : $key"
|
|
mv -- "$tmp" "$STAGE/$relative" || fail "écriture de sortie impossible : $key"
|
|
hash_output "$relative"
|
|
if [[ -s "$STAGE/$relative" ]]; then record "$key" "$class" SUCCESS "$relative"
|
|
else record "$key" "$class" EMPTY "$relative"; fi
|
|
else
|
|
rm -f -- "$tmp"
|
|
record "$key" "$class" COLLECT_ERROR 'lecture_privilegiee_en_echec'
|
|
[[ "$class" == optional ]] || fail "lecture obligatoire échouée : $key"
|
|
fi
|
|
}
|
|
make_limine_model() {
|
|
local raw="$STAGE/.work/limine_raw" model="$STAGE/.work/limine_model" state token tokens unsupported=0 grep_code
|
|
CURRENT=limine_model
|
|
if state="$(sudo -n /usr/bin/bash -c "$INSPECT_CODE" -- /boot/limine.conf)"; then :
|
|
else record limine_model optional CHECK_ERROR 'inspection_privilegiee_en_echec'; return 0; fi
|
|
case "$state" in
|
|
ABSENT) record limine_model optional ABSENT 'absence_etablie'; return 0 ;;
|
|
EXISTING) ;;
|
|
*) record limine_model optional CHECK_ERROR "etat_invalide_${state:-vide}"; return 0 ;;
|
|
esac
|
|
if ! sudo -n /usr/bin/cat -- /boot/limine.conf > "$raw"; then
|
|
rm -f -- "$raw"
|
|
record limine_model optional COLLECT_ERROR 'lecture_privilegiee_en_echec'; return 0
|
|
fi
|
|
if ! awk '/^[[:space:]]*### Auto-generated by limine-snapper-sync/ {exit} {print}' "$raw" |
|
|
sed -E -e 's/(^|[[:space:]])root=UUID=[[:xdigit:]-]+/\1root=UUID=@ROOT_UUID@/g' \
|
|
-e 's/(^|[[:space:]])root=PARTUUID=[[:xdigit:]-]+/\1root=PARTUUID=@ROOT_PARTUUID@/g' > "$model"; then
|
|
rm -f -- "$raw" "$model"
|
|
record limine_model optional COLLECT_ERROR 'transformation_en_echec'; return 0
|
|
fi
|
|
rm -f -- "$raw"
|
|
if [[ ! -s "$model" ]]; then
|
|
rm -f -- "$model"; record limine_model optional COLLECT_ERROR 'modele_vide'; return 0
|
|
fi
|
|
if tokens="$(grep -oE '(^|[[:space:]])root=[^[:space:]]+' "$model")"; then
|
|
while IFS= read -r token; do
|
|
token="${token#"${token%%[![:space:]]*}"}"
|
|
case "$token" in
|
|
'root=UUID=@ROOT_UUID@'|'root=PARTUUID=@ROOT_PARTUUID@') ;;
|
|
*) unsupported=1 ;;
|
|
esac
|
|
done <<< "$tokens"
|
|
else
|
|
grep_code=$?
|
|
if (( grep_code != 1 )); then
|
|
rm -f -- "$model"
|
|
record limine_model optional COLLECT_ERROR 'analyse_root_en_echec'; return 0
|
|
fi
|
|
unsupported=1
|
|
fi
|
|
mkdir -p -- "$STAGE/templates" || fail 'répertoire du modèle impossible'
|
|
mv -- "$model" "$STAGE/templates/limine.conf" || fail 'écriture du modèle impossible'
|
|
hash_output templates/limine.conf
|
|
if (( unsupported )); then
|
|
record limine_model optional PORTABILITY_REVIEW 'root_non_pris_en_charge_et_autres_champs_a_relire'
|
|
else
|
|
record limine_model optional PORTABILITY_REVIEW 'autres_champs_a_relire'
|
|
fi
|
|
}
|
|
show_plan() {
|
|
printf '[PLAN] Parent : %s ; chaque collecte aurait un sous-répertoire unique.\n' "$PARENT"
|
|
cat <<'PLAN'
|
|
[PLAN] Classe | source ou commande -> destination relative au candidat
|
|
[PLAN] obligatoire | pacman -Qqen -> pkglist/pacman.txt
|
|
[PLAN] obligatoire | pacman -Qqem -> pkglist/foreign.txt
|
|
[PLAN] facultatif | pacman -Qdtq -> pkglist/orphans.txt
|
|
[PLAN] obligatoire | systemctl système enabled -> inventory/systemd/system-enabled.txt
|
|
[PLAN] obligatoire | systemctl système running -> inventory/systemd/system-running.txt
|
|
[PLAN] obligatoire | systemctl système timers -> inventory/systemd/system-timers.txt
|
|
[PLAN] facultatif | systemctl système failed -> inventory/systemd/system-failed.txt
|
|
[PLAN] facultatif | systemctl utilisateur enabled -> inventory/systemd/user-enabled.txt
|
|
[PLAN] facultatif | systemctl utilisateur running -> inventory/systemd/user-running.txt
|
|
[PLAN] facultatif | systemctl utilisateur timers -> inventory/systemd/user-timers.txt
|
|
[PLAN] facultatif | systemctl utilisateur failed -> inventory/systemd/user-failed.txt
|
|
[PLAN] obligatoire | lsblk -> inventory/storage/lsblk.txt
|
|
[PLAN] obligatoire | findmnt -> inventory/storage/findmnt.txt
|
|
[PLAN] obligatoire | /etc/fstab -> inventory/storage/fstab.current
|
|
[PLAN] obligatoire | btrfs subvolume list / -> inventory/storage/btrfs-subvolumes.txt
|
|
[PLAN] obligatoire | btrfs filesystem usage / -> inventory/storage/btrfs-usage.txt
|
|
[PLAN] facultatif | btrfs scrub status / -> inventory/storage/btrfs-scrub.txt
|
|
[PLAN] obligatoire | free -h -> inventory/storage/memory.txt
|
|
[PLAN] obligatoire | swapon --show -> inventory/storage/swap.txt
|
|
[PLAN] facultatif | nvme smart-log /dev/nvme0 -> inventory/storage/nvme-health.txt
|
|
[PLAN] obligatoire | /etc/pacman.conf -> rootfs/etc/pacman.conf
|
|
[PLAN] obligatoire | /etc/makepkg.conf -> rootfs/etc/makepkg.conf
|
|
[PLAN] obligatoire | /etc/mkinitcpio.conf -> rootfs/etc/mkinitcpio.conf
|
|
[PLAN] facultatif | /etc/nftables.conf -> rootfs/etc/nftables.conf
|
|
[PLAN] facultatif | /etc/limine-snapper-sync.conf -> rootfs/etc/limine-snapper-sync.conf
|
|
[PLAN] facultatif | /etc/tlp.d/10-archasp.conf -> rootfs/etc/tlp.d/10-archasp.conf
|
|
[PLAN] facultatif | /etc/systemd/zram-generator.conf -> rootfs/etc/systemd/zram-generator.conf
|
|
[PLAN] facultatif | /etc/snapper/configs/root -> rootfs/etc/snapper/configs/root
|
|
[PLAN] facultatif | /usr/local/sbin/batlimit-set -> rootfs/usr/local/sbin/batlimit-set
|
|
[PLAN] facultatif | /usr/local/sbin/vmware-stack -> rootfs/usr/local/sbin/vmware-stack
|
|
[PLAN] facultatif | /boot/limine.conf -> templates/limine.conf
|
|
[PLAN] Transformations : tri des listes, retrait de la section Limine générée,
|
|
[PLAN] remplacement des identifiants root UUID/PARTUUID pris en charge ;
|
|
[PLAN] modèle restant à relire pour sa portabilité.
|
|
PLAN
|
|
printf '[PLAN] Aucun fichier du dépôt ou export existant ne sera modifié.\n'
|
|
}
|
|
|
|
(( EUID != 0 )) || fail 'exécution root refusée'
|
|
command -v realpath >/dev/null 2>&1 || fail 'commande requise absente : realpath'
|
|
choose_parent
|
|
if (( DRY_RUN )); then show_plan; exit 0; fi
|
|
for tool in stat mkdir mktemp mv date sha256sum git sudo sort; do
|
|
command -v "$tool" >/dev/null 2>&1 || fail "commande requise absente : $tool"
|
|
done
|
|
REVISION="$(git -C "$REPO_DIR" rev-parse HEAD)" || fail 'révision source indisponible'
|
|
[[ "$REVISION" =~ ^[[:xdigit:]]{40}$ ]] || fail 'révision source invalide'
|
|
make_parent
|
|
STAGE="$(mktemp -d "$PARENT/.incomplete.XXXXXXXX")" || fail 'création de la zone privée impossible'
|
|
STARTED="$(date -u +%Y-%m-%dT%H:%M:%SZ)" || fail 'horodatage indisponible'
|
|
mkdir -m 0700 -- "$STAGE/.work" || fail 'zone temporaire impossible'
|
|
write_manifest INCOMPLETE || fail 'manifeste initial impossible'
|
|
printf '[*] Export incomplet privé : %s\n' "$STAGE"
|
|
|
|
collect_command pacman_native required pkglist/pacman.txt yes pacman -Qqen
|
|
collect_command pacman_foreign required pkglist/foreign.txt yes pacman -Qqem
|
|
collect_command pacman_orphans optional pkglist/orphans.txt yes pacman -Qdtq
|
|
collect_command system_enabled required inventory/systemd/system-enabled.txt yes systemctl list-unit-files --type=service --state=enabled --no-legend --no-pager
|
|
collect_command system_running required inventory/systemd/system-running.txt yes systemctl --type=service --state=running --no-legend --no-pager
|
|
collect_command system_timers required inventory/systemd/system-timers.txt yes systemctl list-timers --all --no-legend --no-pager
|
|
collect_command system_failed optional inventory/systemd/system-failed.txt yes systemctl --failed --no-legend --no-pager
|
|
collect_command user_enabled optional inventory/systemd/user-enabled.txt yes systemctl --user list-unit-files --type=service --state=enabled --no-legend --no-pager
|
|
collect_command user_running optional inventory/systemd/user-running.txt yes systemctl --user --type=service --state=running --no-legend --no-pager
|
|
collect_command user_timers optional inventory/systemd/user-timers.txt yes systemctl --user list-timers --all --no-legend --no-pager
|
|
collect_command user_failed optional inventory/systemd/user-failed.txt yes systemctl --user --failed --no-legend --no-pager
|
|
collect_command lsblk required inventory/storage/lsblk.txt no lsblk -e7 -o NAME,MODEL,SIZE,ROTA,TRAN,FSTYPE,FSVER,LABEL,UUID,PARTUUID,MOUNTPOINTS
|
|
collect_command findmnt required inventory/storage/findmnt.txt no findmnt
|
|
copy_source fstab required /etc/fstab inventory/storage/fstab.current
|
|
collect_command btrfs_subvolumes required inventory/storage/btrfs-subvolumes.txt no sudo -n /usr/bin/btrfs subvolume list /
|
|
collect_command btrfs_usage required inventory/storage/btrfs-usage.txt no sudo -n /usr/bin/btrfs filesystem usage /
|
|
collect_command btrfs_scrub optional inventory/storage/btrfs-scrub.txt yes sudo -n /usr/bin/btrfs scrub status /
|
|
collect_command memory required inventory/storage/memory.txt no free -h
|
|
collect_command swap required inventory/storage/swap.txt yes swapon --show
|
|
if command -v nvme >/dev/null 2>&1 && [[ -e /dev/nvme0 ]]; then
|
|
collect_command nvme_health optional inventory/storage/nvme-health.txt no sudo -n /usr/bin/nvme smart-log /dev/nvme0
|
|
else record nvme_health optional UNAVAILABLE 'nvme0_ou_outil_absent'; fi
|
|
|
|
copy_source pacman_conf required /etc/pacman.conf rootfs/etc/pacman.conf
|
|
copy_source makepkg_conf required /etc/makepkg.conf rootfs/etc/makepkg.conf
|
|
copy_source mkinitcpio_conf required /etc/mkinitcpio.conf rootfs/etc/mkinitcpio.conf
|
|
copy_source nftables_conf optional /etc/nftables.conf rootfs/etc/nftables.conf
|
|
copy_source limine_sync_conf optional /etc/limine-snapper-sync.conf rootfs/etc/limine-snapper-sync.conf
|
|
copy_source tlp_conf optional /etc/tlp.d/10-archasp.conf rootfs/etc/tlp.d/10-archasp.conf
|
|
copy_source zram_conf optional /etc/systemd/zram-generator.conf rootfs/etc/systemd/zram-generator.conf
|
|
copy_source snapper_conf optional /etc/snapper/configs/root rootfs/etc/snapper/configs/root
|
|
copy_source batlimit_set optional /usr/local/sbin/batlimit-set rootfs/usr/local/sbin/batlimit-set
|
|
copy_source vmware_stack optional /usr/local/sbin/vmware-stack rootfs/usr/local/sbin/vmware-stack
|
|
make_limine_model
|
|
|
|
CURRENT=finalisation
|
|
rmdir -- "$STAGE/.work" || fail 'zone temporaire non vide'
|
|
if (( WARNINGS )); then final_status=COMPLETE_WITH_WARNINGS; else final_status=COMPLETE; fi
|
|
write_manifest "$final_status" || fail 'manifeste final impossible'
|
|
FINAL="$PARENT/export-${STAGE##*.incomplete.}"
|
|
[[ ! -e "$FINAL" && ! -L "$FINAL" ]] || fail "destination finale déjà existante : $FINAL"
|
|
# Une réponse de mv peut arriver après son effet, voire être suivie d'un signal
|
|
# avant l'instruction suivante. Mémoriser l'identité avant cette fenêtre.
|
|
STAGE_ID="$(stat -c '%d:%i' -- "$STAGE")" || fail 'identité de staging indisponible'
|
|
# mv -n interdit le remplacement ; sa sortie 0 peut aussi signifier non déplacé.
|
|
if mv -T -n -- "$STAGE" "$FINAL"; then :
|
|
else
|
|
reconcile_finalization
|
|
if (( FINAL_ACQUIRED )); then fail 'renommage accompli mais commande terminée en erreur'
|
|
else fail 'renommage final impossible'; fi
|
|
fi
|
|
reconcile_finalization
|
|
(( FINAL_ACQUIRED )) || fail 'renommage final non effectué'
|
|
STAGE=''
|
|
printf '[+] Candidat privé finalisé (%s) : %s\n' "$final_status" "$FINAL"
|
|
FINAL_ANNOUNCED=1
|