Last active
June 1, 2025 06:04
-
-
Save marhensa/5296c317b629d7778468c9d988c779df to your computer and use it in GitHub Desktop.
Mount and Unmount BitLocker Drive from Linux (Debian/Ubuntu Based)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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