{"id":51302288,"url":"https://github.com/peterblenessy/airlocked-agents","last_synced_at":"2026-06-30T21:01:43.136Z","repository":{"id":366997651,"uuid":"1276155390","full_name":"PeterBlenessy/airlocked-agents","owner":"PeterBlenessy","description":"Deterministic IaC for a private, self-hosted AI automation stack — untrusted content stays in the airlock.","archived":false,"fork":false,"pushed_at":"2026-06-24T05:28:21.000Z","size":94,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-24T07:25:18.937Z","etag":null,"topics":["ai-agents","ansible","automation","docker-compose","homelab","infrastructure-as-code","llama-cpp","llm","local-first","mcp","n8n","privacy","prompt-injection","security","self-hosted"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PeterBlenessy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-21T15:57:50.000Z","updated_at":"2026-06-24T05:28:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/PeterBlenessy/airlocked-agents","commit_stats":null,"previous_names":["peterblenessy/airlocked-agents"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/PeterBlenessy/airlocked-agents","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PeterBlenessy%2Fairlocked-agents","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PeterBlenessy%2Fairlocked-agents/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PeterBlenessy%2Fairlocked-agents/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PeterBlenessy%2Fairlocked-agents/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PeterBlenessy","download_url":"https://codeload.github.com/PeterBlenessy/airlocked-agents/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PeterBlenessy%2Fairlocked-agents/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34983171,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","ansible","automation","docker-compose","homelab","infrastructure-as-code","llama-cpp","llm","local-first","mcp","n8n","privacy","prompt-injection","security","self-hosted"],"created_at":"2026-06-30T21:01:41.461Z","updated_at":"2026-06-30T21:01:43.126Z","avatar_url":"https://github.com/PeterBlenessy.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# airlocked-agents\n\n**Deterministic IaC for a private, self-hosted AI automation stack — untrusted content stays in the airlock.**\n\n\u003e **Refocus in progress (2026-06):** this is becoming the **local, no-inbound automation + capture layer for [Notesage](https://github.com/PeterBlenessy/note-sage)** — n8n for scheduled/event jobs + Telegram mobile capture, a local **Gemma** model for summarising, writing into a folder Notesage indexes. **Khoj and the bundled \"second brain\" are dropped** (Notesage is that, installed separately). See [`ARCHITECTURE.md`](ARCHITECTURE.md) for the new design and open questions; the sections below still describe the older bundled stack and are being rewritten.\n\nIdempotent setup of the **automation + capture layer for Notesage** on **one dedicated, always-on\nMac mini**: a local **Gemma** model (llama.cpp) + **n8n** for scheduled/event jobs and **Telegram\nmobile capture**, writing summaries into a folder Notesage indexes — with **no public inbound**\n(Telegram is polled). Notesage (the second brain) is installed separately.\nClaude + MCP are used cloud-side for public work only.\n\nThe whole point of this repo is to keep one rule true automatically: **no single component reads untrusted content, holds your credentials, and can send — all at once.**\n\n\u003e What each component is and why it was chosen: see [`COMPONENTS.md`](COMPONENTS.md).\n\u003e Architecture and data flows (single-box): see [`ARCHITECTURE.md`](ARCHITECTURE.md).\n\u003e Threat model and how to report issues: see [`SECURITY.md`](SECURITY.md).\n\u003e How to contribute: [`CONTRIBUTING.md`](CONTRIBUTING.md) · [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).\n\n**Topics** (set automatically by `make repo`, or add them in the GitHub UI):\n`infrastructure-as-code` · `ansible` · `docker-compose` · `self-hosted` · `ai-agents` · `privacy` · `security` · `llm` · `local-first` · `n8n` · `mcp` · `homelab` · `prompt-injection` · `automation` · `llama-cpp`\n\nNot to be confused with [`NevaMind-AI/airlock`](https://github.com/NevaMind-AI/airlock) — that's a Python egress-guard *library* you embed; this is a deployed *stack* you run.\n\n---\n\n## What is deterministic vs. what is manual (read this first)\n\nHonest IaC draws the line clearly. This repo does too.\n\n**Fully deterministic + idempotent (run as many times as you like):**\n- All package installs (Homebrew) and the container runtime (Apple `container`, or Colima).\n- llama.cpp (Gemma) as a native launchd service; n8n as a container with a localhost-only UI.\n- The llama→container gateway bridge; the n8n workflow import.\n- Service enablement (launchd) and the capture-folder mount.\n\n**Manual, one-time, by design (cannot be deterministic — interactive consent):**\n- **Telegram bot token** — created interactively via `@BotFather` (reached by polling, no webhook).\n- **n8n owner account** — created once in the n8n UI on first visit (localhost).\n- **Gmail OAuth** — Google requires browser consent; create the OAuth app, paste client id/secret, authorize once in the n8n UI.\n- **Model download** — scriptable (`make model`) but a multi-GB file, so opt-in.\n\nEverything manual is reduced to filling `.env` and the short checklist in `docs-MANUAL-STEPS.md`. Nothing secret is ever committed.\n\n---\n\n## Prerequisites\n\n- A **dedicated, always-on Mac mini** (Apple Silicon) with Homebrew. Enough RAM for the Gemma model + n8n (16 GB is comfortable).\n- macOS 26+ for the default container runtime (**Apple `container`**, installed for you). On older macOS it falls back to **Colima**; set `CONTAINER_RUNTIME=docker` to use Docker Desktop instead.\n- `ansible` and `make` (`brew install ansible make`) — or just run `make setup`, which installs what's missing.\n- Your llama.cpp model file (or run `make model` to fetch one).\n\n## Layout\n\n```\nairlocked-agents/\n├── Makefile                 # the entrypoint — see `make help`\n├── README.md\n├── CONTRIBUTING.md          # the security-first contribution bar\n├── LICENSE                  # MIT\n├── docs-MANUAL-STEPS.md     # the irreducible interactive steps\n├── .env.example             # copy to .env and fill in\n├── .devcontainer/           # Linux dev env mirroring CI (lint/validate the IaC locally)\n│   └── devcontainer.json\n├── .github/workflows/\n│   └── ci.yml               # syntax + compose + json/shell checks + secret guard\n├── ansible/\n│   ├── inventory.ini        # localhost (the Mac mini) only\n│   └── mac.yml              # llama.cpp (Gemma) + n8n + launchd\n├── compose/\n│   └── n8n.yml              # n8n, localhost-only (Colima/Docker fallback path)\n├── mac/\n│   ├── Brewfile             # declarative Mac packages\n│   ├── com.local.llama-server.plist.tmpl    # the Gemma model, 127.0.0.1 only\n│   ├── n8n-runtime.sh       # n8n up/down + workflow import (Apple container / compose)\n│   └── download-model.sh\n├── n8n/\n│   ├── allowlist.code.js    # canonical chat-id + recipient guard (embedded in workflows)\n│   ├── workflow.capture.json      # Telegram mobile capture → folder (skeleton)\n│   ├── workflow.read-path.json    # importable skeleton (verify in editor)\n│   └── workflow.write-path.json   # write path with the guard inlined\n├── scripts/\n│   ├── setup.sh             # the guided installer (`make setup`) — start here\n│   ├── teardown.sh          # reverses setup via its manifest (`make teardown`)\n│   ├── verify.sh            # health + boundary checks (binding, no-egress, guard test)\n│   ├── injection-selftest.sh      # automated guard unit test (run by make verify)\n│   ├── bootstrap-repo.sh    # git init + gh repo create + push (with secret guard)\n│   └── injection-selftest.md      # the manual end-to-end injection test\n└── secrets/                 # gitignored; your keys live here, never committed\n\n* Cua install is best-effort (the npm package name is unverified upstream); see ansible/mac.yml.\n```\n\n## Publishing to GitHub\n\n```bash\nmake repo                       # uses the gh CLI: init, commit, create, push\n# or with options:\nmake repo NAME=my-stack VIS=public\n```\n\nThe bootstrap script refuses to commit if any secret-like file (`.env`, keys, rendered\n`*.conf`) is staged, and asks for confirmation before creating the remote. CI then runs on\nevery PR (`.github/workflows/ci.yml`).\n\n## Quick start\n\nThe low-friction path is one command — a guided installer that checks your system, offers to\ninstall what's missing, collects config with live validation, and brings up the whole local stack:\n\n```bash\nmake setup                    # interactive, idempotent — start here\n```\n\n`make doctor` reports what's present/missing without changing anything. Re-run `make setup`\nany time to change values or finish skipped steps.\n\nNervous about running it? **`make setup-dryrun`** runs the whole wizard and prints exactly what it\n*would* install, change, and write to `.env` — installing nothing, writing nothing, touching nothing.\n\n\u003cdetails\u003e\n\u003csummary\u003ePrefer to drive it yourself? The individual targets:\u003c/summary\u003e\n\n```bash\ncp .env.example .env          # then edit .env with your values\nmake help                     # list targets\nmake model                    # download the GGUF model (multi-GB, opt-in)\nmake mac                      # provision the mini: llama.cpp (Gemma) + n8n (idempotent)\nmake workflows                # (re)import the n8n workflow skeletons\nmake verify                   # health + boundary checks\n```\n\u003c/details\u003e\n\nEach target is safe to re-run; Ansible converges to the declared state. To stop the container stacks: `make down`.\n\n### Uninstall / rollback\n\n`make setup` writes a **transactional manifest** (`.airlocked/manifest.tsv`, gitignored) recording\nevery change it makes — and it records a package as installed *only if it was absent beforehand*.\n\n```bash\nmake teardown     # replays the manifest in reverse, undoing only what setup did\n```\n\nBecause it follows the manifest rather than guessing, `make teardown` removes the services,\ncontainers, volumes, launchd agents, model file, and the container runtime that setup created — but\n**never uninstalls tools you already had**. Every destructive step asks first. (`make down` just\nstops the container stacks; `make teardown` is the full reversal.)\n\nWhat stays manual even with `make setup`: the interactive consent steps that *cannot* be\nautomated — creating the Telegram bot in `@BotFather`, the n8n owner account, and clicking\n**Connect** on the Gmail OAuth credential in the n8n editor. `make setup` walks you up to each and\nvalidates what it can (e.g. it tests your Telegram token and auto-detects your chat id).\n\n## The determinism guarantee, restated\n\n`make mac` converges the same way every time from a clean mini, given the same `.env`. The only\nthings it cannot create for you are the interactive secrets above — a property of OAuth and bot\nregistration, not a limitation of this repo. See `docs-MANUAL-STEPS.md`.\n\n## Security notes baked in\n\n- All services listen on `127.0.0.1` or the host-only container-network gateway (the llama bridge) — **never a public interface, and no public inbound** (Telegram is polled). Verified by `make verify`.\n- n8n holds no secrets and only **writes** to the capture folder (the airlock); Notesage only **reads** it. Captured web content can't exfiltrate because the reader has no send path.\n- The allowlist guard (chat-id) is embedded in the workflows (canonical source: `n8n/allowlist.code.js`), so only you can submit captures/commands.\n- Sensitive work routes to the local model; nothing private egresses to the cloud.\n- `make verify` runs the allowlist guard self-test (`scripts/injection-selftest.sh`); the full end-to-end prompt-injection test is manual — see `scripts/injection-selftest.md`.\n\nCommands and image tags drift; pin digests in `.env` and re-run `make verify` after upgrades.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterblenessy%2Fairlocked-agents","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpeterblenessy%2Fairlocked-agents","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpeterblenessy%2Fairlocked-agents/lists"}