Skip to content

fix(startos): audit packaging — drop stale sdk-build, add aarch64, complete i18n - #1

Merged
MattDHill merged 2 commits into
mainfrom
audit/startos-packaging-review
Jul 31, 2026
Merged

fix(startos): audit packaging — drop stale sdk-build, add aarch64, complete i18n#1
MattDHill merged 2 commits into
mainfrom
audit/startos-packaging-review

Conversation

@helix-nine

@helix-nine helix-nine commented Jul 30, 2026

Copy link
Copy Markdown

Full audit of the StartOS packaging ahead of Community Registry listing. This fork maintains only the StartOS wrapper: it consumes the image upstream publishes and never builds or pushes one of its own. Changes are confined to startos/, the build files, and CI — no application code is touched, so the fork stays cleanly rebasable on upstream.

What was broken

sdk-build/ shipped stale duplicate source. Its build script copied startos/*.ts in before bundling, but 19 of those copies were also committed, frozen at version 0.1.1:0 and image tag 0.5.2 while startos/ said 0.5.3. Deleted; the package now builds startos/ directly.

CI was publishing container images from this fork. Both release.yml and test-build.yml built the app and pushed it to ghcr.io/<this repo>. This fork maintains only the StartOS wrapper, so both jobs are gone: each workflow now just packs an s9pk against the tag named in the manifest, and nothing in CI writes to a container registry.

That also removes a destructive bug. test-build.yml rebuilt the released tag as amd64-only on every push to main and force-pushed it over the multi-arch image, plus :latest — which is why 0.5.2 in GHCR has no arm64 layer while 0.5.3 still does.

The manifest locked out ARM. arch: ['x86_64'], although the published 0.5.3 image is genuinely amd64 + arm64 — which excluded every ARM server. Now ['x86_64', 'aarch64'], verified by packing the aarch64 s9pk.

A dead duplicate setInterfaces in index.ts. start-core reads only main/init/uninit/createBackup/manifest/actions from the bundle, so it was never invoked. Restored to canonical plumbing.

Four version files with no-op migrations. v0_4_2/v0_4_7/v0_5_0/v0_5_2 all had empty up. Removed, other: []. The packed manifest still reports canMigrateFrom: "<=0.5.3:1", so every prior install upgrades in one hop.

The alerts block no longer exists in SDK 2.0. It passed typecheck only because setupManifest infers its generic from the argument, and was discarded at pack time regardless. Removed.

The marketplace long description was factually wrong — it advertised "BraiinsOS+ / CGMiner API", predating the asic-rs rewrite.

i18n was a scaffold, not an implementation. translations.ts was an empty stub. Now es_ES/de_DE/pl_PL/fr_FR across all six keys, plus those locales on the manifest descriptions and release notes.

startos/README.md was a dead scaffold citing a manifest.yaml that does not exist and StartOS 0.4.0.x. Rewritten against the packaging guide's writing-readmes.md; instructions.md rewritten against writing-instructions.md.

Also: SDK 2.0.62.0.9, main.ts defers to the image CMD via sdk.useEntrypoint() instead of duplicating it, packageRepo points at this fork, and stale release steps corrected in CONTRIBUTING.md and CLAUDE.md.

Known limitation, documented — and being fixed upstream

The service writes nothing to its mounted volume. All user state — miner list, names, captured power ceilings, and saved miner API passwords — lives in the browser's localStorage (blisspoint.state.v2). So main mounts at /data and stays empty, and Backups.ofVolumes('main') captures nothing: a restore yields an empty miner list. Confirmed empirically on the box, and documented in both the README limitations and the user-facing instructions rather than leaving the backup silently implying it works.

#2 addresses exactly this, and is merged into this branch: server/serve.cjs gains GET/POST/DELETE /api/state writing atomically to /data/state.json, and the Zustand store gets a storage adapter that prefers the server copy and falls back to localStorage.

It is inert until a new image ships. That change is application code baked into the container, and the manifest pins the currently published tag, which predates it — verified against the running install, where GET /api/state returns 404 and /data is still empty. So the volume and backup documentation in this PR stays as written, because it describes the artifact this PR actually produces.

Sequencing to land the feature:

  1. Upstream publishes an image containing the persistence change.
  2. Bump dockerTag in startos/manifest/index.ts and the version in startos/versions/current.ts.
  3. Rewrite the Volume/Backups sections and limitations 1–3 in startos/README.md, and the Limitations in instructions.md. Note that miner API passwords then move from browser-only storage into /data/state.json in plaintext, and into backups — that is a real change in posture and should be stated.
  4. Re-test: confirm /data/state.json appears, survives a restart, and that a restore repopulates the miner list.

Two fixes were needed on top of #2 as merged:

  • Dropped pnpm-lock.yaml. The repo is npm — package-lock.json is the lockfile and both the Dockerfile and CI run npm ci. A second lockfile from another package manager only invites drift.
  • Fixed npm run lint. Four bare catch (_) {} blocks in the storage adapter are hard no-empty errors, and ci.yml runs lint on every PR, so feat(persistence): add backend state persistence to /data/state.json for StartOS backups #2 as submitted would have failed CI. Now optional catch bindings with a note on why each failure is safe to swallow.

Test plan

Verified on a real StartOS 0.4.0.1 box (x86_64):

  1. make x86 → builds and packs blisspoint_x86_64.s9pk at v0.5.3:1, sdk 2.0.9. make arm also packs, confirming the arm64 image resolves.
  2. start-cli package install -s blisspoint_x86_64.s9pk → installs as 0.5.3:1.
  3. start-cli package start blisspoint → daemon starts; logs show Blisspoint serving on :80.
  4. Health check goes green: {"result": "success", "message": "The web interface is ready"}.
  5. Inside the container: node server/serve.cjs on 0.0.0.0:80 and proxy-rs on 127.0.0.1:8081 only.
  6. LAN egress works — the container reached a host and the gateway on the local /24. This is the whole premise of the service.
  7. GET /api/scan?subnet=<lan> swept a real /24 and returned cleanly.
  8. Web UI reachable over the LAN through the StartOS gateway; SPA fallback and /api/health both work end-to-end.
  9. stop is immediate and clean (no PID-1 signal hang, so runAsInit is not needed); start returns to green.
  10. /data confirmed empty after install, start, restart and a scan.

Not covered: miner control itself — pause/resume and power-target were not exercised against real hardware, as no ASIC was available on the test network. The transport around them is verified; the control paths are not.

…mplete i18n

This fork maintains only the StartOS wrapper: it consumes the image upstream
publishes and never builds or pushes one of its own. Changes are confined to
startos/, the build files, and CI; no application code is touched.

Build:
- Delete sdk-build/. Its build script copied startos/*.ts in before bundling,
  but 19 of those copies were also committed, frozen at 0.1.1:0 / image tag
  0.5.2 while startos/ said 0.5.3. Build startos/ directly instead: the SDK and
  ncc move to the root package.json as startos:check / startos:build, with the
  packaging tsconfig and a commonjs marker beside the source. The marker is
  load-bearing — the app root is "type": "module", and `s9pk pack` require()s
  the bundle.
- The package cannot use the SDK's s9pk.mk, which needs `npm run build` to be
  the ncc step while here it is the Vite build, so the Makefile stays bespoke.
- Bump @start9labs/start-sdk 2.0.6 -> 2.0.9; pack with an assets dir.

CI — stop publishing images:
- Both release.yml and test-build.yml built the app and pushed it to
  ghcr.io/<this repo>. Drop those jobs entirely; each workflow now only packs
  an s9pk against the tag named in the manifest. Nothing in CI writes to a
  container registry.
- test-build.yml previously rebuilt the released tag as amd64-only on every
  push to main and force-pushed it over the multi-arch image, plus :latest.
  That is how 0.5.2 lost its arm64 layer. Removing the job removes the hazard.
- Release and artifact names now derive from the packed manifest, with ':'
  replaced by '_' since git refs cannot contain it.

Package:
- arch was ['x86_64'] although the published image is amd64+arm64, excluding
  ARM servers entirely. Now ['x86_64', 'aarch64'].
- Drop the alerts block: it no longer exists in the SDK 2.0 types, passes
  typecheck only because setupManifest infers its generic from the argument,
  and is discarded at pack time.
- packageRepo now points at this fork, which is where the packaging lives;
  upstreamRepo and marketingUrl stay on the application's own repo.
- Remove a dead duplicate setInterfaces from index.ts; start-core reads only
  main/init/uninit/createBackup/manifest/actions from the bundle.
- Drop four migration-less version files and set other: []. The packed manifest
  still reports canMigrateFrom "<=0.5.3:1".
- main.ts defers to the image CMD via sdk.useEntrypoint() rather than
  restating it.
- The long description still advertised "BraiinsOS+ / CGMiner API", predating
  the asic-rs rewrite.

i18n: translations.ts was an empty stub. Populate es_ES/de_DE/pl_PL/fr_FR for
all six keys and add those locales to the descriptions and release notes.

Docs: startos/README.md was a scaffold citing a manifest.yaml that does not
exist and StartOS 0.4.0.x; rewritten against writing-readmes.md, with
instructions.md rewritten against writing-instructions.md. Both now state that
no data is written to the mounted volume — all state is browser localStorage,
so backups capture nothing.

Verified on StartOS 0.4.0.1 (x86_64): installs, daemon starts, health green,
UI reachable over LAN, LAN egress from the container works, a real /24 scan
completes, stop/restart clean, /data empty.
@helix-nine
helix-nine force-pushed the audit/startos-packaging-review branch from a4225ad to 30e58eb Compare July 30, 2026 22:59
@heatpunk

Copy link
Copy Markdown

Thanks for the feedback in PR #1! We have implemented backend state persistence for StartOS backups (/data/state.json) and opened PR #2 with the complete implementation: #2

@helix-nine

Copy link
Copy Markdown
Author

Thanks @heatpunk — that's exactly the gap this review turned up, and it's now merged into this branch. Your commit is carried here with authorship intact, so #2 is closed as superseded.

Two small things I fixed on the way in:

  • pnpm-lock.yaml — removed. This project uses npm: package-lock.json is the lockfile, and both the Dockerfile and CI run npm ci. Two lockfiles from different tools drift apart and give people different versions of the same dependency.
  • The lint check was failing. The four empty catch blocks in the storage adapter are treated as errors, and lint runs on every change here, so it would have gone red. They now use a shorter form with a note explaining why each failure is safe to ignore.

The implementation itself looks right — /api/state is handled before the miner API passthrough, and writes go to a temporary file first and then get renamed, so a crash mid-write can't corrupt the saved settings.


What's needed to actually switch this on — one job, on your side.

Nothing changes for StartOS users yet. StartOS installs a prebuilt Blisspoint, and the one it installs today was built before your change, so there is still nowhere for it to save to. We confirmed that on a real server: asking it for the saved settings returns "not found", and the storage folder is empty.

Please publish a new Blisspoint release with this change in it, then let us know it's out. That's the whole ask.

Once it exists we will:

  1. Point the StartOS package at the new version.
  2. Update the text that currently warns people their miner list isn't included in backups.
  3. Install it and confirm that a backup and restore really does bring the miners back.

One thing worth deciding before it ships: miner passwords are currently only kept in the browser. After this change they are saved on the server as plain text, and they will be included in backups. That is a reasonable trade for settings that survive, but it should be stated plainly in the docs so nobody is surprised.

@helix-nine
helix-nine force-pushed the audit/startos-packaging-review branch from d6114ea to 30e58eb Compare July 31, 2026 18:45
@heatpunk

Copy link
Copy Markdown

Upstream image ghcr.io/heatpunk/blisspoint:0.5.4 is now built and published with multi-arch (amd64/arm64) support and the /api/state persistence backend. Ready for bumping dockerTag to 0.5.4.

Upstream published 0.5.4 with a state-persistence backend, so the package can
finally deliver working backups. Bump the image tag and version, and rewrite
the documentation that described the old behaviour.

- dockerTag -> 0.5.4 (verified multi-arch: linux/amd64 + linux/arm64).
- Version -> 0.5.4:0; the upstream component moved, so the revision resets.
- The Node server now writes all user state to /data/state.json via tmp+rename,
  and the browser's localStorage becomes a cache and offline fallback rather
  than the system of record. The README's Volume and Backups sections said the
  opposite and are rewritten.
- Two limitations replace the three that this fixes: miner API passwords are
  now on disk in plaintext and therefore inside every backup, and concurrent
  edits from two devices are last-write-wins with no merge. Both are stated in
  the user-facing instructions too, since a password landing in a backup is
  something the operator should know without reading the README.

No application code is carried here — the persistence work is upstream's, and
this package consumes the image it publishes.

Verified on StartOS 0.4.0.1 (x86_64), upgrading 0.5.3:1 -> 0.5.4:0: health goes
green; GET /api/state returns {} on a fresh volume; POST creates
/data/state.json and reads back byte-identical; the file survives a service
restart; DELETE removes it; LAN egress from the container and the proxy health
check still pass.
@helix-nine
helix-nine force-pushed the audit/startos-packaging-review branch from d6114ea to 12c5bed Compare July 31, 2026 19:03
@helix-nine

Copy link
Copy Markdown
Author

Confirmed 0.5.4 is published and multi-arch (amd64 + arm64) — thanks. The package now points at it, and this branch is updated to 0.5.4:0.

Your persistence work is doing exactly what it should. Tested on a real StartOS server, upgrading from the previous version:

  • Asking for saved settings on a fresh install returns nothing, as expected.
  • Saving settings creates the file on the server's storage, and reading it back gives exactly what was saved.
  • The settings survive a restart — which is the whole point, and means they are now included in StartOS backups.
  • Clearing them works too, and the storage folder is left empty.
  • The miner connection and the health check still behave as before.

The wording that warned people their miner list wasn't included in backups is gone, replaced with two things they should know instead: miner passwords are saved as ordinary text and therefore end up inside backups, and if two people change settings from different phones at the same moment the last one to save wins.

One note on how we work with this repo going forward: we keep our copy limited to the StartOS packaging only, so we no longer carry your app changes here — we just point at the version you publish. If we ever need something changed in the app itself, we'll open a pull request on your repo rather than change it on our side. That keeps our copy easy to keep in step with yours.

@MattDHill MattDHill left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Packaging audit plus adoption of upstream 0.5.4, verified end-to-end on a StartOS 0.4.0.1 box: health green, state persists to /data/state.json and survives a restart, LAN egress intact, both arches pack.

Wrapper-only, as intended — no application code in this branch.

@MattDHill
MattDHill merged commit 0af7a89 into main Jul 31, 2026
3 checks passed
@MattDHill
MattDHill deleted the audit/startos-packaging-review branch July 31, 2026 19:12
@MattDHill

Copy link
Copy Markdown

Merged. Thanks @heatpunk — the persistence work landed the same day it was needed, and Blisspoint now backs up properly on StartOS and runs on ARM servers as well as x86.

How we'll work together from here

This repository is the StartOS packaging only. We don't change the app here. If we find something that needs fixing in the app itself — the interface, the Node server, proxy-rs — we'll open a pull request on your repository rather than change it on our side. That keeps this copy easy to keep in step with yours, and keeps you the single owner of the application.

You publish, we point at it. Nothing here builds or publishes a container image any more; both workflows just package against the version you publish to ghcr.io/heatpunk/blisspoint. So the rhythm is:

  1. You release a new Blisspoint version and tell us it's out.
  2. We point the package at it, update the docs if anything user-visible changed, and test it on a real server.
  3. It goes out to people on StartOS.

Please keep releasing multi-architecture builds (both amd64 and arm64, as you did for 0.5.4). A lot of StartOS servers are ARM, and if a release is amd64-only those users can't install it at all.

One thing to watch. Earlier today four release builds for the same version ran at once and raced to publish the same tag. It worked out, but if two finish together it's luck which one wins. Letting one finish before starting another avoids that.

Worth knowing about the change you just made

Miner passwords are now saved on the server as ordinary text, so they're inside every backup of Blisspoint. That's a fair trade for settings that survive, and it's written plainly in both the packaging notes and the instructions people see in StartOS — but if you ever want to encrypt or stop storing them, that would be a genuine improvement.

Also, when someone with no miners yet taps Scan, it only sweeps 192.168.1.x. Anyone on a different home network has to add the first miner by hand. Detecting the server's own network would remove that stumble for a lot of first-time users.

Neither is blocking anything — just the two things most likely to generate questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants