sysdeck (0.4.6-1) unstable; urgency=medium * QCrows format-aware Kata bridge: qcrows-list parses metadata.toml + menu.toml in memory; new qcrows-inspect / qcrows-verify subcommands mirror cockpit-kata's master verification (kernel magic, config, sha256 hash walk) without extracting to disk. Kata panel enriched with image metadata + Verify/Inspect actions. * integrity.score() returns None on PermissionError (root-only /var/log/lynis.log no longer crashes unprivileged sessions). -- Jeremy Anderson Sun, 27 Sep 2026 01:13:57 +0000 sysdeck (0.4.5-1) unstable; urgency=medium * PRODUCTION-HARDENING RELEASE — full MoE QA pass (web designers, backend, JS/React/Next, CSS, UI/UX, algorithms, Linux systems, DevOps). * Build: the web-dev recipe is attached to its own target (a spliced recipe ran fester + the dev console from uninstall-branding); master tarball excludes dev/server logs and .env; dist is reproducible (sorted, pinned mtime/owner) and gated on a clean git tree when one exists; distcheck uses mktemp; recipes quote every rm path. * Bridge: prometheus push-log validates the module filename and escapes exposition labels (root-level path traversal + metric-line injection closed); db query admits one read-only statement (no batches, no CLI meta-commands; sqlite refuses honestly instead of querying nothing); glances snapshot family degrades to {available:false} with a timeout; package mutations survive the 600s timeout; vmdb2 builds carry the mkosi output-dir guard; theme variable values are allowlisted against CSS injection; auth/vault/ firmware/fleet/integrity helpers run with hard timeouts; kerberos status derives from the real TGT end time; unwhitelist matches exactly; modules3p renders usage errors as JSON. * Web console: theme switching goes through applySdTheme (light themes keep their palette, the mirror stays in sync); cockpit modules table renders valid rows; overview reports the registry version and a live tarball link; fester health probes are cached and single-flight; usePoll rejects stale responses; the session clock is hydration-safe; tsc + eslint run as build gates (both green). * Firewall templates: all six nftables rulesets validate structurally; table-scoped flush replaces `flush ruleset` on every template (docker/libvirt tables survive an apply); no-services gains loopback accept, valid jump syntax, braced set-adds, and the SSH-port fix that previously locked operators out; vps-webserver SSH rule carries an explicit verdict; the Cilium default policy scopes its HTTP method filter to 80/443 and resolves DNS in standalone mode. * Packaging: RPM %files matches the 27-plugin install and the spec builds noarch; debian gains the nodejs build dependency and stops recommending media/virtualization stacks; pacman hooks live in sysdeck.install; AppStream addon extends the cockpit component; Caddyfile's port gateway is a 3010 allowlist. * Comments state standing decisions in the present tense throughout the first-party tree. -- Jeremy Anderson Thu, 17 Sep 2026 10:00:00 -0400 sysdeck (0.4.4-1) unstable; urgency=medium * v0.4.4: ten package-manager backends on both editions + the blog essay. The cockpit packages bridge (bridge/packages.py) carries the same ten-manager step-down as the web console — pacman, emerge (corroborated by /var/db/pkg), lunar, sorcery, xbps (probed via xbps-query), apk, zypper, dnf, yum, apt — with a unified MUTATION_CMDS table, header-located zypper table parsing, an emerge update regex anchored after the class bracket (the old capture grabbed the bracket and dropped every row), and honest capability reporting for managers without an update preview (lunar). The web console gains the same parser fixes and the xbps-query detection probe. BLOG.md is now a single long-form technical essay (the shellm blog pattern), not release notes. -- Jeremy Anderson Sat, 12 Sep 2026 18:00:00 -0400 sysdeck (0.4.3-1) unstable; urgency=medium * v0.4.3: the MoE QA pass — production hardening across every axis. stdin-piped privileged writes with verification, comment injection guards, mktemp staging, admin-gated mutations (SYSDECK_MUTATIONS), honest dry-runs and unbans, XFF trust gating, TTL + single-flight polling caches, and cockpit-side bridge parity (sensors chain, dnf rc-100, subprocess timeouts). -- Jeremy Anderson Sat, 12 Sep 2026 06:30:00 -0400 sysdeck (0.4.2-1) unstable; urgency=medium * v0.4.2: the zero-demo release — production implementations only. The sensors bridge reads the real lm-sensors JSON (sensors -j) with an honest sysfs fallback; the netsec ban layer enforces for real (atomic nftables batch: table inet sysdeck + blacklist set with 30d timeouts, iptables DROP fallback) and merges the live fail2ban ban list; the firewall panel gains a live-ruleset tab (real nft -j list ruleset / iptables-save) and its template catalog now carries all seven shipped topologies; LUKS header backups are hashed from the actual image. The bridge envelope no longer admits a 'demo' source at the type level — every module reads real host state or fails honestly. -- Jeremy Anderson Sat, 12 Sep 2026 20:00:00 -0400 sysdeck (0.4.1-1) unstable; urgency=medium * v0.4.1: cockpit module detection for the web console — every installed cockpit module (distro modules like cockpit-machines and cockpit-podman, addons, anything with a manifest menu entry) is scanned from /usr/share/cockpit and loaded into the console navigation: a "Cockpit" sidebar group with per-module detail views (manifest, shipped files, backend version probes) and jumps to the native panels covering each domain. SYSDECK_COCKPIT_SCAN adds extra scan roots for staged trees. UI codenames retired — the console subtitle is now dcos.net (login banner, sidebar, status bar); page title is "SysDeck". -- Jeremy Anderson Sat, 12 Sep 2026 18:00:00 -0400 sysdeck (0.4.0-1) unstable; urgency=medium * v0.4.0: Unix-account login for the web edition, the Cockpit way. Username + password verified by the host PAM stack (web/scripts/pam-auth.py, stdlib ctypes client of libpam; service "sysdeck" when /etc/pam.d/sysdeck exists, else the stock "login" stack; credentials over stdin, never argv). Session cookies are v2 user-bound tokens (v1 still verifies — upgrades keep sessions). Cockpit-style shell identity: account menu with avatar, user@host, PAM/local provenance, wheel "Administrative access" badge, live session-expiry countdown; status bar carries user@host. Auth modes: pam (default, run as root), pam+local (SdUser scrypt fallback for unprivileged installs), local (console accounts, managed by scripts/manage-users.mjs). Login failures rate limited per-IP AND per-username; audited with the unix username as actor. Fester gates REST+WS on the same v2 token. Cockpit edition untouched — all modules keep working. -- Jeremy Anderson Sat, 12 Sep 2026 12:00:00 -0400 sysdeck (0.3.1-1) unstable; urgency=medium * v0.3.1: cockpit-style login for the web edition — shared password (SYSDECK_WEB_PASSWORD, default 'sysdeck' with an on-screen nag), HMAC-signed 12h session cookie, page-level server gate, all /api/* routes 401 until signed in, login/logout audited, per-IP login rate limit. The fester mini-service verifies the identical session token against the shared SQLite secret, so the direct WebSocket event stream (port-gateway path) is gated too. LAN-side posture unchanged: loopback binds stay the outer boundary. * Version surfaces bumped 0.3.0 -> 0.3.1. -- Jeremy Anderson Sun, 13 Sep 2026 12:00:00 -0400 sysdeck (0.3.0-1) unstable; urgency=medium * v0.3.0 AI GATEWAY EDITION: klanker-gate (the Frosty Deno LLM gateway, Deno 2 + TypeScript, own independent version 0.9.0) is vendored at /klanker-gate with complete Arch Linux packaging (arch/: PKGBUILD, hardened systemd unit, sysusers/tmpfiles, run wrapper, INSTALL-ARCH.md runbook). The Arch port needed ZERO upstream source changes — the codebase is Linux-first, not Windows-first, as commonly believed. * NEW klanker module (AI Gateway) in both editions: bridge/klanker.py (9 subcommands: status/providers/models/vkeys/logs/analytics/runtime/ service/journal — stdlib REST client against the gateway on KLANKER_URL, Bearer KLANKER_ADMIN_TOKEN, graceful offline JSON) and the fully-built plugins/sysdeck-klanker panel; web edition gets the hybrid AI Gateway panel (live REST when the gateway runs, badged demo data otherwise, KLANKER_URL env). * bridge.js klanker surface: 10 methods; check-bridge-subcommands now verifies 215 calls across 28 bridge modules (was 206/27); 28 plugin manifests (was 27). * Version surfaces bumped 0.2.0 -> 0.3.0 (Makefile, bridge/__init__.py, setup.py, PKGBUILD, spec, debian/changelog, compat-manifest, metainfo, version-sync test). -- Jeremy Anderson Fri, 11 Sep 2026 12:00:00 -0400 sysdeck (0.2.0-1) unstable; urgency=medium * v0.2.0 MASTER EDITION: one tarball bundling the cockpit edition, the new SysDeck Web Edition (web/, Next.js console, 28 bridge modules), and Fester pre-integrated (web/mini-services/fester, vendored at its own independent version 0.2.1). * bridge/fester.py rewritten from the v0.0.31 systemd-listing stub to a real REST client (11 subcommands, FESTER_URL override, graceful offline JSON); plugins/sysdeck-fester is a full panel; the shared bridge.js fester surface grew from 1 method to 11. * Makefile: recipes are tab-indented (GNU make rejects 8-space indents with "missing separator"; a build-time guard enforces it); new targets fester-start, web-install, web-dev, master. -- Jeremy Anderson Thu, 20 Aug 2026 12:00:00 -0400 sysdeck (0.1.3-1) unstable; urgency=critical * v0.1.3 CRITICAL FIX: host package import was silently failing + mkosi still wasn't reading the profile config. Two root causes fixed, plus new download/manage UI for builds. * IMPORT BUG (root cause): _detect_host_packages() relied on `from __init__ import PKG_MANAGER` which silently failed in the cockpit superuser channel context (different Python path). When the import failed, PKG_MANAGER defaulted to "unknown" and the host query returned an EMPTY list. The operator saw "tries to build only 2" because the import wrote nothing and the build used the profile's original template packages. FIX: _detect_host_packages() now uses shutil.which() to find pacman/apt-mark/dnf directly — no import dependency, works in any execution context. * BUILD BUG (root cause): v0.1.2's --include flag does NOT work as a config loader. mkosi's --include includes a drop-in fragment ON TOP OF the base mkosi.conf — it does NOT replace the base config. If there's no mkosi.conf in the cwd, mkosi uses defaults and ignores the --include file entirely. This is why v0.1.2 still produced builds with only 2 packages (mkosi's hardcoded base). FIX: build() now creates a temp directory, symlinks the profile file into it as `mkosi.conf`, and sets work_dir to that temp dir. mkosi finds `mkosi.conf` (the symlink), follows it, reads the actual profile. Works for ANY profile path regardless of filename or location. Temp dir is cleaned up after the build finishes. New helper: _prepare_mkosi_work_dir(). * NEW FEATURE: artifact download + management UI. Each artifact in the Artifacts panel now has: - ⬇ Download button — reads the file via cockpit.spawn(["cat", path]) with superuser, creates a Blob, triggers browser download. - 🗑 delete button — removes a single artifact file via the new artifact-delete subcommand. - 🗑 Clear all button — removes ALL artifacts for a profile via the new artifacts-clear subcommand. Shows file count + bytes freed. Each profile's artifacts card now shows total size in the header. * NEW FEATURE: build management. Each build in the Builds table now has a 🗑 delete button. Two-step confirm: 1. "Delete build record?" — OK = delete state + log only. 2. If Cancel: "Also delete ALL artifacts for profile?" — OK = delete state + log + artifacts dir. New subcommand: build-delete [--artifacts]. Reads the state file FIRST (to get the profile name for artifact cleanup) before deleting it. * REGRESSION TESTS: 11 new unit tests across two new test classes: - TestBuilderArtifactManagement (7 tests): artifact-delete file removal + path traversal refusal, artifacts-clear, build-delete with/without --artifacts, nonexistent build-id, COMMANDS registration. - TestBuilderMkosiTempWorkDir (3 tests): _prepare_mkosi_work_dir creates temp dir with mkosi.conf symlink, returns None for missing path / nonexistent file. Existing test_detect_host_packages_pacman rewritten to mock shutil.which instead of __import__. test_build_success_path updated to no longer expect --include on the command line. Total: 254 tests (was 243 in v0.1.2; +11). * VERSION SYNC: bumped 0.1.2 -> 0.1.3 across all 9 release surfaces. -- Jeremy Anderson Tue, 19 Aug 2026 03:00:00 -0400 sysdeck (0.1.2-1) unstable; urgency=critical * v0.1.2 CRITICAL FIX: mkosi was not reading the profile config at all — packages were silently ignored. An operator reported: "the builder absolutely does not work yet. it has zero awareness of packages we tell it to add." * ROOT CAUSE 1 (config not loaded): _backend_build_command() for mkosi was ["mkosi", "build", "--output", ..., "--output-dir", ...] with NO flag telling mkosi WHERE the profile config file is. mkosi only reads a file literally named `mkosi.conf` from the cwd. For v0.0.x profiles at /etc/mkosi/mkosi.conf.d/.conf, mkosi ran in that dir, found no `mkosi.conf` (the file is named .conf), and used EMPTY defaults — zero packages, default distro, default everything. The operator's Packages= setting was never seen by mkosi. * FIX 1: _backend_build_command() now ALWAYS passes --include on the CLI. This tells mkosi to explicitly load the profile config by path, regardless of its filename or location. CLI --include overrides the default mkosi.conf discovery. * ROOT CAUSE 2 (legacy Packages= syntax): even when mkosi DID read the profile file (e.g. v0.1.0+ profiles with correct location), profiles created by v0.0.x used the old indented Packages= syntax: Packages= linux linux-firmware mkosi v22+ (Arch ships 25.x) only understands single-line: Packages=linux linux-firmware The old form is silently parsed as a single package name with embedded newlines ("linux\nlinux-firmware\n..."), which doesn't exist in any repo — so mkosi installs NOTHING. * FIX 2: new _migrate_legacy_mkosi_packages() function detects the old indented syntax and rewrites it to single-line IN-PLACE before the build command is constructed. build() calls this automatically on every mkosi build. The migration is logged in both the build state JSON (warnings array) and the log file header (# MIGRATED: ...). If the file already uses modern syntax, the migration is a no-op. * REGRESSION TESTS: 4 new unit tests in TestBuilderBuildPath: - test_migrate_rewrites_old_indented_syntax: verifies old Packages=\n linux\n vim\n is rewritten to Packages=linux vim. - test_migrate_noop_on_modern_syntax: verifies already-modern files are left unchanged. - test_migrate_noop_on_no_packages_section: verifies files without [Packages] are left unchanged. - test_migrate_runs_during_build: end-to-end — build() with a profile containing old syntax auto-migrates before mkosi runs, and the migration is recorded in state + log. Existing test_build_success_path extended to verify --include is on the command line and points at the profile file. * VERSION SYNC: bumped 0.1.1 -> 0.1.2 across all 9 release surfaces. Total unit tests now 243 (was 239 in v0.1.1; +4). -- Jeremy Anderson Tue, 19 Aug 2026 02:00:00 -0400 sysdeck (0.1.1-1) unstable; urgency=high * v0.1.1 OUTPUT PATH SAFETY FIX. An operator reported: "this is NOT a safe output path. fix this now." The v0.1.0 release relied on OutputDirectory= in the scaffolded mkosi.conf to route build outputs to /var/lib/sysdeck/builder/artifacts//. But when the operator built an OLD v0.0.x profile (whose mkosi.conf had no OutputDirectory= setting), mkosi defaulted to writing image.raw into the cwd — which was /etc/mkosi/mkosi.conf.d/, a system config directory owned by root. mkosi then refused to overwrite the existing image.raw, blocking every rebuild. * ROOT CAUSE: _backend_build_command() for mkosi was just `["mkosi", "build"]` with no CLI output flags. It trusted the profile's mkosi.conf to set OutputDirectory=, which: - Doesn't exist on v0.0.x profiles (silent default to cwd) - Can be hand-edited to anything (no validation) - Is ignored by mkosi if the profile is a drop-in fragment that mkosi never reads (the v0.0.x bug 1 from v0.1.0) * FIX: _backend_build_command() now ALWAYS passes --output, --output-dir, and --force on the CLI for mkosi builds. CLI flags override mkosi.conf, so the output path is forced to /var/lib/sysdeck/builder/artifacts//.raw regardless of what the profile says. --force overwrites any existing image so rebuilds don't fail with "Output path exists already." * SAFETY CHECK: build() now refuses to proceed if the resolved output_dir is not under /var/lib/, /tmp/, /var/tmp/, or the configured BUILDER_ARTIFACTS_DIR. This blocks /etc/, /usr/, /boot/, /bin/, /sbin/, /lib/, /root/, /home/, etc. — anywhere a stray image.raw would corrupt the system or pollute a user's home. Belt-and-suspenders: even if an operator passes options.output_dir=/etc/something via the JS bridge, the build is refused before subprocess.run is called. * LEGACY PROFILE WARNING: build() now detects profiles in /etc/mkosi/mkosi.conf.d/ (the v0.0.x drop-in layout) and records a warning in both the build state JSON and the log file: "WARNING: profile is in /etc/mkosi/mkosi.conf.d/ (legacy v0.0.x layout). mkosi may silently ignore this drop-in fragment. Migrate to /etc/mkosi/profiles// mkosi.conf for a real profile." * LOG IMPROVEMENT: build log header now includes the resolved output_dir so the operator can see exactly where the image will land before mkosi starts. Format: $ mkosi build --output myarch.raw --output-dir /var/lib/... # work_dir: /etc/mkosi/profiles/myarch # backend: mkosi # profile: myarch # output_dir: /var/lib/sysdeck/builder/artifacts/myarch * REGRESSION TESTS: 2 new unit tests in TestBuilderBuildPath: - test_build_refuses_output_dir_under_etc: verifies the safety check rejects output_dir=/etc/mkosi/evil. - test_build_legacy_v050_profile_records_warning: verifies building a profile in /etc/mkosi/mkosi.conf.d/ records the legacy warning in state + log. The existing test_build_success_path was extended to verify the mkosi command line includes --output, --output-dir, and --force, and that --output-dir points at the per-profile artifacts dir. * VERSION SYNC: bumped 0.1.0 -> 0.1.1 across all 9 release surfaces. Total unit tests now 239 (was 237 in v0.1.0; +2). -- Jeremy Anderson Tue, 19 Aug 2026 01:00:00 -0400 sysdeck (0.1.0-1) unstable; urgency=medium * v0.1.0 BUILDER PROFILE FIXUP + HOST PKG IMPORT. Three compounding bugs in the v0.0.x mkosi build path were silently producing empty 33M images with no kernel/systemd/openssh, plus a new operator feature requested in the same release cycle. * BUG 1 (scaffold location): profile-create wrote /etc/mkosi/mkosi.conf.d/.conf — a drop-in fragment that mkosi only honors when a parent /etc/mkosi/mkosi.conf exists to layer it onto. With no parent, mkosi ran with empty defaults. Fix: each profile now lives in its own directory /etc/mkosi/profiles//mkosi.conf (the only filename mkosi reads automatically from the cwd). MKOSI_DIRS updated to scan /etc/mkosi/profiles first. * BUG 2 (Packages= syntax): _MKOSI_TEMPLATE and _write_packages_mkosi used the indented-continuation form (Packages=\n linux\n ...) which was the old systemd-mkosi (<=v15) syntax. mkosi v22+ (Arch ships 25.x) expects single-line space-separated: Packages=linux linux-firmware systemd openssh. The v0.0.x form was silently parsed as a single package named "linux\n..." and failed to install. Fix: template + writer now emit the modern single-line form. The reader accepts both forms so v0.0.x profiles migrate cleanly on first append/replace. * BUG 3 (output routing): mkosi wrote its output to the cwd (/etc/mkosi/mkosi.conf.d/image.raw) but build() only scanned /var/lib/sysdeck/builder/artifacts// for artifacts — so every successful build looked like a failure in the panel. Fix: _MKOSI_TEMPLATE now sets OutputDirectory= to the per-profile artifacts dir so mkosi writes directly there. * NEW FEATURE: profile-import-packages subcommand. Queries the host's explicitly-installed package set (pacman -Qqe on Arch, apt-mark showmanual on Debian, dnf repoquery --userinstalled on Fedora) and writes it into a profile's package list via the existing _write_packages dispatch. Defaults to append mode so the profile's baseline (kernel, systemd, openssh) is preserved. Supports --mode=replace, --dry-run for preview, and --packages= for manual override (useful for importing a list captured on another host). New polkit exec paths for pacman/apt-mark/dnf added to org.sysdeck.builder.modify. * PANEL UX: each profile row in the Builder panel now has a "Import host pkgs" button. Click -> dry-run preview -> window.confirm with package count, source distro, and first 200 packages -> append write. Falls back to operator cancel without writing. * REGRESSION TESTS: 7 new unit tests in TestBuilderImportHostPackages cover _detect_host_packages dispatch (pacman path + dedup), the --packages override end-to-end, --dry-run no-write behavior, and the unknown-profile / no-args / bad-mode / COMMANDS-registration error paths. 4 existing tests in TestBuilderPackagesField updated for the new single-line Packages= syntax; 1 new test (test_mkosi_modern_single_line_input_parsed) guards against a regression where the writer emits the new form but the reader only understands the old one. * VERSION SYNC: bumped 0.0.50 -> 0.1.0 across all 9 release surfaces (Makefile, bridge/__init__.py, packaging/setup.py, packaging/PKGBUILD, packaging/sysdeck.spec, packaging/debian/changelog, compat/compat-manifest.json, packaging/sysdeck.metainfo.xml, README.md). Version-sync test renamed to test_version_sync_all_surfaces_report_010. * All build-time guards pass. Total unit tests now 237 (was 228 in v0.0.50; +8 TestBuilderImportHostPackages + 1 new test_mkosi_modern_single_line_input_parsed). -- Jeremy Anderson Tue, 19 Aug 2026 00:00:00 -0400 sysdeck (0.0.50-1) unstable; urgency=medium * v0.0.50 BUILD PATH NameError FIX. An operator reported: "NameError: name 're' is not defined. Did you forget to import 're'? happens right away on build for a new profile i created." The traceback pointed at _new_build_id() line 492: safe_profile = re.sub(r"[^A-Za-z0-9_-]", "_", profile). * ROOT CAUSE: bridge/builder.py's module-level imports were `import json / os / shutil / subprocess / sys` + `from pathlib import Path` + `from typing import Any`. No `import re`. _new_build_id has used re.sub since v0.0.31 (when the full- featured build operations were added), but no test ever exercised the build() code path — the unit tests only covered profile_create / profile_copy / profile_delete and the v0.0.49 package-writing helpers. The bug went undetected for 18 releases (v0.0.31 through v0.0.49) until an operator actually clicked Build on a freshly-created profile. * FIX: added `import re` to the module-level imports in bridge/builder.py. Removed the now-redundant local `import re` inside _write_packages_vmdb2 (it was a v0.0.49 workaround that's no longer needed — the module-level import covers both callers). * REGRESSION TESTS: 9 new unit tests in tests/test_bridge_parsers.py TestBuilderBuildPath cover: - _new_build_id format: -<14-digit-timestamp>. - _new_build_id sanitizes unsafe chars: dots → underscores (operators commonly name profiles myarch.v2). - _new_build_id preserves safe chars: hyphens + underscores kept. - test_new_build_id_re_imported_at_module_level: explicit assertion that `re` is in the builder module's globals. If anyone ever removes the `import re` line in a future refactor, this test will catch it — the v0.0.31-v0.0.49 bug can't recur. - build() end-to-end with mocked subprocess.run: verifies the build state file + log file are written under BUILDER_STATE_DIR / BUILDER_LOGS_DIR, the response shape is correct (build_id / state / rc / success / duration_s / artifacts / log_path), and subprocess.run was actually called with the right argv. - build() with unknown profile returns a clear "not found" error. - build() with no args returns a usage error (not a crash). - build() with backend-not-installed returns a clear error with an install hint. - build() with non-zero subprocess returncode records state "failed" (not "succeeded") and rc != 0. All tests mock subprocess.run and the module-level BUILDER_STATE_DIR / BUILDER_LOGS_DIR / BUILDER_ARTIFACTS_DIR so they run hermetically — no real /var/lib/ writes, no real backend invocation. * AUDIT: ran an AST-based audit of bridge/builder.py to find any other names used at module level but not imported. The audit walks every function body, collects Name loads, and checks each against (module-level names + function locals + builtins). No real undefined names found — every flagged item was a comprehension local (b, v, s, p), tuple-unpacking target (cid, chint, k, v, backend_id, binary, vargs, kind), except-clause target (exc), or __file__ (provided by Python in every module). The build path is now fully exercisable by tests. * VERSION SYNC: bumped 0.0.49 → 0.0.50 across all 9 release surfaces (Makefile VERSION + header comment, bridge/__init__.py __version__, packaging/setup.py VERSION, PKGBUILD pkgver, RPM spec Version + %changelog entry, debian/changelog entry, compat/compat-manifest.json version + _comment, packaging/ sysdeck.metainfo.xml , README.md Version line). * GUARDS: all build-time guards pass — manifest consistency (26 manifests), metainfo consistency, Makefile recipe indentation, no broken imports, bridge.js subcommand cross-check (102 calls verified — unchanged from v0.0.49 since this is a Python-only fix with no JS changes), version sync, all unit tests pass (228 total: 9 new TestBuilderBuildPath + 28 TestBuilderPackagesField + 15 TestBuilderProfileCopy + existing TestFirewallV047* / TestMetainfoV047* / TestServicesPluginV047* / etc.). -- Jeremy Anderson Tue, 19 Aug 2026 18:00:00 +0000 sysdeck (0.0.49-1) unstable; urgency=medium * v0.0.49 BUILDER INLINE PACKAGE LIST. Per user directive: "we should allow adding a pacman -Sy applist.txt with a literal list of baseline apps for the profile being generated." Both the Create Profile and Copy shipped profile forms now include a Baseline packages textarea, a file upload input (applist.txt), and a merge- mode toggle (append | replace). The package list is written to the backend-specific package file in the same operation as the scaffold/copy — closing the loop on the profile-creation flow (previously the operator had to drop to a shell to edit the package list after creating/copying a profile). * BACKEND COVERAGE: all 4 backends supported. Each writes to its native package-list location: - mkosi → [Packages] section of .conf (INI continuation) - vmdb2 → bootstrap.include list in .yaml (YAML list) - archiso → packages.x86_64 in the profile dir (one per line) - live-build → config/package-lists/sysdeck.list (one per line) * INPUT: textarea for inline paste (one package per line, # comments allowed) AND file upload (applist.txt / .list / .conf accepted). File upload populates the textarea via the browser's FileReader API so the operator can review/edit the uploaded content before submitting — the textarea is always the source of truth. 1 MB cap on uploaded files (anything larger is probably not a package list). * MERGE MODE: operator chooses per-operation via a dropdown toggle: - append (default for Copy): preserves the baseline's existing packages (e.g. 'linux'/'base' for archiso, 'linux-image-amd64' for vmdb2), adds the operator's packages, deduplicates while preserving first-occurrence order. - replace (default for Create): overwrites the baseline's package file with the operator's list. The operator must include 'linux'/'base' themselves if they want them. * NEW BRIDGE HELPERS in bridge/builder.py: - _extract_opts(args): splits argv into (positional, opts) so profile-create/profile-copy can accept --packages= and --mode=append|replace without breaking their existing positional [base] / [backend] signatures. - _parse_packages_text(text): parses multiline text into a deduped list of package names. Strips full-line comments (# at start), inline comments (# after package name), blank lines, and surrounding whitespace. Preserves first-occurrence order. - _write_packages_mkosi(conf_path, packages, mode): reads the existing mkosi.conf, parses the [Packages] section, dedups on append, rebuilds the section with the merged/replaced list. Other sections ([Distribution], [Output], etc.) are preserved. - _write_packages_vmdb2(yaml_path, packages, mode): regex-based surgery on the bootstrap.include list in the YAML. pyyaml is NOT a hard dependency (vmdb2 isn't typically installed on Arch, and we shouldn't pull in a YAML parser just to update a list). Other YAML sections (partitions, commands) are preserved. - _write_packages_archiso(profile_dir, packages, mode): reads packages.x86_64, preserves the comment header on append, dedups, rewrites. In replace mode, writes a fresh file with a header comment + the operator's packages. - _write_packages_live_build(profile_dir, packages, mode): writes config/package-lists/sysdeck.list. live-build merges all .list files at build time, so each list file is an independent package set. In replace mode, removes old sysdeck*.list files (does NOT touch baseline .list files like baseline.list). In append mode, just writes/overwrites sysdeck.list (the file is the unit). - _write_packages(profile_path, backend, packages_text, mode): dispatcher that validates mode, parses packages_text, and routes to the right per-backend writer. * EXTENDED profile_create() and profile_copy() to accept --packages= and --mode=append|replace. Default mode for create is "replace" (the scaffold's minimal defaults are replaced by the operator's list); for copy it's "append" (the baseline's packages are preserved). Both return a new "packages" field in their success response: {count, mode, path}. If package-writing fails, the profile is still created/copied and a "packages_error" field is included (non-fatal — the operator can fix the package file by hand). * UPDATED shared/bridge.js: profileCreate(name, backend, base, packagesText, mode) and profileCopy(srcName, newName, backend, packagesText, mode). packagesText is JSON-encoded via JSON.stringify() so newlines, quotes, and unicode survive the argv boundary cleanly. When packagesText is omitted/null, the bridge writes no package file (back-compat with v0.0.48 callers). * UPDATED plugins/sysdeck-builder/builder.js: - New renderPackagesField(prefix, defaultMode) helper shared by both forms. Emits a