#!/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 ### End of user interaction # Install required package apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y genisoimage grub-efi-amd64-bin util-linux # 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[@]} -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 echo "[+] Stopping any existing RAID arrays..." mdadm --stop /dev/md0 2>/dev/null || true # Unmount any existing partitions echo "[+] Unmounting partitions..." for disk in $DISK1 $DISK2; do umount -f $disk* 2>/dev/null || true done # Stop any device mapper devices echo "[+] Stopping device mapper devices..." dmsetup remove_all 2>/dev/null || true # Disconnect NVMe devices if present if [[ "$DISK1" =~ nvme ]] || [[ "$DISK2" =~ nvme ]]; then echo "Disconnecting NVMe devices..." nvme disconnect-all sleep 2 fi # Zero out partition tables echo "[+] Zeroing out partition tables..." for disk in $DISK1 $DISK2; do # Use blkdiscard for NVMe drives, dd for others if [[ "$disk" =~ nvme ]]; then blkdiscard -f $disk else # Zero first 2MB and last 2MB of disk 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 echo "[+] Creating partitions..." for disk in $DISK1 $DISK2; do # Create new GPT partition table parted -s $disk mklabel gpt # Create EFI partition (512MB) parted -s $disk mkpart primary fat32 0% 512MB # Create boot partition (1GB) parted -s $disk mkpart primary ext4 512MB 1.5GB # Create root partition (rest of disk) parted -s $disk mkpart primary ext4 1.5GB 100% # Set boot flag on first partition 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 echo "Detected disks:" echo "Disk 1: $DISK1 (will make partitions: $PART1, $PART2, $PART3)" echo "Disk 2: $DISK2 (will make partitions: $PART4, $PART5, $PART6)" # Create EFI partitions echo "[+] Creating EFI partitions..." mkfs.vfat -F 32 $PART1 mkfs.vfat -F 32 $PART4 # Create boot RAID1 echo "[+] Creating boot RAID1 array..." mdadm --create /dev/md0 --level=1 --raid-devices=2 --metadata=0.90 --force --run $PART2 $PART5 mkfs.ext4 /dev/md0 # Create LUKS volumes echo "[+] Setting up LUKS encryption..." echo "${LUKS_PASSPHRASE}" | cryptsetup luksFormat $PART3 --type luks2 echo "${LUKS_PASSPHRASE}" | cryptsetup luksFormat $PART6 --type luks2 # Open LUKS volumes echo "[+] Opening LUKS volumes..." echo "${LUKS_PASSPHRASE}" | cryptsetup luksOpen $PART3 root_a echo "${LUKS_PASSPHRASE}" | cryptsetup luksOpen $PART6 root_b # Create BTRFS RAID1 echo "[+] Creating BTRFS RAID1 filesystem..." mkfs.btrfs -f -d raid1 -m raid1 /dev/mapper/root_a /dev/mapper/root_b # Create subvolumes echo "[+] Creating BTRFS subvolumes..." mount /dev/mapper/root_a /mnt btrfs subvolume create /mnt/@root btrfs subvolume create /mnt/@home btrfs subvolume create /mnt/@db chattr +C /mnt/@db # Download Fedora installer echo "[+] Downloading Fedora installer..." wget "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 "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/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 # 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" :^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] MOTD fi # Configure Clevis if [ ${#TANG_SERVERS[@]} -gt 0 ] || [ "${TPM_ENABLED}" = true ]; then mkdir -p /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 # Create Clevis config cat > /etc/clevis/clevis.conf << "CLEVIS" { "t": 2, "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} }") } } CLEVIS # Bind LUKS volumes clevis luks bind -d ${PART3} sss -c /etc/clevis/clevis.conf clevis luks bind -d ${PART6} sss -c /etc/clevis/clevis.conf else echo "No Tang servers or TPM available, skipping Clevis setup" fi # Enable required services systemctl enable clevis-luks-askpass systemctl enable dropbear # Create TPM update script cat > /root/update-tpm-bindings.py << "TPMSCRIPT" #!/usr/bin/env python3 import subprocess import sys import os def run_command(cmd): try: result = subprocess.run(cmd, capture_output=True, text=True, check=True) return result.stdout.strip() except subprocess.CalledProcessError as e: print(f"Error running command: {' '.join(cmd)}") print(f"Error: {e.stderr}") sys.exit(1) def get_pcr_values(): print("Current PCR values:") for i in range(0, 9): pcr = run_command(['tpm2_pcrread', f'sha256:{i}']) print(f"PCR {i}: {pcr.split('=')[1].strip()}") def update_bindings(): print("\nUpdating TPM bindings...") devices = ['${PART3}', '${PART6}'] for device in devices: print(f"\nUpdating bindings for {device}") try: # Remove existing bindings run_command(['clevis', 'luks', 'unbind', '-d', device, '-s', '1']) run_command(['clevis', 'luks', 'unbind', '-d', device, '-s', '2']) # Create new bindings run_command(['clevis', 'luks', 'bind', '-d', device, 'sss', '-c', '/etc/clevis/clevis.conf']) print(f"Successfully updated bindings for {device}") except subprocess.CalledProcessError as e: print(f"Error updating bindings for {device}: {e.stderr}") continue def verify_bindings(): print("\nVerifying bindings...") devices = ['${PART3}', '${PART6}'] for device in devices: print(f"\nBindings for {device}:") try: bindings = run_command(['clevis', 'luks', 'list', '-d', device]) print(bindings) except subprocess.CalledProcessError as e: print(f"Error verifying bindings for {device}: {e.stderr}") continue def main(): if os.geteuid() != 0: print("This script must be run as root") sys.exit(1) print("TPM Binding Update Script") print("========================") get_pcr_values() update_bindings() verify_bindings() print("\nUpdate complete. Please reboot to test the new bindings.") if __name__ == "__main__": main() TPMSCRIPT chmod +x /root/update-tpm-bindings.py # Force SELinux relabel on next boot touch /.autorelabel # Cleanup echo "[+] Now rebooting..." reboot KICKSTART # Start Fedora installation echo "Starting Fedora installation..." echo "This will take some time. Please wait..." echo "SSH access will be available on port 2222" # Create ALL needed directories mkdir -p /mnt/boot mount /dev/md0 /mnt/boot mkdir -p /mnt/boot/efi mount $PART1 /mnt/boot/efi mkdir -p /mnt/boot/efi/EFI/fedora mkdir -p /mnt/boot/grub mkdir -p /mnt/boot/installer mkdir -p /mnt/iso # Now mount and copy cp /mnt/iso/images/pxeboot/vmlinuz /mnt/boot/installer/ cp /mnt/iso/images/pxeboot/initrd.img /mnt/boot/installer/ cp /mnt/ks.cfg /mnt/boot/installer/ # Get actual ISO label ISO_LABEL=$(isoinfo -d -i "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" cat > /mnt/boot/grub/grub.cfg << "GRUBCFG" set timeout=5 set default=0 menuentry "Fedora Installer" { linux /installer/vmlinuz inst.ks=file:///installer/ks.cfg inst.stage2=hd:LABEL=${ISO_LABEL} ${KERNEL_NET_PARAMS} inst.sshd inst.ssh.port=2222 inst.ssh.key=${SSH_KEY} initrd /installer/initrd.img } GRUBCFG # Install GRUB EFI grub-install --target=x86_64-efi --boot-directory=/mnt/boot --efi-directory=/mnt/boot/efi --removable $DISK1 grub-install --target=x86_64-efi --boot-directory=/mnt/boot --efi-directory=/mnt/boot/efi --removable $DISK2 umount /mnt/boot/efi umount /mnt/boot umount /mnt/iso #reboot