413 lines
12 KiB
Bash
Executable File
413 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
BANNER=$(cat << "EOF"
|
|
:^7J5GB##&&##GPY?~:
|
|
^75B&@@@@@@&&&@@@@@@@#GJ~:
|
|
5&@@@&B5?7~^^^^^~!7YP#@@@@#!
|
|
Y##P7^ :~JB#B!
|
|
:: :
|
|
7PP?: :^~!!~^: :?PP7
|
|
:B@@B: !5B&@@@@&B5! :#@@B:
|
|
:!!: ^G@@@&BPPB@@@@G^ :!!:
|
|
:B@@@5^ ^5@@@B:
|
|
:7J7: !@@@# :&@@@~ :?J7:
|
|
J@@@5 :#@@@Y: :Y@@@B: 5@@@J
|
|
!@@@&^ ~B@@@&G55G&@@@B~ ~&@@@~
|
|
5@@@G: :7P#@@@@@@#P7: :B@@@Y
|
|
:P@@@B~ :~!77!~: ~B@@@P
|
|
Y@@@&Y^ ^5@@@@J
|
|
!G@@@&P7^ ^7P&@@@G~
|
|
!P&@@@&B? :: ?B&@@@&P!
|
|
^75#&&Y :P&&5: 5&&B57^
|
|
:^^ :P&&5: ^^:
|
|
^^
|
|
|
|
[nullpoint]
|
|
EOF
|
|
)
|
|
|
|
TANG_SERVERS=(
|
|
# "https://tang1.example.com your-thumbprint-1"
|
|
# "https://tang2.example.com your-thumbprint-2"
|
|
)
|
|
TPM_PCR_BANK="sha256"
|
|
TPM_PCR_IDS="0,1,2,3,4,5,6,7,8"
|
|
FEDORA_VERSION="42"
|
|
FEDORA_USER="null"
|
|
ENABLE_MOTD=true
|
|
SSH_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOkoTn2NreAXMriOUqzyj3YoFW6jMo9B5B+3R5k8yrMi dodox@dodox-ProArt"
|
|
|
|
########################################################
|
|
# Config End
|
|
########################################################
|
|
|
|
set -euo pipefail
|
|
alias c="clear"
|
|
alias cls="clear"
|
|
clear
|
|
echo -e "\n$BANNER"
|
|
echo -e "\n[+] Starting installation..."
|
|
|
|
# Check for TPM
|
|
echo "[+] Checking for TPM..."
|
|
if [ ! -d "/sys/class/tpm/tpm0" ]; then
|
|
echo "WARNING: No TPM detected!"
|
|
echo "This system will not be able to use TPM-based boot verification."
|
|
echo "You might need to enable TPM in the BIOS. (On Hetzner make a support ticket for KVM access)"
|
|
read -p "Continue without TPM? [y/N] " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Installation aborted."
|
|
exit 1
|
|
fi
|
|
echo "Proceeding without TPM..."
|
|
TPM_ENABLED=false
|
|
else
|
|
echo "TPM detected."
|
|
TPM_ENABLED=true
|
|
fi
|
|
|
|
# Check for SSH key
|
|
if [ -z "${SSH_KEY:-}" ]; then
|
|
echo "No SSH key provided. Please enter your public SSH key:"
|
|
read -r SSH_KEY
|
|
if [ -z "$SSH_KEY" ]; then
|
|
echo "Error: SSH key is required for installation"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Generate secure LUKS passphrase
|
|
echo "[+] Generating secure LUKS passphrase..."
|
|
LUKS_PASSPHRASE=$(openssl rand -base64 30)
|
|
|
|
echo "----------------------------------------"
|
|
echo "Generated LUKS passphrase:"
|
|
echo "${LUKS_PASSPHRASE}"
|
|
echo "----------------------------------------"
|
|
echo "Please save these credentials securely. You will need them for recovery."
|
|
echo "Press Enter to continue..."
|
|
read
|
|
|
|
# Install required packages
|
|
apt-get update
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y genisoimage grub-efi-amd64-bin util-linux kexec-tools
|
|
|
|
# Detect disk naming scheme and set variables
|
|
echo "[+] Detecting disk configuration..."
|
|
DISKS=($(lsblk -d -n -o NAME | grep -E '^(sd[a-z]|nvme[0-9]+n[0-9]+)$' | sort))
|
|
if [ ${#DISKS[@]} -lt 1 ]; then
|
|
echo "Error: Expected at least 1 disk, found ${#DISKS[@]}"
|
|
exit 1
|
|
fi
|
|
|
|
DISK="/dev/${DISKS[0]}"
|
|
|
|
# Create a small partition for installer files
|
|
echo "[+] Creating installer partition..."
|
|
parted -s $DISK mklabel gpt
|
|
parted -s $DISK mkpart primary ext4 0% 2GB
|
|
parted -s $DISK name 1 installer
|
|
mkfs.ext4 -L installer ${DISK}1
|
|
mkdir -p /mnt/installer
|
|
mount ${DISK}1 /mnt/installer
|
|
|
|
# Download Fedora installer
|
|
echo "[+] Downloading Fedora installer..."
|
|
wget -O /mnt/installer/Fedora-Server-netinst-x86_64-${FEDORA_VERSION}-1.1.iso "https://download.fedoraproject.org/pub/fedora/linux/releases/${FEDORA_VERSION}/Server/x86_64/iso/Fedora-Server-netinst-x86_64-${FEDORA_VERSION}-1.1.iso"
|
|
|
|
# Mount Fedora ISO
|
|
echo "[+] Mounting Fedora installer..."
|
|
mkdir -p /mnt/iso
|
|
mount -o loop /mnt/installer/Fedora-Server-netinst-x86_64-${FEDORA_VERSION}-1.1.iso /mnt/iso
|
|
|
|
# Get current IP address and gateway from first non-loopback interface
|
|
echo "[+] Detecting current IP address and gateway..."
|
|
INTERFACE=$(ip -o -4 route show to default | awk '{print $5}' | head -n1)
|
|
IPV4=$(ip -4 addr show $INTERFACE | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}')
|
|
GATEWAY=$(ip route show default | awk '/default/ {print $3}')
|
|
|
|
echo "[+] Detected network configuration:"
|
|
echo "Interface: $INTERFACE"
|
|
echo "IP: $IPV4"
|
|
echo "Gateway: $GATEWAY"
|
|
|
|
# Create kickstart file
|
|
echo "[+] Creating kickstart configuration..."
|
|
cat > /mnt/installer/ks.cfg << 'KICKSTART'
|
|
# Fedora Server installation with our secure setup
|
|
text
|
|
lang en_US.UTF-8
|
|
keyboard us
|
|
timezone --utc Etc/UTC
|
|
|
|
# Security settings
|
|
selinux --enforcing
|
|
rootpw --lock
|
|
user --name=${FEDORA_USER} --groups=wheel --shell=/bin/bash --lock
|
|
|
|
# SSH setup
|
|
sshkey --username=${FEDORA_USER} "${SSH_KEY}"
|
|
|
|
# Network - let installer detect interface
|
|
network --bootproto=static --ip=${IPV4} --netmask=255.255.255.255 --gateway=${GATEWAY} --nameserver=185.12.64.1 --nameserver=185.12.64.2 --activate
|
|
|
|
# Bootloader
|
|
bootloader --timeout=1 --location=mbr --append="no_timer_check console=tty1 console=ttyS0,115200n8"
|
|
|
|
# Services
|
|
services --enabled=sshd,clevis-luks-askpass,dropbear
|
|
|
|
# Use existing partitions
|
|
part /boot/efi --fstype=vfat --onpart=${PART1}
|
|
part /boot --fstype=ext4 --onpart=/dev/md0
|
|
part / --fstype=btrfs --onpart=/dev/mapper/root_a
|
|
|
|
# Packages
|
|
%packages
|
|
@^server-product-environment
|
|
clevis
|
|
clevis-luks
|
|
clevis-tang
|
|
clevis-tpm2
|
|
tpm2-tools
|
|
tpm2-tss
|
|
cryptsetup
|
|
btrfs-progs
|
|
mdadm
|
|
dropbear
|
|
git
|
|
zsh
|
|
lsd
|
|
bat
|
|
tmux
|
|
neovim
|
|
fortune-mod
|
|
cowsay
|
|
lolcat
|
|
xclip
|
|
python3-pip
|
|
%end
|
|
|
|
# Pre-installation
|
|
%pre
|
|
#!/bin/bash
|
|
|
|
# Use the LUKS passphrase from rescue script
|
|
LUKS_PASSPHRASE="${LUKS_PASSPHRASE}"
|
|
echo "\$LUKS_PASSPHRASE" > /tmp/luks.key
|
|
chmod 600 /tmp/luks.key
|
|
|
|
# Detect disk naming scheme and set variables
|
|
DISKS=($(lsblk -d -n -o NAME | grep -E '^(sd[a-z]|nvme[0-9]+n[0-9]+)$' | sort))
|
|
if [ ${#DISKS[@]} -ne 2 ]; then
|
|
echo "Error: Expected exactly 2 disks, found ${#DISKS[@]}"
|
|
exit 1
|
|
fi
|
|
|
|
# Set disk variables
|
|
DISK1="/dev/${DISKS[0]}"
|
|
DISK2="/dev/${DISKS[1]}"
|
|
|
|
# Stop any existing RAID arrays
|
|
mdadm --stop /dev/md0 2>/dev/null || true
|
|
|
|
# Unmount any existing partitions
|
|
for disk in $DISK1 $DISK2; do
|
|
umount -f $disk* 2>/dev/null || true
|
|
done
|
|
|
|
# Stop any device mapper devices
|
|
dmsetup remove_all 2>/dev/null || true
|
|
|
|
# Disconnect NVMe devices if present
|
|
if [[ "$DISK1" =~ nvme ]] || [[ "$DISK2" =~ nvme ]]; then
|
|
nvme disconnect-all
|
|
sleep 2
|
|
fi
|
|
|
|
# Zero out partition tables
|
|
for disk in $DISK1 $DISK2; do
|
|
if [[ "$disk" =~ nvme ]]; then
|
|
blkdiscard -f $disk
|
|
else
|
|
dd if=/dev/zero of=$disk bs=1M count=2 conv=fsync
|
|
dd if=/dev/zero of=$disk bs=1M seek=$(($(blockdev --getsz $disk) / 2048 - 2)) count=2 conv=fsync
|
|
fi
|
|
sync
|
|
done
|
|
|
|
# Create partitions
|
|
for disk in $DISK1 $DISK2; do
|
|
parted -s $disk mklabel gpt
|
|
parted -s $disk mkpart primary fat32 0% 512MB
|
|
parted -s $disk mkpart primary ext4 512MB 1.5GB
|
|
parted -s $disk mkpart primary ext4 1.5GB 100%
|
|
parted -s $disk set 1 boot on
|
|
sync
|
|
done
|
|
|
|
# For NVMe disks, we need to append 'p' to partition numbers
|
|
if [[ "$DISK1" =~ nvme ]]; then
|
|
PART1="${DISK1}p1"
|
|
PART2="${DISK1}p2"
|
|
PART3="${DISK1}p3"
|
|
PART4="${DISK2}p1"
|
|
PART5="${DISK2}p2"
|
|
PART6="${DISK2}p3"
|
|
else
|
|
PART1="${DISK1}1"
|
|
PART2="${DISK1}2"
|
|
PART3="${DISK1}3"
|
|
PART4="${DISK2}1"
|
|
PART5="${DISK2}2"
|
|
PART6="${DISK2}3"
|
|
fi
|
|
|
|
# Create EFI partitions
|
|
mkfs.vfat -F 32 $PART1
|
|
mkfs.vfat -F 32 $PART4
|
|
|
|
# Create boot RAID1
|
|
mdadm --create /dev/md0 --level=1 --raid-devices=2 --metadata=0.90 --force --run $PART2 $PART5
|
|
mkfs.ext4 /dev/md0
|
|
|
|
# Create LUKS volumes
|
|
echo "$LUKS_PASSPHRASE" | cryptsetup luksFormat $PART3 --type luks2
|
|
echo "$LUKS_PASSPHRASE" | cryptsetup luksFormat $PART6 --type luks2
|
|
|
|
# Open LUKS volumes
|
|
echo "$LUKS_PASSPHRASE" | cryptsetup luksOpen $PART3 root_a
|
|
echo "$LUKS_PASSPHRASE" | cryptsetup luksOpen $PART6 root_b
|
|
|
|
# Create BTRFS RAID1
|
|
mkfs.btrfs -f -d raid1 -m raid1 /dev/mapper/root_a /dev/mapper/root_b
|
|
|
|
# Create subvolumes
|
|
mount /dev/mapper/root_a /mnt/sysimage
|
|
btrfs subvolume create /mnt/sysimage/@root
|
|
btrfs subvolume create /mnt/sysimage/@home
|
|
btrfs subvolume create /mnt/sysimage/@db
|
|
chattr +C /mnt/sysimage/@db
|
|
|
|
# Configure Clevis
|
|
if [ ${#TANG_SERVERS[@]} -gt 0 ] || [ "$TPM_ENABLED" = true ]; then
|
|
mkdir -p /mnt/sysimage/etc/clevis
|
|
|
|
# Build Tang servers JSON array if we have any
|
|
if [ ${#TANG_SERVERS[@]} -gt 0 ]; then
|
|
TANG_JSON="["
|
|
for server in "${TANG_SERVERS[@]}"; do
|
|
read -r url thumbprint <<< "$server"
|
|
TANG_JSON+="{\"url\":\"$url\",\"thumbprint\":\"$thumbprint\"},"
|
|
done
|
|
TANG_JSON="${TANG_JSON%,}]" # Remove trailing comma and close array
|
|
fi
|
|
|
|
# Calculate t value based on enabled methods
|
|
T_VALUE=1
|
|
if [ ${#TANG_SERVERS[@]} -gt 0 ] && [ "$TPM_ENABLED" = true ]; then
|
|
T_VALUE=2
|
|
fi
|
|
|
|
# Create Clevis config
|
|
cat > /mnt/sysimage/etc/clevis/clevis.conf << EOF
|
|
{
|
|
"t": $T_VALUE,
|
|
"pins": {
|
|
$([ "$TPM_ENABLED" = true ] && echo "\"tpm2\": {
|
|
\"pcr_bank\": \"$TPM_PCR_BANK\",
|
|
\"pcr_ids\": \"$TPM_PCR_IDS\"
|
|
},")
|
|
$([ ${#TANG_SERVERS[@]} -gt 0 ] && echo "\"tang\": {
|
|
\"t\": 1,
|
|
\"tang\": $TANG_JSON
|
|
}")
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Bind LUKS volumes
|
|
clevis luks bind -d $PART3 sss -c /mnt/sysimage/etc/clevis/clevis.conf
|
|
clevis luks bind -d $PART6 sss -c /mnt/sysimage/etc/clevis/clevis.conf
|
|
fi
|
|
|
|
# Cleanup
|
|
umount /mnt/sysimage
|
|
%end
|
|
|
|
# Post-installation
|
|
%post
|
|
# Configure network with static IP (Hetzner dedicated server style)
|
|
cat > /etc/sysconfig/network-scripts/ifcfg-ens3 << EOF
|
|
DEVICE=ens3
|
|
ONBOOT=yes
|
|
BOOTPROTO=static
|
|
IPADDR=$IPV4
|
|
NETMASK=255.255.255.255
|
|
SCOPE="peer $GATEWAY"
|
|
EOF
|
|
|
|
# Create route file
|
|
cat > /etc/sysconfig/network-scripts/route-ens3 << EOF
|
|
ADDRESS0=0.0.0.0
|
|
NETMASK0=0.0.0.0
|
|
GATEWAY0=$GATEWAY
|
|
EOF
|
|
|
|
# Reload network configuration
|
|
nmcli con reload || true
|
|
nmcli con up ens3 || true
|
|
|
|
# Update fstab
|
|
cat > /etc/fstab << "FSTAB"
|
|
${PART1} /boot/efi vfat defaults 1 2
|
|
/dev/md0 /boot ext4 defaults 1 2
|
|
/dev/mapper/root_a / btrfs subvol=@root,defaults,noatime 0 0
|
|
/dev/mapper/root_a /home btrfs subvol=@home,defaults,noatime 0 0
|
|
/dev/mapper/root_a /db btrfs subvol=@db,defaults,noatime,nodatacow 0 0
|
|
FSTAB
|
|
|
|
# Configure dropbear for early SSH access
|
|
mkdir -p /etc/dracut.conf.d
|
|
cat > /etc/dracut.conf.d/dropbear.conf << "DROPBEAR"
|
|
add_drivers+=" dropbear "
|
|
install_optional_items=yes
|
|
DROPBEAR
|
|
|
|
# Add SSH key to dropbear
|
|
mkdir -p /etc/dropbear
|
|
echo "$SSH_KEY" > /etc/dropbear/authorized_keys
|
|
chmod 600 /etc/dropbear/authorized_keys
|
|
|
|
# Regenerate initramfs with dropbear
|
|
dracut -f
|
|
|
|
# Set up MOTD
|
|
if [ "$ENABLE_MOTD" = true ]; then
|
|
cat > /etc/motd << "MOTD"
|
|
$BANNER
|
|
MOTD
|
|
fi
|
|
|
|
# Enable required services
|
|
systemctl enable clevis-luks-askpass
|
|
systemctl enable dropbear
|
|
|
|
# Force SELinux relabel on next boot
|
|
touch /.autorelabel
|
|
%end
|
|
KICKSTART
|
|
|
|
# Get actual ISO label
|
|
ISO_LABEL=$(isoinfo -d -i /mnt/installer/Fedora-Server-netinst-x86_64-${FEDORA_VERSION}-1.1.iso | grep "Volume id:" | cut -d: -f2 | tr -d ' ')
|
|
|
|
# Set IP-related kernel boot params for installer
|
|
KERNEL_NET_PARAMS="ip=$IPV4::$GATEWAY:255.255.255.255::ens3:none nameserver=185.12.64.1 nameserver=185.12.64.2"
|
|
|
|
echo "[+] Configuration complete. Starting installation with kexec..."
|
|
echo "----------------------------------------"
|
|
cat /mnt/installer/ks.cfg
|
|
echo "----------------------------------------"
|
|
read -p "Press Enter to continue..."
|
|
kexec -l /mnt/iso/images/pxeboot/vmlinuz --initrd=/mnt/iso/images/pxeboot/initrd.img --append="inst.ks=file:///dev/disk/by-label/installer/ks.cfg inst.stage2=hd:LABEL=${ISO_LABEL} ${KERNEL_NET_PARAMS} inst.sshd inst.ssh.port=2222 inst.ssh.key=${SSH_KEY}"
|
|
kexec -e |