Refactor Debian 13 Trixie Packer templates for LUKS support

- Removed obsolete variable files: variables-common.pkr.hcl and variables.pkr.hcl.
- Updated debian-trixie.pkr.hcl to include local values for LUKS configuration.
- Modified boot command to include LUKS arguments based on the enable_luks variable.
- Enhanced initial-setup.sh to support LUKS detection and resizing.
- Replaced preseed.cfg with preseed.cfg.pkrtpl for dynamic LUKS configuration.
- Added enable_luks variable to control LUKS encryption during image build.
- Introduced luks.pkrvars.hcl for LUKS-specific variable settings.
- Updated mise.toml to support new variable file argument for Packer builds.
This commit is contained in:
Philip Henning 2026-05-11 19:13:11 +02:00
parent e57f2d977b
commit eded7180dc
20 changed files with 281 additions and 2445 deletions

View file

@ -7,6 +7,20 @@ packer {
}
}
locals {
image_slug = var.enable_luks ? "debian-13-trixie-luks" : "debian-13-trixie"
template_description = var.enable_luks ? "Debian 13 Trixie, LUKS encrypted, built with Packer on ${local.timestamp}\n\nLUKS default passphrase: `${var.default_luks_passphrase}`" : "Debian 13 Trixie, built with Packer on ${local.timestamp}"
luks_boot_args = var.enable_luks ? [
"partman-crypto/passphrase='${var.default_luks_passphrase}' ",
"partman-crypto/passphrase-again='${var.default_luks_passphrase}' ",
"INSTALL_FINISHED_INFORM_URL='http://{{ .HTTPIP }}:${var.install_finished_inform_port}/install_finished' ",
] : []
clevis_install_commands = var.enable_luks ? [
"apt-get update",
"apt-get install -y clevis clevis-luks clevis-initramfs",
] : ["true"]
}
source "proxmox-iso" "debian-13-trixie" {
# Proxmox Connection Settings
proxmox_url = "${var.proxmox_api_url}"
@ -19,8 +33,8 @@ source "proxmox-iso" "debian-13-trixie" {
# VM General Settings
node = "${var.proxmox_node}"
vm_id = "${var.template_vm_id}"
vm_name = "debian-13-trixie-${local.timestamp}"
template_description = "Debian 13 Trixie, built with Packer on ${local.timestamp}"
vm_name = "${local.image_slug}-${local.timestamp}"
template_description = "${local.template_description}"
os = "l26"
qemu_agent = true
@ -78,42 +92,26 @@ source "proxmox-iso" "debian-13-trixie" {
boot = "order=scsi0;scsi1"
boot_wait = "10s"
communicator = "ssh"
boot_command = [
boot_command = concat([
"<wait3>c<wait3>",
"linux /install.amd/vmlinuz auto-install/enable=true priority=critical ",
"DEBIAN_FRONTEND=text ",
"console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 ",
"passwd/root-password='${var.default_root_passphrase}' ",
"passwd/root-password-again='${var.default_root_passphrase}' ",
], local.luks_boot_args, [
"preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg noprompt<enter>",
"initrd /install.amd/initrd.gz<enter>",
"DEBCONF_DEBUG=5<enter>",
"boot<enter>"
]
# Static IP
# boot_command = [
# "<wait3>c<wait3>",
# "linux /install.amd/vmlinuz auto-install/enable=true priority=critical ",
# "DEBIAN_FRONTEND=text ",
# "console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200 consoleblank=0 ",
# "passwd/root-password='${var.default_root_passphrase}' ",
# "passwd/root-password-again='${var.default_root_passphrase}' ",
# "netcfg/disable_autoconfig=true ",
# "netcfg/get_ipaddress=172.16.2.254 ",
# "netcfg/get_netmask=255.255.255.0 ",
# "netcfg/get_gateway=172.16.2.3 ",
# "netcfg/get_nameservers=172.16.2.3 ",
# "netcfg/confirm_static=true ",
# "netcfg/get_hostname=debian-installer ",
# "netcfg/get_domain=local ",
# "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg noprompt<enter>",
# "initrd /install.amd/initrd.gz<enter>",
# "DEBCONF_DEBUG=5<enter>",
# "boot<enter>"
# ]
"boot<enter>",
])
# PACKER Autoinstall Settings
http_directory = "debian/13-trixie/http"
http_content = {
"/preseed.cfg" = templatefile("${abspath(path.root)}/http/preseed.cfg.pkrtpl", {
enable_luks = var.enable_luks
})
}
http_interface = "${var.source_proxmox_http_interface}"
# SSH Settings
@ -124,7 +122,7 @@ source "proxmox-iso" "debian-13-trixie" {
}
build {
name = "debian-13-trixie-image"
name = "${local.image_slug}-image"
sources = ["source.proxmox-iso.debian-13-trixie"]
# Install dependencies and default packages
@ -197,6 +195,11 @@ build {
]
}
# Install Clevis for LUKS builds
provisioner "shell" {
inline = local.clevis_install_commands
}
# Setup Serial Console for xterm.js in Proxmox VE
provisioner "shell" {
inline = [

View file

@ -5,12 +5,18 @@ IFS=$'\n\t'
SCRIPT_NAME="$(basename "$0")"
TASK_INDEX=0
TASK_TOTAL=0
TEMP_FILES=()
STORAGE_DETECTED=0
STORAGE_MODE=""
ROOT_SOURCE=""
VG_NAME=""
LV_NAME=""
PV_NAME=""
LUKS_DEV=""
LUKS_NAME=""
LUKS_PART=""
RESIZE_PART=""
DISK_DEV=""
PART_NUM=""
@ -56,6 +62,18 @@ section() {
echo "${DIM}------------------------------------------------------------${RESET}"
}
add_temp_file() {
TEMP_FILES+=("$1")
}
cleanup() {
local file
for file in "${TEMP_FILES[@]:-}"; do
[ -f "$file" ] && rm -f "$file"
done
}
trap cleanup EXIT
ensure_tty() {
if [ ! -t 0 ] || [ ! -t 1 ]; then
die "This setup must run interactively in a TTY."
@ -227,6 +245,29 @@ infer_disk_part_from_partition() {
return 1
}
resolve_luks_backing_partition() {
local mapper_path="$1"
local mapper_name="$2"
local mapper_name_alt="$3"
local part=""
if command -v cryptsetup >/dev/null 2>&1; then
part="$(cryptsetup status "$mapper_name" 2>/dev/null | awk '/^[[:space:]]*device:/ {print $2; exit}' | xargs || true)"
if [ -z "$part" ] && [ -n "$mapper_name_alt" ] && [ "$mapper_name_alt" != "$mapper_name" ]; then
part="$(cryptsetup status "$mapper_name_alt" 2>/dev/null | awk '/^[[:space:]]*device:/ {print $2; exit}' | xargs || true)"
fi
if [ -z "$part" ]; then
part="$(cryptsetup status "$mapper_path" 2>/dev/null | awk '/^[[:space:]]*device:/ {print $2; exit}' | xargs || true)"
fi
fi
if [ -z "$part" ]; then
part="$(lsblk -nro PATH,TYPE -s "$mapper_path" 2>/dev/null | awk '$2=="part" {print $1; exit}' | xargs || true)"
fi
printf '%s' "$part"
}
refresh_partition_table() {
local disk="$1"
if command -v partprobe >/dev/null 2>&1; then
@ -324,8 +365,20 @@ show_disk_layout() {
fi
}
resize_open_luks_mapping() {
if [ -n "$LUKS_NAME" ] && cryptsetup resize "$LUKS_NAME" >/dev/null 2>&1; then
return 0
fi
cryptsetup resize "$LUKS_DEV" >/dev/null 2>&1
}
detect_storage_stack() {
STORAGE_DETECTED=0
STORAGE_MODE=""
LUKS_DEV=""
LUKS_NAME=""
LUKS_PART=""
RESIZE_PART=""
DISK_DEV=""
PART_NUM=""
@ -358,10 +411,29 @@ detect_storage_stack() {
return 1
fi
local pv_type
pv_type="$(lsblk_attr "$PV_NAME" TYPE)"
if [ "$pv_type" = "crypt" ]; then
STORAGE_MODE="luks_lvm"
LUKS_DEV="$PV_NAME"
LUKS_NAME="$(basename "$LUKS_DEV")"
local mapper_name_alt
mapper_name_alt="$(lsblk_attr "$LUKS_DEV" NAME)"
LUKS_PART="$(resolve_luks_backing_partition "$LUKS_DEV" "$LUKS_NAME" "$mapper_name_alt")"
if [ -z "$LUKS_PART" ] || [ ! -b "$LUKS_PART" ]; then
log_warn "Unable to detect LUKS backing partition."
return 1
fi
RESIZE_PART="$LUKS_PART"
else
STORAGE_MODE="lvm"
RESIZE_PART="$PV_NAME"
fi
local inferred
local inferred_disk=""
local inferred_part=""
inferred="$(infer_disk_part_from_partition "$PV_NAME" || true)"
inferred="$(infer_disk_part_from_partition "$RESIZE_PART" || true)"
if [ -n "$inferred" ]; then
inferred_disk="$(printf '%s\n' "$inferred" | sed -n '1p')"
inferred_part="$(printf '%s\n' "$inferred" | sed -n '2p')"
@ -372,21 +444,21 @@ detect_storage_stack() {
if [ -z "$DISK_DEV" ]; then
local disk_parent
disk_parent="$(lsblk_attr "$PV_NAME" PKNAME)"
disk_parent="$(lsblk_attr "$RESIZE_PART" PKNAME)"
if [ -n "$disk_parent" ]; then
DISK_DEV="/dev/${disk_parent}"
else
DISK_DEV="$(lsblk -nro PATH,TYPE -s "$PV_NAME" 2>/dev/null | awk '$2=="disk" {print $1; exit}' | xargs || true)"
DISK_DEV="$(lsblk -nro PATH,TYPE -s "$RESIZE_PART" 2>/dev/null | awk '$2=="disk" {print $1; exit}' | xargs || true)"
fi
fi
if [ -z "$PART_NUM" ]; then
PART_NUM="$(lsblk_attr "$PV_NAME" PARTNUM)"
PART_NUM="$(lsblk_attr "$RESIZE_PART" PARTNUM)"
fi
if [ -z "$DISK_DEV" ] || [ ! -b "$DISK_DEV" ] || [ -z "$PART_NUM" ] || ! [[ "$PART_NUM" =~ ^[0-9]+$ ]]; then
log_warn "Unable to detect disk device or partition number."
log_warn "Detected values: PV_NAME=${PV_NAME:-<empty>} DISK_DEV=${DISK_DEV:-<empty>} PART_NUM=${PART_NUM:-<empty>}"
log_warn "Detected values: RESIZE_PART=${RESIZE_PART:-<empty>} DISK_DEV=${DISK_DEV:-<empty>} PART_NUM=${PART_NUM:-<empty>}"
return 1
fi
@ -394,7 +466,7 @@ detect_storage_stack() {
return 0
}
resize_lvm() {
resize_lvm_storage() {
if [ "$STORAGE_DETECTED" -ne 1 ]; then
log_warn "Storage layout not detected; skipping resize."
return 0
@ -402,12 +474,21 @@ resize_lvm() {
require_cmd parted || return 0
require_cmd blockdev || return 0
if [ "$STORAGE_MODE" = "luks_lvm" ]; then
require_cmd cryptsetup || return 0
fi
require_cmd pvresize || return 0
require_cmd lvextend || return 0
log_info "Root LV: ${ROOT_SOURCE}"
log_info "VG: ${VG_NAME} | LV: ${LV_NAME}"
log_info "Storage mode: ${STORAGE_MODE}"
log_info "LVM PV: ${PV_NAME}"
if [ "$STORAGE_MODE" = "luks_lvm" ]; then
log_info "LUKS device: ${LUKS_DEV}"
log_info "LUKS partition: ${LUKS_PART}"
[ -n "$LUKS_NAME" ] && log_info "LUKS mapper name: ${LUKS_NAME}"
fi
log_info "Disk: ${DISK_DEV} | Partition number: ${PART_NUM}"
if ! repair_gpt_backup_header "$DISK_DEV"; then
@ -422,7 +503,7 @@ resize_lvm() {
local part_size_before
local free_bytes
part_size_before="$(blockdev --getsize64 "$PV_NAME" 2>/dev/null || echo 0)"
part_size_before="$(blockdev --getsize64 "$RESIZE_PART" 2>/dev/null || echo 0)"
free_bytes="$(get_trailing_free_bytes "$DISK_DEV" "$PART_NUM" || true)"
if [ -z "$free_bytes" ]; then
log_warn "Unable to determine free disk space."
@ -436,22 +517,36 @@ resize_lvm() {
log_info "Unallocated space available: $(human_bytes "$free_bytes")"
show_disk_layout "$DISK_DEV"
if ! confirm "Extend partition, VG, and root LV now?" "yes"; then
local resize_prompt
if [ "$STORAGE_MODE" = "luks_lvm" ]; then
resize_prompt="Extend partition, LUKS device, VG, and root LV now?"
else
resize_prompt="Extend partition, VG, and root LV now?"
fi
if ! confirm "$resize_prompt" "yes"; then
log_warn "Skipped resize."
return 0
fi
log_info "Extending partition ${PV_NAME} to 100% of disk..."
log_info "Extending partition ${RESIZE_PART} to 100% of disk..."
if ! parted -s "$DISK_DEV" resizepart "$PART_NUM" 100%; then
log_warn "Partition resize failed."
return 0
fi
refresh_partition_table "$DISK_DEV"
if [ "$part_size_before" -gt 0 ] && ! wait_for_partition_growth "$PV_NAME" "$part_size_before"; then
if [ "$part_size_before" -gt 0 ] && ! wait_for_partition_growth "$RESIZE_PART" "$part_size_before"; then
log_warn "Kernel has not reported the new partition size yet; continuing anyway."
fi
if [ "$STORAGE_MODE" = "luks_lvm" ]; then
log_info "Resizing LUKS device..."
if ! resize_open_luks_mapping; then
log_warn "LUKS resize failed."
return 0
fi
fi
log_info "Resizing LVM physical volume..."
if ! pvresize "$PV_NAME"; then
log_warn "pvresize failed."
@ -549,6 +644,62 @@ setup_swap() {
log_ok "Swap enabled."
}
change_luks_passphrase() {
if [ "$STORAGE_MODE" != "luks_lvm" ]; then
log_info "Root storage is not LUKS-backed; skipping LUKS passphrase change."
return 0
fi
if [ -z "$LUKS_PART" ] || [ ! -e "$LUKS_PART" ]; then
log_warn "LUKS partition not found; skipping."
return 0
fi
require_cmd cryptsetup || return 0
if ! confirm "Change LUKS passphrase in slot 0 now?" "yes"; then
log_warn "Skipped LUKS passphrase change."
return 0
fi
local old_pass
local new_pass
local tmp_old
local tmp_new
old_pass="$(prompt_secret "Enter current LUKS passphrase")"
new_pass="$(prompt_secret_confirm "Enter new LUKS passphrase" "Confirm new LUKS passphrase")"
tmp_old="$(mktemp)"
tmp_new="$(mktemp)"
add_temp_file "$tmp_old"
add_temp_file "$tmp_new"
printf '%s' "$old_pass" >"$tmp_old"
printf '%s' "$new_pass" >"$tmp_new"
log_info "Updating LUKS passphrase in slot 0..."
if cryptsetup luksChangeKey --batch-mode --key-slot 0 --key-file "$tmp_old" "$LUKS_PART" "$tmp_new"; then
log_ok "LUKS passphrase updated."
else
log_warn "Failed to update LUKS passphrase."
fi
}
setup_clevis() {
if [ "$STORAGE_MODE" != "luks_lvm" ]; then
log_info "Root storage is not LUKS-backed; skipping Clevis/Tang setup."
return 0
fi
log_info "Clevis/Tang setup is not implemented in this template yet."
if confirm "Would you like to configure Clevis with a Tang server now? (will be skipped)" "no"; then
local tang
tang="$(prompt_input "Tang server URL" "http://tang.int.r3w.de")"
log_warn "Clevis setup for ${tang} is not implemented yet. Skipping."
else
log_info "Skipping Clevis setup."
fi
}
setup_tailscale() {
if ! require_cmd tailscale; then
log_warn "Tailscale is not installed; skipping."
@ -639,7 +790,7 @@ main() {
welcome
if detect_storage_stack; then
log_ok "Detected LVM storage layout."
log_ok "Detected ${STORAGE_MODE} storage layout."
else
log_warn "Storage layout detection incomplete; some steps may be skipped."
fi
@ -647,8 +798,12 @@ main() {
TASK_TITLES=()
TASK_FUNCS=()
add_task "Resize LVM (if free space exists)" resize_lvm
add_task "Resize LVM storage (if free space exists)" resize_lvm_storage
add_task "Configure swap file" setup_swap
if [ "$STORAGE_MODE" = "luks_lvm" ]; then
add_task "Change LUKS passphrase (slot 0)" change_luks_passphrase
add_task "Clevis/Tang setup (placeholder)" setup_clevis
fi
add_task "Configure Tailscale" setup_tailscale
add_task "Configure CrowdSec" setup_crowdsec
add_task "Reboot recommendation" prompt_reboot

View file

@ -58,7 +58,7 @@ d-i preseed/early_command string \
# "crypto" = LVM within an encrypted partition [oai_citation:3‡Debian](https://www.debian.org/releases/stable/amd64/apbs04.en.html)
# "lvm" = LVM without encryption
d-i partman-auto/method string lvm
d-i partman-auto/method string ${enable_luks ? "crypto" : "lvm"}
d-i partman-auto-lvm/guided_size string max
d-i partman-auto-lvm/new_vg_name string vg0
@ -72,13 +72,15 @@ d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-md/confirm boolean true
d-i partman-md/confirm_nooverwrite boolean true
# # LUKS password
# # LUKS passphrase is set via kernel cmdline in debian-trixie.pkr.hcl; these lines are ignored but left here for reference:
# # d-i partman-crypto/passphrase password "$PACKER_LUKS_PASS"
# # d-i partman-crypto/passphrase-again password "$PACKER_LUKS_PASS"
# d-i partman-crypto/weak_passphrase boolean true
# d-i partman-crypto/confirm boolean true
# d-i partman-auto-crypto/erase_disks boolean false
%{ if enable_luks ~}
# LUKS password
# LUKS passphrase is set via kernel cmdline in debian-trixie.pkr.hcl; these lines are ignored but left here for reference:
# d-i partman-crypto/passphrase password "$PACKER_LUKS_PASS"
# d-i partman-crypto/passphrase-again password "$PACKER_LUKS_PASS"
d-i partman-crypto/weak_passphrase boolean true
d-i partman-crypto/confirm boolean true
d-i partman-auto-crypto/erase_disks boolean false
%{ endif ~}
# Ensure GPT
d-i partman-partitioning/choose_label select gpt
@ -96,9 +98,9 @@ d-i partman-basicfilesystems/no_swap seen true
# Custom recipe
# NOTE: Avoid putting comments inside this expert_recipe block; d-i can ignore it. [oai_citation:4‡Unix & Linux Stack Exchange](https://unix.stackexchange.com/questions/796185/debian-preseed-install-auto-creates-swap)
d-i partman-auto/choose_recipe select luks-lvm
d-i partman-auto/choose_recipe select debian-lvm
d-i partman-auto/expert_recipe string \
luks-lvm :: \
debian-lvm :: \
1075 1075 1075 fat32 \
$primary{ } \
$iflabel{ gpt } \
@ -151,7 +153,8 @@ d-i grub-installer/bootdev string default
# 2) Enable root ssh login (same intent as your original)
d-i preseed/late_command string \
lvremove -f /dev/vg0/reserved || true; \
in-target sed -i 's/^#PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config || true
in-target sed -i 's/^#PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config || true%{ if enable_luks }; \
in-target curl -X POST "$INSTALL_FINISHED_INFORM_URL"%{ endif }
# Eject the installation media before rebooting
d-i cdrom-detect/eject boolean true

View file

@ -59,7 +59,11 @@ variable "install_finished_inform_port" {
description = "The server port to inform when installation is finished"
}
variable "enable_luks" {
type = bool
default = false
description = "Build Debian with LUKS encrypted root storage."
}
# local values
local "timestamp" {

View file

@ -0,0 +1,2 @@
enable_luks = true
template_vm_id = "65001"