Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions npm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Native binaries are build artifacts, not source — populated by build-binaries.sh
packages/*/bin/cipherstash-proxy
packages/*/bin/cipherstash-proxy.exe

# npm install / pack output
**/node_modules/
**/package-lock.json
*.tgz
82 changes: 82 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# `npx stash proxy` — npm distribution prototype

Proof-of-concept for shipping CipherStash Proxy through npm so it runs as
`npx stash proxy [...]`, with **no native bindings** — the npm package is a thin
launcher around the existing prebuilt Rust binary (the esbuild / Biome / SWC
pattern).

## Why this shape (not N-API bindings)

Proxy is a standalone server (its own tokio runtime, listeners, TLS, signals).
We don't need to call it from JS in-process, so a native Node addon would add
lifecycle/complexity for no benefit. Instead: npm *distributes* the binary and a
tiny JS shim *launches* it.

## Layout

```
npm/
packages/
stash/ # meta package — `bin: stash`
bin/stash.js # dispatch `stash proxy [...]` -> exec binary
lib/resolve.js # pick the platform package for this host
package.json # optionalDependencies = the platform packages
proxy-darwin-arm64/ # one package per target, each ships one binary
proxy-darwin-x64/ # package.json sets os/cpu so npm installs
proxy-linux-x64/ # only the matching one on a given host
proxy-linux-arm64/
build-binaries.sh # populate the host's platform package
demo.sh # end-to-end local proof
```

How resolution works: the meta package lists each `@cipherstash/proxy-<os>-<arch>`
as an **optionalDependency**. Each platform package declares `os`/`cpu`, so npm
installs only the one matching the host. The shim `require.resolve()`s the binary
from that package and `exec`s it, forwarding argv, stdio, exit code, and signals.

## Try it (local, no registry)

```bash
bash npm/demo.sh
```

This builds the host binary, `npm install`s the meta package (which pulls in just
the matching platform package), then runs `npx . proxy --version`,
`... proxy --help`, and an unknown-subcommand case. The binaries are git-ignored
build artifacts; `build-binaries.sh` regenerates them.

> Locally we use `file:` optionalDependencies and `npx .` so it works offline.
> In production these become published, versioned packages and the command is
> literally `npx stash proxy` (or `npx @cipherstash/stash proxy`).

## What production needs

1. **Release CI matrix** builds `cipherstash-proxy` for every target:
- macOS arm64 / x64 — **build on a macOS runner** so the linker ad-hoc-signs
for free (enough to run on Apple Silicon; **no Developer ID / notarization
needed** for CLI-installed binaries — npm doesn't set the Gatekeeper
quarantine attribute).
- Linux x64 / arm64 (glibc; add musl for Alpine if wanted).
- Windows x64 later (`.exe`; CLI use sidesteps SmartScreen).
2. Publish each platform package (`@cipherstash/proxy-<os>-<arch>`) plus the meta
`stash` package, all at the same version, pinned exactly.
3. The meta package's `optionalDependencies` reference the published versions
instead of `file:` paths.

## The code-signing win (the original motivation)

- **Avoided:** macOS notarization + Developer ID certificates, and Windows
Authenticode — the expensive, account-bound parts. npm-installed CLI binaries
aren't quarantined, so Gatekeeper/SmartScreen don't block them.
- **Still required (but free/automatic):** an *ad-hoc* signature on Apple
Silicon, which the macOS linker applies during the build. `build-binaries.sh`
re-asserts it with `codesign -s -`.

## Caveats

- Requires Node/npx on the host. Great for dev laptops & CI; **k8s should keep
using the Docker image / raw binary** — npm is an additional channel.
- `npx` for a long-running server is slightly unconventional but works (runs in
the foreground; signals are forwarded).
- This is a throwaway prototype: packages are `private: true` and versioned
`0.0.0-prototype` to prevent accidental publish.
36 changes: 36 additions & 0 deletions npm/build-binaries.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
#
# Populate the per-platform packages with prebuilt proxy binaries.
#
# Prototype scope: builds/copies the binary for the CURRENT host into its
# platform package. In production this is replaced by a CI matrix that builds
# every target on the appropriate runner (macOS runners ad-hoc-sign for free;
# Linux runners produce glibc/musl builds), then publishes each platform package.
#
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${HERE}/.." && pwd)"

case "$(uname -s)-$(uname -m)" in
Darwin-arm64) pkg=proxy-darwin-arm64 ;;
Darwin-x86_64) pkg=proxy-darwin-x64 ;;
Linux-aarch64) pkg=proxy-linux-arm64 ;;
Linux-x86_64) pkg=proxy-linux-x64 ;;
*) echo "Unsupported host: $(uname -s)-$(uname -m)" >&2; exit 1 ;;
esac

echo "Building cipherstash-proxy (release) for host -> ${pkg}"
( cd "${REPO_ROOT}" && cargo build --release -p cipherstash-proxy )

dest="${HERE}/packages/${pkg}/bin"
mkdir -p "${dest}"
cp -f "${REPO_ROOT}/target/release/cipherstash-proxy" "${dest}/cipherstash-proxy"
chmod +x "${dest}/cipherstash-proxy"

# macOS arm64 requires at least an ad-hoc signature to execute. Binaries linked
# on macOS are ad-hoc-signed automatically, but re-assert it to be safe.
if [[ "$(uname -s)" == "Darwin" ]]; then
codesign --force --sign - "${dest}/cipherstash-proxy" 2>/dev/null || true
fi

echo "Installed $(du -h "${dest}/cipherstash-proxy" | cut -f1) binary into ${pkg}/bin/"
31 changes: 31 additions & 0 deletions npm/demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
#
# End-to-end local proof of `npx stash proxy`:
# 1. build + install the host binary into its platform package
# 2. npm install the meta package (resolves the matching platform package
# via os/cpu-filtered optionalDependencies)
# 3. invoke through npx and through the `stash` bin, exercising arg passthrough
#
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
META="${HERE}/packages/stash"

echo "== 1. populate host platform binary =="
bash "${HERE}/build-binaries.sh"

echo "== 2. install meta package (resolves platform optionalDependency) =="
( cd "${META}" && npm install --silent )
echo "installed platform packages:"
ls "${META}/node_modules/@cipherstash" 2>/dev/null || echo " (none — check os/cpu match)"

echo "== 3a. npx <local> proxy --version =="
( cd "${META}" && npx . proxy --version )

echo "== 3b. npx <local> proxy --help (subcommand passthrough) =="
( cd "${META}" && npx . proxy --help | head -20 )

echo "== 3c. exit codes are forwarded =="
( cd "${META}" && npx . proxy --version ) >/dev/null 2>&1; echo " proxy --version -> exit $? (expect 0)"
( cd "${META}" && npx . frobnicate ) >/dev/null 2>&1; echo " unknown subcommand -> exit $? (expect non-zero)"

echo "== done =="
9 changes: 9 additions & 0 deletions npm/packages/proxy-darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@cipherstash/proxy-darwin-arm64",
"version": "0.0.0-prototype",
"private": true,
"description": "CipherStash Proxy native binary for macOS arm64",
"os": ["darwin"],
"cpu": ["arm64"],
"files": ["bin/cipherstash-proxy"]
}
9 changes: 9 additions & 0 deletions npm/packages/proxy-darwin-x64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@cipherstash/proxy-darwin-x64",
"version": "0.0.0-prototype",
"private": true,
"description": "CipherStash Proxy native binary for macOS x86_64",
"os": ["darwin"],
"cpu": ["x64"],
"files": ["bin/cipherstash-proxy"]
}
9 changes: 9 additions & 0 deletions npm/packages/proxy-linux-arm64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@cipherstash/proxy-linux-arm64",
"version": "0.0.0-prototype",
"private": true,
"description": "CipherStash Proxy native binary for Linux arm64 (glibc)",
"os": ["linux"],
"cpu": ["arm64"],
"files": ["bin/cipherstash-proxy"]
}
9 changes: 9 additions & 0 deletions npm/packages/proxy-linux-x64/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@cipherstash/proxy-linux-x64",
"version": "0.0.0-prototype",
"private": true,
"description": "CipherStash Proxy native binary for Linux x86_64 (glibc)",
"os": ["linux"],
"cpu": ["x64"],
"files": ["bin/cipherstash-proxy"]
}
Loading