Driver rename and icons: `Driver.display_name` plus a `DriverIcon` table, both global/shared like the `Driver` row they hang off, so a rename or an icon is what every Owner sees. The rename/icon dialog keeps its forms as siblings (nested forms are invalid HTML) and the icon routes return an `hx-swap-oob` thumbnail refresh rather than re-rendering the table, which would tear the open `<dialog>` out of the DOM. Web image picker: `GET /web/images` renders a pickable grid for a printer or a driver icon, with the search term prefilled from the entity name and editable. Picking one downloads it server-side and normalizes it. Driver download search: `GET /web/drivers` searches for a vendor-wide driver (the term is rewritten into the vendor's real product name for 15 brands) or for the exact model as typed. Links only — nothing is downloaded, and the fragment says the results are unvetted. Icon uploads no longer reject off-size or non-PNG files: `normalize_icon()` letterboxes any decodable raster into a 256x256 PNG. An already-exact 256x256 PNG is returned byte-identical, because icon storage is content-addressed and re-encoding would move the file on every save. `fetch_image()` makes the request from the server, so `assert_fetchable()` refuses any URL resolving to a private, loopback, or link-local address, and re-runs on every redirect. ImpTune sits on the same LAN as the printers it configures; an unguarded fetcher would be a port scanner for anyone who can reach the UI. DuckDuckGo is scraped, not called through an API — no key needed, but fragile, so both search functions swallow parse failures and return [] instead of 500ing a page. `WEB_SEARCH=false` disables every outbound request and hides the controls, for air-gapped installs. Also: one shared `Jinja2Templates` in `templating.py` instead of five per-router instances, so a template global is declared once; `_add_missing_columns()` in `database.py` adds new nullable columns to a pre-existing table, which `create_tables(safe=True)` skips; `db_env` in test_db.py now closes its connection on teardown, or the next test's ORM writes land in the previous test's DB file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ImpTune
Build printer deploy packages (.intunewin for Intune, .zip for NinjaRMM) from Windows driver ZIPs via a web UI.
Run
Local development
docker compose up
docker-compose.override.yml is merged automatically: it mounts your working
copy into the container and runs uvicorn with --reload, so code edits are
picked up live. The override is gitignored (personal / per-machine).
Run the published image
To run the image from the registry instead of building locally, skip the override:
docker compose -f docker-compose.yml pull
docker compose -f docker-compose.yml up
Then open http://localhost:8000
Publishing
scripts/publish.ps1 builds the image and pushes it to the Gitea container
registry at git.azuze.fr/kawa/imptune.
# build + push :<short-git-sha> and :latest (prompts for a Gitea token)
./scripts/publish.ps1
# tag an explicit version
./scripts/publish.ps1 -Tag v1.2.0
Use a Gitea access token (Settings → Applications, with package read/write
scope) as the password. For non-interactive runs set GITEA_USER /
GITEA_TOKEN env vars. Run Get-Help ./scripts/publish.ps1 -Detailed for all
parameters (-Registry, -Owner, -Image, -NoBuild, -SkipLogin, …).
Environment variables
Set these under environment: in docker-compose.yml.
| Variable | Default | Purpose |
|---|---|---|
DATA_DIR |
/data |
Storage root for the SQLite DB, drivers and icons. Should map to the imptune_data volume. |
PORT |
8000 |
Port the server listens on inside the container. |
COOKIE_SECURE |
true |
Session mode: true (Secure cookie), false (plain-HTTP cookie, memory-only) or single_user (no cookie, one shared store) — see below. |
COOKIE_SECURE and HTTPS
Printers, print defaults and clients belong to a session identified by an opaque
key in a cookie (there are no accounts). That cookie is Secure by default, so
it only travels over HTTPS.
Behind a TLS-terminating proxy (nginx, Traefik, Caddy — the normal setup): leave the default. The session cookie lasts ten years, so a browser keeps its printers indefinitely.
Reached directly over plain HTTP (http://host:8000): set
COOKIE_SECURE=false, otherwise the browser refuses the cookie and every
request starts a brand-new empty session — no printer you save is ever visible
again.
In that mode the app is fully usable and keeps remembering everything, but the
cookie becomes memory-only: the session ends when the browser closes, and
every page shows a warning saying so. This is deliberate — over plain HTTP the
key is readable on the wire, so it is not written to disk for ten years. Use
Download my backup key to save the key to a file; /session/restore takes
it back on the next browser start, or on another machine.
services:
imptune:
environment:
- DATA_DIR=/data
- COOKIE_SECURE=false # only when serving plain HTTP
For local development the same applies:
export DATA_DIR=/tmp/imptune_data
export COOKIE_SECURE=false
uvicorn imptune.main:app --reload --port 8000
COOKIE_SECURE=single_user — no sessions at all
For a test box or a local deployment used by one person, sessions are pure
friction. COOKIE_SECURE=single_user (also accepted: single-user, single)
drops them:
- No cookie is read or set. Every request — every browser, every device, curl — resolves to one shared owner, so all printers, print defaults and clients are simply "the server's".
- The onboarding modal, the memory-only warning, the "This session" sidebar menu
and both
/session/*key routes disappear (the routes return404). There is no backup key to lose, and none to hand out. - Every page shows a banner stating that whoever reaches the app sees the same data.
There is no isolation left in this mode, so put it only where reaching the app is already the permission — localhost, or a network you trust. Switching an existing deployment over adopts the oldest existing owner, so printers saved under a cookie stay visible; switching back re-enables cookie scoping and hands new browsers a fresh empty session (that same data is then reachable only with its key, which single-user mode never printed — download a backup key before switching if you may switch back).
services:
imptune:
environment:
- DATA_DIR=/data
- COOKIE_SECURE=single_user