Same fix as 317a424, applied to the files an agent actually reads before
touching the host: AGENTS.md and both copies of the homelab-ssh skill.
They all claimed a headless Ubuntu server; valhalla is EndeavourOS (Arch).
Also record two things that cost time this session: there is no apt/apt-get
on the host, and ginnoir has passwordless sudo (needed for anything writing
to root-owned paths like /config/caddy/Caddyfile).
Historical records under plans/ and docs/superpowers/plans/ are left alone
on purpose — they document what was true when the work was done.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.0 KiB
5.0 KiB
name, description
| name | description |
|---|---|
| homelab-ssh | Connect to and run commands on the valhalla homelab server (ginnoir@valhalla) over SSH. Use when inspecting the live per-domain stacks, tailing logs, restarting a container, reloading Caddy, or checking container/network/volume state. There is no `dc` alias anymore — services are addressed by container name with plain `docker` commands. |
homelab-ssh
The production homelab runs on a headless EndeavourOS (Arch) host reachable at ssh ginnoir@valhalla (key-based auth; resolves and works from this Windows host directly). Package manager is pacman — there is no apt/apt-get. ginnoir has passwordless sudo.
Connect (non-interactive / safe for automation)
Always pass -o BatchMode=yes so a missing key or password prompt fails fast instead of hanging:
ssh -o BatchMode=yes -o ConnectTimeout=8 ginnoir@valhalla "<command>"
The first Bash of a session is gated by a GateGuard hook — state the user request + what the command does, then retry.
The estate is now 11 Portainer-managed stacks + the management plane
| Stack (Portainer project name) | Containers |
|---|---|
proxy |
caddy |
media |
deluge, nzbget, sonarr, radarr, bazarr, prowlarr, overseerr, tautulli, qbittorrent, whisparr, stash |
foundry |
foundry, foundry2, 5etools |
owncloud |
owncloud_server, owncloud_mariadb, owncloud_redis |
resume |
resume, postgres_resume, minio_resume, chrome |
famapp |
famapp, postgres_famapp, minio_famapp |
authentik |
authentik_server, authentik_worker, postgres_authentik, redis_authentik |
notify |
ntfy, freshrss, vigilant |
monitoring |
uptime_kuma, homarr |
remote |
hbbr, hbbs |
dev |
gitea, code_server, bookstack, mariadb_bookstack, dbx, registry, postgres_gitea, postgres_plane, redis_plane, plane_minio, plane_api, plane_web, plane_worker, plane_beat, plane_create_bucket |
management plane (raw compose at ~/valhalla-lab/portainer-compose.yml, project name valhalla-lab) |
portainer, vault, github_runner, watchtower |
Common ops by container name
There's no umbrella dc alias. Use plain docker:
ssh -o BatchMode=yes ginnoir@valhalla "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Label \"com.docker.compose.project\"}}'"
ssh -o BatchMode=yes ginnoir@valhalla "docker logs --tail 200 -f <container>"
ssh -o BatchMode=yes ginnoir@valhalla "docker restart <container>"
ssh -o BatchMode=yes ginnoir@valhalla "docker exec <container> <command>"
ssh -o BatchMode=yes ginnoir@valhalla "docker exec caddy caddy reload --config /etc/caddy/Caddyfile"
Per-stack docker compose operations
Each Portainer git stack lives at /data/compose/<id>/stacks/<domain>/docker-compose.yml (Portainer chooses the <id> when the stack is created). To inspect a stack's resolved config or run a one-off compose op:
# Pick the stack file from its label
ssh ginnoir@valhalla "docker inspect <container> --format '{{ index .Config.Labels \"com.docker.compose.project.config_files\"}}'"
# Then point compose at it
ssh ginnoir@valhalla "docker compose -f /data/compose/14/stacks/owncloud/docker-compose.yml ps"
For management plane only:
ssh ginnoir@valhalla "docker compose -f ~/valhalla-lab/portainer-compose.yml --env-file ~/valhalla-lab/.env ps"
Key paths on the server
~/valhalla-lab/portainer-compose.yml+~/valhalla-lab/.env— management plane (raw compose, applied viaapply-compose.ps1 -Portainer)/config/caddy/Caddyfile— Caddy config (mounted intocaddyat/etc/caddy/Caddyfile)/config/<service>/— per-service persisted config (SSD tier: configs + Postgres + Redis)/storage1/labdata/<service>/— high-density data on the ZFS pool (MinIO buckets, registry layers, gitea repos)/storage1/— media library (Books, Tabletop, etc.)/data/compose/<id>/stacks/<domain>/— Portainer's working copy of each stack (Portainer auto-pulls these from git every 5 min)
Networks
edge— shared reverse-proxy network.caddy+ every service Caddy proxies. Anything new that needs to be public must join this.<domain>(e.g.media,authentik,dev) — per-stack private network for intra-stack traffic (app ↔ its db/cache/minio).portainer_proxy,valhalla-lab_default— management-plane networks (legacy names, kept).bridge,host,none— Docker defaults.
Notes
- Read-only exploration of
/config,/storage1, and container state is fine for answering questions. - For destructive ops (
docker rm -f, network/volume pruning), get explicit confirmation first. - To deploy config changes, use the
homelab-applyskill (per-domain stacks → git push; mgmt plane / Caddyfile →apply-compose.ps1). htpc-download-box_*andvalhalla-lab_*named volumes still exist as a data safety net from the pre-split monolith — every per-domain stack uses bind mounts now, so those orphans can be reaped once you're confident in the new persistence.