Update LUKS unlock wait behavior

This commit is contained in:
Philip Henning 2026-02-05 18:42:02 +01:00
parent fccecfde5d
commit 66dd860c3f
2 changed files with 99 additions and 18 deletions

View file

@ -15,6 +15,7 @@ from __future__ import annotations
import argparse import argparse
import random import random
import subprocess import subprocess
import sys
import threading import threading
import time import time
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
@ -46,11 +47,17 @@ def main() -> int:
required=True, required=True,
help="Path to directory containing variables.pkr.hcl (also passed to mise build).", help="Path to directory containing variables.pkr.hcl (also passed to mise build).",
) )
parser.add_argument(
"--luks-wait-seconds",
type=int,
default=45,
help="Seconds to wait before sending the LUKS password (default: 45).",
)
args = parser.parse_args() args = parser.parse_args()
script_root = Path(__file__).resolve().parents[1] script_root = Path(__file__).resolve().parents[1]
variables_common_path = script_root / "variables-common.pkr.hcl" variables_common_path = script_root / "variables-common.pkr.hcl"
credentials_path = script_root / "debian/13-trixie/credentials.auto.pkrvars.hcl" credentials_path = script_root / "credentials.auto.pkrvars.hcl"
vars_dir = Path(args.template) vars_dir = Path(args.template)
if not vars_dir.is_absolute(): if not vars_dir.is_absolute():
vars_dir = script_root / vars_dir vars_dir = script_root / vars_dir
@ -101,16 +108,66 @@ def main() -> int:
port, httpd = find_random_port() port, httpd = find_random_port()
def stream_colors(stream: object) -> tuple[str, str]:
color = ""
reset = ""
is_tty = getattr(stream, "isatty", None)
if callable(is_tty) and is_tty():
if stream is sys.stderr:
color = "\033[31m"
else:
color = "\033[36m"
reset = "\033[0m"
return color, reset
def log(message: str, stream: object = sys.stdout) -> None:
color, reset = stream_colors(stream)
stream.write(f"{color}[luks-unlock-wrapper] {message}{reset}\n")
stream.flush()
def write_status(message: str, stream: object = sys.stdout, *, newline: bool) -> None:
color, reset = stream_colors(stream)
is_tty = getattr(stream, "isatty", None)
prefix = f"{color}[luks-unlock-wrapper] "
suffix = f"{reset}\n" if newline else reset
if callable(is_tty) and is_tty():
stream.write(f"\r\033[2K{prefix}{message}{suffix}")
else:
stream.write(f"{prefix}{message}{suffix}")
stream.flush()
def serve() -> None: def serve() -> None:
httpd.serve_forever() httpd.serve_forever()
server_thread = threading.Thread(target=serve, daemon=True) server_thread = threading.Thread(target=serve, daemon=True)
server_thread.start() server_thread.start()
print(f"Listening for POST on /install_finished at port {port}") log(f"Listening for POST on /install_finished at port {port}")
build_cmd = ["mise", "build", args.template, "-i", str(port)] build_cmd = ["mise", "build", args.template, "-i", str(port)]
build_proc = subprocess.Popen(build_cmd) build_proc = subprocess.Popen(
build_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
)
def relay_stream(stream: object, prefix: str, target: object) -> None:
if not stream:
return
for line in stream:
target.write(f"{prefix} {line}")
target.flush()
stdout_thread = threading.Thread(
target=relay_stream, args=(build_proc.stdout, "[packer]", sys.stdout), daemon=True
)
stderr_thread = threading.Thread(
target=relay_stream, args=(build_proc.stderr, "[packer]", sys.stderr), daemon=True
)
stdout_thread.start()
stderr_thread.start()
notified = False notified = False
action_started = False action_started = False
@ -148,28 +205,52 @@ def main() -> int:
try: try:
response = proxmox_request("GET", "/version") response = proxmox_request("GET", "/version")
response.raise_for_status() response.raise_for_status()
print("Authenticated to Proxmox VE API.") log("Authenticated to Proxmox VE API.")
break break
except Exception as exc: except Exception as exc:
print(f"Proxmox auth failed: {exc}. Retrying in {retry_delay}s.") log(
f"Proxmox auth failed: {exc}. Retrying in {retry_delay}s.",
stream=sys.stderr,
)
time.sleep(retry_delay) time.sleep(retry_delay)
retry_delay += 1 retry_delay += 1
try: try:
print("Waiting 45 seconds before sending LUKS password.") countdown_seconds = max(0, args.luks_wait_seconds)
time.sleep(45) # Braille spinner: 8 dots, one missing dot rotates clockwise.
full_mask = 0xFF # dots 1-8
dot_bits = {
1: 0x01,
2: 0x02,
3: 0x04,
4: 0x08,
5: 0x10,
6: 0x20,
7: 0x40,
8: 0x80,
}
rotation = [1, 4, 5, 6, 8, 7, 3, 2] # clockwise around the cell
spinner = [chr(0x2800 + (full_mask - dot_bits[dot])) for dot in rotation]
for remaining in range(countdown_seconds, -1, -1):
minutes, seconds = divmod(remaining, 60)
countdown = f"{minutes:02d}:{seconds:02d}"
frame = spinner[(countdown_seconds - remaining) % len(spinner)]
write_status(f"{frame} {countdown}", newline=False)
if remaining:
time.sleep(1)
write_status(f"{spinner[0]} 00:00", newline=True)
for char in "packer": for char in "packer":
send_key(char) send_key(char)
time.sleep(0.1) time.sleep(0.1)
send_key("ret") send_key("ret")
print("Sent LUKS password and Enter.") log("Unlocking encrypted disk. Entering LUKS password.")
except Exception as exc: except Exception as exc:
print(f"Post-install actions failed after auth: {exc}") log(f"Post-install actions failed after auth: {exc}", stream=sys.stderr)
try: try:
while True: while True:
if server_event.is_set() and not notified: if server_event.is_set() and not notified:
print("Installation finished.\nRestarting.") log("Installation finished.\nRestarting.")
notified = True notified = True
if server_event.is_set() and not action_started: if server_event.is_set() and not action_started:
action_started = True action_started = True

View file

@ -7,7 +7,7 @@ packer {
} }
} }
source "proxmox-iso" "debian-13-trixie" { source "proxmox-iso" "debian-13-trixie-luks" {
# Proxmox Connection Settings # Proxmox Connection Settings
proxmox_url = "${var.proxmox_api_url}" proxmox_url = "${var.proxmox_api_url}"
username = "${var.proxmox_api_token_id}" username = "${var.proxmox_api_token_id}"
@ -19,8 +19,8 @@ source "proxmox-iso" "debian-13-trixie" {
# VM General Settings # VM General Settings
node = "${var.proxmox_node}" node = "${var.proxmox_node}"
vm_id = "${var.template_vm_id}" vm_id = "${var.template_vm_id}"
vm_name = "debian-13-trixie-${local.timestamp}" vm_name = "debian-13-trixie-luks-${local.timestamp}"
template_description = "Debian 13 Trixie, built with Packer on ${local.timestamp}" template_description = "Debian 13 Trixie, LUKS encrypted, built with Packer on ${local.timestamp}"
os = "l26" os = "l26"
qemu_agent = true qemu_agent = true
@ -82,7 +82,7 @@ source "proxmox-iso" "debian-13-trixie" {
] ]
# PACKER Autoinstall Settings # PACKER Autoinstall Settings
http_directory = "debian/13-trixie/http" http_directory = "debian/13-trixie-luks/http"
http_interface = "${var.source_proxmox_http_interface}" http_interface = "${var.source_proxmox_http_interface}"
# SSH Settings # SSH Settings
@ -93,8 +93,8 @@ source "proxmox-iso" "debian-13-trixie" {
} }
build { build {
name = "debian-13-trixie-image" name = "debian-13-trixie-luks-image"
sources = ["source.proxmox-iso.debian-13-trixie"] sources = ["source.proxmox-iso.debian-13-trixie-luks"]
# Provisioning the VM Template for Cloud-Init Integration in Proxmox #1 # Provisioning the VM Template for Cloud-Init Integration in Proxmox #1
provisioner "shell" { provisioner "shell" {
@ -113,7 +113,7 @@ build {
# Provisioning the VM Template for Cloud-Init Integration in Proxmox #2 # Provisioning the VM Template for Cloud-Init Integration in Proxmox #2
provisioner "file" { provisioner "file" {
source = "debian/13-trixie/files/99-pve.cfg" source = "debian/13-trixie-luks/files/99-pve.cfg"
destination = "/tmp/99-pve.cfg" destination = "/tmp/99-pve.cfg"
} }
@ -129,7 +129,7 @@ build {
# Add custom APT sources list # Add custom APT sources list
provisioner "file" { provisioner "file" {
source = "debian/13-trixie/files/debian.sources" source = "debian/13-trixie-luks/files/debian.sources"
destination = "/etc/apt/sources.list.d/debian.sources" destination = "/etc/apt/sources.list.d/debian.sources"
} }
} }