Skip to content

Instantly share code, notes, and snippets.

@afreisinger
Last active June 8, 2025 19:07
Show Gist options
  • Save afreisinger/ab82936d5434b83503562ed66534373e to your computer and use it in GitHub Desktop.
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…
#!/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
@afreisinger
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment