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>
156 lines
6.3 KiB
Markdown
156 lines
6.3 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
# 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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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 `cat`s 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:
|
|
|
|
```bash
|
|
./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.
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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.
|