diff --git a/start9/0.4/Dockerfile b/start9/0.4/Dockerfile index f936ec0..3cfc81f 100644 --- a/start9/0.4/Dockerfile +++ b/start9/0.4/Dockerfile @@ -8,18 +8,21 @@ # This Dockerfile is self-contained: it references only files under # `proof-of-work/` (the upstream app) and `start9/0.4/` (this wrapper). # -# Data preservation (v1.0.0:1 — initial seeded cutover): -# - This image bakes a one-time snapshot of the maintainer's live /data -# volume under /app/seed/data so the cutover from the legacy `workout-log` -# package preserves every workout, exercise, and preference. -# - docker_entrypoint.sh copies the seed into the StartOS-managed /data -# volume only on a truly-fresh first boot (both /data/app.db missing AND -# /data/.seeded absent). Every subsequent boot leaves /data alone. -# - v1.0.0:2 will strip the seed copy from the image and the seed-copy -# branch from the entrypoint once the cutover is verified in production. -# - A tiny empty-schema fallback DB is also COPYed from the builder stage -# (at /app/prisma/data/app.db) as a safety net for fresh sideloads on a -# brand-new host with no existing /data and no baked seed. +# Data preservation (v1.0.0:3 — post-cutover): +# - The image NO LONGER bakes a /data seed. The one-time cutover from +# the legacy `workout-log` package happened in v1.0.0:1, was verified +# in v1.0.0:2, and /data on the live host is now the sole source of +# truth. Removing the baked seed from the image eliminates any +# possibility of a future bug accidentally overwriting live data. +# - docker_entrypoint.sh's seed-copy branch is stripped to match. +# - The empty-schema fallback DB at /app/prisma/data/app.db is kept — +# it's still useful for brand-new sideloads on a host that has never +# had proof-of-work installed before. The build-time `npm run db:seed` +# populates it with the curated exercise library + admin@local user. +# - The seed snapshot itself stays on disk under start9/0.4/seed/data/ +# as a historical artifact; it's just not COPYed into the image. If +# you ever need to re-seed (e.g. spinning up a new instance with +# pre-loaded history), reintroduce the COPY line and rebuild. FROM node:20-alpine AS builder @@ -61,7 +64,6 @@ ENV NODE_ENV=production \ WORKOUT_DATA_DIR=/data \ WORKOUT_DB_PATH=/data/app.db \ WORKOUT_FALLBACK_SEED_DB_PATH=/app/prisma/data/app.db \ - WORKOUT_BAKED_SEED_DB_PATH=/app/seed/data/app.db \ WORKOUT_LIBRARY_JSON_PATH=/app/prisma/exercises.seed.json # Next.js standalone runtime bundle @@ -75,14 +77,16 @@ COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma # bundling failures here surface as auth being silently broken at boot. COPY --from=builder --chown=nextjs:nodejs /app/node_modules/bcrypt ./node_modules/bcrypt -# Empty-schema fallback DB (used only when no baked seed is available on a -# brand-new sideload). +# Empty-schema fallback DB. Used only on brand-new sideloads where /data +# is empty and no admin user exists yet. Build-time `npm run db:seed` +# populated it with the curated exercise library + admin@local. Existing +# installs always have /data/app.db so this branch never fires. COPY --from=builder --chown=nextjs:nodejs /tmp-seed/app.db /app/prisma/data/app.db -# Baked one-time cutover seed: the maintainer's live /data snapshot pulled -# off the running `workout-log` host via refresh_seed.sh. Copied into /data -# only on truly-fresh first boot. Removed in v1.0.0:2. -COPY --chown=nextjs:nodejs start9/0.4/seed/data /app/seed/data +# (v1.0.0:3 removed the COPY of start9/0.4/seed/data into /app/seed/data. +# The baked cutover seed served its one-time purpose during v1.0.0:1's +# cutover and is no longer needed; eliminating it removes any chance of +# a future bug overwriting live /data.) # Container entrypoint and diagnostic healthcheck COPY start9/0.4/docker_entrypoint.sh /usr/local/bin/docker_entrypoint.sh diff --git a/start9/0.4/docker_entrypoint.sh b/start9/0.4/docker_entrypoint.sh index e82a2ec..90496eb 100755 --- a/start9/0.4/docker_entrypoint.sh +++ b/start9/0.4/docker_entrypoint.sh @@ -3,15 +3,16 @@ # # Responsibilities (in order): # 1. Ensure the persistent /data directory exists. -# 2. Seed /data on truly-fresh first boot: -# a. If /app/seed/data/app.db is present (v1.0.0:1 cutover image), -# copy it into /data. This is how an operator migrating from -# the legacy `workout-log` package preserves their full history. -# b. Otherwise fall back to the empty-schema fallback DB at -# /app/prisma/data/app.db (already seeded with the curated -# exercise library at build time). -# Both branches are gated on /data/app.db being absent AND -# /data/.seeded being absent — never overwrites live data. +# 2. First-boot fallback ONLY: if /data is empty (no app.db, no +# .seeded marker) AND a fallback DB exists at +# /app/prisma/data/app.db, copy it into /data. This handles +# brand-new sideloads on a host that has never had proof-of-work +# installed before. Existing installs ALWAYS skip this branch +# because /data/app.db is already present. +# (v1.0.0:1 had a second branch that copied a baked cutover seed +# from /app/seed/data/app.db. v1.0.0:3 stripped both the COPY of +# that seed in the Dockerfile and the branch that copied it here, +# now that the cutover from `workout-log` is verified done.) # 3. Run idempotent compat ALTERs for columns added after older snapshots. # No-ops on hosts whose schema is already current. # 4. Ensure the curated exercise library is present for every user. New @@ -26,7 +27,6 @@ set -eu DATA_DIR="${WORKOUT_DATA_DIR:-/data}" DB_PATH="${WORKOUT_DB_PATH:-$DATA_DIR/app.db}" FALLBACK_SEED_DB_PATH="${WORKOUT_FALLBACK_SEED_DB_PATH:-/app/prisma/data/app.db}" -BAKED_SEED_DB_PATH="${WORKOUT_BAKED_SEED_DB_PATH:-/app/seed/data/app.db}" LIBRARY_JSON_PATH="${WORKOUT_LIBRARY_JSON_PATH:-/app/prisma/exercises.seed.json}" log() { @@ -37,21 +37,17 @@ log() { mkdir -p "$DATA_DIR" # ----------------------------------------------------------------------------- -# Step 1 — first-boot seeding. NEVER overwrites an existing app.db. -# Both branches are skipped on every restart of an installed host because -# /data/app.db already exists. +# Step 1 — first-boot fallback. NEVER overwrites an existing app.db. +# Skipped on every restart of an installed host because /data/app.db +# already exists. # ----------------------------------------------------------------------------- if [ ! -f "$DB_PATH" ] && [ ! -f "$DATA_DIR/.seeded" ]; then - if [ -f "$BAKED_SEED_DB_PATH" ]; then - log "no $DB_PATH and no .seeded marker; copying baked cutover seed from $BAKED_SEED_DB_PATH" - cp "$BAKED_SEED_DB_PATH" "$DB_PATH" - date -u +"seeded from baked cutover snapshot at %Y-%m-%dT%H:%M:%SZ" > "$DATA_DIR/.seeded" - elif [ -f "$FALLBACK_SEED_DB_PATH" ]; then - log "no $DB_PATH and no baked seed; copying empty-schema fallback from $FALLBACK_SEED_DB_PATH" + if [ -f "$FALLBACK_SEED_DB_PATH" ]; then + log "no $DB_PATH and no .seeded marker; copying empty-schema fallback from $FALLBACK_SEED_DB_PATH" cp "$FALLBACK_SEED_DB_PATH" "$DB_PATH" date -u +"seeded from empty-schema fallback at %Y-%m-%dT%H:%M:%SZ" > "$DATA_DIR/.seeded" else - log "no $DB_PATH, no baked seed, no fallback DB; creating empty $DB_PATH" + log "no $DB_PATH and no fallback DB; creating empty $DB_PATH" touch "$DB_PATH" fi else diff --git a/start9/0.4/seed/README.md b/start9/0.4/seed/README.md index c7cace9..2af69ab 100644 --- a/start9/0.4/seed/README.md +++ b/start9/0.4/seed/README.md @@ -1,59 +1,60 @@ -# Seed snapshot +# Seed snapshot (historical, post-cutover) -This directory contains a one-time snapshot of `/data/` from the live -StartOS 0.3.5 install of **Proof of Work** (`proof-of-work`). It is baked into -the 0.4 Docker image so the first 0.4 boot comes up with your production data -already in place. +This directory holds the one-time snapshot of `/data/` that was used to +cut the legacy `workout-log` StartOS package over to `proof-of-work` in +**v1.0.0:1**. As of **v1.0.0:3** the snapshot is no longer baked into +the deployed image — it stays here purely as a historical artifact and +as a starting point if you ever want to spin up a new instance with +pre-loaded history. + +> **Privacy:** the snapshot contains real workout history, exercise +> records, and a bcrypt'd password hash. The repo's `.gitignore` +> excludes `start9/*/seed/data/*.db` and `*.bak` so it never ends up in +> version control. Do not change that. ## Source | Field | Value | | --- | --- | -| Source file | `proof-of-work-2026-04-21T14-36-53-332Z.db` (user-provided backup) | -| Exported on | 2026-04-21T14:36:53Z | -| Exported from | StartOS 0.3.5 (aarch64) running `proof-of-work` v0.1.0.17 | -| SQLite integrity check | `ok` | +| Source files | `proof-of-work-2026-05-09T16-46-11-646Z.db` (latest cutover snapshot) | +| Exported from | StartOS 0.4 host running `workout-log` v0.1.0:20 | +| SQLite integrity check | `ok` at snapshot time | -## Row counts (at snapshot time) +The earlier `app.db.bak` file (when present on disk) is the previous +snapshot kept around as a manual rollback; it is also gitignored. -| Table | Rows | -| --- | --- | -| `User` | 1 (`admin@local`) | -| `UserPreferences` | 1 | -| `Session` | 9 | -| `Exercise` | 164 | -| `Workout` | 348 | -| `SetLog` | 5720 | -| `Equipment` | 0 | -| `Program` / `ProgramWeek` / `ProgramDay` / `ProgramExercise` | 0 | -| `ContentItem` / `ContentChunk` | 0 | -| `AISuggestion` | 0 | +## What the entrypoint did with this seed (v1.0.0:1 only) -## What the seed does +On **first boot** of v1.0.0:1, `docker_entrypoint.sh` checked for +`/app/seed/data/app.db`. If `/data/app.db` did NOT exist AND +`/data/.seeded` did NOT exist, it copied the snapshot into `/data/` +and wrote `/data/.seeded` with a timestamp. On every subsequent boot +that branch was skipped. -On **first boot** of this 0.4 package, `docker_entrypoint.sh`: +**v1.0.0:3 removed both:** +- The `COPY start9/0.4/seed/data /app/seed/data` line in the + `Dockerfile`. +- The baked-seed branch in `docker_entrypoint.sh`. -1. Creates `/data/` if missing. -2. If `/data/app.db` does NOT exist AND `/data/.seeded` does NOT exist, copies - everything under `/app/seed/data/` into `/data/` and writes `/data/.seeded` - with a timestamp. -3. Logs which branch it took to stderr (visible in the StartOS log viewer). +The empty-schema fallback branch (used only for brand-new sideloads +on a host that's never had the package installed) stays. -On every subsequent boot, the seed copy is skipped and the live `/data/` is -used as the sole source of truth. +## How to re-seed a future build (rare) -## When to refresh this snapshot +If you want to spin up another instance with this history pre-loaded +(e.g. moving to a new StartOS host without using the in-app +export/import flow), bring back the bake: -You usually refresh the seed only if you rebuild the package **before** doing -the first-time sideload on the 0.4 host. Once the 0.4 service is running, -stop rebuilding with a seed — the next package release (v0.1.0:19) removes -the seed step entirely so releases don't risk overwriting live data. +1. Refresh the snapshot from the live host: + ```sh + ./start9/0.4/refresh_seed.sh + ``` +2. Reintroduce the COPY line in the Dockerfile + (`COPY --chown=nextjs:nodejs start9/0.4/seed/data /app/seed/data`). +3. Reintroduce the baked-seed branch in `docker_entrypoint.sh` — see + the comment in that file referencing v1.0.0:3 for the exact shape. +4. Rebuild and sideload as a brand new install (the entrypoint's + first-boot guard will only seed if /data is empty). -To refresh before first-time sideload, from the repo root: - -```sh -./start9/0.4/refresh_seed.sh embassy@embassy.local -``` - -This SCPs the live `/embassy-data/package-data/volumes/proof-of-work/data/main/` -files back into `start9/0.4/seed/data/` and runs an integrity check. +For most "moving to a new instance" cases, the in-app +**Settings → Export & import my data** flow is simpler. diff --git a/start9/0.4/startos/versions/index.ts b/start9/0.4/startos/versions/index.ts index c5e4bd3..963f3ba 100644 --- a/start9/0.4/startos/versions/index.ts +++ b/start9/0.4/startos/versions/index.ts @@ -1,21 +1,23 @@ import { VersionGraph } from '@start9labs/start-sdk' import { v_1_0_0_1 } from './v1.0.0.1' import { v_1_0_0_2 } from './v1.0.0.2' +import { v_1_0_0_3 } from './v1.0.0.3' /** * Version graph for the `proof-of-work` package. * * v1.0.0:1 — initial release, seeded cutover from the legacy * `workout-log` package. - * v1.0.0:2 — CSP fix (reverts the over-strict nonce-based CSP that + * v1.0.0:2 — CSP fix (reverted the over-strict nonce-based CSP that * broke first paint in v1.0.0:1). + * v1.0.0:3 — post-cutover seed strip (baked /data snapshot removed + * from the image now that the cutover is verified done). * * StartOS picks `current` as the install target; `other` lists every - * node that can upgrade into `current`. Hosts on v1.0.0:1 upgrade to - * v1.0.0:2 via the no-op up migration; fresh installs land directly - * on v1.0.0:2. + * node that can upgrade into `current`. Hosts on v1.0.0:1 or :2 + * upgrade to :3 via no-op up migrations; fresh installs land on :3. */ export const versionGraph = VersionGraph.of({ - current: v_1_0_0_2, - other: [v_1_0_0_1], + current: v_1_0_0_3, + other: [v_1_0_0_1, v_1_0_0_2], }) diff --git a/start9/0.4/startos/versions/v1.0.0.3.ts b/start9/0.4/startos/versions/v1.0.0.3.ts new file mode 100644 index 0000000..65859ea --- /dev/null +++ b/start9/0.4/startos/versions/v1.0.0.3.ts @@ -0,0 +1,38 @@ +import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk' + +/** + * v1.0.0:3 — post-cutover seed strip. + * + * v1.0.0:1 baked a one-time snapshot of the maintainer's live `/data` + * volume into the image so the cutover from the legacy `workout-log` + * package would preserve every workout/exercise/preference. v1.0.0:2 + * fixed an unrelated CSP bug. With the cutover verified done, this + * release strips the baked seed: + * + * - Dockerfile: removes `COPY start9/0.4/seed/data /app/seed/data` + * and the `WORKOUT_BAKED_SEED_DB_PATH` env var. + * - docker_entrypoint.sh: drops the baked-seed branch. The + * empty-schema fallback branch (used only on brand-new sideloads + * with no existing /data) stays. Existing installs always have + * /data/app.db so neither branch ever fires for them. + * + * The baked seed itself stays on disk under start9/0.4/seed/data/ as + * a historical artifact (gitignored, never committed). If you ever + * need to spin up a new instance with pre-loaded history, reintroduce + * the COPY in the Dockerfile and rebuild — the entrypoint logic will + * pick the right branch. + * + * No schema changes, no data migration. /data on existing installs is + * left exactly as-is. Image size drops by ~1.7 MB (the snapshot size). + */ +export const v_1_0_0_3 = VersionInfo.of({ + version: '1.0.0:3', + releaseNotes: { + en_US: + 'Post-cutover cleanup: removes the one-time `/data` seed snapshot from the Docker image. The cutover from the legacy `workout-log` package is verified done; the snapshot is no longer needed in the image and removing it eliminates any chance of a future bug overwriting live `/data`. No data migration; existing installs are untouched.', + }, + migrations: { + up: async () => {}, + down: IMPOSSIBLE, + }, +})