Commit Graph
16 Commits
Author SHA1 Message Date
Miles WardandClaude Fable 5 2a0c808149 Make provenance-label readers multi-arch aware
Sync upstream and build images / sync-and-build (push) Successful in 25s
Publishing -r2 exposed this: a buildx multi-arch push stores an OCI index at
the tag, and both label readers assumed a single-arch manifest. Consequences
were a release body with 'pnpm ?' and — much worse — CI's freshness check
seeing the pinned image as permanently stale, which would have published a
new -rN revision every six hours forever.

Both readers now follow an index to a platform manifest (preferring amd64,
the deployment's arch) before reading the config blob, and the publisher
warns when an image carries no provenance labels at all. Verified against
both a multi-arch tag (0.1.1-rc.2-r2) and a single-arch one (0.1.5-rc.2):
the pinned build now reads as current, so no redundant rebuild.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-11 12:11:25 -04:00
Miles WardandClaude Fable 5 400797ecac Wrap the pnpm family too, not just dsh
Sync upstream and build images / sync-and-build (push) Successful in 1m45s
Verifying the pushed -r1 image found one hole: only dsh was wrapped, so a
root 'docker exec <c> pnpm ...' created PNPM_HOME/XDG_CONFIG_HOME owned by
root inside the state volume — which the unprivileged dsh could not then use
for plugin installs (self-healing on the next restart, since the entrypoint
re-chowns, but broken until then).

The wrapper is now generic (execs the resolved target directly; dsh's bin.js
is +x with a node shebang and pnpm's bin is an ELF, so no node prefix is
needed) and is installed over dsh, pnpm, pnpx, pn and pnx. Renamed
dsh-wrapper.sh -> run-as-runtime-user.sh to match what it does.

Verified: root 'docker exec pnpm --version' + 'pnpm store path' + 'dsh plugin
add' leave zero root-owned files in the volume; 23-case matrix still green.
Ships as 0.1.1-rc.2-r2.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-11 12:08:54 -04:00
Miles WardandClaude Fable 5 acd81b2f79 Self-healing volume ownership: no more manual chown, no more EACCES
Sync upstream and build images / sync-and-build (push) Failing after 22s
A bind mount arrives with the host's ownership, so a freshly created appdata
directory killed dsh with EACCES on mkdir /home/node/.dsh/profiles/web. Only
root inside the container can fix that, so:

- docker-entrypoint.sh starts as root, aligns the node account with PUID/PGID
  (default 1000:1000, Unraid convention), takes ownership of the state volume
  when needed, then setpriv's down before exec'ing dsh. The network-reachable
  process is never root and no setuid binary is left behind. The workspace
  mount point is made writable but its CONTENTS are left alone unless
  DSH_CHOWN_WORKSPACE=1 — never silently rewrite ownership across user data.
- Dropped USER node (root is required to chown); dsh is wrapped so a root
   re-execs as PUID:PGID and cannot leave
  root-owned files in the volume. Wrapper resolves the real bin.js at build
  time and self-tests.
- Explicit --user is honored: ownership cannot be repaired, so the entrypoint
  verifies writability and exits with one actionable line instead of a Node
  stack trace.
- XDG_CONFIG_HOME pinned into the volume too; pnpm was reaching for
  /root/.config when HOME was unset.
- CI: labels now carry a recipe hash, and the freshness check cuts a -rN
  revision when the recipe changes — so an image fix reaches the pinned
  deployment without waiting for an upstream release.
- README/template: chown step removed, PUID/PGID/DSH_CHOWN_WORKSPACE
  documented, template pinned to 0.1.1-rc.2-r1.

Verified with a 23-case matrix: root-owned volume self-heals and serves 200,
already-correct volume skips the chown, 99:100 + PUID/PGID works, plugin
add/remove via root docker exec leaves no root-owned files, explicit --user
still works and fails clearly when it can't, both argument forms serve.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-11 12:00:30 -04:00
Miles WardandClaude Fable 5 d6a97dd8ea inspect-deployment: keep format strings inside the YAML block
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-11 11:42:58 -04:00
Miles WardandClaude Fable 5 95754b9124 Add manual inspect-deployment workflow (what is the Unraid container actually running?)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-11 11:39:41 -04:00
Miles WardandClaude Fable 5 7a5e8a4ca0 Releases: mark the pinned deployment version automatically
Sync upstream and build images / sync-and-build (push) Successful in 25s
The generator now receives the Dockerfile's default DSH_VERSION and calls out
that release as the pinned deployment version, so the Releases page says
which tag the Unraid template runs without hand-editing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-10 17:18:50 -04:00
Miles WardandClaude Fable 5 ca70b61844 Build npm next too; release generator with upstream notes + provenance; tags cleanup
Sync upstream and build images / sync-and-build (push) Successful in 2m35s
- CI plan: candidates are npm latest AND next (release candidates), plus an
  optional workflow_dispatch 'version' input for any exact version. This is
  what was holding back 0.1.5-rc.2 (published as next, not latest).
- Upstream dsh-v* tags are no longer mirrored into this repo (deleted the
  existing ones); master stays an exact branch mirror and every release links
  to its upstream tag. This repo's tags are now exclusively v<image tag>.
- Dockerfile: build-provenance labels (org.opencontainers.image.revision /
  .created, dev.milesward.ci-run) so releases cite the commit and CI run that
  actually built the image instead of whatever HEAD is at publish time.
- Releases: shell template replaced by .gitea/scripts/publish-releases.js,
  run on the node:22 base image (no runner deps). It pulls the upstream
  GitHub release's English notes, adds version-dependent caveats (launch
  token from 0.1.2, Session format V3 from 0.1.5), image facts and Unraid
  deploy steps. Existing v* tags re-pointed to 2d033c3 (the commit that
  built those images); their releases are regenerated in this format.
- README: dynamic badges (shields Gitea release, Gitea workflow status),
  0.1.5-rc.2 row, pipeline/tag-namespace docs updated.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-10 17:04:16 -04:00
Miles WardandClaude Fable 5 1a13239fb6 CI: build release body with printf (heredoc broke the YAML block scalar)
Sync upstream and build images / sync-and-build (push) Successful in 26s
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-10 15:39:33 -04:00
Miles WardandClaude Fable 5 5121d84b53 Transparent brand logo, per-tag releases, README rewrite, MIT license
- assets/: replace the white-background avatar with a transparent 512px
  PNG (brand blue #4d6bfe, tight-cropped) plus a 128px header variant.
  Same path, so the Unraid template icon keeps working.
- CI: every image tag in the registry now gets a v<tag> git tag on main
  and a Gitea release carrying digest, dsh/pnpm/base versions, deploy
  notes and the 0.1.2+ launch-token caveat. Idempotent, so it backfills
  the three existing tags on the next run.
- README rewritten to match OpenPXE/Discord-Authenticator: centered brand
  header, badges, contents, why/highlights, quick start, Unraid template,
  image contract, versions & upgrade procedure (0.1.5 session-format V3
  has no downgrade), plugins, security model, pipeline, layout.
- LICENSE: MIT for the build tooling.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-10 15:34:47 -04:00
Miles WardandClaude Fable 5 2d033c323b Post-incident hardening: pnpm, pinned versions, immutable tags, no more :latest
Sync upstream and build images / sync-and-build (push) Successful in 2m4s
- Dockerfile: install pnpm (pinned via PNPM_VERSION arg) alongside dsh so
  'dsh plugin --profile web add|update|remove' works in-container. Plain
  'npm i -g' instead of corepack: corepack shims resolve versions at runtime
  and write to a root-owned cache, breaking the uid-1000/read-only contract.
  All pnpm writable dirs (store via PNPM_HOME, cache/state via XDG vars)
  point into the /home/node/.dsh volume - verified zero rootfs writes.
- Dockerfile: ENTRYPOINT ['dsh'], CMD carries the patch flag AND --no-open
  (a headless server must not open a browser; on 0.1.2+ that can burn the
  one-time launch token). DSH_VERSION default pinned to 0.1.1-rc.2.
- CI: never publishes or moves 'latest' (deleted from the registry). Builds
  only missing immutable version tags; a toolchain freshness check rebuilds
  the pinned version as <ver>-rN when a newer pnpm or node base image ships.
  Every build passes a smoke test before push: dsh/pnpm versions match and
  the server boots with the baked patch, answering on the container's
  non-loopback interface (200 pre-token, 303/401 on 0.1.2+).
- README: operating contract - pinned version rationale, PostArgs-replace-CMD
  warning, 0.1.2 launch-token behavior, plugin management, lanmode
  incompatibility, template pinned to :0.1.1-rc.2.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-06 02:43:10 -04:00
Miles WardandClaude Fable 5 768de4ac75 Unraid template support: baked --patch layer, /workspace, logo, template in README
Sync upstream and build image / sync-and-build (push) Successful in 19s
- Ship /opt/deepseek-harness/web.cordis.patch.yml and pass it via upstream's
  --patch launcher flag (must precede the web command's own flags) instead of
  seeding DSH_HOME; entrypoint accepts template Post Arguments verbatim.
- Add /workspace (agent WORKDIR) and pin DSH_HOME=/home/node/.dsh; drop the
  implicit /home/node volume so templates mount .dsh and /workspace cleanly.
- README: Unraid template (repo/support/icon/versions corrected for this
  image, lanmode plugin caution), first-run chown note, DeepSeek logo.

Verified: no-args, flags-only, and full template invocations all serve the
web UI; trust fence still 403s unknown Hosts; state persists to mounted .dsh.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 03:07:45 -04:00
Miles WardandClaude Fable 5 f21287e5c0 Validation: separate dsh-own-code diff from dependency-tree noise
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 02:54:01 -04:00
Miles WardandClaude Fable 5 af0cacd06c Validation: correct suspect image name (runzhliu), report containers using it
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 02:51:36 -04:00
Miles WardandClaude Fable 5 b20949fcc9 Use scoped REGISTRY_TOKEN for registry auth; add suspect-image validation workflow
Sync upstream and build image / sync-and-build (push) Successful in 2m18s
The Actions GITHUB_TOKEN can push git refs but the container registry
rejects it (run 1 failure). Also adds a manual, never-executes-the-image
forensic workflow comparing a third-party image on the runner host's
daemon against the trusted build.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 02:47:42 -04:00
Miles WardandClaude Fable 5 94869972ea Bind via upstream profile patch layer instead of container IP
Sync upstream and build image / sync-and-build (push) Successful in 19s
dsh's webserver schema only accepts 127.0.0.1 or 0.0.0.0; the CLI refuses
0.0.0.0 by design. Seed $DSH_HOME/profiles/web/cordis.patch.yml (the
supported override) from the entrypoint instead. Verified: UI serves 200
through a published port, /api trust fence 403s unknown Hosts, and
--trusted-host pass-through lifts it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 02:44:37 -04:00
Miles WardandClaude Fable 5 ca31308799 CI: mirror upstream and build trusted dsh container image
Sync upstream and build image / sync-and-build (push) Failing after 1m39s
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-09-02 02:35:03 -04:00