Refactor LUKS unlock script to enhance spinner functionality and improve message colorization

This commit is contained in:
Philip Henning 2026-02-06 10:40:11 +01:00
parent 8949540fd5
commit 6d351fcdb3

View file

@ -4,6 +4,7 @@
# dependencies = [ # dependencies = [
# "python-hcl2==4.*", # "python-hcl2==4.*",
# "requests==2.*", # "requests==2.*",
# "yaspin==3.*",
# ] # ]
# /// # ///
@ -23,6 +24,7 @@ from pathlib import Path
import hcl2 import hcl2
import requests import requests
from yaspin import yaspin
def load_hcl(path: Path) -> dict: def load_hcl(path: Path) -> dict:
@ -125,16 +127,11 @@ def main() -> int:
stream.write(f"{color}[luks-unlock-wrapper] {message}{reset}\n") stream.write(f"{color}[luks-unlock-wrapper] {message}{reset}\n")
stream.flush() stream.flush()
def write_status(message: str, stream: object = sys.stdout, *, newline: bool) -> None: def colorize_message(message: str, stream: object = sys.stdout) -> str:
color, reset = stream_colors(stream) color, reset = stream_colors(stream)
is_tty = getattr(stream, "isatty", None) if not color:
prefix = f"{color}[luks-unlock-wrapper] " return message
suffix = f"{reset}\n" if newline else reset return f"{color}{message}{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()
@ -215,42 +212,48 @@ def main() -> int:
time.sleep(retry_delay) time.sleep(retry_delay)
retry_delay += 1 retry_delay += 1
spinner = None
try: try:
countdown_seconds = max(0, args.luks_wait_seconds) countdown_seconds = max(0, args.luks_wait_seconds)
# Braille spinner: 8 dots, one missing dot rotates clockwise. spinner = yaspin(
full_mask = 0xFF # dots 1-8 text=colorize_message(
dot_bits = { f"[luks-unlock-wrapper] {countdown_seconds // 60:02d}:{countdown_seconds % 60:02d}",
1: 0x01, sys.stdout,
2: 0x02, ),
3: 0x04, color="cyan",
4: 0x08, stream=sys.stdout,
5: 0x10, )
6: 0x20, spinner.start()
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): for remaining in range(countdown_seconds, -1, -1):
minutes, seconds = divmod(remaining, 60) minutes, seconds = divmod(remaining, 60)
countdown = f"{minutes:02d}:{seconds:02d}" countdown = f"{minutes:02d}:{seconds:02d}"
frame = spinner[(countdown_seconds - remaining) % len(spinner)] spinner.text = colorize_message(
write_status(f"{frame} {countdown}", newline=False) f"[luks-unlock-wrapper] {countdown}", sys.stdout
)
if remaining: if remaining:
time.sleep(1) 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")
log("Unlocking encrypted disk. Entering LUKS password.") spinner.text = colorize_message(
"[luks-unlock-wrapper] ✅ Unlocking encrypted disk. Entering LUKS password.",
sys.stdout,
)
spinner.ok("")
except Exception as exc: except Exception as exc:
if spinner:
spinner.text = colorize_message(
"[luks-unlock-wrapper] 💥 Post-install actions failed.",
sys.stdout,
)
spinner.fail("")
log(f"Post-install actions failed after auth: {exc}", stream=sys.stderr) 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:
log("Installation finished.\nRestarting.") log("Installation finished. -- Restarting.")
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