Update install_arch_solphyr.sh

This commit is contained in:
Conor Budworth 2025-11-09 04:30:02 +01:00
parent d9a519f755
commit 154deb02cf

View File

@ -1,12 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# ──────────────────────────────────────────────────────────────── # ───────────────────────────────────────────────
# Solphyr Arch Linux Auto-Installer # Solphyr Arch Linux Auto-Installer (base only)
# Full LUKS2 encryption • systemd-boot • yay • WireGuard • Hardening # LUKS2 • systemd-boot • yay • reproducible .env
# ──────────────────────────────────────────────────────────────── # ───────────────────────────────────────────────
# Colour helpers
b=$(tput bold); r=$(tput sgr0); g=$(tput setaf 2); y=$(tput setaf 3); c=$(tput setaf 6); e=$(tput setaf 1) b=$(tput bold); r=$(tput sgr0); g=$(tput setaf 2); y=$(tput setaf 3); c=$(tput setaf 6); e=$(tput setaf 1)
info(){ echo "${c}==>${r} $*"; } info(){ echo "${c}==>${r} $*"; }
warn(){ echo "${y}!${r} $*"; } warn(){ echo "${y}!${r} $*"; }
@ -15,80 +14,45 @@ die(){ echo "${e}x${r} $*" >&2; exit 1; }
NO_PROMPT=0 NO_PROMPT=0
[[ "${1:-}" =~ (--no-prompt|--auto) ]] && NO_PROMPT=1 [[ "${1:-}" =~ (--no-prompt|--auto) ]] && NO_PROMPT=1
# ── Load or create .env ─────────────────────────────────────────
[[ ! -f .env ]] && { warn ".env missing, creating from template"; cp .env.template .env 2>/dev/null || touch .env; } [[ ! -f .env ]] && { warn ".env missing, creating from template"; cp .env.template .env 2>/dev/null || touch .env; }
source .env || true source .env || true
# ── Safe prompting helpers ───────────────────────────────────── # ── Prompt helpers ─────────────────────────────
# --- Safe prompting helpers ---
prompt_var() { prompt_var() {
local var_name="$1" local var_name="$1" prompt_text="$2"
local prompt_text="$2" local default_val; default_val="$(eval "echo \${$var_name:-}")"
local default_val if [[ "$NO_PROMPT" == "1" ]]; then info "Using $var_name=$default_val"; return 0; fi
default_val="$(eval "echo \${$var_name:-}")" local input; set +e
read -e -p "$prompt_text [${default_val}]: " input; local rc=$?; set -e
if [[ "$NO_PROMPT" == "1" ]]; then
info "Using $var_name=$default_val"
return 0
fi
local input
# disable errexit for the read itself
set +e
read -e -p "$prompt_text [${default_val}]: " input
local rc=$?
set -e
[[ $rc -ne 0 ]] && die "Input cancelled" [[ $rc -ne 0 ]] && die "Input cancelled"
if [[ -n "$input" ]]; then [[ -n "$input" ]] && eval "$var_name=\"\$input\""
eval "$var_name=\"\$input\""
fi
return 0
} }
prompt_secret() { prompt_secret() {
local var_name="$1" local var_name="$1" prompt_text="$2"
local prompt_text="$2" local default_val; default_val="$(eval "echo \${$var_name:-}")"
local default_val
default_val="$(eval "echo \${$var_name:-}")"
if [[ -z "$default_val" ]]; then if [[ -z "$default_val" ]]; then
set +e set +e; read -s -p "$prompt_text: " input; local rc=$?; set -e; echo
read -s -p "$prompt_text: " input
local rc=$?
set -e
echo
[[ $rc -ne 0 ]] && die "Input cancelled" [[ $rc -ne 0 ]] && die "Input cancelled"
eval "$var_name=\"\$input\"" eval "$var_name=\"\$input\""
else else info "Using $var_name from .env"; fi
info "Using $var_name from .env"
fi
return 0
} }
save_env(){ local n=$1 v; v="$(eval "echo \${$n}")"; sed -i "/^${n}=/d" .env; echo "${n}=\"${v}\"" >> .env; }
save_env() { # ── Gather config ──────────────────────────────
local var_name="$1"
local var_value
var_value="$(eval "echo \${$var_name}")"
sed -i "/^${var_name}=/d" .env
echo "${var_name}=\"${var_value}\"" >> .env
return 0
}
# ── Gather configuration ────────────────────────────────────────
prompt_var HOSTNAME "Hostname"; prompt_var USERNAME "Username" prompt_var HOSTNAME "Hostname"; prompt_var USERNAME "Username"
prompt_var KEYMAP "Keymap"; prompt_var TIMEZONE "Timezone"; prompt_var LOCALE "Locale" prompt_var KEYMAP "Keymap"; prompt_var TIMEZONE "Timezone"; prompt_var LOCALE "Locale"
save_env HOSTNAME; save_env USERNAME; save_env KEYMAP; save_env TIMEZONE; save_env LOCALE save_env HOSTNAME; save_env USERNAME; save_env KEYMAP; save_env TIMEZONE; save_env LOCALE
# ── Networking ───────────────────────────────────────────────── # ── Network ────────────────────────────────────
timedatectl set-ntp true timedatectl set-ntp true
if [[ -z "$(ip link | grep -E 'state UP')" ]]; then if [[ -z "$(ip link | grep -E 'state UP')" ]]; then
warn "No active network found" warn "No active network found"
prompt_var WIFI_SSID "Wi-Fi SSID"; prompt_secret WIFI_PASS "Wi-Fi password" prompt_var WIFI_SSID "Wi-Fi SSID"; prompt_secret WIFI_PASS "Wi-Fi password"
iwctl station wlan0 scan || true iwctl station wlan0 scan || true
iwctl station wlan0 connect "$WIFI_SSID" --passphrase "$WIFI_PASS" || die "Wi-Fi connection failed" iwctl station wlan0 connect "$WIFI_SSID" --passphrase "$WIFI_PASS" || die "Wi-Fi failed"
else info "Network detected"; fi else info "Network detected"; fi
# ── Disk selection ───────────────────────────────────────────── # ── Disk ───────────────────────────────────────
if [[ -z "${DISK:-}" ]]; then if [[ -z "${DISK:-}" ]]; then
info "Available disks:"; lsblk -dno NAME,SIZE,MODEL | grep -v loop | nl -w2 -s'. ' info "Available disks:"; lsblk -dno NAME,SIZE,MODEL | grep -v loop | nl -w2 -s'. '
[[ $NO_PROMPT == 1 ]] && die "No DISK specified in .env" [[ $NO_PROMPT == 1 ]] && die "No DISK specified in .env"
@ -97,7 +61,6 @@ fi; save_env DISK
EFI="${DISK}p1"; ROOT="${DISK}p2" EFI="${DISK}p1"; ROOT="${DISK}p2"
read -p "${e}${b}⚠️ Erase $DISK? ENTER to confirm${r}" read -p "${e}${b}⚠️ Erase $DISK? ENTER to confirm${r}"
# ── Partition + encryption ─────────────────────────────────────
sgdisk --zap-all "$DISK" sgdisk --zap-all "$DISK"
sgdisk -n1:0:+1G -t1:ef00 -c1:"EFI System" "$DISK" sgdisk -n1:0:+1G -t1:ef00 -c1:"EFI System" "$DISK"
sgdisk -n2:0:0 -t2:8300 -c2:"Linux LUKS" "$DISK"; partprobe "$DISK" sgdisk -n2:0:0 -t2:8300 -c2:"Linux LUKS" "$DISK"; partprobe "$DISK"
@ -108,14 +71,13 @@ echo -n "$LUKS_PASSWORD" | cryptsetup open "$ROOT" cryptroot -
mkfs.fat -F32 "$EFI"; mkfs.ext4 /dev/mapper/cryptroot mkfs.fat -F32 "$EFI"; mkfs.ext4 /dev/mapper/cryptroot
mount /dev/mapper/cryptroot /mnt; mkdir /mnt/boot; mount "$EFI" /mnt/boot mount /dev/mapper/cryptroot /mnt; mkdir /mnt/boot; mount "$EFI" /mnt/boot
# ── Mirrors + base system ────────────────────────────────────── # ── Base system ────────────────────────────────
pacman -Sy --noconfirm reflector pacman -Sy --noconfirm reflector
reflector --country "United Kingdom" --latest 20 --sort rate --save /etc/pacman.d/mirrorlist reflector --country "United Kingdom" --latest 20 --sort rate --save /etc/pacman.d/mirrorlist
pacstrap -K /mnt base linux linux-firmware vim networkmanager sudo base-devel git pacstrap -K /mnt base linux linux-firmware vim networkmanager sudo base-devel git
genfstab -U /mnt >> /mnt/etc/fstab genfstab -U /mnt >> /mnt/etc/fstab
# ── System configuration inside chroot ───────────────────────── # ── Configure system ───────────────────────────
arch-chroot /mnt /bin/bash <<CHROOT arch-chroot /mnt /bin/bash <<CHROOT
set -euo pipefail set -euo pipefail
ln -sf /usr/share/zoneinfo/$TIMEZONE /etc/localtime ln -sf /usr/share/zoneinfo/$TIMEZONE /etc/localtime
@ -130,17 +92,13 @@ cat <<EOF >/etc/hosts
127.0.1.1 $HOSTNAME.localdomain $HOSTNAME 127.0.1.1 $HOSTNAME.localdomain $HOSTNAME
EOF EOF
echo "KEYMAP=$KEYMAP" > /etc/vconsole.conf echo "KEYMAP=$KEYMAP" > /etc/vconsole.conf
sed -i 's/\(filesystems\)/encrypt \1/' /etc/mkinitcpio.conf sed -i 's/\(filesystems\)/encrypt \1/' /etc/mkinitcpio.conf
mkinitcpio -P mkinitcpio -P
bootctl install bootctl install
# Fix insecure permissions from FAT32
chmod 600 /boot/loader/random-seed 2>/dev/null || true chmod 600 /boot/loader/random-seed 2>/dev/null || true
chmod 700 /boot/loader 2>/dev/null || true chmod 700 /boot/loader 2>/dev/null || true
chmod 700 /boot/EFI/systemd 2>/dev/null || true chmod 700 /boot/EFI/systemd 2>/dev/null || true
chown -R root:root /boot /boot/EFI || true chown -R root:root /boot /boot/EFI || true
UUID=\$(blkid -s UUID -o value ${ROOT}) UUID=\$(blkid -s UUID -o value ${ROOT})
cat <<EOF >/boot/loader/loader.conf cat <<EOF >/boot/loader/loader.conf
default arch.conf default arch.conf
@ -154,13 +112,10 @@ linux /vmlinuz-linux
initrd /initramfs-linux.img initrd /initramfs-linux.img
options cryptdevice=UUID=\${UUID}:cryptroot root=/dev/mapper/cryptroot rw options cryptdevice=UUID=\${UUID}:cryptroot root=/dev/mapper/cryptroot rw
EOF EOF
# Create user but skip passwords (set later outside heredoc)
useradd -m -G wheel -s /bin/bash $USERNAME useradd -m -G wheel -s /bin/bash $USERNAME
sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers
systemctl enable NetworkManager systemctl enable NetworkManager
# yay installation
# yay (AUR helper)
if [[ "${INSTALL_YAY,,}" == "yes" ]]; then if [[ "${INSTALL_YAY,,}" == "yes" ]]; then
pacman -S --noconfirm base-devel git go pacman -S --noconfirm base-devel git go
echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/00-yay-temp echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/00-yay-temp
@ -175,58 +130,25 @@ if [[ "${INSTALL_YAY,,}" == "yes" ]]; then
' '
rm -f /etc/sudoers.d/00-yay-temp rm -f /etc/sudoers.d/00-yay-temp
fi fi
# Hardening
if [[ "${ENABLE_HARDENING,,}" == "yes" ]]; then
pacman -Sy --noconfirm ufw apparmor fail2ban archlinux-keyring
systemctl enable ufw; systemctl start ufw
ufw default deny incoming; ufw default allow outgoing; ufw enable
systemctl enable apparmor; systemctl enable fail2ban
cat <<EOF >/etc/sysctl.d/99-hardening.conf
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
kernel.randomize_va_space = 2
EOF
sysctl --system
echo 'Defaults timestamp_timeout=5' > /etc/sudoers.d/hardening
fi
CHROOT CHROOT
# ── Interactive password setup (outside heredoc) ─────────────── # ── Passwords (interactive) ────────────────────
info "Set root password" info "Set root password"
until arch-chroot /mnt passwd; do echo "Try again."; done until arch-chroot /mnt passwd; do echo "Try again."; done
info "Set password for ${USERNAME}"
info "Set password for user ${USERNAME}"
until arch-chroot /mnt passwd "${USERNAME}"; do echo "Try again."; done until arch-chroot /mnt passwd "${USERNAME}"; do echo "Try again."; done
# ── WireGuard (optional) ────────────────────────────────────────
if [[ "${ENABLE_WIREGUARD,,}" == "yes" && -f "${WIREGUARD_CONF_PATH}" ]]; then
info "Installing WireGuard..."
install -Dm600 "$WIREGUARD_CONF_PATH" "/mnt/etc/wireguard/wg0.conf"
arch-chroot /mnt pacman -Sy --noconfirm wireguard-tools
arch-chroot /mnt systemctl enable wg-quick@wg0.service
else
warn "WireGuard skipped (no config or disabled)."
fi
# ── Finish ─────────────────────────────────────────────────────
umount -R /mnt umount -R /mnt
cryptsetup close cryptroot cryptsetup close cryptroot
info "Installation complete" info "Installation complete"
cat <<EOF cat <<EOF
${g}${b}✅ Arch Linux installation complete!${r} ${g}${b}✅ Base Arch Linux installation complete!${r}
Remove installation media and reboot: Remove install media and reboot:
${b}reboot${r} ${b}reboot${r}
Then log in as '${USERNAME}' and pull your next setup, e.g.: Then log in as '${USERNAME}' and run:
${c}wget https://your.gitea.instance/raw/setup_dev_env.sh && bash setup_dev_env.sh${r} ${c}wget https://your.gitea.instance/raw/setup_postinstall.sh && bash setup_postinstall.sh${r}
EOF EOF