- add lambda invoke IAM user + outputs - update fleet control to invoke lambda directly - wire new control access-key secrets - update docs + secrets guidance
5.9 KiB
5.9 KiB
Secrets Wiring
Principle: secrets never land in git. One secret per file, decrypted at runtime.
Infrastructure (OpenTofu):
- AWS credentials via environment variable (required for
infra/opentofu/aws). - Do NOT commit
*.tfvarswith secrets.
Image pipeline (CI):
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_REGION/S3_BUCKET(required).CLAWDINATOR_AGE_KEY(required; used to build the bootstrap bundle uploaded to S3).
Control plane (OOB):
control_api_token(Lambda env or OpenTofu variable; stored asclawdinator-control-token.age).github_token(workflow dispatch PAT).
Runtime control (CLAWDINATOR):
clawdinator-control-token.ageis injected to/run/agenix/clawdinator-control-tokenand used by/fleet.clawdinator-control-aws-access-key-id.age+clawdinator-control-aws-secret-access-key.ageallow Lambda invocation.- Token is shared across instances (KISS); policy enforcement happens in the skill.
Local storage:
- Keep AWS keys encrypted in
../nix/nix-secretsfor local runs if needed. - CI pulls credentials from GitHub Actions secrets (never from host files).
Runtime (CLAWDINATOR):
- Discord bot token (required, per instance;
clawdinator-discord-token-<n>.age). - Telegram bot token (required if Telegram channel is enabled).
- GitHub token (required): GitHub App installation token (preferred) or a read-only PAT.
- Anthropic API key (required for Claude models).
- OpenAI API key (required for OpenAI models).
Explicit token files (standard):
services.clawdinator.discordTokenFileservices.clawdinator.anthropicApiKeyFileservices.clawdinator.openaiApiKeyFileservices.clawdinator.githubPatFile(PAT path, if not using GitHub App; exportsGITHUB_TOKEN+GH_TOKEN)services.clawdinator.telegramAllowFromFile(optional; exportsCLAWDINATOR_TELEGRAM_ALLOW_FROM)
Telegram token wiring (OpenClaw config):
services.clawdinator.config.channels.telegram.tokenFile(preferred)- or
TELEGRAM_BOT_TOKENenvironment variable channels.telegram.allowFromcan reference\${CLAWDINATOR_TELEGRAM_ALLOW_FROM}when exported viaservices.clawdinator.telegramAllowFromFile
GitHub App (preferred):
- Private key PEM decrypted to
/run/agenix/clawdinator-github-app.pem. - App ID + Installation ID in
services.clawdinator.githubApp.*. - Timer mints short-lived tokens into
/run/clawd/github-app.envwithGITHUB_TOKEN+GH_TOKEN. - Timer also writes a GH CLI auth file at
/var/lib/clawd/gh/hosts.yml(gateway usesGH_CONFIG_DIR=/var/lib/clawd/gh).
Agenix (local secrets repo):
- Store encrypted files in
../nix/nix-secrets(relative to this repo). - Sync encrypted secrets to the host at
/var/lib/clawd/nix-secrets. - Decrypt on host with agenix; point NixOS options at
/run/agenix/*. - Image builds do not bake the agenix identity; the age key is injected at runtime via the bootstrap bundle.
- Required files (minimum):
clawdinator-github-app.pem.age,clawdinator-anthropic-api-key.age,clawdinator-openai-api-key-peter-2.age,clawdinator-control-token.age,clawdinator-control-aws-access-key-id.age,clawdinator-control-aws-secret-access-key.age. - Required per instance:
clawdinator-discord-token-1.age,clawdinator-discord-token-2.age(one per instance). - Required for Telegram:
clawdinator-telegram-bot-token.age(when Telegram is enabled). - Telegram allowlist (if using allowFrom secrets):
clawdinator-telegram-allow-from.age. - CI image pipeline (stored locally, not on hosts):
clawdinator-image-uploader-access-key-id.age,clawdinator-image-uploader-secret-access-key.age,clawdinator-image-bucket-name.age,clawdinator-image-bucket-region.age.
Bootstrap bundle (runtime injection):
- CI uploads
secrets.tar.zst+repo-seeds.tar.zsttos3://${S3_BUCKET}/bootstrap/<instance>/. secrets.tar.zstcontains:clawdinator.agekeysecrets/directory with*.agefiles.
- The host downloads + installs these on boot (
clawdinator-bootstrap.service).
Example NixOS wiring (agenix):
{ inputs, ... }:
{
imports = [ inputs.agenix.nixosModules.default ];
age.secrets."clawdinator-github-app.pem".file =
"/var/lib/clawd/nix-secrets/clawdinator-github-app.pem.age";
age.secrets."clawdinator-anthropic-api-key".file =
"/var/lib/clawd/nix-secrets/clawdinator-anthropic-api-key.age";
age.secrets."clawdinator-openai-api-key-peter-2".file =
"/var/lib/clawd/nix-secrets/clawdinator-openai-api-key-peter-2.age";
age.secrets."clawdinator-discord-token-1".file =
"/var/lib/clawd/nix-secrets/clawdinator-discord-token-1.age";
age.secrets."clawdinator-control-token".file =
"/var/lib/clawd/nix-secrets/clawdinator-control-token.age";
age.secrets."clawdinator-control-aws-access-key-id".file =
"/var/lib/clawd/nix-secrets/clawdinator-control-aws-access-key-id.age";
age.secrets."clawdinator-control-aws-secret-access-key".file =
"/var/lib/clawd/nix-secrets/clawdinator-control-aws-secret-access-key.age";
age.secrets."clawdinator-telegram-bot-token".file =
"/var/lib/clawd/nix-secrets/clawdinator-telegram-bot-token.age";
age.secrets."clawdinator-telegram-allow-from".file =
"/var/lib/clawd/nix-secrets/clawdinator-telegram-allow-from.age";
services.clawdinator.githubApp.privateKeyFile =
"/run/agenix/clawdinator-github-app.pem";
services.clawdinator.anthropicApiKeyFile =
"/run/agenix/clawdinator-anthropic-api-key";
services.clawdinator.openaiApiKeyFile =
"/run/agenix/clawdinator-openai-api-key-peter-2";
services.clawdinator.discordTokenFile =
"/run/agenix/clawdinator-discord-token-1";
services.clawdinator.telegramAllowFromFile =
"/run/agenix/clawdinator-telegram-allow-from";
services.clawdinator.config.channels.telegram = {
enabled = true;
dmPolicy = "allowlist";
allowFrom = [ "\${CLAWDINATOR_TELEGRAM_ALLOW_FROM}" ];
groupPolicy = "disabled";
tokenFile = "/run/agenix/clawdinator-telegram-bot-token";
};
}