Files
proof-of-work/start9/START9_PACKAGING_LOG.md
T
2026-02-28 09:27:26 -06:00

6.2 KiB

Start9 Packaging Log: Workout Log (from example to working 0.3.5 wrapper)

This file records exactly what was adapted from start9-example-packaging to package workout-planner for a Start9 server on StartOS 0.3.5 (Raspberry Pi / ARM64).

It is written as reusable documentation so you can repeat this process for future apps.

0) Goal and constraints

  • Target now: StartOS 0.3.5 on Raspberry Pi.
  • Future target: StartOS 0.4.0 when stable.
  • Priority: package should work now, while keeping data layout and wrapper structure easy to migrate later.

1) What was reviewed first

Reviewed the existing example wrapper in start9-example-packaging/0.3.5:

  • manifest.yaml
  • Makefile
  • Dockerfile
  • docker_entrypoint.sh
  • healthcheck.sh
  • instructions.md
  • README.md
  • DEPLOY_035.md

Then reviewed the app in workout-planner:

  • App Docker build/runtime behavior (workout-planner/Dockerfile)
  • DB shape + config (prisma/schema.prisma, DATABASE_URL usage)
  • Health endpoint (/api/health)
  • Seed/default user (prisma/seed.ts)

2) New Start9 wrapper scaffold created

Created a new folder (separate from your example):

  • start9/0.3.5/
  • start9/0.4/ (planning placeholder)

Files created in start9/0.3.5:

  • manifest.yaml
  • Dockerfile
  • docker_entrypoint.sh
  • healthcheck.sh
  • Makefile
  • instructions.md
  • README.md
  • DEPLOY_035.md
  • LICENSE
  • icon.png (copied from app icon assets)

Also created:

  • start9/0.4/README.md (migration intent notes)

3) Key packaging design decisions

3.1 Keep persistent data in /data

To make upgrades/migrations safer, wrapper mounts Start9 volume at /data and stores SQLite DB at:

  • /data/app.db

This is the most important continuity contract for migration to a future wrapper.

3.2 Keep runtime app files out of persistent volume

App code/binaries stay in image layers; only state goes in /data. This prevents app updates from overwriting user data.

3.3 Health checks use app endpoint

Wrapper health check calls:

  • http://127.0.0.1:${PORT}/api/health

This checks both server and DB connectivity (as implemented by your app).

3.4 Backup/restore copies whole /data

Manifest backup/restore actions copy /data <-> /backup to align with Start9 expectations and keep DB safe.

3.5 Future 0.4.0 migration posture

Added explicit notes to preserve:

  • package id (workout-log) where compatible
  • DB path contract (/data/app.db)
  • backup semantics

4) Runtime wiring added for first boot

In docker_entrypoint.sh:

  • Ensures /data exists.
  • Uses /data/app.db as DATABASE_URL target.
  • If /data/app.db does not exist on first run, copies a seeded template DB from image (/app/prisma/data/app.db).
  • Starts app with node /app/server.js.

Why this matters:

  • First launch has a ready DB + default user.
  • Subsequent restarts/upgrades keep existing /data/app.db untouched.

5) Build issues discovered and fixes applied

Issue A: Prisma/OpenSSL runtime failure on ARM musl

Observed error during ARM container validation:

  • Prisma engine expected OpenSSL compatibility; DB access failed.

Fix applied in wrapper Dockerfile:

  • Install openssl in builder stage.
  • Install openssl in runner stage.

Result:

  • Prisma client loads correctly at runtime.

Issue B: First-run DB missing tables (main.User does not exist)

Root cause:

  • Repo .dockerignore excludes local .db files, so no seeded DB was copied from source tree.

Fix applied in wrapper Dockerfile build stage:

  1. Generate Prisma client.
  2. Create temporary DB: DATABASE_URL=file:/tmp-seed/app.db npx prisma db push --skip-generate
  3. Seed it: DATABASE_URL=file:/tmp-seed/app.db npm run db:seed
  4. Copy seeded DB into image: /app/prisma/data/app.db

Result:

  • First boot can copy seeded DB into /data/app.db.
  • Health endpoint reports DB connected.

6) Validation steps run

Successfully validated:

  1. ARM image build from wrapper:

    • make -C start9/0.3.5 image-arm
  2. Local smoke run from built image tar:

    • docker load -i start9/0.3.5/image.tar
    • run container and query /api/health

Final smoke result:

  • HTTP 200
  • JSON contained status: ok and database: connected

7) Why start-sdk pack failed on this machine

start-sdk pack failed with:

  • fatal: not a git repository (or any of the parent directories): .git

Meaning:

  • start-sdk expects to run from inside a Git repository so it can compute metadata (commit/hash).

This is unrelated to your app logic; it is a packaging environment requirement.

8) What “Step 1” means (plain English)

When I said “initialize under git,” I meant:

  • The folder where you run start-sdk pack must be inside a Git repo.

If your Workout-log folder is just a normal folder today, do this once:

cd /Users/macpro/Projects/Workout-log
git init
git add .
git commit -m "Initial commit for Start9 packaging"

After that, this should work:

make -C start9/0.3.5 package

You do not need your local dev server on port 3000 running for packaging. Packaging builds a Docker image and .s9pk artifact separately.

9) Install flow on StartOS 0.3.5

  1. Build package:
cd /Users/macpro/Projects/Workout-log
make -C start9/0.3.5 package
  1. In StartOS UI (0.3.5), sideload:
  • start9/0.3.5/workout-log.s9pk
  1. Install + start service.

  2. Open service UI and login:

  • admin@local / workout123
  1. Change password immediately.

  2. Run a manual backup.

10) Reusable checklist for your next app

Use this sequence next time:

  1. Copy known-good wrapper structure (manifest, Dockerfile, entrypoint, healthcheck, docs, makefile).
  2. Define persistent data contract first (/data/...).
  3. Ensure first boot initializes DB/schema (migration or seeded template).
  4. Verify health endpoint checks both app + DB.
  5. Build ARM image and smoke-test locally before Start9 sideload.
  6. Ensure repo is a Git repo before start-sdk pack.
  7. Document migration invariants for future StartOS versions (ID, DB path, backup format).

11) Files to edit before publishing/distributing

In start9/0.3.5/manifest.yaml, replace placeholder values:

  • wrapper-repo
  • upstream-repo
  • support-site
  • marketing-site
  • license (if you choose MIT or another license)