Skip to content

Changelog

Historical changes/commits before I went rogue: upstream

Program will remain in 0.* because of the nature of this software. This means that its always labeled as "Alpha" and recommend latest. Simply because of the true evolving state of Arch-based systems.

0.1.15-0

- `schema.jsonc` becomes `schema.toml`, generated by
  `lib/schema_gen.py` from the installer's own package definitions
  instead of hand-transcribed. `--script schema` rewrites it,
  sections in the order the install runs them. `lib/schema.py`
  shrinks to `tomllib` + paths, still loadable by path from NVGEN
  with no runtime. `test_schema_drift.py` (447 lines pinned against
  code) gives way to `test_schema.py`, which fails on a stale file
- Terminal is a choice, not a per-profile hardcode: alacritty, foot,
  kitty, ghostty, konsole, wezterm, xterm. One gets installed and
  `TERMINAL` exported in `/etc/environment`
    - i3 and labwc call `*-sensible-terminal`, which read
      `$TERMINAL`; qtile's `guess_terminal()` ignores it but walks a
      fixed list every enum value is on; the rest have their config
      rewritten at provision by `swap_terminal`, which warns rather
      than no-op when upstream renames its default
    - xmonad keeps xterm, its terminal is compiled into
      `XMonad/Config.hs`
- `noctalia` profile (v5 native shell from extra), compositor
  multi-select over niri, hyprland, sway and labwc, niri default.
  Per-compositor autostart, IPC binds and layer rules from the
  noctalia docs. `pyproject` package-data now ships xml and
  extensionless configs, so the sway config is packaged at all
- dms drops its 16 vendored configs for upstream's
  `dms setup headless`, verified byte-for-byte against what it
  replaces. Per user through `arch_chroot(run_as=)`; it also writes
  the `hyprland-session.target` we hand-rolled and niri's
  `windowrules.kdl`, which we never copied. Only the terminal
  differs, so `swap_terminal` repoints the keybind after
    - `dms-greeter` is gone: dms-shell 1.6.0 embedded the UI in the
      binary and dropped `/usr/share/quickshell/dms`, so our
      `config.toml` named a path that no longer exists and greetd
      hit its start limit every boot. `GreeterType.Tuigreet`
      replaces it (in extra, draws on the tty, no seat group) and
      both greetd front-ends share one config write
    - assets resynced from upstream's embedded configs: the
      `dankdash` ipc verb is `dash toggle` now, so Mod+Y was dead
- Seat access names the mechanism, `systemd-logind` or `seatd`, while
  the value stays the package (the nvchecker and saved-config token).
  Only seatd ships a unit, so `seat_services()` replaces seven
  identical bodies that enabled a D-Bus-activated polkit with no
  `[Install]` and printed systemd's "not meant to be enabled".
  `usermod` centralised in `add_to_seat_group`
  (archlinux/archinstall#3467)
- DNS is a system-wide choice with DNS over TLS (#4757).
  `DnsProvider` (quad9, cloudflare, google, custom) carries the servers
  and the certificate name resolved validates against, and the handler
  writes one `resolved.conf.d` drop-in for every network type.
  `Domains=~.` is what makes it system-wide: it puts the choice ahead
  of the per-link DHCP servers
    - the picker sits after the network type, with "use the DNS the
      network provides" as the way out and a DoT yes/no that
      defaults to yes. That entry carries `None`, which
      `Result.get_value()` refuses, so it reads `item().value` like
      `select_network`
    - `CUSTOM` takes space-separated addresses, checked one by one,
      plus the certificate hostname when over TLS
    - the DoH half of the request is a pass: DoT is native to
      resolved and dnscrypt-proxy would be a second resolver daemon.
      A foreign host warns, its `resolv.conf` being a copy
- NetworkManager resolves through systemd-resolved like the iwd, iso
  and manual paths already did. NM flips to `dns=systemd-resolved`
  itself once `/etc/resolv.conf` is the stub symlink, so there is no NM
  drop-in to write. The enable-plus-symlink pair was spread over four
  branches and is one `Installer.use_resolved()` called after the type
  match, which fixes manual: it enabled resolved but never linked the
  stub, so glibc kept reading an empty `resolv.conf` and the DNS
  drop-in sat idle. `tests/test_network.py` locks both per type
- MAC address randomization, `keep` by default (#4758). NetworkManager
  gets a `cloned-mac-address` drop-in, iwd standalone
  `AddressRandomization` in `main.conf`, networkd a `.link` with
  `MACAddressPolicy=random`. `stable` is the per-network hash that
  keeps DHCP leases and captive portals working; it has no wired
  equivalent under networkd, so that path warns and keeps the hardware
  address
- `media_codecs` application category, the codec list from EOS'
- Boot layouts, each found in a VM and now unit-tested
    - limine reads FAT and ISO9660 only, but guided UEFI+btrfs put
      the ESP at `/efi` and left kernels in `@`: installed, then
      PANIC'd on the graphical console where serial showed nothing.
      The FAT rule `layouts.py` applied for BIOS now covers UEFI
    - UKI is self-contained on the ESP, so naming a separate `/boot`
      to `bootctl --boot-path` demanded XBOOTLDR typing for no gain
      and failed the install after a full pacstrap
    - `_validate_bootloader` only ran from the menu, so `--silent`
      died at `bootctl` after pacstrap. Now a pure function in
      `bootloader/validation.py` called from both: 1-2s exit, disk
      untouched. Two of its rules were wrong anyway (the XBOOTLDR
      one ignored UKI, the limine one never fired)
    - `boot != efi` compared `_obj_id`, a per-instance uuid4 in the
      dataclass `__eq__`: an identity check dressed as a value
      check, in two places. One `_has_separate_boot()` on `dev_path`
    - `_obj_id` is `str` everywhere: `__post_init__` minted a uuid4
      where `parse_arg` restored a str, so one partition hashed into
      two buckets by origin, and `PartitionModification` is a dict
      key in the luks handling
    - grub-btrfs: `--boot-directory=<esp>` when `/boot` isn't the
      ESP, so `grub.cfg` stops landing outside `@` and going stale
      against the kernel a restored snapshot brings back (#4701)
    - refind's kernel directory extracted from three nested branches
      into `_refind_kernel_dir`, strings preserved
- Disk
    - `partition()` removed every mapper `dmsetup ls` returned, so
      installing to a second disk from a LUKS/LVM host would
      `dmsetup remove --force` the host's own root. `/sys/block/
      dm-N/slaves/` is the source now, walked to keep only mappers
      ending on the target, deepest first
    - `partition_table` explicit per device modification
      (`gpt`/`msdos`, host default when absent), so a config stops
      meaning different things on different machines.
      `validate_bootloader` rejects bios_grub-on-msdos, BIOS+GPT
      +grub without bios_grub and MBR past 3 partitions before
      the wipe, each previously a parted traceback mid-partition
    - `disk_encryption` parses from config: `obj_id` references
      resolve to partitions and LVM volumes, unknown ids fail loud,
      no password in a silent install errors instead of hanging, and
      `json()` never writes the plaintext back
    - LUKS opens `--allow-discards --persistent`, so the flag lands
      in the LUKS2 header and every consumer inherits it. The
      `fstrim.timer` `minimal_installation` always enabled was
      trimming nothing on encrypted installs, btrfs' `discard=async`
      dropped the same way. VM: 61.7 GiB where it moved zero
      (#897, #1837)
    - lsblk fstype casing preserved, the enum spells `crypto_LUKS`
- Swap: BIOS resume offset in PAGE_SIZE units (`filefrag -b4096`),
  which the fs blocksize only coincidentally matched; swapfile
  failure warns instead of aborting; hibernate swapfile defaults on;
  zram sizes to RAM with a priority; models move to `swap.py`.
  Bootloaders take a serial console kernel parameter
- Nothing waits forever. NTP and keyring WKD sync were `while True`:
  a live ISO with a route but no reachability (filtered UDP 123,
  captive portal) hung before touching the disk. Both take 60s, then
  warn and point at `--skip-ntp`/`--skip-wkd`. The reflector wait is
  deleted: archiso v87 stopped enabling `reflector.service`, so it
  exited on its first poll printing a misleading "completed". Both
  ISO units skip entirely under `h2t`
- `SysCallError` subclasses `CalledProcessError`, `run()` re-raises
  into it with returncode, cmd, output and stderr mapped. Found via
  the sshd profile: `ufw allow ssh` failing in chroot escaped
  `allow_ssh`'s handler and aborted the guided install, and every
  `except SysCallError` around a list-form `arch_chroot` was dead the
  same way. Fixed one layer down, not per call site
- `_run_script` caught `ModuleNotFoundError` and re-raised only when
  the message named the script module, so a missing import inside a
  script returned normally and `--script guided` exited 0 having done
  nothing. Matches on `e.name` now
- The temporary doas rule for AUR builds is scoped to pacman. Sudoers
  already granted NOPASSWD for `/usr/bin/pacman` only; doas got
  `permit nopass <user> as root` with no `cmd`, so the build user
  held unrestricted root for the whole grimoire run
- `--silent` for `packages` and `live`, and the silent/menu flow in
  all three scripts collapses into one `resolve_config`.
  Running-system flows provision the invoking user
  (`Os.invoking_username`: sudo, run0, doas): `packages` skipped it
  entirely, `live` falls back to it when auth was skipped
- Every environment variable is `A2_*` prefixed, across `isos/ISOMOD`,
  the cache script and the bootstrap
- Cirrus amps split over the ACPI bus are detected by walking module
  dependencies, which also restores the missing `libvpl`
- grimoire: `run0` is skipped when `pkcheck` is absent (it auths via
  polkit, an optdep of systemd) instead of chosen and failed; doas
  matches `cmd` rules against argv[0] as typed, so the command is
  resolved through `which`, the way makepkg hands `PACMAN_AUTH` a
  full path
- An installed release reports `REL`: git always fails around
  site-packages, so the plain fallback labelled every installed
  package `DEV`. `resolve_gitstat(has_pkgbuild)` tells a stamped
  wheel from a checkout
- Three helpers in `tui/prompts.py` absorb the menu boilerplate:
  `prompt_yes_no` (17 sites over 11 files), `prompt_text` (11 over 8)
  and `prompt_choice` (33 over 15). Each takes a header, a preset and
  `allow_skip`, returns `None` for a skip, and overloads give a bare
  value where skipping is off, so the caller reads the answer instead
  of the result type. The unreachable Reset branches and their
  "Unhandled result type" raises go with them, and
  `select_encryption_type` stops focusing by display text against enum
  values, which had kept the cursor off the preset
    - left alone deliberately: menus that branch on skip vs
      selection, the two with preview panes, the list-manager core,
      the two-column btrfs mount-option picker, the hidden-input
      password prompts and the curses-internal interrupt prompt
- `_Category` base collapses the 14 single-field application config
  classes to a decorator, a class line and a field: `json()` and
  `parse_arg()` read the one field, whether it holds an enum, a bool
  or a list. `ApplicationConfiguration._config_parsers` derives from
  its own fields, so a new category is the field plus its TypedDict.
  Field names and keyword construction unchanged, so menus, handlers
  and saved configs are untouched
- `color_pair` gated behind `has_colors`
- Lint and typing: ruff `ANN`, `B006`, `B905`, `RUF015`, `RET504`,
  `B909`, `C419`, pylint `cell-var-from-loop`,
  `attribute-defined-outside-init`, `raise-missing-from`,
  `unspecified-encoding`, `unused-argument`. `pre-commit --all-files`
  green with no path shims, mypy and pylint scoped to `^installer/`
  minus tests to match CI, local parted stubs replace the
  dependency's own (#4735). Examples carry `terminal_config` and the
  full sample fills `power_management`, `cpu_scheduler`,
  `development` and `sysctl`, round-tripped by `test_config_drift`
- PCH: Simplified pre-commits hooks
- Fix parted stub

0.1.14-2

- Boot partition mount hardening (#4714), centralised in
  `_harden_boot_options` and shared with the encrypted-`/boot` path
    - `nodev,nosuid,noexec` on ESP, XBOOTLDR and `/boot`;
      `nosymfollow` only on ESP/XBOOTLDR (a plain `/boot` keeps
      symlinks, UKI layouts use them)
    - FAT32 masks tightened to `fmask=0177,dmask=0077` (files 0600,
      same set systemd mounts boot media with) and now cover XBOOTLDR,
      not only the ESP: an initramfs on a world-readable `/boot` can
      carry a keyfile
    - An explicit opposite in the saved config wins (`exec`, `dev`,
      ...): mount takes the last option. `BootMountOption` maps what a
      config spells to the hardened form; tests in
      `test_mount_options.py`
- systemd-boot with a separate `/boot` is validated up front: it must
  be FAT and marked XBOOTLDR. bootctl verifies the partition GUID but
  never the filesystem, so an ext4 XBOOTLDR installed cleanly and then
  booted into an empty menu
- `sched_ext` CPU scheduler choice under Applications (`--advanced`):
  the 13 scx schedulers split stable/experimental per the upstream
  maturity table. Installs `scx-scheds` + `scx-tools`, writes
  `/etc/scx_loader/config.toml` and enables `scx_loader`; userspace
  BPF, no initramfs involvement
- `kernel.nmi_watchdog = 0` joins the sysctl defaults: the hard lockup
  detector fires periodic NMIs while CPUs are busy and pins a perf
  counter; KVM guests already default off
- Print service enables `avahi-daemon` next to `cups`: the dnssd
  backend needs the daemon running to discover network printers,
  resolved can't substitute (OpenPrinting/libcups#81)
- `ntsync-autoload` in Management: modules-load.d entry so Wine/Proton
  find `/dev/ntsync` (kernels ship the module, nothing autoloads it)

0.1.14-1

- Version tracking (`nvchecker/NVGEN`) covers what a config can install,
  not only what `schema.jsonc` lists: kernels and their `-headers`,
  vendor firmware, the gfx extras the schema cannot model (`dkms`,
  `nvidia-open-dkms`), microcode and seat access all live in code enums.
- `count`/`size` never expanded pacman groups
- Group membership comes from a single `pacman -Sgg`. Walking
  `/var/lib/pacman/sync/*.db` also read repos that sit on disk but are
  disabled in `pacman.conf` (`core-testing`, a local repo), pulling in
  members pacman would never install
- The installer and the tooling share one copy of each: `lib/schema.py`
  owns `schema.jsonc` (path, comment stripping, `SCHEMA`) and
  `lib/pm/groups.py` owns group expansion. NVGEN loads both by path,
  which is why they stay stdlib-only with no intra-package imports.
  Drift tests pin the sharing, the enum parse and the generated toml
- Log directory derives from `__file__`, never the cwd: `installer/logs`
  from a checkout, `/var/log/archinstoo` installed as root,
  `$XDG_STATE_HOME/archinstoo` for the rootless scripts, which have no
  business writing to `/var/log`. `Path.cwd() / 'logs'` gave `./RUN`
  (which cds into `installer/`) and pytest (run from the repo root) a
  copy each. Tests now log to a tmp dir
- `--version` pins its git queries to the source tree with `git -C`.
  They ran in the process cwd, so an installed archinstoo reported
  whichever repo you happened to stand in, and `DEV` outside one
- `build()` builds from a copy in `$srcdir`, file list from
  `git ls-files -co --exclude-standard`: `.gitignore` stays the only
  place listing what to leave out, and uncommitted work still builds.
  The stamp `sed` no longer leaves a dirty `_version.py`, `build/` and
  `dist/` behind in the checkout
- `linux-rt` and `linux-rt-lts` join the kernel list. `Kernel` and the
  zram models move out of `models/application.py` into `models/kernel.py`
  and `models/zram.py`
- lsblk filesystem names are normalised before the enum lookup: every FAT
  width reports as `vfat` with the width in `fsver` (a missing one falls
  back to FAT32, which an ESP nearly always is), and lsblk's `swap` is
  `linux-swap` to parted. A filesystem we detect but do not manage
  (hfs+, apfs, zfs) logs its value, not the parted repr that buries it
  under object addresses
- `SysInfo.mem_total` reads the single key it needs from `/proc/meminfo`.
  The old dict cached `MemFree`/`MemAvailable` alongside it, which are
  live values
- `CONFIG.md` states where the log directory and the saved config land in
  each case, and that `--clean` wipes both

0.1.14-0

- Repo layout: the middle `archinstoo/` directory is now `installer/`,
  so the package sits at `installer/archinstoo/` instead of three deep.
  Import name, `pkgname` and the console script are untouched
- Firmware detection reworked (`SysInfo.firmware_vendors`)
    - `0x17cb` was mapped to `-qcom`: QCA6390/WCN6855/WCN7850 bind
      `ath11k`/`ath12k` and those blobs ship in `-atheros`, so a
      Qualcomm wifi laptop pulled 171 MiB of SoC firmware it cannot
      use and missed the package it needs
    - `0x0cf3` is a USB vendor ID sitting in a PCI table (absent from
      `pci.ids`), so it could never match a `/sys/bus/pci` sweep
    - USB pass added over `/sys/bus/usb/devices/*/idVendor`: BT radios
      and dongles hang off USB even when the wifi is PCIe. Silicon
      vendors only, OEM rebrands do not name the chip
    - `0x1814` Ralink -> `-mediatek`, which carries `rt2x00`/`mt7601u`
    - `CIRRUS` and `OTHER` are taken unconditionally: CS35L41/CS42L43
      amps enumerate over ACPI/I2C/SPI and the catch-all has no vendor
      ID at all, so no scan can ever tick them
    - Default is `minimal` in a VM (virtio ships no blobs) and `full`
      on bare metal, resolved by `FirmwareConfiguration.default()` at
      `ArchConfig` build time, not in the menu
- `live`: kernel selection is back, and kernels, headers and firmware
  install as regular packages since `_base_packages` never runs there.
  Auth is no longer skipped, live mode does create users
- `packages` joins `live` in `NO_DISK_SCRIPTS`: target `/` means plain
  `pacman -S`, never pacstrap, so both skip `disk_depends`.
  `arch-install-scripts` moves up to `base_depends`
- Version has one source of truth: `read_pkgbuild_version()` reads
  `pkgver`/`pkgrel` off the PKGBUILD in a checkout, `__pkgstamp__` is
  only the wheel fallback that `build()` stamps via sed. Drift tests
  translate that sed back into a regex and apply it
- GNOME profile lists its packages instead of pulling the `gnome`
  group: the group carries ~60 packages (games, maps, weather, tour)
  that install fine after the fact
- Intel Gen12+/Arc: `vpl-gpu-rt` for the QSV runtime (ffmpeg, OBS),
  inert on older generations
- Upstream ports
    - `aarch64` root partitions were stamped with the `x86_64` type
      GUID, so `systemd-gpt-auto-generator` could not find `/`
      (e8ffccc8, mapping lives on `PartitionGUID` here)
    - Custom command scripts build their path with `LPath`, fixing
      `///var/tmp/...` in live mode where the target is `/` (fe162389)
    - `cockpit` takes `cockpit-storaged`/`cockpit-packagekit` (#4688)
    - `lsblk` goes through `run()` and `CalledProcessError`; profile
      URL import encodes with `data.encode()`
- Drop the "compile packages from source" advanced option: menu entry,
  `compile_packages` config key, installer branch and the `TVM` flag
- Locale: `set_locale` splitting moved into `localization/utils.py`
  next to the encoding menu that scopes itself the same way, and a
  failure now warns like `set_timezone` already did

0.1.13-7

- Init on arch ISO must skip updates

0.1.13-6

- Bootstrap `aarch64` from a non-Arch host: off `x86_64` every source
  follows [archlinuxarm.org](https://archlinuxarm.org)
    - `pacman.conf` + mirrorlist from ALARM packaging: `[alarm]`/`[aur]`
      repos, `$arch/$repo` layout, `SigLevel` already set
    - `archlinuxarm-keyring` + `pacman-key --populate archlinuxarm`;
      the no-op guard checks `<keyring>.gpg`, so an `archlinux.gpg`
      shipped by the host no longer short-circuits it
    - Accept `.pkg.tar.xz` (ALARM) next to `.zst` (Arch)
- `ID=archarm` counts as an Arch host: it was classed foreign, which
  skipped the python deps, forced plain `chroot(8)` over
  `arch-chroot -S`, and copied resolv.conf instead of the stub symlink
- Skip the keyring WKD sync wait off `x86_64`, same guard as reflector
  (unit ships with `archlinux-keyring`, the wait is unbounded)
- GRUB/Limine EFI on `aarch64`: `arm64-efi` target, `BOOTAA64.EFI`
- Split bootstrap deps `base_depends` / `disk_depends`
    - `live` takes base only: no disk, no chroot, and `pyparted` stays
      out to match the import guard
    - `live` skips the `-S python` refresh + re-exec (nothing to reload,
      and it partial-upgrades a running system)
- `--script` alone decides the code path; `ArchConfig.script` is a
  label saved into the JSON (`--config-url` must not pick what runs)
- Rename `ConfigurationHandler` -> `ConfigStore`: parse side binds
  `handler`, persist side binds `store` (`rescue` was shadowing its
  own `DeviceHandler`); `clean_up()` -> `clean_logs()` next to
  `clean_cache()` in `checkpoints.py`
- `guard_host_conf()` requires an Arch host: on a foreign one the
  snapshot ran after `pm/bootstrap.py` had written the conf, so the
  atexit restore put the modified file back
- Live mode: install selected firmware, sync artifacts to
  `/etc/archinstoo.d`, skip the iwd/networkd copies when target is `/`
  (copying a path onto itself raised `OSError` 22)
- Fix TUI focus crash; disk delete + resume parse, both with tests
- Locale fixes (#4640), then re-pooled against 3 upstream PRs
- Schema: drop waybar, sync hyprland/pipewire lists, add `rtkit`,
  align Hyprland with the welcome-screen set, trim sway selection
- Chores: renovate (ruff 0.15.22, mkdocs-material, setup-python v7,
  checkout v7.0.1), `nvchecker` entries, `fmt: off` fence so ruff
  stops collapsing the dep tables

0.1.13-5

- `live` mode no longer needs `pyparted` at all
    - `models/device.py` inlines the `PED_PARTITION_*` constants,
      `disk/device_handler.py` treats the import as optional
    - Two guards: constants match real pyparted values, and the live
      module chain imports with parted blocked
- `live` skips auth by default, `packages` mode fix
- `grimoire` sync (AUR builder)
- `_version.py` stamping fixes for CI
- DMS: hyprland `.lua` asset fix, VM testing helper in `TVM`
- Docs restructure, renovate (mypy 2.3.0, gh-release digest)

0.1.13-4

- Separate `architecture/` folder for non-x86 targets, moved out of
  `ARM_SUPPORT.md`
    - Generic `ARM` stage 1: partition, extract, keyring, locale,
      fstab by PARTUUID, wired DHCP, getty/sshd/networkd enabled
    - Per-board profiles: `rpi5` (foundation kernel + firmware swap),
      `generic-aarch64` (boots as shipped)
- Distro ID via `platform.freedesktop_os_release()`; collapse the
  redundant PEP 758 `except` in `hardware.py` to `OSError`
- `count`: add applet when desktop is `nm_*`
- Version stamping across PKGBUILD / `_version.py` / manpage, with an
  args test
- Docs: view-on-GitHub links in mkdocs
- Style: drop dead paths in disk, luks, hardware, installer,
  `list_manager`, output, `profiles_handler`

0.1.13-3

- Add `dms` profile (DankMaterialShell): port upstream niri profile
  (#4554), generalized behind a compositor multi-select
    - Compositors: `niri` (kdl assets) + `hyprland` (DMS-managed
      `.lua` configs); packages swap per selection in `_resolve`
    - Seat access picker (seatd) like other wlr compositors
    - `dms` schema type + drift test coverage
    - `ttf-jetbrains-mono-nerd` over full nerd fonts
- Greeter joins `seat` group for dms greeter too (was regreet-only,
  its compositor couldn't open DRM under seatd)
- `niri`: `seat_access` no longer hardcoded to seatd; chosen
  provider appended to packages
- LUKS: keyfile slots follow menu pbkdf and `iter_time` choices
  (`luksAddKey` defaults new slots to argon2id otherwise)
    - 200ms iteration floor matches the GRUB `/boot` clamp; better
      helptext when `/boot` is encrypted
- Config save omits unset values (`None`/empty string)
- Continue `distros/` documentation and upstream fix

0.1.13-2

- More LUKS2 ciphers: `serpent-xts`, `aes-hctr2`, `camellia-xts`
    - Kernel-validated specs; `--key-size` derived from cipher at
      the cryptsetup boundary (XTS 512, single-key 256)
- Add `argon2i` KDF; `--pbkdf-memory` + GRUB `/boot` constraints now
  cover both argon2 variants (were argon2id-only)
- FIDO2: `token-timeout=5`, absent token falls back to passphrase in
  ~5s instead of the 30s crypttab default
- Distros: fix Alpine bootstrap hang
- Tests: drift guard keeps `Arguments` in sync with manpage
- Chores: newer python features, stale cfg/docs cleanup, `_` file
  naming, docs refreshes

0.1.13-1

- Add LUKS2 cipher selection (`EncryptionCipher` enum threaded to
  `--cipher`; `None` = `aes-xts-plain64` default)
    - Adiantum/chacha20 for CPUs without AES acceleration
    - LUKS size passthrough on parent menu
- Add FIDO2 token LUKS auto-unlock (HSM)
    - Detect via `systemd-cryptenroll --fido2-device=list`; token
      picker in encryption menu, passphrase keyslot as fallback
    - Strap `libfido2` before initramfs; combined `rd.luks.options=`
      (tpm2/fido2), fido2 gets `password-echo=no`
    - Menu hidden when no token, gated behind a ready prompt
    - `SYSTEMD_EMOJI=0` so cryptenroll PIN/touch prompts stay plain
- Extend locale for Wayland: ENV vars + keyboard models, variants,
  options; fallback to fetch from official tree
- Distros: bootstrap on non-Arch hosts
    - Add `NIX` (flake) + Alpine scripts; `distros/`
    - Skip services/foreign-host checks off Arch; fallbacks for tz,
      crypt sha512, missing deps, reap pid helper
- Splash defaults to off; separate `/home` also defaults to off
- Fix sshd profile selection (#4616)
- Port upstream #3845: `Root only` LVM layout (no separate `/home`,
  whole VG to root)
- Remove fragile `systemd` version comparisons
- `ooo`: handle Ctrl+C cleanly mid-install
- `uki`: random seed warning fix
- Suppress noisy GRUB output, kept in `cmd_output.txt`
- LVM: relax pv check (experimental)
- CI: tag naming change, release archive carries short SHA

0.1.13-0

- Add bootstrap path to support installing from Debian FORKY OR SID
    - Separate `distros/` with deps and a dockerfile for reprod
- Remove locales as it's not maintainable solo/had already drifted
- Rework nvchecker and tests to always match jsonc schema

0.1.12-0

- Add `cage` + `regreet` greeter option (greetd-based graphical login)
    - Seatd-compatible Wayland login
    - `greeter` user joins the `seat` group only when seatd is
      installed, so non-seatd installs don't break
- Seat access: drop the greeter gate, still default to `ly` under seatd
    - All greeters are selectable again instead of filtered out; seatd
      just preselects `ly` as a compatible baseline
- Mirrors: fix the speedtest timeout not bounding the connect phase
    - Dead/multi-homed mirrors now skip in ~5s instead of ~40s
      (arm the timer before `urlopen`, not just around `read()`)
- Applications: `Development` no longer shows as modified when entered
  and left without selecting anything
- Help: add an `ESC` hint to the menu
- `TVM`: dev test VM gains sound and teardown
- Docs: refreshes, typo fixes, page split

0.1.11-1

- Add `Development` langs/tools under `--advanced` Allows for faster tooling
  for devs.
- Add `shfmt` to flow, run it on all files: for 8 spaces = tab.
    - Follows https://docs.kernel.org/process/coding-style.html

0.1.11-0

- Documentation moved to MkDocs + Material, published to GitHub Pages via
  Actions
    - Source stays in-repo (`.github/*.md`); `README.md` is the
      single-source index (symlink), cross-tree links made absolute
    - GitHub-style `> [!TIP]` alerts render as Material admonitions
      (callouts plugin)
    - Build/deploy only runs on `.md` or docs-config changes
- Type hygiene pass across the installer
    - `cast` / explicit `Any` / `# type: ignore` cut down to genuine
      FFI/dynamic boundaries
    - `ANN401` + banned-`cast` ruff rules added as a ratchet against new
      ones
    - JSON boundaries validated instead of cast; ctypes/parted reads typed
      via annotation
    - archlinux.org mirror v3 schema modeled as `TypedDict`
    - Removed the dead `archlinux.de` fallback (API is `410 Gone`) and a
      latent `to_json` datetime bug with it
- `scripts/size.py`: estimate target-system size from a saved config
    - Reuses `count.py`'s package resolution (extracted to
      `scripts/_resolve.py`); sums sync-DB download/install sizes via
      `expac`
    - `--unit` to pin the output unit, `--top N` for the largest packages
- Package manager fixes
    - Never leave the host `pacman.conf` modified after a host-to-target run
    - Route GPGME/signature sync errors to a keyring reset
- LUKS: surface `cryptsetup` error output when unlock fails (upstream fix
  credits to 0xdeadd)
- Hardware: add Marvell PCI IDs
- btrfs: level-1 zstd for the default layout (credits to Tiagoquix)

0.1.10-1

- Enum value parsing fixes and schema consistency

0.1.10-0

- Enum normalization: all profiles, bootloaders, drivers,
  snapshot/network/disk types to lowercase/kebab-case
    - Added `display_name()` methods for human-readable UI display
    - Schema keys aligned with enum `.value` fields
    - Saved configs with old casing require migration (altho most people use
      the menu directly, this will allow for better checks/tests)
- Hardware detection allow for vendor specific list in `Firmware`
    - Updated `count.py` to work with new firmware choice logic
- Fix doas path resolution on grimoire installs
    - Configure `/etc/makepkg.conf` directly
    - Validate AUR selection requires elevation
    - Update grimoire to new version -> respects makepkg properly now + other
      various changes
- Convert docstrings to plain `#` comments
- Logging fixes: Escaping using existing helpers
- Clean up help text and user-facing messages
- Add `exhaustive-match` pattern (16 sites)
    - Enable additional ruff rules (noqa S shell, cast reduction)
- Remove test hacks from upstream
- Remove easter-egg
- Leave fork network
- Various docs refreshes

0.1.09-0

- UKI + grub-btrfs co-existence (#4505)
    - `add_bootloader(..., keep_standalone_initramfs=True)` keeps
      `linux`/`initrd` artifacts alongside the UKI
    - when the user picks GRUB + UKI + btrfs snapshots (grub-btrfs's
      snapshot entries cannot consume a UKI)
    - `_config_uki` rewrites mkinitcpio presets: prepends `default` to
      `PRESETS=(...)`, skips the redundant-image unlink, leaves the standard
      initramfs in place
    - Decision pulled into `scripts/guided.py` so `setup_btrfs_snapshot`
      runs after bootloader install with the correct flag
- Fix grub-btrfs cfg path when ESP is not at `/boot`
    - grub-btrfs's `41_snapshots-btrfs` hook hardcodes `/boot/grub`;
      archinstoo writes `<esp>/grub` when the ESP mounts at `/efi`
    - Now writes `/etc/default/grub-btrfs/config` with
      `GRUB_BTRFS_GRUB_DIRNAME="<esp>/grub"` so snapshot entries land in the
      right cfg
- `iwd` standalone option in network menu
    - New `NicType.IWD` alongside existing `NM` and `NM_IWD`
    - `Installer.configure_iwd_standalone()` writes `/etc/iwd/main.conf`
      with `EnableNetworkConfiguration=true` +
      `NameResolvingService=systemd` (iwd handles DHCP, systemd-resolved
      picks up DNS)
    - `NetworkHandler` enables `iwd` + `systemd-resolved`, no NetworkManager
      pulled in
    - `scripts/count.py` adjusted accordingly
- Profiles: package customization step
    - New `Customize packages` menu item under `ProfileMenu`, multi-select
      toggle of each profile's package list
    - Persists exclusions in `Profile.custom_settings['excluded_packages']`;
      only enabled when at least one selected profile is non-`Minimal`
    - Drops dead-code "advanced" profile path
- `--list` rework
    - Adds `count` to `ROOTLESS_SCRIPTS`
    - New `DEFAULT = 'guided'` sentinel; `scripts/list.py` marks the default
      script in the listing and pulls the `[*] requires root` legend out of
      the per-row formatting
- TUI: SIGWINCH race condition in `Tui._sig_win_resize`
    - Bails when `Tui._t is None` (signal can fire after teardown)
    - Wraps `curses.endwin()`/`initscr()`/resize in `try/except
      curses.error`
    - `__enter__` saves the previous SIGWINCH handler; `__exit__` restores
      it (was leaking into the host process after `Tui` exit)
- `pm/packages.py`: additional packages parsing
    - `installed_package` / `enrich_package_info` switch `.strip()` ->
      `.rstrip()` so leading whitespace in pacman `-Q --info` / `-Si` output
      (continuation lines for multi-line fields) is preserved
    - Avoids field merges when a value wraps onto the next line
- Log / artifact sync ordering
    - `_sync_artifacts_to_target` promoted to public; called from
      `scripts/guided.py` rather than `Installer.__exit__`
    - Sync now runs before the post-install menu (chroot, reboot, exit) so
      users picking "exit" still get the artifacts on target
    - Sync also runs on the failure path
    - `pathnames.ARTIFACTS_STORE` now relative to `/` so chroot-relative
      writes resolve correctly
- `share-log` curl hint switched from `0x0.st` (dead) to `paste.rs` (`curl
  --data-binary @<log> https://paste.rs`)
- `scripts/count.py` parsing fixes
    - Excludes-count cleanup for profiles that contribute no packages
    - Per-profile parsing reworked to match the new exclusion field
- `examples/config-sample-full.json`: add missing fields, drop stales from
  `schema.jsonc`
- `desktops/hyprland.py`: drop `polkit` (pulled in transitively); revert
  niri profile change
- Drop `cutefish` desktop profile (#4514, upstream-archived project)
- `network/`: small helper for nic stub creation path
- `sysctl`: drop redundant duplicate lines
- Dev: `TVM` script (renamed from `VM`) for boot-time VM smoke-test (credits
  to softer)
- Deps: bump `mypy` to v2 (project + pre-commit mirror), pre-commit hooks
  resync
- Various docs refresh (BUILD_ISOS, DEP_CHAINS, link/block fixes)
- Various upstream syncs

0.1.08-0

- Auto-detect firmware vendors from PCI bus
    - New `_SysInfo.firmware_vendors` cached probe reads
      `/sys/bus/pci/devices/*/vendor` maps to `FirmwareVendor` enum names
    - AMD disambiguation via `/sys/bus/pci/devices/*/driver` symlink (amdgpu
      vs radeon)
    - VM short-circuit (`SysInfo.is_vm()`) skips PCI scan on virtio-only
      hosts
    - `select_firmware()` seeds the multi-select with detected vendors only
      when no preset is loaded saved configs with explicit picks still win
- Refactor: `DisplayServer` moved out of `hardware.py`
    - Now lives in `lib/profile/base.py` next to `ProfileType` /
      `GreeterType` / `SelectResult` (it's a profile-derived choice, not a
      hardware probe)
    - `GfxDriver.gfx_packages()` no longer bundles xorg-server/xorg-xinit
    - Multi-profile X11+Wayland selection now correctly includes X11 base
      packages (passes aggregated `profile_config.display_servers()`
- `info(..., step=True)` for major install-phases
    - Prepends `==>` (cyan) and trailing blank line for visual separation
    - Wired at `mount_ordered_layout`, `minimal_installation`,
      `add_bootloader`, `create_users`, `genfstab`
- `cleanup.py` module extracted from `installer.py` (#4496 upstream port)
    - `teardown_layout` / `swapoff_layout` / `close_luks` as free functions
    - `__exit__` runs teardown via `try/finally` on every exit (success and
      failure paths)
    - `_layout_teardown_required` flag guards no-op when nothing was mounted
- Minimal ISO support
    - `isos/ISOMOD` reworked to optionally produce minimal ISOs (smaller,
      faster build)
    - Doc updates with size references (`.github/BUILD_ISOS.md`)
- Post-install artifact sync uses `ARTIFACTS_STORE` constant from
  `pathnames.py`
- `_sync_artifacts_to_target` simplified (loop over `(src, dst)` pairs, drop
  `import stat`)
- Various docs refresh
- Various upstream syncs

0.1.07-1

- CI: add `release.yaml` workflow to auto-publish a GitHub release when
  `PKGBUILD` `pkgver`/`pkgrel` change
- Publish `.tar.gz` as official format
- Fix deps pinning, remove 'procps-ng' dep by using python internals

0.1.07-0

- TPM2 auto-unlock for LUKS-encrypted installs (opt-in)
    - New `Installer.enroll_tpm2()` runs `systemd-cryptenroll
      --tpm2-device=auto` in chroot at bootloader-install time
    - Hardware-gated menu toggle: only appears when
      `/sys/class/tpm/*/tpm_version_major == 2` (`SysInfo.has_tpm2()`)
    - Multi-select PCR picker restricted to firmware-time PCRs (0, 1, 2, 3,
      7) that are stable host->target
    - Default `0+7` (firmware + Secure Boot state); passphrase keyslot stays
      as fallback
    - Adds `rd.luks.options=tpm2-device=auto` to kernel cmdline so
      sd-encrypt attempts TPM2 unlock at boot
    - Transient unlock keyfile follows `_create_root_keyfile` convention
      (`/etc/cryptsetup-keys.d/`), unlinked after enrollment
- Post-install artifacts synced into target for debugging
    - `/etc/archinstoo.d/<UTC-timestamp>-install.log` and
      `<UTC-timestamp>-config.json`
    - Runs in `Installer.__exit__` on both success and failure paths
- Target teardown on successful install (`_teardown_target`)
    - Encryption-topology-aware order: unmount tree, deactivate VGs, close
      LUKS mappers
    - Lazy `umount -R -l` fallback for stuck mountpoints (e.g. leftover
      `arch-chroot -S` scopes)
    - `cryptsetup close --deferred` so the close completes asynchronously
      without blocking on residual fds
    - Host left in a clean state after install: re-running the installer no
      longer requires a reboot to clear stale mounts/mappers

0.1.06-1

- Sibling import cycle cleanup (internal restructuring)
    - 24 -> 3 cycle-breaking edges across `archinstoo.lib`
      (`grimp`/`import-linter`)
    - Move `crypt.py` out of `authentication/` (leaf util)
    - Move `fetch_data_from_url` / `DownloadTimer` from `network/` into
      `utils/net.py`
    - Move `confirm_abort` into `tui/prompts.py`; `get_password` into
      `authentication/password_prompt.py`
    - Move `add_number_of_parallel_downloads` into
      `pm/config.set_parallel_downloads`
    - Move `select_driver` into `profile/driver_select.py`
    - Promote `default_profiles/profile.py` -> `lib/profile/base.py`
    - Move `DisplayServer` enum into `hardware.py`; inline
      `Profile.display_servers()` (drop lazy handler call)
    - Rename `network/utils.py` -> `network/interfaces.py`
- CI track-only checks (`continue-on-error: true`)
    - Add `import-linter` sibling-acyclicity contract
    - Add `vulture` dead-code detection (with `vulture_whitelist.py` for
      false positives)
- Remove dead code surfaced by `vulture` (~17 symbols across `installer.py`,
  `models/device.py`, `tui/`, etc.)
- `disk` module split (`disk.layouts` + `disk.selectors`) to reduce
  circular-dep risk
- `ISOMOD_LIVE` script merged into `ISOMOD` via `LIVE=1` flag (single source
  for installer + Plasma live-session ISO)
- AUR update menu: wire up `format_update_candidate` print loop in
  `interactive_select_updates`
- Add `mkinitcpio` to `__base_packages__` (#4484 upstream port)
    - Pacstrap installs deterministically; avoids dracut/etc. being picked
      up on non-Arch hosts (EndeavourOS prefers dracut)
    - Various other upstream ports
- Add `audit` security option
- Drop `ELEV` workaround hacks
- Docs: new `.github/BUILD_ISOS.md` for ISO scripts and flags

0.1.06-0

- Explicit vendor firmware selection
    - New `firmware.py` model + `system_conf` interaction wired into global
      menu and guided script
- Rework explicit `None` options
    - Always last in the menu, returns `None` to config
    - Available in `Greeter`, `Graphics driver`, `Network`, `Audio`,
      `Bootloader`
- LIVE KDE ISO
    - New `isos/ISOMOD_LIVE` script
    - Plasma profile cleanups (drop redundant entry, add missing
      `bluedevil`)
    - Switch plasma to new plasma-login-manager (PLM)
- Seatd vs logind conflict (#4471)
    - Return `ly` greeter when `seatd` is selected
    - Guard `uses_logind()` to filter based on `seat_access` selection
      (`needs_seatd`)
    - Drop `default_greeter_type` from most Wayland profiles (works for
      aggregate profiles)
- Wire `makepkg.conf` to `elev` (privilege escalation selection)
- Installer edge case fix on non-Arch host distros
- Upstream syncs
    - #4443 installer cleanup
    - #4473 new pattern for commands + port across call sites (audio,
      docker, shell, installer)
    - #4474 port smaller validations into global menu

0.1.05-2

- Upstream syncs:
    - Enum casing: `FilesystemType`, `ModificationStatus`, `PartitionType`
      migrated to `StrEnum` / UPPER_CASE
    - Limine validation uses `FilesystemType.is_fat()`; block install when
      ESP is boot partition mounted outside `/boot` without UKI
    - Power management: auto-enable `power-profiles-daemon.service` /
      `tuned.service` based on selection
    - New `LPath` helper and `pathnames` module (centralizes
      `ARCHISO_MOUNTPOINT`, `PACMAN_CONF`)
    - Installer ordering, mirrors, pacman config, budgie profile, general.py
      tweaks
- Docs: refresh project structure tree
- Enable ruff `RUF051` (if-key-in-dict-del)
- Dependency updates

0.1.05-1

- Deduplicate `_base_packages` before pacstrap
    - Multiple bcachefs partitions (e.g. / + /home) caused duplicate
      packages
- ESP permissions fix for /boot mountpoint
    - `fmask=0077,dmask=0077` now applied when ESP is mounted at `/boot`
      (was previously skipped, leaving random seed file world-accessible)
- PM: file:// custom repos inserted before [core] for priority
- Custom repos (file://) stripped from target pacman.conf on persist
    - Install-time local cache repos no longer leak into the installed
      system

0.1.05-0

- Limine bootloader fixes
    - Fix boot failure when EFI is mounted at `/efi` or `/boot/efi`
        - `path_root` was incorrectly staying as `boot()` instead of
          `uuid(root)`,
          causing kernel/initramfs paths to resolve on the EFI partition
          rather than root
    - Fix `limine.conf` placed in wrong location for removable installs
        - Config was written to `/boot/limine/` instead of alongside the EFI
          binary on the ESP
    - Both bugs are upstream regressions from archinstall#3932
- AUR: fix install order to run after DE/WM profile
    - Prevents AUR packages that depend on a desktop environment from
      failing
- grimoire improvements
    - Prefer `run0` over `sudo` in GUI mode
    - Add `--depth=1` to all git clone commands

    - Add kernel version and build date to env utils
- LUKS: explicitly load `dm_crypt` module before use
- `run()`: capture stderr from subprocess calls
- Installer: replace `SysCommand('sync')` with `os.sync()` builtin, remove
  custom `sync()` method
- Config: fix key name `archinstoo-language` → `archinstoo_language` in
  serialization/deserialization
- Repo: move ISO files into `isos/` subdirectory
- Docs: add multi-boot guidance for issue #100 (manual partitioning +
  `skip-boot` hint)
- Dependency updates: ruff 0.15.8, actions/upload-artifact v7,
  actions/checkout digest bump

0.1.04-0

- Bcachefs support
    - Add Bcachefs as a filesystem option
    - Bcachefs backed in ISO builds
- BIOS/GPT partitioning
    - Offer partition table choice on BIOS systems (MBR or GPT)
    - Add gating logic for BIOS/GPT layouts
    - Use `raw` type instead of `unknown` for unformatted partitions
- Sysctl editor
    - Add sysctl configuration editor
    - Allow Ctrl+C to clear editor values
- ZRAM configuration (#4266)
    - Add default option for kernel default ZRAM settings
    - Add recompression algorithm choice when not using default
    - Simplify ZRAM size to RAM / 2
- Btrfs
    - Add btrfs subvolume option (#582)
- Niri window manager Various fixes
- Encryption fix (#1856)
    - Check if `/` is not encrypted before generating keyfile
    - Skip keyfile generation and use password prompt for unencrypted root
- TUI input fixes
    - Fix dsync behaviors on TUI inputs (behave like insert mode)
    - Delete/backspace works correctly and never overwrites current selected
      char
- LVM refactor
    - Refactor LVM logic
- Advanced mode
    - Pass `--advanced` flag consistently for filesystems
    - Fix ownerships gate
- CI/CD and tooling
    - Pin dependencies
    - Drop flake8 in favor of more ruff rules
    - Remove flake8 workflow
    - Add nvchecker dependency to DEV PKGBUILD
    - Add nvchecker version tracking
- Documentation
    - Add man page
    - Drop Sphinx entirely
    - Remove readthedocs integration
    - Remove Python publish workflow
    - Add curl upload logs helper
- Chores
    - Remove legacy `wireless_tools`
    - Remove unnecessary post-init
    - Help text updates
    - Various linting and structure cleanups

0.1.03-2

- Refactor disk utils (DI Ext.)
- Add `plasma-login-manager` greeter
    - Add `PlasmaLoginManager` to `GreeterType` enum, schema, and install
      logic
- LVM fixes for `/efi`-only layouts
    - Fix `suggest_lvm_layout` crashing when no `/boot` partition exists
      (fallback to `/efi`)
    - Fix `/efi` partition not being formatted before LVM setup in
      `filesystem.py`
    - Exclude `/efi` from LVM physical volumes in both `/efi`-only and
      `/efi`+`/boot` layouts
- Bootloader defaults fix
    - Fix UKI and Removable disabled by default for GRUB (regression from
      `df046f01` merge)
    - `get_default()` was receiving `skip_boot` as `uefi` parameter
- Encryption improvements (#86 follow ups)
    - Refactor LUKS UUID helper from `device_handler` to `luks.py`
    - Add info print for key file generation (shows encryption type)
    - Fix auto-unlock gating: require EFI partition present
- Disk utils refactor
    - Separate `swapoff` into `disk/utils.py`
    - Clean up `device_handler` by moving utility functions out
- Misc
    - Fix rescue script paths
    - Remove private prefixes from public API methods
    - Contributing docs: add testing notes

0.1.03-1

- Encryption (#84)
    - Add choices for LUKS key derivation function (argon2id/pbkdf2)
    - Add auto-unlock option for non-UKI/GRUB setups (shorter iters for mem
      restrictions)
    - Switch from `encrypt` hook to `sd-encrypt` with systemd initramfs
      hooks
    - Update `schema.jsonc` for new encryption fields
    - New `EncryptionType` and derivation models in device module
- Security applications
    - Switch from `busybox` type initramfs hooks to `systemd` hooks
        - This will simplify `sbctl` and `systemd-cryptenroll` setups
    - Add `pam-u2f` and `sbctl` to Security category options
- Bootloader fixes
    - Fix efistub installation path
    - Refactor UEFI references across bootloader menu and models (cf)
- Error handling
    - Refactor error reporting for bad configs in global menu
    - Rename error variables in `grimoire.py` for consistency
- Resume and config fixes
    - Fix resume behavior for multiple settings
    - Fix profile names for `httpd` and `sshd` servers
    - Update `config-sample-full.json` with new fields

0.1.03-0

- Encrypted `/boot` separate `/efi` (GRUB + argon2id)
    - Constrain LUKS key slot PBKDF for boot partitions (`--pbkdf-memory
      32768 --iter-time 200`)
    - Apply same constraints to keyfile generation (`luksAddKey`) so GRUB
      can decrypt both slots
    - Add `--batch-mode` to `cryptsetup open` for non-interactive unlock
- ESP mount security
    - Mount FAT32 ESP with `fmask=0077,dmask=0077` (root-only access)
    - Applied at mount time in installer for both Default and Manual
      partitioning
- Default BTRFS layout: mount ESP at `/efi` so `/boot` stays in `@`
  subvolume
- User services and multi-profile support
    - New `UserService` model for per-user systemd `--user` services
    - Service enable/disable during installation
- Move `fail2ban` from Management to Security category
    - Service auto-enabled on install
- Schema and count script updates
    - Add `security` section to `schema.jsonc`
    - Count script now resolves security tools
- Docs
    - Add beginner notes and network documentation
    - Fix example configs
- Fix install order: kernel headers/AUR before profiles

0.1.02-1

- Add `smartmontools` to Server profiles (disk health monitoring)
    - This means original profiles list now only contains `xdg-utils |
      smartmontools` for desktops

Down from the original list (the rest is all in logic or choices), see PHILOSOPHY.

0.1.02-0

- Graphics driver improvements
    - Add VM-specific options: `VM (software rendering)` and `VM
      (virtio-gpu)` allows to pick guest drivers
    - `Mesa (open-source)` auto-detects vulkan-intel or vulkan-radeon based
      on hardware
    - Plan future implementation for other drivers
    - Virtual: `vulkan-driver`
    ALREADY COVERED: `nvidia-utils, vulkan-intel, vulkan-radeon, vulkan-swrast`
    `vulkan-virtio, vulkan-nouveau`
    MISSING: `vulkan-gfxstream, vulkan-dzn, vulkan-asahi, vulkan-freedreno`
    `vulkan-broadcom, vulkan-panfrost, vulkan-powervr`
    - Fix gfx driver installation OoO: now installs BEFORE profiles to
      satisfy vulkan-driver dependency.
        - This avoid pulling in nvidia-utils on a non-nvidia install because
          none was installed yet, default or 1st choice
    - Update recommendation text with VM detection hint
- Pacman config changes
    - Move `parallel_downloads` setting into Pacman config submenu
    - Fix misc options (ILoveCandy) not applied when option doesn't exist in
      pacman.conf
- Profile fixes
    - Rename `desktop_base` to `profile_base` (applies to any profile, not
      just desktop)
    - Move `wireless_tools` to wpa_supplicant backend installation (remove
      from always installed)
    - Fix profile types: DesktopEnv → WindowMgr
    - Add `preview_text` to WaylandProfile (shows "Type: X (Wayland) or
      (Xorg)")
- Bug fixes
    - Fix guided.py advanced options disappearing on install
    - Fix duplicate grub package installation on UEFI systems
    - Fix locale menu

0.1.01-1

- New Security application category
    - AppArmor support with kernel LSM parameter
      (`lsm=landlock,lockdown,yama,integrity,apparmor,bpf`)
    - Firejail namespace-based sandboxing
    - Bubblewrap low-level sandboxing
    - Multi-select menu allowing any combination of tools
- Add `fail2ban` to Management tools
    - Auto-enables `fail2ban.service` on install
- Add `run0` privilege escalation option
    - Systemd-based alternative to sudo/doas
    - Uses polkit, wheel group membership sufficient
    - Update `base-devel-flex` and `grur`
- Add poweroff option to post-installation menu
    - Useful for VM testing workflows
- Installer improvements
    - Add `add_kernel_param()` method for applications to set kernel
      parameters
    - Order:
        - Move user creation before application installation
        - Move application installation before bootloader (kernel params now
          included in bootloader config)
- CI/Workflow changes
    - Remove publish workflow
    - Trigger only on pushes
- Test updates
    - Add security_config to test fixtures
    - Update test assertions for new Security model
- Work on grimoire linting

0.1.01-0

- ARM (aarch64) support
    - Parse ARM mirror list format (different HTML from x86)
    - Filter out `reflector` on non-x86_64 (not packaged for ARM)
    - Remove `vi` from desktop_base (conflicts with vi compat on ARM)
    - Skip filesystem ops when `wipe=False` on existing partitions and safe
      copies
    - Graphics driver menu shows only Mesa on ARM
- Live mode (`--script live`)
    - New `scripts/live.py` for configuring the running system in-place
    - Uses `pacman -S --needed` instead of `pacstrap` when target is `/`
    - Skips `arch-chroot` wrapper when target is `/`
    - Disables disk, bootloader, kernel menu items
- AUR support
    - Full AUR builder (`grimoire.py`) with temporary non-root user
    - AUR packages menu item (gated behind `--advanced`)
    - Fix `/tmp` writability inside chroot for `makepkg`
    - Out-of-tree DKMS module builds
- Graphics driver overhaul
    - Replace `VMOpenSource` with `MesaOpenSource` (mesa +
      libva-mesa-driver)
    - VMs and ARM both default to Mesa group
- Boot / EFI fixes
    - Move EFI bitness check to `hardware.py`
    - Fix UKI, Grub, EFISTUB installation paths
    - UEFI `/efi` partition maps to `/boot`
    - Add pretty name to `os-release`
    - Named kernel entries (e.g. `linux-zen`)
    - Show `linux-home` flag when mountpoint is `/home`
    - Dependency injection for bootloader skip logic
- TUI improvements
    - Remove hjkl nav in favor of type-to-search anywhere
    - Fix number key selection
    - Add `greetd` greeter choice (hopefully cage option soon)
- Code quality
    - Lazy imports in `__init__.py`
    - Move `ROOTLESS_SCRIPTS` to `__init__.py`, fix `--list` formatting
    - Type checking and mypy fixes
    - Fix resume behavior for saved configurations
- Docs
    - Add multi-boot guide
    - Add AUR / out-of-tree docs
    - Add philosophy doc
    - Update contributing and headless guides

0.0.02-2

- Add Tailscale and Java (headless JRE) server profiles
- Add headless server docs with SteamCMD examples
- Add fallback for common Pacman issues
    - Auto-reset keyring on corrupted packages
    - Auto-reset pacman.conf on dependency failures
- Fix #3772 try block for mount, add `wipefs` helper
- Rename `ask` functions to `select` across codebase
- Remove `advanced_mode` from profile base class
- Remove all `from __future__ import annotations` lines
- Change to absolute imports (35 files)
- Apply ruff lint rules A, C4, FURB, PERF, RET, SIM (-307 lines net)
- Default root elevation to empty
- Add `-h2t` flag to DEV script
- Add `--depth 1` to docs clone
- Fix honest `type: Any` in output
- Peek outputs for pacman operations
- Docs restructure and refresh

0.0.02-1

- Add `/boot` encryption support (#74)
- Fix /efi mount logic
    - /efi exists → /efi = ESP, /boot = no flags needed
    - /efi absent → /boot = ESP
- Mark bootable flag only available for BIOS systems
- Add `argon2id` memory limit, better print
- Remove `wget` and `openssh` from defaults
- Auto select Minimal Profile and remove from list
- Add separator between base actions and terminate actions
- Fix duplicate entries/filters
- Clean up comments

0.0.02-0

- Encrypted swap partition support (#4169)
    - Allow swap partitions in the encryption selection menu
    - Add `swap` mapper name (consistent with `root`/`home` naming)
    - Call `swapon` on the LUKS mapper device for encrypted swap
    - Manually add swap fstab entry (genfstab doesn't capture swap mount
      points)
    - Generate keyfile and crypttab entry for passwordless unlock at boot
- Fix multi-select ENTER key behavior
    - ENTER now only confirms selection, no longer auto-selects the focused
      item
    - TAB/SPACE remain the toggle keys for individual items
- TUI search improvements
    - Auto-activate search when typing any letter (no longer need to press
      `/` first)
    - Remove `/` requirement from help text
- Remove `--silent` flag
    - Disk and Authentication config are always required interactively
    - Remove `silent` param from `Pacman`,
      `ConfigurationHandler.prompt_resume()`
    - Fix custom commands only running with `--advanced` flag
- Fix final confirmation prompt using recursion
    - Replace recursive `return guided()` / `return format_disk()` with
      `while` loop
    - Fix `sudo` mention to `elevated privileges` in validation message
- Console fonts listing
    - Skip `README*` files in `/usr/share/kbd/consolefonts/`
- Move `generate_password()` from `general.py` to `disk/luks.py`
- Finish `count` script, move `schema.jsonc` into `archinstall/`
- Smaller mini config sample
- Docs: rewrite PHILOSOPHY.md

0.0.01-9

- Add console fonts config
- Add optional groups per user
- Move shell config to be per user
- Various UX changes for resumes/clears
- Various structure changes
- Add `count` script to list total pkgs installed by a given config
- Add custom profiles examples

0.0.01-8

- Start building `env` utils for support to build on alpine
- Rework `--script list` to show what needs root or not
- Add actual custom examples #71
- Add `count` script that let's you map out how many pkgs would be installed
  by a profile
- Add schema.jsonc with all the possible packages installed
- Add mirrors progress indicator and average bit rate
- Style changes use `SystemExit` instead of importing sys on every file
- Add `--clean` args to remove logs too
- Wrap parted imports in try blocks
- Update missing packages from plasma profile
- Add newb docs and refresh more docs
- Fix missing iwd case for resume
- Fix disks menu QOL for resume
- Add `mirror` script
- Add lazy fallback to unmount
- Change disks to always show debug

0.0.01-7

- Manual partitioning improvements
    - Add "Create empty layout (wipe all)" option for fresh starts
    - Add "Default layout" option in manual partitioning for brtfs
      subvolumes
    - Manual partitioning highlighted by default
    - Add udev_sync calls between disk operations for timing issues
    - Remove partprobe (redundant with disk.commit)
    - Add mount_fs to all formatting calls (attempt fixes #4164 vfat mount
      issues)
    - Always log mount disk operations
- Init/startup refactor
    - Rework init with cleaner OS imports and lazy args
    - Separate offline check and wire up rc functions properly
    - Move prepare hook, separate helper for ping
    - Bypass unnecessary init steps
- Config improvements
    - Add config-sample-mini.json (no disk/user data)
    - Add test shortcuts in DEV script (-tf, -tm)
    - Remove duplicate audio_config from test config
- Fixes
    - Fix shell not being set for user
    - Remove hardcoded sudo mention
    - Grub as default bootloader
- Code comments and structure cleanup

0.0.01-6

- Mirrors module rework
    - Move mirrors code to `lib/pm/` (package manager)
    - Use class-level cache for `MirrorListHandler` (fetch once, reuse)
    - Swap mirror fetch order: archlinux.org first, archlinux.de fallback
    - Fix "Loading mirror regions..." showing repeatedly
    - Fix "database already registered" error for custom repos
    - Skip duplicate repos when writing to pacman.conf
- X11 keyboard layout configuration
    - Auto-write `/etc/X11/xorg.conf.d/00-keyboard.conf` for Xorg profiles
    - Use `DisplayServer.X11` check instead of profile type checks
    - Verify layout inside chroot where X11 is installed
    - Strip vconsole suffixes (-latin1, -nodeadkeys, etc.) for X11 format
    - Remove Boot module that had complex systemd-spwn logic for setting
      this
- Remove unused `is_xorg_type_profile()` method
- Use `profile.display_servers()` for graphics driver installation check
- Network refactor with DI pattern
- Add accent colors to theme selector
- Add menu previews
- Various type hints and cleanup

0.0.01-5

- Dependency Injection refactor (from archinstoo issues #4149)
    - Remove module-level singletons: device_handler, profile_handler,
      mirror_list_handler, application_handler
    - Consumer classes accept optional handler params with fallback defaults
    - Keep logger and translation_handler as singletons
- Add `Os` class wrapper for environment variables
    - `Os.get_env()`, `Os.set_env()`, `Os.has_env()`
- Merge `resumehandler.py` into `ConfigurationHandler`
    - Add `ConfigurationHandler.prompt_resume()` method
    - Fix resume config loading bug (config was captured before resume
      check)
- Menu UX improvements
    - Move Disk config and Authentication to top (assumed encryption
      empty/same for auth)
    - Add `MenuItem.separator()` factory method for visual separators
    - Change "Cancel" to "Back" in sub-menus (ListManager)
    - Move Archinstoo settings to bottom with Install/Abort
- Update tests and docs for refactors
- New `logs/` dir for outputs (restore_perms on h2t mode)
- Move `tui/` into `lib/` for more readable imports / flat lib / honest
  architecture
    - A lot of UI code is called from lib interactions or similar
    - Having them as siblings when they are interlaced is misleading
- Separate `env` module in utils for these types of checks
- Rename `MirrorConfiguration` to `PacmanConfiguration`
    - Add pacman misc options (Color, ILoveCandy, VerbosePkgLists)
    - Rename `mirror_config` field to `pacman_config`
    - Custom repos browsable in Additional packages
    - Apply config on GlobalMenu init for loaded configs
    - Fix repos being added multiple times
- Rename `.sudo` to `.elev` across codebase
    - Add `User.any_elevated()` static helper
    - Consolidate duplicate elevated user checks
- Simplify `Password` class
    - Remove unused setter
- Log to current directory for debugging
- Light/dark mode theme selector with accent colors
- Add resizing and various UI fixes
- Separate scripts that need preparation phase
- Various test updates for renamed fields

0.0.01-4

- Remove `python-pydantic` dataclasses dependency
    - Use ClassVar abstractions reduce copy/paste
- Fixes #3717 enable cryptodisk in grub cfg + More UKI
- Fix #4148 pass script to main and handle non-root commands early
- Add stash module for git dotfiles
    - Allow multiple stashes per user (clones to `.stash/repo`)
    - Branch parsing with #branch format
- New settings submenu with color themes
- Nvidia driver better hints
- Fix config assertion error on reload
- Add validators for checkmark and config loading
- Fix input cursor not being shown and various UI
- Rework menu sections/orders
- Simplify bootloader entry naming
    - Remove init_time timestamp mechanism
- Add examples (custom commands, priv esc, deps)
- Docs refresh

0.0.01-3

- Change menu order for mirrors
- Work on ISOMOD scripts to add pre-caching
    - Profile system/cow space mods
    - Logging etc
- Work on init scripts debugging
    - Add is_venv check
- More formatting

0.0.01-2

- Fix vgs not closing properly
- Fix menu/ Add dynamic resize
- Chores use walrus op where available
- Work on DEV / BUILD utils

0.0.01-1

- Change structure to be more readable
    - Editor/PCH goes to top level
    - Rest of checks are specific to archinstall
    - Seperate into DEV / ONLINE PKGBUILDS
- Add dev/run helpers bash
- Remove UV from runners
- Change more PKGBUILD/workflows for new structure
- Work on refreshing docs

0.0.01-0

- Change PKGBUILD strucs for proper dependency mngmnt
    - Remove python-cryptography -> Only ever used to encrypt config file
      which now assumes passwords/disk passwords to never be resumed.
    - Remove python-textual -> Keep ncurses for portability
    - Add __init__ check of deps
- Simplify auth config to be inside global config
    - Never output passwords/FDE related
    - Load config but never creds (disk/auth config to do only)
    - Offer choice to delete all of contents post-install
    - Lock root and doas alternative
- Add resume/abort features with proper deletions of cfg
  archlinux/archinstall#4035
- Wayland/x11 major refactor
    - Nvidia fixes
    - Sway, River, Niri fixes
    - Explicit Nogreeter type
- Custom commands handler
    - Replaced plugins system
- Explicit --script utils
    - Rescue/Format/Minimal/...
- More bootloader/UKI fixes
- More applications menus, less hardcoded defaults
- Update user facing instructions

- Remove U2F in favor of configurable privilege escalation tools
- Remove all backwards compat related code
- Remove all dead code snippets