Skip to content

Commit 531bcfe

Browse files
committed
config: support Ubuntu — replace grep -P with grep -E, add distro-agnostic guideline
1 parent 26fff12 commit 531bcfe

6 files changed

Lines changed: 135 additions & 20 deletions

File tree

.pi/todos/20e26efc.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"id": "20e26efc",
3+
"title": "CI job: run setup + tests on Ubuntu droplet per PR",
4+
"tags": [
5+
"infra",
6+
"ci",
7+
"ubuntu"
8+
],
9+
"status": "todo",
10+
"created_at": "2026-02-17T02:30:52.375Z"
11+
}
12+
13+
## Goal
14+
Add a GitHub Actions workflow that SSHes into the DigitalOcean droplet and runs the full hornet setup + test suite on every PR.
15+
16+
## Depends on
17+
- TODO-cb931656 (manual verification must pass first)
18+
19+
## Design decisions needed
20+
- **Fresh state per run**: `uninstall.sh` at start of each run? Or snapshot/restore? Or ephemeral droplet via DO API?
21+
- **Secrets**: droplet IP + SSH key stored as GitHub Actions secrets (`DROPLET_IP`, `DROPLET_SSH_KEY`)
22+
- **Concurrency**: only one CI run at a time on the droplet (use GitHub concurrency group)
23+
- **Scope**: full setup + test, or just test.sh (setup is slow, ~2-3 min)?
24+
25+
## Proposed workflow
26+
```yaml
27+
name: Integration (Ubuntu)
28+
on:
29+
pull_request:
30+
branches: [main]
31+
32+
concurrency:
33+
group: droplet-integration
34+
cancel-in-progress: true
35+
36+
jobs:
37+
integration:
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@v4
41+
- name: Run on droplet
42+
env:
43+
DROPLET_IP: ${{ secrets.DROPLET_IP }}
44+
SSH_KEY: ${{ secrets.DROPLET_SSH_KEY }}
45+
run: |
46+
# SSH into droplet, rsync repo, run uninstall (clean slate),
47+
# run setup.sh, deploy.sh, test.sh, security-audit.sh
48+
```
49+
50+
## Steps
51+
1. Create SSH key pair for CI, add public key to droplet
52+
2. Add `DROPLET_IP` and `DROPLET_SSH_KEY` as GitHub repo secrets
53+
3. Write the workflow file (`.github/workflows/integration.yml`)
54+
4. Handle cleanup: uninstall.sh at start of run for clean state
55+
5. Fail the PR if any step exits non-zero
56+
6. Consider: also run security-audit.sh (some checks need live system)
57+
58+
## Open questions
59+
- Do we want to spin up/destroy droplets per run (more isolated, costs more) or reuse one?
60+
- Should we test `start.sh` actually booting an agent, or just setup + unit tests?

.pi/todos/cb931656.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"id": "cb931656",
3+
"title": "Verify hornet setup on Ubuntu droplet (manual)",
4+
"tags": [
5+
"infra",
6+
"ubuntu",
7+
"ci"
8+
],
9+
"status": "todo",
10+
"created_at": "2026-02-17T02:30:39.055Z",
11+
"assigned_to_session": "381813d9-c69a-4472-9a00-e232ffb746d1"
12+
}
13+
14+
## Goal
15+
SSH into the DigitalOcean Ubuntu droplet and manually verify the full hornet setup works end-to-end.
16+
17+
## Steps
18+
1. SSH into the box as root
19+
2. Install prerequisites: `git`, `curl`, `docker`, `iptables`, `tmux`
20+
3. Clone the hornet repo
21+
4. Run `setup.sh <admin_user>` — creates `hornet_agent` user, installs Node, pi, firewall, etc.
22+
5. Create a minimal `.env` with dummy/test values (enough to pass varlock validation)
23+
6. Run `bin/deploy.sh` — deploy extensions, skills, bridge to runtime
24+
7. Run `bin/test.sh` — all 207 tests must pass
25+
8. Run `bin/security-audit.sh` — verify firewall, perms, proc isolation
26+
9. Boot the agent: `sudo -u hornet_agent ~/runtime/start.sh` — verify it starts without errors
27+
10. Tear down: run `bin/uninstall.sh` to verify clean removal
28+
29+
## Success criteria
30+
- `setup.sh` completes without errors on Ubuntu
31+
- All tests pass
32+
- Security audit is clean
33+
- Agent boots and varlock validates the env
34+
35+
## Notes
36+
- Need droplet IP, root credentials (store securely, don't commit)
37+
- This is a one-time manual run; the CI todo automates it afterward

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ Add new test files to `bin/test.sh` — don't scatter test invocations across CI
110110
- Skills are deployed from `pi/skills/` → agent's `~/.pi/agent/skills/`.
111111
- Agent commits operational learnings to its own skills dir (not back to source).
112112
- **When changing behavior, update all docs.** Check and update: `README.md`, `CONFIGURATION.md`, skill files (`pi/skills/*/SKILL.md`), and `AGENTS.md`. Inline code examples in docs must match the actual implementation.
113+
- **No distro-specific commands.** Scripts must work on both Arch and Ubuntu (and any standard Linux). Use `grep -E` (not `grep -P`), POSIX-compatible tools, and avoid package manager calls (`pacman`, `apt`, etc.). If a package is needed, document it as a prerequisite rather than auto-installing it.
113114

114115
## Security Notes
115116

bin/harden-permissions.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ if [ -d "$HOME/.pi/agent/sessions" ]; then
5151
fi
5252

5353
# Session logs (full conversation history)
54-
find "$HOME/.pi/agent/sessions" -name '*.jsonl' -not -perm 600 -exec chmod 600 {} + 2>/dev/null && \
55-
count=$(find "$HOME/.pi/agent/sessions" -name '*.jsonl' 2>/dev/null | wc -l) && \
54+
if [ -d "$HOME/.pi/agent/sessions" ]; then
55+
find "$HOME/.pi/agent/sessions" -name '*.jsonl' -not -perm 600 -exec chmod 600 {} + 2>/dev/null || true
56+
count=$(find "$HOME/.pi/agent/sessions" -name '*.jsonl' 2>/dev/null | wc -l)
5657
[ "$count" -gt 0 ] && echo "$count session log(s) → 600"
58+
fi
5759

5860
# Pi settings
5961
fix_file "$HOME/.pi/agent/settings.json" "600"

bin/hornet-safe-bash

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#
66
# This is defense-in-depth — the agent's instructions also prohibit these,
77
# but a successful injection might override soft instructions.
8+
#
9+
# NOTE: Avoid grep -P (Perl regex) — not available on all distros.
10+
# Use grep -E (extended regex) or awk instead.
811

912
# Patterns that should NEVER be executed by the agent
1013
COMMAND="$*"
@@ -16,64 +19,67 @@ block() {
1619
}
1720

1821
# Fork bomb
19-
if echo "$COMMAND" | grep -qP ':\(\)\s*\{.*\|.*&.*\}'; then
22+
if echo "$COMMAND" | grep -qE ':\(\)[[:space:]]*\{.*\|.*&.*\}'; then
2023
block "fork bomb"
2124
fi
2225

2326
# rm -rf / or rm -rf /* (root filesystem deletion)
24-
if echo "$COMMAND" | grep -qP 'rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?(-[a-zA-Z]*r[a-zA-Z]*\s+)?(\/\s*$|\/\*|\/\s+)'; then
27+
if echo "$COMMAND" | grep -qE 'rm[[:space:]]+(-[a-zA-Z]*f[a-zA-Z]*[[:space:]]+)?(-[a-zA-Z]*r[a-zA-Z]*[[:space:]]+)?(/[[:space:]]*$|/\*|/[[:space:]]+)'; then
2528
block "recursive delete of root filesystem"
2629
fi
27-
if echo "$COMMAND" | grep -qP 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?(-[a-zA-Z]*f[a-zA-Z]*\s+)?(\/\s*$|\/\*|\/\s+)'; then
30+
if echo "$COMMAND" | grep -qE 'rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*[[:space:]]+)?(-[a-zA-Z]*f[a-zA-Z]*[[:space:]]+)?(/[[:space:]]*$|/\*|/[[:space:]]+)'; then
2831
block "recursive delete of root filesystem"
2932
fi
3033

3134
# dd writing to block devices
32-
if echo "$COMMAND" | grep -qP 'dd\s+.*of=/dev/(sd|vd|nvme|xvd)'; then
35+
if echo "$COMMAND" | grep -qE 'dd[[:space:]]+.*of=/dev/(sd|vd|nvme|xvd)'; then
3336
block "dd write to block device"
3437
fi
3538

3639
# mkfs on block devices
37-
if echo "$COMMAND" | grep -qP 'mkfs\b.*\/dev\/'; then
40+
if echo "$COMMAND" | grep -qE 'mkfs[^a-zA-Z].*/dev/'; then
3841
block "mkfs on block device"
3942
fi
4043

4144
# chmod 777 on sensitive paths
42-
if echo "$COMMAND" | grep -qP 'chmod\s+(-[a-zA-Z]*\s+)?777\s+(\/|\/etc|\/home|\/root|\/var)'; then
45+
if echo "$COMMAND" | grep -qE 'chmod[[:space:]]+(-[a-zA-Z]*[[:space:]]+)?777[[:space:]]+(/|/etc|/home|/root|/var)'; then
4346
block "chmod 777 on sensitive path"
4447
fi
4548

4649
# Curl/wget piped to shell
47-
if echo "$COMMAND" | grep -qP '(curl|wget)\s+.*\|\s*(ba)?sh'; then
50+
if echo "$COMMAND" | grep -qE '(curl|wget)[[:space:]]+.*\|[[:space:]]*(ba)?sh'; then
4851
block "piping download to shell"
4952
fi
5053

5154
# Reverse shell patterns
52-
if echo "$COMMAND" | grep -qP 'bash\s+-i\s+>(&|\|)\s*/dev/tcp/'; then
55+
if echo "$COMMAND" | grep -qE 'bash[[:space:]]+-i[[:space:]]+>[&|][[:space:]]*/dev/tcp/'; then
5356
block "reverse shell (bash /dev/tcp)"
5457
fi
55-
if echo "$COMMAND" | grep -qP 'nc\s+(-[a-zA-Z]*\s+)*[0-9]+.*-e\s*(\/bin\/)?(ba)?sh'; then
58+
if echo "$COMMAND" | grep -qE 'nc[[:space:]]+(-[a-zA-Z]*[[:space:]]+)*[0-9]+.*-e[[:space:]]*(\/bin\/)?(ba)?sh'; then
5659
block "reverse shell (netcat)"
5760
fi
58-
if echo "$COMMAND" | grep -qP 'python[23]?\s+-c.*socket.*connect.*subprocess'; then
61+
if echo "$COMMAND" | grep -qE 'python[23]?[[:space:]]+-c.*socket.*connect.*subprocess'; then
5962
block "reverse shell (python)"
6063
fi
6164

6265
# crontab modification (persistence)
63-
if echo "$COMMAND" | grep -qP '(crontab\s+-[erl]|echo.*>\s*/etc/cron)'; then
66+
if echo "$COMMAND" | grep -qE '(crontab[[:space:]]+-[erl]|echo.*>[[:space:]]*/etc/cron)'; then
6467
block "crontab modification"
6568
fi
6669

6770
# Modifying /etc/passwd or /etc/shadow
68-
if echo "$COMMAND" | grep -qP '>\s*/etc/(passwd|shadow|sudoers)'; then
71+
if echo "$COMMAND" | grep -qE '>[[:space:]]*/etc/(passwd|shadow|sudoers)'; then
6972
block "write to system auth files"
7073
fi
7174

7275
# SSH key injection to other users
73-
if echo "$COMMAND" | grep -qP '>\s*/home/(?!hornet_agent).*/\.ssh/authorized_keys'; then
74-
block "SSH key injection to another user"
76+
# Can't use negative lookahead without grep -P, so match broadly then exclude our user
77+
if echo "$COMMAND" | grep -qE '>[[:space:]]*/home/.*/.ssh/authorized_keys'; then
78+
if ! echo "$COMMAND" | grep -qE '>[[:space:]]*/home/hornet_agent/.ssh/authorized_keys'; then
79+
block "SSH key injection to another user"
80+
fi
7581
fi
76-
if echo "$COMMAND" | grep -qP '>\s*/root/\.ssh/authorized_keys'; then
82+
if echo "$COMMAND" | grep -qE '>[[:space:]]*/root/.ssh/authorized_keys'; then
7783
block "SSH key injection to root"
7884
fi
7985

setup.sh

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Run as root or with sudo from the admin user account
44
#
55
# Prerequisites:
6-
# - Arch Linux (or similar)
6+
# - Linux (tested on Arch and Ubuntu)
77
# - Docker installed
88
#
99
# This script:
@@ -103,7 +103,16 @@ echo "=== Configuring shared repo permissions ==="
103103
# Set core.sharedRepository=group on all repos so git creates objects
104104
# with group-write perms. Without this, umask 077 in start.sh causes
105105
# new .git/objects to be owner-only, breaking group access (admin user).
106-
for repo in "$REPO_DIR" "$HORNET_HOME/workspace/modem" "$HORNET_HOME/workspace/website"; do
106+
107+
# Source repo — set as admin user (agent can't access admin home, and root
108+
# needs safe.directory due to different ownership)
109+
if [ -d "$REPO_DIR/.git" ]; then
110+
sudo -u "$ADMIN_USER" git -C "$REPO_DIR" config core.sharedRepository group
111+
echo "$REPO_DIR"
112+
fi
113+
114+
# Agent workspace repos — set as agent
115+
for repo in "$HORNET_HOME/workspace/modem" "$HORNET_HOME/workspace/website"; do
107116
if [ -d "$repo/.git" ]; then
108117
sudo -u hornet_agent git -C "$repo" config core.sharedRepository group
109118
echo "$repo"
@@ -232,7 +241,7 @@ fi
232241
echo "Process isolation: hornet_agent can only see its own processes"
233242

234243
echo "=== Hardening permissions ==="
235-
sudo -u hornet_agent bash -c "'$REPO_DIR/bin/harden-permissions.sh'"
244+
sudo -u hornet_agent bash -c "'$HORNET_HOME/runtime/bin/harden-permissions.sh'"
236245

237246
echo ""
238247
echo "=== Setup complete ==="

0 commit comments

Comments
 (0)