Last active
May 12, 2025 08:29
-
-
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.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 | |
## "CUSTOMLABEL|DEVICE_PATH|RAW_MOUNT_POINT|FINAL_MOUNT_POINT" | |
## "c_system|/dev/nvme0n1p3|/mnt/raw-c_system|/mnt/c_system" | |
## "d_labs|/dev/nvme0n1p7|/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="ro" | |
# 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.sh d_labs | |
# or | |
sudo ./mount.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 GPG-encrypted keys | |
# --- Determine Real User's Home Directory --- | |
if [ -n "$SUDO_USER" ]; then | |
# Ensure SUDO_USER is not root itself if script is run as "sudo su - root" then "./script" | |
# In that case, SUDO_USER might not be set, or might be the previous user. | |
# A more robust way for "sudo ./script" | |
if [ "$(id -u)" -eq 0 ] && [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then | |
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6) | |
else | |
# If run as root directly, or SUDO_USER is root, default to root's home for key file | |
# For this use case, if truly run as root, maybe /root/.config is intended. | |
REAL_HOME="$HOME" # This will be /root if script is "sudo ./script" and SUDO_USER wasn't set right | |
fi | |
else | |
REAL_HOME="$HOME" # Running as normal user | |
fi | |
# More direct check for "sudo ./script.sh" scenario: | |
INVOKING_USER_HOME="$HOME" # Home of the user whose sudo session it is, likely /root | |
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then | |
CANDIDATE_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6) | |
if [ -d "$CANDIDATE_HOME" ]; then # Check if the home directory actually exists | |
INVOKING_USER_HOME="$CANDIDATE_HOME" | |
fi | |
fi | |
# Use INVOKING_USER_HOME as it's more likely what's intended for user-specific config | |
# when script is run with "sudo ./script.sh" | |
# --- Configuration --- | |
ENCRYPTED_KEY_FILE="$INVOKING_USER_HOME/.config/bitlocker_keys.conf.gpg" # Path to your GPG encrypted key file | |
# Drive definitions: "LABEL|DEVICE_PATH|RAW_MOUNT_POINT|FINAL_MOUNT_POINT" | |
DRIVES=( | |
"c_system|/dev/nvme0n1p3|/mnt/raw-c_system|/mnt/c_system" | |
"d_labs|/dev/nvme0n1p7|/mnt/raw-d_labs|/mnt/d_labs" | |
"e_labsgames|/dev/nvme1n1p2|/mnt/raw-e_labsgames|/mnt/e_labsgames" | |
"f_games|/dev/sdb1|/mnt/raw-f_games|/mnt/f_games" | |
"g_data|/dev/sdc1|/mnt/raw-g_data|/mnt/g_data" | |
"h_dataactive|/dev/sda1|/mnt/raw-h_dataactive|/mnt/h_dataactive" | |
"i_dataarchive|/dev/sda2|/mnt/raw-i_dataarchive|/mnt/i_dataarchive" | |
) | |
# Determine target UID/GID for mount options | |
TARGET_UID=$(id -u) # Default to current user (root if script is sudo'd) | |
TARGET_GID=$(id -g) | |
if [ -n "$SUDO_UID" ]; then # If SUDO_UID is set (script run with sudo by a normal user) | |
TARGET_UID="$SUDO_UID" | |
fi | |
if [ -n "$SUDO_GID" ]; then # If SUDO_GID is set | |
TARGET_GID="$SUDO_GID" | |
fi | |
# --- Mount Options Configuration --- | |
# Choose your desired mount mode, | |
# the option is: "rw_ntfs3", "rw_ntfs-3g", or "ro" (Read-Only) | |
MOUNT_MODE="ro" | |
if [ "$MOUNT_MODE" == "rw_ntfs3" ]; then | |
MOUNT_TYPE="ntfs3" | |
MOUNT_OPTS="loop,rw,uid=$TARGET_UID,gid=$TARGET_GID,fmask=0022,dmask=0022,iocharset=utf8" | |
elif [ "$MOUNT_MODE" == "rw_ntfs-3g" ]; then | |
MOUNT_TYPE="ntfs-3g" | |
MOUNT_OPTS="loop,rw,uid=$TARGET_UID,gid=$TARGET_GID,fmask=0022,dmask=0022,allow_other,big_writes" | |
elif [ "$MOUNT_MODE" == "ro" ]; then | |
MOUNT_TYPE="" # Auto-detect, likely uses ntfs3 if available for loop ro | |
MOUNT_OPTS="ro,loop,uid=$TARGET_UID,gid=$TARGET_GID,allow_other" | |
else | |
echo "ERROR: Invalid MOUNT_MODE set. Defaulting to Read-Only." | |
MOUNT_TYPE="" | |
MOUNT_OPTS="ro,loop,uid=$TARGET_UID,gid=$TARGET_GID,allow_other" | |
fi | |
# --- End Mount Options Configuration --- | |
# Temporary storage for decrypted keys | |
DECRYPTED_KEYS_CONTENT="" | |
# --- Helper Functions --- | |
decrypt_key_file() { | |
if [ ! -f "$ENCRYPTED_KEY_FILE" ]; then | |
echo "ERROR: Encrypted key file not found: $ENCRYPTED_KEY_FILE" | |
echo "If you ran with sudo, \$HOME might be /root." | |
echo "Ensure the path is correct for the user who owns the key file." | |
return 1 | |
fi | |
echo "Attempting to decrypt key file: $ENCRYPTED_KEY_FILE" | |
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 | |
DECRYPTED_KEYS_CONTENT=$($gpg_user_opts gpg --decrypt --quiet --batch --pinentry-mode loopback --passphrase-fd 0 "$ENCRYPTED_KEY_FILE" <<< "$gpg_passphrase" 2>/dev/null) | |
for i in $(seq 1 ${#gpg_passphrase}); do gpg_passphrase="${gpg_passphrase%?}#"; done | |
gpg_passphrase="" | |
if [ -z "$DECRYPTED_KEYS_CONTENT" ]; then | |
echo "ERROR: Failed to decrypt key file or file is empty. Check GPG passphrase or file permissions." | |
echo "Attempted to read: $ENCRYPTED_KEY_FILE" | |
return 1 | |
fi | |
echo "Key file decrypted successfully (in memory)." | |
return 0 | |
} | |
get_key_for_label_from_decrypted() { | |
local label="$1" | |
if [ -n "$DECRYPTED_KEYS_CONTENT" ]; then | |
echo "$DECRYPTED_KEYS_CONTENT" | grep "^${label}:" | cut -d':' -f2- | |
fi | |
} | |
unlock_and_mount() { | |
local label="$1" | |
local device_path="$2" | |
local raw_mp="$3" | |
local final_mp="$4" | |
local recovery_key="" | |
echo "-----------------------------------------------------" | |
echo "Processing: $label ($device_path)" | |
if mountpoint -q "$final_mp"; then | |
echo "$final_mp is already mounted. Skipping." | |
return 0 | |
fi | |
mkdir -p "$raw_mp" "$final_mp" | |
if [ $? -ne 0 ]; then | |
echo "ERROR: Could not create mount points for $label. (run with sudo?)" | |
return 1 | |
fi | |
if mountpoint -q "$raw_mp"; then | |
echo "$raw_mp is already a mountpoint. Attempting to use it." | |
else | |
recovery_key=$(get_key_for_label_from_decrypted "$label") | |
if [ -n "$recovery_key" ]; then | |
echo "Using decrypted key for $label." | |
dislocker-fuse -v -V "$device_path" -p"$recovery_key" -- "$raw_mp" | |
else | |
echo "Key for $label not found in decrypted content or decryption failed." | |
echo "This script expects the key file '$ENCRYPTED_KEY_FILE' to be GPG encrypted and accessible." | |
return 1 | |
fi | |
if [ $? -ne 0 ]; then | |
echo "ERROR: dislocker-fuse failed for $label ($device_path)." | |
rmdir "$raw_mp" "$final_mp" 2>/dev/null | |
return 1 | |
fi | |
echo "Dislocker layer mounted at $raw_mp" | |
fi | |
echo "Mounting decrypted filesystem to $final_mp..." | |
if [ -n "$MOUNT_TYPE" ]; then | |
mount -t "$MOUNT_TYPE" -o "$MOUNT_OPTS" "$raw_mp/dislocker-file" "$final_mp" | |
else | |
mount -o "$MOUNT_OPTS" "$raw_mp/dislocker-file" "$final_mp" | |
fi | |
if [ $? -ne 0 ]; then | |
echo "ERROR: Failed to mount $raw_mp/dislocker-file to $final_mp." | |
echo "Unmounting dislocker layer at $raw_mp..." | |
umount "$raw_mp" | |
return 1 | |
else | |
echo "$label ($device_path) successfully unlocked and mounted to $final_mp." | |
fi | |
echo "-----------------------------------------------------" | |
} | |
# --- Main Logic --- | |
if [ "$(id -u)" -ne 0 ]; then | |
echo "This script must be run with sudo or as root." >&2 | |
exit 1 | |
fi | |
if [ -z "$1" ]; then | |
echo "Usage: $(basename "$0") <drive_label | all>" | |
echo "Available labels:" | |
for drive_info in "${DRIVES[@]}"; do | |
IFS='|' read -r label _ _ _ <<< "$drive_info" | |
echo " $label" | |
done | |
exit 1 | |
fi | |
if ! decrypt_key_file; then | |
DECRYPTED_KEYS_CONTENT="" | |
exit 1 | |
fi | |
if [ "$1" == "all" ]; then | |
for drive_info in "${DRIVES[@]}"; do | |
IFS='|' read -r label device raw final <<< "$drive_info" | |
unlock_and_mount "$label" "$device" "$raw" "$final" | |
done | |
else | |
TARGET_LABEL="$1" | |
FOUND=0 | |
for drive_info in "${DRIVES[@]}"; do | |
IFS='|' read -r label device raw final <<< "$drive_info" | |
if [ "$label" == "$TARGET_LABEL" ]; then | |
unlock_and_mount "$label" "$device" "$raw" "$final" | |
FOUND=1 | |
break | |
fi | |
done | |
if [ "$FOUND" -eq 0 ]; then | |
echo "Error: Drive label '$TARGET_LABEL' not found in configuration." | |
DECRYPTED_KEYS_CONTENT="" | |
exit 1 | |
fi | |
fi | |
DECRYPTED_KEYS_CONTENT="" | |
for i in $(seq 1 ${#DECRYPTED_KEYS_CONTENT}); do DECRYPTED_KEYS_CONTENT="${DECRYPTED_KEYS_CONTENT%?}#"; done | |
DECRYPTED_KEYS_CONTENT="" | |
echo "Mounting process complete. Decrypted keys cleared from script memory." |
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 with colored output | |
# Define colors | |
NC='\033[0m' # No Color | |
GREEN='\033[0;32m' | |
YELLOW='\033[0;33m' # For "not mounted" or warnings | |
RED='\033[0;31m' | |
BLUE='\033[0;34m' # For informational messages | |
# --- Configuration (should match unlock_bitlocker.sh for labels and mount points) --- | |
DRIVES=( | |
"c_system|/dev/nvme0n1p3|/mnt/raw-c_system|/mnt/c_system" | |
"d_labs|/dev/nvme0n1p7|/mnt/raw-d_labs|/mnt/d_labs" | |
"e_labsgames|/dev/nvme1n1p2|/mnt/raw-e_labsgames|/mnt/e_labsgames" | |
"f_games|/dev/sdb1|/mnt/raw-f_games|/mnt/f_games" | |
"g_data|/dev/sdc1|/mnt/raw-g_data|/mnt/g_data" | |
"h_dataactive|/dev/sda1|/mnt/raw-h_dataactive|/mnt/h_dataactive" | |
"i_dataarchive|/dev/sda2|/mnt/raw-i_dataarchive|/mnt/i_dataarchive" | |
) | |
# --- Helper Function --- | |
unmount_and_lock() { | |
local label="$1" | |
# local device_path="$2" # Not strictly needed for unmount | |
local raw_mp="$3" | |
local final_mp="$4" | |
local final_mp_unmounted=0 | |
local raw_mp_unmounted=0 | |
local error_occurred=0 | |
echo -e "${BLUE}-----------------------------------------------------${NC}" | |
echo -e "${BLUE}Unmounting: ${label}${NC}" | |
# Unmount the final user-accessible mount point | |
if mountpoint -q "$final_mp"; then | |
echo "Unmounting $final_mp..." | |
sudo umount "$final_mp" | |
if [ $? -ne 0 ]; then | |
echo -e "${RED}ERROR: Failed to unmount $final_mp. It might be busy.${NC}" | |
error_occurred=1 | |
else | |
echo -e "${GREEN}$final_mp unmounted.${NC}" | |
final_mp_unmounted=1 | |
fi | |
else | |
echo -e "${YELLOW}$final_mp is not mounted.${NC}" | |
fi | |
# Unmount the dislocker raw FUSE mount point (this stops dislocker-fuse daemon) | |
if mountpoint -q "$raw_mp"; then | |
echo "Unmounting dislocker layer $raw_mp..." | |
sudo umount "$raw_mp" | |
if [ $? -ne 0 ]; then | |
echo -e "${RED}ERROR: Failed to unmount dislocker layer $raw_mp.${NC}" | |
error_occurred=1 | |
else | |
echo -e "${GREEN}Dislocker layer $raw_mp unmounted.${NC}" | |
raw_mp_unmounted=1 | |
fi | |
else | |
echo -e "${YELLOW}Dislocker layer $raw_mp is not mounted.${NC}" | |
fi | |
if [ "$error_occurred" -eq 1 ]; then | |
echo -e "${RED}${label} processed for unmounting with errors.${NC}" | |
elif [ "$final_mp_unmounted" -eq 1 ] || [ "$raw_mp_unmounted" -eq 1 ]; then | |
echo -e "${GREEN}${label} successfully processed for unmounting.${NC}" | |
else | |
echo -e "${YELLOW}${label} was not mounted, no action taken.${NC}" | |
fi | |
echo -e "${BLUE}-----------------------------------------------------${NC}" | |
} | |
# --- 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 [ -z "$1" ]; then | |
echo "Usage: $(basename "$0") <drive_label | all>" | |
echo "Available labels:" | |
for drive_info in "${DRIVES[@]}"; do | |
IFS='|' read -r label _ _ _ <<< "$drive_info" | |
echo " $label" | |
done | |
exit 1 | |
fi | |
if [ "$1" == "all" ]; then | |
for drive_info in "${DRIVES[@]}"; do | |
IFS='|' read -r label device raw final <<< "$drive_info" | |
unmount_and_lock "$label" "$device" "$raw" "$final" | |
done | |
else | |
TARGET_LABEL="$1" | |
FOUND=0 | |
for drive_info in "${DRIVES[@]}"; do | |
IFS='|' read -r label device raw final <<< "$drive_info" | |
if [ "$label" == "$TARGET_LABEL" ]; then | |
unmount_and_lock "$label" "$device" "$raw" "$final" | |
FOUND=1 | |
break | |
fi | |
done | |
if [ "$FOUND" -eq 0 ]; then | |
echo -e "${RED}Error: Drive label '$TARGET_LABEL' not found in configuration.${NC}" | |
exit 1 | |
fi | |
fi | |
echo -e "${GREEN}Unmounting process complete.${NC}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment