The CLI only touches Node builtins and libsodium-wrappers (no native addons like better-sqlite3, which the server needs but the CLI never imports) -- confirmed by checking its actual import graph, not assumed. That makes it a good candidate for a single-file esbuild bundle rather than requiring a full git checkout + npm install on every machine that needs to run `keep pull`. deploy-cli.sh bundles src/cli (~18kb) and ships it straight to a host over SSH -- HOST=user@vps ./deploy-cli.sh -- mirroring flit's deploy-daemon.sh ssh-cat-chmod pattern rather than inventing a new convention. No git, no npm install, no node_modules on the target at all, just the one file plus a `node` runtime already there from whatever else is deployed on that box. Caught a real bug while building this: --banner:js="#!/usr/bin/env node" duplicated the shebang esbuild already preserves from the source file automatically, landing it on line 2 instead of line 1 -- Node only special-cases a shebang on line 1, so the bundle threw a syntax error on every invocation until the redundant banner flag was removed. Verified the bundle for real, not just that it builds: ran `keep identity init` / `identity show` against it directly and confirmed a real Ed25519 keypair comes out the other end -- the specific thing worth checking given libsodium-wrappers needed a createRequire workaround for its own broken ESM packaging, and bundling could easily have broken that differently. Also documents both CLI-provisioning paths in INTEGRATION.md (scripts/install-cli.sh for a machine you're fine cloning onto, deploy-cli.sh for one you'd rather not) as a "getting keep onto a machine" prerequisite section, ahead of the existing deploy-pattern docs that all assume it's already there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.3 KiB
Incorporating keep into an existing deploy setup
Concrete, by deploy pattern rather than by naming specific projects — the shape of the fit depends on how a project currently gets its config onto a target machine, not on what the project is.
The general shape
Wherever a deploy script or a manually-maintained .env exists today, the
change is the same: replace "assume .env is already sitting there,
hand-copied at some point" with a keep pull at the top of the script.
# before — a deploy script assumes .env already exists on this machine
set -euo pipefail
source .env # or docker-compose reads it directly
# after
set -euo pipefail
keep pull <project>/<env> --out .env
The one-time setup cost per machine: keep identity init once, send the
printed public key to whoever runs keep recipient add, then
keep identity set-id <id> with what comes back. After that, every future
deploy from that machine just works — no more "wait, what's the DB
password again" when setting up a new box.
A deploy identity should almost always be granted read-only access
(keep grant <vault> <deploy-recipient-id> --read-only) — it only ever
needs to pull, and a read-only grant means a compromised deploy box
can't also rotate the vault or add itself more recipients.
Getting the keep CLI onto a machine
Chicken-and-egg: every pattern below assumes keep is already a runnable
command on the machine doing the pulling. Two ways to get there, pick
based on whether you're fine with a git checkout existing on that
machine:
A machine you're already developing on, or don't mind cloning onto (your laptop, a VPS you administer directly):
git clone <this repo>
cd keep
npm install
./scripts/install-cli.sh # builds, symlinks dist/cli/index.js onto PATH as `keep`
Symlinked rather than copied — a future git pull && npm run build is
the entire upgrade story, no need to re-run the install script.
A deploy target you'd rather not clone a git repo onto — bundle the
CLI into a single portable file and ship just that, over SSH, the same
way flit/waste-go's deploy-*.sh scripts ship a compiled binary:
HOST=user@your-vps ./deploy-cli.sh
This bundles src/cli (esbuild, single file, ~18kb — the CLI only
touches Node builtins and libsodium-wrappers, no native addons, so it's
fully portable) and cats it straight to ~/bin/keep on the target over
SSH — no git, no npm install, no node_modules on that machine at all,
just a node runtime (already there, if it's running any of the other
Node-based projects in this family) and the one file. Re-run it any time
the CLI changes; it's a single file, safe to overwrite.
Pattern: several docker-compose projects on one or more VPSes
The common shape once more than one or two projects are on keep:
each project lives in its own directory on the VPS
(/opt/docker/<project>/docker-compose.yml), and "deploy" means pulling
that project's env and running docker compose up -d in its directory.
scripts/deploy.sh in this repo is a small generic wrapper for exactly
that:
./deploy.sh myapp # pulls myapp/production, runs compose in /opt/docker/myapp
./deploy.sh myapp staging # pulls myapp/staging instead
DEPLOY_ROOT=/srv/apps ./deploy.sh myapp # different project root
One-time setup per VPS: keep identity init on that machine, register
its public key with keep recipient add, keep identity set-id, then
grant it read-only access to each project vault it deploys
(keep grant <project>/production <vps-recipient-id> --read-only). After
that, ./deploy.sh <project> is the whole deploy step — no .env
hand-copied onto the box first.
For "which machine can deploy which project," keep overview (admin)
prints every vault and every recipient's access in one screen — the
thing to reach for instead of checking each vault's grants one at a
time.
Pattern: docker-compose reading a local .env
A common shape — docker compose up -d reads environment: values from
a .env file sitting next to docker-compose.yml on the deploy machine,
hand-maintained and containing real secrets.
keep pull myapp/production --out .env
docker compose up -d
No code changes needed in the target project — keep only changes how
the .env file gets there, not what reads it. This is the easiest
category to retrofit.
Pattern: cross-compile + scp + SSH-restart deploy scripts
A shape where a deploy script builds a binary locally, ships it to a VPS,
and restarts a service over SSH — reading a local .env next to the
script for things like shared secrets that shouldn't be hardcoded or
re-typed on every invocation.
keep pull myapp/production --out .env
set -a && source .env && set +a
# ...rest of the deploy script unchanged
Same retrofit as the docker-compose case, just applied before whatever build/ship step already exists.
Pattern: a plaintext config file served to a browser
Not every config.js/similar file that looks like environment config
actually holds secrets — some of it is public-by-design (an API base URL,
a feature flag) that's explicitly meant to be visible to anyone loading
the page. keep solves a confidentiality problem; a file with nothing
confidential in it doesn't have one. Worth checking this distinction
before wiring keep into anything — retrofitting a non-secret config
file would be solving a problem that file doesn't have.
A vault server's own configuration
Whatever runs keep itself needs its own ADMIN_PASSWORD to be a real,
manually-set secret on its own host — it can't bootstrap its own trust
root by pulling itself from itself. This is the same "root of trust
starts somewhere unmanaged" fact every secrets system has (Vault's own
unseal keys aren't stored in Vault either). Not a gap, just worth being
explicit about: keep's own deploy stays on a plain .env/
docker-compose.yml environment variables.
New projects going forward
The actual best use case isn't retrofitting existing deploys — it's never
having the "hand-reconstruct a .env from memory" moment for a new
deploy target in the first place. For whatever gets built next: register
its production host as a keep recipient as part of first setup, write
the deploy script to keep pull from day one, and the annoyance this was
built to fix never has a chance to happen.