Files
2026-04-15 17:57:12 +02:00

8.1 KiB

Milestone v1.0: ImpTune MVP

Status: SHIPPED 2026-04-13 Phases: 1-7 Total Plans: 13 Timeline: 2026-04-10 → 2026-04-13 (4 days)

Overview

Initial release of ImpTune — a self-hosted single-container webapp that lets IT technicians configure printer deployments and export ready-to-deploy packages for Microsoft Intune (.intunewin) or NinjaRMM (ZIP). Ships driver ZIP upload with INF parsing, full printer configuration with client/tenant grouping, PowerShell script generation (install/uninstall/detect) with UAC elevation and WOW64 guards, Python-native .intunewin assembly with embedded icon, and NinjaRMM ZIP export — all behind a no-auth HTMX/Alpine.js browser UI.

Phases

Phase 1: Foundation

Goal: A running Docker container with the app scaffold, data schema, and validated .intunewin generation capability Depends on: Nothing Requirements: INFRA-01, INFRA-02 Plans: 3 plans

Plans:

  • 01-01: Docker container scaffold (python:3.12-slim-bookworm, FastAPI, Jinja2, HTMX, Alpine.js, Pico CSS, offline static baking, healthcheck, sidebar nav shell)
  • 01-02: SQLite schema — Peewee WAL mode, full 4-table ORM (Client/Driver/Printer/Icon) created upfront, SHA256 content-addressed DriverStore, auto-init via lifespan
  • 01-03: Python-native .intunewin format spike — build_intunewin() with AES-256-CBC, HMAC-SHA256, detection.xml, 14 byte-level validation tests

Phase 2: Driver Management

Goal: Technicians upload driver packages and select driver names from parsed INF data — no free-text entry Depends on: Phase 1 Requirements: DRV-01, DRV-02, DRV-03, DRV-04, DRV-05 Plans: 2 plans

Plans:

  • 02-01: INF parser service — TDD, RawConfigParser(strict=False), BOM/UTF-16 detection, %TOKEN% resolution, multi-model support, unused-files detection
  • 02-02: Driver upload endpoint + drivers page — POST /drivers/upload with ZIP validation, SHA256 dedup, Peewee persistence, HTMX partial refresh, 8 integration tests

Phase 3: Printer Configuration

Goal: Technicians configure all printer parameters, assign printers to clients, and regenerate saved configs without re-uploading drivers Depends on: Phase 2 Requirements: PRNT-01 through PRNT-10 Plans: 2 plans

Plans:

  • 03-01: Printer + Client CRUD — form with all fields, Alpine.js IP→port auto-derivation (preserves manual edits), grouped list with LEFT OUTER JOIN, HTMX outerHTML swap, integration tests covering PRNT-01..09
  • 03-02: Printer detail page — full-page template with all config fields, driver association, regenerate placeholder, clickable links in printer list

Phase 4: Script Generation

Goal: System produces correct, production-ready PowerShell scripts handling all Intune and RMM execution contexts Depends on: Phase 3 Requirements: SCRPT-01, SCRPT-02, SCRPT-03, SCRPT-04, SCRPT-05 Plans: 2 plans

Plans:

  • 04-01: render_install() with Jinja2 template — WOW64 64-bit relaunch guard, UAC self-elevation, pnputil two-step staging, duplex mapping, idempotency, plain-string args for DB-free unit testability
  • 04-02: render_uninstall() + render_detect() templates, 3 script download endpoints (/install, /uninstall, /detect), _get_printer_and_driver() shared helper, PlainTextResponse with Content-Disposition

Phase 5: Package Export

Goal: Technicians download a complete, ready-to-deploy package for either Intune or NinjaRMM in one click Depends on: Phase 4 Requirements: PKG-01, PKG-02, PKG-03, PKG-04, PKG-05 Plans: 2 plans

Plans:

  • 05-01: /printers/{id}/packages/ninja + /packages/intunewin endpoints — in-memory ZIP assembly with BytesIO, TemporaryDirectory staging for intunewin, driver ZIP existence validation
  • 05-02: Icon upload with Pillow validation (PNG 256x256 ≤750KB), SHA256-addressed icon storage, printer detail page with Intune Commands section (copy buttons), Export section, Icon Upload form

Phase 6: Wire Icon into .intunewin Export (gap closure)

Goal: Uploaded PNG icon is embedded in the .intunewin package so Intune displays it as the app icon Depends on: Phase 5 Requirements: PKG-04 (closes gap from first audit) Plans: 1 plan

Plans:

  • 06-01: Wire Icon.get_or_none() lookup into packages.py, shutil.copy2() icon to tmpdir as icon.png, silent-skip on missing record/file, integration test verifying icon presence in exported package

Details: Added as gap-closure phase after first milestone audit flagged PKG-04 as unsatisfied — icon was uploaded and stored but never embedded in the .intunewin output.

Phase 7: Dashboard & Navigation Polish (gap closure)

Goal: Navigation links work correctly and dashboard shows real data instead of empty placeholders Depends on: Phase 3 Requirements: None (UX/integration fixes) Plans: 1 plan

Plans:

  • 07-01: Add GET /packages route (LEFT OUTER join on Client + Driver, switch(Printer)), wire dashboard recent_printers / recent_packages to live DB queries, new packages.html template, clickable nav links

Details: Added as gap-closure phase after first milestone audit flagged base.html → /packages 404 (route missing) and hardcoded [] in dashboard queries.


Milestone Summary

Key Decisions:

  • Python-native .intunewin — IntuneWinAppUtil.exe is a Windows PE binary, cannot run in Linux container. Reimplemented the AES-256-CBC / HMAC-SHA256 format in Python with pycryptodome, validated byte-level against the C# reference. ✓ Good
  • Stack: Python 3.12 + FastAPI + Jinja2 + HTMX + Alpine.js + SQLite + Peewee + pycryptodome + Pillow. ✓ Good — minimal runtime, single container, no Node.js
  • Full 4-table schema upfront (Phase 1) — Client/Driver/Printer/Icon all created in 01-02 so later phases add routes only, no schema migrations. ✓ Good
  • Sync FastAPI routes — runs in thread pool, Peewee-compatible without async ORM complexity. ✓ Good
  • Content-addressed storage — SHA256 for drivers and icons, deduplication for free, consistent pattern. ✓ Good
  • Plain-string args for script generators (not ORM objects) — keeps unit tests DB-free. ✓ Good
  • Test isolation — TestClient used as context manager for Starlette 0.46+ lifespan; thread-local Peewee connections closed in conftest teardown; list(Model.select()) wrapper avoids cursor caching across DB re-inits. ✓ Good
  • Silent-skip on missing icon — export always succeeds regardless of icon presence, optional feature. ✓ Good
  • Gap-closure phases 6 & 7 — added post-audit rather than shipping with known defects; cleaner than carrying PKG-04 and /packages 404 as tech debt into v1.1. ✓ Good

Issues Resolved:

  • Peewee datetime.utcnow() deprecation warning (root cause was project-level _utcnow() usage, not library)
  • DriverStore path mismatch bug — .zip suffix inconsistency between save and lookup broke upload→export flow in production (masked by pre-staged test fixtures); centralized in DriverStore.get_path() and added tests/test_upload_export_roundtrip.py regression test
  • Uninstall copy button mislabel ("Uninstall copy" → "Copy")
  • Stale requirements-completed frontmatter in 5 SUMMARY.md files (back-filled)
  • PKG-04 icon→.intunewin wiring break (Phase 6)
  • /packages 404 + dashboard hardcoded [] (Phase 7)

Issues Deferred to v1.1 (Tech Debt):

  • Printer form driver dropdown requires manual page reload after uploading a new driver on /drivers (Phase 2)
  • PRNT-03 Alpine.js port auto-derivation — code correct, needs live browser verification (Phase 3)
  • No UI links to individual script downloads — only accessible via package export or direct URL (Phase 5)

Nyquist Validation: All 7 phases have draft VALIDATION.md files but none are Nyquist-compliant. Wave 0 not complete. Not a milestone blocker — separate validation track for v1.1.

Known Runtime Validations Pending:

  • .intunewin byte-level format must be validated against a real Intune tenant
  • pnputil + $PSScriptRoot path resolution under SYSTEM context on a real Intune-managed device

For current project status, see .planning/ROADMAP.md