#!/usr/bin/env bash
revision=0.1a
set -a
set -E
echo

# Environment variables
tee -a /etc/environment >/dev/null <<- environment
EDITOR=vim
SUDO_EDITOR=vim
environment

# Global bashrc
tee -a /etc/skel/.bashrc >/dev/null <<- 'bashglobal'

# Source bash functions
if [ -d ~/.local/functions ]
then
    for file in $(find ~/.local/functions -type f)
    do
        . ${file}
    done
fi

# Add local functions folder to path
export PATH=${PATH}:${HOME}/.local/bin:/zfs/bin:/opt/local/bin
export SUDO_PROMPT=$'\a'"$(tput rev)[sudo] password for %p:$(tput sgr0)"' '

# Colored prompts
alias diff='diff --color=auto'
alias ip='ip -color=auto'
export LESS='-R --use-color -Dd+r$Du+b$'

# Adjust terminal upon window resize
shopt -s checkwinsize

# Auto cd into directory
shopt -s autocd

# Enable tab complete for sudo
complete -c -f sudo

# Ignore duplicate and whitespace history entries
export HISTCONTROL=ignoreboth

#
# ~/.bash_aliases
#

# ZFS/btrfs
alias zfs='sudo zfs'
alias zpool='sudo zpool'
alias btrfs='sudo btrfs'

# Shutdown reboot
alias poweroff='sudo poweroff'
alias reboot='sudo reboot'

# Text editors
alias v='vim'
alias sv='sudo vim'

# Clear bash history
alias clearhistory='rm ${HISTFILE}; history -c -w'

# Miscellanous pacman
alias orphans='sudo pacman -Rcns $(pacman -Qtdq)'
alias unlockpacman='sudo rm /var/lib/pacman/db.lck && sudo pacman -Syyu'

# Rsync
alias rsync='rsync -v -h --progress --info=progress2 --partial --append-verify'
# --log-file=
# --remove-source-files

#
# ~/.bash_functions
#

# Pacman tools
function installer
{
    /opt/local/bin/cacheserver
    sudo pacman -S ${@}
    echo
}

function uninstall
{
    sudo pacman -Rcns ${@}
    echo
}

function mirrors
{
    echo
    sudo reflector --country CA,US --age 24 --latest 20 --protocol https --fastest 25 --sort rate --save /etc/pacman.d/mirrorlist
    echo
    cat /etc/pacman.d/mirrorlist
    echo
}

function syur
{
    /opt/local/bin/syu &&
    reboot
}

function syup
{
    /opt/local/bin/syu &&
    poweroff
}

# Update bash
function update-bash
{
    vim ~/.bashrc &&
    source ~/.bashrc
}
bashglobal

# Root bashrc
rsync -a /etc/skel/.bashrc ~/
mkdir -p ~/.local/functions/
cat > ~/.local/functions/bashrc <<- 'bashrc'
#!/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
bashrc

# Locale
sed '/#en_US.UTF-8 UTF-8/ s/#//' -i /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 <<HOSTS
127.0.0.1		localhost
127.0.1.1		${hostname}
HOSTS
say as heading 'Hostname configured'

# User and superuser
useradd -m -g users -G wheel -s /usr/bin/bash ${username} || die 'User account creation has failed'
printf '%s\n' "${userpass}" "${userpass}" | passwd ${username} >/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 BTRFS    =   /usr/bin/btrfs, /usr/bin/timeshift, /usr/bin/timeshift-gtk, /usr/bin/timeshift-launcher
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 ARCHISO  =   /opt/local/bin/mkairgap, /opt/local/bin/mkiso
Cmnd_Alias PACMAN   =   /usr/bin/pacman -Sy
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, BTRFS, QEMU, FAIL2BAN, ARCHISO, PACMAN, IPTABLES, MISC
WHEEL

install -m 0440 /dev/stdin /etc/sudoers.d/.zz-NOPASSWD <<- NOPASSWD
Defaults:${username} !authenticate
NOPASSWD

case ${filesystem} in
	zfs)
		# ZFS setup
		zpool set cachefile=/etc/zfs/zpool.cache zroot
		zgenhostid $(hostid)

		# ZFS files
		touch /zfs/snapshot-syu
		chown ${username}:users /zfs/snapshot-syu

		# 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

		# Pre and Post update backup hooks
		cat >/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/snapshot-syu)'
		AbortOnFail
		pre
		cat >/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 /boot; rsync -a --mkpath --delete /boot/ "/.boot/\$(cat /zfs/snapshot-syu)_pre"/'
		AbortOnFail
		pre
		cat >/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/snapshot-syu)_post"/'
		post
		cat >/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/snapshot-syu)'
		post

		# Custom pacman wrapper
		install /dev/stdin /opt/local/bin/syu <<- syu
		#!/usr/bin/env bash
		set -e
		mirrorlist=

		# Enable or disable pacman cache server
		/opt/local/bin/cacheserver

		# Fetch latest mirrors
		sudo curl --fail --silent \${mirrorlist} -o /etc/pacman.d/mirrorlist

		# Record current time
		echo \$(date "+%Y-%m-%d-%H:%M:%S") >/zfs/snapshot-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 -Sy >/dev/null

		    # Check zfs-linux kernel dependency
		    zfslinux=\$(pacman -Si zfs-${linux_kernel} | grep "Depends On" | sed "s|.*${linux_kernel}=||")
		    linux=\$(pacman -Si ${linux_kernel} | grep "Version" | awk '{print \$3}')
		    if [ \${zfslinux} != \${linux} ]
		    then
		        archzfs="--ignore zfs-utils,zfs-${linux_kernel},${linux_kernel}"
		    fi

		    # Update archlinux-keyring first
		    if [[ \${newpkg[@]} =~ "archlinux-keyring" ]]
		    then
		        sudo pacman --ask 4 -S archlinux-keyring
		        echo
		    fi

		    # Perform update if dependencies are satisfied
		    if sudo pacman --ask 4 -Syu \${archzfs} --needed
		    then
		        echo
		        sudo pacdiff
		        exit 0
		    fi
		fi
		syu
		;;
	*)
		case ${filesystem} in
			btrfs)
				# Pre and post update backup hooks
				cat >/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]
				Depends = timeshift
				Description = Creating pre root snapshot...
				When = PreTransaction
				Exec = /usr/bin/bash -c 'timeshift --create --comments "pre_\$(date "+%Y-%m-%d-%H:%M:%S")" >/dev/null'
				AbortOnFail
				pre
				cat >/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 'rsync -a --mkpath --delete --exclude 'header.img' /boot/ "/.boot/\$(date "+%Y-%m-%d-%H:%M:%S")_pre"/'
				AbortOnFail
				pre
				cat >/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 --exclude 'header.img' /boot/ "/.boot/\$(date "+%Y-%m-%d-%H:%M:%S")_post"/'
				post
				cat >/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]
				Depends = timeshift
				Description = Creating post root snapshot...
				When = PostTransaction
				Exec = /usr/bin/bash -c 'timeshift --create --comments "post_\$(date "+%Y-%m-%d-%H:%M:%S")" >/dev/null'
				post

				install /dev/stdin /usr/local/bin/timeshift-gui <<- 'timeshift'
				#!/bin/sh
				pkexec env WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" XDG_RUNTIME_DIR=/run/user/0 timeshift-launcher
				timeshift
				;;
		esac

		# Custom pacman wrapper
		install /dev/stdin /opt/local/bin/syu <<- syu
		#!/usr/bin/env bash
		set -e
		mirrorlist=

		# Enable or disable pacman cache server
		/opt/local/bin/cacheserver

		# Fetch latest mirrors
		sudo curl --fail --silent \${mirrorlist} -o /etc/pacman.d/mirrorlist

		# Check for new packages and continue if found
		newpkg+=(\$(checkupdates --nocolor | awk '{print \$1}'))
		if [ "\${newpkg}" ]
		then
		    # Update archlinux-keyring first
		    if [[ \${newpkg[@]} =~ "archlinux-keyring" ]]
		    then
		        sudo pacman --ask 4 -S archlinux-keyring
		        echo
		    fi

		    # Perform update if dependencies are satisfied
		    if sudo pacman --ask 4 -Syu --needed
		    then
		        echo
		        sudo pacdiff
		        exit 0
		    fi
		fi
		syu
		;;
esac
echo

# /opt/local/bin/cacheserver
install /dev/stdin /opt/local/bin/cacheserver <<- 'cacheserver'
#!/usr/bin/env bash
set -e

# Enable cacheserver if active
cacheserver=
port=
scheme=

if [ -z ${cacheserver} ]
then
    exit 1
fi

if nc -z -4 -w 3 ${cacheserver} ${port:-80} >/dev/null 2>&1
then
    if grep -q "CacheServer" /etc/pacman.conf
    then
        sudo sed "/CacheServer/ s/^\(#\)*//g" -i /etc/pacman.conf
    else
        sudo sed "/^\[core\]$\|^\[extra\]$\|^\[myvezfs\]$/a CacheServer = ${scheme:-http}://${cacheserver}:${port:-80}" -i /etc/pacman.conf
    fi
else
    sudo sed "/CacheServer/ s/^/#/g" -i /etc/pacman.conf
fi
cacheserver
if ls -l /dev/disk/* | grep -q 'VBOX\|virtio\|QEMU'
then
	sed -e "/^cacheserver=/c cacheserver=192.168.122.1" \
		-e "/^port=/c port=9090" \
		-i /opt/local/bin/cacheserver
	/opt/local/bin/cacheserver
fi

# mkinitcpio
say as heading 'Regenerating cpio image'
mkinitcpio -P
echo

# Install GRUB
if [[ ${arch} = [3567] ]]
then
	say as heading 'Installing GRUB'
	grub-install --target=x86_64-efi --bootloader-id="Arch Linux (${hostname})" --efi-directory=/boot/efi --recheck || die 'Grub installation failed'
	grub-mkconfig -o /boot/grub/grub.cfg
	echo
fi

# Sysctl custom settings
cat >/etc/sysctl.d/99-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
vm.swappiness=10
vm.vfs_cache_pressure=50
SYSCTL

# iptables
cat >/etc/iptables/userinput.rules <<- IPTABLES
## User input

# SSH port
-A INPUT -p tcp -m tcp --dport ${port:-22} -j ACCEPT -m comment --comment "SSH 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
echo '/dev/zram0                                      none            swap            defaults        0 0' >>/etc/fstab

# Configure ssh and ssh_config.d/10-global.conf
ssh-keygen -q \
	-t ed25519 \
	-P "" \
	-C "${USER}@${hostname}" \
	-f ~/.ssh/id_ed25519
mkdir ~/.ssh/sockets/
cat >/etc/ssh/sshd_config.d/10-sshd.conf <<- sshd
PermitRootLogin no
PasswordAuthentication no
AuthenticationMethods publickey
sshd
cat >/etc/ssh/ssh_config.d/10-global.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.*
    StrictHostKeyChecking no
    UserKnownHostsFile=/dev/null
    LogLevel Error
sshconfig

# Polkit
mkdir -p /etc/polkit-1/rules.d/
cat >/etc/polkit-1/rules.d/49-nopasswd_global.rules <<- 'polkit'
/* Allow members of the wheel group to execute any actions
 * without password authentication, similar to "sudo NOPASSWD:"
 */
polkit.addRule(function(action, subject) {
    if (subject.isInGroup("wheel")) {
        return polkit.Result.YES;
    }
});
polkit

# Persistent journal logging
mkdir -p /etc/systemd/journald.conf.d/
cat >/etc/systemd/journald.conf.d/zz-journald.conf <<- eof
[Journal]
Storage=persistent
eof

# makepkg
mkdir -p /etc/makepkg.conf.d/
cat >/etc/makepkg.conf.d/zz-makepkg.conf <<- makepkg
PKGEXT=".pkg.tar"
MAKEFLAGS="--jobs=$(nproc)"
COMPRESSZST=(zstd -c -T0 --auto-threads=logical -)
makepkg

# Virtual machine settings
if ls -l /dev/disk/* | grep -q 'VBOX\|virtio\|QEMU'
then
	mv /etc/sudoers.d/.zz-NOPASSWD /etc/sudoers.d/zz-NOPASSWD
fi

##
## Hooks
##

# paccache
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/#//' -i /etc/locale.gen.pacnew
    mv /etc/locale.gen.pacnew /etc/locale.gen
    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

# iptables
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

su ${username} <<- "user"
curl --fail --silent https://git.myvelabs.com/lab/archlinux/raw/branch/master/functions/user -o /tmp/user
bash /tmp/user
user
