# 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`*
