mirror of
https://git.myvelabs.com/lab/archlinux.git
synced 2025-12-17 21:36:25 +00:00
2315 lines
57 KiB
Bash
Executable file
2315 lines
57 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -a
|
|
set -E
|
|
revision=1.0l
|
|
|
|
# ZFS key
|
|
# zfsgpgkey=DDF7DB817396A49B2A2723F7403BD972F75D9D76 # archzfs
|
|
zfsgpgkey=D0F2AE55C1BF11A026D155813A658A95B8CFCC51 # myvezfs
|
|
|
|
# SSH public keys, if not supplied
|
|
sshkey=https://git.myvelabs.com/lab/archlinux/raw/branch/master/notes/sshkeys.pub
|
|
|
|
# Exit function
|
|
trap '[ "${?}" -ne 77 ] || exit 77' ERR
|
|
function die
|
|
{
|
|
local reset="$(tput sgr0)"
|
|
local red="${reset}$(tput setaf 1)"
|
|
local yellow="${reset}$(tput setaf 3)"
|
|
|
|
cat <<- abort
|
|
${red}
|
|
Error encountered for the following reason:
|
|
${yellow}
|
|
${@}
|
|
${red}
|
|
Script aborted...
|
|
${reset}
|
|
abort
|
|
|
|
exit 77
|
|
}
|
|
|
|
# EFI system check
|
|
if [ ! -d /sys/firmware/efi/efivars/ ]
|
|
then
|
|
die 'This script only works with UEFI systems'
|
|
fi
|
|
|
|
# Quit if zfs modules aren't loaded
|
|
modprobe zfs || die 'ZFS modules are not loaded'
|
|
|
|
##
|
|
## Declare functions
|
|
##
|
|
|
|
# Prompt function
|
|
function reformat
|
|
{
|
|
case "${1}" in
|
|
# Formatting
|
|
bold) format+=($(tput bold)) ;;
|
|
italic) format+=($(tput sitm)) ;;
|
|
uline) format+=($(tput smul)) ;;
|
|
blink) format+=($(tput blink)) ;;
|
|
dim) format+=($(tput dim)) ;;
|
|
|
|
# Colours
|
|
black) format+=($(tput setaf 0)) ;;
|
|
red) format+=($(tput setaf 1)) ;;
|
|
green) format+=($(tput setaf 2)) ;;
|
|
yellow) format+=($(tput setaf 3)) ;;
|
|
blue) format+=($(tput setaf 4)) ;;
|
|
magenta) format+=($(tput setaf 5)) ;;
|
|
cyan) format+=($(tput setaf 6)) ;;
|
|
white) format+=($(tput setaf 7)) ;;
|
|
|
|
# Read-specific
|
|
array) array="-a" ;;
|
|
secret) secret="-s" ;;
|
|
press) press="-n 1" ;;
|
|
|
|
# Everything else goes into line
|
|
*) line="${@}" ;;
|
|
esac
|
|
}
|
|
|
|
# Syntax: ask for var [in format] "statement"
|
|
function ask
|
|
{
|
|
local format variable line secret press array
|
|
|
|
case "${1}" in
|
|
for)
|
|
case "${2}" in
|
|
in)
|
|
die "Syntax error detected (missing variable before $(tput sitm)$(tput smso)in$(tput rmso)$(tput ritm))
|
|
Usage: ask for \${variable} in \${format[@]} \"\${line}\""
|
|
;;
|
|
*)
|
|
variable="${2}"
|
|
shift 2
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
die "Syntax error detected (misuse of $(tput sitm)$(tput smso)for$(tput rmso)$(tput ritm))
|
|
Usage: ask for \${variable} in \${format[@]} \"\${line}\""
|
|
;;
|
|
esac
|
|
|
|
case "${1}" in
|
|
in)
|
|
shift
|
|
for arg in "${@}"
|
|
do
|
|
reformat ${arg}
|
|
done
|
|
;;
|
|
*) line="${@}" ;;
|
|
esac
|
|
|
|
read ${press} -r -p "$(printf "%s" "${format[@]}" "${line}: " "$(tput sgr0)")" ${secret} ${array} ${variable}
|
|
echo
|
|
|
|
export ${variable}
|
|
}
|
|
|
|
# Syntax: say [as template] [in format] "statement"
|
|
function say
|
|
{
|
|
local format line heading
|
|
|
|
case "${1}" in
|
|
as)
|
|
# Templates
|
|
case ${2} in
|
|
title)
|
|
format+=($(tput setaf 6)) # cyan
|
|
;;
|
|
heading)
|
|
format+=($(tput setaf 5) $(tput bold)) # bold magenta
|
|
heading=":: "
|
|
;;
|
|
success)
|
|
format+=($(tput setaf 2)) # green
|
|
;;
|
|
warning)
|
|
format+=($(tput setaf 1)) # red
|
|
;;
|
|
*)
|
|
die "Syntax error detected (misuse of $(tput sitm)$(tput smso)as$(tput rmso)$(tput ritm))
|
|
Usage: say as \${template} in \${format[@]} \"\${line}\""
|
|
;;
|
|
esac
|
|
shift 2
|
|
;;
|
|
esac
|
|
|
|
case "${1}" in
|
|
in)
|
|
shift
|
|
for arg in "${@}"
|
|
do
|
|
reformat ${arg}
|
|
done
|
|
;;
|
|
*) line="${@}" ;;
|
|
esac
|
|
|
|
printf "%s" "${format[@]}" "${heading}" "${line}" "$(tput sgr0)"
|
|
echo
|
|
}
|
|
|
|
function presskeytoresume
|
|
{
|
|
read -n 1 -s -p "Press any key when ready..."
|
|
echo
|
|
}
|
|
|
|
# Repeat a command until it exits with code 0
|
|
function repeat
|
|
{
|
|
until ${@}
|
|
do
|
|
sleep 3
|
|
done
|
|
}
|
|
|
|
# Fetch flags
|
|
rm -f sshkeys
|
|
while [ ${1} ]
|
|
do
|
|
case ${1} in
|
|
-s | --ssh-key )
|
|
if [ "${2}" ]
|
|
then
|
|
echo "${2}" >>sshkeys
|
|
if ! ssh-keygen -l -f sshkeys >/dev/null
|
|
then
|
|
die 'Invalid SSH public key detected'
|
|
fi
|
|
shift
|
|
fi
|
|
;;
|
|
-p | --port )
|
|
if [ "${2}" ]
|
|
then
|
|
port=${2}
|
|
shift
|
|
fi
|
|
;;
|
|
-c | --cache )
|
|
if [ "${2}" ]
|
|
then
|
|
cacheserver=${2}
|
|
shift
|
|
fi
|
|
;;
|
|
-t | --test )
|
|
load_defaults=y
|
|
;;
|
|
-? | -h | --help )
|
|
cat <<- help
|
|
Parameters:
|
|
-s, --ssh-key Add SSH public key (enclosed in quotes)
|
|
-p, --port SSH port
|
|
-c, --cache Pacman cache server
|
|
-t, --test Load troubleshooting defaults
|
|
-?, -h, --help This help screen
|
|
help
|
|
exit 0
|
|
;;
|
|
* ) die "Unknown flag: ${1}" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Quit if ssh keys are missing
|
|
if [ ! -f sshkeys ]
|
|
then
|
|
curl --fail --silent ${sshkey} -o sshkeys
|
|
fi
|
|
|
|
# Ensure pacman.conf settings are properly set
|
|
if [ ! -f /etc/pacman.conf.bkp ]
|
|
then
|
|
mv /etc/pacman.conf /etc/pacman.conf.bkp
|
|
fi
|
|
sed -e '/ParallelDownloads/c ParallelDownloads = 10' \
|
|
-e '/Color/c Color' /etc/pacman.conf.bkp >/etc/pacman.conf
|
|
if ! grep -q myvezfs /etc/pacman.conf
|
|
then
|
|
sed '/\[core\]/i [myvezfs]\
|
|
Server = https://mirror.myvelabs.com/repo/$repo\
|
|
Server = https://repo.myvelabs.com/$repo\n' -i /etc/pacman.conf
|
|
fi
|
|
|
|
# Internet connection check
|
|
if nc -z -w 1 archlinux.org 443 >/dev/null 2>&1 || nc -z -w 1 google.com 443 >/dev/null 2>&1
|
|
then
|
|
timedatectl set-ntp true
|
|
pacman -Sy --ask 4 >/dev/null 2>&1
|
|
else
|
|
die 'No internet connectivity detected, plug in an ethernet cable and try again'
|
|
fi
|
|
|
|
# Determine microcode
|
|
if lscpu | grep 'Model name:' | grep -q AMD
|
|
then
|
|
ucode=amd-ucode
|
|
elif lscpu | grep 'Model name:' | grep -q Intel
|
|
then
|
|
ucode=intel-ucode
|
|
else
|
|
die 'Unable to determine CPU type'
|
|
fi
|
|
|
|
# Network interface details
|
|
originaliface=$(dmesg | grep $(ip r | grep 'default via' | awk '{print $5}') | grep 'renamed from' | awk '{print $NF}')
|
|
iface=$(ip r | grep 'default via' | awk '{print $5}')
|
|
macaddress=$(cat /sys/class/net/${iface}/address)
|
|
|
|
# Detect needed firmwares
|
|
if ls -l /dev/disk/* | grep -q VBOX
|
|
then
|
|
linux_firmware+=(virtualbox-guest-utils)
|
|
systemd_services+=(vboxservice.service)
|
|
[ ${cacheserver} ] || cacheserver=http://10.0.2.2:9090
|
|
elif ls -l /dev/disk/* | grep -q 'virtio\|QEMU'
|
|
then
|
|
linux_firmware+=(qemu-guest-agent spice-vdagent)
|
|
systemd_services+=(qemu-guest-agent.service)
|
|
[ ${cacheserver} ] || cacheserver=http://192.168.122.1:9090
|
|
if lspci | grep VGA | grep -q QXL
|
|
then
|
|
linux_firmware+=(xf86-video-qxl)
|
|
fi
|
|
else
|
|
linux_firmware+=(linux-firmware)
|
|
for firmware in $(pacman -Ssq linux-firmware- | sed 's/linux-firmware-//')
|
|
do
|
|
if lspci | grep -q -i ${firmware}
|
|
then
|
|
linux_firmware+=(linux-firmware-${firmware})
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Virtual machine defaults
|
|
function virtualMachineDefaults
|
|
{
|
|
hostname=archzfs
|
|
userpass=123
|
|
lukspass=12345678
|
|
installation_drive_array=($(lsblk -d -n -o name,type | awk '($2 == "disk") {print $1}'))
|
|
|
|
primaryip=192.168.122.122
|
|
gateway=192.168.122.1
|
|
netconf=ip=${primaryip}::${gateway}:255.255.255.0::\${originaliface}:none
|
|
|
|
say in italic 'Troubleshooting defaults loaded'
|
|
echo
|
|
}
|
|
|
|
# Fetch disk ID
|
|
function returnDISKID
|
|
{
|
|
find -L /dev/disk/by-id/ -samefile /dev/${1} | grep -E "(wwn|nvme-(uuid|nvme|eui)|QEMU|virtio|VBOX)" | head -1
|
|
}
|
|
|
|
# Grab system variables
|
|
function initialSetup
|
|
{
|
|
# Hostname
|
|
say as title "Create a name for your computer"
|
|
until [ ${hostname} ]
|
|
do
|
|
ask for hostname "Hostname"
|
|
if [ -z ${hostname} ]
|
|
then
|
|
say as warning "Hostname cannot be empty, try again"
|
|
fi
|
|
done
|
|
|
|
# User password
|
|
say as title "Set a password for user"
|
|
until [ "${userpass}" = "${userpass2}" -a "${userpass}" ]
|
|
do
|
|
ask for userpass in secret "User password"
|
|
ask for userpass2 in secret "Verify user password"
|
|
echo
|
|
if [ -z "${userpass}" ]
|
|
then
|
|
say as warning "Password field cannot be empty, try again"
|
|
elif [ "${userpass}" != "${userpass2}" ]
|
|
then
|
|
say as warning "Passwords did not match, try again"
|
|
fi
|
|
done
|
|
say as success "user@${hostname}'s password has been saved"
|
|
echo
|
|
|
|
# Encryption key
|
|
say as title "Create an encryption passphrase for the system drive"
|
|
until [ "${lukspass}" = "${lukspass2}" -a "${lukspass}" -a "${#lukspass}" -ge 8 ]
|
|
do
|
|
ask for lukspass in secret "Encryption password"
|
|
ask for lukspass2 in secret "Verify encryption password"
|
|
echo
|
|
if [ -z "${lukspass}" ]
|
|
then
|
|
say as warning "Passphrase field cannot be empty, try again"
|
|
elif [ "${lukspass}" != "${lukspass2}" ]
|
|
then
|
|
say as warning "Passphrases did not match, try again"
|
|
elif [ "${#lukspass}" -lt 8 ]
|
|
then
|
|
say as warning "Passphrase needs to be at least 8 characters"
|
|
fi
|
|
done
|
|
say as success "Encryption passphrase has been saved"
|
|
echo
|
|
|
|
# Assign system drive
|
|
local installation_drive tmpavailabledrives availabledrives valid
|
|
say as title "Identify the system drive from the list of available devices below"
|
|
say in italic '## SSD and HDD device format begins with "sd" or "hd" (sda, sdb, sd[*])'
|
|
say in italic '## NVME and PCI device format is "nvme[*]n1" (nvme0n1, nvme1n1, nvme[*]n1)'
|
|
while true
|
|
do
|
|
if [ "${installation_drive}" ]
|
|
then
|
|
installation_drive=($(printf "%s\n" ${installation_drive} | sort -u))
|
|
local tmpavailabledrives=(${availabledrives[@]})
|
|
for i in ${installation_drive[@]}
|
|
do
|
|
if [[ " ${availabledrives[@]} " =~ " ${i} " ]]
|
|
then
|
|
installation_drive_array+=(${i})
|
|
availabledrives=(${availabledrives[@]/${i}})
|
|
valid=1
|
|
else
|
|
say as warning "Invalid selection: ${i}"
|
|
say as warning "Try again with the following valid drives:"
|
|
echo "$(tput smul)${tmpavailabledrives[@]}$(tput sgr0)"
|
|
echo
|
|
valid=0
|
|
break
|
|
fi
|
|
done
|
|
[ ${valid} -eq 0 ] || break
|
|
fi
|
|
|
|
# Reset arrays
|
|
unset installation_drive_array
|
|
availabledrives=($(lsblk -d -n -o name,type | awk '($2 == "disk") {print $1}'))
|
|
lsblk -d -o name,model,size,mountpoint,type | grep "NAME\|disk"
|
|
ask for installation_drive "Installation device"
|
|
done
|
|
say as success "You have selected: ${installation_drive_array[@]/#/\/dev\/}"
|
|
echo
|
|
|
|
# Assign more system drives if available
|
|
local tmpavailabledrives next_installation_drive tmpinstallation_drive_array
|
|
while true
|
|
do
|
|
if [ "${availabledrives[*]}" ]
|
|
then
|
|
say as title "More available drives detected: ${availabledrives[@]}"
|
|
say in italic "(leave blank to skip this step)"
|
|
say in italic '## SSD and HDD device format begins with "sd" or "hd" (sda, sdb, sd[*])'
|
|
say in italic '## NVME and PCI device format is "nvme[*]n1" (nvme0n1, nvme1n1, nvme[*]n1)'
|
|
unset tmpinstallation_drive_array
|
|
tmpavailabledrives=(${availabledrives[@]})
|
|
ask for next_installation_drive "Installation device"
|
|
|
|
# Skip if blank
|
|
if [ "${next_installation_drive}" ]
|
|
then
|
|
next_installation_drive=($(printf "%s\n" ${next_installation_drive} | sort -u))
|
|
for i in ${next_installation_drive[@]}
|
|
do
|
|
if [[ " ${tmpavailabledrives[@]} " =~ " ${i} " ]]
|
|
then
|
|
tmpinstallation_drive_array+=(${i})
|
|
tmpavailabledrives=(${tmpavailabledrives[@]/${i}})
|
|
valid=1
|
|
else
|
|
say as warning "Invalid selection: ${i}"
|
|
say as warning "Try again with the following valid drives:"
|
|
echo "$(tput smul)${availabledrives[@]}$(tput sgr0)"
|
|
echo
|
|
valid=0
|
|
break
|
|
fi
|
|
done
|
|
else
|
|
break
|
|
fi
|
|
|
|
# Success
|
|
if [ ${valid} -eq 1 ]
|
|
then
|
|
installation_drive_array+=(${tmpinstallation_drive_array[@]})
|
|
availabledrives=(${tmpavailabledrives[@]})
|
|
say as success "You have added: ${tmpinstallation_drive_array[@]/#/\/dev\/}"
|
|
echo
|
|
fi
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Static IP
|
|
while true
|
|
do
|
|
ask for staticip in blue press 'Would you like to use a static IP? (y/n)'
|
|
echo
|
|
case ${staticip} in
|
|
[yY])
|
|
# Static IP
|
|
say as title "Fill in your machine's static IP"
|
|
until grep -qE -o '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<<${primaryip}
|
|
do
|
|
ask for primaryip "IP Address"
|
|
if ! grep -qE -o '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<<${primaryip}
|
|
then
|
|
say as warning "Invalid address format, try again"
|
|
fi
|
|
done
|
|
# Gateway
|
|
say as title "Fill in your network's gateway"
|
|
until grep -qE -o '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<<${gateway}
|
|
do
|
|
ask for gateway "Gateway"
|
|
if ! grep -qE -o '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<<${gateway}
|
|
then
|
|
say as warning "Invalid address format, try again"
|
|
fi
|
|
done
|
|
# Set boot parameter netconf variable for static ip
|
|
netconf=ip=${primaryip}::${gateway}:255.255.255.0::\${originaliface}:none
|
|
break
|
|
;;
|
|
[nN])
|
|
# Set boot parameter netconf variable for dhcp
|
|
netconf=ip=:::::\${originaliface}:dhcp
|
|
break
|
|
;;
|
|
*) say as warning 'Not a valid answer, type "y" or "n"' ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Customize mirrored zpools
|
|
function mirrorsetup
|
|
{
|
|
local mirrorsetupArray=(${installation_drive_array[@]})
|
|
local mirror pair pairArray diskid pool
|
|
local index=1
|
|
|
|
while true
|
|
do
|
|
# Exit only when all drives are assigned
|
|
if [[ $(wc -w <<<${mirrorsetupArray[@]}) -gt 0 ]]
|
|
then
|
|
say in black bold "# System drives: $(echo ${mirrorsetupArray[@]})"
|
|
ask for pool${index} in array "Enter mirror pool ${index}"
|
|
else
|
|
break 1
|
|
fi
|
|
|
|
# Temporary variables
|
|
pair=pool${index}[@]
|
|
pairArray=($(printf '%s\n' ${!pair}))
|
|
|
|
# Escape only if selections are valid
|
|
while true
|
|
do
|
|
# Ensure drive is chosen from installation drive pool
|
|
for drive in ${pairArray[@]}
|
|
do
|
|
if ! [[ " ${mirrorsetupArray[*]} " =~ " ${drive} " ]]
|
|
then
|
|
say as warning "Invalid drive selected"
|
|
echo
|
|
break 2
|
|
fi
|
|
done
|
|
# Reject mirrored pair if drives are entered more than once
|
|
if [ $(printf '%s\n' ${!pair} | sort | uniq -d) ]
|
|
then
|
|
say as warning "Duplicate entries detected"
|
|
echo
|
|
break 1
|
|
# Need at least 2 drives per mirrored pool
|
|
elif [[ ${#pairArray[@]} -lt 2 ]]
|
|
then
|
|
say as warning 'Mirrored pairs must have at least two drives'
|
|
echo
|
|
break 1
|
|
# Stray protection
|
|
elif [[ $((${#mirrorsetupArray[@]}-${#pairArray[@]})) -eq 1 ]]
|
|
then
|
|
say as warning 'Remaining drive does not have a mirror pair'
|
|
echo
|
|
break 1
|
|
# Success: remove entries from remaining drive pool
|
|
else
|
|
for delete in ${pairArray[@]}
|
|
do
|
|
mirrorsetupArray=(${mirrorsetupArray[@]/${delete}})
|
|
done
|
|
# Add an index if there are more drives remaining
|
|
((index++))
|
|
break 1
|
|
fi
|
|
done
|
|
done
|
|
|
|
# Convert drives to persistent disk/by-id naming
|
|
for pool in ${!pool*}
|
|
do
|
|
((mirror++))
|
|
local diskid=${pool}[@]
|
|
for drive in ${!diskid}
|
|
do
|
|
eval "mirrorpool${mirror}+=(${drive})"
|
|
done
|
|
done
|
|
|
|
# Export zroot layout
|
|
unset zrootlayout
|
|
pool=0
|
|
for mirrorpool in ${!mirrorpool*}
|
|
do
|
|
((pool++))
|
|
zpool=${mirrorpool}[@]
|
|
say as success "zroot mirror ${pool}: ${!zpool}"
|
|
zrootlayout+=($(echo "mirror ${!zpool}"))
|
|
done
|
|
echo
|
|
}
|
|
|
|
# Generate SSH keys
|
|
function gensshid
|
|
{
|
|
ssh-keygen -q \
|
|
-t ed25519 \
|
|
-P "" \
|
|
-C "${USER}@${hostname}" \
|
|
-f ~/.ssh/id_ed25519
|
|
mkdir ~/.ssh/sockets/
|
|
}
|
|
|
|
clear
|
|
|
|
say in italic "## rev ${revision}"
|
|
|
|
echo
|
|
say in uline white dim "Full Disk Encryption with EFISTUB"
|
|
echo
|
|
presskeytoresume
|
|
echo
|
|
|
|
# Setup
|
|
if ls -l /dev/disk/* | grep -q 'VBOX\|virtio\|QEMU'
|
|
then
|
|
while true
|
|
do
|
|
if ! [ -v load_defaults ]
|
|
then
|
|
ask for load_defaults in blue press "Would you like to load virtual machine troubleshooting defaults? (y/n)"
|
|
echo
|
|
fi
|
|
case ${load_defaults} in
|
|
[yY])
|
|
virtualMachineDefaults
|
|
break
|
|
;;
|
|
[nN])
|
|
initialSetup
|
|
break
|
|
;;
|
|
*) say as warning 'Not a valid answer, type "y" or "n"' ;;
|
|
esac
|
|
done
|
|
else
|
|
initialSetup
|
|
fi
|
|
|
|
# Default zroot layout
|
|
zrootlayout=($(echo mirror ${installation_drive_array[@]}))
|
|
|
|
# Optional: choose zroot mirror layout
|
|
if [ ${#installation_drive_array[@]} -ge 4 ]
|
|
then
|
|
while true
|
|
do
|
|
ask for mirror_setup in blue press "Would you like to split the installation disks into mirrored pairs? (y/n)"
|
|
echo
|
|
case ${mirror_setup} in
|
|
[yY])
|
|
mirrorsetup
|
|
break
|
|
;;
|
|
[nN])
|
|
break
|
|
;;
|
|
*) say as warning 'Not a valid answer, type "y" or "n"' ;;
|
|
esac
|
|
done
|
|
elif [ ${#installation_drive_array[@]} -eq 1 ]
|
|
then
|
|
zrootlayout=(${installation_drive_array[@]})
|
|
fi
|
|
|
|
# Fetch disk ID of installation disks
|
|
for drive in ${installation_drive_array[@]}
|
|
do
|
|
diskid=$(returnDISKID ${drive})
|
|
zrootlayout=(${zrootlayout[@]/${drive}/${diskid}-part2})
|
|
installation_disks+=(${diskid})
|
|
done
|
|
|
|
# Device shredding
|
|
while true
|
|
do
|
|
ask for shred_disk in blue press 'Would you like to shred your installation drive? (y/n)'
|
|
echo
|
|
|
|
case ${shred_disk} in
|
|
[yY])
|
|
say as title "Select an overwrite source"
|
|
cat <<- shred
|
|
1) zero
|
|
2) urandom
|
|
shred
|
|
while true
|
|
do
|
|
ask for shred_source in press ":"
|
|
case ${shred_source} in
|
|
1)
|
|
shred=zero
|
|
break
|
|
;;
|
|
2)
|
|
shred=urandom
|
|
break
|
|
;;
|
|
*)
|
|
echo
|
|
say as warning "Invalid selection, type an option from 1 to 2"
|
|
;;
|
|
esac
|
|
done
|
|
echo
|
|
# Disk shredding function
|
|
function destroy_disk
|
|
{
|
|
say as title "Shredding ${1}"
|
|
dd if=/dev/${shred} of=${1} bs=4M status=progress
|
|
echo 3 >/proc/sys/vm/drop_caches
|
|
say as success "${1} shredded"
|
|
echo
|
|
}
|
|
break
|
|
;;
|
|
[nN])
|
|
say in yellow "Disk shredding skipped"
|
|
echo
|
|
|
|
# Empty disk shredding function
|
|
function destroy_disk
|
|
{
|
|
true
|
|
}
|
|
break
|
|
;;
|
|
*) say as warning 'Not a valid answer, type "y" or "n"' ;;
|
|
esac
|
|
done
|
|
|
|
say as heading "Shredding drives"
|
|
cat <<- warning
|
|
$(tput setab 1)#############################################################$(tput sgr0)
|
|
$(tput setab 1)#############################################################$(tput sgr0)
|
|
$(tput setab 1) $(tput sgr0)
|
|
$(tput setab 1) $(tput blink)!!!WARNING!!! $(tput sgr0)
|
|
$(tput setab 1) !!!ALL DATA WILL BE WIPED FROM THE DRIVE!!! $(tput sgr0)
|
|
$(tput setab 1) $(tput sgr0)
|
|
$(tput setab 1)#############################################################$(tput sgr0)
|
|
$(tput setab 1)#############################################################$(tput sgr0)
|
|
## This will overwrite the drive by one pass
|
|
## Confirm by typing "Y" to proceed
|
|
## Anything other than capital Y will abort the installation
|
|
warning
|
|
ask for begin_install in press ":"
|
|
|
|
if ! [[ ${begin_install} = Y ]]
|
|
then
|
|
echo
|
|
say as warning in bold 'Aborting installation
|
|
Installer script stopped'
|
|
echo
|
|
exit 1
|
|
fi
|
|
echo
|
|
|
|
# Unmount if mounted
|
|
until ! mount | grep -q /mnt/boot
|
|
do
|
|
umount /mnt/boot
|
|
done
|
|
|
|
# Export zpool
|
|
zpool export -a
|
|
|
|
# Clean slate
|
|
for drive in ${installation_disks[@]}
|
|
do
|
|
destroy_disk ${drive} &
|
|
done
|
|
wait
|
|
for drive in ${installation_disks[@]}
|
|
do
|
|
blkdiscard --quiet --force --secure ${drive} >/dev/null 2>&1
|
|
wipefs --quiet --force --all ${drive}
|
|
done
|
|
|
|
# Partition each drive
|
|
say as heading "Partitioning system drive"
|
|
for drive in ${installation_disks[@]}
|
|
do
|
|
parted --script --align=optimal ${drive} \
|
|
mklabel gpt \
|
|
mkpart boot 1MiB 300MiB \
|
|
mkpart zroot 300MiB 100% \
|
|
set 1 esp on
|
|
partprobe ${drive}
|
|
done
|
|
say as success "Partitioning completed!"
|
|
echo
|
|
|
|
# Format zfs
|
|
say as heading "Formatting zfs partitions"
|
|
until printf '%s' "${lukspass}" |\
|
|
zpool create -f zroot \
|
|
-o ashift=12 \
|
|
-o autotrim=on \
|
|
-R /mnt \
|
|
-O acltype=posixacl \
|
|
-O canmount=off \
|
|
-O compression=zstd \
|
|
-O dnodesize=auto \
|
|
-O normalization=formD \
|
|
-O atime=off \
|
|
-O xattr=sa \
|
|
-O mountpoint=none \
|
|
-O encryption=aes-256-gcm \
|
|
-O keyformat=passphrase \
|
|
-O keylocation=prompt \
|
|
${zrootlayout[@]} \
|
|
>/dev/null
|
|
do
|
|
sleep 1
|
|
done
|
|
|
|
# Mount datasets
|
|
zfs create -o mountpoint=/ -o canmount=noauto zroot/ROOT
|
|
zfs create -o mountpoint=/.boot zroot/BOOT
|
|
zfs create -o mountpoint=/home zroot/HOME
|
|
zfs create -o mountpoint=/etc/letsencrypt zroot/HTTPS
|
|
zfs create -o mountpoint=/opt/local zroot/LOCAL
|
|
zfs create -o mountpoint=/var/lib/libvirt/images -o recordsize=64K zroot/QEMU
|
|
zfs create -o mountpoint=/var/lib/docker zroot/DOCKER
|
|
zfs create -o mountpoint=/var/cache/pacman/pkg zroot/PKG
|
|
zfs create -o mountpoint=/var/log zroot/LOG
|
|
zfs create -o mountpoint=/var/tmp zroot/TMP
|
|
zpool export zroot
|
|
zpool import -R /mnt zroot -N -d ${installation_disks}-part2
|
|
printf '%s' "${lukspass}" | zfs load-key zroot
|
|
zfs mount zroot/ROOT
|
|
zfs mount -a
|
|
|
|
# Format efi partition
|
|
for drive in ${installation_disks[@]}
|
|
do
|
|
yes | mkfs.fat -F 32 ${drive}-part1
|
|
done
|
|
echo
|
|
|
|
# Mount efi partition
|
|
mount --mkdir ${installation_disks}-part1 /mnt/boot/
|
|
|
|
# # Confirm mounts
|
|
findmnt '/mnt' | grep -q -w 'zroot/ROOT' || die 'Root partition has not been mounted properly'
|
|
findmnt '/mnt/boot' | grep -q -w '/mnt/boot' || die 'Boot partition has not been mounted properly'
|
|
chmod 1777 /mnt/var/tmp/
|
|
|
|
# Delete unneccessary EFI files
|
|
rm -f /sys/firmware/efi/efivars/dump-*
|
|
|
|
# Default packages
|
|
archpkgs="smartmontools \
|
|
${linux_firmware[@]} \
|
|
${ucode} \
|
|
opendoas openssh efibootmgr fakeroot \
|
|
vim pacman-contrib bash-completion parted \
|
|
mkinitcpio-netconf mkinitcpio-tinyssh tinyssh \
|
|
reflector rsync sshfs \
|
|
archiso \
|
|
fail2ban \
|
|
nginx-mainline certbot certbot-nginx apache \
|
|
nextcloud-client gnome-keyring \
|
|
qemu-desktop virt-manager edk2-ovmf \
|
|
iptables-nft dnsmasq dmidecode vde2 bridge-utils openbsd-netcat \
|
|
docker docker-compose docker-buildx \
|
|
pipewire-jack ttf-dejavu ttf-hack \
|
|
sway seatd swaybg bemenu bemenu-wayland foot kate dolphin konsole kompare breeze-icons kde-cli-tools \
|
|
wayvnc polkit-kde-agent \
|
|
firefox firefox-decentraleyes firefox-ublock-origin \
|
|
shotwell mpv ffmpegthumbs \
|
|
git less pv" # i3status ark okular inetutils
|
|
|
|
# Add CacheServer if inside a VM
|
|
if [ ${cacheserver} ]
|
|
then
|
|
grep -q "^CacheServer" /etc/pacman.conf ||\
|
|
sed "/^\[core\]$\|^\[extra\]$\|^\[myvezfs\]$/a CacheServer = ${cacheserver}" -i /etc/pacman.conf
|
|
fi
|
|
|
|
# Temporarily disable mkinitcpio
|
|
ln -s -f /dev/null /etc/pacman.d/hooks/90-mkinitcpio-install.hook
|
|
|
|
# Install archzfs
|
|
say as heading "Installing Arch Linux base packages"
|
|
repeat pacstrap -K /mnt --ask 4 \
|
|
linux zfs-linux zfs-utils \
|
|
base mkinitcpio \
|
|
${archpkgs}
|
|
|
|
# Configure zfs for pacman
|
|
sed -e '/ParallelDownloads/c ParallelDownloads = 10' \
|
|
-e '/Color/c Color' \
|
|
-e '/\[core\]/i [myvezfs]\
|
|
Server = https://mirror.myvelabs.com/repo/$repo\
|
|
Server = https://repo.myvelabs.com/$repo\n' \
|
|
-i /mnt/etc/pacman.conf
|
|
|
|
# Add CacheServer if inside a VM
|
|
if [ ${cacheserver} ]
|
|
then
|
|
sed "/^\[core\]$\|^\[extra\]$\|^\[myvezfs\]$/a CacheServer = ${cacheserver}" -i /mnt/etc/pacman.conf
|
|
fi
|
|
|
|
# Add archzfs keys
|
|
arch-chroot /mnt pacman-key -r ${zfsgpgkey} >/dev/null 2>&1
|
|
arch-chroot /mnt pacman-key --lsign-key ${zfsgpgkey} >/dev/null 2>&1
|
|
|
|
# Manually configure mkinitcpio
|
|
sed -e "s|%PKGBASE%|linux|g" \
|
|
-e "s/^fallback/#&/g" \
|
|
-e "s/ 'fallback'//" \
|
|
/mnt/usr/share/mkinitcpio/hook.preset >/mnt/etc/mkinitcpio.d/linux.preset
|
|
rsync -a /mnt/usr/lib/modules/*/vmlinuz /mnt/boot/vmlinuz-linux
|
|
|
|
# Create custom system dirs
|
|
mkdir -p /mnt/etc/{zfs/zfs-list.cache,pacman.d/hooks,nginx/sites-{available,enabled}}/ \
|
|
/mnt/{zfs/{bin,snapshots},etc/libvirt/hooks/qemu.d}/ \
|
|
/mnt/etc/systemd/{logind,journald}.conf.d/ \
|
|
/mnt/opt/local/{bin,systemd,hooks}/ \
|
|
/mnt/srv/{repo,sftp}/
|
|
|
|
# Configure zfs for mkinitcpio
|
|
sed -e '/^HOOKS/ s/filesystems/netconf tinyssh zfsencryptssh zfs &/' \
|
|
-e '/^HOOKS/ s/ fsck//' \
|
|
-e '/^HOOKS/ s/systemd/udev/' \
|
|
/mnt/etc/mkinitcpio.conf >/mnt/etc/mkinitcpio.conf.d/zz-hooks.conf
|
|
cat >/mnt/etc/mkinitcpio.conf.d/zz-binaries.conf <<- 'binaries'
|
|
BINARIES+=(/usr/bin/zfs)
|
|
binaries
|
|
cat >/mnt/etc/mkinitcpio.conf.d/zz-modules.conf <<- 'modules'
|
|
MODULES_DECOMPRESS="yes"
|
|
MODULES+=(zfs)
|
|
modules
|
|
|
|
# ZFS properties
|
|
zpool set bootfs=zroot/ROOT zroot
|
|
zpool set cachefile=/etc/zfs/zpool.cache zroot
|
|
rsync -a /etc/zfs/zpool.cache /mnt/etc/zfs/zpool.cache
|
|
touch /mnt/etc/zfs/zfs-list.cache/zroot
|
|
|
|
# Networking
|
|
ln -s -f /run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf
|
|
cat >/mnt/etc/systemd/network/20-${iface}.link <<- link
|
|
[Match]
|
|
PermanentMACAddress=${macaddress}
|
|
|
|
[Link]
|
|
Description=Rename primary network interface to eth0
|
|
Name=eth0
|
|
link
|
|
if [ ${primaryip} ]
|
|
then
|
|
# Static IP
|
|
rsync -a /etc/systemd/network/20-ethernet.network /mnt/etc/systemd/network/zz-fallback.network
|
|
cat >/mnt/etc/systemd/network/20-${iface}.network <<- network
|
|
[Match]
|
|
Name=eth0
|
|
|
|
[Network]
|
|
Description=Static network
|
|
DHCP=no
|
|
|
|
# IPv4
|
|
Address=${primaryip}/${netmask4:-24}
|
|
Gateway=${gateway}
|
|
network
|
|
else
|
|
# DHCP
|
|
rsync -a /etc/systemd/network/20-ethernet.network /mnt/etc/systemd/network/20-${iface}.network
|
|
fi
|
|
|
|
# SSH and Dropbear
|
|
cat sshkeys >/mnt/etc/tinyssh/root_key
|
|
sed -i "s/ 22 / ${port:-22} /" /mnt/usr/lib/initcpio/hooks/tinyssh
|
|
|
|
# Chroot into new root
|
|
arch-chroot /mnt /usr/bin/bash <<- "CHROOT"
|
|
|
|
# Global bashrc
|
|
tee -a /etc/skel/.bashrc >/dev/null <<'bashglobal'
|
|
|
|
# Environment additions
|
|
export PATH=${PATH}:/opt/local/bin:/zfs/bin
|
|
export SUDO_PROMPT=$'\a'"$(tput rev)[sudo] password for %p:$(tput sgr0)"' '
|
|
alias sudoedit='sudo vim'
|
|
|
|
# Colored prompts
|
|
alias diff='diff --color=auto'
|
|
alias ip='ip -color=auto'
|
|
export LESS='-R --use-color -Dd+r$Du+b$'
|
|
|
|
# Source bash functions
|
|
for file in $(find ~/.local/functions -type f)
|
|
do
|
|
. ${file}
|
|
done
|
|
|
|
# Auto cd into directory
|
|
shopt -s autocd
|
|
|
|
# Enable tab complete for sudo
|
|
complete -c -f doas
|
|
complete -F _command doas
|
|
complete -c -f sudo
|
|
complete -F _command sudo
|
|
|
|
# Ignore duplicate and whitespace history entries
|
|
export HISTCONTROL=ignoreboth
|
|
|
|
#
|
|
# ~/.bash_aliases
|
|
#
|
|
|
|
# ZFS/btrfs
|
|
alias zpool-monitor='watch -d -n 1 zpool status -c smart'
|
|
# alias zfs='sudo zfs'
|
|
# alias zpool='sudo zpool'
|
|
|
|
# Shutdown reboot
|
|
alias poweroff='sudo poweroff'
|
|
alias reboot='sudo reboot'
|
|
|
|
# Clear bash history
|
|
alias clearhistory='rm ${HISTFILE}; history -c -w'
|
|
|
|
# Miscellanous pacman
|
|
alias orphans='pacman -Qdtq && pacman -Qdtq | sudo pacman -Rns -'
|
|
alias dbunlock='sudo rm -f /var/lib/pacman/db.lck && sudo pacman -Syyu'
|
|
|
|
# Rsync
|
|
alias rsync='rsync -v -h --progress --info=progress2 --partial --append-verify'
|
|
|
|
#
|
|
# ~/.bash_functions
|
|
#
|
|
|
|
# Remove login lock (for successive failed login attempts)
|
|
function unlock-user
|
|
{
|
|
faillock --user ${1:-user} --reset
|
|
}
|
|
|
|
# Pacman tools
|
|
function installer
|
|
{
|
|
sudo pacman --sync ${@}
|
|
echo
|
|
}
|
|
|
|
function uninstall
|
|
{
|
|
sudo pacman --remove --cascade --nosave --recursive ${@}
|
|
echo
|
|
}
|
|
|
|
function syur
|
|
{
|
|
/opt/local/bin/syu &&
|
|
reboot
|
|
}
|
|
|
|
function syup
|
|
{
|
|
/opt/local/bin/syu &&
|
|
poweroff
|
|
}
|
|
|
|
# Update bash
|
|
function update-bash
|
|
{
|
|
vim ~/.bashrc &&
|
|
source ~/.bashrc
|
|
}
|
|
|
|
# Restart swayvnc
|
|
function restart-swayvnc
|
|
{
|
|
systemctl --quiet --user daemon-reload
|
|
systemctl --quiet --user restart sway.service wayvnc.service
|
|
systemctl --user status sway.service wayvnc.service
|
|
}
|
|
bashglobal
|
|
|
|
# Root bashrc
|
|
rsync -a /etc/skel/.bashrc ~/
|
|
mkdir -p ~/.local/functions/
|
|
cat > ~/.local/functions/root <<- 'rootbashrc'
|
|
#!/usr/bin/env bash
|
|
# Root shell color
|
|
PS1="$(tput setaf 1)[\u@\h \W \$?]\$$(tput sgr0) "
|
|
|
|
# Colored prompts
|
|
alias ll='ls --color=auto -l -a -h'
|
|
alias egrep='egrep --color=auto'
|
|
alias fgrep='fgrep --color=auto'
|
|
|
|
# Disable history
|
|
unset HISTFILE
|
|
rm -f ${HISTFILE}
|
|
history -c -w
|
|
rootbashrc
|
|
|
|
# Configure ssh
|
|
gensshid
|
|
ssh-keygen -A >/dev/null
|
|
cat >/etc/ssh/sshd_config.d/zz-homelab.conf <<- sshd
|
|
Port ${port:-22}
|
|
PermitRootLogin no
|
|
PasswordAuthentication no
|
|
AuthenticationMethods publickey
|
|
sshd
|
|
|
|
# tinyssh
|
|
rm -r -f /etc/tinyssh/sshkeydir/
|
|
tinyssh-convert /etc/tinyssh/sshkeydir/ </etc/ssh/ssh_host_ed25519_key
|
|
|
|
# ZFS setup
|
|
zpool set cachefile=/etc/zfs/zpool.cache zroot
|
|
zgenhostid $(hostid)
|
|
|
|
# Trim zroot monthly
|
|
cat >/etc/systemd/system/zfs-trim@.timer <<'TRIM'
|
|
[Unit]
|
|
Description=Monthly zpool trim on %i
|
|
|
|
[Timer]
|
|
OnCalendar=monthly
|
|
AccuracySec=1h
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
TRIM
|
|
|
|
cat >/etc/systemd/system/zfs-trim@.service <<'TRIM'
|
|
[Unit]
|
|
Description=zpool trim on %i
|
|
Documentation=man:zpool-trim(8)
|
|
Requires=zfs.target
|
|
After=zfs.target
|
|
ConditionACPower=true
|
|
ConditionPathIsDirectory=/sys/module/zfs
|
|
|
|
[Service]
|
|
Nice=19
|
|
IOSchedulingClass=idle
|
|
KillSignal=SIGINT
|
|
ExecStart=/bin/sh -c '\
|
|
if /usr/bin/zpool status %i | grep "trimming"; then\
|
|
exec /usr/bin/zpool wait -t trim %i;\
|
|
else exec /usr/bin/zpool trim -w %i; fi'
|
|
ExecStop=-/bin/sh -c '/usr/bin/zpool trim -s %i 2>/dev/null || true'
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
TRIM
|
|
|
|
# Scrub zroot monthly
|
|
cat >/etc/systemd/system/zfs-scrub@.timer <<'SCRUB'
|
|
[Unit]
|
|
Description=Monthly zpool scrub on %i
|
|
|
|
[Timer]
|
|
OnCalendar=monthly
|
|
AccuracySec=1h
|
|
Persistent=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SCRUB
|
|
|
|
cat >/etc/systemd/system/zfs-scrub@.service <<'SCRUB'
|
|
[Unit]
|
|
Description=zpool scrub on %i
|
|
|
|
[Service]
|
|
Nice=19
|
|
IOSchedulingClass=idle
|
|
KillSignal=SIGINT
|
|
ExecStart=/usr/bin/zpool scrub %i
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SCRUB
|
|
echo
|
|
|
|
# mkinitcpio
|
|
say as heading "Regenerating cpio image"
|
|
mkinitcpio -P
|
|
echo
|
|
|
|
# Locale
|
|
sed -i '/#en_US.UTF-8 UTF-8/ s/#//' /etc/locale.gen
|
|
locale-gen >/dev/null
|
|
echo 'LANG=en_US.UTF-8' >>/etc/locale.conf
|
|
say as heading "Locale configured"
|
|
|
|
# Time zone
|
|
if ls -l /dev/disk/* | grep -q 'VBOX\|virtio\|QEMU'
|
|
then
|
|
ln -s -f $(find /usr/share/zoneinfo/ | shuf -n 1) /etc/localtime
|
|
else
|
|
ln -s -f /usr/share/zoneinfo/UTC /etc/localtime
|
|
fi
|
|
hwclock --systohc --utc
|
|
say as heading "Time zone configured"
|
|
|
|
# Hostname
|
|
echo ${hostname} >/etc/hostname
|
|
cat >>/etc/hosts <<- host
|
|
127.0.0.1 localhost
|
|
127.0.1.1 ${hostname}
|
|
host
|
|
say as heading "Hostname configured"
|
|
|
|
# User
|
|
useradd --create-home --gid users --groups wheel,libvirt,seat,docker --shell /usr/bin/bash user || die "User account creation has failed"
|
|
printf '%s\n' "${userpass}" "${userpass}" | passwd user >/dev/null 2>&1
|
|
unset userpass userpass2
|
|
|
|
# Disable root account
|
|
passwd -l root >/dev/null 2>&1
|
|
|
|
# Sudoers
|
|
# install -m 0440 /dev/stdin /etc/sudoers.d/01-DEFAULTS <<- 'DEFAULTS'
|
|
# Defaults passwd_timeout=0
|
|
# Defaults timestamp_type=global
|
|
# Defaults insults
|
|
# DEFAULTS
|
|
#
|
|
# install -m 0440 /dev/stdin /etc/sudoers.d/02-COMMANDS <<- 'COMMANDS'
|
|
# Cmnd_Alias POWER = /usr/bin/poweroff, /usr/bin/reboot
|
|
# Cmnd_Alias ZFS = /usr/bin/zfs, /usr/bin/zpool
|
|
# Cmnd_Alias QEMU = /usr/bin/virsh, /usr/bin/qemu-system-x86_64, /usr/bin/virt-install
|
|
# Cmnd_Alias FAIL2BAN = /usr/bin/fail2ban-client
|
|
# Cmnd_Alias PACMAN = /usr/bin/pacman
|
|
# Cmnd_Alias IPTABLES = /usr/bin/iptables, /usr/bin/iptables-save
|
|
# Cmnd_Alias MISC = /usr/bin/rsync
|
|
# COMMANDS
|
|
#
|
|
# install -m 0440 /dev/stdin /etc/sudoers.d/03-WHEEL <<- 'WHEEL'
|
|
# %wheel ALL=(ALL:ALL) ALL
|
|
# %wheel ALL=(ALL:ALL) NOPASSWD: POWER, ZFS, QEMU, FAIL2BAN, PACMAN, IPTABLES, MISC
|
|
# WHEEL
|
|
#
|
|
# install -m 0440 /dev/stdin /etc/sudoers.d/zz-NOPASSWD <<- 'NOPASSWD'
|
|
# Defaults:user !authenticate
|
|
# NOPASSWD
|
|
# Doas
|
|
install -m 0440 /dev/stdin /etc/doas.conf <<- 'doas'
|
|
permit nopass :root
|
|
permit nopass :wheel
|
|
doas
|
|
install /dev/stdin /usr/local/bin/sudo <<- 'doas'
|
|
#!/usr/bin/env bash
|
|
exec doas "${@/--preserve-env*/}"
|
|
doas
|
|
|
|
say as heading "Configured superuser and user"
|
|
|
|
# ZFS files
|
|
touch /zfs/snapshots/syu
|
|
chown user:users /zfs/snapshots/syu
|
|
|
|
# Sysctl custom settings
|
|
cat >/etc/sysctl.d/zz-sysctl.conf <<- SYSCTL
|
|
net.core.netdev_max_backlog = 16384
|
|
net.core.somaxconn = 8192
|
|
net.core.rmem_default = 1048576
|
|
net.core.rmem_max = 16777216
|
|
net.core.wmem_default = 1048576
|
|
net.core.wmem_max = 16777216
|
|
net.core.optmem_max = 65536
|
|
net.ipv4.tcp_rmem = 4096 1048576 2097152
|
|
net.ipv4.tcp_wmem = 4096 65536 16777216
|
|
net.ipv4.udp_rmem_min = 8192
|
|
net.ipv4.udp_wmem_min = 8192
|
|
net.ipv4.tcp_fastopen = 3
|
|
net.ipv4.tcp_timestamps = 0
|
|
net.core.default_qdisc = cake
|
|
net.ipv4.tcp_congestion_control = bbr
|
|
|
|
# Swapiness
|
|
vm.swappiness=10
|
|
vm.vfs_cache_pressure=50
|
|
SYSCTL
|
|
|
|
# iptables
|
|
cat >/etc/iptables/userinput.rules <<- IPTABLES
|
|
|
|
## User input
|
|
# Pacman cache server
|
|
-A INPUT -s 192.168.0.0/16 -p tcp -m tcp --dport 9090 -j ACCEPT -m comment --comment "Pacman cache server"
|
|
|
|
# Open ports
|
|
-A INPUT -p tcp -m tcp --dport ${port:-22} -j ACCEPT -m comment --comment "SSH Port"
|
|
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT -m comment --comment "HTTP Port"
|
|
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT -m comment --comment "HTTPS Port"
|
|
-A INPUT -p udp -m udp --dport 443 -j ACCEPT -m comment --comment "HTTP3 Port"
|
|
|
|
## Simple Firewall
|
|
IPTABLES
|
|
sed "/OUTPUT ACCEPT/r /etc/iptables/userinput.rules" /etc/iptables/simple_firewall.rules >/etc/iptables/iptables.rules
|
|
|
|
# zram
|
|
echo 'zram' >/etc/modules-load.d/zram.conf
|
|
echo 'options zram num_devices=1' >/etc/modprobe.d/zram.conf
|
|
echo 'KERNEL=="zram0", ATTR{comp_algorithm}="lz4", ATTR{disksize}="512M" RUN="/usr/bin/mkswap /dev/zram0", TAG+="systemd"' >/etc/udev/rules.d/99-zram.rules
|
|
|
|
# Global ssh config
|
|
cat >/etc/ssh/ssh_config.d/zz-homelab.conf <<- sshconfig
|
|
# Preferred ciphers
|
|
Ciphers aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
|
|
|
|
# Only use ipv4
|
|
AddressFamily inet
|
|
|
|
# Multiplex
|
|
ControlMaster auto
|
|
ControlPath ~/.ssh/sockets/%r@%h-%p
|
|
ControlPersist 10m
|
|
|
|
# Ease up on local area network devices
|
|
Host 192.168.* *.kvm *.local
|
|
StrictHostKeyChecking no
|
|
UserKnownHostsFile=/dev/null
|
|
LogLevel Error
|
|
sshconfig
|
|
|
|
# Paccache hook
|
|
cat >/etc/pacman.d/hooks/zz-paccache.hook <<- PACCACHE
|
|
[Trigger]
|
|
Operation = Upgrade
|
|
Operation = Install
|
|
Operation = Remove
|
|
Type = Package
|
|
Target = *
|
|
|
|
[Action]
|
|
Description = Cleaning pacman cache...
|
|
When = PostTransaction
|
|
Exec = /usr/bin/paccache --remove
|
|
PACCACHE
|
|
|
|
# Locale.gen.pacnew hook
|
|
install /dev/stdin /opt/local/hooks/localegen <<hook
|
|
#!/usr/bin/env bash
|
|
if [ -f /etc/locale.gen.pacnew ]
|
|
then
|
|
sed '/#en_US.UTF-8 UTF-8/ s/#//' /etc/locale.gen.pacnew >/etc/locale.gen
|
|
rm /etc/locale.gen.pacnew
|
|
locale-gen >/dev/null
|
|
fi
|
|
hook
|
|
# locale.gen.pacnew hook
|
|
cat >/etc/pacman.d/hooks/100-localegen.hook <<localegen
|
|
[Trigger]
|
|
Operation = Install
|
|
Operation = Upgrade
|
|
Type = Package
|
|
Target = glibc
|
|
|
|
[Action]
|
|
Description = Fixing locale.gen
|
|
When = PostTransaction
|
|
Exec = /opt/local/hooks/localegen
|
|
localegen
|
|
|
|
# Environment
|
|
tee -a /etc/environment >/dev/null <<environment
|
|
EDITOR=vim
|
|
SUDO_EDITOR=vim
|
|
QT_QPA_PLATFORM=wayland
|
|
DOCKER_BUILDKIT=1
|
|
COMPOSE_PARALLEL_LIMIT=-1
|
|
environment
|
|
|
|
# Sway user service
|
|
cat >/etc/systemd/user/sway.service <<- 'swayvnc'
|
|
[Unit]
|
|
Description=Sway
|
|
After=network.target systemd-user-sessions.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=WLR_BACKENDS=headless
|
|
Environment=WLR_LIBINPUT_NO_DEVICES=1
|
|
ExecStart=/usr/bin/sway
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
swayvnc
|
|
|
|
# WayVNC user service
|
|
cat >/etc/systemd/user/wayvnc.service <<- 'swayvnc'
|
|
[Unit]
|
|
Description=WayVNC
|
|
Requires=sway.service
|
|
After=sway.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=WAYLAND_DISPLAY=wayland-1
|
|
ExecStart=/usr/bin/wayvnc --verbose localhost 5900
|
|
ExecStartPost=/usr/bin/bash -c "exec /usr/lib/polkit-kde-authentication-agent-1"
|
|
Restart=on-failure
|
|
RestartSec=2
|
|
TimeoutStopSec=10
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
swayvnc
|
|
|
|
# Switch into new user
|
|
su user <<- "CHANGEUSER"
|
|
mkdir -p ~/.local/{bin,functions}/ \
|
|
~/.config/{sway/config.d,foot}/ \
|
|
~/.config/menus/
|
|
|
|
# # i3status
|
|
# mkdir ~/.config/i3status/
|
|
# cp /etc/i3status.conf ~/.config/i3status/config
|
|
|
|
# Identify sway conf locations
|
|
cat > ~/.config/sway/config <<- 'config'
|
|
include /etc/sway/config.d/*
|
|
include ~/.config/sway/config.d/*
|
|
config
|
|
|
|
# sway config
|
|
sed -e 's/mod Mod4/mod Mod1/' \
|
|
-e 's/term foot/&client/' \
|
|
-e '/set $menu/c set $menu bemenu-run -p "" --no-overlap --tb "#285577" --hb "#285577" --tf "#eeeeee" --hf "#eeeeee" --nf "#bbbbbb"' \
|
|
-e 's/position top/position bottom/' \
|
|
/etc/sway/config > ~/.config/sway/config.d/00-config
|
|
|
|
cat > ~/.config/sway/config.d/zz-wayvnc <<- 'swayvnc'
|
|
# WayVNC
|
|
output HEADLESS-1 {
|
|
pos 0,0
|
|
mode 1920x1080@60Hz
|
|
scale 1.25
|
|
}
|
|
swayvnc
|
|
|
|
cat > ~/.config/sway/config.d/zz-sway <<- 'sway'
|
|
# Disable xwayland
|
|
xwayland disable
|
|
|
|
# Start foot terminal server
|
|
exec foot --server
|
|
|
|
# Floating windows
|
|
for_window [window_role="About"] floating enable
|
|
for_window [window_role="Organizer"] floating enable
|
|
for_window [window_role="Preferences"] floating enable
|
|
for_window [window_role="bubble"] floating enable
|
|
for_window [window_role="page-info"] floating enable
|
|
for_window [window_role="pop-up"] floating enable
|
|
for_window [window_role="task_dialog"] floating enable
|
|
for_window [window_role="toolbox"] floating enable
|
|
for_window [window_role="webconsole"] floating enable
|
|
for_window [window_type="dialog"] floating enable
|
|
for_window [window_type="menu"] floating enable
|
|
|
|
# Solid black background
|
|
output HEADLESS-1 bg #000000 solid_color
|
|
|
|
# Mouse and keyboard defaults
|
|
input type:keyboard xkb_numlock enabled
|
|
sway
|
|
|
|
# Nextcloud
|
|
cat > ~/.config/sway/config.d/zz-nextcloud <<- 'nextcloud'
|
|
# Nextcloud
|
|
exec nextcloud
|
|
for_window [title="Nextcloud Settings"] floating enable
|
|
nextcloud
|
|
|
|
# Foot terminal config (/etc/xdg/foot/foot.ini)
|
|
cat > ~/.config/foot/foot.ini <<- 'foot'
|
|
[main]
|
|
term=xterm-256color
|
|
include=/usr/share/foot/themes/kitty
|
|
font=Hack:style=Regular:size=12
|
|
font-bold=Hack:style=Bold:size=12
|
|
font-italic=Hack:style=Italic:size=12
|
|
font-bold-italic=Hack:style=Bold Italic:size=12
|
|
workers=32
|
|
|
|
[scrollback]
|
|
lines=16384
|
|
foot
|
|
|
|
# Dolphin
|
|
curl --fail --silent https://raw.githubusercontent.com/KDE/plasma-workspace/master/menu/desktop/plasma-applications.menu -o ~/.config/menus/applications.menu
|
|
kbuildsycoca6 >/dev/null 2>&1
|
|
cat >> ~/.config/kdeglobals <<- foot
|
|
[General]
|
|
TerminalApplication=footclient
|
|
foot
|
|
|
|
if ls -l /dev/disk/* | grep -q 'VBOX'
|
|
then
|
|
echo 'exec VBoxClient-all' >> ~/.config/sway/config.d/zz-vbox
|
|
fi
|
|
|
|
# Generate SSH identity
|
|
gensshid
|
|
cat /etc/tinyssh/root_key >~/.ssh/authorized_keys
|
|
|
|
cat >> ~/.bashrc <<- 'BASHRC'
|
|
|
|
# Custom shell color
|
|
PS1="$(tput setaf 4)[\u@\h \W \$?]\$$(tput sgr0) "
|
|
|
|
# Colored prompts
|
|
alias ll='ls --color=auto -l -a -h'
|
|
alias egrep='egrep --color=auto'
|
|
alias fgrep='fgrep --color=auto'
|
|
BASHRC
|
|
|
|
# Startup script
|
|
cat > ~/.local/functions/init <<- 'init'
|
|
#!/usr/bin/env bash
|
|
if [ -f ~/.local/bin/startup ]
|
|
then
|
|
echo -e "\n\e[1;31mInit script hasn't been run yet, executing script before proceeding...\e[0m\n"
|
|
echo "Choose a subnet for libvirt"
|
|
until grep -qE -o '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<<${subnet}
|
|
do
|
|
read -r -p "Subnet: " subnet
|
|
echo
|
|
if ! grep -qE -o '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' <<<${subnet}
|
|
then
|
|
echo "Invalid address format, try again"
|
|
echo
|
|
fi
|
|
done
|
|
~/.local/bin/startup ${subnet} || exit 1
|
|
fi
|
|
init
|
|
|
|
install /dev/stdin ~/.local/bin/startup <<'EOF'
|
|
#!/usr/bin/env bash
|
|
# eg., 192.168.222
|
|
subnet=${@}
|
|
|
|
## Tput codes
|
|
reset=$(tput sgr0)
|
|
red=${reset}$(tput setaf 1)
|
|
yellow=${reset}$(tput setaf 3)
|
|
|
|
# Exit function
|
|
trap '[ "${?}" -ne 77 ] || exit 77' ERR
|
|
function die
|
|
{
|
|
cat <<- abort
|
|
${red}
|
|
Error encountered for the following reason:
|
|
${yellow}
|
|
"${1}"
|
|
${red}
|
|
Script aborted...
|
|
${reset}
|
|
abort
|
|
exit 77
|
|
}
|
|
|
|
# Subnet check
|
|
[ ${subnet} ] || die 'Define ${subnet} before proceeding (eg, 192.168.222)'
|
|
|
|
# Internet connection check
|
|
if nc -z -w 1 archlinux.org 443 >/dev/null 2>&1 || nc -z -w 1 google.com 443 >/dev/null 2>&1
|
|
then
|
|
sudo timedatectl set-ntp true
|
|
else
|
|
die 'No internet connectivity detected, plug in an ethernet cable and try again'
|
|
fi
|
|
|
|
# SwayVNC services
|
|
systemctl --user --quiet enable --now sway.service wayvnc.service
|
|
|
|
# Start spice agent
|
|
if ls -l /dev/disk/* | grep -q 'virtio\|QEMU'
|
|
then
|
|
systemctl --user --quiet enable --now spice-vdagent.service
|
|
fi
|
|
|
|
# Services
|
|
sudo systemctl --quiet restart systemd-journald.service
|
|
sudo systemctl --quiet disable zfs-mount.service
|
|
sudo systemctl --quiet enable --now zfs-zed.service
|
|
sudo zfs set canmount=off zroot/TMP
|
|
sudo zfs set canmount=on zroot/TMP
|
|
|
|
# QEMU
|
|
# Macvtap
|
|
cat >/tmp/macvtap.xml <<- macvtap
|
|
<network>
|
|
<name>macvtap</name>
|
|
<forward mode='bridge'>
|
|
<interface dev='$(ip r | grep 'default via' | awk '{print $5}')'/>
|
|
</forward>
|
|
</network>
|
|
macvtap
|
|
sudo virsh -q net-define /tmp/macvtap.xml
|
|
sudo virsh -q net-autostart macvtap
|
|
sudo virsh -q net-start macvtap
|
|
|
|
# Isolated
|
|
cat >/tmp/isolated.xml <<- isolated
|
|
<network>
|
|
<name>isolated</name>
|
|
<ip address='${subnet}.1' netmask='255.255.255.0'>
|
|
<dhcp>
|
|
<range start='${subnet}.2' end='${subnet}.254' />
|
|
</dhcp>
|
|
</ip>
|
|
</network>
|
|
isolated
|
|
sudo virsh -q net-define /tmp/isolated.xml
|
|
sudo virsh -q net-autostart isolated
|
|
sudo virsh -q net-start isolated
|
|
|
|
# Cleanup
|
|
rm -f /tmp/*.xml
|
|
|
|
# Sway VNC service
|
|
loginctl enable-linger
|
|
|
|
if [ -f ~/.config/sway/config ]
|
|
then
|
|
rm -f ${0} ~/.local/functions/init
|
|
# sudo mv /etc/sudoers.d/zz-NOPASSWD /etc/sudoers.d/.zz-NOPASSWD
|
|
echo -e 'Supplementary installer completed, reboot one last time\e[0m\n'
|
|
else
|
|
echo -e '\n\e[31mSway configuration file not found'
|
|
echo -e 'Try again'
|
|
echo -e 'Aborting installer...\e[0m\n'
|
|
exit 1
|
|
fi
|
|
EOF
|
|
CHANGEUSER
|
|
|
|
# fail2ban
|
|
install /dev/stdin /opt/local/bin/fail2ban-jails <<- 'jails'
|
|
#!/usr/bin/env bash
|
|
JAILS=$(sudo fail2ban-client status | grep "Jail list" | sed -E 's/^[^:]+:[ \t]+//' | sed 's/,//g')
|
|
for JAIL in ${JAILS}
|
|
do
|
|
sudo fail2ban-client status ${JAIL}
|
|
done
|
|
jails
|
|
cat >/etc/fail2ban/jail.d/sshd.conf <<- 'sshd'
|
|
[sshd]
|
|
enabled = true
|
|
filter = sshd
|
|
backend = systemd
|
|
maxretry = 5
|
|
findtime = 1d
|
|
bantime = 4w
|
|
ignoreip = 127.0.0.1/8
|
|
sshd
|
|
|
|
# Nginx
|
|
sed '/^http {/a\
|
|
include http_redirect;\
|
|
include sites-enabled/\*.conf;\n\
|
|
types_hash_max_size 4096;\
|
|
server_names_hash_bucket_size 128;\n' \
|
|
-i /etc/nginx/nginx.conf
|
|
cat >/etc/nginx/http_redirect <<- 'redirect'
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
if ($scheme = "http") {
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
}
|
|
redirect
|
|
cat >/etc/nginx/proxy_params <<- 'proxy_params'
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Host $host;
|
|
proxy_set_header X-Forwarded-Port $server_port;
|
|
proxy_set_header X-Forwarded-Scheme $scheme;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Accept-Encoding "";
|
|
proxy_set_header Host $host;
|
|
|
|
proxy_next_upstream error timeout;
|
|
|
|
client_body_buffer_size 512k;
|
|
proxy_read_timeout 86400s;
|
|
client_max_body_size 0;
|
|
|
|
# Websocket
|
|
proxy_http_version 1.1;
|
|
proxy_cache_bypass $http_upgrade;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_params
|
|
cat >/etc/nginx/http_upgrade <<- 'http_upgrade'
|
|
# Security
|
|
server_tokens off;
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header Referrer-Policy "no-referrer" always;
|
|
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
|
add_header X-Robots-Tag "noindex, nofollow" always;
|
|
|
|
# http2
|
|
http2 on;
|
|
|
|
# http3 (open port 443/udp to use http3)
|
|
# Add reuseport to ONLY ONE virtual host: listen 443 quic reuseport;
|
|
add_header Alt-Svc 'h3=":443"; ma=86400';
|
|
quic_retry on;
|
|
http3 on;
|
|
|
|
# Certbot defaults
|
|
add_header Strict-Transport-Security "max-age=31536000" always;
|
|
http_upgrade
|
|
|
|
# Nginx hook
|
|
install /dev/stdin /opt/local/hooks/nginx <<- 'hook'
|
|
#!/usr/bin/env bash
|
|
if [ -f /etc/nginx/nginx.conf.pacnew ]
|
|
then
|
|
sed '/^http {/a\
|
|
include http_redirect;\
|
|
include sites-enabled/\*.conf;\n\
|
|
types_hash_max_size 4096;\
|
|
server_names_hash_bucket_size 128;\n' \
|
|
-i /etc/nginx/nginx.conf.pacnew
|
|
mv /etc/nginx/nginx.conf.pacnew /etc/nginx/nginx.conf
|
|
fi
|
|
hook
|
|
# /etc/nginx/nginx.conf.pacnew hook
|
|
cat >/etc/pacman.d/hooks/100-nginx.hook <<- nginx
|
|
[Trigger]
|
|
Operation = Install
|
|
Operation = Upgrade
|
|
Type = Package
|
|
Target = nginx-mainline
|
|
|
|
[Action]
|
|
Description = Fixing nginx.conf
|
|
When = PostTransaction
|
|
Exec = /opt/local/hooks/nginx
|
|
nginx
|
|
|
|
# Pacman cache
|
|
# Nginx conf
|
|
cat >/etc/nginx/sites-available/pacman.conf <<- 'pacman'
|
|
server {
|
|
listen 9090;
|
|
client_max_body_size 0;
|
|
|
|
location / {
|
|
alias /var/cache/pacman/pkg/;
|
|
autoindex on;
|
|
|
|
error_log /var/log/nginx/pacman_error.log;
|
|
access_log /var/log/nginx/pacman_access.log;
|
|
}
|
|
}
|
|
pacman
|
|
ln -s /etc/nginx/sites-available/pacman.conf /etc/nginx/sites-enabled/
|
|
# # Systemd service
|
|
# cat >/etc/systemd/system/local-update-pkg-cache.service <<'service'
|
|
# [Unit]
|
|
# Description=Refresh package cache twice daily
|
|
#
|
|
# [Service]
|
|
# Type=oneshot
|
|
# ExecStart=/usr/bin/bash -c "/usr/bin/pacman -Syw -d --ask 4 $(curl --fail --silent -L https://git.myvelabs.app/lab/archlinux/raw/branch/master/pkg/homelab)"
|
|
# service
|
|
# # Timer
|
|
# cat >/etc/systemd/system/local-update-pkg-cache.timer <<'timer'
|
|
# [Unit]
|
|
# Description=Refresh pacman package cache
|
|
#
|
|
# [Timer]
|
|
# OnCalendar=*-*-* 00/12:00:00
|
|
# RandomizedDelaySec=12h
|
|
# Persistent=true
|
|
#
|
|
# [Install]
|
|
# WantedBy=timers.target
|
|
# timer
|
|
|
|
# Libvirt
|
|
echo 'firewall_backend = "iptables"' >/etc/libvirt/network.conf
|
|
|
|
# Pacman hooks
|
|
# pacman.conf hook
|
|
cat >/etc/pacman.d/hooks/100-pacman.conf.hook <<- pacman
|
|
[Trigger]
|
|
Operation = Install
|
|
Operation = Upgrade
|
|
Type = Package
|
|
Target = pacman
|
|
|
|
[Action]
|
|
Description = Fixing pacman.conf
|
|
When = PostTransaction
|
|
Exec = /opt/local/hooks/pacman.conf
|
|
pacman
|
|
install /dev/stdin /opt/local/hooks/pacman.conf <<- 'hook'
|
|
#!/usr/bin/env bash
|
|
if [ -f /etc/pacman.conf.pacnew ]
|
|
then
|
|
sed -e '/ParallelDownloads/c ParallelDownloads = 10' \
|
|
-e '/Color/c Color' \
|
|
-e '/\[core\]/i [myvezfs]\
|
|
Server = https://mirror.myvelabs.com/repo/$repo\
|
|
Server = https://repo.myvelabs.com/$repo\n' \
|
|
/etc/pacman.conf.pacnew >/etc/pacman.conf
|
|
rm /etc/pacman.conf.pacnew
|
|
fi
|
|
hook
|
|
|
|
# mkinitcpio conf hook
|
|
cat >/etc/pacman.d/hooks/85-mkinitcpio.conf.hook <<- preset
|
|
[Trigger]
|
|
Operation = Install
|
|
Operation = Upgrade
|
|
Type = Package
|
|
Target = mkinitcpio
|
|
|
|
[Action]
|
|
Description = Updating mkinitcpio.conf
|
|
When = PostTransaction
|
|
Exec = /opt/local/hooks/mkinitcpio.conf
|
|
preset
|
|
install /dev/stdin /opt/local/hooks/mkinitcpio.conf <<- 'hook'
|
|
#!/usr/bin/env bash
|
|
# Hooks
|
|
sed -e '/^HOOKS/ s/filesystems/netconf tinyssh zfsencryptssh zfs &/' \
|
|
-e '/^HOOKS/ s/ fsck//' \
|
|
-e '/^HOOKS/ s/systemd/udev/' \
|
|
/etc/mkinitcpio.conf >/etc/mkinitcpio.conf.d/zz-hooks.conf
|
|
|
|
# Linux preset
|
|
sed -e "s|%PKGBASE%|linux|g" \
|
|
-e "s/^fallback/#&/g" \
|
|
-e "s/ 'fallback'//" \
|
|
/usr/share/mkinitcpio/hook.preset >/etc/mkinitcpio.d/linux.preset
|
|
hook
|
|
|
|
# # bash.bashrc.pacnew hook
|
|
# cat >/etc/pacman.d/hooks/100-bash.bashrc.hook <<bashrc
|
|
# [Trigger]
|
|
# Operation = Install
|
|
# Operation = Upgrade
|
|
# Type = Package
|
|
# Target = bash
|
|
#
|
|
# [Action]
|
|
# Description = Fixing global bashrc
|
|
# When = PostTransaction
|
|
# Exec = /opt/local/hooks/bashrc
|
|
# bashrc
|
|
# install /dev/stdin /opt/local/hooks/bashrc <<'hook'
|
|
# #!/usr/bin/env bash
|
|
# if [ -f /etc/bash.bashrc.pacnew ]
|
|
# then
|
|
# tee -a /etc/bash.bashrc.pacnew >/dev/null <<bashglobal
|
|
#
|
|
# . /etc/bash.bashrc.d/global
|
|
# bashglobal
|
|
# mv /etc/bash.bashrc.pacnew /etc/bash.bashrc
|
|
# fi
|
|
# hook
|
|
|
|
# iptables.rules
|
|
cat >/etc/pacman.d/hooks/100-iptables.rules.hook <<- iptables
|
|
[Trigger]
|
|
Operation = Install
|
|
Operation = Upgrade
|
|
Type = Package
|
|
Target = iptables-nft
|
|
|
|
[Action]
|
|
Description = Fixing iptables rules
|
|
When = PostTransaction
|
|
Exec = /opt/local/hooks/iptables.rules
|
|
iptables
|
|
install /dev/stdin /opt/local/hooks/iptables.rules <<- hook
|
|
#!/usr/bin/env bash
|
|
if [ -f /etc/iptables/iptables.rules.pacsave ]
|
|
then
|
|
sed "/OUTPUT ACCEPT/r /etc/iptables/userinput.rules" /etc/iptables/simple_firewall.rules >/etc/iptables/iptables.rules
|
|
rm /etc/iptables/iptables.rules.pacsave
|
|
fi
|
|
hook
|
|
|
|
# tinyssh
|
|
if [ "${port}" ]
|
|
then
|
|
cat >/etc/pacman.d/hooks/100-tinyssh.hook <<- tinyssh
|
|
[Trigger]
|
|
Operation = Install
|
|
Operation = Upgrade
|
|
Type = Package
|
|
Target = mkinitcpio-tinyssh
|
|
|
|
[Action]
|
|
Description = Fixing tinyssh port
|
|
When = PostTransaction
|
|
Exec = /opt/local/hooks/tinyssh
|
|
tinyssh
|
|
install /dev/stdin /opt/local/hooks/tinyssh <<- hook
|
|
#!/usr/bin/env bash
|
|
if [ -f /usr/lib/initcpio/hooks/tinyssh.pacnew ]
|
|
then
|
|
sed -i "s/ 22 / ${port} /" /usr/lib/initcpio/hooks/tinyssh.pacnew
|
|
mv /usr/lib/initcpio/hooks/tinyssh.pacnew /usr/lib/initcpio/hooks/tinyssh
|
|
fi
|
|
hook
|
|
fi
|
|
|
|
# Disable power button
|
|
cat >/etc/systemd/logind.conf.d/zz-disablepowerbutton.conf <<- eof
|
|
[Login]
|
|
HandlePowerKey=ignore
|
|
HandlePowerKeyLongPress=ignore
|
|
PowerKeyIgnoreInhibited=yes
|
|
eof
|
|
|
|
# Persistent journal logging
|
|
cat >/etc/systemd/journald.conf.d/zz-journald.conf <<- eof
|
|
[Journal]
|
|
Storage=persistent
|
|
SystemMaxUse=100M
|
|
eof
|
|
|
|
# Custom pacman update wrapper
|
|
install /dev/stdin /opt/local/bin/syu <<- 'syu'
|
|
#!/usr/bin/env bash
|
|
set -e
|
|
|
|
# Record current time
|
|
echo $(date "+%Y-%m-%d-%H:%M:%S") >/zfs/snapshots/syu
|
|
|
|
# Check for new packages and continue if found
|
|
newpkg=($(checkupdates --nocolor | awk '{print $1}'))
|
|
if [ "${newpkg}" ]
|
|
then
|
|
# Sync pacman dbs
|
|
sudo pacman --ask 4 --sync --refresh >/dev/null
|
|
|
|
# Check zfs-linux kernel dependency
|
|
zfslinux=$(pacman -Si zfs-linux | grep "Depends On" | sed "s|.*linux=||" | awk '{print $1}')
|
|
linux=$(pacman -Si linux | grep "Version" | awk '{print $3}')
|
|
|
|
# Perform update if dependencies are satisfied
|
|
if [ ${zfslinux} = ${linux} ]
|
|
then
|
|
# Update archlinux-keyring first
|
|
if [[ ${newpkg[@]} =~ "archlinux-keyring" ]]
|
|
then
|
|
sudo pacman --ask 4 --sync --needed archlinux-keyring
|
|
echo
|
|
fi
|
|
if sudo pacman --ask 4 --sync --sysupgrade --needed
|
|
then
|
|
echo
|
|
sudo pacdiff
|
|
exit 0
|
|
fi
|
|
else
|
|
# Show update list if unable to perform update
|
|
printf "%s\n" "${newpkg[@]}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
syu
|
|
CHROOT
|
|
|
|
## Pre and post update backup hooks
|
|
# Boot
|
|
cat >/mnt/etc/pacman.d/hooks/55-bootbackup_pre.hook <<- pre
|
|
[Trigger]
|
|
Operation = Upgrade
|
|
Operation = Install
|
|
Operation = Remove
|
|
Type = Path
|
|
Target = usr/lib/modules/*/vmlinuz
|
|
Target = usr/lib/initcpio/*
|
|
Target = usr/lib/firmware/*
|
|
Target = usr/src/*/dkms.conf
|
|
|
|
[Action]
|
|
Depends = rsync
|
|
Description = Backing up pre /boot...
|
|
When = PreTransaction
|
|
Exec = /usr/bin/bash -c 'mount ${installation_disks}-part1; rsync -a --mkpath --delete /boot/ "/.boot/\$(cat /zfs/snapshots/syu)_pre"/'
|
|
AbortOnFail
|
|
pre
|
|
cat >/mnt/etc/pacman.d/hooks/95-bootbackup_post.hook <<- post
|
|
[Trigger]
|
|
Operation = Upgrade
|
|
Operation = Install
|
|
Operation = Remove
|
|
Type = Path
|
|
Target = usr/lib/modules/*/vmlinuz
|
|
Target = usr/lib/initcpio/*
|
|
Target = usr/lib/firmware/*
|
|
Target = usr/src/*/dkms.conf
|
|
|
|
[Action]
|
|
Depends = rsync
|
|
Description = Backing up post /boot...
|
|
When = PostTransaction
|
|
Exec = /usr/bin/bash -c 'rsync -a --mkpath --delete /boot/ "/.boot/\$(cat /zfs/snapshots/syu)_post"/; /opt/local/hooks/post-install-boot'
|
|
post
|
|
# zroot
|
|
cat >/mnt/etc/pacman.d/hooks/00-syu_pre.hook <<- pre
|
|
[Trigger]
|
|
Type = Path
|
|
Operation = Upgrade
|
|
Operation = Install
|
|
Operation = Remove
|
|
Target = usr/lib/modules/*/vmlinuz
|
|
Target = usr/lib/initcpio/*
|
|
Target = usr/lib/firmware/*
|
|
Target = usr/src/*/dkms.conf
|
|
|
|
[Action]
|
|
Description = Creating pre zroot snapshot...
|
|
When = PreTransaction
|
|
Exec = /usr/bin/bash -c 'zfs snapshot zroot/ROOT@pre-\$(cat /zfs/snapshots/syu)'
|
|
AbortOnFail
|
|
pre
|
|
cat >/mnt/etc/pacman.d/hooks/zz-syu_post.hook <<- post
|
|
[Trigger]
|
|
Type = Path
|
|
Operation = Upgrade
|
|
Operation = Install
|
|
Operation = Remove
|
|
Target = usr/lib/modules/*/vmlinuz
|
|
Target = usr/lib/initcpio/*
|
|
Target = usr/lib/firmware/*
|
|
Target = usr/src/*/dkms.conf
|
|
|
|
[Action]
|
|
Description = Creating post zroot snapshot...
|
|
When = PostTransaction
|
|
Exec = /usr/bin/bash -c 'zfs snapshot zroot/ROOT@post-\$(cat /zfs/snapshots/syu)'
|
|
post
|
|
|
|
# efistub
|
|
cat <<- EFISTUB |\
|
|
sed s/{{ipconfig}}/${netconf}/ |\
|
|
install /dev/stdin /mnt/opt/local/bin/efistub
|
|
#!/usr/bin/env bash
|
|
# Network adapter
|
|
originaliface=\$(dmesg | grep \$(ip r | grep 'default via' | awk '{print \$5}') | grep 'renamed from' | awk '{print \$NF}')
|
|
|
|
# Kernel parameters
|
|
kernelparams="zfs=bootfs {{ipconfig}} rw quiet bgrt_disable"
|
|
|
|
# Delete old bootloader entries
|
|
for entry in \$(efibootmgr | grep "Arch" | grep -o 'Boot....' | sed 's/Boot//')
|
|
do
|
|
efibootmgr --quiet -Bb \${entry}
|
|
done
|
|
|
|
# Add bootloader entries
|
|
for drive in ${installation_disks[@]}
|
|
do
|
|
efibootmgr --quiet --create \\
|
|
--disk \${drive} --part 1 \\
|
|
--label "Arch Server (${hostname}) - \${drive}" \\
|
|
--loader "/vmlinuz-linux" \\
|
|
--unicode "initrd=\initramfs-linux.img \${kernelparams}"
|
|
done
|
|
echo -e "\$(tput setaf 5)\$(tput bold):: Added efi boot entries\$(tput sgr0)"
|
|
EFISTUB
|
|
/mnt/opt/local/bin/efistub
|
|
|
|
# Sync multiple boot partitions
|
|
if [ ${#installation_disks[@]} -gt 1 ]
|
|
then
|
|
install /dev/stdin /mnt/opt/local/hooks/post-install-boot <<- backup
|
|
#!/usr/bin/env bash
|
|
# Clone boot files
|
|
for drive in $(printf '%s-part1 ' ${installation_disks[@]:1})
|
|
do
|
|
backupdir=\$(mktemp -d)
|
|
mount \${drive} \${backupdir}
|
|
rsync -a --delete /boot/ \${backupdir}
|
|
umount \${backupdir}
|
|
rmdir \${backupdir}
|
|
done
|
|
|
|
# Unmount boot partition
|
|
umount /boot
|
|
backup
|
|
|
|
# Clone boots
|
|
arch-chroot /mnt /opt/local/hooks/post-install-boot
|
|
say as heading "Cloned boot drives"
|
|
|
|
# Disk recovery script
|
|
install /dev/stdin /mnt/zfs/bin/recover <<- RECOVERY
|
|
#!/usr/bin/env bash
|
|
|
|
## Tput codes
|
|
reset=\$(tput sgr0)
|
|
|
|
bold=\$(tput bold)
|
|
italic=\$(tput sitm)
|
|
blink=\$(tput blink)
|
|
|
|
black=\$(tput setaf 0)
|
|
red=\$(tput setaf 1)
|
|
green=\$(tput setaf 2)
|
|
yellow=\$(tput setaf 3)
|
|
blue=\$(tput setaf 4)
|
|
magenta=\$(tput setaf 5)
|
|
cyan=\$(tput setaf 6)
|
|
white=\$(tput setaf 7)
|
|
|
|
##
|
|
## functions start
|
|
##
|
|
|
|
# Color codes
|
|
function say
|
|
{
|
|
for format in \${@:2}
|
|
do
|
|
echo -n \${!format}
|
|
done
|
|
echo -e "\${1}"
|
|
echo -n \${reset}
|
|
}
|
|
function say_n
|
|
{
|
|
for format in \${@:2}
|
|
do
|
|
echo -n \${!format}
|
|
done
|
|
echo -e -n "\${1}"
|
|
echo -n \${reset}
|
|
}
|
|
function heading
|
|
{
|
|
say ":: \${1}" magenta bold
|
|
}
|
|
|
|
# Exit function
|
|
trap '[ "\$?" -ne 77 ] || exit 77' ERR
|
|
function die
|
|
{
|
|
cat <<- abort
|
|
\${red}
|
|
Error encountered for the following reason:
|
|
\${reset}\${yellow}
|
|
"\${1}"
|
|
\${reset}\${red}
|
|
Script aborted...
|
|
\${reset}
|
|
abort
|
|
exit 77
|
|
}
|
|
|
|
zpool status | grep -q DEGRADED || die 'Nothing is wrong with zroot'
|
|
|
|
# New disks
|
|
new_disk=\${1} ## eg. vda, nvme2n1
|
|
|
|
# Exit if no replacement disk specified
|
|
if ! lsblk -d -n -o name,type | grep disk | grep -q -w \${new_disk}
|
|
then
|
|
die 'Replacement disk is invalid'
|
|
elif zpool status | grep -q "\$(ls /dev/disk/by-id/ -l | grep \${new_disk}\$ | awk '{print \$9}')-part2"
|
|
then
|
|
die 'Replacement disk cannot be the same as existing disk'
|
|
fi
|
|
|
|
# Surviving drive/s
|
|
for drive in \$(sudo zpool status | grep -- -part2 | grep ONLINE | awk '{print \$1}')
|
|
do
|
|
existing_disk+=(/dev/disk/by-id/\${drive})
|
|
done
|
|
# ID
|
|
for drive in \${existing_disk[@]}
|
|
do
|
|
existing_disk_id+=(\$(echo \${drive} | sed 's/-part2\$//'))
|
|
done
|
|
# UUID
|
|
for drive in \${existing_disk_id[@]}
|
|
do
|
|
existing_disk_uuid+=(\$(sudo blkid -s UUID -o value \${drive}-part1))
|
|
done
|
|
|
|
heading 'Beginning failed disk replacement'
|
|
echo
|
|
|
|
# Missing/dead drive
|
|
missing_disk=\$(sudo zpool status | grep UNAVAIL | grep -o 'was.*' | sed 's/was //')
|
|
missing_disk_id=\$(echo \${missing_disk} | sed 's/-part2\$//')
|
|
|
|
# Replacement disk ID
|
|
replacement_disk=\$(ls -l /dev/disk/by-id/* | grep "\${new_disk}\$" | grep 'wwn\|nvme-uuid\|nvme-nvme\|nvme-eui\|QEMU\|virtio\|VBOX' | awk '{print \$9}' | head -1)
|
|
|
|
# Partition new disk
|
|
heading 'Partitioning replacement disk'
|
|
sudo blkdiscard --quiet --force --secure \${replacement_disk} >/dev/null 2>&1
|
|
sudo wipefs --quiet --force --all \${replacement_disk}
|
|
sudo parted --script --align=optimal \${replacement_disk} \\
|
|
mklabel gpt \\
|
|
mkpart boot 1MiB 300MiB \\
|
|
mkpart zroot 300MiB 100% \\
|
|
set 1 esp on
|
|
sudo partprobe \${replacement_disk}
|
|
|
|
# Clone and replace zpool
|
|
heading 'Replacing missing disk in zroot'
|
|
until sudo zpool replace -f zroot \${missing_disk} \${replacement_disk}-part2
|
|
do
|
|
sleep 3
|
|
done
|
|
yes | sudo mkfs.fat -F 32 \${replacement_disk}-part1 >/dev/null
|
|
|
|
# Update /etc/fstab boot partition of new disk
|
|
sudo sed -i "/\$(printf '%s\|' \${existing_disk_uuid[@]} | sed 's/\\\|\$//')/! s|UUID=.*/boot|UUID=\$(sudo blkid -s UUID -o value \${replacement_disk}-part1) /boot|" /etc/fstab
|
|
sudo systemctl daemon-reload
|
|
heading 'Updated /etc/fstab with replacement boot partition'
|
|
|
|
## Update /etc/pacman.d/hooks/55-bootbackup_pre.hook
|
|
sudo sed -i "s|/dev/disk/by-id/.*-part1|\${replacement_disk}-part1|" /etc/pacman.d/hooks/55-bootbackup_pre.hook
|
|
heading 'Updated /etc/pacman.d/hooks/55-bootbackup_pre.hook boot partition reference'
|
|
|
|
## Update /opt/local/hooks/post-install-boot
|
|
sudo sed -i "/^for drive in/c\for drive in \$(printf '%s-part1 ' \${existing_disk_id[@]})" /opt/local/hooks/post-install-boot
|
|
heading 'Updated /opt/local/hooks/post-install-boot boot partition reference'
|
|
|
|
# efibootmgr
|
|
for boot in \$(mount | grep -w '/boot' | grep -w -v 'systemd-1' | awk '{print \$1}')
|
|
do
|
|
sudo umount \${boot}
|
|
done
|
|
sudo mount \${replacement_disk}-part1
|
|
sudo rsync -a /usr/lib/modules/\$(uname -r)/vmlinuz /boot/vmlinuz-linux
|
|
sudo zpool set cachefile=/etc/zfs/zpool.cache zroot
|
|
sudo sed -i "/^for drive in/c\for drive in \${replacement_disk} \$(echo \${existing_disk_id[@]})" /opt/local/bin/efistub
|
|
sudo /opt/local/bin/efistub
|
|
echo
|
|
|
|
# mkinitcpio
|
|
heading 'Regenerating initramfs'
|
|
sudo mkinitcpio -P
|
|
echo
|
|
|
|
# Clone boot partitions
|
|
sudo /opt/local/hooks/post-install-boot
|
|
heading 'Cloned boot drives'
|
|
echo
|
|
|
|
# Wait for resilver to complete
|
|
watch -d sudo zpool status
|
|
RECOVERY
|
|
fi
|
|
|
|
# System services
|
|
arch-chroot /mnt systemctl enable ${systemd_services[@]} \
|
|
systemd-resolved.service systemd-networkd.service sshd.service iptables.service \
|
|
libvirtd.service docker.service \
|
|
nginx.service \
|
|
fail2ban.service \
|
|
zfs.target zfs-import-cache.service zfs-mount.service zfs-import.target \
|
|
zfs-trim@zroot.timer zfs-scrub@zroot.timer \
|
|
seatd.service \
|
|
certbot-renew.timer || die "Unable to start systemd services"
|
|
|
|
# Generate fstab
|
|
for drive in ${installation_disks[@]}
|
|
do
|
|
mount ${drive}-part1 /mnt/boot
|
|
done
|
|
genfstab -U -p /mnt/boot | sed "s|vfat.*rw|vfat rw,x-systemd.idle-timeout=1min,x-systemd.automount,noauto,nofail|" >>/mnt/etc/fstab
|
|
sed -i '/UUID.*vfat/ s|/|/boot|' /mnt/etc/fstab
|
|
|
|
# Add zram to fstab
|
|
echo '/dev/zram0 none swap defaults 0 0' >>/mnt/etc/fstab
|
|
echo
|
|
|
|
# Reboot only if script succeeded
|
|
if /usr/bin/bash -c 'arch-chroot /mnt uname -a' | grep -q Linux
|
|
then
|
|
until ! mount | grep -q /mnt/boot
|
|
do
|
|
umount /mnt/boot
|
|
done
|
|
zfs snapshot zroot/ROOT@fresh-installation
|
|
zpool export -a
|
|
say as success in bold "Installer has completed and system drive has been unmounted"
|
|
say as success in bold "Boot into the new system, connect to a network and run $(tput smso)startup$(tput rmso) in the terminal"
|
|
say as success in bold "Rebooting..."
|
|
echo
|
|
reboot
|
|
else
|
|
die 'Something does not feel right'
|
|
fi
|