Files
keep/package.json

31 lines
906 B
JSON
Raw Permalink Normal View History

Implement keep: self-hosted E2E encrypted secrets sync Server: Express + better-sqlite3 (WAL), multi-recipient key-wrapping per IMPLEMENTATION.md's design — vaults/recipients/vault_grants/ access_log. Two auth paths: ADMIN_PASSWORD header for recipient management and revoke (pure metadata operations), signed-request auth (Ed25519 signature over method+path+timestamp+body-hash) for push/pull/grant, mirroring the spirit of this project family's other signed-handshake patterns without naming them. CLI: identity init/show/set-id, push/pull/grant/log, admin recipient add/list/remove and revoke. Grant is a client-side crypto operation (the granter unwraps the vault's current key locally and reseals it for the new recipient) rather than a server-side operation, since the server never holds an unwrapped key to grant with. Verified end-to-end with two independent local identities against a live server and separately against the built Docker image: register, push, pull (granted and ungranted), grant without re-pushing, admin revoke, a subsequent rotation confirming the revoked recipient stays excluded, and rejection of missing/malformed signed-request auth. Two real bugs caught during verification, not just written up: - libsodium-wrappers' published ESM build does a relative import only resolvable under bundler-style resolution — broken under plain Node ESM. Fixed via createRequire to force the CJS build. - Express's req.path inside a sub-router is relative to the mount point, which would have silently mismatched a client signing the full request path. Fixed by verifying against req.originalUrl. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 19:38:24 +02:00
{
"name": "keep",
"version": "0.1.0",
"license": "MIT",
"type": "module",
"bin": {
"keep": "./dist/cli/index.js"
},
"scripts": {
"dev:server": "tsx watch --env-file=.env src/server/index.ts",
"cli": "tsx src/cli/index.ts",
"build": "tsc",
Fix deploy-cli.sh: bundle was completely broken on a real host Caught live, deployed to the actual target -- the previous version failed on the real host with "Cannot use import statement outside a module" (Node 18, no ancestor package.json to signal ESM for an extensionless file). That surfaced two stacked bugs my own testing had missed by running the bundle from inside this repo's own directory tree, which coincidentally supplied both things the standalone artifact was silently depending on: 1. Module-format ambiguity: an extensionless file with no controlling package.json defaults to CommonJS on older Node (no auto-detection heuristic before recent versions). Fixed by naming the bundle output keep.mjs -- unconditionally ESM on any Node version, regardless of extension-less-file heuristics or nearby package.json. 2. The bigger one: crypto.ts's createRequire(import.meta.url) trick (needed because libsodium-wrappers' published ESM build follows a broken relative import) is opaque to esbuild's static bundler -- it's a runtime-obtained require reference, not the literal `require` token esbuild's bundling recognizes. The previous "18kb bundle" never actually contained libsodium-wrappers at all; it silently relied on real node_modules being nearby on disk, which was only ever true by accident when testing from within this repo. On a clean host it threw "Cannot find module 'libsodium-wrappers'". Tried forcing static inlining via a literal require() call (esbuild does special-case that even in ESM-format output, unlike `import`, which is permanently pinned to the "import" resolution condition and can't be routed to the package's working CJS build no matter what --conditions/--main-fields/--alias combination is tried this was tried and confirmed exhaustively). That got real resolution and a real 1.7MB bundle, but broke at runtime with "No secure random number generator found" -- inlining the WASM engine breaks its own Node-crypto feature detection. Landed on: ship libsodium-wrappers (and its own dependency, libsodium) as real, unmodified package files alongside the ~18kb bundle, via a new copy-cli-deps.mjs step, rather than fighting further to force single-file inlining. ~1.6MB total, still one tar stream over SSH, still the entire deploy step. deploy-cli.sh now tars dist-bundle/ (bundle + node_modules) instead of catting a single file, extracts to ~/.keep-cli/ on the target, and symlinks ~/bin/keep to the entry point inside it. Verified this time in conditions that actually match the failure: full isolation (mktemp'd HOME and install dir, no ancestor package.json, no adjacent node_modules from this repo) for both `node keep.mjs` and direct shebang execution, plus the exact tar/extract cycle deploy-cli.sh performs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 15:59:44 +02:00
"build:cli-bundle": "esbuild src/cli/index.ts --bundle --platform=node --format=esm --outfile=dist-bundle/keep.mjs --external:node:* --external:libsodium-wrappers && node scripts/copy-cli-deps.mjs",
Implement keep: self-hosted E2E encrypted secrets sync Server: Express + better-sqlite3 (WAL), multi-recipient key-wrapping per IMPLEMENTATION.md's design — vaults/recipients/vault_grants/ access_log. Two auth paths: ADMIN_PASSWORD header for recipient management and revoke (pure metadata operations), signed-request auth (Ed25519 signature over method+path+timestamp+body-hash) for push/pull/grant, mirroring the spirit of this project family's other signed-handshake patterns without naming them. CLI: identity init/show/set-id, push/pull/grant/log, admin recipient add/list/remove and revoke. Grant is a client-side crypto operation (the granter unwraps the vault's current key locally and reseals it for the new recipient) rather than a server-side operation, since the server never holds an unwrapped key to grant with. Verified end-to-end with two independent local identities against a live server and separately against the built Docker image: register, push, pull (granted and ungranted), grant without re-pushing, admin revoke, a subsequent rotation confirming the revoked recipient stays excluded, and rejection of missing/malformed signed-request auth. Two real bugs caught during verification, not just written up: - libsodium-wrappers' published ESM build does a relative import only resolvable under bundler-style resolution — broken under plain Node ESM. Fixed via createRequire to force the CJS build. - Express's req.path inside a sub-router is relative to the mount point, which would have silently mismatched a client signing the full request path. Fixed by verifying against req.originalUrl. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 19:38:24 +02:00
"start": "node --env-file=.env dist/server/index.js"
},
"dependencies": {
"better-sqlite3": "^11.3.0",
Add deploy-cli.sh: ship the CLI as a single bundled file, no git clone needed 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>
2026-07-15 15:45:31 +02:00
"express": "^4.19.2",
Implement keep: self-hosted E2E encrypted secrets sync Server: Express + better-sqlite3 (WAL), multi-recipient key-wrapping per IMPLEMENTATION.md's design — vaults/recipients/vault_grants/ access_log. Two auth paths: ADMIN_PASSWORD header for recipient management and revoke (pure metadata operations), signed-request auth (Ed25519 signature over method+path+timestamp+body-hash) for push/pull/grant, mirroring the spirit of this project family's other signed-handshake patterns without naming them. CLI: identity init/show/set-id, push/pull/grant/log, admin recipient add/list/remove and revoke. Grant is a client-side crypto operation (the granter unwraps the vault's current key locally and reseals it for the new recipient) rather than a server-side operation, since the server never holds an unwrapped key to grant with. Verified end-to-end with two independent local identities against a live server and separately against the built Docker image: register, push, pull (granted and ungranted), grant without re-pushing, admin revoke, a subsequent rotation confirming the revoked recipient stays excluded, and rejection of missing/malformed signed-request auth. Two real bugs caught during verification, not just written up: - libsodium-wrappers' published ESM build does a relative import only resolvable under bundler-style resolution — broken under plain Node ESM. Fixed via createRequire to force the CJS build. - Express's req.path inside a sub-router is relative to the mount point, which would have silently mismatched a client signing the full request path. Fixed by verifying against req.originalUrl. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 19:38:24 +02:00
"libsodium-wrappers": "^0.7.15"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.11",
Add deploy-cli.sh: ship the CLI as a single bundled file, no git clone needed 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>
2026-07-15 15:45:31 +02:00
"@types/express": "^4.17.21",
Implement keep: self-hosted E2E encrypted secrets sync Server: Express + better-sqlite3 (WAL), multi-recipient key-wrapping per IMPLEMENTATION.md's design — vaults/recipients/vault_grants/ access_log. Two auth paths: ADMIN_PASSWORD header for recipient management and revoke (pure metadata operations), signed-request auth (Ed25519 signature over method+path+timestamp+body-hash) for push/pull/grant, mirroring the spirit of this project family's other signed-handshake patterns without naming them. CLI: identity init/show/set-id, push/pull/grant/log, admin recipient add/list/remove and revoke. Grant is a client-side crypto operation (the granter unwraps the vault's current key locally and reseals it for the new recipient) rather than a server-side operation, since the server never holds an unwrapped key to grant with. Verified end-to-end with two independent local identities against a live server and separately against the built Docker image: register, push, pull (granted and ungranted), grant without re-pushing, admin revoke, a subsequent rotation confirming the revoked recipient stays excluded, and rejection of missing/malformed signed-request auth. Two real bugs caught during verification, not just written up: - libsodium-wrappers' published ESM build does a relative import only resolvable under bundler-style resolution — broken under plain Node ESM. Fixed via createRequire to force the CJS build. - Express's req.path inside a sub-router is relative to the mount point, which would have silently mismatched a client signing the full request path. Fixed by verifying against req.originalUrl. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 19:38:24 +02:00
"@types/libsodium-wrappers": "^0.7.14",
Add deploy-cli.sh: ship the CLI as a single bundled file, no git clone needed 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>
2026-07-15 15:45:31 +02:00
"@types/node": "^22.0.0",
"esbuild": "^0.28.1",
Implement keep: self-hosted E2E encrypted secrets sync Server: Express + better-sqlite3 (WAL), multi-recipient key-wrapping per IMPLEMENTATION.md's design — vaults/recipients/vault_grants/ access_log. Two auth paths: ADMIN_PASSWORD header for recipient management and revoke (pure metadata operations), signed-request auth (Ed25519 signature over method+path+timestamp+body-hash) for push/pull/grant, mirroring the spirit of this project family's other signed-handshake patterns without naming them. CLI: identity init/show/set-id, push/pull/grant/log, admin recipient add/list/remove and revoke. Grant is a client-side crypto operation (the granter unwraps the vault's current key locally and reseals it for the new recipient) rather than a server-side operation, since the server never holds an unwrapped key to grant with. Verified end-to-end with two independent local identities against a live server and separately against the built Docker image: register, push, pull (granted and ungranted), grant without re-pushing, admin revoke, a subsequent rotation confirming the revoked recipient stays excluded, and rejection of missing/malformed signed-request auth. Two real bugs caught during verification, not just written up: - libsodium-wrappers' published ESM build does a relative import only resolvable under bundler-style resolution — broken under plain Node ESM. Fixed via createRequire to force the CJS build. - Express's req.path inside a sub-router is relative to the mount point, which would have silently mismatched a client signing the full request path. Fixed by verifying against req.originalUrl. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 19:38:24 +02:00
"tsx": "^4.15.0",
"typescript": "^5.5.0"
}
}