Skip to content

Instantly share code, notes, and snippets.

@marhensa
Last active June 1, 2025 06:04
Show Gist options
  • Save marhensa/5296c317b629d7778468c9d988c779df to your computer and use it in GitHub Desktop.
Save marhensa/5296c317b629d7778468c9d988c779df to your computer and use it in GitHub Desktop.
Mount and Unmount BitLocker Drive from Linux (Debian/Ubuntu Based)
# Disable Fast Startup in Windows (CRITICAL for read-write access & NTFS health)
# In Windows: Control Panel -> Power Options -> "Choose what the power buttons do"
# -> "Change settings that are currently unavailable" -> (UN)check "Turn on fast startup".
#
# If not doing that (i.e., if Fast Startup is LEFT ON), please restart Windows (not just shutdown),
# because in contrary, Windows shutdown with Fast Startup ON is not "clean"
# and possibly dangerous to use partition in this state on another machine.
# TL;DR: if Fast Startup is ON, then before accessing drives in Linux for read-write, use Restart from Windows.
# Restarting from Windows to Linux is a safe alternative to get a clean filesystem state.
# If Fast Startup is OFF, then a normal "Shut down" is also fine.
sudo apt update
sudo apt install gnupg ntfs-3g dislocker
##########
# For some reason, the apt repository hasn't been updated to the latest changes on Windows 11 24H2 about BitLocker
# (not being able to decrypt the OSV / Windows partition):
# "Error looking for the VIRTUALIZATION datum type 15 (VIRTUALIZATION INFO)"
# https://github.com/Aorimn/dislocker/issues/334
# If this happens, use this instead:
sudo apt remove dislocker
sudo apt install git gcc cmake make libfuse-dev libmbedtls-dev ruby-dev
cd ~
mkdir -p Labs && cd Labs
git clone https://github.com/Aorimn/dislocker.git && cd dislocker
mkdir build && cd build
cmake ..
make
sudo make install
which dislocker-fuse
dislocker-fuse -h
##########
nano ~/mount_rw.sh
# Edit the # Drive definitions section, accordingly
## use `sudo blkid`
## and `lsblk -o NAME,FSTYPE,LABEL,PARTLABEL,PARTUUID,MOUNTPOINT`
## to determine the device and partition path in Linux
## "LABEL|DEVICE_PATH_PARTUUID|RAW_MOUNT_POINT|FINAL_MOUNT_POINT"
## "c_system|/dev/disk/by-partuuid/d5045a6b-061d-4a4c-83eb-f6f364f8b09f|/mnt/raw_c_system|/mnt/c_system"
## "d_labs|/dev/disk/by-partuuid/000c55b3-b3f0-d1b9-05bd-de2566ab1800|/mnt/raw_d_labs|/mnt/d_labs"
## ... so on
# Choose as Read-Only or Read/Write,
# on # Mount Options Configuration, the default set to Read-Only.
# MOUNT_MODE="rw_ntfs3"
# the option is: "rw_ntfs3" (modern), "rw_ntfs-3g", or "ro" (Read-Only)
# Save
chmod +x ~/mount.sh
nano ~/unmount.sh
# Edit the # Configuration section
# Should match `mount.sh` for its labels and mount points
# Save
chmod +x ~/unmount.sh
nano ~/.config/bitlocker_keys.conf
# Put your BitLocker keys for each partition here, each line for each partition
## For example:
## c_system:111111-111111-111111-111111-111111-111111-111111-111111
## d_labs:222222-222222-222222-222222-222222-222222-222222-222222
## ... so on
# Encrypt your `bitlocker_keys.conf`
gpg -c ~/.config/bitlocker_keys.conf
# Enter STRONG passphrase, this is your master key now.
# Remember it (and backup the .gpg file & passphrase securely)!
# Check the decryption of your key is valid or not:
gpg-connect-agent reloadagent /bye
gpg -d ~/.config/bitlocker_keys.conf.gpg
# Enter your passphrase
# Remove securely the plain-text (not encrypted) `bitlocker_keys.conf`:
shred -u ~/.config/bitlocker_keys.conf
# Main usage: Decrypt the BitLocker partition (using dislocker-fuse) and then mount the decrypted view
sudo ./mount_rw.sh d_labs
# or
sudo ./mount_rw.sh all
# It will be mounted on /mnt/(decrypted-bitlocker-partition)
# Ending session: Unmount the decrypted partition and raw BitLocker partition
sudo ./unmount.sh d_labs
# or
sudo ./unmount.sh all
# It will be not visible again on /mnt/ (but the mounting folder exist, ignore this)
#!/bin/bash
# Script to unlock and mount BitLocker drives using dislocker and GPG-encrypted keys
# Define colors
NC='\033[0m' # No Color
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
# --- Determine Real User's Home Directory (for GPG key file) ---
INVOKING_USER_HOME="$HOME" # Default
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
CANDIDATE_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
if [ -d "$CANDIDATE_HOME" ]; then
INVOKING_USER_HOME="$CANDIDATE_HOME"
fi
fi
# --- Configuration ---
ENCRYPTED_KEY_FILE="$INVOKING_USER_HOME/.config/bitlocker_keys.conf.gpg" # Path to GPG encrypted key file
# Drive definitions: "LABEL|DEVICE_PATH_PARTUUID|RAW_MOUNT_POINT|FINAL_MOUNT_POINT"
# RAW_MOUNT_POINT is where dislocker-fuse initially mounts the decrypted volume.
# FINAL_MOUNT_POINT is where dislocker-file is loop-mounted for user access.
# Example: "label|/dev/disk/by-partuuid/xxxx-xxxx-xxxx|/mnt/raw_label|/mnt/label"
DRIVES=(
"c_system|/dev/disk/by-partuuid/d5045a6b-061d-4a4c-83eb-f6f364f8b09f|/mnt/raw_c_system|/mnt/c_system"
"d_labs|/dev/disk/by-partuuid/000c55b3-b3f0-d1b9-05bd-de2566ab1800|/mnt/raw_d_labs|/mnt/d_labs"
"e_labsgames|/dev/disk/by-partuuid/c449a822-a590-42af-9cbc-62225a7f6493|/mnt/raw_e_labsgames|/mnt/e_labsgames"
"f_games|/dev/disk/by-partuuid/0000004f-2fa0-3dc3-0a8d-da019e000000|/mnt/raw_f_games|/mnt/f_games"
"g_data|/dev/disk/by-partuuid/25e6445d-bb03-4bf0-8956-e53d090ec97f|/mnt/raw_g_data|/mnt/g_data"
"h_dataactive|/dev/disk/by-partuuid/15162efb-06c6-4c94-9e55-04bb30d11c80|/mnt/raw_h_dataactive|/mnt/h_dataactive"
"i_dataarchive|/dev/disk/by-partuuid/ce3bddb4-ea10-44a3-a58d-b858344ad219|/mnt/raw_i_dataarchive|/mnt/i_dataarchive"
)
# Determine target UID/GID for mount options
TARGET_UID="${SUDO_UID:-$(id -u)}"
TARGET_GID="${SUDO_GID:-$(id -g)}"
# --- Mount Options Configuration ---
MOUNT_MODE="rw_ntfs3" # Options: "rw_ntfs3", "rw_ntfs-3g", or "ro"
BASE_MOUNT_OPTS="" # Base options for the final mount of dislocker-file
if [ "$MOUNT_MODE" == "rw_ntfs3" ]; then
MOUNT_TYPE="ntfs3"
# 'loop' is implicit when mounting a file, but can be stated.
# Dislocker-fuse specific options are handled by dislocker itself.
BASE_MOUNT_OPTS="rw,uid=$TARGET_UID,gid=$TARGET_GID,fmask=0022,dmask=0022,iocharset=utf8"
elif [ "$MOUNT_MODE" == "rw_ntfs-3g" ]; then
MOUNT_TYPE="ntfs-3g"
BASE_MOUNT_OPTS="rw,uid=$TARGET_UID,gid=$TARGET_GID,fmask=0022,dmask=0022,allow_other,big_writes"
elif [ "$MOUNT_MODE" == "ro" ]; then
MOUNT_TYPE="" # Auto-detect
BASE_MOUNT_OPTS="ro,uid=$TARGET_UID,gid=$TARGET_GID,allow_other"
else
echo -e "${RED}ERROR: Invalid MOUNT_MODE set. Defaulting to Read-Only.${NC}"
MOUNT_TYPE=""
BASE_MOUNT_OPTS="ro,uid=$TARGET_UID,gid=$TARGET_GID,allow_other"
fi
# Note: The 'loop' option is typically added automatically by mount when mounting a regular file.
# It can be specified explicitly if needed: e.g., "loop,$BASE_MOUNT_OPTS"
# For clarity, we will add loop explicitly in the mount command later.
# --- End Mount Options Configuration ---
DECRYPTED_KEYS_CONTENT=""
# --- Helper Functions ---
decrypt_key_file() {
if [ ! -f "$ENCRYPTED_KEY_FILE" ]; then
echo -e "${RED}ERROR: Encrypted key file not found: $ENCRYPTED_KEY_FILE${NC}"
echo "Ensure the path is correct for the user who owns the key file (${SUDO_USER:-$(whoami)})."
return 1
fi
echo -e "${YELLOW}Attempting to decrypt key file: $ENCRYPTED_KEY_FILE${NC}"
local gpg_user_opts=""
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
gpg_user_opts="sudo -u $SUDO_USER"
fi
read -s -r -p "Enter GPG passphrase for $ENCRYPTED_KEY_FILE (for user ${SUDO_USER:-$(whoami)}): " gpg_passphrase
echo
# shellcheck disable=SC2086
DECRYPTED_KEYS_CONTENT=$($gpg_user_opts gpg --decrypt --quiet --batch --pinentry-mode loopback --passphrase-fd 0 "$ENCRYPTED_KEY_FILE" <<< "$gpg_passphrase" 2>/dev/null)
local passphrase_len=${#gpg_passphrase}
for (( i=0; i<passphrase_len; i++ )); do gpg_passphrase="${gpg_passphrase%?}#"; done
gpg_passphrase=""
if [ -z "$DECRYPTED_KEYS_CONTENT" ]; then
echo -e "${RED}ERROR: Failed to decrypt key file or file is empty. Check GPG passphrase or file permissions.${NC}"
echo "Attempted to read: $ENCRYPTED_KEY_FILE as user ${SUDO_USER:-$(whoami)}"
return 1
fi
echo -e "${GREEN}Key file decrypted successfully (in memory).${NC}"
return 0
}
get_key_for_label_from_decrypted() {
local label_to_find="$1"
if [ -n "$DECRYPTED_KEYS_CONTENT" ]; then
echo "$DECRYPTED_KEYS_CONTENT" | grep "^${label_to_find}:" | cut -d':' -f2-
fi
}
unlock_and_mount() {
local label="$1"
local device_path="$2"
local raw_mp="$3" # Intermediate mount point for dislocker-fuse
local final_mp="$4" # Final user-accessible mount point
local recovery_key=""
local dislocker_mounted_by_this_script=0
echo -e "${BLUE}-----------------------------------------------------${NC}"
echo -e "${BLUE}Processing mount for: ${label} (Device: ${device_path}, Raw MP: ${raw_mp}, Final MP: ${final_mp})${NC}"
if mountpoint -q "$final_mp"; then
echo -e "${YELLOW}$final_mp is already mounted. Skipping.${NC}"
return 0 # Success, already done
fi
# Ensure mount point directories exist
mkdir -p "$raw_mp" "$final_mp"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Could not create mount points ($raw_mp, $final_mp). (Permissions issue?)${NC}"
return 1
fi
# Stage 1: Mount with dislocker-fuse
if mountpoint -q "$raw_mp"; then
echo -e "${YELLOW}$raw_mp is already a mountpoint. Assuming dislocker layer is active. Proceeding to final mount.${NC}"
# Check if dislocker-file exists, if not, something is wrong with the existing raw_mp
if [ ! -e "$raw_mp/dislocker-file" ]; then
echo -e "${RED}ERROR: $raw_mp is mounted, but $raw_mp/dislocker-file does not exist! Trying to unmount $raw_mp...${NC}"
umount "$raw_mp" 2>/dev/null # Attempt to clean up
# Fall through to attempt dislocker mount again
fi
fi # Re-check mountpoint status after potential cleanup or if it wasn't mounted initially
if ! mountpoint -q "$raw_mp"; then # If raw_mp is not (or no longer) mounted
recovery_key=$(get_key_for_label_from_decrypted "$label")
if [ -z "$recovery_key" ]; then
echo -e "${RED}ERROR: Recovery key not found for $label in decrypted content or decryption failed.${NC}"
rmdir "$raw_mp" "$final_mp" 2>/dev/null || true
return 1
fi
echo "Attempting to unlock $device_path with dislocker-fuse to $raw_mp..."
# Using -p for recovery key. Ensure no space after -p.
# dislocker-fuse [options] <device> -- <mountpoint>
# Common options: -r for read-only dislocker layer
dislocker-fuse -V "$device_path" -p"$recovery_key" -- "$raw_mp"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: dislocker-fuse failed for $label ($device_path).${NC}"
rmdir "$raw_mp" "$final_mp" 2>/dev/null || true
return 1
fi
dislocker_mounted_by_this_script=1
echo -e "${GREEN}Dislocker layer for $label successfully mounted at $raw_mp.${NC}"
fi
# Check if dislocker-file exists after attempting dislocker-fuse mount
if [ ! -e "$raw_mp/dislocker-file" ]; then
echo -e "${RED}ERROR: $raw_mp/dislocker-file not found after dislocker-fuse operation! Cannot proceed.${NC}"
if [ "$dislocker_mounted_by_this_script" -eq 1 ]; then
echo -e "${YELLOW}Unmounting dislocker layer $raw_mp due to error...${NC}"
umount "$raw_mp"
fi
rmdir "$raw_mp" "$final_mp" 2>/dev/null || true
return 1
fi
# Stage 2: Mount the dislocker-file (loop mount)
echo "Mounting decrypted filesystem $raw_mp/dislocker-file to $final_mp..."
local current_mount_opts="loop,$BASE_MOUNT_OPTS" # Add loop explicitly for clarity
if [ -n "$MOUNT_TYPE" ]; then
mount -t "$MOUNT_TYPE" -o "$current_mount_opts" "$raw_mp/dislocker-file" "$final_mp"
else
mount -o "$current_mount_opts" "$raw_mp/dislocker-file" "$final_mp"
fi
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Failed to mount $raw_mp/dislocker-file to $final_mp.${NC}"
if [ "$dislocker_mounted_by_this_script" -eq 1 ]; then
echo -e "${YELLOW}Unmounting dislocker layer $raw_mp due to final mount failure...${NC}"
umount "$raw_mp"
fi
# Do not remove final_mp if dislocker was not mounted by this script,
# as the raw_mp might be in use by another process or was pre-existing.
# Only rmdir final_mp if it was created by us and dislocker was our doing.
if [ "$dislocker_mounted_by_this_script" -eq 1 ]; then # Or if we just created it
rmdir "$final_mp" 2>/dev/null || true
fi
# raw_mp is not removed here if dislocker-fuse failed but raw_mp directory was pre-existing.
# if dislocker_mounted_by_this_script and it failed, then raw_mp was also removed.
return 1
else
echo -e "${GREEN}$label ($device_path via $raw_mp/dislocker-file) successfully mounted to $final_mp.${NC}"
fi
echo -e "${BLUE}-----------------------------------------------------${NC}"
return 0 # Success
}
cleanup_keys() {
if [ -n "$DECRYPTED_KEYS_CONTENT" ]; then
local content_len=${#DECRYPTED_KEYS_CONTENT}
for (( i=0; i<content_len; i++ )); do DECRYPTED_KEYS_CONTENT="${DECRYPTED_KEYS_CONTENT%?}#"; done
DECRYPTED_KEYS_CONTENT=""
echo -e "${GREEN}Decrypted keys cleared from script memory.${NC}"
fi
}
trap cleanup_keys EXIT SIGINT SIGTERM
# --- Main Logic ---
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}This script must be run with sudo or as root.${NC}" >&2
exit 1
fi
if ! command -v dislocker-fuse &> /dev/null; then
echo -e "${RED}ERROR: dislocker-fuse command not found. Please install dislocker.${NC}" >&2
echo "(e.g., sudo apt install dislocker)"
exit 1
fi
if ! command -v gpg &> /dev/null; then
echo -e "${RED}ERROR: gpg command not found. Please install gnupg.${NC}" >&2
exit 1
fi
if [ "$#" -eq 0 ]; then # Check if no arguments are passed
echo "Usage: $(basename "$0") <all | drive_label1 [drive_label2 ... ]>"
echo "Provide one or more drive labels separated by spaces, or 'all'."
echo "If a drive label contains spaces, enclose it in quotes (e.g., \"my drive\")."
echo "Available labels:"
for drive_info in "${DRIVES[@]}"; do
# For dislocker, we have 4 fields: LABEL|DEVICE|RAW_MP|FINAL_MP
IFS='|' read -r label device raw_mp final_mp <<< "$drive_info"
echo " $label (device: $device, raw_mp: $raw_mp, final_mp: $final_mp)"
done
exit 1
fi
if ! decrypt_key_file; then
exit 1 # cleanup_keys will run via trap
fi
OVERALL_SCRIPT_EXIT_STATUS=0 # 0 for success, 1 for overall failure
if [ "$1" == "all" ] && [ "$#" -eq 1 ]; then
echo -e "${BLUE}Processing mount for all configured drives...${NC}"
any_failed=0
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label device raw_mp final_mp <<< "$drive_info"
if ! unlock_and_mount "$label" "$device" "$raw_mp" "$final_mp"; then
any_failed=1 # Track if any individual mount fails
fi
done
if [ "$any_failed" -eq 1 ]; then
OVERALL_SCRIPT_EXIT_STATUS=1
fi
else
ANY_DRIVE_PROCESSED_SUCCESSFULLY=0
ALL_INPUT_LABELS_VALID_AND_PROCESSED=1 # Assume true initially
INPUT_HAS_VALID_LABEL_NAMES=0
for TARGET_LABEL_RAW in "$@"; do
if [ "$TARGET_LABEL_RAW" == "all" ]; then
echo -e "${YELLOW}Warning: 'all' keyword should be used alone. Skipping 'all' in multi-label list.${NC}"
continue
fi
TARGET_LABEL=$(echo "$TARGET_LABEL_RAW" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -z "$TARGET_LABEL" ]; then
continue
fi
INPUT_HAS_VALID_LABEL_NAMES=1
echo -e "${BLUE}Attempting to process mount for label: ${TARGET_LABEL}${NC}"
FOUND_IN_CONFIG=0
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label_cfg device_cfg raw_mp_cfg final_mp_cfg <<< "$drive_info"
if [ "$label_cfg" == "$TARGET_LABEL" ]; then
FOUND_IN_CONFIG=1
if unlock_and_mount "$label_cfg" "$device_cfg" "$raw_mp_cfg" "$final_mp_cfg"; then
ANY_DRIVE_PROCESSED_SUCCESSFULLY=1
else
ALL_INPUT_LABELS_VALID_AND_PROCESSED=0
OVERALL_SCRIPT_EXIT_STATUS=1 # Mark overall failure if any drive fails
fi
break
fi
done
if [ "$FOUND_IN_CONFIG" -eq 0 ]; then
echo -e "${RED}Error: Drive label '$TARGET_LABEL' not found in configuration.${NC}"
ALL_INPUT_LABELS_VALID_AND_PROCESSED=0
OVERALL_SCRIPT_EXIT_STATUS=1 # Mark overall failure
fi
done
if [ "$INPUT_HAS_VALID_LABEL_NAMES" -eq 0 ]; then
if ! ( [ "$1" == "all" ] && [ "$#" -eq 1 ] ); then
echo -e "${RED}Error: No valid drive labels provided or 'all' was misused.${NC}"
OVERALL_SCRIPT_EXIT_STATUS=1
fi
elif [ "$ALL_INPUT_LABELS_VALID_AND_PROCESSED" -eq 0 ]; then
# Message handled by individual errors and final status
echo -e "${YELLOW}Warning: Not all specified drives were mounted successfully or some labels were invalid.${NC}"
# OVERALL_SCRIPT_EXIT_STATUS is already set if there were issues
if [ "$ANY_DRIVE_PROCESSED_SUCCESSFULLY" -eq 0 ]; then
echo -e "${RED}No drives were successfully mounted.${NC}"
# OVERALL_SCRIPT_EXIT_STATUS is already set
fi
elif [ "$ANY_DRIVE_PROCESSED_SUCCESSFULLY" -eq 0 ] && [ "$INPUT_HAS_VALID_LABEL_NAMES" -eq 1 ]; then
echo -e "${RED}No drives were successfully mounted despite valid labels being provided.${NC}"
OVERALL_SCRIPT_EXIT_STATUS=1
fi
fi
if [ "$OVERALL_SCRIPT_EXIT_STATUS" -eq 0 ]; then
echo -e "${GREEN}Mounting process complete.${NC}"
else
echo -e "${RED}Mounting process completed with errors.${NC}"
fi
# cleanup_keys will run via trap on exit
exit $OVERALL_SCRIPT_EXIT_STATUS
#!/bin/bash
# Script to unmount BitLocker drives (mounted via dislocker)
# Define colors
NC='\033[0m' # No Color
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
# --- Configuration (should match mount_rw.sh for labels and mount points) ---
# Drive definitions: "LABEL|DEVICE_PATH_PARTUUID|RAW_MOUNT_POINT|FINAL_MOUNT_POINT"
# RAW_MOUNT_POINT is where dislocker-fuse initially mounts the decrypted volume.
# FINAL_MOUNT_POINT is where dislocker-file is loop-mounted for user access.
# Example: "label|/dev/disk/by-partuuid/xxxx-xxxx-xxxx|/mnt/raw_label|/mnt/label"
# /dev/disk/by-partuuid/xxxx-xxxx-xxxx is not used for unmount though, it's just for consitency of column
DRIVES=(
"c_system|/dev/disk/by-partuuid/d5045a6b-061d-4a4c-83eb-f6f364f8b09f|/mnt/raw_c_system|/mnt/c_system"
"d_labs|/dev/disk/by-partuuid/000c55b3-b3f0-d1b9-05bd-de2566ab1800|/mnt/raw_d_labs|/mnt/d_labs"
"e_labsgames|/dev/disk/by-partuuid/c449a822-a590-42af-9cbc-62225a7f6493|/mnt/raw_e_labsgames|/mnt/e_labsgames"
"f_games|/dev/disk/by-partuuid/0000004f-2fa0-3dc3-0a8d-da019e000000|/mnt/raw_f_games|/mnt/f_games"
"g_data|/dev/disk/by-partuuid/25e6445d-bb03-4bf0-8956-e53d090ec97f|/mnt/raw_g_data|/mnt/g_data"
"h_dataactive|/dev/disk/by-partuuid/15162efb-06c6-4c94-9e55-04bb30d11c80|/mnt/raw_h_dataactive|/mnt/h_dataactive"
"i_dataarchive|/dev/disk/by-partuuid/ce3bddb4-ea10-44a3-a58d-b858344ad219|/mnt/raw_i_dataarchive|/mnt/i_dataarchive"
)
# --- Helper Function ---
unmount_dislocker_drive() {
local label="$1"
# local device_path_unused="$2" # Not used for unmount, it's just for consitency of column
local raw_mp="$3" # Intermediate dislocker-fuse mount point
local final_mp="$4" # Final user-accessible mount point
local operation_status=0 # Shell convention: 0 for success, 1 for failure
echo -e "${BLUE}-----------------------------------------------------${NC}"
echo -e "${BLUE}Processing unmount for: ${label} (Final MP: ${final_mp}, Raw MP: ${raw_mp})${NC}"
local final_mp_was_mounted_by_us_to_check=0 # Track if we actually saw it mounted
# Stage 1: Unmount the final user-accessible mount point (loop mount of dislocker-file)
if mountpoint -q "$final_mp"; then
final_mp_was_mounted_by_us_to_check=1
echo "Unmounting final mount point $final_mp..."
umount "$final_mp"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Failed to unmount $final_mp. It might be busy.${NC}"
operation_status=1
# Do not proceed to unmount raw_mp if final_mp is busy, as dislocker-file might be in use
echo -e "${BLUE}-----------------------------------------------------${NC}"
return $operation_status
else
echo -e "${GREEN}$final_mp unmounted successfully.${NC}"
# Optional: remove the final_mp directory if it's empty and we created it
# rmdir "$final_mp" 2>/dev/null || true
fi
else
echo -e "${YELLOW}$final_mp is not mounted.${NC}"
fi
# Stage 2: Unmount the dislocker-fuse raw mount point
if mountpoint -q "$raw_mp"; then
echo "Unmounting dislocker layer $raw_mp..."
umount "$raw_mp"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Failed to unmount dislocker layer $raw_mp. It might be busy.${NC}"
echo -e "${RED}If $final_mp was unmounted, $raw_mp should not be busy unless accessed directly.${NC}"
operation_status=1
else
echo -e "${GREEN}Dislocker layer $raw_mp unmounted successfully.${NC}"
# Optional: remove the raw_mp directory if it's empty and we created it
# rmdir "$raw_mp" 2>/dev/null || true
fi
else
echo -e "${YELLOW}Dislocker layer $raw_mp is not mounted.${NC}"
# If the final mount point was not mounted AND raw_mp is not mounted,
# but we expected the final_mp to be, it might be an inconsistent state or already clean.
if [ "$final_mp_was_mounted_by_us_to_check" -eq 0 ]; then
echo -e "${YELLOW}Both $final_mp and $raw_mp appear to be unmounted already.${NC}"
fi
fi
echo -e "${BLUE}-----------------------------------------------------${NC}"
return $operation_status
}
# --- Main Logic ---
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}This script must be run with sudo or as root.${NC}" >&2
exit 1
fi
if ! command -v dislocker-fuse &> /dev/null; then
echo -e "${RED}ERROR: dislocker-fuse command not found. Please install dislocker.${NC}" >&2
echo "(e.g., sudo apt install dislocker)"
exit 1
fi
if [ "$#" -eq 0 ]; then
echo "Usage: $(basename "$0") <all | drive_label1 [drive_label2 ... ]>"
echo "Provide one or more drive labels separated by spaces, or 'all'."
echo "If a drive label contains spaces, enclose it in quotes (e.g., \"my drive\")."
echo "Available labels:"
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label _ raw_mp final_mp <<< "$drive_info" # Device path not needed for listing
echo " $label (raw_mp: $raw_mp, final_mp: $final_mp)"
done
exit 1
fi
OVERALL_SCRIPT_EXIT_STATUS=0 # 0 for success, 1 for overall failure
if [ "$1" == "all" ] && [ "$#" -eq 1 ]; then
echo -e "${BLUE}Processing unmount for all configured drives...${NC}"
any_failed=0
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label device_path raw_mp final_mp <<< "$drive_info" # device_path read but unused
if ! unmount_dislocker_drive "$label" "$device_path" "$raw_mp" "$final_mp"; then
any_failed=1
fi
done
if [ "$any_failed" -eq 1 ]; then
OVERALL_SCRIPT_EXIT_STATUS=1
fi
else
ANY_DRIVE_PROCESSED_SUCCESSFULLY=0 # More like ANY_DRIVE_UNMOUNTED_CLEANLY
ALL_INPUT_LABELS_VALID_AND_PROCESSED=1
INPUT_HAS_VALID_LABEL_NAMES=0
for TARGET_LABEL_RAW in "$@"; do
if [ "$TARGET_LABEL_RAW" == "all" ]; then
echo -e "${YELLOW}Warning: 'all' keyword should be used alone. Skipping 'all' in multi-label list.${NC}"
continue
fi
TARGET_LABEL=$(echo "$TARGET_LABEL_RAW" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
if [ -z "$TARGET_LABEL" ]; then
continue
fi
INPUT_HAS_VALID_LABEL_NAMES=1
echo -e "${BLUE}Attempting to process unmount for label: ${TARGET_LABEL}${NC}"
FOUND_IN_CONFIG=0
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label_cfg device_path_cfg raw_mp_cfg final_mp_cfg <<< "$drive_info"
if [ "$label_cfg" == "$TARGET_LABEL" ]; then
FOUND_IN_CONFIG=1
if unmount_dislocker_drive "$label_cfg" "$device_path_cfg" "$raw_mp_cfg" "$final_mp_cfg"; then
ANY_DRIVE_PROCESSED_SUCCESSFULLY=1 # This flag may need rethinking for unmount success
else
ALL_INPUT_LABELS_VALID_AND_PROCESSED=0
OVERALL_SCRIPT_EXIT_STATUS=1
fi
break
fi
done
if [ "$FOUND_IN_CONFIG" -eq 0 ]; then
echo -e "${RED}Error: Drive label '$TARGET_LABEL' not found in configuration.${NC}"
ALL_INPUT_LABELS_VALID_AND_PROCESSED=0
OVERALL_SCRIPT_EXIT_STATUS=1
fi
done
if [ "$INPUT_HAS_VALID_LABEL_NAMES" -eq 0 ]; then
if ! ( [ "$1" == "all" ] && [ "$#" -eq 1 ] ); then
echo -e "${RED}Error: No valid drive labels provided or 'all' was misused.${NC}"
OVERALL_SCRIPT_EXIT_STATUS=1
fi
elif [ "$ALL_INPUT_LABELS_VALID_AND_PROCESSED" -eq 0 ]; then
echo -e "${YELLOW}Warning: Not all specified drives were unmounted successfully or some labels were invalid.${NC}"
# OVERALL_SCRIPT_EXIT_STATUS is already set if there were issues
# For unmount, "ANY_DRIVE_PROCESSED_SUCCESSFULLY" might mean "at least one unmount call returned success"
# which is different from mount. If all failed, it's a clear failure.
elif [ "$INPUT_HAS_VALID_LABEL_NAMES" -eq 1 ] && [ "$OVERALL_SCRIPT_EXIT_STATUS" -ne 0 ] && [ "$ANY_DRIVE_PROCESSED_SUCCESSFULLY" -eq 0 ]; then
# This means valid labels were provided, but none of them resulted in a successful unmount operation sequence
# (though some might have already been unmounted). This is tricky to message perfectly.
# The OVERALL_SCRIPT_EXIT_STATUS reflects if any unmount operation *failed*.
: # The "Warning" message above and the final error message cover this.
fi
fi
if [ "$OVERALL_SCRIPT_EXIT_STATUS" -eq 0 ]; then
echo -e "${GREEN}Unmounting process complete.${NC}"
else
echo -e "${RED}Unmounting process completed with errors.${NC}"
fi
exit $OVERALL_SCRIPT_EXIT_STATUS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment