Keep the personal half of the desktop in one place, and ask before installing it

Agent instructions, skills, SSH host aliases and expansion triggers are worth
having identical on every machine one person owns, and belong in none of the
shared configuration. They live in user/ now, with a manifest saying where each
piece goes and a link-user stage that puts it there.

That stage does nothing unless the machine said yes. Somebody who clones Panama
to try the desktop keeps their own ~/.claude/CLAUDE.md exactly where it was;
the question names the destinations and defaults to no. Anything displaced goes
to config/old rather than being deleted.

~/.claude/CLAUDE.md and ~/.codex/AGENTS.md were byte-identical copies of one
file, which is the drift this exists to prevent.

Also adds the vitals toggles for the battery and Claude usage readouts, which
had preferences and no way to reach them.
This commit is contained in:
Gabriel Brown
2026-08-22 08:54:43 -04:00
parent 8b96d907a1
commit 89761a7da3
156 changed files with 16439 additions and 6 deletions
@@ -0,0 +1,159 @@
# Infisical Agent Configuration Reference
## Running the Agent
```bash
infisical agent --config /path/to/agent-config.yaml
```
Requires the Infisical CLI to be installed first.
## Full Config File Structure
```yaml
infisical:
address: "https://app.infisical.com" # Infisical instance URL
exit-after-auth: false # Exit after first auth + render
revoke-credentials-on-shutdown: false # Revoke leases/tokens on shutdown
retry-strategy:
max-retries: 3 # Max retry attempts
max-delay: "5s" # Max delay between retries
base-delay: "200ms" # Base delay (exponential backoff)
auth:
type: "<auth-method>" # See Auth Methods below
config:
# Auth-method-specific fields
sinks: # Where access tokens are deposited
- type: "file"
config:
path: "/path/to/access-token"
cache: # Optional persistent caching
persistent:
type: "kubernetes"
path: "/home/infisical/cache"
service-account-token-path: "/var/run/secrets/kubernetes.io/serviceaccount/token"
templates: # Secret rendering templates
- source-path: "/path/to/template.tpl" # File-based template
# OR
template-content: | # Inline template
{{- with listSecrets "project-id" "env" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: "/path/to/output/.env"
config:
polling-interval: "5m" # How often to check for changes
execute:
command: "./reload-app.sh" # Run on secret change
timeout: 30 # Command timeout in seconds
```
## Auth Methods
### Universal Auth (fallback for any environment)
```yaml
auth:
type: "universal-auth"
config:
client-id: "./client-id" # Path to file containing client ID
client-secret: "./client-secret" # Path to file containing client secret
remove_client_secret_on_read: false # Delete secret file after reading
```
### Kubernetes (recommended on K8s)
```yaml
auth:
type: "kubernetes"
config:
identity-id: "./identity-id" # Path to file with machine identity ID
service-account-token: "/var/run/secrets/kubernetes.io/serviceaccount/token" # Optional
```
### AWS IAM (recommended on AWS)
```yaml
auth:
type: "aws-iam"
config:
identity-id: "./identity-id" # Path to file with machine identity ID
```
Uses the instance's IAM role automatically — no access keys needed.
### Azure (recommended on Azure)
```yaml
auth:
type: "azure"
config:
identity-id: "./identity-id" # Path to file with machine identity ID
```
### GCP ID Token (recommended on GCP)
```yaml
auth:
type: "gcp-id-token"
config:
identity-id: "./identity-id" # Path to file with machine identity ID
```
### GCP IAM
```yaml
auth:
type: "gcp-iam"
config:
identity-id: "./identity-id" # Path to file with machine identity ID
service-account-key: "./key.json" # Path to GCP service account JSON key
```
## Sinks
Sinks are where the agent deposits renewed access tokens. Currently only file sinks are supported.
```yaml
sinks:
- type: "file"
config:
path: "/tmp/access-token"
```
**Important distinction:** Sinks deposit raw access tokens (for SDK/API use). Templates render actual secret values to files. Most users want templates, not sinks.
## Token Renewal Lifecycle
1. Agent starts → authenticates using configured auth method
2. If auth fails → retries with exponential backoff (base-delay up to max-delay)
3. Token obtained → written to all sinks
4. Agent monitors token expiry → renews before expiration
5. Each renewal → writes new token to all sinks
6. Templates rendered → secrets fetched using the token
7. Templates re-render on polling-interval → detects secret changes
8. If secrets changed and `execute.command` is set → command runs
## Caching (Kubernetes only)
Persistent caching stores secrets locally so the agent can serve them even if Infisical is temporarily unavailable.
```yaml
cache:
persistent:
type: "kubernetes"
path: "/home/infisical/cache"
service-account-token-path: "/var/run/secrets/kubernetes.io/serviceaccount/token"
```
- Only available in Kubernetes environments
- Stale dynamic secret leases are auto-evicted and refreshed
- Cache GC runs every 10 minutes
## Key Config Options
| Setting | When to use |
|---------|------------|
| `exit-after-auth: true` | Init containers, one-shot renders (render secrets once and exit) |
| `revoke-credentials-on-shutdown: true` | Clean up dynamic secret leases when agent stops |
| `polling-interval: "30s"` | Latency-sensitive apps that need fast secret updates |
| `polling-interval: "60m"` | Stable configs where secrets rarely change |
| `execute.command` | Trigger app restarts or config reloads on secret changes |
@@ -0,0 +1,297 @@
# Infisical Agent Deployment Examples
## Basic Local Development
```yaml
# agent-config.yaml
infisical:
address: "https://app.infisical.com"
auth:
type: "universal-auth"
config:
client-id: "./client-id"
client-secret: "./client-secret"
sinks:
- type: "file"
config:
path: "/tmp/infisical-token"
templates:
- template-content: |
{{- with listSecrets "6553ccb2b7da580d7f6e7260" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: /app/.env
config:
polling-interval: 5m
execute:
command: ./restart-app.sh
timeout: 30
```
**Run:** `infisical agent --config agent-config.yaml`
---
## Docker Compose Sidecar
```yaml
# docker-compose.yml
version: "3.8"
services:
infisical-agent:
image: infisical/cli:latest
command: agent --config /etc/infisical/agent-config.yaml
volumes:
- ./agent-config.yaml:/etc/infisical/agent-config.yaml:ro
- ./client-id:/etc/infisical/client-id:ro
- ./client-secret:/etc/infisical/client-secret:ro
- shared-secrets:/infisical/secrets
app:
image: myapp:latest
volumes:
- shared-secrets:/app/secrets:ro
depends_on:
- infisical-agent
volumes:
shared-secrets:
```
```yaml
# agent-config.yaml (for Docker Compose)
infisical:
address: "https://app.infisical.com"
auth:
type: "universal-auth"
config:
client-id: "/etc/infisical/client-id"
client-secret: "/etc/infisical/client-secret"
sinks:
- type: "file"
config:
path: "/infisical/secrets/access-token"
templates:
- template-content: |
{{- with listSecrets "<project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: /infisical/secrets/.env
config:
polling-interval: 5m
```
---
## AWS ECS Sidecar
Use `aws-iam` auth so no credentials need to be stored. The agent uses the ECS task role automatically.
```yaml
# agent-config.yaml (for ECS)
infisical:
address: "https://app.infisical.com"
exit-after-auth: true # Render once and exit (init-style)
auth:
type: "aws-iam"
config:
identity-id: "<machine-identity-id>" # Inline ID (no file path needed in ECS)
sinks:
- type: "file"
config:
path: "/infisical/secrets/access-token"
templates:
- template-content: |
{{- with listSecretsByProjectSlug "my-project" "prod" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: /infisical/secrets/.env
```
**ECS Task Definition snippet:**
```json
{
"containerDefinitions": [
{
"name": "infisical-agent",
"image": "infisical/cli:latest",
"command": ["agent", "--config", "/etc/infisical/agent-config.yaml"],
"essential": false,
"mountPoints": [
{ "sourceVolume": "secrets", "containerPath": "/infisical/secrets" }
],
"environment": [
{ "name": "INFISICAL_MACHINE_IDENTITY_ID", "value": "<identity-id>" }
]
},
{
"name": "app",
"image": "myapp:latest",
"essential": true,
"dependsOn": [
{ "containerName": "infisical-agent", "condition": "COMPLETE" }
],
"mountPoints": [
{ "sourceVolume": "secrets", "containerPath": "/app/secrets", "readOnly": true }
]
}
],
"volumes": [
{ "name": "secrets" }
]
}
```
---
## Kubernetes Init Container
Use `exit-after-auth: true` to render secrets once and let the main container start.
```yaml
# agent-config.yaml (for K8s init container)
infisical:
address: "https://app.infisical.com"
exit-after-auth: true
auth:
type: "kubernetes"
config:
identity-id: "/etc/infisical/identity-id"
service-account-token: "/var/run/secrets/kubernetes.io/serviceaccount/token"
templates:
- template-content: |
{{- with listSecretsByProjectSlug "my-project" "prod" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: /infisical/secrets/.env
```
```yaml
# Kubernetes Pod spec
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
serviceAccountName: my-app-sa
initContainers:
- name: infisical-agent
image: infisical/cli:latest
command: ["infisical", "agent", "--config", "/etc/infisical/agent-config.yaml"]
volumeMounts:
- name: secrets
mountPath: /infisical/secrets
- name: agent-config
mountPath: /etc/infisical
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: secrets
mountPath: /app/secrets
readOnly: true
volumes:
- name: secrets
emptyDir: {}
- name: agent-config
configMap:
name: infisical-agent-config
```
---
## Kubernetes Sidecar (continuous sync)
For apps that need live secret updates, run the agent as a sidecar instead of an init container.
```yaml
# agent-config.yaml (sidecar mode)
infisical:
address: "https://app.infisical.com"
# exit-after-auth: false (default — keep running)
auth:
type: "kubernetes"
config:
identity-id: "/etc/infisical/identity-id"
cache:
persistent:
type: "kubernetes"
path: "/home/infisical/cache"
templates:
- template-content: |
{{- with listSecretsByProjectSlug "my-project" "prod" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: /infisical/secrets/.env
config:
polling-interval: "1m"
execute:
command: "kill -HUP 1" # Signal main process to reload
timeout: 10
```
---
## With Dynamic Secrets (Database Credentials)
```yaml
# agent-config.yaml
infisical:
address: "https://app.infisical.com"
revoke-credentials-on-shutdown: true # Clean up DB users on shutdown
auth:
type: "aws-iam"
config:
identity-id: "<machine-identity-id>"
templates:
# Static secrets
- template-content: |
{{- with listSecretsByProjectSlug "my-project" "prod" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
destination-path: /app/secrets/static.env
# Dynamic database credentials
- template-content: |
{{ with dynamicSecret "my-project" "prod" "/" "postgres-creds" "1h" }}
DB_HOST=db.internal.example.com
DB_PORT=5432
DB_NAME=myapp
DB_USER={{ .DB_USERNAME }}
DB_PASS={{ .DB_PASSWORD }}
{{ end }}
destination-path: /app/secrets/db.env
config:
polling-interval: "5m"
execute:
command: "./reconnect-db.sh"
timeout: 30
```
@@ -0,0 +1,200 @@
# Infisical Agent Template Functions
Templates use Go's `text/template` syntax. All functions are available inside template blocks.
## listSecrets
Fetch all secrets from a project environment and path. **Most common function** — use for rendering .env files.
```
listSecrets "<project-id>" "<environment-slug>" "<secret-path>" "<optional-modifier>"
```
**Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| project-id | string | UUID of the project |
| environment-slug | string | `dev`, `staging`, `prod`, etc. |
| secret-path | string | `/`, `/api`, `/database`, etc. |
| optional-modifier | JSON string | `{"recursive": bool, "expandSecretReferences": bool}` |
- `recursive` (default: `false`) — Fetch secrets from subdirectories too
- `expandSecretReferences` (default: `true`) — Resolve `${SECRET_NAME}` references
**Returns:** Array of objects with: `Key`, `Value`, `SecretPath`, `WorkspaceId`, `Type`, `ID`, `Comment`
**Example — .env file:**
```go
{{- with listSecrets "6553ccb2b7da580d7f6e7260" "dev" "/" `{"recursive": false, "expandSecretReferences": true}` }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
**Example — recursive with paths:**
```go
{{- with listSecrets "da8056c8-01e2-4d24-b39f-cb4e004b8d44" "staging" "/" `{"recursive": true, "expandSecretReferences": true}` }}
{{- range . }}
{{- if eq .SecretPath "/"}}
{{ .Key }}={{ .Value }}
{{- else}}
{{ .SecretPath }}/{{ .Key }}={{ .Value }}
{{- end}}
{{- end }}
{{- end }}
```
---
## listSecretsByProjectSlug
Same as `listSecrets` but uses the project slug instead of UUID. Easier to read in configs.
```
listSecretsByProjectSlug "<project-slug>" "<environment-slug>" "<secret-path>" "<optional-modifier>"
```
**Parameters:** Same as `listSecrets`, except first param is project slug (e.g., `"my-project"`) instead of UUID.
**Returns:** Same as `listSecrets`.
**Example:**
```go
{{- with listSecretsByProjectSlug "my-project" "prod" "/" `{"recursive": true}` }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
---
## getSecretByName
Fetch a single secret by name.
```
getSecretByName "<project-id>" "<environment-slug>" "<secret-path>" "<secret-name>"
```
**Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| project-id | string | UUID of the project |
| environment-slug | string | `dev`, `staging`, `prod`, etc. |
| secret-path | string | `/`, `/api`, etc. |
| secret-name | string | Exact secret name (e.g., `DATABASE_URL`) |
**Returns:** Single object with: `Key`, `Value`, `WorkspaceId`, `Type`, `ID`, `Comment`
**Example — config file snippet:**
```go
{{ with getSecretByName "d821f21d-aa90-453b-8448-8c78c1160a0e" "dev" "/" "POSTHOG_HOST" }}
{{ if .Value }}
analytics_host = "{{ .Value }}"
{{ end }}
{{ end }}
```
---
## dynamicSecret
Create and auto-renew a dynamic secret lease. **Use for database credentials, cloud IAM tokens, etc.**
```
dynamicSecret "<project-slug>" "<environment-slug>" "<secret-path>" "<dynamic-secret-name>" "<lease-ttl>"
```
**Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| project-slug | string | Project slug |
| environment-slug | string | `dev`, `staging`, `prod`, etc. |
| secret-path | string | `/`, `/database`, etc. |
| dynamic-secret-name | string | Name of the dynamic secret (e.g., `postgres-creds`) |
| lease-ttl | string | Lease duration (e.g., `1m`, `1h`, `24h`) |
**Returns:** Object with keys specific to the dynamic secret type:
- SQL databases: `DB_USERNAME`, `DB_PASSWORD`
- AWS IAM: `ACCESS_KEY`, `SECRET_ACCESS_KEY`, `SESSION_TOKEN` (if temporary)
- Redis: `DB_USERNAME`, `DB_PASSWORD`
**Key behaviors:**
- Automatically renews credentials before expiration
- Deduplication: Multiple templates with identical dynamic secret configs share one lease
- Revoked on shutdown if `revoke-credentials-on-shutdown: true`
**Example — PostgreSQL credentials:**
```go
{{ with dynamicSecret "my-project" "dev" "/" "postgres-creds" "1h" }}
DB_HOST=db.example.com
DB_USER={{ .DB_USERNAME }}
DB_PASSWORD={{ .DB_PASSWORD }}
{{ end }}
```
**Example — Redis credentials:**
```go
{{ with dynamicSecret "my-project" "prod" "/" "redis" "30m" }}
REDIS_USER={{ .DB_USERNAME }}
REDIS_PASS={{ .DB_PASSWORD }}
{{ end }}
```
---
## Common Template Patterns
### .env file (most common)
```go
{{- with listSecrets "<project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
### JSON config
```go
{
{{- with listSecrets "<project-id>" "prod" "/" }}
{{- range $i, $s := . }}
{{- if $i }},{{ end }}
"{{ $s.Key }}": "{{ $s.Value }}"
{{- end }}
{{- end }}
}
```
### YAML config
```go
{{- with listSecrets "<project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}: "{{ .Value }}"
{{- end }}
{{- end }}
```
### Mixed static + dynamic secrets
```go
{{- with listSecrets "<project-id>" "prod" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
{{ with dynamicSecret "my-project" "prod" "/" "postgres" "1h" }}
DB_DYNAMIC_USER={{ .DB_USERNAME }}
DB_DYNAMIC_PASS={{ .DB_PASSWORD }}
{{ end }}
```
### Export format (for `source .env`)
```go
{{- with listSecrets "<project-id>" "dev" "/" }}
{{- range . }}
export {{ .Key }}="{{ .Value }}"
{{- end }}
{{- end }}
```