Skip to content

Commit 8e2afcd

Browse files
committed
config: replace dotenv with varlock for env validation
- Add .env.schema with types, @required, @sensitive decorators for all env vars - Remove dotenv from slack-bridge, env vars now injected via varlock run - start.sh validates with `varlock load` before launching - startup-cleanup.sh uses `varlock run` for bridge tmux session - deploy.sh deploys .env.schema to ~/.config/.env.schema - setup.sh installs varlock binary - Update CONFIGURATION.md with schema validation docs Replaces blind dotenv loading with schema-validated startup. Catches wrong token prefixes, missing required vars, and invalid emails before any connections are attempted.
1 parent 95815a2 commit 8e2afcd

9 files changed

Lines changed: 155 additions & 32 deletions

File tree

.env.schema

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Hornet agent configuration schema
2+
# See CONFIGURATION.md for details on each variable.
3+
#
4+
# Secrets live at ~/.config/.env (600 perms, never committed).
5+
# This schema is deployed to ~/.config/.env.schema alongside the .env file.
6+
#
7+
# @defaultSensitive=true
8+
# @defaultRequired=false
9+
# ---
10+
11+
# ── LLM Access ───────────────────────────────────────────────────────────────
12+
13+
# API key for the LLM provider (Anthropic, OpenAI, or a proxy)
14+
# @required @type=string
15+
# @docs(https://docs.anthropic.com/en/api/getting-started)
16+
OPENCODE_ZEN_API_KEY=
17+
18+
# ── GitHub ───────────────────────────────────────────────────────────────────
19+
20+
# GitHub Personal Access Token (fine-grained, scoped to agent repos)
21+
# @required @type=string(startsWith=ghp_)
22+
# @docs(https://github.com/settings/tokens)
23+
GITHUB_TOKEN=
24+
25+
# ── Slack ────────────────────────────────────────────────────────────────────
26+
27+
# Slack bot OAuth token
28+
# @required @type=string(startsWith=xoxb-)
29+
# @docs("Create a Slack app", https://api.slack.com/apps)
30+
SLACK_BOT_TOKEN=
31+
32+
# Slack app-level token (Socket Mode)
33+
# @required @type=string(startsWith=xapp-)
34+
SLACK_APP_TOKEN=
35+
36+
# Comma-separated Slack user IDs allowed to interact with the agent
37+
# Bridge refuses to start without at least one user ID.
38+
# @required @sensitive=false @type=string
39+
# @example="U01ABCDEF,U02GHIJKL"
40+
SLACK_ALLOWED_USERS=
41+
42+
# ── Email Monitor ────────────────────────────────────────────────────────────
43+
44+
# AgentMail API key
45+
# @type=string
46+
# @docs(https://app.agentmail.to)
47+
AGENTMAIL_API_KEY=
48+
49+
# Agent's monitored email address
50+
# @sensitive=false @type=email
51+
HORNET_EMAIL=
52+
53+
# Shared secret for email sender authentication
54+
# @type=string
55+
HORNET_SECRET=
56+
57+
# Comma-separated sender email allowlist
58+
# @sensitive=false @type=string
59+
# @example="you@example.com,teammate@example.com"
60+
HORNET_ALLOWED_EMAILS=
61+
62+
# ── Sentry (optional) ───────────────────────────────────────────────────────
63+
64+
# Sentry API bearer token
65+
# @type=string
66+
# @docs(https://sentry.io/settings/account/api/auth-tokens/)
67+
SENTRY_AUTH_TOKEN=
68+
69+
# Sentry organization slug
70+
# @sensitive=false @type=string
71+
SENTRY_ORG=
72+
73+
# Slack channel ID for Sentry alerts
74+
# @sensitive=false @type=string(startsWith=C)
75+
SENTRY_CHANNEL_ID=
76+
77+
# ── Slack Channels (optional) ───────────────────────────────────────────────
78+
79+
# Additional monitored channel (responds to all messages, not just @mentions)
80+
# @sensitive=false @type=string(startsWith=C)
81+
SLACK_CHANNEL_ID=
82+
83+
# ── Kernel (optional) ───────────────────────────────────────────────────────
84+
85+
# Kernel cloud browser API key
86+
# @type=string
87+
# @docs(https://kernel.computer)
88+
KERNEL_API_KEY=
89+
90+
# ── Tool Guard ───────────────────────────────────────────────────────────────
91+
92+
# Unix username of the agent
93+
# @sensitive=false @type=string
94+
HORNET_AGENT_USER=hornet_agent
95+
96+
# Agent's home directory
97+
# @sensitive=false @type=string
98+
HORNET_AGENT_HOME=/home/hornet_agent
99+
100+
# Path to admin-owned source repo (enables source repo write protection)
101+
# @sensitive=false @type=string
102+
HORNET_SOURCE_DIR=
103+
104+
# ── Bridge ───────────────────────────────────────────────────────────────────
105+
106+
# Local HTTP API port for outbound Slack messages
107+
# @sensitive=false @type=port
108+
BRIDGE_API_PORT=7890
109+
110+
# Target pi session ID (auto-detects control-agent if unset)
111+
# @sensitive=false @type=string
112+
PI_SESSION_ID=

CONFIGURATION.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All secrets and configuration live in `~/.config/.env` on the agent's home directory (`/home/hornet_agent/.config/.env`). This file is `600` permissions and never committed to the repo.
44

5+
## Schema Validation
6+
7+
Hornet uses [Varlock](https://varlock.dev) to validate environment variables at startup. The schema (`.env.schema`) is committed to the repo and deployed to `~/.config/.env.schema` alongside the secrets file. It defines types, required/optional status, and sensitivity for each variable.
8+
9+
`start.sh` runs `varlock load` to validate before launching — the agent won't start with missing or malformed variables. The bridge uses `varlock run` to inject validated env vars. Varlock must be installed on the agent system (`brew install dmno-dev/tap/varlock` or `curl -sSfL https://varlock.dev/install.sh | sh -s`).
10+
511
## Required Variables
612

713
### LLM Access
@@ -123,4 +129,4 @@ sudo -u hornet_agent pkill -u hornet_agent
123129
sudo -u hornet_agent ~/runtime/start.sh
124130
```
125131

126-
The bridge and all sub-agents source `~/.config/.env` on startup via `set -a && source ~/.config/.env && set +a`.
132+
The bridge and all sub-agents load `~/.config/.env` on startup. If varlock is installed, variables are validated against `.env.schema` before injection.

bin/deploy.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ if [ "$DRY_RUN" -eq 0 ]; then
5252
[ -f "$HORNET_SRC/bin/$script" ] && cp --no-preserve=ownership "$HORNET_SRC/bin/$script" "$STAGE_DIR/bin/$script"
5353
done
5454
[ -f "$HORNET_SRC/pi/settings.json" ] && cp --no-preserve=ownership "$HORNET_SRC/pi/settings.json" "$STAGE_DIR/settings.json"
55+
[ -f "$HORNET_SRC/.env.schema" ] && cp --no-preserve=ownership "$HORNET_SRC/.env.schema" "$STAGE_DIR/.env.schema"
5556
chmod -R a+rX "$STAGE_DIR"
5657
fi
5758

@@ -210,6 +211,20 @@ if [ -f "$STAGE_DIR/settings.json" ]; then
210211
fi
211212
fi
212213

214+
# ── Env schema ───────────────────────────────────────────────────────────────
215+
216+
echo "Deploying env schema..."
217+
218+
if [ -f "$STAGE_DIR/.env.schema" ]; then
219+
if [ "$DRY_RUN" -eq 0 ]; then
220+
as_agent cp "$STAGE_DIR/.env.schema" "$HORNET_HOME/.config/.env.schema"
221+
as_agent chmod 644 "$HORNET_HOME/.config/.env.schema"
222+
log "✓ .env.schema → ~/.config/.env.schema"
223+
else
224+
log "would copy: .env.schema → ~/.config/.env.schema"
225+
fi
226+
fi
227+
213228
# ── Version stamp + integrity manifest ────────────────────────────────────────
214229

215230
echo "Stamping version..."

pi/skills/control-agent/startup-cleanup.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fi
7676
# Start fresh slack-bridge
7777
echo "Starting slack-bridge with PI_SESSION_ID=$MY_UUID..."
7878
tmux new-session -d -s slack-bridge \
79-
"set -a && source ~/.config/.env && set +a && export PATH=\$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && export PI_SESSION_ID=$MY_UUID && cd ~/hornet/slack-bridge && exec node bridge.mjs"
79+
"export PATH=\$HOME/.varlock/bin:\$HOME/opt/node-v22.14.0-linux-x64/bin:\$PATH && export PI_SESSION_ID=$MY_UUID && cd ~/runtime/slack-bridge && exec varlock run --path ~/.config/ -- node bridge.mjs"
8080

8181
# Wait for bridge to come up
8282
sleep 3

setup.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ done
176176
echo "=== Installing Slack bridge dependencies ==="
177177
(cd "$REPO_DIR/slack-bridge" && npm install)
178178

179+
echo "=== Installing varlock ==="
180+
if command -v varlock &>/dev/null; then
181+
echo "varlock already installed, skipping"
182+
else
183+
curl -sSfL https://varlock.dev/install.sh | sh -s
184+
fi
185+
179186
echo "=== Deploying from source to runtime ==="
180187
# deploy.sh runs as admin (needs read access to source, write+chown to agent home).
181188
# It copies extensions, skills, bridge, and utility scripts to runtime dirs.

slack-bridge/bridge.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
* BRIDGE_API_PORT - outbound API port (default: 7890)
1818
*/
1919

20-
import "dotenv/config";
20+
// Env vars loaded and validated by varlock (via `varlock run` or `start.sh`).
21+
// No dotenv/varlock import needed — env is already in process.env.
2122
import { App } from "@slack/bolt";
2223
import * as net from "node:net";
2324
import * as fs from "node:fs";

slack-bridge/package-lock.json

Lines changed: 4 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

slack-bridge/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"test": "node --test security.test.mjs"
1010
},
1111
"dependencies": {
12-
"@slack/bolt": "^4.6.0",
13-
"dotenv": "^17.3.1"
12+
"@slack/bolt": "^4.6.0"
1413
}
1514
}

start.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ set -euo pipefail
1414
cd ~
1515

1616
# Set PATH
17-
export PATH="$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH"
17+
export PATH="$HOME/.varlock/bin:$HOME/opt/node-v22.14.0-linux-x64/bin:$PATH"
1818

19-
# Load secrets
19+
# Validate and load secrets via varlock
20+
varlock load --path ~/.config/ || {
21+
echo "❌ Environment validation failed — check ~/.config/.env against .env.schema"
22+
exit 1
23+
}
2024
set -a
2125
source ~/.config/.env
2226
set +a

0 commit comments

Comments
 (0)