Commit Graph

13 Commits

Author SHA1 Message Date
julian d3288e29eb fix(release): don't let a grep-not-found kill the script under set -e
CI / check (push) Successful in 42s
Release desktop / bundle (push) Successful in 4m26s
Every REL_ID lookup piped grep -o '"id":...' straight into head/cut with no
guard. Under set -e + pipefail, a Gitea API response with no id (e.g.
"tag already exists" on a retry, or an empty existing-assets list on the
first desktop-latest publish) makes grep exit 1, which aborts the whole
step immediately — before the intended fallback lookup ever runs. Hit on
retrying v0.1.0 after the previous filename fix: the release already
existed from the earlier failed run, and the script died with no output at
all instead of finding it by tag. Guarded every such pipeline with || true.
2026-09-03 10:33:03 +02:00
julian baf7a4a99d fix(release): strip spaces from bundle filenames before upload
CI / check (push) Successful in 44s
Release desktop / bundle (push) Failing after 3m57s
productName "Parking System" produces installer filenames with a literal
space (e.g. "Parking System_0.1.0_amd64.deb"). curl rejected the resulting
asset-upload URL outright on the first real v0.1.0 release ("Malformed
input to a URL function"), before the job ever reached the new
public_releases mirror step. Sanitized on copy into dist/.
2026-09-03 10:26:17 +02:00
julian a1f3103a76 fix(desktop): mirror signed releases to public repo for the updater
Build desktop / desktop (push) Successful in 4m46s
CI / check (push) Successful in 43s
The updater endpoint pointed at mca/parking_solution's own Gitea "latest
release" redirect, but that repo is private and field appliances have no
Gitea credentials — every update check was silently failing. release.yml
now mirrors signed installers to mca/public_releases (public, installers
only) under a fixed desktop-latest tag; tauri.conf.json points there.
Rejected embedding a read token in the app instead, given the booth-operator
threat model.

Also: make the appliance-provisioning root_directory gotcha impossible to
skim past (boxed callout + explicit next-step pointers), after it caused a
second missed step on the park-2 install.
2026-09-03 09:56:49 +02:00
julian 642c5f4f70 feat(setup): show running build version in the Setup tab bar
Build desktop / desktop (push) Successful in 4m21s
Build & push images / images (push) Successful in 3m6s
CI / check (push) Successful in 42s
CI already computes <branch>-<short-sha> for image tags but never
surfaced it anywhere reachable from the app, so there was no way to
tell what's actually deployed on a booth without cross-referencing
komodo/resources.toml's TAG by hand.

Thread it through: CI passes BUILD_VERSION as a Docker build-arg,
the Dockerfile captures it as a runtime env var, GET /api/version
(gated by the existing site:read permission) exposes it, and the
Setup page's tab bar shows it right-aligned, muted, absent entirely
on a local/dev build with no CI-supplied value.

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
2026-08-30 19:01:15 +02:00
julian 39c778fbac ci(build-images): trigger on komodo/** too
Build & push images / images (push) Successful in 2m50s
A push only builds if it touches a path in the filter. The first stage commit was
komodo-only, so no :stage image was ever built. Add komodo/** so IaC/Stack changes
(and a komodo-only push to stage) also build+check — a deploy-config change gets the
same sanity pass before it reaches a booth. This commit itself touches the workflow
file (already filtered), so it triggers the build that produces the first :stage image.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-29 15:19:20 +02:00
julian 381046190b feat(deploy): add stage tier — park-buzi as the staging booth
Model the staging-vs-production split that fleet-deployment-komodo flagged as open.
Three tiers: dev (working, no booth) -> stage (staging booth park-buzi, real-world
test) -> main (production, manual + pinned).

- build-images.yml: trigger on [dev, stage, main]. The tag computation is already
  branch-derived, so :stage / :stage-<sha> build with no other change.
- komodo/resources.toml: park-buzi now branch=stage + TAG=stage-<sha> (pinned;
  no webhook even on staging). BACKUP_KEY already wired as a per-booth secret.
- komodo/README.md: a Promotion (dev->stage->main) section; per-booth secret list
  now includes backup_key; hard-rule #1 generalised to pinned <branch>-<sha>.
- wiki: fleet-deployment-komodo open-item resolved + a Promotion-tiers table;
  deploy-trigger choice generalised; container-deployment tag list gains :stage.

Promotion is a merge: when dev is ready, merge dev->stage, CI builds the image,
bump TAG=stage-<sha> in resources.toml, deploy from Core. stage is branched from
dev HEAD so the first real-world test carries the full current app. Per-booth
secrets must pre-exist in Core; migrations run at boot so a promotion auto-migrates
the staging ledger (where a bad migration is caught before production).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-29 13:11:35 +02:00
julian 215a3ac405 fix(ci): publish desktop installers via Gitea Release, not upload-artifact
Build desktop / desktop (push) Successful in 4m17s
CI / check (push) Successful in 39s
actions/upload-artifact@v4's backend fails on the Gitea runner (Upload installers
step errored). Mirror release.yml's proven path instead: curl + the built-in token
to the Releases API, into a ROLLING per-branch prerelease (tag desktop-<branch>,
deleted+recreated each push). Installers renamed space-free
(parking-desktop-<branch>-<sha>.{deb,AppImage}). Signed v* releases unchanged.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-24 10:32:29 +02:00
julian e0cfeb5e71 fix(ci): unsigned desktop build must disable updater artifacts
CI / check (push) Successful in 38s
Build desktop / desktop (push) Failing after 3m56s
createUpdaterArtifacts:true (for release.yml's .sig signing) makes `tauri build`
demand TAURI_SIGNING_PRIVATE_KEY and fail without it — even though the .deb/.AppImage
built fine. Override it off for the unsigned per-commit build via
--config '{"bundle":{"createUpdaterArtifacts":false}}'. release.yml keeps signing.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-24 10:24:47 +02:00
julian 8129b63a8c feat(profile): self-service name/email/password + desktop installers in CI
Build desktop / desktop (push) Failing after 5m2s
Build & push images / images (push) Successful in 3m1s
CI / check (push) Successful in 40s
Self-service profile: any signed-in user edits their OWN fullName/email and
changes their OWN password (proving the current one), without any user:*
permission. New routes PUT /api/auth/profile + /api/auth/password act only on
req.user.sub (cannot touch username/role), CSRF-guarded; SPA screen at /profile
reachable from the header username chip. email added to the session view +
SessionUser. 7 tests (routes/profile.test.ts); 148 server tests green.

Desktop in CI: new .gitea/workflows/build-desktop.yml builds .deb + .AppImage
on every push to dev/main and uploads them as unsigned workflow artifacts
(per-commit test build). Signed/versioned release stays on release.yml (tag v*).

Wiki: local-jwt-auth (self-service routes), desktop-shell-tauri (two-workflow CI
split), log entry.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-24 10:15:34 +02:00
julian 0a22eab4a8 fix(ci): install uv via official script, not astral-sh/setup-uv action
Build & push images / images (push) Successful in 2m49s
CI / check (push) Successful in 35s
The Gitea runner can't reliably resolve the astral-sh/setup-uv@v5 action — the
"Set up uv" step failed (exit 1) in build-images.yml (and the same step exists in
ci.yml). Replace the action with uv's official standalone install script
(`curl -LsSf https://astral.sh/uv/install.sh | sh`) + add $HOME/.local/bin to
$GITHUB_PATH, matching how the rest of the pipeline provisions tools (apt, corepack).
No third-party action dependency. Verified the install method yields a working uv on
a clean HOME. Same fix in both workflows.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-23 15:59:10 +02:00
julian 8155ff456b feat(deploy): Docker images for server (API+SPA) and vision + branch-aware build pipeline
CI / check (push) Successful in 35s
Build & push images / images (push) Failing after 17s
Containerize the two non-desktop apps for the booth appliance. The desktop app stays
on its own tag-only release.yml.

- apps/server/Dockerfile: multi-stage node:22-alpine. `pnpm deploy --legacy --prod`
  (NOT prune — the monorepo native better-sqlite3 won't resolve under a root prune)
  yields a self-contained bundle; build stage adds node-gyp toolchain, runtime adds
  libstdc++; non-root, healthcheck. Migrates the mounted DB on boot via a drizzle-kit-
  free runtime migrator (packages/db/scripts/migrate-runtime.mjs) — drizzle-kit is a
  devDep, pruned from prod.
- apps/server/src/static-spa.ts: Fastify serves the built React SPA (one container
  serves API + UI). GET-only fallback to index.html, excludes /api + /health so it never
  shadows the backend; a no-op in dev (no dist). Registered last in server.ts.
- apps/vision/Dockerfile: uv base, --extra alpr, model weights PRE-WARMED into the image
  as the runtime user so fast_alpr boots offline (0 downloads at runtime). Engine env-
  selected (VISION_RECOGNIZER stub|fast_alpr).
- Branch-aware: docker-compose.yml (base) + .dev.yml (build local, stub, ports) +
  .prod.yml (pull pinned, fast_alpr, vision internal, restart always); REGISTRY/TAG from
  env so a branch deploy pulls that branch's image.
- .gitea/workflows/build-images.yml: on push to dev/main, run the full turbo build+lint+
  test gate, then buildx push both images to git.infra.msai.al/mca/parking_solution with
  branch + branch-<sha> tags (registry cache; optional Komodo webhook behind KOMODO_ENABLED).
- .dockerignore excludes **/parking.sqlite* so the signed ledger is NEVER baked.

Verified locally (Docker 29): server image migrates + serves API+SPA (/health 200, /
+ /booth HTML, /api/nope JSON 404, no sqlite outside /data); vision image boots fast_alpr
with 0 runtime downloads; compose stack healthy with server→vision over the private network.

Wiki: new container-deployment.md; vision-service-packaging open Qs resolved; index + log.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-23 15:07:52 +02:00
julian 8a437d0c4b feat(booth): cancel wrongly-printed ticket (signed void) + refused-vs-anomaly display; fix CI uv
CI / check (push) Failing after 56s
Cancel a misprinted/test/wrong-vehicle ticket via a SIGNED `void` event — the
vehicle_entry is never edited/deleted (append-only). VoidFlow appends void{
voidedEntryRef, voidReason, operator, reasonCode:"void.ticketCancelled" }; route
POST /api/tickets/void gated event:void + open shift; reason REQUIRED. Refuses a
subscription / already-exited / already-voided / paid ticket (refund out of scope).
The void folds the session CLOSED everywhere it's counted — occupancy (count +
reserved spots), pay-station (lookup/activeSessions), exit-flow (#sessionFor), and
reports (excluded from entries) — so a voided car stops occupying a spot, can't be
paid/exited, and doesn't inflate "cars entered". No barrier action. Booth UI: a
"Cancel ticket" action in the pay/exit lookup modal (transient + unpaid + open;
gated on event:void) with a preset-or-free reason prompt.

Reclassify the Live feed: refused-action events (exitRefused/entryRefused/
permitRefused — e.g. a double card-scan, at-capacity subscriber, exit on a closed
session) are benign warnings, not red anomalies. event-detail.tsx now shows them as
amber REFUZUAR/REFUSED, reserving red ANOMALI for genuine red-flags. Display-only —
no ledger change, so historical events reclassify too.

CI: install uv + sync vision deps before the Turbo run. @parking/vision's lint/
typecheck/test shell to `uv run …`, but CI set up only Node+pnpm, so `uv run ruff`
failed ("uv not found") and broke the whole Turbo run. The Python checks pass once
uv provisions the toolchain.

- new: void-flow.ts (+ tests, 8) ; occupancy void-fold test
- shared: reason code void.ticketCancelled ; both web catalogs (sq/en parity)
- wiki: parking-session (ticket-void folds + guards, refused/anomaly split), log

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-22 20:13:21 +02:00
julian ae5c122980 ci(gitea): CI checks + tag-triggered signed Tauri desktop release
Mirror the house Gitea Actions pattern (cf. trm/processor): corepack pnpm +
frozen install on ubuntu-latest.

ci.yml — push/PR to dev → pnpm turbo run build lint + test (covers tsc, vite
build, i18n catalog type-parity, and the shared vitest suite).

release.yml — on a v* tag → install Tauri Linux deps (webkit2gtk-4.1, libsoup-3,
gtk-3, appindicator, rsvg, patchelf) + rustup, cache cargo/target, then
`pnpm --filter @parking/desktop bundle` signed with the updater key from Gitea
secrets (TAURI_SIGNING_PRIVATE_KEY + _PASSWORD). Collects .deb/.rpm/.AppImage +
their .sig, assembles latest.json (platform key linux-x86_64 — verified against
the tauri-plugin-updater target format), and publishes a Gitea Release via the
API with the built-in token (no marketplace release action needed).

Both workflows validated (PyYAML parse). No secret values committed — only
${{ secrets.* }} references.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-21 12:33:20 +02:00