Skip to content

Instantly share code, notes, and snippets.

@p7cq
Last active October 10, 2020 22:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save p7cq/70e858c2d3853482928149281413c388 to your computer and use it in GitHub Desktop.
Save p7cq/70e858c2d3853482928149281413c388 to your computer and use it in GitHub Desktop.
Install Arch Linux with Root on Btrfs

Arch Linux Root on Btrfs

Prepare disks

Keeping about 15% unallocated space on both disks.

sgdisk --zap-all /dev/disk/by-id/ata-CT240BX500SSD1_A
sgdisk -n1:0:+550M -t1:ef00 /dev/disk/by-id/ata-CT240BX500SSD1_A
sgdisk -n2:0:+190G -t2:8300 /dev/disk/by-id/ata-CT240BX500SSD1_A
sgdisk -n3:0:+8G -t3:8200 /dev/disk/by-id/ata-CT240BX500SSD1_A

sgdisk --zap-all /dev/disk/by-id/ata-CT240BX500SSD1_B
sgdisk -n1:0:+550M -t1:ef00 /dev/disk/by-id/ata-CT240BX500SSD1_B
sgdisk -n2:0:+190G -t2:8300 /dev/disk/by-id/ata-CT240BX500SSD1_B
sgdisk -n3:0:8G -t3:8200 /dev/disk/by-id/ata-CT240BX500SSD1_B

mkfs.vfat -F 32 /dev/disk/by-id/ata-CT240BX500SSD1_A-part1
mkfs.vfat -F 32 /dev/disk/by-id/ata-CT240BX500SSD1_B-part1

mkswap /dev/disk/by-id/ata-CT240BX500SSD1_A-part3
mkswap /dev/disk/by-id/ata-CT240BX500SSD1_B-part3
swapon /dev/disk/by-id/ata-CT240BX500SSD1_A-part3 /dev/disk/by-id/ata-CT240BX500SSD1_B-part3

mkfs.btrfs -L system -m raid1 -d raid0 /dev/disk/by-id/ata-CT240BX500SSD1_A-part2 /dev/disk/by-id/ata-CT240BX500SSD1_B-part2

mkdir /mnt/system
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache /dev/disk/by-label/system /mnt/system
mkdir /mnt/system/{snapshots,default}

btrfs su cr /mnt/system/default/ROOT
btrfs su cr /mnt/system/snapshots/ROOT
btrfs su cr /mnt/system/default/home
btrfs su cr /mnt/system/default/local
btrfs su cr /mnt/system/default/log
btrfs su cr /mnt/system/default/pkg
btrfs su cr /mnt/system/default/docker
btrfs su cr /mnt/system/default/mysql

mkdir -p /mnt/default
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,subvol=default/ROOT /dev/disk/by-label/system /mnt/default

mkdir -p /mnt/default/{boot,home,local,var/log,var/cache/pacman/pkg,var/lib/docker,var/lib/mysql,.snapshots}

mount /dev/disk/by-id/ata-CT240BX500SSD1_A-part1 /mnt/default/boot

mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,subvol=default/home /dev/disk/by-label/system            /mnt/default/home
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,subvol=default/local /dev/disk/by-label/system           /mnt/default/local
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,subvol=default/log /dev/disk/by-label/system             /mnt/default/var/log
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,subvol=default/pkg /dev/disk/by-label/system             /mnt/default/var/cache/pacman/pkg
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,subvol=default/docker /dev/disk/by-label/system          /mnt/default/var/lib/docker
mount -o rw,noatime,nodiratime,compress=no,ssd,space_cache,nodatacow,subvol=default/mysql /dev/disk/by-label/system /mnt/default/var/lib/mysql
mount -o rw,noatime,nodiratime,compress=zstd:3,ssd,space_cache,subvol=snapshots/ROOT /dev/disk/by-label/system      /mnt/default/.snapshots

pacstrap /mnt/default base base-devel linux linux-firmware btrfs-progs vim sudo networkmanager openssh amd-ucode rsync

genfstab -U /mnt/default >> /mnt/default/etc/fstab

arch-chroot /mnt/default

[...]
bootctl --path=/boot install
[...]

Add btrfs hook in mkinitcpio.conf and create initial ramdisk.

mkinitcpio -p linux
passwd
exit
umount -R /mnt/{default,system}
reboot

Snapshots

Create directories:

mkdir /.snapshots/{B,R}OOT /run/ROOT

Mount top-level volume:

UUID=415391c1-657a-4102-82c3-239e4363fc71 /run/ROOT btrfs rw,nodev,nosuid,noexec,noatime,nodiratime,compress=no,ssd,space_cache 0 0
#!/usr/bin/env bash
#
# bsys, a BTRFS snapshot/rollback script
#
# @p7cq
#N=$(date +%s%N); export PS4='+[$((($(date +%s%N)-$N)/1000000))ms][${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; set -x;
ss_to_keep=400
rootmark="ROOT"
bootmark="BOOT"
root="/run/${rootmark}/default/${rootmark}"
root_ss="/.snapshots/${rootmark}"
boot_ss="/.snapshots/${bootmark}"
boot_part="/boot"
loader_file="${boot_part}/loader/entries/arch.conf"
action=${1}
ss_date=$(/usr/bin/date +"%A %B %e, %Y %k:%M:%S")
ss_manifest_file=".manifest"
function _help() {
local topic=${1}
[[ ${topic} == "usage" ]] && \
/usr/bin/echo -e "usage:\n \
bsys delete [snapshot id] remove all snapshots keeping only the last ${ss_to_keep}\n \
bsys snapshot [comment] snapshot the system; comment max length 64 chars\n \
bsys rollback [snapshot id] rollback the system to the previous state (the last snapshot)\n \
bsys list list snapshots\n \
bsys help usage" && \
exit 0
}
function _boot_ss() {
[[ -d "${1}" ]] && /usr/bin/echo "\e[97m\e[1mboot snapshot id is undefined\e[0m" && exit 1
local ss_id="${1}"
/usr/bin/mkdir ${boot_ss}/${ss_id}
/usr/bin/echo ${ss_manifest} > ${boot_ss}/${ss_id}/${ss_manifest_file}
/usr/bin/rsync -avzq --delete ${boot_part}/ ${boot_ss}/${ss_id}/${bootmark}
}
function _boot_rb() {
local ss_id="${1}"
[[ -z "${ss_id}" ]] && /usr/bin/echo "\e[97m\e[1mboot snapshot id is undefined\e[0m" && exit 1
/usr/bin/echo -e "\e[97m\e[1moverwriting boot partition at \e[0m\e[33m\e[1m${boot_part}\e[0m"
/usr/bin/rsync -avzq --delete ${boot_ss}/${ss_id}/${bootmark}/ ${boot_part}
IFS='^' read -ra manifest <<< "$(/usr/bin/cat ${boot_ss}/${ss_id}/${ss_manifest_file})"
local title="title Arch Linux (${ss_id}: ${manifest[0]} ${manifest[1]})"
/usr/bin/sed -i "1 s/^.*$/${title}/g" ${loader_file}
}
function _ss_manifest() {
local comment="${1}"
ss_manifest="^${ss_date}"
[[ -z "${comment}" ]] && comment="snapshot"
comment=$(/usr/bin/sed 's/\^/_/g' <<< ${comment})
ss_manifest="${comment:0:64}${ss_manifest}"
}
function _restrict() {
if ((${EUID:-0} || "$(/usr/bin/id -u)")); then
/usr/bin/echo -e "\e[97m\e[1myou must be root to do this\e[0m"
exit 1
fi
}
declare -a args=("delete" "d" "snapshot" "s" "rollback" "r" "list" "l" "help" "h")
[[ "$#" = "0" ]] || [[ ! " ${args[@]} " =~ " ${1} " ]] && _help "usage" && exit 0
[[ ! -d ${root_ss} ]] && /usr/bin/echo "${root_ss} does not exist" && exit 1
[[ ! -d ${boot_ss} ]] && /usr/bin/echo "${boot_ss} does not exist" && exit 1
ss_count=$(/usr/bin/ls ${root_ss} | /usr/bin/wc -l)
if [[ ${action} == "delete" ]] || [[ ${action} = "d" ]]; then
_restrict
ss_id=${2}
if [[ ! -z "${ss_id}" ]]; then
re='^[0-9]+$'
[[ ! ${ss_id} =~ ${re} ]] && /usr/bin/echo -e "\e[97m\e[1ma positive integer is expected (see \e[0m\e[33m\e[1mbsys list\e[0m)\e[0m" && exit 1
[[ ! -d "${root_ss}/${ss_id}" ]] && /usr/bin/echo -e "\e[97m\e[1mno such snapshot\e[0m" && exit 1
/usr/bin/btrfs pr se -ts ${root_ss}/${ss_id}/${rootmark} ro false
/usr/bin/rm -rf ${root_ss}/${ss_id} ${boot_ss}/${ss_id}
exit 0
fi
if (( ss_count > ss_to_keep )); then
lwm=$(/usr/bin/ls ${root_ss} | /usr/bin/sort -n | /usr/bin/head -1)
hwm=$(/usr/bin/ls ${root_ss} | /usr/bin/sort -nr | /usr/bin/head -1)
end=$((hwm-ss_to_keep))
if [[ ! -z "${lwm}" ]] && [[ ! -z "${hwm}" ]]; then
for ((s=lwm; s<=end; s++)) do
[[ ! -d "${root_ss}/${s}" ]] && continue
/usr/bin/btrfs pr se -ts ${root_ss}/${s}/${rootmark} ro false
/usr/bin/rm -rf ${root_ss}/${s} ${boot_ss}/${s}
done
fi
else
/usr/bin/echo -e "\e[97m\e[1mno snapshots deleted (less than \e[0m\e[33m\e[1m${ss_to_keep}\e[0m\e[97m\e[1m)\e[0m"
fi
exit 0
fi
if [[ ${action} == "snapshot" ]] || [[ ${action} == "s" ]]; then
_restrict
ss_id=$(($(/usr/bin/ls ${root_ss} | /usr/bin/sort -nr | /usr/bin/head -1)+1))
[[ -d "${root}.old" ]] && /usr/bin/rm -rf ${root}.old
/usr/bin/mkdir ${root_ss}/${ss_id}
_ss_manifest "${2}"
/usr/bin/echo ${ss_manifest} > ${root_ss}/${ss_id}/${ss_manifest_file}
/usr/bin/btrfs su sn -r ${root} ${root_ss}/${ss_id}/${rootmark} > /dev/null
_boot_ss ${ss_id}
/usr/bin/echo -e "\e[97m\e[1mcreated snapshot with id \e[0m\e[33m\e[1m${ss_id}\e[0m"
exit 0
fi
if [[ ${action} == "rollback" ]] || [[ ${action} == "r" ]]; then
_restrict
[[ -d "${root}.old" ]] && /usr/bin/rm -rf ${root}.old
ss_id=${2}
last_ss=$(/usr/bin/ls ${root_ss} | /usr/bin/sort -nr | /usr/bin/head -1)
if [[ ! -z "${ss_id}" ]]; then
[[ "${ss_id}" == "0" ]] && /usr/bin/echo -e "\e[97m\e[1mno such snapshot\e[0m" && exit 1
([[ "${ss_id}" == "0" ]] || [[ ! -d "${root_ss}/${ss_id}" ]] || [[ ! -d "${boot_ss}/${ss_id}" ]]) && /usr/bin/echo -e "\e[97m\e[1mno such snapshot\e[0m" && exit 1
re='^[0-9]+$'
[[ ! ${ss_id} =~ ${re} ]] && /usr/bin/echo -e "\e[97m\e[1ma positive integer is expected\e[0m" && exit 1
else
ss_id=${last_ss}
fi
IFS='^' read -ra manifest <<< "$(/usr/bin/cat ${root_ss}/${ss_id}/${ss_manifest_file})"
/usr/bin/echo -e "\e[97m\e[1mrolling back to snapshot \e[0m\e[33m\e[1m${ss_id}\e[0m\e[97m\e[1m: \e[0m\e[33m\e[1m${manifest[0]}\e[97m\e[1m taken on \e[0m\e[33m\e[1m${manifest[1]}\e[0m"
read -p $'\e[97m\e[1ma reboot will be required, proceed? (yes/no): \e[0m' answer
if [[ ${answer} == "no" ]]; then exit 0; fi
/usr/bin/mv ${root} ${root}.old
/usr/bin/btrfs su sn ${root_ss}/${ss_id}/${rootmark} ${root} > /dev/null
_boot_rb ${ss_id}
/usr/bin/echo -e "\e[97m\e[1msnapshot \e[0m\e[33m\e[1m${manifest[0]}\e[0m\e[97m\e[1m is now the active system, rebooting...\e[0m"
sleep 2
/usr/bin/systemctl reboot
fi
if [[ ${action} == "help" ]] || [[ ${action} == "h" ]]; then
_help "usage"
exit 0
fi
if [[ ${action} == "list" ]] || [[ ${action} == "l" ]]; then
if ((ss_count == 0)); then
/usr/bin/echo -e "\e[97m\e[1mno snapshots available\e[0m"
exit 0
fi
for id in $(/usr/bin/ls ${root_ss} | /usr/bin/sort -nr); do
IFS='^' read -ra manifest <<< "$(/usr/bin/cat ${root_ss}/${id}/${ss_manifest_file})"
/usr/bin/echo -e "\e[33m\e[1m${id}\e[0m^\e[97m\e[1m${manifest[0]}^${manifest[1]}\e[0m"
done | /usr/bin/column -t -N ID,COMMENT,DATE -s "^"
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment