Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3cb44c5e6 | ||
|
|
87e3f12f4c | ||
|
|
9d741e75d0 | ||
|
|
7887d16f11 | ||
|
|
03ee131c6e | ||
|
|
2c6234be48 | ||
|
|
7969c164c4 | ||
|
|
4b80fffe67 |
@@ -1,5 +1,136 @@
|
||||
# NestQuest Release Notes
|
||||
|
||||
## Version 0.7.0 — 2026-09-24
|
||||
|
||||
### Scope
|
||||
|
||||
Seventh release of NestQuest, promoting the re-imagined architecture's admin
|
||||
surface from `dev` onto 0.6.0: the API deployment and identity layer, the
|
||||
admin-plane API work, and the standalone admin PWA that uses them.
|
||||
|
||||
- **Feature 17 — API Deployment and Identity:** the NestQuest API is packaged as
|
||||
a container (`Dockerfile`, `deploy/compose.yaml`, pinned base image and exact
|
||||
dependencies) and deployed behind a TLS reverse proxy (the shared Traefik, ACME
|
||||
via the Cloudflare DNS-01 challenge); the Authentik application, OIDC provider
|
||||
and `nestquest-admins` group are wired and the API validates the provider's
|
||||
issuer/audience/JWKS (RS256); the panel plane is restricted to the Home
|
||||
Assistant box while the admin plane is internet-facing behind Authentik;
|
||||
continuous backup runs as a contained Litestream sidecar over SFTP to a NAS and
|
||||
a restore is proven; a deployment runbook (`deploy/RUNBOOK.md`) and the
|
||||
host-firewall unit (`deploy/systemd/nestquest-api-firewall.service`) ship.
|
||||
(PRs #183, #214, #215, #216, #218.)
|
||||
- **Feature 19 — Admin PWA:** the parent-facing admin surface is now a
|
||||
standalone mobile-first React progressive web app (`admin/`), implementing
|
||||
`design/ADMIN-SPEC.md`: Authentik OIDC authorization-code + PKCE login with
|
||||
an explicit refusal for a user outside `nestquest-admins`; an installable
|
||||
PWA (manifest + service worker); and the Today, Definitions, Schedule and
|
||||
History screens. Every mutating action goes through the API admin routes;
|
||||
the API remains the security boundary (D-003/D-012). Today shows the
|
||||
household snapshot; Definitions supports create/edit with multi-select
|
||||
assignees, a recurrence rule with interval and per-window due times, a
|
||||
skip-on-away toggle, a shipped Lucide icon subset and a backend-computed
|
||||
occurrence preview; Schedule has a presence month grid (cycle position from
|
||||
the anchor date, never ISO parity, D-004) and an override editor with a live
|
||||
"removes N upcoming tasks" consequence count; History has All/Reversals/
|
||||
Missed filters, day-grouped rows, per-period stats, CSV export and the
|
||||
append-only footer. Undo reverses a completion, and live updates arrive over
|
||||
the admin SSE stream without a manual reload. (PRs #187–#210, plus the
|
||||
feature-review fix #212.)
|
||||
- **Feature 21 — Admin Today Snapshot API:** `GET /api/v1/admin/snapshot`
|
||||
serves the Today view's household snapshot on the admin plane (reusing the
|
||||
core snapshot builder with missed instances included, D-009). (PRs #190/#191.)
|
||||
- **Feature 22 — Admin Definitions Read and skip_on_away:**
|
||||
`GET /api/v1/admin/quest-definitions` lists all definitions (active and
|
||||
inactive), and a persisted per-definition `skip_on_away` boolean is accepted
|
||||
by create/update and honoured by materialization. Adds an optional
|
||||
`assignee_child_ids` to the edit route so the PWA can reassign children.
|
||||
(PRs #193, #194, #196, #197.)
|
||||
- **Feature 23 — Admin API Completion (preview, presence, live):** a bounded
|
||||
rule occurrence-preview route; presence-schedule and presence-override
|
||||
reads; a live override consequence count computed from the same core
|
||||
generator the materializer uses; and an admin-plane transition SSE stream
|
||||
(`GET /api/v1/admin/events`) off the one in-process publisher. (PRs
|
||||
#199–#203.)
|
||||
|
||||
### Requirements
|
||||
|
||||
- Minimum Home Assistant version: **2024.6.0**.
|
||||
- A running NestQuest API service, reachable at the base URL and panel service
|
||||
token configured in the integration options. The deployable ships in this
|
||||
release: `Dockerfile`, `deploy/compose.yaml` (API + a contained Litestream
|
||||
backup sidecar), `api/requirements.txt`, the host-firewall unit and
|
||||
`deploy/RUNBOOK.md`. Deploying it needs a host, the DNS/TLS path and the
|
||||
Authentik/Synology services described in the runbook.
|
||||
- The admin PWA is built to run at `https://nestquest.cubecraftlabs.com` behind
|
||||
Authentik. Serving the built `admin/` static bundle at that hostname is part of
|
||||
the deployment described in the runbook.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **Schema version advances 7 → 8.** Migration 8 adds the additive
|
||||
`skip_on_away` column to `quest_definitions` (default 1, so every existing
|
||||
definition keeps today's skip-when-absent behaviour). The migration runs on
|
||||
the first start of this version.
|
||||
- **Once migrated, a plain downgrade to 0.6.0 is impossible** — older builds
|
||||
refuse a newer schema version. **Back up `nestquest.db` (plus `-wal`/`-shm`)
|
||||
before upgrading**; see Rollback.
|
||||
- No Home Assistant entity, service, event or automation surface changes in
|
||||
this release. The API's new admin routes are additive.
|
||||
|
||||
### Known Limitations
|
||||
|
||||
- **The API and its identity/backup layer are deployed by the owner's
|
||||
infrastructure, not by this release alone.** The deployment artifacts and
|
||||
runbook ship here, but a running service still requires the host, the shared
|
||||
reverse proxy, the Authentik application/provider/`nestquest-admins` group, the
|
||||
backup target and the panel/admin network separation to be in place as the
|
||||
runbook describes.
|
||||
- **Serving the admin PWA is still outstanding.** `admin/` builds and is
|
||||
unit/integration tested and the API it talks to is live, but the built static
|
||||
bundle must still be hosted at `https://nestquest.cubecraftlabs.com` behind
|
||||
Authentik; until then the PWA is not reachable end-to-end.
|
||||
- **Feature 12's TouchHub/DAKOS runbook task remains physically unverified**
|
||||
(needs the wall panel).
|
||||
- Moving the household database from Home Assistant to the API box is a manual
|
||||
file copy (the schema is unchanged by that move beyond the v8 migration).
|
||||
- Single household; single API service.
|
||||
|
||||
### Rollback
|
||||
|
||||
**Schema version advances 7 → 8.** Migration 8 is additive, but a migrated
|
||||
database **cannot** be read by 0.6.0 or earlier (they refuse a newer schema
|
||||
version). The database is owned and migrated by the **NestQuest API service**,
|
||||
not by Home Assistant: it is the file at the API's configured
|
||||
`NESTQUEST_DB_PATH` on the API host, which need not be anywhere near the Home
|
||||
Assistant config directory. To return to 0.6.0 you must restore a backup of
|
||||
that file taken before the upgrade; a plain downgrade is impossible once
|
||||
migrated.
|
||||
|
||||
**API host (database):**
|
||||
|
||||
1. Before upgrading, stop the NestQuest API service and back up the database
|
||||
file at its configured `NESTQUEST_DB_PATH`, plus its `-wal` and `-shm`
|
||||
sidecars (`<NESTQUEST_DB_PATH>-wal`, `<NESTQUEST_DB_PATH>-shm`) if present.
|
||||
Only then deploy 0.7.0 and start the API service, which applies migration 8.
|
||||
2. To roll back: stop the API service, restore the backed-up database and
|
||||
sidecars to `NESTQUEST_DB_PATH`, redeploy the API service at 0.6.0, and roll
|
||||
back the Home Assistant integration to 0.6.0 (below). Then restart the API
|
||||
service and Home Assistant.
|
||||
|
||||
**Home Assistant integration — HACS installs** (integration component only;
|
||||
the database lives with the API, see above):
|
||||
|
||||
1. To roll back: HACS > Integrations > NestQuest > Redownload > 0.6.0, then
|
||||
restart Home Assistant.
|
||||
|
||||
**Home Assistant integration — Manual installs** (integration component only;
|
||||
the database lives with the API, see above):
|
||||
|
||||
1. To roll back: replace `custom_components/nestquest/` with the 0.6.0 tree,
|
||||
then restart Home Assistant.
|
||||
|
||||
**Repository operators:** git-revert the 0.7.0 release merge on `main`.
|
||||
|
||||
## Version 0.6.0 — 2026-09-23
|
||||
|
||||
### Scope
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/Maniacs-Mansion/NestQuest/issues",
|
||||
"requirements": [],
|
||||
"version": "0.6.0"
|
||||
"version": "0.7.0"
|
||||
}
|
||||
|
||||
@@ -21,3 +21,14 @@ NESTQUEST_OIDC_JWKS_URL=
|
||||
# unset; on the API box set the LAN address the reverse proxy
|
||||
# forwards to, e.g. 10.60.1.14.
|
||||
NESTQUEST_BIND_ADDRESS=
|
||||
|
||||
# Admin PWA build args (compose interpolation into the pwa service's
|
||||
# VITE_* build args; baked into the bundle, so rebuild after changing).
|
||||
# PUBLIC values, not secrets: the Authentik application is a PKCE client
|
||||
# with no client secret. All three are required — the pwa image build
|
||||
# fails when any is empty.
|
||||
# NESTQUEST_AUTHENTIK_ISSUER=https://auth.cubecraftlabs.com/application/o/nestquest/
|
||||
# NESTQUEST_API_BASE_URL=https://nestquest.cubecraftlabs.com
|
||||
NESTQUEST_AUTHENTIK_CLIENT_ID=
|
||||
NESTQUEST_AUTHENTIK_ISSUER=
|
||||
NESTQUEST_API_BASE_URL=
|
||||
|
||||
+110
-12
@@ -8,7 +8,7 @@ given, never their content.
|
||||
|
||||
| Piece | Where | Real value |
|
||||
| --- | --- | --- |
|
||||
| API box (Docker host) | this repository's `deploy/` | `10.60.1.14`, port `8080` |
|
||||
| API box (Docker host) | this repository's `deploy/` | `10.60.1.14`, port `8080` (API), `8081` (admin PWA, section 10) |
|
||||
| Reverse proxy / TLS | shared Traefik on `nukapi-03` | `10.60.1.33` |
|
||||
| Public hostname | Cloudflare DNS | `nestquest.cubecraftlabs.com` |
|
||||
| Identity | Authentik | `https://auth.cubecraftlabs.com` |
|
||||
@@ -57,12 +57,13 @@ section 5 depend on uid 999):
|
||||
|
||||
## 2. Compose
|
||||
|
||||
`deploy/compose.yaml` defines two services and one named volume:
|
||||
`deploy/compose.yaml` defines three services and one named volume:
|
||||
|
||||
| Service | Image | Role |
|
||||
| --- | --- | --- |
|
||||
| `api` | `nestquest-api:latest` (built, section 1) | FastAPI service on `8080`, healthcheck on `/health` |
|
||||
| `litestream` | `litestream/litestream:0.5.17` | Continuous SFTP backup of the database (section 7); starts only once `api` is healthy |
|
||||
| `pwa` | `nestquest-pwa:latest` (built from `deploy/pwa`, section 10) | Admin PWA served by nginx on `8081`; independent of the other two |
|
||||
|
||||
- **Volume `nestquest-db`** is mounted at `/var/lib/nestquest` in both
|
||||
services. The database is `NESTQUEST_DB_PATH=/var/lib/nestquest/nestquest.db`
|
||||
@@ -96,14 +97,18 @@ repository. What it must contain:
|
||||
|
||||
| Router | Rule | Middleware | Service |
|
||||
| --- | --- | --- | --- |
|
||||
| panel | ``Host(`nestquest.cubecraftlabs.com`) && PathPrefix(`/api/v1/panel`)`` | `ipAllowList`, `sourceRange: ["10.60.1.80/32"]` (HA box only) | `http://10.60.1.14:8080` |
|
||||
| admin (everything else: `/health`, `/docs`, `/openapi.json`, `/api/v1/admin/*`) | ``Host(`nestquest.cubecraftlabs.com`)`` | none — public; the API itself enforces Authentik JWTs (section 4) | `http://10.60.1.14:8080` |
|
||||
| `nestquest-panel` (priority 200) | ``Host(`nestquest.cubecraftlabs.com`) && PathPrefix(`/api/v1/panel`)`` | `nestquest-panel-lan`: `ipAllowList`, `sourceRange: ["10.60.1.80/32"]` (HA box only) | `nestquest-api` → `http://10.60.1.14:8080` |
|
||||
| `nestquest-admin` (priority 100: `/api/v1/admin`, `/health`, `/openapi.json`, `/docs`) | ``Host(`nestquest.cubecraftlabs.com`) && (PathPrefix(`/api/v1/admin`) \|\| Path(`/health`) \|\| Path(`/openapi.json`) \|\| PathPrefix(`/docs`))`` | `nestquest-headers` — no source restriction; the API itself enforces Authentik JWTs (section 4) | `nestquest-api` → `http://10.60.1.14:8080` |
|
||||
| `nestquest-app` (priority 50: everything else — the admin PWA) | see section 10 | none at Traefik; nginx allow-lists the proxy | `nestquest-pwa` → `http://10.60.1.14:8081` |
|
||||
|
||||
Both routers use the HTTPS entrypoint `websecure` with `tls.certResolver: myresolver`
|
||||
(HTTP is redirected to HTTPS by the shared Traefik). The panel router's
|
||||
rule is longer, so Traefik's default rule-length priority evaluates it
|
||||
before the catch-all admin router; if you set explicit priorities, keep
|
||||
panel higher.
|
||||
All routers use the HTTPS entrypoint `websecure` with `tls.certResolver: myresolver`
|
||||
(HTTP is redirected to HTTPS by the shared Traefik). Priorities are set
|
||||
explicitly so the order never depends on rule length: panel (200) above
|
||||
admin (100), and the PWA's `nestquest-app` router (50) below both. The
|
||||
admin router is scoped to `/api/v1/admin`, `/health`, `/openapi.json`
|
||||
and `/docs` — a bare ``Host(...)`` rule at a higher priority would
|
||||
swallow `/` and every PWA route. Any other `/api/*` path matches no
|
||||
router (the app router excludes `/api`), so Traefik answers `404`.
|
||||
|
||||
Reference shape, to rebuild the file if it is ever lost (entrypoint names
|
||||
must match the shared Traefik's static config on nukapi-03):
|
||||
@@ -113,26 +118,42 @@ http:
|
||||
routers:
|
||||
nestquest-panel:
|
||||
rule: "Host(`nestquest.cubecraftlabs.com`) && PathPrefix(`/api/v1/panel`)"
|
||||
priority: 200
|
||||
entryPoints: [websecure]
|
||||
middlewares: [nestquest-panel-allowlist]
|
||||
middlewares: [nestquest-panel-lan]
|
||||
service: nestquest-api
|
||||
tls:
|
||||
certResolver: myresolver
|
||||
nestquest-admin:
|
||||
rule: "Host(`nestquest.cubecraftlabs.com`)"
|
||||
rule: "Host(`nestquest.cubecraftlabs.com`) && (PathPrefix(`/api/v1/admin`) || Path(`/health`) || Path(`/openapi.json`) || PathPrefix(`/docs`))"
|
||||
priority: 100
|
||||
entryPoints: [websecure]
|
||||
middlewares: [nestquest-headers]
|
||||
service: nestquest-api
|
||||
tls:
|
||||
certResolver: myresolver
|
||||
nestquest-app:
|
||||
rule: "Host(`nestquest.cubecraftlabs.com`) && !PathPrefix(`/api`) && !Path(`/health`) && !Path(`/openapi.json`) && !PathPrefix(`/docs`)"
|
||||
priority: 50
|
||||
entryPoints: [websecure]
|
||||
service: nestquest-pwa
|
||||
tls:
|
||||
certResolver: myresolver
|
||||
middlewares:
|
||||
nestquest-panel-allowlist:
|
||||
nestquest-panel-lan:
|
||||
ipAllowList:
|
||||
sourceRange: ["10.60.1.80/32"]
|
||||
nestquest-headers:
|
||||
# headers middleware; copy its definition from the live file
|
||||
services:
|
||||
nestquest-api:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://10.60.1.14:8080"
|
||||
nestquest-pwa:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://10.60.1.14:8081"
|
||||
```
|
||||
|
||||
**Certificates.** `myresolver` is the shared Traefik's ACME (Let's
|
||||
@@ -453,3 +474,80 @@ To obtain `$ADMIN_JWT`: sign in to the admin PWA as the user, open the
|
||||
browser developer tools, and copy the session-storage value
|
||||
`nestquest.access_token`. Tokens are short-lived and are credentials —
|
||||
do not store them.
|
||||
|
||||
## 10. Admin PWA hosting
|
||||
|
||||
The admin PWA (`admin/`, a Vite + React single-page app) is served from
|
||||
the API box by the compose service **`pwa`**: nginx on container port
|
||||
`80`, published on `${NESTQUEST_BIND_ADDRESS:-127.0.0.1}:8081` (on the API
|
||||
box `10.60.1.14:8081`). Traefik sends every path of
|
||||
`nestquest.cubecraftlabs.com` that is not the API to it.
|
||||
|
||||
**Image** — `deploy/pwa/Dockerfile`, built from the **repository root**
|
||||
(it needs `admin/`); `deploy/pwa/Dockerfile.dockerignore` limits the
|
||||
context to `admin/` (without `node_modules`, `dist` or `.env` files) and
|
||||
`deploy/pwa/nginx.conf`.
|
||||
|
||||
- Stage 1, `node:22-alpine` (pinned by digest): `npm ci`, then
|
||||
`npm run build` with the three `VITE_*` build args below. Vite bakes
|
||||
them into the bundle, so **changing any of them needs a rebuild**. The
|
||||
build fails if any is empty.
|
||||
- Stage 2, `nginx:alpine` pinned to
|
||||
`sha256:1ed1b0e1d7652937d6cbdaf4018c7b6fc009a7dd6c3047351e2eddda745de43f`
|
||||
(nginx 1.31.6): the built `dist/` in `/usr/share/nginx/html` and
|
||||
`deploy/pwa/nginx.conf` as `conf.d/default.conf`.
|
||||
|
||||
**Build args** — compose reads them from `deploy/.env` (documented in
|
||||
`deploy/.env.example`). They are **public values, not secrets**: the
|
||||
Authentik application is a PKCE client with no client secret.
|
||||
|
||||
| `deploy/.env` variable | Build arg | Production value |
|
||||
| --- | --- | --- |
|
||||
| `NESTQUEST_AUTHENTIK_CLIENT_ID` | `VITE_AUTHENTIK_CLIENT_ID` | client id of the Authentik NestQuest application (Authentik admin → Applications → Providers) |
|
||||
| `NESTQUEST_AUTHENTIK_ISSUER` | `VITE_AUTHENTIK_ISSUER` | `https://auth.cubecraftlabs.com/application/o/nestquest/` |
|
||||
| `NESTQUEST_API_BASE_URL` | `VITE_API_BASE_URL` | `https://nestquest.cubecraftlabs.com` |
|
||||
|
||||
The Authentik application's redirect URI must include
|
||||
`https://nestquest.cubecraftlabs.com/auth/callback`.
|
||||
|
||||
**nginx** (`deploy/pwa/nginx.conf`):
|
||||
|
||||
- `allow 10.60.1.33; allow 10.60.1.14; deny all;` — only Traefik on
|
||||
nukapi-03 and the API box itself get content; any other source gets
|
||||
`403`, even with the port reachable on the LAN. Checks run on the API
|
||||
box must use its LAN address (`10.60.1.14:8081`), not `127.0.0.1`.
|
||||
- SPA fallback: unknown paths (e.g. `/auth/callback`) serve `index.html`;
|
||||
`/assets/*` (content-hashed, cached for a year) returns a real `404`
|
||||
when missing.
|
||||
- `/sw.js` and `/manifest.webmanifest` (`application/manifest+json`) are
|
||||
served from the site root with `Cache-Control: no-cache`, as is
|
||||
`index.html`, so a new release's service worker is picked up.
|
||||
|
||||
Build and start (the `api` and `litestream` services are unaffected):
|
||||
|
||||
dc build pwa
|
||||
dc up -d pwa
|
||||
dc ps pwa # "Up"
|
||||
|
||||
**Traefik router** — in `config/dynamic/nestquest-ccl.yml` on nukapi-03
|
||||
(reference shape in section 3):
|
||||
|
||||
| Router | Rule | Priority | Service |
|
||||
| --- | --- | --- | --- |
|
||||
| `nestquest-app` | ``Host(`nestquest.cubecraftlabs.com`) && !PathPrefix(`/api`) && !Path(`/health`) && !Path(`/openapi.json`) && !PathPrefix(`/docs`)`` | `50` — below `nestquest-admin` (100) and `nestquest-panel` (200) | `nestquest-pwa` → `http://10.60.1.14:8081` |
|
||||
|
||||
Same `websecure` entrypoint and `myresolver` certificate as the API
|
||||
routers; no Traefik middleware (nginx enforces the source allow-list).
|
||||
The file provider reloads the dynamic file on save.
|
||||
|
||||
**Verification** (in addition to section 9):
|
||||
|
||||
| # | Check | Command | Expect |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | PWA root over TLS | `curl -s -w '\n%{http_code}\n' https://nestquest.cubecraftlabs.com/ \| tail -3` | `200`, the `NestQuest Admin` shell |
|
||||
| 2 | Client route fallback | `curl -s -o /dev/null -w '%{http_code} %{content_type}\n' https://nestquest.cubecraftlabs.com/auth/callback` | `200 text/html` |
|
||||
| 3 | Manifest and worker | `curl -s -o /dev/null -w '%{http_code} %{content_type}\n' https://nestquest.cubecraftlabs.com/manifest.webmanifest` and the same for `/sw.js` | `200 application/manifest+json`, `200 application/javascript` |
|
||||
| 4 | API still routed to the API | `curl -s -o /dev/null -w '%{http_code}\n' https://nestquest.cubecraftlabs.com/health` | `200` (`{"status":"ok"}`, not HTML) |
|
||||
| 5 | Panel refused externally | section 9 check 4 | `403` |
|
||||
| 6 | Non-Traefik source refused | from a LAN machine other than `10.60.1.33`/`10.60.1.14`: `curl -s -o /dev/null -w '%{http_code}\n' http://10.60.1.14:8081/` | `403` (nginx `deny all`) |
|
||||
| 7 | Proxy source allowed | on `10.60.1.14`: `curl -s -o /dev/null -w '%{http_code}\n' http://10.60.1.14:8081/` | `200` |
|
||||
|
||||
@@ -42,6 +42,25 @@ services:
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
|
||||
# Admin PWA (Feature 24): the Vite bundle from admin/ served by nginx
|
||||
# (deploy/pwa/). Traefik routes every non-API path of the public host
|
||||
# here on port 8081; nginx itself only accepts the proxy (10.60.1.33)
|
||||
# and this host (10.60.1.14). The three build args are PUBLIC values
|
||||
# (the OIDC client is a PKCE client, no secret) read from deploy/.env
|
||||
# by compose interpolation; the image build fails if any is empty.
|
||||
pwa:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/pwa/Dockerfile
|
||||
args:
|
||||
VITE_AUTHENTIK_CLIENT_ID: ${NESTQUEST_AUTHENTIK_CLIENT_ID:-}
|
||||
VITE_AUTHENTIK_ISSUER: ${NESTQUEST_AUTHENTIK_ISSUER:-}
|
||||
VITE_API_BASE_URL: ${NESTQUEST_API_BASE_URL:-}
|
||||
image: nestquest-pwa:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${NESTQUEST_BIND_ADDRESS:-127.0.0.1}:8081:80"
|
||||
|
||||
# Continuous backup: streams the SQLite database on the shared named
|
||||
# volume to the Synology NAS over SFTP (config: deploy/litestream.yml).
|
||||
# The SFTP private key deploy/litestream_key is git-ignored and must be
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Admin PWA image (Feature 24): builds the Vite bundle from admin/ and
|
||||
# serves it with nginx. Build context is the REPOSITORY ROOT (it needs
|
||||
# admin/); deploy/pwa/Dockerfile.dockerignore limits that context to
|
||||
# admin/ and this directory's nginx.conf.
|
||||
#
|
||||
# docker compose -f deploy/compose.yaml build pwa
|
||||
#
|
||||
# The VITE_* values are baked into the bundle at build time. None is a
|
||||
# secret (the OIDC client is a public PKCE client), and all three are
|
||||
# required: the build fails fast rather than relying on app defaults.
|
||||
|
||||
FROM node:22-alpine@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 AS build
|
||||
|
||||
ARG VITE_AUTHENTIK_CLIENT_ID
|
||||
ARG VITE_AUTHENTIK_ISSUER
|
||||
ARG VITE_API_BASE_URL
|
||||
|
||||
WORKDIR /src/admin
|
||||
COPY admin/package.json admin/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY admin/ ./
|
||||
RUN test -n "$VITE_AUTHENTIK_CLIENT_ID" && test -n "$VITE_AUTHENTIK_ISSUER" && test -n "$VITE_API_BASE_URL" \
|
||||
|| { echo "VITE_AUTHENTIK_CLIENT_ID, VITE_AUTHENTIK_ISSUER and VITE_API_BASE_URL build args are required" >&2; exit 1; }
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine@sha256:1ed1b0e1d7652937d6cbdaf4018c7b6fc009a7dd6c3047351e2eddda745de43f
|
||||
|
||||
COPY deploy/pwa/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /src/admin/dist/ /usr/share/nginx/html/
|
||||
@@ -0,0 +1,11 @@
|
||||
# Build-context filter for deploy/pwa/Dockerfile (BuildKit reads
|
||||
# <Dockerfile>.dockerignore in place of the root .dockerignore, which
|
||||
# excludes deploy/). Only admin/ sources and nginx.conf enter the
|
||||
# build; local dependencies, build output and .env files never do.
|
||||
*
|
||||
!admin/
|
||||
!deploy/pwa/nginx.conf
|
||||
admin/node_modules
|
||||
admin/dist
|
||||
**/.env
|
||||
**/.env.*
|
||||
@@ -0,0 +1,53 @@
|
||||
# nginx server for the NestQuest admin PWA (Feature 24). Replaces the
|
||||
# image's /etc/nginx/conf.d/default.conf. The shared Traefik on
|
||||
# nukapi-03 (10.60.1.33) terminates TLS for nestquest.cubecraftlabs.com
|
||||
# and forwards every path that is not the API (/api, /health,
|
||||
# /openapi.json, /docs) here; see deploy/RUNBOOK.md section 10.
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Only the reverse proxy and the API box itself may connect; every
|
||||
# other source is refused (403) even if the port is reachable.
|
||||
allow 10.60.1.33;
|
||||
allow 10.60.1.14;
|
||||
deny all;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
server_tokens off;
|
||||
|
||||
# Content-hashed bundle files: cache forever, and a missing asset is
|
||||
# a real 404 rather than the SPA shell.
|
||||
location /assets/ {
|
||||
try_files $uri =404;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
}
|
||||
|
||||
# The service worker must be revalidated on every load so a new
|
||||
# release's worker (fresh revision, see admin/vite-sw-plugin.ts) is
|
||||
# picked up.
|
||||
location = /sw.js {
|
||||
try_files $uri =404;
|
||||
add_header Cache-Control "no-cache";
|
||||
}
|
||||
|
||||
# nginx's stock mime.types has no .webmanifest entry.
|
||||
location = /manifest.webmanifest {
|
||||
types { }
|
||||
default_type application/manifest+json;
|
||||
try_files $uri =404;
|
||||
add_header Cache-Control "no-cache";
|
||||
}
|
||||
|
||||
# SPA fallback: client routes such as /auth/callback resolve to the
|
||||
# app shell, which is never cached so it always names the current
|
||||
# hashed assets.
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location = /index.html {
|
||||
add_header Cache-Control "no-cache";
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "nestquest"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
description = "NestQuest Home Assistant Integration"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
Reference in New Issue
Block a user