From 10af489ba4552e21d260bd4bfea9b5e5783ce139 Mon Sep 17 00:00:00 2001 From: Ben Tossell Date: Mon, 16 Feb 2026 21:30:17 -0500 Subject: [PATCH 1/4] =?UTF-8?q?config:=20support=20Ubuntu=20=E2=80=94=20re?= =?UTF-8?q?place=20grep=20-P=20with=20grep=20-E,=20add=20distro-agnostic?= =?UTF-8?q?=20guideline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pi/todos/20e26efc.md | 60 +++++++++++++++++++++++++++++++++++++++ .pi/todos/cb931656.md | 37 ++++++++++++++++++++++++ AGENTS.md | 1 + bin/harden-permissions.sh | 6 ++-- bin/hornet-safe-bash | 36 +++++++++++++---------- setup.sh | 19 +++++++++++-- 6 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 .pi/todos/20e26efc.md create mode 100644 .pi/todos/cb931656.md diff --git a/.pi/todos/20e26efc.md b/.pi/todos/20e26efc.md new file mode 100644 index 0000000..91d42e6 --- /dev/null +++ b/.pi/todos/20e26efc.md @@ -0,0 +1,60 @@ +{ + "id": "20e26efc", + "title": "CI job: run setup + tests on Ubuntu droplet per PR", + "tags": [ + "infra", + "ci", + "ubuntu" + ], + "status": "todo", + "created_at": "2026-02-17T02:30:52.375Z" +} + +## Goal +Add a GitHub Actions workflow that SSHes into the DigitalOcean droplet and runs the full hornet setup + test suite on every PR. + +## Depends on +- TODO-cb931656 (manual verification must pass first) + +## Design decisions needed +- **Fresh state per run**: `uninstall.sh` at start of each run? Or snapshot/restore? Or ephemeral droplet via DO API? +- **Secrets**: droplet IP + SSH key stored as GitHub Actions secrets (`DROPLET_IP`, `DROPLET_SSH_KEY`) +- **Concurrency**: only one CI run at a time on the droplet (use GitHub concurrency group) +- **Scope**: full setup + test, or just test.sh (setup is slow, ~2-3 min)? + +## Proposed workflow +```yaml +name: Integration (Ubuntu) +on: + pull_request: + branches: [main] + +concurrency: + group: droplet-integration + cancel-in-progress: true + +jobs: + integration: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run on droplet + env: + DROPLET_IP: ${{ secrets.DROPLET_IP }} + SSH_KEY: ${{ secrets.DROPLET_SSH_KEY }} + run: | + # SSH into droplet, rsync repo, run uninstall (clean slate), + # run setup.sh, deploy.sh, test.sh, security-audit.sh +``` + +## Steps +1. Create SSH key pair for CI, add public key to droplet +2. Add `DROPLET_IP` and `DROPLET_SSH_KEY` as GitHub repo secrets +3. Write the workflow file (`.github/workflows/integration.yml`) +4. Handle cleanup: uninstall.sh at start of run for clean state +5. Fail the PR if any step exits non-zero +6. Consider: also run security-audit.sh (some checks need live system) + +## Open questions +- Do we want to spin up/destroy droplets per run (more isolated, costs more) or reuse one? +- Should we test `start.sh` actually booting an agent, or just setup + unit tests? diff --git a/.pi/todos/cb931656.md b/.pi/todos/cb931656.md new file mode 100644 index 0000000..c34efbb --- /dev/null +++ b/.pi/todos/cb931656.md @@ -0,0 +1,37 @@ +{ + "id": "cb931656", + "title": "Verify hornet setup on Ubuntu droplet (manual)", + "tags": [ + "infra", + "ubuntu", + "ci" + ], + "status": "todo", + "created_at": "2026-02-17T02:30:39.055Z", + "assigned_to_session": "381813d9-c69a-4472-9a00-e232ffb746d1" +} + +## Goal +SSH into the DigitalOcean Ubuntu droplet and manually verify the full hornet setup works end-to-end. + +## Steps +1. SSH into the box as root +2. Install prerequisites: `git`, `curl`, `docker`, `iptables`, `tmux` +3. Clone the hornet repo +4. Run `setup.sh ` — creates `hornet_agent` user, installs Node, pi, firewall, etc. +5. Create a minimal `.env` with dummy/test values (enough to pass varlock validation) +6. Run `bin/deploy.sh` — deploy extensions, skills, bridge to runtime +7. Run `bin/test.sh` — all 207 tests must pass +8. Run `bin/security-audit.sh` — verify firewall, perms, proc isolation +9. Boot the agent: `sudo -u hornet_agent ~/runtime/start.sh` — verify it starts without errors +10. Tear down: run `bin/uninstall.sh` to verify clean removal + +## Success criteria +- `setup.sh` completes without errors on Ubuntu +- All tests pass +- Security audit is clean +- Agent boots and varlock validates the env + +## Notes +- Need droplet IP, root credentials (store securely, don't commit) +- This is a one-time manual run; the CI todo automates it afterward diff --git a/AGENTS.md b/AGENTS.md index 37be779..4419597 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,6 +110,7 @@ Add new test files to `bin/test.sh` — don't scatter test invocations across CI - Skills are deployed from `pi/skills/` → agent's `~/.pi/agent/skills/`. - Agent commits operational learnings to its own skills dir (not back to source). - **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. +- **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. ## Security Notes diff --git a/bin/harden-permissions.sh b/bin/harden-permissions.sh index 26ddf4c..6a492db 100755 --- a/bin/harden-permissions.sh +++ b/bin/harden-permissions.sh @@ -51,9 +51,11 @@ if [ -d "$HOME/.pi/agent/sessions" ]; then fi # Session logs (full conversation history) -find "$HOME/.pi/agent/sessions" -name '*.jsonl' -not -perm 600 -exec chmod 600 {} + 2>/dev/null && \ - count=$(find "$HOME/.pi/agent/sessions" -name '*.jsonl' 2>/dev/null | wc -l) && \ +if [ -d "$HOME/.pi/agent/sessions" ]; then + find "$HOME/.pi/agent/sessions" -name '*.jsonl' -not -perm 600 -exec chmod 600 {} + 2>/dev/null || true + count=$(find "$HOME/.pi/agent/sessions" -name '*.jsonl' 2>/dev/null | wc -l) [ "$count" -gt 0 ] && echo " ✓ $count session log(s) → 600" +fi # Pi settings fix_file "$HOME/.pi/agent/settings.json" "600" diff --git a/bin/hornet-safe-bash b/bin/hornet-safe-bash index 5ede7f6..e255e1a 100755 --- a/bin/hornet-safe-bash +++ b/bin/hornet-safe-bash @@ -5,6 +5,9 @@ # # This is defense-in-depth — the agent's instructions also prohibit these, # but a successful injection might override soft instructions. +# +# NOTE: Avoid grep -P (Perl regex) — not available on all distros. +# Use grep -E (extended regex) or awk instead. # Patterns that should NEVER be executed by the agent COMMAND="$*" @@ -16,64 +19,67 @@ block() { } # Fork bomb -if echo "$COMMAND" | grep -qP ':\(\)\s*\{.*\|.*&.*\}'; then +if echo "$COMMAND" | grep -qE ':\(\)[[:space:]]*\{.*\|.*&.*\}'; then block "fork bomb" fi # rm -rf / or rm -rf /* (root filesystem deletion) -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 +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 block "recursive delete of root filesystem" fi -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 +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 block "recursive delete of root filesystem" fi # dd writing to block devices -if echo "$COMMAND" | grep -qP 'dd\s+.*of=/dev/(sd|vd|nvme|xvd)'; then +if echo "$COMMAND" | grep -qE 'dd[[:space:]]+.*of=/dev/(sd|vd|nvme|xvd)'; then block "dd write to block device" fi # mkfs on block devices -if echo "$COMMAND" | grep -qP 'mkfs\b.*\/dev\/'; then +if echo "$COMMAND" | grep -qE 'mkfs[^a-zA-Z].*/dev/'; then block "mkfs on block device" fi # chmod 777 on sensitive paths -if echo "$COMMAND" | grep -qP 'chmod\s+(-[a-zA-Z]*\s+)?777\s+(\/|\/etc|\/home|\/root|\/var)'; then +if echo "$COMMAND" | grep -qE 'chmod[[:space:]]+(-[a-zA-Z]*[[:space:]]+)?777[[:space:]]+(/|/etc|/home|/root|/var)'; then block "chmod 777 on sensitive path" fi # Curl/wget piped to shell -if echo "$COMMAND" | grep -qP '(curl|wget)\s+.*\|\s*(ba)?sh'; then +if echo "$COMMAND" | grep -qE '(curl|wget)[[:space:]]+.*\|[[:space:]]*(ba)?sh'; then block "piping download to shell" fi # Reverse shell patterns -if echo "$COMMAND" | grep -qP 'bash\s+-i\s+>(&|\|)\s*/dev/tcp/'; then +if echo "$COMMAND" | grep -qE 'bash[[:space:]]+-i[[:space:]]+>[&|][[:space:]]*/dev/tcp/'; then block "reverse shell (bash /dev/tcp)" fi -if echo "$COMMAND" | grep -qP 'nc\s+(-[a-zA-Z]*\s+)*[0-9]+.*-e\s*(\/bin\/)?(ba)?sh'; then +if echo "$COMMAND" | grep -qE 'nc[[:space:]]+(-[a-zA-Z]*[[:space:]]+)*[0-9]+.*-e[[:space:]]*(\/bin\/)?(ba)?sh'; then block "reverse shell (netcat)" fi -if echo "$COMMAND" | grep -qP 'python[23]?\s+-c.*socket.*connect.*subprocess'; then +if echo "$COMMAND" | grep -qE 'python[23]?[[:space:]]+-c.*socket.*connect.*subprocess'; then block "reverse shell (python)" fi # crontab modification (persistence) -if echo "$COMMAND" | grep -qP '(crontab\s+-[erl]|echo.*>\s*/etc/cron)'; then +if echo "$COMMAND" | grep -qE '(crontab[[:space:]]+-[erl]|echo.*>[[:space:]]*/etc/cron)'; then block "crontab modification" fi # Modifying /etc/passwd or /etc/shadow -if echo "$COMMAND" | grep -qP '>\s*/etc/(passwd|shadow|sudoers)'; then +if echo "$COMMAND" | grep -qE '>[[:space:]]*/etc/(passwd|shadow|sudoers)'; then block "write to system auth files" fi # SSH key injection to other users -if echo "$COMMAND" | grep -qP '>\s*/home/(?!hornet_agent).*/\.ssh/authorized_keys'; then - block "SSH key injection to another user" +# Can't use negative lookahead without grep -P, so match broadly then exclude our user +if echo "$COMMAND" | grep -qE '>[[:space:]]*/home/.*/.ssh/authorized_keys'; then + if ! echo "$COMMAND" | grep -qE '>[[:space:]]*/home/hornet_agent/.ssh/authorized_keys'; then + block "SSH key injection to another user" + fi fi -if echo "$COMMAND" | grep -qP '>\s*/root/\.ssh/authorized_keys'; then +if echo "$COMMAND" | grep -qE '>[[:space:]]*/root/.ssh/authorized_keys'; then block "SSH key injection to root" fi diff --git a/setup.sh b/setup.sh index 6e0dd51..2db7e29 100755 --- a/setup.sh +++ b/setup.sh @@ -3,7 +3,7 @@ # Run as root or with sudo from the admin user account # # Prerequisites: -# - Arch Linux (or similar) +# - Linux (tested on Arch and Ubuntu) # - Docker installed # # This script: @@ -35,6 +35,10 @@ HORNET_HOME="/home/hornet_agent" REPO_DIR="$(cd "$(dirname "$0")" && pwd)" NODE_VERSION="22.14.0" +# Work from a neutral directory — sudo -u hornet_agent inherits CWD, and +# git/find fail if CWD is a directory the agent can't access (e.g. /root). +cd /tmp + echo "=== Creating hornet_agent user ===" if id hornet_agent &>/dev/null; then echo "User already exists, skipping" @@ -103,7 +107,16 @@ echo "=== Configuring shared repo permissions ===" # Set core.sharedRepository=group on all repos so git creates objects # with group-write perms. Without this, umask 077 in start.sh causes # new .git/objects to be owner-only, breaking group access (admin user). -for repo in "$REPO_DIR" "$HORNET_HOME/workspace/modem" "$HORNET_HOME/workspace/website"; do + +# Source repo — set as admin user (agent can't access admin home, and root +# needs safe.directory due to different ownership) +if [ -d "$REPO_DIR/.git" ]; then + sudo -u "$ADMIN_USER" git -C "$REPO_DIR" config core.sharedRepository group + echo " ✓ $REPO_DIR" +fi + +# Agent workspace repos — set as agent +for repo in "$HORNET_HOME/workspace/modem" "$HORNET_HOME/workspace/website"; do if [ -d "$repo/.git" ]; then sudo -u hornet_agent git -C "$repo" config core.sharedRepository group echo " ✓ $repo" @@ -232,7 +245,7 @@ fi echo "Process isolation: hornet_agent can only see its own processes" echo "=== Hardening permissions ===" -sudo -u hornet_agent bash -c "'$REPO_DIR/bin/harden-permissions.sh'" +sudo -u hornet_agent bash -c "cd ~ && '$HORNET_HOME/runtime/bin/harden-permissions.sh'" echo "" echo "=== Setup complete ===" From 99aca3b3030456e9f82fa54270c2aa7bde3da835 Mon Sep 17 00:00:00 2001 From: Ben Tossell Date: Mon, 16 Feb 2026 22:02:30 -0500 Subject: [PATCH 2/4] ci: add integration tests on ephemeral DigitalOcean droplets - bin/ci/droplet.sh: reusable droplet lifecycle (create/destroy/wait-ssh/run) - bin/ci/setup-ubuntu.sh: Ubuntu prereqs + setup.sh + test suite - .github/workflows/integration.yml: matrix-based workflow (Ubuntu now, extensible to Arch etc) - Ephemeral everything: fresh droplet + SSH key per run, destroyed on cleanup - Single secret: DO_API_TOKEN --- .github/workflows/integration.yml | 83 +++++++++++++++ .pi/todos/20e26efc.md | 92 +++++++++++------ .pi/todos/cb931656.md | 43 ++++---- AGENTS.md | 3 + bin/ci/droplet.sh | 166 ++++++++++++++++++++++++++++++ bin/ci/setup-ubuntu.sh | 47 +++++++++ 6 files changed, 380 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/integration.yml create mode 100755 bin/ci/droplet.sh create mode 100755 bin/ci/setup-ubuntu.sh diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000..5e15ea7 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,83 @@ +name: Integration + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: integration-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + integration: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - distro: ubuntu + image: ubuntu-24-04-x64 + setup_script: bin/ci/setup-ubuntu.sh + # To add Arch (or other distros), add entries here: + # - distro: arch + # image: arch-linux-x64 # or a custom snapshot ID + # setup_script: bin/ci/setup-arch.sh + + name: ${{ matrix.distro }} + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - name: Generate ephemeral SSH key + run: | + mkdir -p ~/.ssh + ssh-keygen -t ed25519 -f ~/.ssh/ci_key -N "" -q + + - name: Create droplet + id: droplet + env: + DO_API_TOKEN: ${{ secrets.DO_API_TOKEN }} + run: | + output=$(bash bin/ci/droplet.sh create \ + "ci-${{ matrix.distro }}-${{ github.run_id }}" \ + "${{ matrix.image }}" \ + ~/.ssh/ci_key.pub) + echo "$output" >> "$GITHUB_OUTPUT" + echo "$output" + + - name: Wait for SSH + env: + DO_API_TOKEN: ${{ secrets.DO_API_TOKEN }} + run: | + bash bin/ci/droplet.sh wait-ssh \ + "${{ steps.droplet.outputs.DROPLET_IP }}" \ + ~/.ssh/ci_key + + - name: Upload source + run: | + tar czf /tmp/hornet-src.tar.gz \ + --exclude=node_modules --exclude=.git . + scp -o StrictHostKeyChecking=no -o BatchMode=yes \ + -i ~/.ssh/ci_key \ + /tmp/hornet-src.tar.gz \ + "root@${{ steps.droplet.outputs.DROPLET_IP }}:/tmp/hornet-src.tar.gz" + + - name: Setup and test + run: | + bash bin/ci/droplet.sh run \ + "${{ steps.droplet.outputs.DROPLET_IP }}" \ + ~/.ssh/ci_key \ + "${{ matrix.setup_script }}" + + - name: Cleanup + if: always() + env: + DO_API_TOKEN: ${{ secrets.DO_API_TOKEN }} + run: | + bash bin/ci/droplet.sh destroy \ + "${{ steps.droplet.outputs.DROPLET_ID }}" \ + "${{ steps.droplet.outputs.SSH_KEY_ID }}" diff --git a/.pi/todos/20e26efc.md b/.pi/todos/20e26efc.md index 91d42e6..b2374bb 100644 --- a/.pi/todos/20e26efc.md +++ b/.pi/todos/20e26efc.md @@ -1,28 +1,58 @@ { "id": "20e26efc", - "title": "CI job: run setup + tests on Ubuntu droplet per PR", + "title": "CI job: run setup + tests on fresh Ubuntu droplet per PR", "tags": [ "infra", "ci", "ubuntu" ], "status": "todo", - "created_at": "2026-02-17T02:30:52.375Z" + "created_at": "2026-02-17T02:30:52.375Z", + "assigned_to_session": "381813d9-c69a-4472-9a00-e232ffb746d1" } ## Goal -Add a GitHub Actions workflow that SSHes into the DigitalOcean droplet and runs the full hornet setup + test suite on every PR. +Add a GitHub Actions workflow that spins up a fresh DigitalOcean droplet, runs the full hornet setup + test suite, and destroys it — on every PR. -## Depends on -- TODO-cb931656 (manual verification must pass first) +## Verified approach +Tested manually — full destroy/create/setup/test cycle works end-to-end in ~2 minutes. -## Design decisions needed -- **Fresh state per run**: `uninstall.sh` at start of each run? Or snapshot/restore? Or ephemeral droplet via DO API? -- **Secrets**: droplet IP + SSH key stored as GitHub Actions secrets (`DROPLET_IP`, `DROPLET_SSH_KEY`) -- **Concurrency**: only one CI run at a time on the droplet (use GitHub concurrency group) -- **Scope**: full setup + test, or just test.sh (setup is slow, ~2-3 min)? +| Step | Method | Time | +|------|--------|------| +| Fresh env | Create droplet via DO API | ~45s | +| Wait for SSH | Poll until port 22 responds | ~15s | +| Prereqs | `apt-get install git curl tmux iptables docker.io` (wait for unattended-upgrades lock first) | ~20s | +| Source | `tar + scp + extract + git init` | ~5s | +| Setup | `setup.sh hornet_admin` | ~60s | +| Tests | `npm install + bin/test.sh` | ~15s | +| Cleanup | Destroy droplet + delete SSH key from DO (always, even on failure) | instant | -## Proposed workflow +## GitHub Actions secrets needed +- `DO_API_TOKEN` — DigitalOcean API token + +That's it. SSH keys are **ephemeral per run**: +1. `ssh-keygen` a throwaway ed25519 keypair on the runner +2. Register pubkey with DO API → get `ssh_key_id` +3. Create droplet with that `ssh_key_id` +4. Use the private key to SSH in +5. Cleanup: destroy droplet AND delete SSH key from DO account + +No persistent SSH keys stored anywhere. + +## Design +- **Ephemeral everything**: fresh droplet, fresh SSH key, destroyed after. Zero state between runs. +- **Concurrency group**: only one integration run at a time (avoid parallel droplets piling up). +- **Always cleanup**: use `if: always()` so droplet + SSH key are destroyed even if tests fail. +- **Region/size**: `tor1`, `s-2vcpu-4gb` (~$0.003/run at ~2 min). +- **Unattended-upgrades**: fresh Ubuntu runs apt on first boot — CI must wait for the dpkg lock. + +## Steps +1. Add `DO_API_TOKEN` secret to GitHub repo via `gh secret set` +2. Write `.github/workflows/integration.yml` +3. Test on a real PR +4. Optionally add status badge to README + +## Workflow skeleton ```yaml name: Integration (Ubuntu) on: @@ -30,7 +60,7 @@ on: branches: [main] concurrency: - group: droplet-integration + group: integration-ubuntu cancel-in-progress: true jobs: @@ -38,23 +68,25 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Run on droplet - env: - DROPLET_IP: ${{ secrets.DROPLET_IP }} - SSH_KEY: ${{ secrets.DROPLET_SSH_KEY }} + - name: Generate ephemeral SSH key + run: | + ssh-keygen -t ed25519 -f ~/.ssh/ci_key -N "" -q + # Register with DO, save key ID + - name: Create droplet + # POST /v2/droplets with ssh_key_id, poll until active, extract IP + - name: Wait for SSH + # Loop ssh -o ConnectTimeout=5 until success + - name: Install prereqs + # Wait for apt lock, then apt-get install + - name: Upload source + # tar + scp + - name: Run setup + # setup.sh hornet_admin + - name: Run tests + # npm install + bin/test.sh + - name: Cleanup + if: always() run: | - # SSH into droplet, rsync repo, run uninstall (clean slate), - # run setup.sh, deploy.sh, test.sh, security-audit.sh + # DELETE /v2/droplets/$DROPLET_ID + # DELETE /v2/account/keys/$SSH_KEY_ID ``` - -## Steps -1. Create SSH key pair for CI, add public key to droplet -2. Add `DROPLET_IP` and `DROPLET_SSH_KEY` as GitHub repo secrets -3. Write the workflow file (`.github/workflows/integration.yml`) -4. Handle cleanup: uninstall.sh at start of run for clean state -5. Fail the PR if any step exits non-zero -6. Consider: also run security-audit.sh (some checks need live system) - -## Open questions -- Do we want to spin up/destroy droplets per run (more isolated, costs more) or reuse one? -- Should we test `start.sh` actually booting an agent, or just setup + unit tests? diff --git a/.pi/todos/cb931656.md b/.pi/todos/cb931656.md index c34efbb..368293f 100644 --- a/.pi/todos/cb931656.md +++ b/.pi/todos/cb931656.md @@ -6,32 +6,27 @@ "ubuntu", "ci" ], - "status": "todo", - "created_at": "2026-02-17T02:30:39.055Z", - "assigned_to_session": "381813d9-c69a-4472-9a00-e232ffb746d1" + "status": "done", + "created_at": "2026-02-17T02:30:39.055Z" } -## Goal -SSH into the DigitalOcean Ubuntu droplet and manually verify the full hornet setup works end-to-end. +## Result +Verified on DigitalOcean Ubuntu 24.04 droplet (4GB RAM, 2 vCPU). -## Steps -1. SSH into the box as root -2. Install prerequisites: `git`, `curl`, `docker`, `iptables`, `tmux` -3. Clone the hornet repo -4. Run `setup.sh ` — creates `hornet_agent` user, installs Node, pi, firewall, etc. -5. Create a minimal `.env` with dummy/test values (enough to pass varlock validation) -6. Run `bin/deploy.sh` — deploy extensions, skills, bridge to runtime -7. Run `bin/test.sh` — all 207 tests must pass -8. Run `bin/security-audit.sh` — verify firewall, perms, proc isolation -9. Boot the agent: `sudo -u hornet_agent ~/runtime/start.sh` — verify it starts without errors -10. Tear down: run `bin/uninstall.sh` to verify clean removal +### Bugs found and fixed +1. **`setup.sh` — shared repo permissions**: ran `git config` as `hornet_agent` on admin's repo → agent can't access admin home. Fix: run as `$ADMIN_USER` instead. +2. **`setup.sh` — harden-permissions path**: called source copy (`$REPO_DIR/bin/harden-permissions.sh`) which agent can't access. Fix: use deployed copy (`$HORNET_HOME/runtime/bin/`). +3. **`setup.sh` — CWD inheritance**: `sudo -u hornet_agent` inherits root's CWD, causing `find`/`git` failures. Fix: `cd /tmp` at top of script. +4. **`harden-permissions.sh` — sessions dir**: `find` on non-existent sessions dir fails under `set -e`. Fix: wrap in `if [ -d ... ]` guard. +5. **`hornet-safe-bash` — `grep -P`**: Perl regex not reliably available on Ubuntu. Fix: all patterns converted to `grep -E`. -## Success criteria -- `setup.sh` completes without errors on Ubuntu -- All tests pass -- Security audit is clean -- Agent boots and varlock validates the env +### Verification results +- ✅ `setup.sh` completes cleanly (user, Node, pi, firewall, hidepid, deploy, harden) +- ✅ All 5 test suites pass (tool-guard, bridge security, extension scanner, safe-bash, log redaction) +- ✅ Varlock validates env schema +- ✅ `start.sh` boots pi agent (fails at API auth with dummy keys — expected) +- ✅ Firewall active with all rules +- ✅ Process isolation working -## Notes -- Need droplet IP, root credentials (store securely, don't commit) -- This is a one-time manual run; the CI todo automates it afterward +### PR +https://github.com/modem-dev/hornet/pull/10 diff --git a/AGENTS.md b/AGENTS.md index 4419597..6d3a06b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,9 @@ bin/ security & operations scripts harden-permissions.sh filesystem hardening (runs on boot) scan-extensions.mjs extension static analysis redact-logs.sh secret scrubber for session logs + ci/ CI integration scripts + droplet.sh ephemeral DigitalOcean droplet lifecycle (create/destroy/ssh) + setup-ubuntu.sh Ubuntu droplet: prereqs + setup + tests hooks/ pre-commit blocks agent from modifying security files in git pi/ diff --git a/bin/ci/droplet.sh b/bin/ci/droplet.sh new file mode 100755 index 0000000..a859406 --- /dev/null +++ b/bin/ci/droplet.sh @@ -0,0 +1,166 @@ +#!/bin/bash +# Manage ephemeral DigitalOcean droplets for CI. +# +# Usage: +# bin/ci/droplet.sh create +# bin/ci/droplet.sh destroy [ssh_key_id] +# bin/ci/droplet.sh wait-ssh +# bin/ci/droplet.sh run