diff --git a/.gitea/workflows/agent.yml b/.gitea/workflows/agent.yml index 777c142..fb99fae 100644 --- a/.gitea/workflows/agent.yml +++ b/.gitea/workflows/agent.yml @@ -130,22 +130,25 @@ jobs: }' > ~/.config/opencode/opencode.json echo "opencode config (secrets masked):"; cat ~/.config/opencode/opencode.json - - name: Set up read-only SSH alias `node1` (swarm diagnostics) - # Writes the deploy key + an SSH config alias so the agent can run - # `ssh node1 ` (matches the homelab opencode.json allowlist). - # No-ops cleanly when the swarm secrets are absent — this shared workflow - # runs in repos that don't have SWARM_HOST/SWARM_USER/SSH_PRIV_KEY, and - # must not fail there. Secrets are passed via env (never inlined in shell). + - name: Set up read-only SSH alias `node1` (+ opencode skill so the agent actually knows about it) + # 1) Writes the deploy key + an SSH config alias so the agent can run + # `ssh node1 ` (matches the homelab opencode.json allowlist). + # 2) Emits a `node1-ssh` opencode Skill file under ~/.config/opencode/skills/ so any + # downstream repo's dev agent discovers this capability via OpenCode's skill registry + # rather than having to trial against the permission allowlist. Only emitted when the + # swarm plumbing is actually wired for that caller (SWARM_HOST/SWARM_USER/SSH_PRIV_KEY). + # All three secrets are passed via env and never inlined into shell — this shared workflow + # runs in repos that don't have them and must not fail there. env: SWARM_HOST: ${{ secrets.SWARM_HOST }} SWARM_USER: ${{ secrets.SWARM_USER }} SSH_PRIV_KEY: ${{ secrets.SSH_PRIV_KEY }} run: | if [ -z "$SWARM_HOST" ] || [ -z "$SWARM_USER" ] || [ -z "$SSH_PRIV_KEY" ]; then - echo "swarm secrets not set in this repo — skipping node1 SSH alias" + echo "swarm secrets not set in this repo — skipping node1 SSH alias + skill" exit 0 fi - mkdir -p ~/.ssh && chmod 700 ~/.ssh + mkdir -p ~/.ssh ~/.config/opencode/skills/node1-ssh && chmod 700 ~/.ssh ~/.config/opencode/skills/node1-ssh # Write the private key with 600 perms; never echo its contents. printf '%s\n' "$SSH_PRIV_KEY" > ~/.ssh/agent_node1 chmod 600 ~/.ssh/agent_node1 @@ -163,6 +166,55 @@ jobs: chmod 600 ~/.ssh/config echo "node1 SSH alias configured (host=$SWARM_HOST user=$SWARM_USER)" + # Emit a reusable opencode Skill that surfaces the capability to downstream agents. + # OpenCode's skill tool registers it via the block, so any dev agent + # can discover "I am allowed to ssh node1" without trial-and-error against the allowlist. + cat > ~/.config/opencode/skills/node1-ssh/SKILL.md <<'SKILLET' + --- + name: node1-ssh + description: Read-only diagnostics on the swarm node — use when debugging a deploy or checking a running service. + domains: [swarm] + tags: [ssh, swarm, diagnostics, docker] + --- + + # `node1-ssh` Skill + + Use this skill to run read-only commands against **node1** (the Docker Swarm host) when: + - A deploy failed and you need to inspect running services. + - You need to see a service's logs for debugging. + - An agent on homelab can't be reached and you want to check the stack from another node. + + ## How it works + + Commands run via `ssh node1 `. The SSH alias is set up in `${HOME}/.ssh/config` + during this workflow (only when swarm secrets are configured for the caller repo). + Read access is enforced by the homelab repo's own OpenCode allowlist: **no other ssh hosts or any write commands on node1 are permitted**. + + ## Allowed commands + + The following read-only commands work via `ssh node1`. Wrap your command in single quotes and escape any `$` signs used inside the remote shell. + + - List services: `docker service ls` + - Service details: `docker service ps [--format ...]` + - View logs: `docker service logs [--tail N] [--since 24h] [--timestamps]` + - Network info: `docker network ls`, `docker network inspect ` + - Volumes: `docker volume ls [-f dangling=true]` + - Swarm nodes: `docker node ls`, `docker node inspect self`, `docker node ps --host node1` + + The same read-only constraint applies — write commands on node1 are rejected by the allowlist even if they appear in other allowlists. + + ## Example + + > The frontend returned a 5xx after deploy #47. + > + > Action: + > ``` + > ssh node1 "docker service logs --tail 100 --timestamps frontend" + > ``` + SKILLET + chmod -R o=rX ~/.config/opencode/skills/node1-ssh + echo "opencode skill node1-ssh installed ($(wc -l < ~/.config/opencode/skills/node1-ssh/SKILL.md) lines)" + - name: Inspect / fetch image attachments (download only for vision agents) id: imgs env: