# 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: - [x] 01-01: Docker container scaffold (python:3.12-slim-bookworm, FastAPI, Jinja2, HTMX, Alpine.js, Pico CSS, offline static baking, healthcheck, sidebar nav shell) - [x] 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 - [x] 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: - [x] 02-01: INF parser service — TDD, RawConfigParser(strict=False), BOM/UTF-16 detection, %TOKEN% resolution, multi-model support, unused-files detection - [x] 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: - [x] 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 - [x] 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: - [x] 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 - [x] 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: - [x] 05-01: `/printers/{id}/packages/ninja` + `/packages/intunewin` endpoints — in-memory ZIP assembly with BytesIO, TemporaryDirectory staging for intunewin, driver ZIP existence validation - [x] 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: - [x] 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: - [x] 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`*