fix(installer): harden multi-disk LUKS, password handling, revision pinning
Several installer reliability fixes that were left uncommitted:
- Impermanence + multi-disk LUKS: disko-config.nix names the main LUKS
mapping `crypted` for single-disk and `crypted_main` once extraDrives is
non-empty. The impermanence rollback hook used to hardcode `crypted`,
which made every multi-disk install fail to mount root in initrd. Added
a `nomarchy.system.impermanence.mainLuksName` option and wired the
installer to write the correct value into the generated system.nix
based on the drive count.
- Password no longer cleartext in /etc/nixos: installer now hashes the
user password with `mkpasswd -m sha-512` and emits
`initialHashedPassword` instead of `initialPassword`. Added mkpasswd to
the live ISO. Cleartext is unset immediately after hashing.
USER_PASSWORD_HASH is deliberately not persisted in --resume state —
configure_user re-prompts on resume.
- Revision pinning that actually works on the live ISO: `inputs.self`
strips .git in the Nix store copy, so `git rev-parse HEAD` would silently
return empty on a real install and the generated flake would track main.
Live ISO now writes `/etc/nomarchy-rev` from `inputs.self.rev` at build
time; install.sh reads it first, falls back to git, and aborts with a
loud confirmation prompt if both are empty (instead of silently
installing an unpinned system).
- Generated `/mnt/etc/nixos/state.json`: toggle scripts (nomarchy-tz-select,
nomarchy-setup-{fido2,fingerprint}, nomarchy-toggle-hybrid-gpu,
nomarchy-wifi-powersave) `jq` this file in place and fail hard if it
doesn't exist. Fresh installs now ship a schema-conformant file matching
lib/state-schema.nix.
- Unmount /mnt before exiting `finish()` regardless of reboot choice. Clean
unmount avoids dirty BTRFS on reboot; on "no", leaving /mnt mounted
blocked a second installer run on the same live ISO.
- Removed obsolete `installer/disko-btrfs-luks.nix` (superseded by
`disko-config.nix` per commit 3aadc36) and dropped its dangling
`docs/STRUCTURE.md` reference.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -10,13 +10,27 @@ in
|
|||||||
|
|
||||||
options.nomarchy.system.impermanence = {
|
options.nomarchy.system.impermanence = {
|
||||||
enable = lib.mkEnableOption "Erase Your Darlings (Impermanence) root wipe on boot";
|
enable = lib.mkEnableOption "Erase Your Darlings (Impermanence) root wipe on boot";
|
||||||
|
|
||||||
|
# The disko layout names the main LUKS mapping `crypted` on single-disk
|
||||||
|
# installs and `crypted_main` on multi-disk installs (see
|
||||||
|
# installer/disko-config.nix: `mainLuksName`). The rollback hook must
|
||||||
|
# mount the right device, otherwise initrd fails on every boot and the
|
||||||
|
# @ → root-blank snapshot is never restored.
|
||||||
|
mainLuksName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "crypted";
|
||||||
|
description = ''
|
||||||
|
Name of the /dev/mapper entry holding the BTRFS root. Set to
|
||||||
|
"crypted_main" on multi-disk installs to match the disko layout.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
# 1. The Rollback Script: Runs in initrd before filesystems are mounted
|
# 1. The Rollback Script: Runs in initrd before filesystems are mounted
|
||||||
boot.initrd.postDeviceCommands = lib.mkAfter ''
|
boot.initrd.postDeviceCommands = lib.mkAfter ''
|
||||||
mkdir -p /btrfs_tmp
|
mkdir -p /btrfs_tmp
|
||||||
mount -o subvol=/ /dev/mapper/crypted /btrfs_tmp
|
mount -o subvol=/ /dev/mapper/${cfg.mainLuksName} /btrfs_tmp
|
||||||
|
|
||||||
if [[ -e /btrfs_tmp/@ ]]; then
|
if [[ -e /btrfs_tmp/@ ]]; then
|
||||||
mkdir -p /btrfs_tmp/old_roots
|
mkdir -p /btrfs_tmp/old_roots
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ The `lib/` directory provides centralized logic and data structures to maintain
|
|||||||
### `installer/` (Bootstrap)
|
### `installer/` (Bootstrap)
|
||||||
- **`install.sh`**: The interactive TTY-based installer. It handles disk partitioning, NixOS installation, and generating a clean "Downstream" flake for the user.
|
- **`install.sh`**: The interactive TTY-based installer. It handles disk partitioning, NixOS installation, and generating a clean "Downstream" flake for the user.
|
||||||
- **`disko-config.nix`**: The disko partition layout (BTRFS on top of LUKS2). A Nix function of `{ mainDrive, extraDrives ? [] }` — single-disk path is `extraDrives = []`; multi-disk adds BTRFS `-d single -m raid1` across the extras. Invoked by `install.sh` via `disko --argstr mainDrive … --arg extraDrives '[…]'`.
|
- **`disko-config.nix`**: The disko partition layout (BTRFS on top of LUKS2). A Nix function of `{ mainDrive, extraDrives ? [] }` — single-disk path is `extraDrives = []`; multi-disk adds BTRFS `-d single -m raid1` across the extras. Invoked by `install.sh` via `disko --argstr mainDrive … --arg extraDrives '[…]'`.
|
||||||
- **`disko-btrfs-luks.nix`**: A simpler reference layout for disk management (not used by the installer).
|
|
||||||
|
|
||||||
### `hosts/` (Targets)
|
### `hosts/` (Targets)
|
||||||
- **`nomarchy-installer.nix`**: Configuration for the minimal, TTY-based installation ISO.
|
- **`nomarchy-installer.nix`**: Configuration for the minimal, TTY-based installation ISO.
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
parted
|
parted
|
||||||
btrfs-progs
|
btrfs-progs
|
||||||
cryptsetup
|
cryptsetup
|
||||||
|
mkpasswd
|
||||||
inputs.disko.packages.${pkgs.stdenv.hostPlatform.system}.disko
|
inputs.disko.packages.${pkgs.stdenv.hostPlatform.system}.disko
|
||||||
(pkgs.makeDesktopItem {
|
(pkgs.makeDesktopItem {
|
||||||
name = "install-nomarchy";
|
name = "install-nomarchy";
|
||||||
@@ -64,6 +65,15 @@
|
|||||||
|
|
||||||
environment.etc."nomarchy".source = inputs.self;
|
environment.etc."nomarchy".source = inputs.self;
|
||||||
|
|
||||||
|
# Embed the git revision the ISO was built from so install.sh can pin the
|
||||||
|
# generated flake to the exact same commit. `inputs.self.rev` exists only
|
||||||
|
# when the flake is built from a clean git tree; from a dirty worktree we
|
||||||
|
# fall back to dirtyRev (which won't be resolvable by `git+https`, so the
|
||||||
|
# installer treats it as "unpinned"). Empty file = unpinned.
|
||||||
|
environment.etc."nomarchy-rev".text =
|
||||||
|
if inputs.self ? rev then inputs.self.rev
|
||||||
|
else "";
|
||||||
|
|
||||||
# Auto-login to the graphical session
|
# Auto-login to the graphical session
|
||||||
services.displayManager.autoLogin.enable = true;
|
services.displayManager.autoLogin.enable = true;
|
||||||
services.displayManager.autoLogin.user = "nixos";
|
services.displayManager.autoLogin.user = "nixos";
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
disko.devices = {
|
|
||||||
disk = {
|
|
||||||
main = {
|
|
||||||
type = "disk";
|
|
||||||
device = "@TARGET_DRIVE@";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
ESP = {
|
|
||||||
priority = 1;
|
|
||||||
name = "ESP";
|
|
||||||
start = "1M";
|
|
||||||
end = "512M";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
mountOptions = [ "umask=0077" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
luks = {
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "luks";
|
|
||||||
name = "crypted";
|
|
||||||
settings.allowDiscards = true;
|
|
||||||
content = {
|
|
||||||
type = "btrfs";
|
|
||||||
extraArgs = [ "-f" ];
|
|
||||||
subvolumes = {
|
|
||||||
"@" = {
|
|
||||||
mountpoint = "/";
|
|
||||||
mountOptions = [ "compress=zstd" "noatime" ];
|
|
||||||
};
|
|
||||||
"@persist" = {
|
|
||||||
mountpoint = "/persist";
|
|
||||||
mountOptions = [ "compress=zstd" "noatime" ];
|
|
||||||
};
|
|
||||||
"@home" = {
|
|
||||||
mountpoint = "/home";
|
|
||||||
mountOptions = [ "compress=zstd" "noatime" ];
|
|
||||||
};
|
|
||||||
"@nix" = {
|
|
||||||
mountpoint = "/nix";
|
|
||||||
mountOptions = [ "compress=zstd" "noatime" ];
|
|
||||||
};
|
|
||||||
"@log" = {
|
|
||||||
mountpoint = "/var/log";
|
|
||||||
mountOptions = [ "compress=zstd" "noatime" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
postCreateHook = ''
|
|
||||||
MNTPOINT=$(mktemp -d)
|
|
||||||
mount -t btrfs /dev/mapper/crypted $MNTPOINT
|
|
||||||
btrfs subvolume snapshot -r $MNTPOINT/@ $MNTPOINT/root-blank
|
|
||||||
umount $MNTPOINT
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,7 @@ TARGET_DRIVE=""
|
|||||||
USERNAME=""
|
USERNAME=""
|
||||||
LUKS_PASSWORD=""
|
LUKS_PASSWORD=""
|
||||||
USER_PASSWORD=""
|
USER_PASSWORD=""
|
||||||
|
USER_PASSWORD_HASH=""
|
||||||
TIMEZONE="UTC"
|
TIMEZONE="UTC"
|
||||||
KEYMAP_LAYOUT=""
|
KEYMAP_LAYOUT=""
|
||||||
KEYMAP_VARIANT=""
|
KEYMAP_VARIANT=""
|
||||||
@@ -92,6 +93,14 @@ parse_args() {
|
|||||||
# Persist non-secret answers so an interrupted install can pick up where it
|
# Persist non-secret answers so an interrupted install can pick up where it
|
||||||
# left off. Uses `declare -p` so each line is a self-contained `declare --`
|
# left off. Uses `declare -p` so each line is a self-contained `declare --`
|
||||||
# statement that `source` re-establishes verbatim.
|
# statement that `source` re-establishes verbatim.
|
||||||
|
#
|
||||||
|
# USER_PASSWORD_HASH is intentionally NOT persisted, even though a SHA-512
|
||||||
|
# crypt hash isn't reversible. The contract is: after --resume, the password
|
||||||
|
# prompt re-runs. configure_user's early-return guard at the top of the
|
||||||
|
# function checks `[[ -n "$USER_PASSWORD_HASH" ]]` for exactly this reason —
|
||||||
|
# if you ever change that guard to skip on USERNAME+HOSTNAME alone, --resume
|
||||||
|
# will silently install a system with an empty password hash and lock the
|
||||||
|
# user out. Keep the guard checking the hash.
|
||||||
save_state() {
|
save_state() {
|
||||||
declare -p \
|
declare -p \
|
||||||
TARGET_DRIVE USERNAME HOSTNAME TIMEZONE \
|
TARGET_DRIVE USERNAME HOSTNAME TIMEZONE \
|
||||||
@@ -128,7 +137,7 @@ clear_step_state() {
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
select_disk) TARGET_DRIVE="" ;;
|
select_disk) TARGET_DRIVE="" ;;
|
||||||
get_luks_passphrase) LUKS_PASSWORD="" ;;
|
get_luks_passphrase) LUKS_PASSWORD="" ;;
|
||||||
configure_user) USERNAME=""; HOSTNAME=""; USER_PASSWORD="" ;;
|
configure_user) USERNAME=""; HOSTNAME=""; USER_PASSWORD=""; USER_PASSWORD_HASH="" ;;
|
||||||
select_keymap_locale) KEYMAP_LAYOUT=""; KEYMAP_VARIANT=""; LOCALE="" ;;
|
select_keymap_locale) KEYMAP_LAYOUT=""; KEYMAP_VARIANT=""; LOCALE="" ;;
|
||||||
select_timezone) TIMEZONE="" ;;
|
select_timezone) TIMEZONE="" ;;
|
||||||
select_hardware) HARDWARE_MODULES=""; NOMARCHY_HW_OPTS="" ;;
|
select_hardware) HARDWARE_MODULES=""; NOMARCHY_HW_OPTS="" ;;
|
||||||
@@ -206,13 +215,32 @@ check_environment() {
|
|||||||
# Capture the exact commit we're installing from. The generated flake
|
# Capture the exact commit we're installing from. The generated flake
|
||||||
# pins `nomarchy.url` to this revision so the installed system can't
|
# pins `nomarchy.url` to this revision so the installed system can't
|
||||||
# silently drift onto a newer (possibly breaking) main.
|
# silently drift onto a newer (possibly breaking) main.
|
||||||
if command -v git >/dev/null 2>&1 && [[ -d "$NOMARCHY_REPO/.git" ]]; then
|
#
|
||||||
|
# Three sources, in priority order:
|
||||||
|
# 1. /etc/nomarchy-rev — written at ISO build time from `inputs.self.rev`
|
||||||
|
# (the only source that works on a normal live-ISO install, because
|
||||||
|
# `inputs.self` strips .git from the Nix store copy at /etc/nomarchy).
|
||||||
|
# 2. `git rev-parse HEAD` in the repo — works when running the installer
|
||||||
|
# from a dev checkout instead of the live ISO.
|
||||||
|
# 3. Empty → unpinned, user gets a loud confirmation prompt below.
|
||||||
|
if [[ -z "$NOMARCHY_REV" ]] && [[ -f /etc/nomarchy-rev ]]; then
|
||||||
|
NOMARCHY_REV=$(tr -d '[:space:]' < /etc/nomarchy-rev)
|
||||||
|
fi
|
||||||
|
if [[ -z "$NOMARCHY_REV" ]] && command -v git >/dev/null 2>&1 && [[ -d "$NOMARCHY_REPO/.git" ]]; then
|
||||||
NOMARCHY_REV=$(git -C "$NOMARCHY_REPO" rev-parse HEAD 2>/dev/null || echo "")
|
NOMARCHY_REV=$(git -C "$NOMARCHY_REPO" rev-parse HEAD 2>/dev/null || echo "")
|
||||||
fi
|
fi
|
||||||
if [[ -n "$NOMARCHY_REV" ]]; then
|
if [[ -n "$NOMARCHY_REV" ]]; then
|
||||||
success "Pinning Nomarchy to $NOMARCHY_REV"
|
success "Pinning Nomarchy to $NOMARCHY_REV"
|
||||||
else
|
else
|
||||||
info "Could not determine Nomarchy revision; downstream flake will track main."
|
error "Could not determine Nomarchy revision."
|
||||||
|
info "The installed system would silently track upstream main, which"
|
||||||
|
info "defeats the point of locking inputs at install time."
|
||||||
|
if [[ "$DRY_RUN" != "true" ]]; then
|
||||||
|
if ! nrun gum confirm --default=false \
|
||||||
|
"Continue anyway with an unpinned (tracking main) configuration?"; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check internet
|
# Check internet
|
||||||
@@ -452,8 +480,8 @@ configure_user() {
|
|||||||
section "User Configuration"
|
section "User Configuration"
|
||||||
|
|
||||||
if [[ -n "$USERNAME" && -n "$HOSTNAME" ]]; then
|
if [[ -n "$USERNAME" && -n "$HOSTNAME" ]]; then
|
||||||
# Password check skipped in dry run or if already set
|
# Password check skipped in dry run or if already hashed in this session.
|
||||||
if [[ "$DRY_RUN" == "true" ]] || [[ -n "$USER_PASSWORD" ]]; then
|
if [[ "$DRY_RUN" == "true" ]] || [[ -n "$USER_PASSWORD_HASH" ]]; then
|
||||||
success "User $USERNAME @ $HOSTNAME configured"
|
success "User $USERNAME @ $HOSTNAME configured"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -493,6 +521,9 @@ configure_user() {
|
|||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
info "Dry run: skipping user password prompt."
|
info "Dry run: skipping user password prompt."
|
||||||
USER_PASSWORD="dryrun-not-used"
|
USER_PASSWORD="dryrun-not-used"
|
||||||
|
# Stable placeholder hash so generated system.nix still parses as Nix.
|
||||||
|
# Never lands on a real install — dry-run skips nixos-install.
|
||||||
|
USER_PASSWORD_HASH='$6$dryrun$3xxK3aQ.0bGcv0fM2RhV4Q9oN3p1mYxz5kSjQ.bC8tZpZ7QnFv2cN0Yhd5lDqJ8X9mP2K1L0vR6BqWqzNk7Yo/'
|
||||||
save_state
|
save_state
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -521,6 +552,20 @@ configure_user() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Hash now so we never have to embed the cleartext in /etc/nixos/system.nix
|
||||||
|
# (where it would be world-readable and break Nix parsing on quotes/backslash).
|
||||||
|
# mkpasswd is added to the live ISO via hosts/nomarchy-live.nix.
|
||||||
|
if ! command -v mkpasswd >/dev/null 2>&1; then
|
||||||
|
error "mkpasswd not found on the live ISO — cannot hash the user password."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
USER_PASSWORD_HASH=$(printf '%s' "$USER_PASSWORD" | mkpasswd -m sha-512 -s)
|
||||||
|
if [[ -z "$USER_PASSWORD_HASH" ]]; then
|
||||||
|
error "mkpasswd produced an empty hash."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
unset pass1 pass2 USER_PASSWORD
|
||||||
|
|
||||||
success "User password set"
|
success "User password set"
|
||||||
save_state
|
save_state
|
||||||
}
|
}
|
||||||
@@ -1346,9 +1391,17 @@ EOF
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
generate_flake_config() {
|
generate_flake_config() {
|
||||||
|
# Impermanence must mount the same /dev/mapper name that disko created.
|
||||||
|
# disko-config.nix uses "crypted" for single-disk and "crypted_main" once
|
||||||
|
# extraDrives is non-empty — keep these in sync.
|
||||||
local impermanence_opt=""
|
local impermanence_opt=""
|
||||||
if [[ "$ENABLE_IMPERMANENCE" == "true" ]]; then
|
if [[ "$ENABLE_IMPERMANENCE" == "true" ]]; then
|
||||||
impermanence_opt="nomarchy.system.impermanence.enable = true;"
|
local _main_luks_name="crypted"
|
||||||
|
local _drives=($TARGET_DRIVE)
|
||||||
|
if (( ${#_drives[@]} > 1 )); then
|
||||||
|
_main_luks_name="crypted_main"
|
||||||
|
fi
|
||||||
|
impermanence_opt=$'nomarchy.system.impermanence.enable = true;\n nomarchy.system.impermanence.mainLuksName = "'"$_main_luks_name"$'";'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local PROFILE_SYSTEM_OPTS=""
|
local PROFILE_SYSTEM_OPTS=""
|
||||||
@@ -1534,7 +1587,9 @@ $xkb_variant_line
|
|||||||
|
|
||||||
users.users."$USERNAME" = {
|
users.users."$USERNAME" = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
initialPassword = "$USER_PASSWORD";
|
# SHA-512 crypt hash generated by mkpasswd during install. Cleartext
|
||||||
|
# never touches /etc/nixos. Change later with \`passwd\`.
|
||||||
|
initialHashedPassword = "$USER_PASSWORD_HASH";
|
||||||
extraGroups = [ "networkmanager" "wheel" "video" "audio" "render" ];
|
extraGroups = [ "networkmanager" "wheel" "video" "audio" "render" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1685,6 +1740,31 @@ EOF
|
|||||||
# Extra Home Manager modules go here (program configs, services, etc.).
|
# Extra Home Manager modules go here (program configs, services, etc.).
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# state.json — consumed by core/system/state.nix at every nixos-rebuild and
|
||||||
|
# mutated by toggle scripts (nomarchy-tz-select, nomarchy-setup-fido2,
|
||||||
|
# nomarchy-toggle-hybrid-gpu, nomarchy-wifi-powersave, ...). Those scripts
|
||||||
|
# `jq` the file in place and fail hard if it doesn't exist or isn't valid
|
||||||
|
# JSON, so a fresh install MUST ship one. Shape must match lib/state-schema.nix.
|
||||||
|
# Quoted heredoc — no shell expansion except the explicit $TIMEZONE below.
|
||||||
|
local _state_tz="${TIMEZONE:-UTC}"
|
||||||
|
cat > /mnt/etc/nixos/state.json <<JSON_EOF
|
||||||
|
{
|
||||||
|
"theme": "nord",
|
||||||
|
"timezone": "${_state_tz}",
|
||||||
|
"dns": "DHCP",
|
||||||
|
"customDns": [],
|
||||||
|
"wifi": {
|
||||||
|
"powersave": true
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"fingerprint": false,
|
||||||
|
"fido2": false,
|
||||||
|
"hybridGPU": false,
|
||||||
|
"makima": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSON_EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -1712,8 +1792,18 @@ finish() {
|
|||||||
echo " variant. Theme switches after that are instant (no rebuild)."
|
echo " variant. Theme switches after that are instant (no rebuild)."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Unmount /mnt before either reboot or returning to the live shell:
|
||||||
|
# - Reboot: clean unmount avoids dirty BTRFS, which would otherwise
|
||||||
|
# force a longer first-boot fsck/replay.
|
||||||
|
# - Decline: leaving /mnt mounted blocks a second `install.sh` run on
|
||||||
|
# the same live ISO (disko refuses to wipe a busy device).
|
||||||
|
# `-R` recursively unmounts /mnt/boot, /mnt/home, /mnt/nix, etc.; the
|
||||||
|
# `|| true` absorbs the case where /mnt was already torn down.
|
||||||
if nrun gum confirm "Reboot now?"; then
|
if nrun gum confirm "Reboot now?"; then
|
||||||
|
umount -R /mnt 2>/dev/null || true
|
||||||
reboot
|
reboot
|
||||||
|
else
|
||||||
|
umount -R /mnt 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user