Document the real Docker/node dist/index.js production path, durable SQLite, single replica, and Firebase credential options so deploys are not treated as equivalent to tsx.
This commit is contained in:
+7
-5
@@ -9,13 +9,15 @@ PORT=3003
|
||||
# PARTNER_URL=https://partner-api.endorser.ch
|
||||
# DEFAULT_PARTNER_API_SERVER=https://partner-api.endorser.ch
|
||||
|
||||
# Firebase Admin: inline service account JSON (one line).
|
||||
# If unset, uses Application Default Credentials (e.g. GOOGLE_APPLICATION_CREDENTIALS).
|
||||
# Firebase Admin (required for WAKEUP_PING and AlertSearch FCM).
|
||||
# Path 1: inline service account JSON (one line). The application reads this variable.
|
||||
# FIREBASE_SERVICE_ACCOUNT_JSON={"type":"service_account",...}
|
||||
# Path 2: if unset/empty, Application Default Credentials (ADC).
|
||||
# ADC may use GOOGLE_APPLICATION_CREDENTIALS; the application does not read that name itself.
|
||||
|
||||
# Directory for the SQLite FCM registration database (default: ./data).
|
||||
# Creates notify.sqlite (plus -wal/-shm while the process is running).
|
||||
# Durable directory for SQLite: notify.sqlite plus -wal/-shm while running (default: ./data).
|
||||
# In production this must be persistent storage (Docker: volume on /app/data).
|
||||
# NOTIFY_DATA_DIR=./data
|
||||
|
||||
# Set to "test-local" to bypass ethr JWT expiry verification in local dev only.
|
||||
# Do not set NODE_ENV=test-local in production (bypasses ethr JWT expiry).
|
||||
# NODE_ENV=test-local
|
||||
|
||||
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [0.1.14] - 2026.09.01
|
||||
### Changed
|
||||
- Production runbook: Docker/`node dist/index.js` is the canonical deploy path; document durable SQLite, single replica, Firebase credential options, and smoke checks
|
||||
|
||||
|
||||
## [0.1.13] - 2026.08.31
|
||||
### Added
|
||||
- AlertSearch scheduler delivers a TimeSafari FCM digest (`type: alert_search`) when a completed run consumed today's JWT and `digest.hasUpdates` is true; WAKEUP_PING and JWT/cursor rules are unchanged
|
||||
|
||||
@@ -24,7 +24,7 @@ pnpm run dev
|
||||
pnpm test
|
||||
```
|
||||
|
||||
The server starts on `http://localhost:3003` (or the port in `PORT`). Hot-reloads on file changes.
|
||||
The server starts on `http://localhost:3003` (or the port in `PORT`). Hot-reloads on file changes. `pnpm start` is the same `tsx` entry without watch. These commands are **not** the production Docker path (`node dist/index.js`).
|
||||
|
||||
On first use, the service creates `NOTIFY_DATA_DIR` (default `./data`) and the SQLite file `notify.sqlite` with the required schema.
|
||||
|
||||
@@ -66,7 +66,7 @@ Authorization: Bearer <current-user-JWT>
|
||||
|
||||
### Alert search retrieval
|
||||
|
||||
`retrieveAlertSearch` (not yet wired to the scheduler) GETs:
|
||||
The daily scheduler runs `retrieveAlertSearch` against:
|
||||
|
||||
- `{ENDORSER_URL}/api/v2/report/alertSearch`
|
||||
- `{PARTNER_URL}/api/partner/alertSearch`
|
||||
@@ -93,6 +93,8 @@ The **delegated** JWT is sent as `Authorization: Bearer`. Pass independent `endo
|
||||
|
||||
`NOTIFY_DATA_DIR` defaults to `./data` (relative to the process working directory). The `data/` directory is gitignored.
|
||||
|
||||
**Production must keep these files on durable storage** (disk or a Docker volume). A container or VM rebuild that drops `NOTIFY_DATA_DIR` loses FCM registrations, delegated JWT batches, and alertSearch cursors. Back up all three files together when using WAL.
|
||||
|
||||
### Schema (high level)
|
||||
|
||||
Table `fcm_registrations` holds one row per registered device:
|
||||
@@ -132,16 +134,47 @@ Persist or back up the SQLite files under `NOTIFY_DATA_DIR`:
|
||||
|
||||
## Production
|
||||
|
||||
Runs TypeScript directly via `tsx` (no compile step).
|
||||
Canonical production deployment is the **Docker image**: `pnpm build` then `node dist/index.js` (`Dockerfile` `CMD`). That is not the same as the development commands `pnpm run dev` / `pnpm start`, which run TypeScript through `tsx` and are for local development only.
|
||||
|
||||
```bash
|
||||
pnpm install --prod
|
||||
pnpm start
|
||||
```
|
||||
### Single replica
|
||||
|
||||
Ensure `NOTIFY_DATA_DIR` points at a durable location (or accept the default `./data` next to the process cwd).
|
||||
Run **exactly one Node process / one production replica**. AlertSearch scheduling and its in-flight overlap guard are process-local. There is no distributed scheduler lock. SQLite is a local file. Two processes will double-run AlertSearch and FCM wakeup and can corrupt or fork the database.
|
||||
|
||||
Or with Docker:
|
||||
### Firebase credentials
|
||||
|
||||
Working Firebase Admin credentials are required for **both** `WAKEUP_PING` and AlertSearch FCM (`type: alert_search`). The app initializes Firebase once at process start (`src/services/firebase.ts`).
|
||||
|
||||
Supported paths (in this order):
|
||||
|
||||
1. `FIREBASE_SERVICE_ACCOUNT_JSON` — inline service-account JSON (one line). The application reads this variable.
|
||||
2. If that variable is unset or empty, **Application Default Credentials**. ADC may use `GOOGLE_APPLICATION_CREDENTIALS` (a file path to a key JSON). **The application does not read `GOOGLE_APPLICATION_CREDENTIALS` itself**; the Google/Firebase ADC stack does.
|
||||
|
||||
Invalid `FIREBASE_SERVICE_ACCOUNT_JSON` prevents the process from starting. Missing ADC typically allows listen/`/health` but FCM sends fail later.
|
||||
|
||||
### Persistent SQLite
|
||||
|
||||
Set `NOTIFY_DATA_DIR` to a durable directory, or keep the Docker default `/app/data` on a **persistent volume**. The service uses:
|
||||
|
||||
- `{NOTIFY_DATA_DIR}/notify.sqlite`
|
||||
- `{NOTIFY_DATA_DIR}/notify.sqlite-wal`
|
||||
- `{NOTIFY_DATA_DIR}/notify.sqlite-shm`
|
||||
|
||||
### Environment checklist
|
||||
|
||||
| Variable / constraint | Production requirement | Default if unset |
|
||||
|---|---|---|
|
||||
| `PORT` | Optional | `3003` |
|
||||
| `ENDORSER_URL` | Optional if using production Endorser | `https://api.endorser.ch` |
|
||||
| `PARTNER_URL` | Optional if using production Partner | `https://partner-api.endorser.ch` |
|
||||
| `FIREBASE_SERVICE_ACCOUNT_JSON` **or** working ADC | **Required** for FCM (wakeup and AlertSearch) | ADC if JSON unset |
|
||||
| `NOTIFY_DATA_DIR` | **Durable** path (or volume on `/app/data`) | `./data` (cwd-relative; in Docker that is `/app/data`) |
|
||||
| `NODE_ENV` | Must **not** be `test-local` (that bypasses ethr JWT expiry) | Docker image sets `production` |
|
||||
| Replicas | **One** process | Not enforced in code |
|
||||
| Persistent volume | **Required** for Docker so SQLite survives replace | None unless you pass `-v` |
|
||||
|
||||
Optional: `DEFAULT_ENDORSER_API_SERVER` / `DEFAULT_PARTNER_API_SERVER` are honored only if the corresponding `ENDORSER_URL` / `PARTNER_URL` is unset (`src/env.ts`).
|
||||
|
||||
### Docker (canonical)
|
||||
|
||||
```bash
|
||||
docker build --no-cache -t notify-wakeup-api:amd-$NOTIFY_WAKEUP_API_VERSION --platform linux/amd64 .
|
||||
@@ -150,14 +183,15 @@ docker run --env-file notify-wakeup-api.env -p 3003:3003 \
|
||||
notify-wakeup-api
|
||||
```
|
||||
|
||||
Mount a volume over `/app/data` (or whatever path you set with `NOTIFY_DATA_DIR`) so the SQLite database is not lost when the container is replaced.
|
||||
The image runs `node dist/index.js`. Mount a volume at `/app/data` (or set `NOTIFY_DATA_DIR` to another mounted path). Do not scale this container to multiple replicas.
|
||||
|
||||
Required environment variables:
|
||||
### Smoke test
|
||||
|
||||
`/health` only means the HTTP server is up. It does **not** prove Firebase, Endorser, Partner, SQLite durability, or AlertSearch.
|
||||
|
||||
1. **Listening:** `curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3003/health` (or the host/port you published). Expect `200`.
|
||||
2. **Health body:** `curl -sS http://127.0.0.1:3003/health` → `{"ok":true}`.
|
||||
3. **Both schedulers are started** from `src/index.ts` (`startScheduler()` then `startAlertSearchScheduler()`) when the process reaches `* Running backend`. Neither scheduler runs a pass on startup; the first pass is on the 5-minute timer.
|
||||
4. **AlertSearch activity in logs:** look for `[AlertSearchScheduler] Pass started` and `[AlertSearchScheduler] Pass completed in` (or `Pass skipped (already in flight)`). User failures log as `[AlertSearchScheduler] User failed`. FCM wakeup passes log `[Scheduler] Pass started` / `[Scheduler] Pass completed in`. These lines appear after the first interval, not at boot.
|
||||
5. **SQLite location:** after the process has handled a request or a scheduler pass that opens the DB, confirm `{NOTIFY_DATA_DIR}/notify.sqlite` exists (Docker default: `/app/data/notify.sqlite` on the volume). WAL sidecars may appear while the process is running.
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `FIREBASE_SERVICE_ACCOUNT_JSON` | Inline service account JSON (one line). If unset, falls back to Application Default Credentials. |
|
||||
| `PORT` | HTTP port (default: `3003`). |
|
||||
| `ENDORSER_URL` | Endorser API base URL used for auth checks and alertSearch (default: `https://api.endorser.ch`). |
|
||||
| `PARTNER_URL` | Partner API base URL used for alertSearch (default: `https://partner-api.endorser.ch`). |
|
||||
| `NOTIFY_DATA_DIR` | Directory for the SQLite database file `notify.sqlite` (default: `./data`). |
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "notification-wakeup-service",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@11.4.0",
|
||||
|
||||
Reference in New Issue
Block a user