kawaandClaude Opus 5 2c06806814 feat: driver rename, driver icons, web image + driver search
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>
2026-08-05 10:44:47 +02:00
2026-04-15 17:57:12 +02:00
2026-04-15 17:57:12 +02:00

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.

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

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 return 404). 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
S
Description
No description provided
Readme
34 MiB
2026-08-06 11:14:20 +02:00
Languages
Python 64.4%
HTML 23.6%
CSS 9.8%
PowerShell 1%
Jinja 0.9%
Other 0.3%