feat: add community publishing and news syndication
This commit is contained in:
+102
-51
@@ -2,8 +2,8 @@
|
||||
|
||||
SHD.build runs in a Node 24 container through the official SvelteKit Node
|
||||
adapter. The image includes application code, migration and notification
|
||||
scripts, and native runtime dependencies. Environment files, databases,
|
||||
backups, and user uploads remain outside the image.
|
||||
scripts, and native runtime dependencies. Environment files, databases, backups,
|
||||
and user uploads remain outside the image.
|
||||
|
||||
## Persistent data
|
||||
|
||||
@@ -16,8 +16,8 @@ mounted at `/app/data`. It contains:
|
||||
- uploaded game-data icons under `game-icons/`.
|
||||
|
||||
Keep this volume during routine deployments. `docker compose down` without
|
||||
`--volumes` preserves it. Before a manual backup or restore, stop the `app`
|
||||
and `scheduler` services because both access the embedded database.
|
||||
`--volumes` preserves it. Before a manual backup or restore, stop the `app` and
|
||||
`scheduler` services because both access the embedded database.
|
||||
|
||||
## Staging environment
|
||||
|
||||
@@ -29,6 +29,21 @@ Staging defaults to:
|
||||
- Passkey relying-party ID: `shd.build`
|
||||
- Adapter canonical origin: `https://stage.shd.build`
|
||||
|
||||
Guide ratings and reports use Cloudflare Turnstile after repeated anonymous
|
||||
activity. Configure both values in each encrypted environment:
|
||||
|
||||
```text
|
||||
TURNSTILE_SITE_KEY=<public site key>
|
||||
TURNSTILE_SECRET_KEY=<server verification secret>
|
||||
```
|
||||
|
||||
Register the exact staging and production hostnames in the Turnstile widget. The
|
||||
browser receives only `TURNSTILE_SITE_KEY`. The server sends a challenge token
|
||||
and the request address to Cloudflare for verification; it never exposes
|
||||
`TURNSTILE_SECRET_KEY` or stores the raw address as visitor identity. Without a
|
||||
valid secret, a visitor who reaches the challenge threshold cannot submit more
|
||||
anonymous ratings or Guide reports.
|
||||
|
||||
Edit its encrypted configuration locally:
|
||||
|
||||
```sh
|
||||
@@ -38,12 +53,11 @@ npm run env:encrypt:staging
|
||||
```
|
||||
|
||||
Add `https://stage.shd.build/auth/discord/callback` to the Discord application's
|
||||
allowed OAuth redirect URIs. Point the staging DNS record at the deployment
|
||||
host and configure the host's TLS reverse proxy to forward
|
||||
`stage.shd.build` to port `5569` on the staging host. Docker publishes this
|
||||
port on the host interfaces so a containerized reverse proxy or tunnel can
|
||||
reach it; firewall the port to trusted LAN/tunnel sources when the host is
|
||||
reachable from other networks.
|
||||
allowed OAuth redirect URIs. Point the staging DNS record at the deployment host
|
||||
and configure the host's TLS reverse proxy to forward `stage.shd.build` to port
|
||||
`5569` on the staging host. Docker publishes this port on the host interfaces so
|
||||
a containerized reverse proxy or tunnel can reach it; firewall the port to
|
||||
trusted LAN/tunnel sources when the host is reachable from other networks.
|
||||
|
||||
The trusted staging runner decrypts `.env.staging` into a mode-`0600` runtime
|
||||
file. Gitea Actions holds the decryption key; Git and the container image do
|
||||
@@ -55,10 +69,10 @@ that signs in instead of writing generated credentials into deployment logs.
|
||||
|
||||
## Gitea Actions runner
|
||||
|
||||
Gitea Actions requires an `act_runner`. Register a trusted runner on the
|
||||
staging host from the repository's **Settings → Actions → Runners** page. The
|
||||
host needs Node/npm, Docker Engine, and Docker Compose v2. The runner account
|
||||
must be able to use Docker and write to the deployment directory.
|
||||
Gitea Actions requires an `act_runner`. Register a trusted runner on the staging
|
||||
host from the repository's **Settings → Actions → Runners** page. The host needs
|
||||
Node/npm, Docker Engine, and Docker Compose v2. The runner account must be able
|
||||
to use Docker and write to the deployment directory.
|
||||
|
||||
Use runner labels that provide an isolated validation job and a host deployment
|
||||
job:
|
||||
@@ -94,9 +108,9 @@ In `https://git.jb.codes/jb/shd.build/settings/actions`:
|
||||
|
||||
`.gitea/workflows/deploy-staging.yml` runs for pushes to `main` and manual
|
||||
dispatches. The trusted staging runner builds each commit-tagged image locally,
|
||||
so the workflow needs no registry credentials. Gitea assigns
|
||||
`gitea.run_number` to `SHD_BUILD_NUMBER`; each new workflow run receives the
|
||||
next integer, while retries of the same run retain their number.
|
||||
so the workflow needs no registry credentials. Gitea assigns `gitea.run_number`
|
||||
to `SHD_BUILD_NUMBER`; each new workflow run receives the next integer, while
|
||||
retries of the same run retain their number.
|
||||
|
||||
## Deployment lifecycle
|
||||
|
||||
@@ -114,12 +128,28 @@ The workflow:
|
||||
The outage covers backup, migration, and container startup. Migrations may also
|
||||
remove records that use retired data formats when a release explicitly ends
|
||||
compatibility. The deployment backup remains the recovery source for those
|
||||
records; the application does not repair or reinterpret them at runtime.
|
||||
The scheduler calls a bearer-authenticated endpoint on the application service
|
||||
once per minute. Scheduled message delivery therefore uses the web process's
|
||||
existing embedded-database connection instead of opening the locked database
|
||||
from a second process. The endpoint runs automated system messages, Oversight
|
||||
consolidation, and email digest checks and is not published through a host port.
|
||||
records; the application does not repair or reinterpret them at runtime. The
|
||||
scheduler calls a bearer-authenticated endpoint on the application service once
|
||||
per minute. Scheduled message delivery therefore uses the web process's existing
|
||||
embedded-database connection instead of opening the locked database from a
|
||||
second process. The endpoint runs automated system messages, Oversight
|
||||
consolidation, email digest checks, and the Ubisoft News importer. Docker does
|
||||
not publish the endpoint through a host port.
|
||||
|
||||
When enabled in production, the Ubisoft importer keeps its own persisted due
|
||||
time, so the one-minute tick starts a feed synchronization no more than once per
|
||||
hour after a successful run. When a synchronization fails, the importer stores
|
||||
the last error and consecutive failure count, then retries after 15 minutes with
|
||||
exponential backoff capped at six hours. Source items that disappear from the
|
||||
feed remain published; the importer performs no automatic removal. The scheduler
|
||||
runs notification delivery before it starts the bounded Ubisoft request, so a
|
||||
slow provider does not postpone notifications for the current tick.
|
||||
|
||||
`deploy/compose.production.yml` declares `UBISOFT_NEWS_IMPORT_ENABLED=true`,
|
||||
while the staging Compose file declares `false`. Development does not set the
|
||||
flag and remains disabled because the importer requires the exact value `true`.
|
||||
The application process evaluates the flag when it handles
|
||||
`/api/internal/scheduler`.
|
||||
|
||||
## Prelaunch operation
|
||||
|
||||
@@ -131,8 +161,8 @@ for a database that does not contain the row:
|
||||
- News, Database, Builds, Guides, and Builder: enabled
|
||||
|
||||
The migration uses an insert-if-missing seed. Later deployments preserve the
|
||||
operator's settings, and configuration changes take effect without a restart.
|
||||
A full environment bootstrap copies the source row. Canonical ISAC game-data
|
||||
operator's settings, and configuration changes take effect without a restart. A
|
||||
full environment bootstrap copies the source row. Canonical ISAC game-data
|
||||
imports leave the target environment's launch settings unchanged.
|
||||
|
||||
Use this operator flow after the first deployment:
|
||||
@@ -149,13 +179,13 @@ Use this operator flow after the first deployment:
|
||||
session before public release. Sign in and verify the same disabled routes
|
||||
remain unavailable.
|
||||
|
||||
Keep Coming Soon enabled while preparing production. Section controls can be
|
||||
set in advance because the launch gate takes precedence for signed-out traffic.
|
||||
Set Account Creation to Disabled if prelaunch access must remain limited to
|
||||
existing identities. Switch the Launch Gate to Public Launch after verification.
|
||||
Disabled sections remain absent from navigation and unavailable by direct URL
|
||||
for every user, including authenticated agents. Authentication bypasses only
|
||||
the Coming Soon gate.
|
||||
Keep Coming Soon enabled while preparing production. Section controls can be set
|
||||
in advance because the launch gate takes precedence for signed-out traffic. Set
|
||||
Account Creation to Disabled if prelaunch access must remain limited to existing
|
||||
identities. Switch the Launch Gate to Public Launch after verification. Disabled
|
||||
sections remain absent from navigation and unavailable by direct URL for every
|
||||
user, including authenticated agents. Authentication bypasses only the Coming
|
||||
Soon gate.
|
||||
|
||||
Turn Coming Soon on to restore the promotional gate. Site-control changes append
|
||||
`system_configuration / site_access.updated` audit events, including the
|
||||
@@ -177,9 +207,9 @@ open while its complete transfer bundle is created:
|
||||
npm run stage:bootstrap:export
|
||||
```
|
||||
|
||||
The command writes a timestamped, ignored JSON bundle under `data/backups/`.
|
||||
The bundle includes user records and password hashes. Transfer it over an
|
||||
encrypted channel, keep it out of Git, then copy it to the staging host and run:
|
||||
The command writes a timestamped, ignored JSON bundle under `data/backups/`. The
|
||||
bundle includes user records and password hashes. Transfer it over an encrypted
|
||||
channel, keep it out of Git, then copy it to the staging host and run:
|
||||
|
||||
```sh
|
||||
/opt/shd-build-stage/bootstrap-staging.sh \
|
||||
@@ -187,11 +217,10 @@ encrypted channel, keep it out of Git, then copy it to the staging host and run:
|
||||
```
|
||||
|
||||
The bootstrap flow stops the app and scheduler, creates a safety backup of the
|
||||
target, verifies that staging is unused, imports the entire development
|
||||
database and uploads, and restarts both services. Delete the transfer copy from
|
||||
the staging host after verifying the imported record counts and critical
|
||||
entities. The copy under the local ignored backup directory remains the
|
||||
recovery source.
|
||||
target, verifies that staging is unused, imports the entire development database
|
||||
and uploads, and restarts both services. Delete the transfer copy from the
|
||||
staging host after verifying the imported record counts and critical entities.
|
||||
The copy under the local ignored backup directory remains the recovery source.
|
||||
|
||||
Because this is a full initial clone, development session tokens and account
|
||||
data also enter staging. Staging and development remain independent from that
|
||||
@@ -293,9 +322,31 @@ Copy the resulting ignored JSON bundle to the deployment host over SSH, then:
|
||||
/secure/path/shd-production-bootstrap-YYYY-MM-DDTHH-MM-SSZ.json
|
||||
```
|
||||
|
||||
The importer verifies the bundle target, creates a production safety backup,
|
||||
and refuses to replace a production database containing users, builds, audit
|
||||
events, or proposals. Remove the remote transfer copy after verification.
|
||||
The importer verifies the bundle target, creates a production safety backup, and
|
||||
refuses to replace a production database containing users, builds, audit events,
|
||||
or proposals. Remove the remote transfer copy after verification.
|
||||
|
||||
### Promote one News article
|
||||
|
||||
Export a locally authored News article by ID or slug while the development
|
||||
server is stopped:
|
||||
|
||||
```sh
|
||||
npm run article:export -- <article-id-or-slug>
|
||||
```
|
||||
|
||||
The ignored bundle contains the article draft, immutable revisions, search row,
|
||||
managed-image metadata, and image bytes. It excludes comments, reports, user
|
||||
records, Builds, and ISAC data.
|
||||
|
||||
After deploying the matching application schema, stop the production app and
|
||||
scheduler and mount the transferred bundle into a one-off application container.
|
||||
Run `scripts/article-transfer.mjs import` with `ARTICLE_IMPORT_ENABLED=true` and
|
||||
`--confirm-article-import`. The importer creates a production database backup
|
||||
before opening the database, resolves the article owner by stable ID or
|
||||
username, rejects ID and slug conflicts, verifies image checksums, and appends
|
||||
`editorial_content / news.promoted`. Restart and health-check both services,
|
||||
then remove the transferred bundle from the host.
|
||||
|
||||
### Pull production ISAC data into development
|
||||
|
||||
@@ -312,10 +363,10 @@ notifications, settings, and audit history untouched.
|
||||
|
||||
### Pull production ISAC data into staging
|
||||
|
||||
Manually run **Replace staging ISAC data from production** in Gitea Actions.
|
||||
The workflow exports a consistent production snapshot, restarts production,
|
||||
then stops staging and imports it. The staging importer creates full and
|
||||
ISAC-only safety backups before replacement. Deployments do not run this flow.
|
||||
Manually run **Replace staging ISAC data from production** in Gitea Actions. The
|
||||
workflow exports a consistent production snapshot, restarts production, then
|
||||
stops staging and imports it. The staging importer creates full and ISAC-only
|
||||
safety backups before replacement. Deployments do not run this flow.
|
||||
|
||||
### Promote managed images without replacing other data
|
||||
|
||||
@@ -333,8 +384,8 @@ internal scheduler. `REMOVE_BG_CREDIT_CHECK_HOURS` controls the account lookup
|
||||
interval and defaults to six hours. Low-capacity alerts require both remaining
|
||||
pools to cross their thresholds: `REMOVE_BG_LOW_CREDIT_THRESHOLD` defaults to
|
||||
five paid credits, and `REMOVE_BG_LOW_FREE_CALL_THRESHOLD` defaults to ten free
|
||||
calls. Alerts are mandatory ISAC notifications for active Administrators and
|
||||
are deduplicated for each warning or critical episode.
|
||||
calls. Alerts are mandatory ISAC notifications for active Administrators and are
|
||||
deduplicated for each warning or critical episode.
|
||||
|
||||
```sh
|
||||
npm run images:export
|
||||
@@ -368,8 +419,8 @@ adds new stable IDs, skips identical assets, and blocks conflicting records.
|
||||
After reviewing an intentional metadata or file replacement, add
|
||||
`--update-existing`. Imported changes append `image.imported` or
|
||||
`image.import_updated` audit events. Remove the transferred bundle after
|
||||
verifying both environments because it may contain unpublished visual assets
|
||||
and creator identifiers.
|
||||
verifying both environments because it may contain unpublished visual assets and
|
||||
creator identifiers.
|
||||
|
||||
The `.env.staging` and `.env.production` npm import commands are intended for
|
||||
maintenance performed directly in an environment where those database and
|
||||
|
||||
Reference in New Issue
Block a user