diff --git a/.sops.yaml b/.sops.yaml index a04fcc1..3976197 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -62,4 +62,14 @@ creation_rules: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6, age1duyl8mkpgu80uv934dy8q7enqjms6yvdz264hme8uryuxmvvqesq6rusq0, age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 + + - path_regex: ^secrets/openrouter-api-key\.yaml$ + # OpenRouter API key consumed by the `hermes` wrapper (bin/hermes) when + # spawning a Goose session. Recipients are any host that should run a + # Nous-Hermes agent. Add a host's age_pubkey here, then + # `sops updatekeys -y secrets/openrouter-api-key.yaml`. + # See operations/hermes-agent.md. + age: >- + age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6, + age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 # webhook noop 2026-05-20T18:16:57+02:00 diff --git a/bin/hermes b/bin/hermes new file mode 100755 index 0000000..596029a --- /dev/null +++ b/bin/hermes @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# hermes — launch a Goose session pre-wired with the homelab persona, +# OpenRouter (Nous Hermes) provider, and the homelab MCP server. +# +# See operations/hermes-agent.md for the full onboarding flow. + +set -euo pipefail + +die() { echo "hermes: $*" >&2; exit 1; } + +command -v goose >/dev/null \ + || die "goose binary not found — re-run bootstrap.sh with --with-hermes" +command -v homelab >/dev/null \ + || die "homelab CLI not found — is this client bootstrapped?" + +# Decrypt OpenRouter API key. +# `homelab secret` re-execs via sudo for non-root users (age key is 0600 root). +SECRET_YAML=$(homelab secret openrouter-api-key 2>&1) || \ + die "could not decrypt secrets/openrouter-api-key.yaml — this host probably +isn't a recipient yet. See operations/hermes-agent.md ('Granting the OpenRouter +key to a new host'). sops output: +${SECRET_YAML}" + +API_KEY=$(printf '%s' "$SECRET_YAML" | python3 -c \ + 'import sys, yaml; print(yaml.safe_load(sys.stdin)["api_key"])') + +case "$API_KEY" in + PLACEHOLDER_*|"") + die "openrouter-api-key.yaml still contains the placeholder; operator +must run \`sops secrets/openrouter-api-key.yaml\` on hubris to insert a real +\`sk-or-...\` key and push the change." ;; +esac + +export OPENROUTER_API_KEY="$API_KEY" +exec goose session "$@" diff --git a/bin/homelab b/bin/homelab index aa3ef04..ee1621c 100755 --- a/bin/homelab +++ b/bin/homelab @@ -199,6 +199,12 @@ SHARED_SECRETS = [ ("secrets/netbird-authentik-oidc.yaml", "^secrets/netbird-authentik-oidc\\.yaml$"), ] +# Secrets granted only to hosts that opt into running the Hermes agent +# (via `homelab client add --finalize-pubkey ... --with-hermes`). +HERMES_SECRETS = [ + ("secrets/openrouter-api-key.yaml", "^secrets/openrouter-api-key\\.yaml$"), +] + # -------- comment-preserving inventory.yaml edits -------- # yaml.safe_load + safe_dump round-trips strip every comment, which is fine @@ -360,11 +366,15 @@ def _add_recipient_to_sops_policy(sops_path: Path, path_regex_pattern: str, pubk return True -def _grant_shared_secrets(pubkey: str) -> None: - """Add `pubkey` to the recipient list of every shared secret + re-key.""" +def _grant_shared_secrets(pubkey: str, secrets: list[tuple[str, str]] = SHARED_SECRETS) -> None: + """Add `pubkey` to the recipient list of every listed secret + re-key. + + `secrets` defaults to SHARED_SECRETS; pass HERMES_SECRETS to grant the + Hermes-only set. + """ sops_path = CONTEXT / ".sops.yaml" env = {**os.environ, "SOPS_AGE_KEY_FILE": str(AGE_KEY)} - for rel_path, pattern in SHARED_SECRETS: + for rel_path, pattern in secrets: target = CONTEXT / rel_path if not target.exists(): print(f" skipping {rel_path}: file does not exist yet") @@ -440,11 +450,14 @@ def _remove_recipient_from_sops_policy(sops_path: Path, path_regex_pattern: str, return True -def _revoke_shared_secrets(pubkey: str) -> None: - """Remove `pubkey` from every shared-secret rule + re-key the files.""" +def _revoke_shared_secrets(pubkey: str, secrets: list[tuple[str, str]] = SHARED_SECRETS) -> None: + """Remove `pubkey` from every listed secret rule + re-key the files. + + Defaults to SHARED_SECRETS; pass HERMES_SECRETS to revoke the Hermes-only set. + """ sops_path = CONTEXT / ".sops.yaml" env = {**os.environ, "SOPS_AGE_KEY_FILE": str(AGE_KEY)} - for rel_path, pattern in SHARED_SECRETS: + for rel_path, pattern in secrets: target = CONTEXT / rel_path if not target.exists(): continue @@ -1033,7 +1046,8 @@ def cmd_client_add(args: argparse.Namespace) -> int: return subprocess.call(["sudo", "-E", sys.argv[0], "client", "add"] + ([args.name] if args.name else []) + (["--finalize-pubkey", args.finalize_pubkey] - if args.finalize_pubkey else [])) + if args.finalize_pubkey else []) + + (["--with-hermes"] if args.with_hermes else [])) name = args.name inv = inventory() if not args.finalize_pubkey: @@ -1054,6 +1068,7 @@ def cmd_client_add(args: argparse.Namespace) -> int: print(f" 2. On {name}: curl -fsSL /dtoro/Homelab-Docs/raw/main/bootstrap.sh | sudo bash") print(f" 3. bootstrap prints an age pubkey — bring it back here and run:") print(f" homelab client add {name} --finalize-pubkey ") + print(f" (append --with-hermes to also grant the Hermes agent's OpenRouter key.)") return 0 # finalize_pubkey path @@ -1065,8 +1080,13 @@ def cmd_client_add(args: argparse.Namespace) -> int: print(f"set age_pubkey for {name}") print("granting shared secrets...") _grant_shared_secrets(pubkey) + commit_subject = f"client-add: {name} (finalize age_pubkey + grant shared secrets)" + if args.with_hermes: + print("granting hermes-only secrets...") + _grant_shared_secrets(pubkey, HERMES_SECRETS) + commit_subject = f"client-add: {name} (finalize age_pubkey + grant shared + hermes secrets)" push_inventory( - f"client-add: {name} (finalize age_pubkey + grant shared secrets)", + commit_subject, extra_paths=[".sops.yaml", "secrets/"], ) print(f"finalized {name}.") @@ -1103,6 +1123,9 @@ def cmd_client_remove(args: argparse.Namespace) -> int: if pubkey: print("revoking shared secrets...") _revoke_shared_secrets(pubkey) + # Also revoke from hermes-only secrets; idempotent if the pubkey + # was never on those rules (logs a "not present" warning, no harm). + _revoke_shared_secrets(pubkey, HERMES_SECRETS) else: print(f" note: no age_pubkey recorded for {name} — skipping sops re-key") @@ -1518,6 +1541,10 @@ def main() -> int: csub_add.add_argument("name") csub_add.add_argument("--finalize-pubkey", default=None, help="set/update age_pubkey for an already-added client") + csub_add.add_argument("--with-hermes", action="store_true", + help="also grant secrets/openrouter-api-key.yaml so this " + "host can run the Hermes agent (see " + "operations/hermes-agent.md). Combine with --finalize-pubkey.") csub_add.set_defaults(func=cmd_client_add) csub_rm = csub.add_parser("remove") csub_rm.add_argument("name") diff --git a/bootstrap.sh b/bootstrap.sh index ec6b275..239e503 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -5,6 +5,7 @@ # curl -fsSL https://git.hubris.network/dtoro/Homelab-Docs/raw/main/bootstrap.sh \ # | sudo bash # curl ... | sudo bash -s -- --with-mcp # also wire Claude's .mcp.json +# curl ... | sudo bash -s -- --with-hermes # also install Goose + Hermes wrapper # curl ... | sudo bash -s -- --dry-run # show what would happen # curl ... | sudo bash -s -- --no-secrets # skip age-key issuance # @@ -23,8 +24,11 @@ CLONE_DIR="${HOMELAB_CONTEXT_DIR:-/opt/homelab-context}" ISSUANCE_URL_NETBIRD="${HOMELAB_ISSUANCE_NETBIRD:-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}" +HERMES_MCP_URI="${HOMELAB_HERMES_MCP_URI:-https://mcp.hubris.network/mcp}" +HERMES_MODEL="${HOMELAB_HERMES_MODEL:-nousresearch/hermes-4-405b}" WITH_MCP=0 +WITH_HERMES=0 DRY_RUN=0 NO_SECRETS=0 GITEA_TOKEN="${HOMELAB_GITEA_TOKEN:-}" @@ -34,6 +38,7 @@ GITEA_USER="${HOMELAB_GITEA_USER:-dtoro}" while [ $# -gt 0 ]; do case "$1" in --with-mcp) WITH_MCP=1; shift ;; + --with-hermes) WITH_HERMES=1; shift ;; --dry-run) DRY_RUN=1; shift ;; --no-secrets) NO_SECRETS=1; shift ;; --gitea-token) GITEA_TOKEN="$2"; shift 2 ;; @@ -403,6 +408,116 @@ PYEOF fi fi +# -------- Hermes (Goose + Nous Hermes) wiring -------- +# Installs the Goose CLI binary system-wide, symlinks the `hermes` wrapper +# and HERMES.md persona, and drops a Goose config that pins the OpenRouter +# provider, the Nous Hermes model, and the homelab MCP extension. +# See operations/hermes-agent.md. +if [ "$WITH_HERMES" -eq 1 ]; then + # Resolve the operator's home (SUDO_USER under `sudo bash`). + if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then + H_USER="$SUDO_USER" + H_HOME=$(eval echo "~$SUDO_USER") + else + H_USER="root" + H_HOME="$HOME" + fi + + # 1. Install Goose binary at /usr/local/bin/goose (idempotent). + if ! command -v goose >/dev/null 2>&1; then + echo "[bootstrap] installing Goose CLI" + if [ "$DRY_RUN" -eq 1 ]; then + echo "+ would run upstream goose installer and symlink to /usr/local/bin/goose" + else + # Upstream installer drops the binary at ~/.local/bin/goose for the + # invoking user. We run it as $H_USER then symlink system-wide. + sudo -u "$H_USER" \ + env CONFIGURE=false \ + bash -c 'curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | bash' + if [ -x "$H_HOME/.local/bin/goose" ]; then + ln -sfn "$H_HOME/.local/bin/goose" /usr/local/bin/goose + else + echo "[bootstrap] WARNING: goose binary not found at $H_HOME/.local/bin/goose after install" >&2 + fi + fi + else + echo "[bootstrap] goose already installed: $(command -v goose)" + fi + + # 2. Symlink hermes wrapper. + echo "[bootstrap] linking hermes CLI to /usr/local/bin/hermes" + run "ln -sfn '$CLONE_DIR/bin/hermes' /usr/local/bin/hermes" + + # 3. Symlink HERMES.md persona. The hermes wrapper does not need it — the + # Goose config below references the canonical clone path — but operators + # frequently `cat /root/HERMES.md` to inspect the persona, mirroring the + # AGENTS.md convention above. + case "$OS" in + Linux) HERMES_LINK=/root/HERMES.md ;; + Darwin) HERMES_LINK=/etc/HERMES.md ;; + esac + run "ln -sfn '$CLONE_DIR/HERMES.md' '$HERMES_LINK'" + echo "[bootstrap] linked HERMES.md → $HERMES_LINK" + + # 4. Drop the Goose config. Idempotent YAML merge — preserves any keys the + # operator added by hand, overwrites only the keys we manage. + GOOSE_DIR="$H_HOME/.config/goose" + GOOSE_CONFIG="$GOOSE_DIR/config.yaml" + GOOSEHINTS="$GOOSE_DIR/.goosehints" + run "mkdir -p '$GOOSE_DIR'" + + PY_GOOSE_MERGE=$(cat </dev/null || true + fi +fi + # -------- netbird tuning (skip per-session SSO for ssh into mesh peers) -------- # Apply the SSH JWT cache TTL so `ssh ... .netbird.selfhosted` doesn't trigger # device-code SSO on every connection. Flag added in netbird 0.71.x diff --git a/operations/agent-enrollment.md b/operations/agent-enrollment.md index 7140cc8..1003557 100644 --- a/operations/agent-enrollment.md +++ b/operations/agent-enrollment.md @@ -5,6 +5,10 @@ joins the cross-client context system: a `/opt/homelab-context/` clone of this repo that auto-syncs every 5 min, a per-client age key for SOPS decryption, the `homelab` CLI, and an MCP endpoint in Claude Code's config. +> Onboarding a Nous-Hermes-powered Goose agent on top of standard enrollment? +> See [hermes-agent.md](./hermes-agent.md). It uses the same `bootstrap.sh` +> with an additional `--with-hermes` flag. + Architecture in [project_homelab_context_plan](https://… memory link); the operational reference is here. @@ -274,6 +278,9 @@ The CLI prints a follow-up checklist that the operator must do manually: ## Changelog +### 2026-05-31 — cross-link to hermes-agent.md +Added a sibling page covering Nous-Hermes-on-Goose enrollment ([hermes-agent.md](./hermes-agent.md)) and noted it at the top of this page. The Hermes flow extends `bootstrap.sh` with `--with-hermes` and `homelab client add` with the same flag; it does not change the underlying enrollment steps documented here. + ### 2026-05-21 — netbird-ssh JWT issuer + username + LAN-fallback troubleshooting rows Added three rows to the troubleshooting table covering issues surfaced during the netbird vanilla migration: (1) post-migration SSH JWT validator cache stuck on old Dex issuer (full `systemctl stop/start` required, not `restart`), (2) `user not found` from netbird-ssh's local-username default (use explicit `root@`), and (3) homelab CLI's LAN→netbird-FQDN fallback for off-LAN operators. Companion code change: per-host `ssh.user` field in `inventory.yaml` + `homelab` CLI's `ssh_target()` helper. diff --git a/operations/hermes-agent.md b/operations/hermes-agent.md new file mode 100644 index 0000000..e39d56e --- /dev/null +++ b/operations/hermes-agent.md @@ -0,0 +1,199 @@ +# Hermes agent — Nous-Hermes-powered Goose sessions on a homelab client + +Onboards [Nous Research's Hermes](https://nousresearch.com/) (a fine-tuned +Llama variant) as a working terminal agent on a homelab client. Builds on top +of standard client enrollment (see [agent-enrollment.md](./agent-enrollment.md)) +— this page covers only the Hermes-specific additions. + +The agent runs as a [Goose](https://goose-docs.ai/) session. Goose provides: + +- The chat loop, multi-turn history, and streaming +- The OpenRouter provider that routes to Nous Hermes +- The built-in `developer` extension (shell + file editor — same surface Claude + Code has) +- A remote MCP extension pointed at `mcp.hubris.network` for read-only + homelab context (`list_lxcs`, `tail_log`, `search_docs`, etc.) + +The persona is `/opt/homelab-context/HERMES.md`, symlinked as Goose's global +`.goosehints` so it's injected into the system prompt on every session. + +## Prerequisites + +| Requirement | How | +| --- | --- | +| Standard enrollment complete (`homelab whoami` works) | [agent-enrollment.md](./agent-enrollment.md) | +| `secrets/openrouter-api-key.yaml` exists with a real `sk-or-...` value | See "Seeding the OpenRouter key" below | +| The host's `age_pubkey` is on the openrouter-api-key.yaml sops rule | `homelab client add --finalize-pubkey --with-hermes` | + +## Onboarding flow + +```bash +# 1. On hubris (or any enrolled client): reserve the inventory entry. +homelab client add new-machine + +# 2. Join new-machine to Netbird (setup-key or OIDC). + +# 3. On new-machine: bootstrap with --with-hermes. +TOKEN=... # gitea PAT, read:repository +curl -fsSL -u "dtoro:$TOKEN" \ + https://git.hubris.network/dtoro/Homelab-Docs/raw/branch/main/bootstrap.sh \ + -o /tmp/bootstrap.sh +sudo HOMELAB_GITEA_TOKEN=$TOKEN bash /tmp/bootstrap.sh --with-mcp --with-hermes + +# 4. Back on hubris: finalize the age pubkey AND grant the Hermes secret. +homelab client add new-machine \ + --finalize-pubkey age1... \ + --with-hermes + +# 5. Wait ≤5 min for sync, then on new-machine: +hermes "what LXCs are running?" +``` + +The bootstrap `--with-hermes` flag does five things, all idempotent: + +1. Downloads the latest Goose binary into the operator's `~/.local/bin/goose` + (upstream installer) and symlinks `/usr/local/bin/goose` to it. +2. Symlinks `/opt/homelab-context/bin/hermes` → `/usr/local/bin/hermes`. +3. Symlinks `/opt/homelab-context/HERMES.md` → `/root/HERMES.md` (Linux) or + `/etc/HERMES.md` (macOS) for `cat`-as-operator convenience. +4. Drops `~/.config/goose/config.yaml` pinning the provider, model, and + extensions (preserves any keys the operator added by hand). +5. Symlinks `~/.config/goose/.goosehints` → HERMES.md, so the persona is + injected as the system prompt on every session. + +## Seeding the OpenRouter key + +The first time anyone enrolls with `--with-hermes`, the encrypted file +`secrets/openrouter-api-key.yaml` contains a placeholder. On hubris (or any +existing recipient): + +```bash +sops secrets/openrouter-api-key.yaml +# editor opens; replace api_key value with the real sk-or-... key, save, close. +git -C /opt/homelab-context add secrets/openrouter-api-key.yaml +git -C /opt/homelab-context commit -m 'openrouter-api-key: seed real key' +git -C /opt/homelab-context push +``` + +Until this step happens, `hermes …` exits with `openrouter-api-key.yaml still +contains the placeholder`. Subsequent enrollees get the real key automatically +via `--with-hermes` (which adds them as a sops recipient on +`secrets/openrouter-api-key.yaml`). + +## Granting the OpenRouter key to an already-enrolled host + +If a host was enrolled without `--with-hermes` and you want to add it later: + +```bash +# On hubris: +PUBKEY=$(homelab whoami --hostname | grep age_pubkey | awk '{print $2}') +homelab client add --finalize-pubkey "$PUBKEY" --with-hermes +``` + +`--finalize-pubkey` is required by the existing flow even when the pubkey is +unchanged — it's also the trigger that runs the sops grant. + +After ≤5 min sync the host can decrypt the key. Bootstrap doesn't need to +re-run; only the secret recipient list changed. + +## Verifying + +```bash +homelab whoami # standard enrollment OK +homelab secret openrouter-api-key | head -c 8 # decrypts (prints `api_key:`) +which goose && which hermes # binaries present +goose info -v # provider/model wiring sane +hermes "what LXCs are running?" # interactive Goose session + +# Non-interactive smoke test: +echo "List the homelab MCP tools you have available" | hermes +``` + +## Configuration + +The bootstrap-managed keys in `~/.config/goose/config.yaml`: + +```yaml +GOOSE_PROVIDER: openrouter +GOOSE_MODEL: nousresearch/hermes-4-405b +GOOSE_MODE: smart_approve # asks before destructive tool calls +extensions: + developer: + type: builtin + bundled: true + enabled: true + name: developer + timeout: 300 + homelab: + type: streamable_http + enabled: true + name: homelab + uri: https://mcp.hubris.network/mcp + timeout: 60 +``` + +Override via env on a single bootstrap run: + +```bash +HOMELAB_HERMES_MODEL=nousresearch/hermes-3-llama-3.1-405b \ +HOMELAB_HERMES_MCP_URI=https://mcp.hubris.network/mcp \ + sudo bash /tmp/bootstrap.sh --with-hermes +``` + +Any keys you add by hand (e.g. `GOOSE_TEMPERATURE`, extra `extensions.*`) are +preserved across re-bootstraps — the merge only overwrites the keys it manages. + +## Tool permissions + +`GOOSE_MODE: smart_approve` is the bootstrap default: Goose runs read-only +shell commands without prompting and asks for confirmation before destructive +ones. To make the agent fully unattended (e.g. for scheduled jobs), set +`GOOSE_MODE: auto` in `~/.config/goose/config.yaml`. To require confirmation on +every tool call, use `approve`. See +[goose-permissions](https://goose-docs.ai/docs/guides/managing-tools/goose-permissions/). + +## Troubleshooting + +| Symptom | Cause | Fix | +| --- | --- | --- | +| `hermes: could not decrypt secrets/openrouter-api-key.yaml` | Host isn't a recipient on the sops rule | `homelab client add --finalize-pubkey --with-hermes` from hubris | +| `hermes: openrouter-api-key.yaml still contains the placeholder` | No real key has been seeded yet | See "Seeding the OpenRouter key" above | +| Goose hangs on first `hermes` invocation with no output | Goose's interactive `configure` ran on first launch and is awaiting input | Re-run; the installer is supposed to skip it (CONFIGURE=false). If it persists, run `goose configure` once manually in a real terminal to commit the config. | +| `homelab` extension fails to connect / no MCP tools listed | MCP server still runs SSE-only; Goose requires `streamable_http`. See follow-up #1 below. | Either: (a) migrate the FastMCP server to streamable_http (one-line change in `mcp/server.py` — `mcp.run(transport="streamable_http")` — then redeploy), or (b) accept that the agent works via the developer extension alone (shell + `homelab` CLI cover everything MCP would). | +| `goose: command not found` after bootstrap | Upstream installer dropped binary in `~/.local/bin/` but `/usr/local/bin/goose` symlink didn't land | Re-run bootstrap with `--with-hermes`; the symlink step is at the end of the install block. If still missing, `ln -sfn ~/.local/bin/goose /usr/local/bin/goose` manually. | +| Tool calls hit OpenRouter rate limits | One shared key across many hosts | Future: per-host keys; for now, see the rate-limits guide referenced in `goose info -v`. | + +## Cross-references + +- [agent-enrollment.md](./agent-enrollment.md) — base client onboarding the + Hermes flow assumes is done. +- [`HERMES.md`](../HERMES.md) — the persona the Hermes agent reads on every + session start (via `~/.config/goose/.goosehints`). +- [`bin/hermes`](../bin/hermes) — the wrapper that decrypts the OpenRouter key + and execs `goose session`. +- [`bootstrap.sh`](../bootstrap.sh) — the `--with-hermes` flag's install block. + +## Follow-ups + +1. **Migrate the MCP server to streamable_http.** Goose 1.x deprecated SSE + (`"SSE transport is no longer supported - kept only for config file + compatibility"` in `crates/goose/src/agents/extension.rs`). Our FastMCP + server at `mcp/server.py:336` still calls `mcp.run(transport="sse")`. Until + that's changed, the `homelab` MCP extension in Goose will fail to connect. + The developer extension (shell + edit) covers most ops without it; this is + a polish item, not a blocker. +2. **Per-host OpenRouter keys** for billing attribution. Today all Hermes + hosts share one key. +3. **Pin the model version** rather than tracking `nousresearch/hermes-4-405b` + directly — OpenRouter periodically rotates the underlying weights. +4. **Local-inference fallback** (ollama / vllm) once the homelab has a GPU + node. The wrapper, persona, and MCP wiring stay unchanged; only + `GOOSE_PROVIDER`/`GOOSE_MODEL` change. + +## Changelog + +### 2026-05-31 — initial page +Captures the Hermes-on-Goose onboarding flow added in the same commit as +`bootstrap.sh --with-hermes`, `bin/hermes`, the sops rule for +`secrets/openrouter-api-key.yaml`, and the `homelab client add --with-hermes` +extension. MCP streamable_http migration is queued as follow-up #1. diff --git a/secrets/openrouter-api-key.yaml b/secrets/openrouter-api-key.yaml new file mode 100644 index 0000000..d592db4 --- /dev/null +++ b/secrets/openrouter-api-key.yaml @@ -0,0 +1,30 @@ +api_key: ENC[AES256_GCM,data:ps6aTT939SWpgyUoMMIj6YsJG3UnJFBdRKuESpoMeyphB9QnrkqSb5Y/kXI=,iv:6KxZTpPUR2GmPzr5opMP4Wp/yPIQqE9AWtAO5xBDGTI=,tag:qfRt1AlQ97PeHMsGmXnqsQ==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1xkklkvnk5z0fsnh6cfgv70hy9ksfy8rdprwerzw4yk3p4p7cxcqs2yvpz6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUZEU0QWtxb0RMMkNqTHI3 + d25ONHF2Z2F6L0c4VWd6amMvYkQ0NU5YeEh3ClpBSEt5bXNJVmF5Q2pzNGdzVWVC + MzFDSTk2YlpyWkxIdDg0RmFxR21EMzgKLS0tIE1GRW52Wk10eXppM05XSDMrc3RH + eWQ5TFR0R29RWUxsbVhlWm1TVkFvb0UKwv6OafGdw06J21tQoUlvNI0aLVvl2CVm + nwbltuhQvWdFPVBRP/Cx0eK7Vh80tJrnOfXeGpNVgiCs2lpxBlUR2g== + -----END AGE ENCRYPTED FILE----- + - recipient: age1vf8h7s8mqsn2q5eadgpdupsj4mwn8zguc77d85ws3xj40sl9rgksx2rxw6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMZVdubENvWkxGWmNnVks0 + NFJYQVBKMnJJZ3FEUDVJWXl2WitMTDFYdEd3CmJQaSt4bTZyUVlBZHgybXRhbWky + UGIzNkwzUzNZaWN3WmNKSUkxQkl0OTAKLS0tIENyNlphYmVRV3BUNU1FQzd0QStN + V0JLNU5RT09RVjd2OTJUTWMvek9NaTQK0R0OfQnm7AhIeroefldUNDsgDHX1cDmk + GybdGEoM8j8Is8GLc6Gk+yQmz7yNvVfYBkaaFsH3Crz3HupuyXI59g== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-30T23:08:58Z" + mac: ENC[AES256_GCM,data:68i7qPK7zJXDspmVYfTVJGd141ldXeiPEHK/tsX3p9QHBSP8otXwAzaEqcjNG83dXhrse9CRzzyJmrLm7EO714hZyHKh6MKZb/BOuiwnMCZse0qbK55ghkzTQLMkeSuIM2KsN396mlH/Ehd/hQV6oC6OPq2RQUs/aJt55ESutLc=,iv:kGPKf9e6w1vz7soEyDL7mGQCBkrxiG0Wvm7FHEYp3jc=,tag:zHd7qoIx9Fei2Aabgsztuw==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.4