381 Commits

Author SHA1 Message Date
Kevin Veen-Birkenbach
b3bd5b6740 Release version 2.3.0
Some checks failed
tests / lint (push) Has been cancelled
tests / pytest (push) Has been cancelled
tests / tor-network-e2e (push) Has been cancelled
tests / qemu-e2e-debian (push) Has been cancelled
latest v2.3.0
2026-07-24 18:16:57 +02:00
Kevin Veen-Birkenbach
496268fa39 feat(cli): numbered selection menus for distributions and versions
Some checks failed
tests / lint (push) Has been cancelled
tests / pytest (push) Has been cancelled
tests / tor-network-e2e (push) Has been cancelled
tests / qemu-e2e-debian (push) Has been cancelled
Replace the free-text distribution/version prompts with numbered menus via a
shared _choose helper. Name-first resolution keeps typing the name working, so
numeric catalog keys (e.g. arch "4") still select by name, not by position.
Applied to the distribution, arch, manjaro, retropie and raspios choosers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:57 +02:00
Kevin Veen-Birkenbach
919651277e fix(image): keep the Tor onion reachable on RTC-less boards
An RTC-less Pi boots at 1970; Tor then rejects the consensus and the unlock
onion never publishes. Bake a clock floor (build epoch) into the initramfs and
jump the clock forward to it before Tor starts, and pass a numeric tor_ntp= in
the cmdline so busybox ntpd syncs without DNS. Proven on hardware: the onion
unlock succeeded on a Pi 3 after this.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:57 +02:00
Kevin Veen-Birkenbach
0c621f60ae fix(session): mount the boot FAT at /boot/firmware on Bookworm
Raspberry Pi OS Bookworm keeps the FAT boot partition at /boot/firmware, where
the raspi-firmware post-update hook syncs the initramfs. Binding it at /boot in
the chroot sent update-initramfs's output to the ext4 root, so the firmware
booted a stock initramfs without cryptsetup -> (initramfs) emergency shell, no
LUKS prompt. boot_bind_target() detects the layout and binds (and unmounts) the
FAT at the right place; legacy single-FAT layouts still use /boot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:56 +02:00
Kevin Veen-Birkenbach
30ee1101ce feat(cli): guided encrypted-image wizard (default) + remote-unlock
Add a guided setup: one interactive command that asks everything up front then
builds an encrypted, Tor-remote-unlockable image unattended (distribution,
target device, hostname, login user + key, password), creating or renaming the
login user and installing the SSH key for both unlock and post-boot login.

- wizard.py: _collect (all prompts) + _execute (autonomous build); renames a
  stock pi/alarm user or creates one, grants sudo, installs the login key.
- unlock.py + `lim --type remote-unlock`: reach the initramfs over Tor (onion,
  torsocks) or plain SSH (host/IP), run cryptroot-unlock or the passphrase
  prompt; the wizard persists a target record under ~/.config/lim/unlocks.
- cli.py: register guided (default --type) and remote-unlock; drop the
  deprecated --extra argument.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:56 +02:00
Kevin Veen-Birkenbach
14094e9436 feat(image): auto-enable cross-arch chroot via qemu binfmt
Building a foreign-arch image (e.g. arm64 Raspberry Pi OS on an x86 host) needs
a qemu binfmt handler, or the chroot fails with "Exec format error". crossarch
detects this before the target is erased and, on confirmation, installs
qemu-user-static via the host package manager (apt-get/dnf/zypper/pacman) and
registers binfmt; otherwise it aborts with per-distro instructions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:56 +02:00
Kevin Veen-Birkenbach
5bef6177ac feat(image): flash full disk images into LUKS; add Raspberry Pi OS
Support distributions that ship a full .img (Raspberry Pi OS, moode, RetroPie,
Manjaro ARM) rather than a rootfs tarball, by loop-mounting the image and
copying its boot + root partitions into a fresh LUKS container.

- distributions.yml/catalog.py/choosers.py: raspios catalog (lite64/desktop64/
  lite32 via the stable _latest redirects) + choose_raspios.
- plan.py: source_url override so a _latest redirect downloads under an .img.xz
  name that decompress_command recognises.
- transfer.py: transfer_disk_image (loop-mount -> repartition -> LUKS -> rsync
  copy with progress, cp fallback -> fix boot fstab); transfer_image gains
  interactive= and routes encrypted non-arch images here; download_image gains
  force_prompt.
- loopimg.py: losetup attach/detach/partition helper.
- fsutil.drop_fstab_mount + register_encrypted_root: replace a stock image's
  existing / fstab line instead of colliding with it.
- encryption.configure_encryption returns the onion address.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:56 +02:00
Kevin Veen-Birkenbach
68ea39b784 chore(session): harden chroot package installs (PATH + apt-get update)
- chroot_bash exports a Debian-safe PATH so /usr/sbin tools (update-initramfs,
  useradd, chpasswd, ...) resolve inside the chroot instead of failing with
  code 127.
- install_packages runs apt-get update before install (a stock image ships
  stale lists whose superseded .deb URLs 404) and non-interactive
  apt-get install -y; pacman gains -Sy for the same index-refresh reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:56 +02:00
Kevin Veen-Birkenbach
c3a41db796 style: apply ruff format across the tree; refresh moved-module doc refs
Bring 14 files that predated the ruff-format run into line with the configured
formatter (line-length 100); provably formatting-only (ruff format of HEAD ==
working tree, and ruff format is semantics-preserving). Also refresh two
lim.image.tor._KEYGEN_SCRIPT doc references in tor_harness.py to
lim.image.initramfs.keygen after the module moved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:55 +02:00
Kevin Veen-Birkenbach
336e68845e ci: run the Debian QEMU e2e on push; add make target and docs
- test.yml: add a qemu-e2e-debian job (build -> boot -> LUKS-unlock in QEMU
  via the deterministic direct transport) running on push/PR, plus a
  workflow_dispatch trigger for manual runs.
- Makefile: test-qemu-debian target (LIM_E2E_OS=debian); document the direct
  vs tor transport on test-qemu.
- README: document the two build scripts and the direct transport.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:55 +02:00
Kevin Veen-Birkenbach
e7712880f1 test(e2e): Debian QEMU build+boot+unlock harness and deterministic direct transport
Extend the QEMU e2e to cover the initramfs-tools backend and add a
deterministic unlock transport that avoids the flaky public-Tor onion
round-trip inside QEMU.

- build_image_debian.sh: debootstrap Bookworm, install the real
  lim/configuration/initramfs-tools hooks, LUKS + cryptsetup-initramfs +
  dropbear-initramfs, offline onion keys, boot-ok marker; same image.env
  contract as build_image.sh.
- config.py: QemuSpec gains os_family / unlock_command / direct_ssh_port;
  Debian cmdline uses root=/dev/mapper (crypttab-baked, no cryptdevice=);
  direct_ssh_port adds hostfwd to guest dropbear and a plain-SSH target.
- harness.py: unlock_transport="direct" default, _NullNet, per-OS build
  script + unlock command, up-front sudo priming with keepalive.
- boot_unlock.py: background delivery worker holds the SSH session open;
  direct vs tor target and initial delay.
- test_qemu_harness_unit.py: always-on guards for the Debian/direct branches;
  test_qemu_unlock_e2e.py parameterized by ARCH/OS/TRANSPORT env.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:55 +02:00
Kevin Veen-Birkenbach
9b7a34989d feat(image): distro-agnostic remote unlock via initramfs backends + Debian support
Split the mkinitcpio-only remote-LUKS-unlock path into an InitramfsBackend
ABC with a get_backend() dispatch, and add the initramfs-tools backend for
Debian / Raspberry Pi OS.

- base.py: six-step backend contract; encryption.py becomes a thin,
  distro-neutral sequencer (get_backend by distribution).
- initramfs_tools.py: crypttab `none luks,initramfs`, cmdline rewritten to
  root=/dev/mapper + ip=::::host:eth0:dhcp, dropbear-initramfs
  authorized_keys, update-initramfs -k all (no build-host uname leak).
- shipped hooks (configuration/initramfs-tools/*): single-hop non-anonymous
  onion, libnss DNS baking, sed-not-source DHCP, kill-tor-before-pivot.
- shared offline onion keygen in keygen.py; tor.py removed (logic moved to
  mkinitcpio.py).
- raspios added to the apt distro family (session.py, raspberry.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:15:54 +02:00
Kevin Veen-Birkenbach
2a4b606cdb Release version 2.2.0
Some checks failed
tests / lint (push) Has been cancelled
tests / pytest (push) Has been cancelled
tests / tor-network-e2e (push) Has been cancelled
2026-07-21 19:12:36 +02:00
Kevin Veen-Birkenbach
8044cdafcd ci: run the offline Tor keygen in CI, add a non-blocking network e2e job
Split the rootless Tor e2e skip so the deterministic offline checks (the
real offline onion keygen and the production-flag guard) run whenever the
tor binary is present, while only the live onion round-trip stays gated on
LIM_E2E_TOR=1. The pytest job now installs tor so the keygen — which
validates the exact production keygen path — runs on every push. A separate
continue-on-error tor-network-e2e job exercises the full round-trip without
making the public Tor network a blocking merge gate.

The full QEMU build/boot/unlock e2e still needs root + KVM + Arch tooling
and stays local/opt-in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:12:36 +02:00
Kevin Veen-Birkenbach
c58dfaeaa8 docs: document Tor unlock, e2e tests, and make targets
README: Tor onion unlock feature + security note, the rootless and QEMU
end-to-end tests and how to run them, project-structure entries, and a
correction that PyYAML>=6 is required (not stdlib-only) on the
package-manager/symlink install path. Makefile: test-tor, test-qemu and
test-all targets, with an overridable PYTHON interpreter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:12:36 +02:00
Kevin Veen-Birkenbach
2c3b29ee98 test(e2e): full virtualized build+boot+unlock QEMU harness
Opt-in via LIM_E2E_QEMU=1. Models the whole process on a virtio VM (the
Pi's USB-gadget net can't be emulated, so it models the software stack,
not the board): builds a LUKS image carrying the real lim initcpio Tor
artifacts, boots it in QEMU rootless, lets the real netconf/tor/dropbear/
encryptssh chain publish the onion, delivers the passphrase over Tor, and
asserts the boot-ok marker on the serial console. Supports a private
offline Tor network via chutney.

The pure command builders (config.qemu_argv/kernel_cmdline/ssh_argv,
qemu_binary), the env parser, and drift guards that keep build_image.sh
aligned with the harness run in the normal suite — no QEMU/root/network.

The build stage needs root; harness.py primes sudo up front, keeps the
credential warm, and reclaims work-dir ownership on every exit path so
the pytest tmp cleanup never trips on root-owned files. QEMU stderr is
captured so an early exit is debuggable, and each teardown step is
fault-isolated so none masks the real error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:12:35 +02:00
Kevin Veen-Birkenbach
6295c09b8f test(e2e): rootless Tor onion unlock end-to-end test
Opt-in via LIM_E2E_TOR=1. Generates the v3 onion keys offline exactly as
the image build does, stands up a real Tor onion service from a torrc
mirroring the baked-in one, and delivers a passphrase through Tor to a
dropbear stand-in — asserting it arrives and the endpoint "unlocks".
Needs the real tor binary and network, so it is skipped otherwise; the
offline keygen and production-flag guard run without network.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:12:35 +02:00
Kevin Veen-Birkenbach
1e711f5c96 feat(image): remote LUKS unlock via a Tor onion service in the initramfs
Encrypted image setups can now bake a Tor onion service into the initramfs
so the dropbear unlock shell stays reachable behind NAT or a dynamic IP.
When the user opts in, configure_encryption installs tor + busybox, drops
the mkinitcpio hooks (ordered `netconf tor dropbear encryptssh`), generates
the v3 onion keys offline in the image chroot, and prints the stable
.onion address. Unlock with `torsocks ssh root@<onion-address>`.

The runtime hook syncs the clock via NTP first (RTC-less boards boot at
1970, which Tor's consensus checks reject) and starts the onion service
pointing at dropbear on 127.0.0.1:22.

Hardening baked in from an adversarial review of the shipped path:
- cmdline.txt boot path (RPi4-class firmware boot) now sets the same
  ip=::::<host>:eth0:dhcp net.ifnames=0 params as the boot.txt path, so
  the initramfs actually gets a network and the onion can publish.
- the initramfs bakes in libnss_dns.so.2 so the NTP hostname resolves.
- the hook extracts DHCP DNS with sed instead of sourcing the lease files,
  which would run attacker-controlled DHCP option strings as root pre-boot.
- NTP is attempted unconditionally (bounded), not gated on DHCP-provided DNS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:12:35 +02:00
Kevin Veen-Birkenbach
b38303b72f Release version 2.1.0
Some checks failed
tests / lint (push) Has been cancelled
tests / pytest (push) Has been cancelled
v2.1.0
2026-07-14 12:06:25 +02:00
Kevin Veen-Birkenbach
557fe159d5 Optimized Python package building 2026-07-14 12:05:08 +02:00
Kevin Veen-Birkenbach
a8c321fca1 build: rename dist to linux-image-manager, add MIRRORS
Some checks failed
tests / lint (push) Has been cancelled
tests / pytest (push) Has been cancelled
"lim" is taken on PyPI (a GLMM genomics package); linux-image-manager
is free and matches the repo name. Import package and CLI alias stay
"lim". MIRRORS follows the hetzner-arch-luks schema.
2026-07-14 11:57:02 +02:00
Kevin Veen-Birkenbach
5cf23fcdd4 Release version 2.0.0 2026-07-14 11:42:13 +02:00
Kevin Veen-Birkenbach
a9a5340ba0 chore(claude): ask on every git commit/push variant 2026-07-14 11:37:19 +02:00
Kevin Veen-Birkenbach
ccdef065df refactor!: port shell scripts to Python package
Bash scripts were untestable and duplicated device/LUKS/mount logic;
the lim/ package centralizes it behind one subprocess wrapper and a
YAML image catalog (single point of truth).

BREAKING CHANGE: scripts/*.sh removed. Use `lim --type <cmd>`; new
types mount/umount/single-boot/raid1-boot/lock/unlock/import/export
replace direct script calls. --extra is deprecated and ignored.

- distributions.yml + lim/catalog.py hold the image catalog (PyYAML)
- pytest suite: 102 tests with mocked subprocess (tests/unit) and a
  250-line max file-length guard (tests/lint)
- ruff strict (select ALL), GitHub Actions CI, Dependabot; Travis gone
- Makefile: install (symlink ~/.local/bin/lim) and test targets
- fixes over bash: SUDO_USER-aware chown, mmcblk/nvme partition paths,
  sha512 checksum support, whole-pipeline failure detection, blkid
  UUID fallback for pre-mounted images, conditional fstab seeding for
  PARTUUID/LABEL images, clean errors for missing binaries
2026-07-14 11:37:19 +02:00
Kevin Veen-Birkenbach
c420dd164d Release version 1.0.0 2026-07-14 10:19:30 +02:00
Kevin Veen-Birkenbach
509ed85fb2 chore(claude): sandbox bash, deny git push/commit 2026-07-07 09:21:59 +02:00
Kevin Veen-Birkenbach
d89a433695 Added Manjaro GNOME 25 2025-10-17 14:03:09 +02:00
Kevin Veen-Birkenbach
e60b3cf2a7 Overall optimations 2025-03-22 11:42:13 +01:00
Kevin Veen-Birkenbach
94fdcf5758 Implemented wrapper for pkgmgr and more sync for safe image transfer and manjaro gnome 24 2025-03-22 10:28:14 +01:00
Kevin Veen-Birkenbach
b4a0b50e1f Added Funding 2025-03-12 20:52:47 +01:00
Kevin Veen-Birkenbach
d613fbf262 Merge branch 'master' of github.com:kevinveenbirkenbach/linux-image-manager 2025-03-12 11:14:39 +01:00
Kevin Veen-Birkenbach
bf95ba6090 Update README.md 2025-03-12 10:49:37 +01:00
Kevin Veen-Birkenbach
7f82c6fcb9 Update README.md 2025-03-04 19:59:47 +01:00
Kevin Veen-Birkenbach
13454c7e87 Added missing keyy add 2025-02-15 12:46:40 +01:00
Kevin Veen-Birkenbach
72c4d95606 Added overview about LUKS encrypted storage devices 2025-02-15 12:03:51 +01:00
Kevin Veen-Birkenbach
3f272790d6 Update Hints 2025-02-14 15:48:01 +01:00
Kevin Veen-Birkenbach
bac3edc404 Optimized logic 2024-07-22 23:49:25 +02:00
Kevin Veen-Birkenbach
b2881c1319 Added platform 2024-07-22 23:43:02 +02:00
Kevin Veen-Birkenbach
3daed9447b Implemented automatic boot_size and .xz file management 2024-07-22 23:23:40 +02:00
Kevin Veen-Birkenbach
bd05fd4c2d Optimized setup of manjaro for raspberry pi 4 2024-07-22 22:46:02 +02:00
Kevin Veen-Birkenbach
bbb54a4237 Optimized logic for luks and implemented administrator setup 2024-07-22 21:23:03 +02:00
Kevin Veen-Birkenbach
9519b314e9 Added logic for luks memory cost 2024-07-22 17:17:37 +02:00
Kevin Veen-Birkenbach
826aa42565 Removed earlyprint 2024-07-22 02:37:59 +02:00
Kevin Veen-Birkenbach
19f5dc7178 Removed typo 2024-07-21 23:13:36 +02:00
Kevin Veen-Birkenbach
5aa8f65584 Implemented optimations for raspberry pi 4 2024-07-21 22:44:39 +02:00
Kevin Veen-Birkenbach
e37d57d569 Solved bug 2024-07-21 16:03:04 +02:00
Kevin Veen-Birkenbach
c05e804fb5 Added cleanup hints 2024-07-21 15:34:05 +02:00
Kevin Veen-Birkenbach
e40974f56d Refactored code and solved bugs 2024-07-21 15:30:17 +02:00
Kevin Veen-Birkenbach
e8581cb448 Solved replacement bug 2024-07-21 03:32:52 +02:00
Kevin Veen-Birkenbach
38a289083d Implemented search replace validation and solved password bug 2024-07-21 01:11:29 +02:00