Last active
June 8, 2025 19:07
-
-
Save afreisinger/ab82936d5434b83503562ed66534373e to your computer and use it in GitHub Desktop.
This script automates the setup of a Btrfs-based filesystem layout on Ubuntu. It mounts the root Btrfs partition, creates a predefined set of subvolumes (including user-specific ones like ~/bin and ~/dev), ensures mount points exist, and sets appropriate ownership for user directories. It's designed to be used after a fresh installation, restori…
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 | |
# set -x | |
# This script automates the setup of a Btrfs-based filesystem layout on Ubuntu. | |
# It mounts the root Btrfs partition, creates a predefined set of subvolumes | |
# (including user-specific ones like ~/bin and ~/dev), ensures mount points | |
# exist, and sets appropriate ownership for user directories. | |
# | |
# It's designed to be used after a fresh installation. It restores filesystem | |
# info from /etc/fstab.bak and prepares the system for a clean and reproducible | |
# /etc/fstab regeneration. | |
# | |
# IMPORTANT: | |
# If you plan to mount SMB/CIFS volumes, make sure to install the 'cifs-utils' package. | |
# On Debian/Ubuntu systems, you can install it with: | |
# sudo apt-get install cifs-utils | |
# | |
# generated by afreisinger/gen-fstab-plus.sh https://gist.github.com/afreisinger/ab82936d5434b83503562ed66534373e | |
# OPTIONAL: enable or disable bind mounts from /data | |
ENABLE_BIND_MOUNTS=true # Set to false to skip bind mounts | |
# OPTIONAL: enable CIFS mount (e.g. for backups) | |
ENABLE_CIFS_MOUNT=true | |
CIFS_SOURCE="//192.168.1.20/bups" | |
CIFS_TARGET="/mnt/bups" | |
CIFS_CREDENTIALS="/etc/cifs_credentials" | |
CIFS_UID=1000 | |
CIFS_GID=1000 | |
CIFS_OPTIONS="credentials=$CIFS_CREDENTIALS,rw,noperm,iocharset=utf8,uid=$CIFS_UID,gid=$CIFS_GID,vers=3.0" | |
LOGFILE="/var/log/btrfs_setup.log" | |
if [ "$EUID" -ne 0 ]; then | |
echo "Please run as root" | |
exit 1 | |
fi | |
log() { | |
local msg="$1" | |
local timestamp | |
timestamp=$(date "+%Y-%m-%d %H:%M:%S") | |
echo "[$timestamp] $msg" | tee -a "$LOGFILE" | |
} | |
verify_mounts() { | |
log "Verifying mounted subvolumes..." | |
local all_mounted=true | |
for subvol in "${!subvolumes[@]}"; do | |
mount_point="${subvolumes[$subvol]}" | |
if mountpoint -q "$mount_point"; then | |
log "$mount_point is mounted." | |
else | |
log "$mount_point is NOT mounted." | |
all_mounted=false | |
fi | |
done | |
if $all_mounted; then | |
log "All configured subvolumes are mounted correctly." | |
else | |
log "One or more subvolumes are not mounted." | |
fi | |
} | |
log "Script started." | |
# Prompt for username (defaults to current user) | |
read -p "Enter your username (default: $USER): " inputUser | |
USER="${inputUser:-$USER}" | |
defaultBtrfsOptions="ssd,noatime,space_cache=v2,compress=lzo" | |
declare -A btrfsOptionsBySubvol=( | |
[@]="$defaultBtrfsOptions" | |
[@snapshots]="$defaultBtrfsOptions" | |
[@cache]="ssd,noatime,space_cache=v2,compress=no" | |
#[@log]="ssd,noatime,space_cache=v2,nodatacow,compress=no" | |
#[@tmp]="ssd,noatime,space_cache=v2" | |
) | |
# Check that backup fstab exists | |
if [ ! -f /etc/fstab.bak ]; then | |
log "Error: /etc/fstab.bak not found. Aborting." | |
exit 1 | |
fi | |
# Parse filesystems from backup | |
getBootFS=$(awk '$3 == "vfat" {print $1}' /etc/fstab.bak) | |
getRootFS=$(awk '$3 == "btrfs" {print $1; exit}' /etc/fstab.bak) | |
getSwapFS=$(lsblk -ln -o NAME,FSTYPE,UUID | awk '$2 == "swap" {print "UUID=" $3}') | |
# Check that Btrfs root filesystem found | |
if [ -z "$getRootFS" ]; then | |
log "Error: No Btrfs root filesystem found in /etc/fstab.bak." | |
exit 1 | |
fi | |
# List of expected subvolumes and mount points | |
declare -A subvolumes=( | |
[@]="/" | |
#[@home]="/home" | |
#[@log]="/var/log" | |
#[@tmp]="/var/tmp" | |
[@cache]="/var/cache" | |
[@snapshots]="/.snapshots" | |
) | |
# Mount root volume if needed to create subvols | |
if ! mountpoint -q /mnt; then | |
mount -o subvol=/ "$getRootFS" /mnt 2>/dev/null || { | |
log "Error: Failed to mount $getRootFS on /mnt." | |
exit 1 | |
} | |
fi | |
# Create subvolumes | |
log "Creating Btrfs subvolumes..." | |
for subvol in "${!subvolumes[@]}"; do | |
subvolPath="/mnt/${subvol#/}" | |
if [ ! -d "$subvolPath" ]; then | |
mkdir -p "$(dirname "$subvolPath")" | |
btrfs subvolume create "$subvolPath" | |
log "Created subvolume: $subvol" | |
else | |
log "Subvolume already exists: $subvol" | |
fi | |
done | |
# Create mount points | |
log "Ensuring mount points exist..." | |
for mountPoint in "${subvolumes[@]}"; do | |
if [ ! -d "$mountPoint" ]; then | |
mkdir -p "$mountPoint" | |
log "Created mount point: $mountPoint" | |
else | |
log "Mount point already exists: $mountPoint" | |
fi | |
done | |
# Unmount temporary mount | |
log "Cleaning up temporary mount..." | |
umount /mnt || { | |
log "Error: Failed to unmount /mnt." | |
exit 1 | |
} | |
log "Generating new /etc/fstab.new..." | |
# Start fresh | |
cat <<EOF > /etc/fstab.new | |
# /etc/fstab: static file system information. | |
# generated by afreisinger/gen-fstab-plus.sh https://gist.github.com/afreisinger/ab82936d5434b83503562ed66534373e | |
# <file system> <mount point> <type> <options> <dump> <pass> | |
EOF | |
# EFI | |
echo -e "\n# EFI boot partition" >> /etc/fstab.new | |
echo "$getBootFS /boot/efi vfat defaults 0 1" >> /etc/fstab.new | |
# Btrfs root | |
echo -e "\n# Mount btrfs subvolumes" >> /etc/fstab.new | |
# Sort mount points by path | |
for subvol in $(for k in "${!subvolumes[@]}"; do | |
echo "$k ${subvolumes[$k]}" | |
done | sort -k2 | awk '{print $1}'); do | |
mountPoint="${subvolumes[$subvol]}" | |
options="${btrfsOptionsBySubvol[$subvol]:-$defaultBtrfsOptions}" | |
echo "$getRootFS $mountPoint btrfs subvol=$subvol,$options 0 0" >> /etc/fstab.new | |
done | |
# Swap | |
if [ -n "$getSwapFS" ]; then | |
echo -e "\n# Swap" >> /etc/fstab.new | |
echo "$getSwapFS none swap sw 0 0" >> /etc/fstab.new | |
fi | |
# Other mounts from original fstab.bak (non-Btrfs, non-vfat) | |
echo -e "\n# Other mounts preserved from fstab.bak" >> /etc/fstab.new | |
awk '$1 !~ /^#/ && $3 != "btrfs" && $3 != "vfat" && $3 != "swap" {print}' /etc/fstab.bak >> /etc/fstab.new | |
# OPTIONAL bind mounts (e.g. ext4 /data to /home subdirs) | |
if [ "$ENABLE_BIND_MOUNTS" = true ]; then | |
echo -e "\n# Bind mounts from ext4 /data" >> /etc/fstab.new | |
declare -A bindMounts=( | |
["/data/dev"]="/home/$USER/dev" | |
["/data/bin"]="/home/$USER/bin" | |
) | |
for src in "${!bindMounts[@]}"; do | |
dst="${bindMounts[$src]}" | |
if [ -d "$src" ]; then | |
mkdir -p "$dst" | |
chown "$USER:$USER" "$dst" | |
echo "$src $dst none bind,nofail 0 0" >> /etc/fstab.new | |
log "Configured bind mount: $src -> $dst" | |
else | |
log "Skipping bind mount: source $src does not exist" | |
fi | |
done | |
else | |
log "Bind mounts from /data are disabled (ENABLE_BIND_MOUNTS=false)" | |
fi | |
# CIFS mount | |
if [ "$ENABLE_CIFS_MOUNT" = true ]; then | |
echo -e "\n# CIFS network share" >> /etc/fstab.new | |
# Install cifs-utils if missing | |
if ! command -v mount.cifs >/dev/null 2>&1; then | |
log "cifs-utils package not found. Installing..." | |
apt-get update && apt-get install -y cifs-utils || { | |
log "Error: Failed to install cifs-utils. Please install manually." | |
exit 1 | |
} | |
else | |
log "cifs-utils already installed." | |
fi | |
if [ ! -f "$CIFS_CREDENTIALS" ]; then | |
log "No credentials file found at $CIFS_CREDENTIALS" | |
read -p "Enter CIFS username: " CIFS_USER_INPUT | |
read -s -p "Enter CIFS password: " CIFS_PASS_INPUT | |
echo | |
cat <<EOF > "$CIFS_CREDENTIALS" | |
username=$CIFS_USER_INPUT | |
password=$CIFS_PASS_INPUT | |
EOF | |
chmod 600 "$CIFS_CREDENTIALS" | |
log "Credentials file created at $CIFS_CREDENTIALS" | |
else | |
log "Credentials file already exists at $CIFS_CREDENTIALS" | |
fi | |
mkdir -p "$CIFS_TARGET" | |
echo "$CIFS_SOURCE $CIFS_TARGET cifs $CIFS_OPTIONS 0 0" >> /etc/fstab.new | |
log "Configured CIFS mount: $CIFS_SOURCE -> $CIFS_TARGET" | |
fi | |
# Attempt to mount if not already mounted | |
if [ "$ENABLE_CIFS_MOUNT" = true ]; then | |
if ! mountpoint -q "$CIFS_TARGET"; then | |
log "Mounting CIFS share at $CIFS_TARGET..." | |
mount -t cifs "$CIFS_SOURCE" "$CIFS_TARGET" -o "$CIFS_OPTIONS" || { | |
log "Error: Failed to mount CIFS share at $CIFS_TARGET." | |
} | |
else | |
log "CIFS share already mounted at $CIFS_TARGET." | |
fi | |
fi | |
log "New fstab saved to /etc/fstab.new" | |
log "Summary of new /etc/fstab.new (non-comment lines):" | |
echo | |
grep -vE '^\s*#|^\s*$' /etc/fstab.new | |
echo | |
# Confirmar reemplazo de fstab | |
read -rp "Do you want to replace /etc/fstab with the new version? [y/N]: " confirm_fstab | |
if [[ "$confirm_fstab" =~ ^[Yy]$ ]]; then | |
log "Replacing /etc/fstab with new version" | |
cp /etc/fstab.new /etc/fstab | |
systemctl daemon-reload | |
# Confirmar si desea ejecutar mount -a | |
read -rp "Do you want to run 'mount -a' now? [y/N]: " confirm_mount | |
if [[ "$confirm_mount" =~ ^[Yy]$ ]]; then | |
log "User chose to run 'mount -a'" | |
if mount -a; then | |
log "'mount -a' completed successfully." | |
log "verify mount." | |
verify_mounts | |
else | |
log "Error during 'mount -a'." | |
exit 1 | |
fi | |
else | |
log "User skipped 'mount -a'" | |
fi | |
else | |
log "User declined to replace fstab. Skipping mount -a" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
GitHub repo