@the-keep/keep
v0.1.6
Published
The keep's machinery — the versioned engine a charter instance pins. Ships the keep, igor, and almanac commands.
Downloads
218
Readme
The Keep
Infrastructure-as-record for the Keep — the always-on orchestrator box where Igor (the research agent) runs experiments and pushes results to the Almanac (the reports website).
This repo is the reproducible definition of the Keep. Clone it onto a fresh box,
run setup.sh, supply the secrets, and you have the Keep again — on any provider.
The universe (naming map)
| Name | What it is | Status | |-------------|---------------------------------------------------------|--------| | Igor | The agent (Claude Code) in a scoped container | ✅ base image + auth (seal) working; phone-steerable | | Keep | The always-on server / this infra | ✅ provisioned, hardened, Docker installed | | rampart | The firewall around the Keep | ✅ applied + verified | | the seal| Shared Claude auth every Igor mounts (see docs/igor.md) | ✅ working (lives on box, never committed) | | Almanac | The reports website (results published here) | not yet built | | workbench | Igor's tools / skills | not yet built |
See docs/igor.md for the Igor subsystem (base image, the seal,
Remote Control) and docs/hardening.md for the host
hardening.
Reproducing the Keep on a new machine
Setup happens in phases. Phase 0 is manual (web console / local machine); Phase 1 bootstraps access; Phase 2 is the scripted on-box setup; Phase 3 is the one-time manual seal capture.
Phase 0 — Provision the box (MANUAL, provider web console)
These steps cannot be scripted without tying us to a provider API, which we deliberately avoid to stay provider-independent. Do them by hand.
Current provider: Hetzner Cloud (but any VPS provider works).
Create the server.
- Type: shared, cost-optimized, x86 — e.g. Hetzner CX33 (4 vCPU / 8 GB / 80 GB).
- Region: EU (e.g. Nuremberg / Falkenstein / Helsinki).
- Image: Ubuntu 24.04 LTS.
- Networking: enable Primary IPv4.
- Backups: on (cheap insurance during bootstrapping).
- SSH key: add your public key (see Phase 1) at creation so you can log in.
- Skip: volumes, private networks, placement groups, cloud-init, apps.
Create + apply the firewall (
rampart).- Inbound: TCP 22 (SSH) from Any IPv4 + Any IPv6. Nothing else.
- Outbound: allow all (Igor needs API calls, git, web; Claude Code Remote Control needs outbound).
- Apply it to the server. Confirm it shows "Fully applied".
- ⚠️ VPN gotcha: a local VPN can make all ports look open when testing from your laptop. Test port exposure with the VPN off (see docs/hardening.md).
Record the box's public IPv4 — you'll need it for SSH config below.
Phase 1 — Bootstrap access (local machine)
Have an SSH key for the Keep. This repo assumes a key named
keep. Managed via macOS keychain / ssh-agent (recommended), or a passphrase-protected file. The public half goes in Hetzner (Phase 0); the private half stays on your machine / in your password manager.Add an SSH config alias (
~/.ssh/configon your laptop):Host keep HostName <BOX_PUBLIC_IPV4> User olo IdentityFile ~/.ssh/keep UseKeychain yes AddKeysToAgent yesNote:
User oloassumes Phase 2 has run (it creates theolouser and disables root login). For the very first connection on a brand-new box, connect asrootto run Phase 2, then switch the alias toolo.First connection (as root, brand-new box):
ssh root@<BOX_PUBLIC_IPV4>Accept the host fingerprint. You're in. Proceed to Phase 2.
Phase 2 — Scripted on-box setup
On the box (still as root on a fresh machine):
# install git if needed, then clone this repo
apt-get update && apt-get install -y git
git clone <THIS_REPO_URL> keep
cd keep
# review what it will do, then run it
less setup.sh
sudo bash setup.shsetup.sh is idempotent — safe to run repeatedly. It will:
- create the non-root sudo user (
olo) - copy your SSH key to that user
- harden SSH (key-only, no passwords, no root login)
- update the system + enable automatic security updates
- install Docker (operator user in the docker group — never agents)
- create the Igor host dirs (
~/igor/seal,~/igor/projects) - build the
igor-baseimage
After it runs, switch your SSH config alias to User olo and reconnect with
ssh keep. Root login is now disabled by design.
Phase 3 — Capture the seal (MANUAL, one-time)
setup.sh creates an empty ~/igor/seal. Populate it once by logging Claude
Code in inside a container — see docs/igor.md.
This is manual and secret; the seal is never committed.
You also need the GitHub deploy key(s) for the repos Igor will touch — see below.
Secrets — what you must supply (NEVER committed)
Everything secret stays off the repo. The repo documents what is needed and where it goes, never the values. Supply these out-of-band (password manager) or generate on the box.
| Secret | Used for | Where it lives |
|----------------------------|-----------------------------------------|----------------|
| keep SSH private key | Logging into the Keep | laptop / password manager |
| olo sudo password | sudo on the box | password manager; set during setup |
| the seal (~/igor/seal) | Shared Claude auth for all Igors | box only; captured manually (Phase 3) |
| deploy keys (~/.ssh/github-deploy-<repo>) | Igor/box pushing to one repo | box only; generated per repo |
GitHub deploy keys (convention)
Per-repo, so the box's GitHub reach is one repo at a time (never your account):
- Generate on the box:
ssh-keygen -t ed25519 -f ~/.ssh/github-deploy-<repo> -C "github-deploy-<repo>@keep" -N "" - Add the public half to that repo's Settings → Deploy keys (with write access if the box needs to push).
- Add a matching SSH config
Hostalias on the box:Host github-deploy-<repo> HostName github.com User git IdentityFile ~/.ssh/github-deploy-<repo> IdentitiesOnly yes - Clone/push via the alias:
git clone github-deploy-<repo>:<owner>/<repo>.git
Chose deploy keys over GitHub Apps: Apps' benefits (org-scale lifecycle, expiring tokens) don't apply to a solo, single-box, per-repo-isolation setup; deploy keys are simpler and self-contained.
Graduation path
setup.sh is the honest starting record. It is written as discrete,
idempotent, Ansible-shaped blocks so it can be migrated to an Ansible playbook
with minimal restructuring once setup is complete.
Plan:
- Finish all setup as idempotent
setup.shblocks. - Translate to an Ansible playbook (each guarded block → one task).
- Provision a fresh throwaway box from the playbook and confirm it comes up identical — turning reproducibility from a claim into a tested fact.
We are deliberately not using Terraform / provider IaC: tying box-creation to a provider API cuts against the provider-independence goal. Box creation stays as the documented manual Phase 0 above.
The golden rule
Every change to the box also goes into setup.sh (or docs) as a guarded block.
That discipline is what keeps "reproducible" honest rather than aspirational.
