diff --git a/.agents/skills/homelab-apply/SKILL.md b/.agents/skills/homelab-apply/SKILL.md new file mode 100644 index 0000000..70d3eb1 --- /dev/null +++ b/.agents/skills/homelab-apply/SKILL.md @@ -0,0 +1,67 @@ +--- +name: homelab-apply +description: Deploy changes to the valhalla homelab. For application stacks (foundry, media, owncloud, etc.), commit and push — Portainer git-polls every 5 min. For the management plane (portainer, vault, watchtower, runner), Caddyfile, or .env on the server, use the apply-compose.ps1 helper. Use after editing any homelab config to get it deployed. +--- + +# homelab-apply + +The repo is canonical. Different config layers deploy through different channels: + +| Edit | Deployment channel | Lag | +|---|---|---| +| `stacks//docker-compose.yml` or `stacks//.env` | **git push** → Portainer polls every 5 min and redeploys that stack | up to 5 min | +| `Caddyfile` | git push triggers `.github/workflows/deploy.yml` → runner pushes file + `docker exec caddy caddy reload` | seconds (if workflow is enabled) | +| `portainer-compose.yml` (mgmt plane: portainer, vault, watchtower, runner) | `apply-compose.ps1 -Portainer` — Portainer can't manage itself | seconds | +| `vault.hcl` | `apply-compose.ps1 -Portainer` (bundled with mgmt plane push) | seconds | +| `.env` at `~/valhalla-lab/.env` (only used by mgmt plane + vault unseal) | `apply-compose.ps1 -EnvFile` | seconds | + +## For application stack edits — just push + +```powershell +git add stacks/media/.env +git commit -m "..." +git push +``` + +Portainer's per-stack auto-update will pick up the new commit within 5 minutes and redeploy. Watch progress: + +```powershell +ssh -o BatchMode=yes ginnoir@valhalla "docker compose -f /data/compose//stacks//docker-compose.yml ps" +``` + +Or just `docker ps` and filter by name. To force an immediate redeploy without waiting, trigger it from the Portainer UI ("Update the stack"). + +## For Caddyfile edits — push and wait for the runner + +Workflow is gated by `paths: [Caddyfile, .github/workflows/deploy.yml]` so only Caddyfile commits trigger it. + +If you don't want to wait (or the workflow is disabled), the manual fallback is: + +```powershell +powershell -File apply-compose.ps1 -Caddy +``` + +This `scp`s the local Caddyfile to `/config/caddy/Caddyfile` and runs `docker exec caddy caddy reload --config /etc/caddy/Caddyfile`. Invalid Caddyfile → reload fails, old config keeps running; read the output. + +## For management-plane edits + +```powershell +powershell -File apply-compose.ps1 -Portainer # portainer-compose.yml + vault.hcl + up -d +powershell -File apply-compose.ps1 -EnvFile # ~/valhalla-lab/.env only +powershell -File apply-compose.ps1 -VaultUnseal # unseal vault after restart (keys from .env) +``` + +No flags = `-EnvFile` + `-Caddy` (the two ad-hoc pushes you'd want for a quick manual deploy). + +## Verify after applying + +```powershell +ssh -o BatchMode=yes ginnoir@valhalla "docker ps --format 'table {{.Names}}\t{{.Status}}'" +ssh -o BatchMode=yes ginnoir@valhalla "docker logs --tail 50 " +``` + +## Notes + +- Compose is **v2** (`docker compose`, space). +- There is no monolithic `dc` alias anymore — each stack has its own compose dir under `/data/compose//stacks//`. Address services by container name with `docker logs`, `docker exec`, etc. — `docker compose ...` only makes sense when you explicitly point at a per-stack compose file. +- A **GateGuard hook** blocks the first `Bash` and every `Write`/`Edit` until you state the user request + what the operation does/affects. State them, retry. diff --git a/.agents/skills/homelab-ssh/SKILL.md b/.agents/skills/homelab-ssh/SKILL.md new file mode 100644 index 0000000..e4517b4 --- /dev/null +++ b/.agents/skills/homelab-ssh/SKILL.md @@ -0,0 +1,87 @@ +--- +name: homelab-ssh +description: 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 Ubuntu host reachable at `ssh ginnoir@valhalla` (key-based auth; resolves and works from this Windows host directly). + +## Connect (non-interactive / safe for automation) + +Always pass `-o BatchMode=yes` so a missing key or password prompt fails fast instead of hanging: + +```powershell +ssh -o BatchMode=yes -o ConnectTimeout=8 ginnoir@valhalla "" +``` + +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`: + +```powershell +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 " +ssh -o BatchMode=yes ginnoir@valhalla "docker restart " +ssh -o BatchMode=yes ginnoir@valhalla "docker exec " +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//stacks//docker-compose.yml` (Portainer chooses the `` when the stack is created). To inspect a stack's resolved config or run a one-off compose op: + +```powershell +# Pick the stack file from its label +ssh ginnoir@valhalla "docker inspect --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: + +```powershell +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//` — per-service persisted config (SSD tier: configs + Postgres + Redis) +- `/storage1/labdata//` — high-density data on the ZFS pool (MinIO buckets, registry layers, gitea repos) +- `/storage1/` — media library (Books, Tabletop, etc.) +- `/data/compose//stacks//` — 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. +- `` (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. diff --git a/.agents/skills/vault/SKILL.md b/.agents/skills/vault/SKILL.md new file mode 100644 index 0000000..da0ea3c --- /dev/null +++ b/.agents/skills/vault/SKILL.md @@ -0,0 +1,93 @@ +--- +name: vault +description: Read and write notes in ginnoir's self-hosted Obsidian vault via the Obsidian MCP server. Use when loading persistent context for a session, saving new context, writing a note, or querying vault contents. The vault is the canonical persistent-context store across Codex sessions. +--- + +# vault + +Obsidian vault accessed via the **`mcp__obsidian__*` MCP tools**. Do not attempt to connect to CouchDB directly — use MCP only. + +The MCP server is configured in `~/.mcp.json` and is available in every Codex session. If the tools are listed as deferred, load them with `ToolSearch` before calling. + +## Core tools + +| Tool | Use | +|---|---| +| `mcp__obsidian__vault_list` | List files/dirs in a directory (omit path for vault root) | +| `mcp__obsidian__vault_read` | Read a file's full content + metadata (tags, frontmatter, links, backlinks) | +| `mcp__obsidian__vault_write` | Create or overwrite a file | +| `mcp__obsidian__vault_patch` | Append or patch a section without a full rewrite | +| `mcp__obsidian__vault_delete` | Delete a file | +| `mcp__obsidian__vault_move` | Move or rename a file | +| `mcp__obsidian__search_simple` | Full-text search across the vault | +| `mcp__obsidian__search_query` | Advanced query search | +| `mcp__obsidian__tag_list` | List all tags | + +## Load context at session start + +``` +1. mcp__obsidian__vault_read path="Codex/Context.md" +2. Follow [[wikilinks]] to read specific notes as needed +``` + +`Codex/Context.md` is the master index. It links to all other context notes under `Codex/`. + +## Read a note + +``` +mcp__obsidian__vault_read path="Codex/Homelab.md" +``` + +Returns: `content` (full markdown), `tags`, `frontmatter`, `links`, `backlinks`, `stat`. + +To read only a section: +``` +mcp__obsidian__vault_read path="Codex/Services.md" targetType="heading" target="BookStack" +``` + +## Write a note + +``` +mcp__obsidian__vault_write path="Codex/MyNote.md" content="# Title\n\nContent here." +``` + +Creates parent directories automatically. Overwrites without warning. + +## Append to a note + +``` +mcp__obsidian__vault_patch path="Codex/Context.md" ... +``` + +Use `vault_patch` when adding a section to an existing note to avoid rewriting the whole file. + +## Search the vault + +``` +mcp__obsidian__search_simple query="portainer env vars" +``` + +## Vault structure + +``` +Codex/ + Context.md — master index, session start guide, quick nav + Homelab.md — topology, stacks, networks, paths + Deploy.md — deployment channels + SSH.md — SSH ops, container management + Portainer.md — env interpolation gotcha, new stack registration + Services.md — per-service quirks (BookStack, Caddy, secrets policy) + ROM Library.md — RomM, igir, Pokémon romhacks, EmuDeck tree + Monitoring.md — Uptime Kuma v2 socket technique + Vault.md — this stack's infrastructure, MCP access +testing.md — original connectivity test note +Pokémon ROM Hacks/ — Pokémon romhack KB in the live vault; catalog + build + scripts live in the standalone Documents\pokemon repo +``` + +## Load tool schemas (if deferred) + +If `mcp__obsidian__vault_read` is not yet callable, run: +``` +ToolSearch query="select:mcp__obsidian__vault_list,mcp__obsidian__vault_read,mcp__obsidian__vault_write" +``` diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..c0b24b8 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,17 @@ +[shell_environment_policy] +inherit = "core" + +[shell_environment_policy.set] +ECC_GATEGUARD = "off" + +[mcp_servers.portainer] +command = "uvx" +args = [ + "--from", + "mcp-portainer~=2.42.0", + "mcp-portainer", +] + +[mcp_servers.portainer.env] +PORTAINER_URL = "https://portainer.ginnoir.com" +PORTAINER_API_KEY = "ptr_Ep2pXkdoT6y2Pn8AAt4gRdUr7NFMyE06S8WYR+ciNq4=" diff --git a/.gitignore b/.gitignore index 7458e91..73e0fde 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,9 @@ Thumbs.db *.tmp *.log +# Loose scratch scripts agents drop at the .agents/ root (RomM/igir DB one-offs etc.). +# Keep .agents/skills/ tracked; ignore stray top-level scripts. +.agents/*.py + # Personal, per-machine Claude Code overrides (not team config) .claude/settings.local.json