Read-only probe for boot failures: collapsed log tail, the profile package.json and patch layers (key-like values redacted, credentials never read), profile-local bundle packages and the rows they insert. Tooling probe falls back to the image when the container is not running.
103 lines
5.9 KiB
YAML
103 lines
5.9 KiB
YAML
name: Inspect deployment
|
|
|
|
# Manual-only: report what the DeepSeek Harness container on the runner host
|
|
# (the Unraid box) is actually running — image reference and digest, whether
|
|
# that digest is one this repo published, recent logs, how the dsh profile in
|
|
# the state volume is composed (bundle layers, user patch layers, profile-local
|
|
# plugins), and whether dsh/pnpm are on PATH inside it. Read-only: nothing is
|
|
# changed, the state volume is mounted read-only, credential files are never
|
|
# read, and any key/token/secret-looking value in a patch file is redacted.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
container:
|
|
description: Container name to inspect
|
|
default: Deepseek
|
|
|
|
jobs:
|
|
inspect:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Locate container
|
|
run: |
|
|
set -eu
|
|
C="${{ inputs.container || 'Deepseek' }}"
|
|
if ! docker inspect "$C" > /dev/null 2>&1; then
|
|
echo "::error::no container named $C on this host"
|
|
echo "== containers on this host whose image mentions deepseek =="
|
|
docker ps -a --format '{{.Names}} {{.Image}} {{.Status}}' | grep -i deepseek || echo "(none)"
|
|
exit 1
|
|
fi
|
|
echo "== $C =="
|
|
docker inspect --format 'image ref: {{.Config.Image}}' "$C"
|
|
docker inspect --format 'image id: {{.Image}}' "$C"
|
|
docker inspect --format 'state: {{.State.Status}} (started {{.State.StartedAt}}, restarts {{.RestartCount}}, exit {{.State.ExitCode}})' "$C"
|
|
docker inspect --format 'entrypoint: {{json .Config.Entrypoint}}' "$C"
|
|
docker inspect --format 'cmd: {{json .Config.Cmd}}' "$C"
|
|
docker inspect --format 'user: {{json .Config.User}}' "$C"
|
|
docker inspect --format 'mounts: {{range .Mounts}}{{.Source}} -> {{.Destination}}; {{end}}' "$C"
|
|
docker inspect --format 'env: {{range .Config.Env}}{{.}} {{end}}' "$C" | sed -E 's/(KEY|TOKEN|SECRET|PASSWORD)=[^ ]*/\1=<redacted>/g'
|
|
IMG=$(docker inspect --format '{{.Image}}' "$C")
|
|
echo "image digests: $(docker image inspect --format '{{json .RepoDigests}}' "$IMG")"
|
|
echo "image labels: $(docker image inspect --format '{{json .Config.Labels}}' "$IMG" | grep -o '"dev.milesward[^,}]*' | tr '\n' ' ' || true)"
|
|
echo "image created: $(docker image inspect --format '{{.Created}}' "$IMG")"
|
|
|
|
- name: Recent container logs
|
|
run: |
|
|
set -eu
|
|
C="${{ inputs.container || 'Deepseek' }}"
|
|
echo "== last 120 log lines of $C (stack frames collapsed) =="
|
|
docker logs --tail 120 "$C" 2>&1 | grep -v -E '^[[:space:]]+at ' || true
|
|
|
|
- name: Profile composition in the state volume
|
|
run: |
|
|
set -eu
|
|
C="${{ inputs.container || 'Deepseek' }}"
|
|
IMG=$(docker inspect --format '{{.Image}}' "$C")
|
|
STATE=$(docker inspect --format '{{range .Mounts}}{{if eq .Destination "/home/node/.dsh"}}{{.Source}}{{end}}{{end}}' "$C")
|
|
if [ -z "$STATE" ]; then
|
|
echo "nothing mounted at /home/node/.dsh — state lives inside the container"
|
|
exit 0
|
|
fi
|
|
echo "state volume: $STATE (mounted read-only below)"
|
|
docker run --rm --entrypoint sh -v "$STATE:/state:ro" "$IMG" -c '
|
|
cd /state || exit 1
|
|
echo "== layout (depth 3; sessions, caches, stores, node_modules elided) =="
|
|
find . -maxdepth 3 -not -path "*/node_modules*" -not -path "./sessions*" -not -path "./.pnpm*" -not -path "./.cache*" -not -path "./.state*" -not -path "./.config*" -not -path "./attachments*" -not -path "./storages/*" | sort
|
|
for f in profiles/web/package.json profiles/web/cordis.patch.yml cordis.patch.yml profiles/web/cordis.yml profiles/web/pnpm-workspace.yaml; do
|
|
if [ ! -f "$f" ]; then echo "== $f: absent =="; continue; fi
|
|
echo "== $f ($(wc -c < "$f") bytes) =="
|
|
sed -E "s/^([[:space:]]*[^:#]*(key|token|secret|password|authorization)[^:]*:).*/\1 <redacted>/I" "$f"
|
|
echo
|
|
done
|
|
echo "== profiles/web/node_modules =="
|
|
if [ -d profiles/web/node_modules ]; then
|
|
ls -la profiles/web/node_modules
|
|
ls -d profiles/web/node_modules/@*/* 2>/dev/null || true
|
|
else
|
|
echo "(none)"
|
|
fi
|
|
echo "== profile-local packages declaring a dsh bundle layer, and the rows their patch inserts =="
|
|
for p in profiles/web/node_modules/*/package.json profiles/web/node_modules/@*/*/package.json; do
|
|
[ -f "$p" ] || continue
|
|
grep -q "\"bundle\"" "$p" || continue
|
|
echo "-- $p: $(grep -o "\"version\": *\"[^\"]*\"" "$p" | head -1)"
|
|
grep -n -E "^[[:space:]]*-[[:space:]]*(id|insert):" "$(dirname "$p")"/*.yml 2>/dev/null | head -30 || true
|
|
done
|
|
echo "== end =="
|
|
'
|
|
|
|
- name: Probe tooling inside the running container
|
|
run: |
|
|
set -eu
|
|
C="${{ inputs.container || 'Deepseek' }}"
|
|
if [ "$(docker inspect --format '{{.State.Status}}' "$C")" != running ]; then
|
|
echo "$C is not running ($(docker inspect --format '{{.State.Status}}' "$C")) — probing the image instead of the container"
|
|
IMG=$(docker inspect --format '{{.Image}}' "$C")
|
|
docker run --rm --entrypoint sh "$IMG" -c 'echo "dsh: $(command -v dsh || echo MISSING) $(dsh --version 2>/dev/null)"; echo "pnpm: $(command -v pnpm || echo MISSING) $(pnpm --version 2>/dev/null)"; echo "node: $(node --version)"'
|
|
exit 0
|
|
fi
|
|
echo "== inside $C =="
|
|
docker exec "$C" sh -c 'echo "uid=$(id -u) HOME=$HOME PATH=$PATH"; echo "dsh: $(command -v dsh || echo MISSING) $(dsh --version 2>/dev/null)"; echo "pnpm: $(command -v pnpm || echo MISSING) $(pnpm --version 2>/dev/null)"; echo "node: $(node --version)"; ls -la /usr/local/bin | grep -E "dsh|pnpm" || true'
|