#!/usr/bin/env bash set -a set -E revision=1.0h ## 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 } # Repeat a command until it exits with code 0 function repeat { until ${@} do sleep 3 done } # Read 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 ;; -? | -h | --help ) cat <<- help Parameters: -s, --ssh-key Add SSH public key (enclosed in quotes) -?, -h, --help This help screen help exit 0 ;; * ) die "Unknown flag: ${1}" ;; esac shift done # Ensure pacman.conf settings are properly set if [ ! -f /etc/pacman.conf.bkp ] then cp /etc/pacman.conf /etc/pacman.conf.bkp fi sed -e "/Color/c Color" \ -e "/ParallelDownloads/c ParallelDownloads = 10" \ -e "/^\[core\]$\|^\[extra\]$/a CacheServer = http://192.168.122.1:9090" \ /etc/pacman.conf.bkp >/etc/pacman.conf # 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 curl --fail --silent https://git.myvelabs.com/lab/archlinux/raw/branch/master/mirrorlist -o /etc/pacman.d/mirrorlist || die "Unable to fetch latest mirrorlist" else die 'No internet connectivity detected, try again' fi clear cat <<- title $(tput setaf 7 smul dim)## rev ${revision}${reset} ${blue}${bold} Arch VM ${reset} title # Default systemd services systemd_services=(systemd-networkd.service systemd-resolved.service sshd.service iptables.service wireguard-startup.service) # Firmware if ls -l /dev/disk/* | grep -q VBOX then linux_firmware+=(virtualbox-guest-utils) systemd_services+=(vboxservice.service) installation_disk=sda else linux_firmware+=(qemu-guest-agent spice-vdagent) systemd_services+=(qemu-guest-agent.service) 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 fi if lspci | grep VGA | grep -q QXL then linux_firmware+=(xf86-video-qxl) fi # Generate username and hostname hostname=$(cat /dev/urandom | tr -d -c 'a-zA-Z' | fold -w 6 | head -n 1) # Unmount if mounted for mount in /mnt/boot /mnt do until ! mount | grep -q ${mount} do umount ${mount} done done say "Partitioning /dev/${installation_disk}" yellow bold wipefs -f -a /dev/${installation_disk} echo if [ -d /sys/firmware/efi/efivars/ ] then parted --script --align=optimal /dev/${installation_disk} \ mklabel gpt \ mkpart boot 1MiB 100MiB \ mkpart zroot 100MiB 100% \ set 1 esp on partprobe /dev/${installation_disk} else bios=syslinux parted --script --align=optimal /dev/${installation_disk} \ mklabel msdos \ mkpart primary 1MiB 100MiB \ mkpart primary 100MiB 100% \ set 1 boot on partprobe /dev/${installation_disk} fi # Assign partitions export {efivol,bootvol}=/dev/$(lsblk -l -o name | grep "${installation_disk}.*1$") export {rootpart,rootvol}=/dev/$(lsblk -l -o name | grep "${installation_disk}.*2$") say "Configuring volumes" yellow bold yes | mkfs.ext4 ${rootvol} mount -o noatime ${rootvol} /mnt # Boot partition yes | mkfs.fat -F 32 ${efivol} mount --mkdir -o uid=0,gid=0,fmask=0077,dmask=0077 ${bootvol} /mnt/boot echo # Temporarily disable mkinitcpio ln -s -f /dev/null /etc/pacman.d/hooks/90-mkinitcpio-install.hook # Install Arch on new root say "Installing Arch Linux on /dev/${installation_disk}" yellow bold archpkgs="sudo-rs fakeroot \ ${linux_firmware[@]} ${bios} \ reflector openssh vim \ git rsync pacman-contrib bash-completion ffmpegthumbs \ xorg xorg-xinit \ i3-wm i3status dmenu konsole kate dolphin kompare breeze-icons kde-cli-tools ttf-dejavu \ firefox firefox-decentraleyes firefox-ublock-origin \ chromium \ wireguard-tools systemd-resolvconf \ noto-fonts-cjk \ iptables-nft pipewire-jack \ openbsd-netcat debugedit" # Pacstrap repeat pacstrap -K /mnt --ask 4 \ linux base mkinitcpio \ ${archpkgs} echo # Generate fs table genfstab -U -p /mnt >>/mnt/etc/fstab # Make custom directories mkdir -p /mnt/etc/{pacman.d/hooks,makepkg.conf.d,systemd/journald.conf.d}/ \ /mnt/opt/local/{bin,hooks}/ # makepkg cat >/mnt/etc/makepkg.conf.d/zz-makepkg.conf <<- makepkg PKGEXT=".pkg.tar" MAKEFLAGS="--jobs=$(nproc)" COMPRESSZST=(zstd -c -T0 --auto-threads=logical -) makepkg # pacman.conf hook install /dev/stdin /mnt/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\]$\|^\[extra\]$/a CacheServer = http://192.168.122.1:9090' \ /etc/pacman.conf.pacnew >/etc/pacman.conf rm /etc/pacman.conf.pacnew fi hook cat >/mnt/etc/pacman.d/hooks/100-pacman.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 # Configure pacman cache server sed -e '/ParallelDownloads/c ParallelDownloads = 10' \ -e '/Color/c Color' \ -e '/^\[core\]$\|^\[extra\]$/a CacheServer = http://192.168.122.1:9090' \ -i /mnt/etc/pacman.conf # Mkinitcpio decompress echo 'MODULES_DECOMPRESS="yes"' >/mnt/etc/mkinitcpio.conf.d/zz-modules.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 rsync -a /mnt/usr/lib/modules/*/vmlinuz /mnt/boot/vmlinuz-linux # 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/ if [ -d /sys/firmware/efi/efivars/ ] then # Systemd-boot say "Configuring systemd-boot" yellow bold arch-chroot -S /mnt bootctl --path=/boot/ install cat >/mnt/boot/loader/entries/arch.conf <<- systemd-boot title Arch Linux linux /vmlinuz-linux initrd /initramfs-linux.img options root=${rootpart} rw quiet systemd-boot cat >/mnt/boot/loader/loader.conf <<- entry default arch timeout 0 console-mode max editor no entry systemd_services+=(systemd-boot-update.service) else # Syslinux say "Configuring syslinux" yellow bold mkdir /mnt/boot/syslinux/ arch-chroot /mnt extlinux --install /boot/syslinux/ arch-chroot /mnt syslinux-install_update -i -a -m cat >/mnt/boot/syslinux/syslinux.cfg <<-syslinux DEFAULT arch PROMPT 0 TIMEOUT 0 MENU TITLE Arch Linux MENU COLOR border 30;44 #40ffffff #a0000000 std MENU COLOR title 1;36;44 #9033ccff #a0000000 std MENU COLOR sel 7;37;40 #e0ffffff #20ffffff all MENU COLOR unsel 37;44 #50ffffff #a0000000 std MENU COLOR help 37;40 #c0ffffff #a0000000 std MENU COLOR timeout_msg 37;40 #80ffffff #00000000 std MENU COLOR timeout 1;37;40 #c0ffffff #00000000 std MENU COLOR msg07 37;40 #90ffffff #a0000000 std MENU COLOR tabmsg 31;40 #30ffffff #00000000 std LABEL arch MENU LABEL Arch Linux LINUX ../vmlinuz-linux APPEND root=${rootpart} rw quiet INITRD ../initramfs-linux.img LABEL hdt MENU LABEL HDT (Hardware Detection Tool) COM32 hdt.c32 LABEL reboot MENU LABEL Reboot COM32 reboot.c32 LABEL poweroff MENU LABEL Poweroff COM32 poweroff.c32 syslinux fi echo arch-chroot /mnt /usr/bin/bash <<- "CHROOT" # Root bashrc cat >> /etc/skel/.bashrc <<- 'bashrc' # Add local functions folder to path export PATH=${PATH}:/opt/local/bin # Source bash functions if [ -d ~/.local/functions ] then for file in $(find ~/.local/functions -type f) do . ${file} done fi # Disable history unset HISTFILE rm -f ${HISTFILE} history -c -w # Rsync alias rsync='rsync --progress --info=progress2 -v -h' bashrc cp /etc/skel/.bashrc ~/ # 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 $(find /usr/share/zoneinfo/ | shuf -n 1) /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 /usr/bin/bash user || die 'User account creation has failed' # echo -e '%wheel ALL=(ALL:ALL) NOPASSWD: ALL' >/etc/sudoers.d/zz-NOPASSWD # 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 cat >/etc/pam.d/sudo <<- 'sudo' #%PAM-1.0 auth include system-auth account include system-auth session include system-auth sudo ln -s -f /etc/pam.d/sudo /etc/pam.d/sudo-i install -m 0440 /dev/stdin /etc/sudoers-rs <<- 'DEFAULTS' Defaults!/usr/bin/visudo env_keep += "SUDO_EDITOR EDITOR VISUAL" Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/bin" root ALL=(ALL:ALL) ALL %wheel ALL=(ALL:ALL) NOPASSWD: ALL DEFAULTS ln -s -f /usr/bin/sudo-rs /usr/local/bin/sudo # Disable root account passwd -dl root >/dev/null 2>&1 mkdir -p /etc/systemd/system/getty@tty1.service.d/ cat >/etc/systemd/system/getty@tty1.service.d/override.conf <<- 'autologin' [Service] ExecStart= ExecStart=-/usr/bin/agetty --autologin user --noclear %I $TERM autologin say "Configured root user" yellow bold echo say "Regenerating cpio image" yellow bold mkinitcpio -P echo # Paccache hook cat >/etc/pacman.d/hooks/zz-paccache.hook </etc/sysctl.d/10-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 # Accept all connections from hypervisor subnet -A INPUT -s 192.168.122.0/24 -j ACCEPT ## Simple Firewall IPTABLES sed "/OUTPUT ACCEPT/r /etc/iptables/userinput.rules" /etc/iptables/simple_firewall.rules >/etc/iptables/iptables.rules # iptables hook install /dev/stdin /opt/local/hooks/iptables </etc/iptables/iptables.rules.pacsave mv /etc/iptables/iptables.rules.pacsave /etc/iptables/iptables.rules fi hook cat >/etc/pacman.d/hooks/100-iptables.hook </dev/null fi hook # locale.gen.pacnew hook cat >/etc/pacman.d/hooks/100-localegen.hook </etc/pacman.d/hooks/85-mkinitcpio.conf.hook </etc/mkinitcpio.d/linux.preset hook # Wireguard install /dev/stdin /usr/local/bin/wireguard-startup <<- 'wireguard' #!/usr/bin/env bash wireguard=$(ls /etc/wireguard | shuf -n 1 | sed 's/.conf//') /usr/bin/wg-quick up ${wireguard} /usr/bin/wg-quick down ${wireguard} /usr/bin/wg-quick up ${wireguard} wireguard cat >/etc/systemd/system/wireguard-startup.service <<- 'wireguard' [Unit] Description=Start wireguard on boot [Service] ExecStart=/usr/local/bin/wireguard-startup [Install] WantedBy=multi-user.target wireguard # SSH cat >/etc/ssh/sshd_config.d/zz-sshd.conf </etc/ssh/ssh_config.d/zz-ssh.conf </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 cat >/etc/systemd/journald.conf.d/zz-journald.conf <<- eof [Journal] Storage=persistent SystemMaxUse=100M eof # Default environment vars tee -a /etc/environment >/dev/null <<- environment EDITOR=vim SUDO_EDITOR=vim environment su user <<- "CHANGEUSER" # ssh identity ssh-keygen -q \ -t ed25519 \ -f ~/.ssh/id_ed25519 \ -C "user@${hostname}" \ -P "" # Config dirs mkdir -p ~/.local/functions/ \ ~/.config/{i3,menus}/ \ ~/.local/share/konsole/ # i3-config curl --fail --silent https://raw.githubusercontent.com/i3/i3/next/etc/config | sed 's/exec i3-config-wizard/# &/' > ~/.config/i3/config cat >> ~/.config/i3/config <<- 'i3' exec xrandr --output $(xrandr -q | grep -w 'connected primary' | awk '{print $1}') --mode 1920x1080 exec xrandr --dpi 192 exec spice-vdagent exec VBoxClient-all i3 # Konsole cat > ~/.config/konsolerc <<- konsole [Desktop Entry] DefaultProfile=profile.profile [General] ConfigVersion=1 [UiSettings] ColorScheme= konsole cat >~/.local/share/konsole/profile.profile <<- 'profile' [Appearance] Font=monospace,16,-1,2,400,0,0,0,0,0,0,0,0,0,0,1 [General] Name=profile Parent=FALLBACK/ [Scrolling] HistoryMode=2 profile # 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 # Autostart i3 cat >> ~/.bash_profile <<- 'autostart' if [ -z "$DISPLAY" ] && [ "$XDG_VTNR" = 1 ] then exec startx /usr/bin/i3 fi autostart cat > ~/.local/functions/i3 <<- 'i3' # Edit i3 config function i3-config { vim ~/.config/i3/config } i3 cat > ~/.local/functions/alias <<- 'alias' # # ~/.bash_aliases # # ls alias ll='ls -l -a -h' # Grep color alias grep='grep --color=auto' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' # Ignore duplicate and whitespace history entries export HISTCONTROL=ignoreboth # Reboot and poweroff alias poweroff='sudo poweroff' alias reboot='sudo reboot' # Miscellanous pacman alias orphans='sudo pacman -Rcns $(pacman -Qtdq)' alias unlockpacmandb='sudo rm /var/lib/pacman/db.lck && sudo pacman -Syy' alias cat > ~/.local/functions/functions <<- 'functions' # # ~/.bash_functions # # Pacman tools function installer { sudo pacman -S ${@} echo } function uninstall { sudo pacman -Rcns "$@" echo } function syur { /opt/local/bin/syu && reboot } function syup { /opt/local/bin/syu && poweroff } # Update configs function update-bash { vim ~/.bashrc && source ~/.bashrc } functions cat > ~/.local/functions/wireguard <<- 'wg' # Wireguard function wglist { if [ ${1} ] then sudo ls /etc/wireguard | grep ${1} else sudo ls /etc/wireguard fi } function wgupdate { if ls | grep -q ".*\.conf" then sudo find /etc/wireguard -type f -delete sudo cp -v * /etc/wireguard echo -e '\n\t\e[1;32mWireguard profiles updated\e[0m\n' fi } function wd { for wireguard in $(sudo wg show | grep interface | awk '{print $2}') do sudo wg-quick down ${wireguard} done } function wu { wd if [ ${1} ] then sudo wg-quick up $(sudo ls /etc/wireguard | grep ${1} | shuf -n 1 | sed -e 's/.conf//') else sudo wg-quick up $(sudo ls /etc/wireguard | shuf -n 1 | sed -e 's/.conf//') fi } wg CHANGEUSER CHROOT # System services arch-chroot /mnt systemctl enable ${systemd_services[@]} || die "Unable to start systemd services" # SSH Authentication if [ -f sshkeys ] then cat sshkeys >/mnt/home/user/.ssh/authorized_keys fi # Custom pacman wrapper install /dev/stdin /mnt/opt/local/bin/syu <<- 'syu' #!/usr/bin/env bash set -e # Fetch latest mirrors sudo reflector --age 24 --latest 10 --protocol https --save /etc/pacman.d/mirrorlist # 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 # 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 # Script for installing desktop environment install /dev/stdin /mnt/opt/local/bin/startup <<'DESKTOPINSTALL' #!/usr/bin/env bash # 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 sudo vim /opt/local/bin/startupscript /opt/local/bin/startupscript exit 0 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' exit 1 fi DESKTOPINSTALL install /dev/stdin /mnt/opt/local/bin/startupscript <<'DESKTOPINSTALL' #!/usr/bin/env bash # AUR package list aur_list=( # jdownloader2 # instaloader # czkawka-gui-bin # ledger-udev ledger-live-bin ) function enable-audio { sudo pacman -S --ask 4 pipewire pipewire-audio pipewire-pulse wireplumber echo systemctl --user --quiet enable --now wireplumber.service pipewire-pulse.service pipewire.service } function wireguard-setup { if ! sudo wg show | grep -q interface then echo -e '\n\e[31mNo wireguard profile installed\e[0m\n' exit 1 fi } function tor-installer { sudo pacman -S --ask 4 torbrowser-launcher echo torbrowser-launcher } wireguard-setup tor-installer # enable-audio # uncomment to enable audio # AUR packages if [ ${aur_list} ] then for package in ${aur_list[@]} do cd ~/ git clone https://aur.archlinux.org/${package}.git cd ${package}/ makepkg -csi echo cd ~ rm -rf ${package}/ done fi # Add user to shared folder group if in virtualbox guest if ls -l /dev/disk/* | grep -q VBOX then sudo gpasswd -a user vboxsf >/dev/null fi echo -e '\n\e[1;32mSupplementary installer completed, reboot one last time\e[0m' sudo rm -f /opt/local/bin/startup* sudo systemctl reboot DESKTOPINSTALL # Reboot only if script succeeded if arch-chroot /mnt uname -a | grep -q Linux then for mount in /mnt/boot /mnt do until ! mount | grep -q ${mount} do umount ${mount} done done echo -e '\e[1;34mInstaller has completed and system drive has been unmounted Boot into the new system, connect to a network and install a DE by running \e[35mdesktop\e[34m in the terminal Rebooting...\n\e[0m' reboot else die 'Something does not feel right' fi