Feat: Automatische Btrfs-Root-Erkennung und Subvolume-Migration (@, @home) hinzugefügt
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -24,6 +24,7 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
|
|||||||
├── lib/ # Wiederverwendbare Funktionsbibliotheken
|
├── lib/ # Wiederverwendbare Funktionsbibliotheken
|
||||||
│ ├── utils.sh # Logging, Error-Traps, Benutzer- und Pfadermittlung, Backups
|
│ ├── utils.sh # Logging, Error-Traps, Benutzer- und Pfadermittlung, Backups
|
||||||
│ ├── apt.sh # Deb822-Quellen, Keyrings (/etc/apt/keyrings), APT-Pinning, Paketinstallation
|
│ ├── apt.sh # Deb822-Quellen, Keyrings (/etc/apt/keyrings), APT-Pinning, Paketinstallation
|
||||||
|
│ ├── btrfs.sh # Btrfs Root- & Subvolume-Erkennung, Migration (@, @home), fstab-Verwaltung
|
||||||
│ └── tui.sh # Whiptail-TUI-Menüs und Dialoge
|
│ └── tui.sh # Whiptail-TUI-Menüs und Dialoge
|
||||||
├── config/ # Konfigurationsdateien & Paketlisten
|
├── config/ # Konfigurationsdateien & Paketlisten
|
||||||
│ ├── setup.conf # Globale Umgebungsvariablen und Standardwerte
|
│ ├── setup.conf # Globale Umgebungsvariablen und Standardwerte
|
||||||
@@ -132,6 +133,12 @@ Dieses Repository ist ein modulares, automatisiertes Setup- und Konfigurations-F
|
|||||||
- `install_apt_keyring "name" "url"`: Lädt einen GPG-Schlüssel herunter, de-armort ihn sauber und speichert ihn in `/etc/apt/keyrings/<name>.gpg`.
|
- `install_apt_keyring "name" "url"`: Lädt einen GPG-Schlüssel herunter, de-armort ihn sauber und speichert ihn in `/etc/apt/keyrings/<name>.gpg`.
|
||||||
- `install_packages_from_file "filepath"`: Liest eine `.list`-Datei ein (ignoriert Kommentare `#` und Leerzeilen) und installiert alle Pakete via `apt-get install -y`.
|
- `install_packages_from_file "filepath"`: Liest eine `.list`-Datei ein (ignoriert Kommentare `#` und Leerzeilen) und installiert alle Pakete via `apt-get install -y`.
|
||||||
|
|
||||||
|
### `lib/btrfs.sh`
|
||||||
|
- `is_root_btrfs`: Prüft, ob das Root-Dateisystem auf Btrfs liegt.
|
||||||
|
- `is_root_subvolume_configured`: Prüft, ob Root bereits in einem Subvolume (`@`) läuft.
|
||||||
|
- `update_btrfs_fstab "fstab_file" "root_id"`: Aktualisiert Mount-Optionen in `/etc/fstab` für `@` und `@home`.
|
||||||
|
- `check_and_setup_btrfs_subvolumes`: Erkennt flache Btrfs-Root-Volumes und migriert `/` und `/home` transparent in Subvolumes `@` und `@home`.
|
||||||
|
|
||||||
### `lib/tui.sh`
|
### `lib/tui.sh`
|
||||||
- `tui_show_stage_selection`: Zeigt ein `whiptail` Checklist-Menü zur interaktiven Auswahl der auszuführenden Phasen.
|
- `tui_show_stage_selection`: Zeigt ein `whiptail` Checklist-Menü zur interaktiven Auswahl der auszuführenden Phasen.
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ sudo ./setup.sh --stages 04,05
|
|||||||
├── lib/ # Wiederverwendbare Hilfsbibliotheken
|
├── lib/ # Wiederverwendbare Hilfsbibliotheken
|
||||||
│ ├── utils.sh # Logging, Error-Traps (set -euo pipefail), Root/User-Erkennung & Abfrage
|
│ ├── utils.sh # Logging, Error-Traps (set -euo pipefail), Root/User-Erkennung & Abfrage
|
||||||
│ ├── apt.sh # APT-Keyring-Management (/etc/apt/keyrings/), Deb822, Pinning
|
│ ├── apt.sh # APT-Keyring-Management (/etc/apt/keyrings/), Deb822, Pinning
|
||||||
|
│ ├── btrfs.sh # Btrfs Root- & Subvolume-Erkennung, Migration (@, @home)
|
||||||
│ └── tui.sh # Whiptail-Menüs und Dialoge
|
│ └── tui.sh # Whiptail-Menüs und Dialoge
|
||||||
├── config/ # Konfigurationsdateien & Paketlisten
|
├── config/ # Konfigurationsdateien & Paketlisten
|
||||||
│ ├── setup.conf # Globale Variablen (Kernel, GRUB-Theme, Plymouth)
|
│ ├── setup.conf # Globale Variablen (Kernel, GRUB-Theme, Plymouth)
|
||||||
|
|||||||
+263
@@ -0,0 +1,263 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# lib/btrfs.sh - Btrfs root volume detection, subvolume management & migration
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
# shellcheck source=lib/utils.sh
|
||||||
|
source "$SCRIPT_DIR/lib/utils.sh"
|
||||||
|
|
||||||
|
# Check if the root filesystem is Btrfs
|
||||||
|
is_root_btrfs() {
|
||||||
|
local fstype
|
||||||
|
fstype="$(findmnt -n -o FSTYPE / 2>/dev/null || true)"
|
||||||
|
[[ "$fstype" == "btrfs" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve the root block device (stripping any subvolume bracket suffix)
|
||||||
|
get_root_btrfs_device() {
|
||||||
|
local dev
|
||||||
|
dev="$(findmnt -n -o SOURCE --canonical / 2>/dev/null || findmnt -n -o SOURCE / 2>/dev/null || true)"
|
||||||
|
dev="${dev%%\[*}"
|
||||||
|
echo "$dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve UUID of root filesystem
|
||||||
|
get_root_btrfs_uuid() {
|
||||||
|
local uuid dev
|
||||||
|
uuid="$(findmnt -n -o UUID / 2>/dev/null || true)"
|
||||||
|
if [[ -z "$uuid" ]]; then
|
||||||
|
dev="$(get_root_btrfs_device)"
|
||||||
|
if [[ -n "$dev" && -b "$dev" ]]; then
|
||||||
|
uuid="$(blkid -s UUID -o value "$dev" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$uuid"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if /home is mounted on a separate filesystem / partition from /
|
||||||
|
is_home_separate_filesystem() {
|
||||||
|
local root_dev home_dev
|
||||||
|
root_dev="$(get_root_btrfs_device)"
|
||||||
|
home_dev="$(findmnt -n -o SOURCE --canonical /home 2>/dev/null || findmnt -n -o SOURCE /home 2>/dev/null || true)"
|
||||||
|
home_dev="${home_dev%%\[*}"
|
||||||
|
if [[ -n "$home_dev" && -n "$root_dev" && "$home_dev" != "$root_dev" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if root is already running from a dedicated subvolume (@ or @rootfs)
|
||||||
|
is_root_subvolume_configured() {
|
||||||
|
local fsroot opts
|
||||||
|
fsroot="$(findmnt -n -o FSROOT / 2>/dev/null || true)"
|
||||||
|
opts="$(findmnt -n -o OPTIONS / 2>/dev/null || true)"
|
||||||
|
if [[ "$fsroot" == "/@" || "$fsroot" == "/@rootfs" ]] || \
|
||||||
|
[[ "$opts" =~ subvol=@ || "$opts" =~ subvol=/@ ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update /etc/fstab to mount / with subvol=@ and /home with subvol=@home
|
||||||
|
update_btrfs_fstab() {
|
||||||
|
local fstab_file="$1"
|
||||||
|
local root_id="$2"
|
||||||
|
|
||||||
|
[[ -f "$fstab_file" ]] || return 0
|
||||||
|
|
||||||
|
backup_file_or_dir "$fstab_file"
|
||||||
|
|
||||||
|
local tmp_fstab
|
||||||
|
tmp_fstab="$(mktemp)"
|
||||||
|
|
||||||
|
local found_root=0
|
||||||
|
local found_home=0
|
||||||
|
local root_spec="${root_id:-}"
|
||||||
|
local root_opts="defaults,subvol=@"
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
# Retain comments and blank lines
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*# || -z "${line// /}" ]]; then
|
||||||
|
echo "$line" >> "$tmp_fstab"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse fields
|
||||||
|
local spec mnt type opts freq pass
|
||||||
|
read -r spec mnt type opts freq pass <<< "$line"
|
||||||
|
|
||||||
|
if [[ "$mnt" == "/" && "$type" == "btrfs" ]]; then
|
||||||
|
found_root=1
|
||||||
|
root_spec="${root_id:-$spec}"
|
||||||
|
local new_opts="$opts"
|
||||||
|
if [[ "$new_opts" =~ subvol= ]] || [[ "$new_opts" =~ subvolid= ]]; then
|
||||||
|
new_opts="$(echo "$new_opts" | sed -E 's/(subvol|subvolid)=[^,]*/subvol=@/g')"
|
||||||
|
else
|
||||||
|
new_opts="${new_opts},subvol=@"
|
||||||
|
fi
|
||||||
|
new_opts="$(echo "$new_opts" | sed -E -e 's/,,+/,/g' -e 's/^,//' -e 's/,$//')"
|
||||||
|
root_opts="$new_opts"
|
||||||
|
spec="$root_spec"
|
||||||
|
printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$spec" "$mnt" "$type" "$new_opts" "${freq:-0}" "${pass:-0}" >> "$tmp_fstab"
|
||||||
|
elif [[ "$mnt" == "/home" && "$type" == "btrfs" ]]; then
|
||||||
|
found_home=1
|
||||||
|
local new_opts="$opts"
|
||||||
|
if [[ "$new_opts" =~ subvol= ]] || [[ "$new_opts" =~ subvolid= ]]; then
|
||||||
|
new_opts="$(echo "$new_opts" | sed -E 's/(subvol|subvolid)=[^,]*/subvol=@home/g')"
|
||||||
|
else
|
||||||
|
new_opts="${new_opts},subvol=@home"
|
||||||
|
fi
|
||||||
|
new_opts="$(echo "$new_opts" | sed -E -e 's/,,+/,/g' -e 's/^,//' -e 's/,$//')"
|
||||||
|
spec="${root_id:-$spec}"
|
||||||
|
printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$spec" "$mnt" "$type" "$new_opts" "${freq:-0}" "${pass:-0}" >> "$tmp_fstab"
|
||||||
|
else
|
||||||
|
echo "$line" >> "$tmp_fstab"
|
||||||
|
fi
|
||||||
|
done < "$fstab_file"
|
||||||
|
|
||||||
|
# If home was not present in fstab and root was found/specified, add /home entry
|
||||||
|
if [[ "$found_home" -eq 0 && ( "$found_root" -eq 1 || -n "$root_spec" ) ]]; then
|
||||||
|
local home_opts="${root_opts/subvol=@/subvol=@home}"
|
||||||
|
if [[ "$home_opts" == "$root_opts" ]]; then
|
||||||
|
home_opts="defaults,subvol=@home"
|
||||||
|
fi
|
||||||
|
printf "%s\t%s\t%s\t%s\t%s\t%s\n" "$root_spec" "/home" "btrfs" "$home_opts" "0" "0" >> "$tmp_fstab"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! cat "$tmp_fstab" > "$fstab_file" 2>/dev/null; then
|
||||||
|
log_warn "Could not write to $fstab_file (permission denied)."
|
||||||
|
fi
|
||||||
|
rm -f "$tmp_fstab"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function to check and configure Btrfs subvolumes for root and home
|
||||||
|
check_and_setup_btrfs_subvolumes() {
|
||||||
|
if ! is_root_btrfs; then
|
||||||
|
local fstype
|
||||||
|
fstype="$(findmnt -n -o FSTYPE / 2>/dev/null || echo "unknown")"
|
||||||
|
log_info "Root filesystem is not Btrfs ($fstype). Skipping Btrfs subvolume migration."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Btrfs root filesystem detected."
|
||||||
|
|
||||||
|
if is_root_subvolume_configured; then
|
||||||
|
local fsroot
|
||||||
|
fsroot="$(findmnt -n -o FSROOT / 2>/dev/null || true)"
|
||||||
|
log_success "Root filesystem is already running inside subvolume '$fsroot'."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure btrfs utility is available
|
||||||
|
if ! command_exists btrfs; then
|
||||||
|
log_info "Installing btrfs-progs..."
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null 2>&1 || true
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends btrfs-progs || {
|
||||||
|
log_error "btrfs-progs could not be installed."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
local root_dev root_uuid
|
||||||
|
root_dev="$(get_root_btrfs_device)"
|
||||||
|
root_uuid="$(get_root_btrfs_uuid)"
|
||||||
|
|
||||||
|
if [[ -z "$root_dev" ]]; then
|
||||||
|
log_warn "Could not determine root block device for Btrfs. Skipping migration."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local root_fstab_id
|
||||||
|
if [[ -n "$root_uuid" ]]; then
|
||||||
|
root_fstab_id="UUID=$root_uuid"
|
||||||
|
else
|
||||||
|
root_fstab_id="$root_dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_step "Btrfs Migration: Creating subvolumes '@' and '@home'..."
|
||||||
|
|
||||||
|
local tmp_mnt
|
||||||
|
tmp_mnt="$(mktemp -d /tmp/btrfs-toplevel.XXXXXX)"
|
||||||
|
|
||||||
|
# Trap to safely unmount and remove temporary mountpoint
|
||||||
|
cleanup_tmp_mnt() {
|
||||||
|
if [[ -d "$tmp_mnt" ]]; then
|
||||||
|
if mountpoint -q "$tmp_mnt"; then
|
||||||
|
umount "$tmp_mnt" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp_mnt"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mount top-level btrfs root (subvolid=5)
|
||||||
|
if ! mount -t btrfs -o subvolid=5 "$root_dev" "$tmp_mnt"; then
|
||||||
|
log_error "Failed to mount top-level Btrfs volume on $tmp_mnt."
|
||||||
|
rm -rf "$tmp_mnt"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. Create or snapshot '@' subvolume for root
|
||||||
|
if [[ ! -d "$tmp_mnt/@" ]]; then
|
||||||
|
log_info "Creating snapshot of top-level filesystem into '@' subvolume..."
|
||||||
|
btrfs subvolume snapshot "$tmp_mnt" "$tmp_mnt/@"
|
||||||
|
log_success "Created '@' subvolume snapshot."
|
||||||
|
else
|
||||||
|
log_info "'@' subvolume already exists in top-level Btrfs tree."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Create '@home' subvolume if /home is on the same volume
|
||||||
|
if ! is_home_separate_filesystem; then
|
||||||
|
if [[ ! -d "$tmp_mnt/@home" ]]; then
|
||||||
|
log_info "Creating '@home' subvolume..."
|
||||||
|
btrfs subvolume create "$tmp_mnt/@home"
|
||||||
|
log_success "Created '@home' subvolume."
|
||||||
|
|
||||||
|
# Move existing home data into @home
|
||||||
|
if [[ -d "$tmp_mnt/@/home" && "$(ls -A "$tmp_mnt/@/home" 2>/dev/null)" ]]; then
|
||||||
|
log_info "Moving existing /home data to '@home' subvolume..."
|
||||||
|
cp -a --reflink=auto "$tmp_mnt/@/home/." "$tmp_mnt/@home/"
|
||||||
|
# Clean up the directory inside @/home so it acts as an empty mountpoint
|
||||||
|
rm -rf "$tmp_mnt/@/home"/* "$tmp_mnt/@/home"/.[!.]* "$tmp_mnt/@/home"/..?* 2>/dev/null || true
|
||||||
|
chmod 755 "$tmp_mnt/@/home" 2>/dev/null || true
|
||||||
|
chown root:root "$tmp_mnt/@/home" 2>/dev/null || true
|
||||||
|
log_success "Moved /home data to '@home' subvolume."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "'@home' subvolume already exists in top-level Btrfs tree."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "/home is mounted on a separate filesystem. Skipping '@home' subvolume creation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Set default subvolume to '@'
|
||||||
|
local subvol_at_id
|
||||||
|
subvol_at_id="$(btrfs subvolume list "$tmp_mnt" 2>/dev/null | awk '$NF == "@" {print $2}' | tail -n1)"
|
||||||
|
if [[ -n "$subvol_at_id" ]]; then
|
||||||
|
btrfs subvolume set-default "$subvol_at_id" "$tmp_mnt"
|
||||||
|
log_success "Set default Btrfs subvolume to '@' (ID: $subvol_at_id)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Update /etc/fstab (and the snapshotted @/etc/fstab)
|
||||||
|
log_info "Updating /etc/fstab entries for '@' and '@home'..."
|
||||||
|
update_btrfs_fstab "/etc/fstab" "$root_fstab_id"
|
||||||
|
if [[ -f "$tmp_mnt/@/etc/fstab" ]]; then
|
||||||
|
update_btrfs_fstab "$tmp_mnt/@/etc/fstab" "$root_fstab_id"
|
||||||
|
fi
|
||||||
|
log_success "Updated /etc/fstab with subvolume mount options."
|
||||||
|
|
||||||
|
# 5. Mount '@home' at /home for the current live session if not already mounted
|
||||||
|
if ! is_home_separate_filesystem; then
|
||||||
|
if ! findmnt /home >/dev/null 2>&1; then
|
||||||
|
log_info "Mounting '@home' subvolume at /home for current setup session..."
|
||||||
|
mount -t btrfs -o subvol=@home "$root_dev" /home || log_warn "Could not mount @home at /home live."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up temporary top-level mount
|
||||||
|
cleanup_tmp_mnt
|
||||||
|
|
||||||
|
log_success "Btrfs subvolume setup and migration completed successfully."
|
||||||
|
}
|
||||||
+12
-1
@@ -250,6 +250,10 @@ run_as_target_user() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_as_user() {
|
||||||
|
run_as_target_user "$@"
|
||||||
|
}
|
||||||
|
|
||||||
# Create a safe temporary directory with auto-cleanup
|
# Create a safe temporary directory with auto-cleanup
|
||||||
create_temp_dir() {
|
create_temp_dir() {
|
||||||
local prefix="${1:-setup-temp}"
|
local prefix="${1:-setup-temp}"
|
||||||
@@ -265,7 +269,14 @@ backup_file_or_dir() {
|
|||||||
timestamp="$(date +%Y%m%d_%H%M%S)"
|
timestamp="$(date +%Y%m%d_%H%M%S)"
|
||||||
if [[ -e "$target" ]]; then
|
if [[ -e "$target" ]]; then
|
||||||
local backup_path="${target}.backup_${timestamp}"
|
local backup_path="${target}.backup_${timestamp}"
|
||||||
cp -r "$target" "$backup_path"
|
if cp -r "$target" "$backup_path" 2>/dev/null; then
|
||||||
log_info "Created backup: $backup_path"
|
log_info "Created backup: $backup_path"
|
||||||
|
else
|
||||||
|
log_warn "Could not create backup of $target (permission denied)."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backup_file() {
|
||||||
|
backup_file_or_dir "$@"
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
# Source Libraries & Config
|
# Source Libraries & Config
|
||||||
source "$SCRIPT_DIR/lib/utils.sh"
|
source "$SCRIPT_DIR/lib/utils.sh"
|
||||||
source "$SCRIPT_DIR/lib/apt.sh"
|
source "$SCRIPT_DIR/lib/apt.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/btrfs.sh"
|
||||||
source "$SCRIPT_DIR/lib/tui.sh"
|
source "$SCRIPT_DIR/lib/tui.sh"
|
||||||
|
|
||||||
if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then
|
if [[ -f "$SCRIPT_DIR/config/setup.conf" ]]; then
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
source "$SCRIPT_DIR/lib/utils.sh"
|
source "$SCRIPT_DIR/lib/utils.sh"
|
||||||
|
source "$SCRIPT_DIR/lib/btrfs.sh"
|
||||||
|
|
||||||
setup_err_trap
|
setup_err_trap
|
||||||
|
|
||||||
@@ -67,4 +68,8 @@ else
|
|||||||
log_success "Sufficient disk space available ($(( AVAILABLE_KB / 1024 / 1024 )) GB free)."
|
log_success "Sufficient disk space available ($(( AVAILABLE_KB / 1024 / 1024 )) GB free)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 6. Check & Setup Btrfs Subvolumes (@, @home)
|
||||||
|
log_substep "Checking Btrfs root volume & subvolume layout..."
|
||||||
|
check_and_setup_btrfs_subvolumes
|
||||||
|
|
||||||
log_success "Stage 00: Pre-flight checks passed successfully."
|
log_success "Stage 00: Pre-flight checks passed successfully."
|
||||||
|
|||||||
Reference in New Issue
Block a user