Files
homelabstack/.agents/skills/homelab-ssh/SKILL.md
T
ginnoirandClaude Opus 5 28455a6787 docs: correct valhalla OS in the remaining agent-facing docs
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>
2026-08-05 12:10:32 -05:00

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 via apply-compose.ps1 -Portainer)
  • /config/caddy/Caddyfile — Caddy config (mounted into caddy at /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-apply skill (per-domain stacks → git push; mgmt plane / Caddyfile → apply-compose.ps1).
  • htpc-download-box_* and valhalla-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.