archlinux/passthrough.sh

783 lines
19 KiB
Bash
Raw Permalink Normal View History

2025-11-22 23:15:27 +00:00
#!/usr/bin/env bash
set -a
set -E
# SSH public keys
sshkeys=''
## Tput codes
reset=$(tput sgr0)
bold=$(tput bold)
italic=$(tput sitm)
blink=$(tput blink)
black=${reset}$(tput setaf 0)
red=${reset}$(tput setaf 1)
green=${reset}$(tput setaf 2)
yellow=${reset}$(tput setaf 3)
blue=${reset}$(tput setaf 4)
magenta=${reset}$(tput setaf 5)
cyan=${reset}$(tput setaf 6)
white=${reset}$(tput setaf 7)
# 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}
}
# 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
}
# 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
else
die 'No internet connectivity detected, plug in an ethernet cable or run \e[32miwd-connect\e[33m if using wifi and try again'
fi
function returnUUID
{
for partuuid in \
$(blkid -s UUID -o value ${1}) \
$(lsblk -d -n -o uuid ${1}) \
$(ls -l -a /dev/disk/by-uuid | grep "$(echo ${1} | sed 's\/dev\\')" | awk '{print $9}')
do
if [ ${partuuid} ]
then
echo ${partuuid}
break
else
unset partuuid
fi
done
if [ -z ${partuuid} ]
then
die 'Unable to determine partition UUID'
fi
}
clear
cat <<title
${black}${bold}## rev ${revision}${reset}
${blue}${bold}
Arch Passthrough
${reset}${bold}
Press any key when ready...${reset}
title
read -n 1 -s
echo
if ls -l /dev/disk/* | grep -q 'virtio'
then
installation_disk='vda'
elif ls -l /dev/disk/* | grep -q 'QEMU'
then
installation_disk='sda'
fi
# Hostname
say "Create a name for your computer" blue
until [ ${hostname} ]
do
read -r -p 'Hostname: ' hostname
echo
[ ${hostname} ] || say "Hostname cannot be empty, try again" red
done
# User password
say "Set a password for user" blue
while true
do
read -s -r -p 'User password: ' userpass
echo
read -s -r -p 'Verify user password: ' userpass2
echo
echo
if [ -z ${userpass} ] || [ "${userpass}" = "${userpass2}" ]
then
break
else
say "Passwords did not match, try again" red
fi
done
say "user@${hostname}'s password has been saved" green
echo
# Superuser password
say "Create a root superuser password" blue
while true
do
read -s -r -p 'Superuser password: ' rootpass
echo
read -s -r -p $'Verify superuser password: ' rootpass2
echo
echo
if [ -z ${rootpass} ] || [ "${rootpass}" = "${rootpass2}" ]
then
break
else
say "Passwords did not match, try again" red
fi
done
say "Root superuser password has been saved" green
echo
# Assign system drive
say "Identify the system drive from the list of available devices below" blue
lsblk -d -o name,model,size,mountpoint | grep -v 'archiso'
say '## SSD and HDD device format begins with "sd" or "hd" (sda, sdb, sd[*])
## NVME and PCI device format is "nvme[*]n1" (nvme0n1, nvme1n1, nvme[*]n1)' black bold
lsblk -o name | grep -q -w ${installation_disk} ||
while true
do
read -r -p 'Installation device: ' installation_disk
if [ ${installation_disk} ] && lsblk -o name | grep -q -w ${installation_disk}
then
break
else
lsblk -d -o name,model,size,mountpoint | grep -v 'archiso'
if [ -z ${installation_disk} ]
then
echo
say "Field cannot be empty, try again" red
else
echo
say "Invalid selection or drive not available, try again" red
fi
fi
done
echo
say "Installation drive set as ${green}${reset}/dev/${installation_disk}" reset
# Choose a linux kernel
cat <<menu
${blue}
Select a linux kernel version${reset}
1) linux
2) linux-lts
3) linux-hardened
4) linux-zen
menu
until [[ ${kernel_choice} = [1-4] ]]
do
read -n 1 -p '> ' kernel_choice
echo
if ! [[ ${kernel_choice} = [1-4] ]]
then
echo
say 'Invalid selection, type an option from 1 to 4' red
fi
done
case ${kernel_choice} in
1)
linux_kernel='linux'
;;
2)
linux_kernel='linux-lts'
;;
3)
linux_kernel='linux-hardened'
;;
4)
linux_kernel='linux-zen'
;;
esac
echo
say " You have chosen ${green}${linux_kernel}${reset} as a kernel" reset
echo
# Unmount if mounted
if findmnt '/mnt' | grep -q -w '/mnt'
then
umount -R /mnt
fi
# Shred installation drive
say "Partitioning /dev/${installation_disk}" yellow bold
wipefs -f -a /dev/${installation_disk}
blkdiscard -f /dev/${installation_disk}
if [ -d /sys/firmware/efi/efivars ]
then
parted --script --align=optimal /dev/${installation_disk} \
mklabel gpt \
mkpart primary fat32 1MiB 100MiB \
mkpart primary ${partition} 100MiB 100% \
set 1 esp on
partprobe /dev/${installation_disk}
export {efivol,bootvol}=/dev/$(lsblk -l -o name | grep "${installation_disk}".*1$)
export {rootpart,rootvol}=/dev/$(lsblk -l -o name | grep "${installation_disk}".*2$)
echo
function bootLoader
{
# Systemd-boot
say "Configuring systemd-boot" yellow bold
bootctl --path=/boot install
echo -e 'title Arch Linux
linux /vmlinuz-'${linux_kernel}'
initrd /initramfs-'${linux_kernel}'.img
options quiet root=UUID='$(returnUUID ${rootpart})' rw' > /boot/loader/entries/arch.conf
echo -e 'default arch
timeout 0
console-mode max
editor no' > /boot/loader/loader.conf
systemdbootservice='systemd-boot-update.service'
}
else
bios='syslinux'
parted --script --align=optimal /dev/${installation_disk} \
mklabel msdos \
mkpart primary ${partition} 1MiB 100% \
set 1 boot on || die 'Disk partitioning failed'
partprobe /dev/${installation_disk}
export {rootpart,rootvol}=/dev/$(lsblk -l -o name | grep "${installation_disk}".*1$)
echo
function bootLoader
{
# Syslinux
say "Configuring syslinux" yellow bold
syslinux-install_update -i -a -m
sed -i 's\LINUX ../vmlinuz-linux.*\LINUX ../vmlinuz-'${linux_kernel}'\g' /boot/syslinux/syslinux.cfg
sed -i 's/APPEND root.*/APPEND root=UUID='"$(returnUUID ${rootpart})"' rw quiet/g' /boot/syslinux/syslinux.cfg
sed -i 's/.*PROMPT.*/PROMPT 0/g' /boot/syslinux/syslinux.cfg
sed -i 's/.*TIMEOUT.*/TIMEOUT 0/g' /boot/syslinux/syslinux.cfg
sed -i 's/.*UI menu.*/#UI menu/g' /boot/syslinux/syslinux.cfg
}
fi
say "Configuring volumes" yellow bold
yes | mkfs.ext4 ${rootvol}
mount ${rootvol} /mnt
if [ -d /sys/firmware/efi/efivars ]
then
mkdir /mnt/boot
yes | mkfs.fat -F 32 ${efivol}
mount ${bootvol} /mnt/boot
findmnt '/mnt/boot' | grep -q -w "${bootvol}" || die 'Boot partition has not been mounted properly'
echo
fi
say "Installing Arch Linux base packages on /dev/${installation_disk}" yellow bold
sed -i \
-e '/Color/c\Color' \
-e '/ParallelDownloads/c\ParallelDownloads = 10' \
-e 's#^\[core\]$\|^\[extra\]$#&\
CacheServer = http://192.168.122.1:9090#' \
/etc/pacman.conf
# Temporarily disable mkinitcpio
ln -s -f /dev/null /etc/pacman.d/hooks/90-mkinitcpio-install.hook
pacstrap -K /mnt ${linux_kernel} qemu-guest-agent base mkinitcpio sudo \
${bios} reflector openssh rsync \
wireguard-tools systemd-resolvconf \
pacman-contrib bash-completion vim
genfstab -U -p /mnt >> /mnt/etc/fstab
grep -q 'UUID' /mnt/etc/fstab || die
# Make custom directories
mkdir -p /mnt/etc/pacman.d/hooks /mnt/usr/bin/local
# pacman.conf hook
install /dev/stdin /mnt/usr/bin/local/hook-pacman.conf <<hook
#!/usr/bin/env bash
if [ -f /etc/pacman.conf.pacnew ]
then
sed -i \\
-e '/Color/c\Color' \\
-e '/ParallelDownloads/c\ParallelDownloads = 10' \\
-e 's#^\[core\]$\|^\[extra\]\$#&\\
CacheServer = http://192.168.122.1:9090#' /etc/pacman.conf.pacnew
mv /etc/pacman.conf.pacnew /etc/pacman.conf
fi
hook
cat >/mnt/etc/pacman.d/hooks/pacman.conf.hook <<pacman
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = pacman
[Action]
Description = Fixing pacman.conf
When = PostTransaction
Exec = /usr/bin/local/hook-pacman.conf
pacman
sed -i \
-e '/Color/c\Color' \
-e '/ParallelDownloads/c\ParallelDownloads = 10' \
-e 's#^\[core\]$\|^\[extra\]$#&\
CacheServer = http://192.168.122.1:9090#' \
/mnt/etc/pacman.conf
# 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
rm /mnt/usr/share/mkinitcpio/hook.preset
rsync -a /mnt/usr/lib/modules/*/vmlinuz /mnt/boot/vmlinuz-linux
# mkinitcpio preset hook
cat >/mnt/etc/pacman.d/hooks/linux.preset.hook <<preset
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = mkinitcpio
[Action]
Description = Updating mkinitcpio linux preset
When = PostTransaction
Exec = /usr/bin/local/hook-linux.preset
preset
install /dev/stdin /mnt/usr/bin/local/hook-linux.preset <<'hook'
#!/usr/bin/env bash
if [ -f /usr/share/mkinitcpio/hook.preset ]
then
sed \
-e "s|%PKGBASE%|linux|g" \
-e "s/^fallback/#&/g" \
-e "s/ 'fallback'//" \
/usr/share/mkinitcpio/hook.preset >/etc/mkinitcpio.d/linux.preset
rm /usr/share/mkinitcpio/hook.preset
fi
hook
arch-chroot /mnt mkinitcpio -P
# Networking
ln -s -f /run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf
rsync -a /etc/systemd/network/20-ethernet.network /mnt/etc/systemd/network
arch-chroot /mnt /bin/bash <<"SYU"
# Install bootloader
echo
bootLoader
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 'Locale configured' yellow bold
# Time zone
ln -s -f /usr/share/zoneinfo/UTC /etc/localtime
hwclock --systohc --utc
say 'Time zone configured' yellow bold
# Hostname
echo ${hostname} >/etc/hostname
cat >>/etc/hosts <<HOSTS
127.0.0.1 localhost
127.0.1.1 ${hostname}
HOSTS
say 'Hostname configured' yellow bold
# User and superuser
useradd -m -g users -G wheel -s /bin/bash user || die
if [ ${userpass} ]
then
printf '%s\n' ${userpass} ${userpass} | passwd user &>/dev/null
echo '%wheel ALL=(ALL:ALL) ALL' > /etc/sudoers.d/wheel
else
echo -e '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/wheel
mkdir -p /etc/systemd/system/getty@tty1.service.d
echo '[Service]
ExecStart=
ExecStart=-/usr/bin/agetty --autologin user --noclear %I $TERM' > /etc/systemd/system/getty@tty1.service.d/override.conf
fi
if [ "$rootpass" ]
then
printf '%s\n' "$rootpass" "$rootpass" | passwd &>/dev/null
fi
say "Configured superuser and user" yellow bold
echo
# Sysctl custom settings
echo -e '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' > /etc/sysctl.d/99-sysctl.conf
# iptables
install /dev/stdin /usr/bin/local/iptables.hook <<IPTABLES
#!/usr/bin/env bash
iptables -N TCP
iptables -N UDP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -P INPUT DROP
iptables -A INPUT -s 192.168.122.0/24 -j ACCEPT
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
iptables -A INPUT -p icmp --icmp-type 8 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p udp -m conntrack --ctstate NEW -j UDP
iptables -A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
iptables -A INPUT -j REJECT --reject-with icmp-proto-unreachable
iptables-save -f \${1}
IPTABLES
/usr/bin/local/iptables.hook /etc/iptables/iptables.rules
# iptables hook
install /dev/stdin /usr/bin/local/hook-iptables.rules <<hook
#!/usr/bin/env bash
if [ -f /etc/iptables/iptables.rules.pacnew ]
then
/usr/bin/local/iptables.hook /etc/iptables/iptables.rules.pacnew
mv /etc/iptables/iptables.rules.pacnew /etc/iptables/iptables.rules
fi
hook
cat >/etc/pacman.d/hooks/iptables.rules.hook <<iptables
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = iptables*
[Action]
Description = Fixing iptables rules
When = PostTransaction
Exec = /usr/bin/local/hook-iptables.rules
iptables
# Paccache hook
cat >/etc/pacman.d/hooks/paccache.hook <<'paccache'
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
Description = Cleaning pacman cache...
When = PostTransaction
Exec = /usr/bin/paccache -r
paccache
# locale.gen.pacnew hook
install /dev/stdin /usr/bin/local/hook-localegen <<hook
#!/usr/bin/env bash
if [ -f /etc/locale.gen.pacnew ]
then
sed -i '/#en_US.UTF-8 UTF-8/ s/#//' /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/localegen.hook <<localegen
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = glibc
[Action]
Description = Fixing locale.gen
When = PostTransaction
Exec = /usr/bin/local/hook-localegen
localegen
# ssh
cat >/etc/ssh/sshd_config.d/10-sshd.conf <<sshd
PermitRootLogin no
PasswordAuthentication no
AuthenticationMethods publickey
Protocol 2
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
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
# Custom pacman wrapper
install /dev/stdin /usr/local/bin/syu <<syu
#!/usr/bin/env bash
2025-12-08 05:11:32 +00:00
mirrorlist=https://git.myvelabs.com/lab/mirrors/raw/branch/master/mirrorlist
2025-11-22 23:15:27 +00:00
2025-12-08 05:11:32 +00:00
sudo curl --fail --silent ${mirrorlist} -o /etc/pacman.d/mirrorlist
2025-11-22 23:15:27 +00:00
if checkupdates | grep -q 'archlinux-keyring'
then
sudo pacman -Sy --noconfirm archlinux-keyring
echo
fi
if sudo pacman -Syu --noconfirm
then
echo
sudo pacdiff
fi
syu
# System services
systemctl -q enable systemd-networkd.service systemd-resolved.service sshd.service iptables.service qemu-guest-agent.service ${systemdbootservice}
su user <<"CHANGEUSER"
# SSH
ssh-keygen -t ed25519 -q -f ~/.ssh/id_ed25519 -P ""
echo "${sshkeys[@]}" > ~/.ssh/authorized_keys
# bash
# cp /etc/skel/.bash* ~/
cat >> ~/.bashrc <<'BASHRC'
unset HISTFILE
alias ll='ls -l -a -h'
# Grep color
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
# Adjust terminal upon window resize
shopt -s checkwinsize
# Auto cd into directory
shopt -s autocd
# Ignore duplicate and whitespace history entries
export HISTCONTROL=ignoreboth
#
# ~/.bash_aliases
#
# Reboot and poweroff
alias poweroff='sudo poweroff'
alias reboot='sudo reboot'
# Miscellanous pacman
alias orphans='pacman -Rcns $(pacman -Qtdq)'
alias unlockpacmandb='rm /var/lib/pacman/db.lck && pacman -Syy'
# Rsync
alias rsync='rsync --progress --info=progress2 -v -h'
#
# ~/.bash_functions
#
# Pacman tools
function installer
{
sudo pacman -S "$@"
echo
}
function uninstall
{
sudo pacman -Rcns "$@"
echo
}
function syur
{
/usr/local/bin/syu &&
reboot
}
function syup
{
/usr/local/bin/syu &&
poweroff
}
# Update configs
function update-bash
{
vim ~/.bashrc &&
source ~/.bashrc
}
BASHRC
CHANGEUSER
SYU
# Script for installing desktop environment
install /dev/stdin /mnt/usr/local/bin/desktop <<'DESKTOPINSTALL'
#!/usr/bin/env bash
set -a -E
# Exit function
trap '[ "$?" -ne 77 ] || exit 77' ERR
die()
{
echo -e '\e[1;31m\nError encountered, script aborted...\n\e[0m'
exit 77
}
# 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
echo -e '\n\e[31mNo internet connectivity detected'
echo -e 'Connect to a network and try again'
echo -e 'Aborting installer...\e[0m\n'
die
fi
echo -e '\n\e[1;35mSelect a desktop\e[0m'
echo -e '\t1) i3'
echo -e '\t2) none'
until [[ ${desktop} = [12] ]]
do
read -n 1 -p '> ' desktop
[[ ${desktop} = [12] ]] || echo -e '\n\n\e[1;31mInvalid selection, type an option from 1 to 2\e[0m'
done
# Assign DE variables
case ${desktop} in
1)
# Install and configure x
echo -e '\n\n\t\e[1mYou have chosen \e[32mi3\e[0m\e[1m desktop\e[0m'
echo -e '\n\e[1;35mInstalling base packages\e[0m'
sudo pacman -S --noconfirm spice-vdagent xf86-video-qxl xorg xorg-xinit phonon-qt5-gstreamer ttf-dejavu ttf-liberation noto-fonts noto-fonts-cjk noto-fonts-emoji firefox i3-gaps i3status i3lock dmenu lightdm lightdm-gtk-greeter pavucontrol konsole kate dolphin kompare breeze-icons
echo
echo 'exec i3' > ~/.xinitrc
echo -e '\nXDG_CURRENT_DESKTOP=gnome' | sudo tee -a /etc/environment >/dev/null
# i3
mkdir -p ~/.config/i3
curl -s -L https://raw.githubusercontent.com/i3/i3/next/etc/config | sed 's/exec i3-config-wizard/# &/' > ~/.config/i3/config
tee -a ~/.config/i3/config >/dev/null <<'I3CONFIG'
exec xrandr --output $(xrandr -q | grep -w 'connected primary' | awk '{print $1}') --mode 1920x1080
gaps inner 8
gaps outer 4
# for_window [class="^.*"] border pixel 2
exec spice-vdagent
I3CONFIG
# i3 function
echo -e '\n# i3 config
function i3-config
{
vim ~/.config/i3/config
}' >> ~/.bash_functions
cat >> ~/.bashrc <<'BASHRC'
# Autostart i3
if [ -z "$DISPLAY" ] && [ "$XDG_VTNR" = 1 ]
then
exec startx
fi
BASHRC
;;
2) echo
;;
esac
sudo rm -f ${0}
case ${desktop} in
1)
echo -e '\e[1;34mDesktop installed, press any key to load i3\e[5m...\e[0m\n'
read -n 1 -s
sudo systemctl -q enable --now lightdm.service
;;
2)
echo -e '\e[1;34mSetup complete, press any key to continue\e[5m...\e[0m\n'
read -n 1 -s
;;
*)
die
;;
esac
DESKTOPINSTALL
# Reboot only if script succeeded
if /usr/bin/sh -c 'arch-chroot /mnt uname -a' | grep -q Linux
then
umount -R /mnt
echo -e '\e[1;34mInstaller has completed and system drive has been unmounted'
echo -e 'Boot into the new system, connect to a network and install a DE by running \e[35mdesktop\e[34m in the terminal'
echo -e 'Rebooting...\n\e[0m'
reboot
else
die
fi