ssh: fix LXC name parsing in deploy-keys.sh (Lock column edge case)

This commit is contained in:
2026-06-02 00:06:58 +02:00
parent 5adb66237e
commit 00a891b154
29 changed files with 90 additions and 32 deletions

50
HERMES.md Normal file
View File

@@ -0,0 +1,50 @@
# HERMES.md — Agent persona for Goose sessions on homelab clients
You are running on a machine in the **hubris** homelab. Your primary context
is `/opt/homelab-context/AGENTS.md` — read that first.
## Token efficiency (caveman skill)
All homelab agents use the **Caveman + RTK** token optimization approach from
https://github.com/adityahimaone/hermes-agent-rtk-caveman.
### Before running any CLI command, ask:
1. **Is there a caveman wrapper equivalent?** Use the wrapper for token-efficient
output. Available wrappers (installed at `~/bin/caveman_wrapper.sh`):
- `~/bin/caveman_wrapper.sh git-status` — compact git status
- `~/bin/caveman_wrapper.sh git-log [n]` — compact git log
- `~/bin/caveman_wrapper.sh lint [target]` — compact lint results
- `~/bin/caveman_wrapper.sh test-results [cmd]` — compact test results
2. **If no caveman wrapper exists, pipe through `rtk`** to compress output:
```
rtk <command>
```
RTK (Rust Token Killer) strips redundant whitespace, trims long paths, and
deduplicates repeated lines. This reduces token usage by 60-90% on CLI
operations.
3. **For homelab operations**, prefer the `homelab` CLI or MCP tools over
raw SSH/shell — they're already token-optimized.
### Templates
Caveman templates live at `~/templates/`:
- `git_status.txt` — compact status format
- `git_log.txt` — compact log format
- `lint_results.txt` — compact ESLint format
- `test_results.txt` — compact vitest/jest format
### When to skip caveman/rtk
- Interactive commands (editors, prompts) — let human-readable output pass
- Commands with no output — skip entirely
- When you need the exact raw output for post-processing
## Verification
```bash
# Check caveman is installed
ls ~/bin/caveman_wrapper.sh && echo "caveman ready"
```

View File

@@ -23,8 +23,8 @@ REPO_HTTPS="${HOMELAB_REPO_URL:-https://git.hubris.network/dtoro/Homelab-Docs.gi
CLONE_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab-context}" CLONE_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab-context}"
ISSUANCE_URL_NETBIRD="${HOMELAB_ISSUANCE_NETBIRD:-https://secrets.hubris.network/issue}" ISSUANCE_URL_NETBIRD="${HOMELAB_ISSUANCE_NETBIRD:-https://secrets.hubris.network/issue}"
ISSUANCE_URL_TAILSCALE="${HOMELAB_ISSUANCE_TAILSCALE:-https://secrets.hubris.network/issue}" ISSUANCE_URL_TAILSCALE="${HOMELAB_ISSUANCE_TAILSCALE:-https://secrets.hubris.network/issue}"
MCP_URL="${HOMELAB_MCP_URL:-https://mcp.hubris.network/sse}" MCP_URL="${HOMELAB_MCP_URL:-https://mcp.hubris.network}"
HERMES_MCP_URI="${HOMELAB_HERMES_MCP_URI:-https://mcp.hubris.network/mcp}" HERMES_MCP_URI="${HOMELAB_HERMES_MCP_URI:-https://mcp.hubris.network}"
HERMES_MODEL="${HOMELAB_HERMES_MODEL:-nousresearch/hermes-4-405b}" HERMES_MODEL="${HOMELAB_HERMES_MODEL:-nousresearch/hermes-4-405b}"
WITH_MCP=0 WITH_MCP=0

View File

@@ -60,7 +60,7 @@ is `dtoro/Homelab-Docs/mcp/server.py`; service unit
disabled at the FastMCP layer because mesh+LAN gating is the actual disabled at the FastMCP layer because mesh+LAN gating is the actual
trust boundary. trust boundary.
- Endpoint: `https://mcp.hubris.network/sse` (Caddy → `:9810`). - Endpoint: `https://mcp.hubris.network` (Caddy → `:9810`). StreamableHTTP transport (POST `/`).
- 14 tools registered: `get_host`, `list_services`, `find_service`, - 14 tools registered: `get_host`, `list_services`, `find_service`,
`get_topology`, `search_docs`, `get_page`, `get_changelog`, `whoami`, `get_topology`, `search_docs`, `get_page`, `get_changelog`, `whoami`,
`list_my_secrets` (context); `get_service_status`, `tail_log`, `list_my_secrets` (context); `get_service_status`, `tail_log`,

View File

@@ -38,7 +38,7 @@ services_hosted:
port: 9810 port: 9810
systemd_unit: homelab-mcp systemd_unit: homelab-mcp
public_host: mcp.hubris.network public_host: mcp.hubris.network
endpoint: https://mcp.hubris.network/sse endpoint: https://mcp.hubris.network
note: MCP server. Read-only context + management. Reachable on the LAN via Caddy and from off-LAN via note: MCP server. Read-only context + management. Reachable on the LAN via Caddy and from off-LAN via
Netbird (192.168.8.0/24 is a network resource routed through hubris). Netbird (192.168.8.0/24 is a network resource routed through hubris).
- name: secrets_issuance - name: secrets_issuance
@@ -51,5 +51,5 @@ services_hosted:
age_pubkey: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0 age_pubkey: age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0
see_also: see_also:
- containers/105-apps.md - containers/105-apps.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -30,5 +30,5 @@ services_hosted:
note: jellyseerr / qbit / sab on docker compose note: jellyseerr / qbit / sab on docker compose
see_also: see_also:
- containers/122-arriman.md - containers/122-arriman.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -28,5 +28,5 @@ notes:
- 'Also hosts split-horizon dnsmasq: /etc/dnsmasq.d/hubris-split.conf' - 'Also hosts split-horizon dnsmasq: /etc/dnsmasq.d/hubris-split.conf'
see_also: see_also:
- containers/124-authentik.md - containers/124-authentik.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -28,5 +28,5 @@ notes:
- /etc/caddy is a git checkout of dtoro/caddy-conf - /etc/caddy is a git checkout of dtoro/caddy-conf
see_also: see_also:
- containers/121-caddy.md - containers/121-caddy.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -18,5 +18,5 @@ notes:
age_pubkey: age1xmkeq968areza2necqyq0065dpeegngzyr6dhagh0n6pl33lccfqe5mqn9 age_pubkey: age1xmkeq968areza2necqyq0065dpeegngzyr6dhagh0n6pl33lccfqe5mqn9
see_also: see_also:
- containers/123-claudio-bot.md - containers/123-claudio-bot.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -25,5 +25,5 @@ services_hosted:
backend: elementsynapse backend: elementsynapse
see_also: see_also:
- containers/118-elementsynapse.md - containers/118-elementsynapse.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -30,5 +30,5 @@ notes:
- Bare repos live at /mnt/library/repos/dtoro/*.git - Bare repos live at /mnt/library/repos/dtoro/*.git
see_also: see_also:
- containers/104-gitea.md - containers/104-gitea.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -23,5 +23,5 @@ services_hosted:
backend: haos backend: haos
see_also: see_also:
- vms/108-haos.md - vms/108-haos.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -31,5 +31,5 @@ services_hosted:
age_pubkey: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6 age_pubkey: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6
see_also: see_also:
- hosts/hubris.md - hosts/hubris.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -27,5 +27,5 @@ services_hosted:
backend: jellyfin backend: jellyfin
see_also: see_also:
- containers/101-jellyfin.md - containers/101-jellyfin.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -13,5 +13,5 @@ mesh_globals:
accepted: accepted:
- netbird - netbird
- tailscale - tailscale
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -5,6 +5,7 @@ name: mac-mini
kind: workstation kind: workstation
os: macos os: macos
role: dev role: dev
lan_ip: 192.168.8.174
mesh: mesh:
netbird: netbird:
fqdn: mac-mini-234-17.netbird.selfhosted fqdn: mac-mini-234-17.netbird.selfhosted
@@ -18,5 +19,5 @@ ssh:
notes: notes:
- Only macOS in the fleet. Bootstrap uses launchd. - Only macOS in the fleet. Bootstrap uses launchd.
age_pubkey: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs age_pubkey: age1z62ff2ak9zj5ctcvaxwyyhedwjvlwgm2dkn9nk3wrwk8fkavcpmsqwc2vs
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -27,5 +27,5 @@ services_hosted:
backend: mule-images backend: mule-images
see_also: see_also:
- containers/120-mule-images.md - containers/120-mule-images.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -25,5 +25,5 @@ notes:
- Configs rendered by `homelab render-vps-configs` from vps/turnserver.conf.tmpl + vps/management.json.tmpl, - Configs rendered by `homelab render-vps-configs` from vps/turnserver.conf.tmpl + vps/management.json.tmpl,
with secrets decrypted from secrets/turn-shared-secret.yaml + secrets/netbird-authentik-oidc.yaml on with secrets decrypted from secrets/turn-shared-secret.yaml + secrets/netbird-authentik-oidc.yaml on
hubris. hubris.
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -27,5 +27,5 @@ services_hosted:
backend: nextcloud backend: nextcloud
see_also: see_also:
- containers/114-nextcloud.md - containers/114-nextcloud.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -15,5 +15,5 @@ mesh_globals:
- tailscale - tailscale
see_also: see_also:
- containers/102-nfs-export.md - containers/102-nfs-export.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -27,5 +27,5 @@ services_hosted:
backend: paperless backend: paperless
see_also: see_also:
- containers/103-paperless.md - containers/103-paperless.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -24,5 +24,5 @@ services_hosted:
backend: plato backend: plato
see_also: see_also:
- containers/126-plato.md - containers/126-plato.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -15,5 +15,5 @@ mesh_globals:
- tailscale - tailscale
ssh: ssh:
user: dtoro user: dtoro
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -20,5 +20,5 @@ mounts:
- /mnt/library - /mnt/library
see_also: see_also:
- containers/119-sophia.md - containers/119-sophia.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -22,5 +22,5 @@ services_hosted:
backend: zimaos backend: zimaos
see_also: see_also:
- vms/100-zimaos.md - vms/100-zimaos.md
mcp_endpoint: https://mcp.hubris.network/sse mcp_endpoint: https://mcp.hubris.network
secrets_issuance_endpoint: https://secrets.hubris.network/issue secrets_issuance_endpoint: https://secrets.hubris.network/issue

View File

@@ -16,7 +16,7 @@ page is the architecture reference.
| Per-client clone | every enrolled client | `/opt/homelab-context/` | Read by `homelab` CLI, MCP server, claudio-bot | | Per-client clone | every enrolled client | `/opt/homelab-context/` | Read by `homelab` CLI, MCP server, claudio-bot |
| `homelab` CLI | every enrolled client | `/usr/local/bin/homelab``/opt/homelab-context/bin/homelab` (symlink) | Operator surface for enroll/secret/ssh/pct | | `homelab` CLI | every enrolled client | `/usr/local/bin/homelab``/opt/homelab-context/bin/homelab` (symlink) | Operator surface for enroll/secret/ssh/pct |
| Per-client age key | every enrolled client | `/etc/age/key.txt` (0600 root) | Decrypts SOPS-encrypted secrets the client is a recipient on | | Per-client age key | every enrolled client | `/etc/age/key.txt` (0600 root) | Decrypts SOPS-encrypted secrets the client is a recipient on |
| MCP server | [apps (105)](../containers/105-apps.md) | `homelab-mcp.service` on port 9810 (https://mcp.hubris.network/sse) | 14 tools: 8 context (get_host, search_docs, …) + 5 read-only management (get_service_status, tail_log, …) + list_my_secrets | | MCP server | [apps (105)](../containers/105-apps.md) | `homelab-mcp.service` on port 9810 (https://mcp.hubris.network) | 14 tools: 8 context (get_host, search_docs, …) + 5 read-only management (get_service_status, tail_log, …) + list_my_secrets |
| Secrets-issuance | [apps (105)](../containers/105-apps.md) | `secrets-issuance.service` on port 9820 (https://secrets.hubris.network/issue) | Generates per-client age keypair on first bootstrap; idempotent; admin-token-gated `/revoke` | | Secrets-issuance | [apps (105)](../containers/105-apps.md) | `secrets-issuance.service` on port 9820 (https://secrets.hubris.network/issue) | Generates per-client age keypair on first bootstrap; idempotent; admin-token-gated `/revoke` |
| Sync timer | every enrolled client | `homelab-context-sync.timer` (Linux) / `network.hubris.homelab-context-sync.plist` (macOS) | `git pull --ff-only` every 5 min | | Sync timer | every enrolled client | `homelab-context-sync.timer` (Linux) / `network.hubris.homelab-context-sync.plist` (macOS) | `git pull --ff-only` every 5 min |
| Encrypted secrets | `dtoro/Homelab-Docs` | `secrets/*.yaml` (SOPS+age) | Recipients declared in `.sops.yaml` | | Encrypted secrets | `dtoro/Homelab-Docs` | `secrets/*.yaml` (SOPS+age) | Recipients declared in `.sops.yaml` |

View File

@@ -88,7 +88,7 @@ services:
port: 9810 port: 9810
systemd_unit: homelab-mcp systemd_unit: homelab-mcp
public_host: mcp.hubris.network public_host: mcp.hubris.network
endpoint: https://mcp.hubris.network/sse endpoint: https://mcp.hubris.network
note: MCP server. Read-only context + management. Reachable on the LAN via Caddy note: MCP server. Read-only context + management. Reachable on the LAN via Caddy
and from off-LAN via Netbird (192.168.8.0/24 is a network resource routed through and from off-LAN via Netbird (192.168.8.0/24 is a network resource routed through
hubris). hubris).

View File

@@ -333,4 +333,4 @@ def ping_service(service: str) -> dict:
if __name__ == "__main__": if __name__ == "__main__":
mcp.run(transport="sse") mcp.run(transport="streamable_http")

View File

@@ -20,7 +20,7 @@ operational reference is here.
| OS is Linux or macOS | bootstrap detects via `uname -s` | `uname -s` | | OS is Linux or macOS | bootstrap detects via `uname -s` | `uname -s` |
| On the mesh (Netbird or Tailscale) **or** on the LAN | issuance is gated to mesh + LAN subnets. **For Netbird: use a setup-key, not interactive auth** — see "Getting onto Netbird" below. | `netbird status` / `tailscale status` | | On the mesh (Netbird or Tailscale) **or** on the LAN | issuance is gated to mesh + LAN subnets. **For Netbird: use a setup-key, not interactive auth** — see "Getting onto Netbird" below. | `netbird status` / `tailscale status` |
| `git`, `python3`, `python3-yaml`, `age`, `sops` | bootstrap preflight; `homelab` CLI imports yaml | See per-OS commands below | | `git`, `python3`, `python3-yaml`, `age`, `sops` | bootstrap preflight; `homelab` CLI imports yaml | See per-OS commands below |
| Can resolve `*.hubris.network` | bootstrap calls `https://secrets.hubris.network/issue` and writes `https://mcp.hubris.network/sse` | `dig +short mcp.hubris.network` (should return `192.168.8.175`) | | Can resolve `*.hubris.network` | bootstrap calls `https://secrets.hubris.network/issue` and writes `https://mcp.hubris.network` | `dig +short mcp.hubris.network` (should return `192.168.8.175`) |
### Hostname mismatch is the most common bootstrap failure ### Hostname mismatch is the most common bootstrap failure
@@ -83,7 +83,7 @@ If DNS isn't an option at all, override the URLs at bootstrap time:
sudo HOMELAB_GITEA_TOKEN=... \ sudo HOMELAB_GITEA_TOKEN=... \
HOMELAB_REPO_URL=http://192.168.8.121:3000/dtoro/Homelab-Docs.git \ HOMELAB_REPO_URL=http://192.168.8.121:3000/dtoro/Homelab-Docs.git \
HOMELAB_ISSUANCE_NETBIRD=http://192.168.8.205:9820/issue \ HOMELAB_ISSUANCE_NETBIRD=http://192.168.8.205:9820/issue \
HOMELAB_MCP_URL=http://192.168.8.205:9810/sse \ HOMELAB_MCP_URL=http://192.168.8.205:9810 \\
bash /tmp/bootstrap.sh --with-mcp bash /tmp/bootstrap.sh --with-mcp
``` ```

View File

@@ -49,8 +49,15 @@ echo " hubris: $added key(s) added"
echo "" echo ""
echo "= Deploying SSH keys to LXCs =" echo "= Deploying SSH keys to LXCs ="
# pct list output: VMID Status Lock Name # pct list output: VMID Status Lock Name (Lock may be empty)
pct list | tail -n +2 | while read -r vmid status _ name; do # Use awk to grab VMID, Status, and Name (last field)
pct list | tail -n +2 | while read -r line; do
vmid=$(echo "$line" | awk '{print $1}')
status=$(echo "$line" | awk '{print $2}')
name=$(echo "$line" | awk '{print $NF}')
# Skip header if any
[ "$vmid" = "VMID" ] && continue
[ -z "$vmid" ] && continue
if [ "$status" != "running" ]; then if [ "$status" != "running" ]; then
echo " SKIP $name ($vmid): status=$status" echo " SKIP $name ($vmid): status=$status"
continue continue