Reorganized hal CLI into subcommand groups + MIT licensed
CLI structure now:
hal {status,diagnose,unlock,forget} HOST
hal connect {rescue,chroot,server} HOST [CMD]
hal setup {image,dropbear,grub,encrypt-root} HOST
hal fix {boot,network,grub,kernel,static-ip,upgrade,expand-fs} HOST
Added subcommands cover the previously-manual sections of the README:
setup image — upload autosetup + run installimage
setup dropbear — install dropbear + mkinitcpio plugins + patch HOOKS
setup grub — initial grub install for LUKS boot
setup encrypt-root — full LUKS conversion of installed root
connect server — SSH to booted Arch (vs rescue/chroot)
unlock — cryptroot-unlock via dropbear with passphrase from keyring
fix expand-fs — lvresize + btrfs resize
Renames (breaking):
upgrade-system -> fix upgrade
expand-fs -> fix expand-fs
forget-passphrase -> forget
reinstall-grub -> fix grub
downgrade-kernel -> fix kernel
use-static-ip -> fix static-ip
fix-{boot,network} -> fix {boot,network}
install-{image,grub} -> setup {image,grub}
setup-dropbear -> setup dropbear
encrypt-root -> setup encrypt-root
Removed downgrade-initramfs (never verified, narrow use case).
README rewritten to reference only hal commands; raw bash blocks for
pacman/cryptsetup/grub-install/mount/chroot are gone. Added autosetup.example
as a template for `hal setup image --autosetup PATH`.
Licensed under MIT (LICENSE file added). Author and homepage shown in
hal --version, hal --help, pyproject.toml, and README.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
59
src/hetzner_arch_luks/resources/setup/dropbear.sh
Normal file
59
src/hetzner_arch_luks/resources/setup/dropbear.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# Runs on the BOOTED Arch system (post-installimage, pre-encryption).
|
||||
# Wires up dropbear + encryptssh + netconf for later remote-LUKS-unlock.
|
||||
#
|
||||
# Performs sections 3.1–3.5 of the README:
|
||||
# - install busybox / mkinitcpio-{dropbear,utils,netconf}
|
||||
# - copy authorized_keys to /etc/dropbear/root_key
|
||||
# - regenerate OpenSSH host keys in PEM format
|
||||
# - convert RSA host key to dropbear format
|
||||
# - replace the HOOKS line in /etc/mkinitcpio.conf
|
||||
#
|
||||
# Idempotent: re-running is safe. A backup of /etc/mkinitcpio.conf is taken
|
||||
# at first patch as /etc/mkinitcpio.conf.hal-backup.
|
||||
|
||||
set -e
|
||||
|
||||
banner() { printf "\n========== %s ==========\n" "$1"; }
|
||||
|
||||
banner "installing dropbear + mkinitcpio plugins"
|
||||
pacman -S --noconfirm --needed \
|
||||
busybox mkinitcpio-dropbear mkinitcpio-utils mkinitcpio-netconf
|
||||
|
||||
banner "copying authorized_keys to /etc/dropbear/root_key"
|
||||
install -d -m 0755 /etc/dropbear
|
||||
install -m 0600 /root/.ssh/authorized_keys /etc/dropbear/root_key
|
||||
chmod 700 /root/.ssh
|
||||
chmod 600 /root/.ssh/authorized_keys
|
||||
|
||||
banner "enabling sshd"
|
||||
systemctl enable sshd
|
||||
|
||||
banner "regenerating OpenSSH host keys (PEM format)"
|
||||
rm -f /etc/ssh/ssh_host_*
|
||||
ssh-keygen -A -m PEM
|
||||
|
||||
banner "importing RSA host key into dropbear"
|
||||
dropbearconvert openssh dropbear \
|
||||
/etc/ssh/ssh_host_rsa_key /etc/dropbear/dropbear_rsa_host_key
|
||||
|
||||
banner "patching HOOKS in /etc/mkinitcpio.conf"
|
||||
[ -f /etc/mkinitcpio.conf.hal-backup ] \
|
||||
|| cp -a /etc/mkinitcpio.conf /etc/mkinitcpio.conf.hal-backup
|
||||
|
||||
# Replace any existing HOOKS=(...) line with the encryptssh-enabled set.
|
||||
sed -i -E \
|
||||
's|^HOOKS=.*|HOOKS=(base udev autodetect modconf block mdadm_udev lvm2 netconf dropbear encryptssh filesystems keyboard fsck)|' \
|
||||
/etc/mkinitcpio.conf
|
||||
|
||||
echo "HOOKS line is now:"
|
||||
grep '^HOOKS=' /etc/mkinitcpio.conf
|
||||
|
||||
banner "done"
|
||||
cat <<EOF
|
||||
Next steps:
|
||||
1. Activate Hetzner Rescue in the Robot, then reboot the server.
|
||||
2. From your client: hal connect rescue <host>
|
||||
3. Inside rescue: hal encrypt-root <host>
|
||||
4. After that: hal install-grub <host>
|
||||
EOF
|
||||
106
src/hetzner_arch_luks/resources/setup/encrypt_root.sh
Normal file
106
src/hetzner_arch_luks/resources/setup/encrypt_root.sh
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# Runs IN HETZNER RESCUE (NOT in chroot). Re-creates the root LV stack on
|
||||
# top of LUKS, preserving the installed Arch by copying it through /oldroot.
|
||||
#
|
||||
# Performs sections 4.4–4.15 of the README in one go:
|
||||
# 4.4 mount the unencrypted /dev/mapper/vg0-root
|
||||
# 4.5 cp -va /mnt → /oldroot at full RAID resync speed
|
||||
# 4.6 umount /mnt
|
||||
# 4.7 vgremove vg0
|
||||
# 4.8 cat /proc/mdstat (display)
|
||||
# 4.9 luksFormat /dev/md1 (prompts for NEW passphrase!)
|
||||
# luksOpen + recreate LVM (vg0 with swap + root)
|
||||
# mkfs.btrfs / mkswap
|
||||
# 4.10 mount the encrypted root at /mnt
|
||||
# 4.12 cp -va /oldroot back into /mnt at full RAID resync speed
|
||||
# 4.13 bind /dev /sys /proc, mount /boot
|
||||
# 4.14 echo cryptroot line into /mnt/etc/crypttab
|
||||
# 4.15 chroot + mkinitcpio -P
|
||||
#
|
||||
# DESTRUCTIVE: /dev/md1 will be re-formatted with LUKS. Any data not under
|
||||
# /mnt (vg0-root) is lost. Confirmation prompted before the format step.
|
||||
|
||||
set -e
|
||||
|
||||
banner() { printf "\n========== %s ==========\n" "$1"; }
|
||||
|
||||
banner "4.4 mount existing unencrypted root"
|
||||
vgscan -v
|
||||
vgchange -a y
|
||||
mount /dev/mapper/vg0-root /mnt
|
||||
|
||||
banner "4.5 copy current system to /oldroot (full RAID resync speed)"
|
||||
mkdir -p /oldroot
|
||||
echo 0 > /proc/sys/dev/raid/speed_limit_max
|
||||
cp -va /mnt/. /oldroot/.
|
||||
echo 200000 > /proc/sys/dev/raid/speed_limit_max
|
||||
|
||||
banner "4.6 unmount original root"
|
||||
umount /mnt
|
||||
|
||||
banner "4.7 remove unencrypted VG (frees /dev/md1)"
|
||||
vgremove -f vg0
|
||||
|
||||
banner "4.8 RAID state"
|
||||
cat /proc/mdstat
|
||||
|
||||
banner "CONFIRMATION REQUIRED"
|
||||
echo "About to luksFormat /dev/md1. This is DESTRUCTIVE for /dev/md1."
|
||||
echo "Type 'YES' to continue (anything else aborts):"
|
||||
read -r confirm
|
||||
if [ "$confirm" != "YES" ]; then
|
||||
echo "Aborted by user before luksFormat. /oldroot still has your data;"
|
||||
echo "you can re-create the original LVM by hand from there if needed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
banner "4.9 LUKS format /dev/md1 (you will be prompted for the NEW passphrase)"
|
||||
cryptsetup --cipher aes-xts-plain64 --key-size 256 --hash sha256 \
|
||||
--iter-time 10000 luksFormat /dev/md1
|
||||
|
||||
banner "4.9b open the LUKS volume (re-enter the same passphrase)"
|
||||
cryptsetup luksOpen /dev/md1 cryptroot
|
||||
|
||||
banner "4.9c recreate LVM on top of /dev/mapper/cryptroot"
|
||||
pvcreate /dev/mapper/cryptroot
|
||||
vgcreate vg0 /dev/mapper/cryptroot
|
||||
lvcreate -n swap -L 8G vg0
|
||||
lvcreate -n root -l 100%FREE vg0
|
||||
mkfs.btrfs /dev/vg0/root
|
||||
mkswap /dev/vg0/swap
|
||||
|
||||
banner "4.10 mount the encrypted root"
|
||||
mount /dev/vg0/root /mnt
|
||||
|
||||
banner "4.12 copy system back into the encrypted root"
|
||||
echo 0 > /proc/sys/dev/raid/speed_limit_max
|
||||
cp -va /oldroot/. /mnt/.
|
||||
echo 200000 > /proc/sys/dev/raid/speed_limit_max
|
||||
|
||||
banner "4.13 bind-mount /dev /sys /proc, mount /boot"
|
||||
mount /dev/md0 /mnt/boot
|
||||
mount --bind /dev /mnt/dev
|
||||
mount --bind /sys /mnt/sys
|
||||
mount --bind /proc /mnt/proc
|
||||
|
||||
banner "4.14 append cryptroot line to /etc/crypttab"
|
||||
if ! grep -qE '^cryptroot[[:space:]]' /mnt/etc/crypttab 2>/dev/null; then
|
||||
echo "cryptroot /dev/md1 none luks" >> /mnt/etc/crypttab
|
||||
fi
|
||||
grep cryptroot /mnt/etc/crypttab
|
||||
|
||||
banner "4.15 regenerate initramfs inside chroot"
|
||||
chroot /mnt /bin/bash -c "mkinitcpio -P"
|
||||
|
||||
banner "done"
|
||||
cat <<EOF
|
||||
Encryption setup complete. /oldroot can be deleted manually after you've
|
||||
confirmed the encrypted boot works.
|
||||
|
||||
Recommended next steps:
|
||||
hal install-grub <host> # configures GRUB for LUKS-encrypted root
|
||||
hal connect rescue <host> reboot
|
||||
# Disable rescue in Hetzner Robot
|
||||
hal status <host> # poll for dropbear / sshd
|
||||
hal unlock <host> # send LUKS passphrase to dropbear
|
||||
EOF
|
||||
78
src/hetzner_arch_luks/resources/setup/grub.sh
Normal file
78
src/hetzner_arch_luks/resources/setup/grub.sh
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
# Runs INSIDE the chroot. Initial GRUB install for the LUKS-encrypted root.
|
||||
# Performs sections 5.1–5.3 of the README:
|
||||
# - install the grub package
|
||||
# - write /etc/default/grub with the LUKS cmdline + GRUB_ENABLE_CRYPTODISK=y
|
||||
# - grub-mkconfig
|
||||
# - grub-install on every disk backing /boot's RAID
|
||||
|
||||
set -e
|
||||
|
||||
banner() { printf "\n========== %s ==========\n" "$1"; }
|
||||
|
||||
# Convert a partition path to its parent disk. (Same helper as fix/grub.sh.)
|
||||
parent_disk() {
|
||||
local part="$1"
|
||||
case "$part" in
|
||||
/dev/nvme[0-9]*n[0-9]*p[0-9]*) echo "${part%p[0-9]*}" ;;
|
||||
/dev/mmcblk[0-9]*p[0-9]*) echo "${part%p[0-9]*}" ;;
|
||||
/dev/sd[a-z]*[0-9]*) echo "$part" | sed -E 's/[0-9]+$//' ;;
|
||||
/dev/vd[a-z]*[0-9]*) echo "$part" | sed -E 's/[0-9]+$//' ;;
|
||||
*)
|
||||
local d
|
||||
d=$(lsblk -no PKNAME "$part" 2>/dev/null | head -1)
|
||||
[ -n "$d" ] && echo "/dev/$d"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
banner "installing grub package"
|
||||
pacman -S --noconfirm --needed grub
|
||||
|
||||
banner "writing /etc/default/grub for LUKS boot"
|
||||
[ -f /etc/default/grub.hal-backup ] || cp -a /etc/default/grub /etc/default/grub.hal-backup
|
||||
cat > /etc/default/grub <<'GRUBEOF'
|
||||
# hetzner-arch-luks default grub config
|
||||
GRUB_DEFAULT=0
|
||||
GRUB_TIMEOUT=5
|
||||
GRUB_DISTRIBUTOR="Arch"
|
||||
GRUB_CMDLINE_LINUX_DEFAULT="consoleblank=0"
|
||||
GRUB_CMDLINE_LINUX="cryptdevice=/dev/md1:cryptroot ip=dhcp"
|
||||
GRUB_PRELOAD_MODULES="part_gpt part_msdos"
|
||||
GRUB_ENABLE_CRYPTODISK=y
|
||||
GRUB_TIMEOUT_STYLE=menu
|
||||
GRUB_TERMINAL_INPUT=console
|
||||
GRUB_GFXMODE=auto
|
||||
GRUB_GFXPAYLOAD_LINUX=keep
|
||||
GRUB_DISABLE_RECOVERY=true
|
||||
GRUBEOF
|
||||
|
||||
echo "Wrote /etc/default/grub. Showing relevant lines:"
|
||||
grep -E '^GRUB_(CMDLINE_LINUX|ENABLE_CRYPTODISK|PRELOAD_MODULES)=' /etc/default/grub
|
||||
|
||||
banner "identifying boot disks (members of md0)"
|
||||
BOOT_DISKS=()
|
||||
for part in $(mdadm --detail /dev/md0 2>/dev/null | awk '/active sync/ {print $NF}'); do
|
||||
disk=$(parent_disk "$part")
|
||||
[ -z "$disk" ] && continue
|
||||
already=0
|
||||
for d in "${BOOT_DISKS[@]}"; do [ "$d" = "$disk" ] && already=1; done
|
||||
[ "$already" -eq 0 ] && BOOT_DISKS+=("$disk")
|
||||
done
|
||||
echo "Boot disks: ${BOOT_DISKS[*]}"
|
||||
|
||||
banner "grub-mkconfig"
|
||||
grub-mkconfig -o /boot/grub/grub.cfg 2>&1 | tail -10
|
||||
|
||||
banner "grub-install on each boot disk"
|
||||
for disk in "${BOOT_DISKS[@]}"; do
|
||||
echo "-- grub-install --target=i386-pc --recheck $disk"
|
||||
grub-install --target=i386-pc --recheck "$disk"
|
||||
done
|
||||
|
||||
banner "done"
|
||||
cat <<EOF
|
||||
GRUB installed for LUKS-encrypted boot.
|
||||
Recommended next step: hal use-static-ip <host> (replaces ip=dhcp with a
|
||||
static kernel-cmdline IP, making the initramfs network independent of DHCP).
|
||||
EOF
|
||||
Reference in New Issue
Block a user