Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c398febf0f | ||
|
|
3849709cc2 |
No files matched your search
@@ -215,6 +215,21 @@ jobs:
|
||||
DOCTOR_RC=0
|
||||
docker exec "$SMOKE" dsh-workspace-doctor.mjs --check > /dev/null || DOCTOR_RC=$?
|
||||
[ "$DOCTOR_RC" -le 1 ]
|
||||
# ...and the entrypoint must actually invoke it. The doctor prints a
|
||||
# "dsh-workspace-doctor:" line when it runs; the entrypoint logs a
|
||||
# "workspace doctor:" line only when it could not. A direct
|
||||
# `docker exec` above sails past a wrong path or a lost exec bit, so
|
||||
# the boot log, not the binary, is what proves the wiring.
|
||||
if ! docker logs "$SMOKE" 2>&1 | grep -q "^dsh-workspace-doctor:"; then
|
||||
echo "the workspace doctor did not run at boot"
|
||||
docker logs "$SMOKE" | tail -30
|
||||
exit 1
|
||||
fi
|
||||
if docker logs "$SMOKE" 2>&1 | grep -q "workspace doctor:"; then
|
||||
echo "the entrypoint could not run the workspace doctor"
|
||||
docker logs "$SMOKE" | tail -30
|
||||
exit 1
|
||||
fi
|
||||
OK=""
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec "$SMOKE" node -e '
|
||||
|
||||
@@ -62,11 +62,15 @@ RUN set -eux; \
|
||||
# installs) lives inside the mounted state volume, never the rootfs.
|
||||
# PUID/PGID: the ids dsh runs as; the entrypoint aligns the state volume to
|
||||
# them at startup. Unraid's share default is 99:100.
|
||||
# DSH_WORKSPACE_DOCTOR: the entrypoint's workspace-grouping step. `repair` is
|
||||
# the default so an update heals the projects whose directories the update
|
||||
# erased; `warn` reports without repairing, `off` is silent.
|
||||
ENV DSH_HOME=/home/node/.dsh \
|
||||
PNPM_HOME=/home/node/.dsh/.pnpm \
|
||||
XDG_CACHE_HOME=/home/node/.dsh/.cache \
|
||||
XDG_STATE_HOME=/home/node/.dsh/.state \
|
||||
XDG_CONFIG_HOME=/home/node/.dsh/.config \
|
||||
DSH_WORKSPACE_DOCTOR=repair \
|
||||
PUID=1000 \
|
||||
PGID=1000
|
||||
|
||||
|
||||
@@ -123,10 +123,11 @@ Three things went wrong with the alternatives, and this repo is shaped by each:
|
||||
- **Workspace grouping is checked before every start.** A project directory outside the
|
||||
mounted `/workspace` and state volume does not survive a container update, and dsh joins
|
||||
a session to its project only at creation — so those chats collect under "Ungrouped" and
|
||||
stay there. The entrypoint reports every such project and every recoverable chat, and
|
||||
with `DSH_WORKSPACE_DOCTOR=repair` recreates the missing directories and re-accounts the
|
||||
orphaned sessions at startup. Session logs are never touched; the workspace registry is
|
||||
backed up first. See [Chats land in Ungrouped after an update](#chats-land-in-ungrouped-after-an-update).
|
||||
stay there. Before every start the entrypoint recreates the missing project directories
|
||||
and re-accounts the orphaned chats, so an update heals itself with no configuration.
|
||||
Session logs are never touched; the workspace registry is backed up first.
|
||||
`DSH_WORKSPACE_DOCTOR=warn` reports without repairing, `=off` is silent. See
|
||||
[Chats land in Ungrouped after an update](#chats-land-in-ungrouped-after-an-update).
|
||||
|
||||
#### Operations
|
||||
- **Upstream mirror.** The `master` branch and the `dsh-v*` tags track GitHub exactly,
|
||||
@@ -229,10 +230,16 @@ Your DeepSeek API key can be set below (stored in the container env) or entered
|
||||
<Config Name="PUID" Target="PUID" Default="1000" Description="User id dsh runs as, and the owner applied to the appdata volume at startup. Set to 99 to keep Unraid's usual nobody ownership." Type="Variable" Display="advanced" Required="false" Mask="false">1000</Config>
|
||||
<Config Name="PGID" Target="PGID" Default="1000" Description="Group id dsh runs as. Set to 100 (users) alongside PUID=99 for Unraid's usual share ownership." Type="Variable" Display="advanced" Required="false" Mask="false">1000</Config>
|
||||
<Config Name="Take ownership of Workspace" Target="DSH_CHOWN_WORKSPACE" Default="0" Description="Set to 1 to recursively chown the Workspace contents to PUID:PGID at startup, so the agent can modify files that arrived owned by someone else. Off by default - it rewrites ownership across your data." Type="Variable" Display="advanced" Required="false" Mask="false">0</Config>
|
||||
<Config Name="Workspace Doctor" Target="DSH_WORKSPACE_DOCTOR" Default="repair" Description="Workspace grouping at startup. warn (default in the image) only reports; repair recreates project directories that the container update erased and re-accounts the chats orphaned with them, so they return to their project instead of 'Ungrouped' (a backup of the workspace registry is written first, session logs are never touched); off is silent. Project directories outside /workspace and appdata are erased by every update - keep project data in Workspace." Type="Variable" Display="advanced" Required="false" Mask="false">repair</Config>
|
||||
<Config Name="Workspace Doctor" Target="DSH_WORKSPACE_DOCTOR" Default="repair" Description="Workspace grouping at startup. repair (the image default, so leaving this alone is fine) recreates project directories a container update erased and re-accounts the chats orphaned with them, so they return to their project instead of 'Ungrouped' (a backup of the workspace registry is written first, session logs are never touched); warn only reports; off is silent. Project directories outside /workspace and appdata are erased by every update - keep project data in Workspace." Type="Variable" Display="advanced" Required="false" Mask="false">repair</Config>
|
||||
</Container>
|
||||
```
|
||||
|
||||
An **existing** container keeps the fields it was created with: Unraid does not add a
|
||||
variable a template gained later. That is fine here — the image default is already
|
||||
`repair`, so the Workspace Doctor row above only matters if you want to differ from it
|
||||
(`warn` reports without repairing, `off` is silent), set by hand under Docker → Edit →
|
||||
*Add another Path, Port, Variable, Label or Device* → Variable.
|
||||
|
||||
Behind a Cloudflare Tunnel, add the public hostname as a `--trusted-host` and have the
|
||||
tunnel rewrite `Host` to that name (and strip `Origin`) so the fence sees a name it
|
||||
trusts.
|
||||
@@ -250,7 +257,7 @@ trusts.
|
||||
| `/home/node/.dsh` | `DSH_HOME` — settings, credentials, sessions, plugins, pnpm store/cache/config. **Mount this.** Taken over at startup. |
|
||||
| `/workspace` | The agent's working directory (`WORKDIR`). Mount only what the agent may read and modify. Mount point made writable; contents untouched unless `DSH_CHOWN_WORKSPACE=1`. |
|
||||
| `/opt/deepseek-harness/web.cordis.patch.yml` | The baked bind patch: `webserver.host: 0.0.0.0`, `port: 3080` |
|
||||
| Env | `PUID`, `PGID`, `DSH_CHOWN_WORKSPACE`, `DSH_WORKSPACE_DOCTOR` (`warn` default, `repair` fixes lost grouping, `off` is silent), plus `DSH_HOME` / `PNPM_HOME` / `XDG_*` pinned into the state volume |
|
||||
| Env | `PUID`, `PGID`, `DSH_CHOWN_WORKSPACE`, `DSH_WORKSPACE_DOCTOR` (`repair` default — reports and repairs lost grouping at every start; `warn` reports only, `off` is silent), plus `DSH_HOME` / `PNPM_HOME` / `XDG_*` pinned into the state volume |
|
||||
| Labels | `dev.milesward.dsh-version`, `.pnpm-version`, `.base-digest`, `.recipe-hash` — what CI's freshness check reads |
|
||||
|
||||
Four rules that are easy to get wrong:
|
||||
@@ -322,15 +329,25 @@ runs once per state volume, on the first project, and never again.) Two conseque
|
||||
chat into it. The old chats stay under "Ungrouped".
|
||||
|
||||
Before every start the entrypoint reports this — each project with its storage, each
|
||||
missing directory, every chat it could recover — and with `DSH_WORKSPACE_DOCTOR=repair`
|
||||
it fixes it at startup:
|
||||
missing directory, every chat it could recover — and repairs it, with no configuration to
|
||||
set. `DSH_WORKSPACE_DOCTOR=warn` reports without repairing, `=off` is silent:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Repairs | recreates missing workspace directories, and re-accounts chats whose recorded directory matches a project but which no project lists |
|
||||
| Never | touches a session log, deletes a session, invents a project, or moves a chat into a project it did not run in |
|
||||
| Before writing | copies the workspace registry to `workspace.json.bak-<timestamp>`; the write itself is atomic and in dsh's own format |
|
||||
| By hand | `docker exec -u 1000:1000 <container> dsh-workspace-doctor.mjs --check` (add `--all --dry-run` to preview a repair) |
|
||||
| By hand | report with `docker exec <container> dsh-workspace-doctor.mjs --check`; repair with the one-off container below |
|
||||
|
||||
To repair without waiting for a container start, stop the container first so nothing races
|
||||
the registry write, then run the doctor against the state volume directly (`--entrypoint`
|
||||
bypasses dsh, and the image's `PUID`/`PGID` are applied to what it writes):
|
||||
|
||||
```sh
|
||||
docker run --rm --entrypoint /usr/local/bin/dsh-workspace-doctor.mjs \
|
||||
-v /mnt/user/appdata/deepseek-harness:/home/node/.dsh \
|
||||
gitea.milesward.dev/mward4/deepseek-harness:latest --all
|
||||
```
|
||||
|
||||
The recreated directory is empty: this restores the *grouping*, not the files — those lived
|
||||
in the container and nothing here can bring them back. Keep project data under `/workspace`
|
||||
|
||||
+34
-14
@@ -133,21 +133,38 @@ if [ "$(id -u)" = 0 ]; then
|
||||
# session is created, against the project directory's canonical path — and a
|
||||
# directory outside the mounted /workspace and state volume does not survive
|
||||
# a container update. Those chats then collect under "Ungrouped" for good,
|
||||
# because the one-time history bootstrap never runs again. Report it always;
|
||||
# recreate the directories and re-account the orphaned sessions when asked
|
||||
# (backup first, session logs never touched). See README:
|
||||
# "Chats land in Ungrouped after an update".
|
||||
case "${DSH_WORKSPACE_DOCTOR:-warn}" in
|
||||
# because the one-time history bootstrap never runs again. The default is to
|
||||
# repair: recreate the directories and re-account the orphaned sessions
|
||||
# (backup first, session logs never touched), so a container update heals
|
||||
# itself with no configuration. DSH_WORKSPACE_DOCTOR=warn only reports,
|
||||
# =off is silent. See README: "Chats land in Ungrouped after an update".
|
||||
#
|
||||
# Resolve it by name: the installed file is dsh-workspace-doctor.mjs, and a
|
||||
# path spelled out here is exactly how a "not found" reaches production. The
|
||||
# CI smoke test asserts this step's output appears in the boot log, so a
|
||||
# rename or a wrong spelling fails the build instead of the container start.
|
||||
DOCTOR="$(command -v dsh-workspace-doctor.mjs 2>/dev/null || true)"
|
||||
case "${DSH_WORKSPACE_DOCTOR:-repair}" in
|
||||
0|off|no) ;;
|
||||
repair|all|1|yes)
|
||||
/usr/local/bin/dsh-workspace-doctor --all \
|
||||
|| log "workspace doctor: repair did not finish; starting dsh with the registry unchanged"
|
||||
# The doctor runs as root here; hand what it wrote back to the runtime user.
|
||||
chown "$PUID:$PGID" "$STATE/storages/workspace.json" "$STATE/storages/"workspace.json.bak-* 2>/dev/null || true
|
||||
warn|check|report)
|
||||
if [ -n "$DOCTOR" ]; then
|
||||
"$DOCTOR" --check \
|
||||
|| log "workspace doctor: see the report above; DSH_WORKSPACE_DOCTOR defaults to repair, so this start was told not to fix it"
|
||||
else
|
||||
log "workspace doctor: dsh-workspace-doctor.mjs is not installed in this image;"
|
||||
log " workspace grouping was NOT checked. Update the image."
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
/usr/local/bin/dsh-workspace-doctor --check \
|
||||
|| log "workspace doctor: see the report above - set DSH_WORKSPACE_DOCTOR=repair to fix it at startup"
|
||||
if [ -n "$DOCTOR" ]; then
|
||||
"$DOCTOR" --all \
|
||||
|| log "workspace doctor: repair did not finish; starting dsh with the registry unchanged"
|
||||
# The doctor runs as root here; hand what it wrote back to the runtime user.
|
||||
chown "$PUID:$PGID" "$STATE/storages/workspace.json" "$STATE/storages/"workspace.json.bak-* 2>/dev/null || true
|
||||
else
|
||||
log "workspace doctor: dsh-workspace-doctor.mjs is not installed in this image;"
|
||||
log " workspace grouping was NOT repaired. Update the image."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -163,6 +180,9 @@ if [ ! -w "$STATE" ]; then
|
||||
log " chown -R $(id -u):$(id -g) <host path mounted at $STATE>"
|
||||
exit 1
|
||||
fi
|
||||
/usr/local/bin/dsh-workspace-doctor --check \
|
||||
|| log "workspace doctor: see the report above (running unprivileged, so it only reports)"
|
||||
DOCTOR="$(command -v dsh-workspace-doctor.mjs 2>/dev/null || true)"
|
||||
if [ -n "$DOCTOR" ]; then
|
||||
"$DOCTOR" --check \
|
||||
|| log "workspace doctor: see the report above (running unprivileged, so it only reports)"
|
||||
fi
|
||||
exec dsh "$@"
|
||||
Reference in new issue
Block a user