diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 420689c..0a79bac 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -84,6 +84,9 @@ jobs: - name: Format run: cargo fmt --check + - name: Compose bootstrap regression + run: ./scripts/test-compose-bootstrap.sh + - name: Lint run: cargo clippy --workspace --all-targets --all-features -- -D warnings diff --git a/README.md b/README.md index 1e2bcb2..93a666d 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,17 @@ The integrated Docker Compose flow starts PostgreSQL, a local Pubky testnet, the Server, and separate creator/reader browser demos. Follow the copy-pasteable setup in [`examples/js-sdk/README.md`](examples/js-sdk/README.md). +From a fresh clone: + +```bash +docker compose up --build +``` + +The Compose entrypoint generates and persists a random creator-authority encryption +key in the private `lock-home` volume. Set +`PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY` before startup only when you need to supply +your own 32-byte base64url key. + Verified browser-facing defaults are: - Lock Server: diff --git a/docker-compose.yml b/docker-compose.yml index f067104..f63d6ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,7 +49,7 @@ services: network_mode: service:pubky-testnet environment: PUBKY_LOCK_DATABASE_URL: postgres://locks:locks@postgres:5432/locks_test - PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY: ${PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY:?set PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY to a 32-byte base64url key} + PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY: ${PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY:-} volumes: - lock-home:/var/lib/pubky-lock command: ["locks-server-compose-entrypoint.sh"] diff --git a/docker/locks-server-compose-entrypoint.sh b/docker/locks-server-compose-entrypoint.sh index bf5cfd2..ed28851 100644 --- a/docker/locks-server-compose-entrypoint.sh +++ b/docker/locks-server-compose-entrypoint.sh @@ -5,9 +5,27 @@ service_home="${LOCKS_SERVICE_HOME:-/var/lib/pubky-lock/.pubky-lock}" generated_config="$service_home/config.toml" compose_config="${LOCKS_COMPOSE_CONFIG:-/var/lib/pubky-lock/config.compose.toml}" secret_path="$service_home/secret.sess" +creator_authority_key_path="$service_home/creator-authority-encryption-key" mkdir -p "$service_home" +if [ -z "${PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY:-}" ]; then + if [ ! -f "$creator_authority_key_path" ]; then + echo "[locks-compose] generating creator-authority encryption key" + umask 077 + temporary_key_path="$creator_authority_key_path.tmp.$$" + head -c 32 /dev/urandom \ + | base64 \ + | tr '+/' '-_' \ + | tr -d '=\n' > "$temporary_key_path" + chmod 600 "$temporary_key_path" + mv "$temporary_key_path" "$creator_authority_key_path" + fi + + PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY="$(cat "$creator_authority_key_path")" + export PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY +fi + if [ ! -f "$generated_config" ] || [ ! -f "$secret_path" ]; then echo "[locks-compose] initializing Lock Server identity/config in $service_home" timeout 5 locks-server || true diff --git a/examples/js-sdk/README.md b/examples/js-sdk/README.md index f52b938..99d36ec 100644 --- a/examples/js-sdk/README.md +++ b/examples/js-sdk/README.md @@ -78,19 +78,17 @@ The demos need four processes/services alive at the same time: ### Docker Compose local stack -For a containerized local stack from the repository root, set a creator-authority encryption key and start compose: +For a containerized local stack from the repository root: ```bash -export PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY="$( - python3 - <<'PY' -import base64, os -print(base64.urlsafe_b64encode(os.urandom(32)).decode().rstrip('=')) -PY -)" - docker compose up --build ``` +On first startup, the Lock Server entrypoint generates a random creator-authority +encryption key and persists it in the private `lock-home` volume. Later starts reuse +that key. To supply your own 32-byte base64url key instead, export +`PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY` before running Compose. + The compose stack starts: - Postgres on host port `55433` @@ -102,7 +100,9 @@ The compose stack starts: The Pubky testnet image is built from the public `pubky/pubky-core` repository at the revision pinned in `docker-compose.yml`; no sibling checkout is required. -Compose keeps Lock Server identity/config in the `lock-home` Docker volume and Postgres data in `postgres-data`. To reset everything: +Compose keeps the Lock Server identity, config, and generated creator-authority +encryption key in the `lock-home` Docker volume and Postgres data in `postgres-data`. +To reset everything: ```bash docker compose down -v diff --git a/scripts/check b/scripts/check index a70d1b5..8b63f43 100755 --- a/scripts/check +++ b/scripts/check @@ -12,6 +12,7 @@ run() { } run cargo fmt --check +run ./scripts/test-compose-bootstrap.sh run cargo clippy --workspace --all-targets --all-features -- -D warnings run cargo nextest run --workspace --exclude locks-e2e --exclude locks-service run cargo nextest run -p locks-service --lib -- --skip infrastructure::postgres diff --git a/scripts/test-compose-bootstrap.sh b/scripts/test-compose-bootstrap.sh new file mode 100755 index 0000000..e7e192a --- /dev/null +++ b/scripts/test-compose-bootstrap.sh @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu + +repo_root="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" +entrypoint="$repo_root/docker/locks-server-compose-entrypoint.sh" +key_name="PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY" + +env -u "$key_name" docker compose -f "$repo_root/docker-compose.yml" config --quiet + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +service_home="$tmp/home/.pubky-lock" +bin_dir="$tmp/bin" +capture="$tmp/captured-key" +mkdir -p "$service_home" "$bin_dir" + +cat > "$service_home/config.toml" <<'EOF' +lock_server_public_key = "test-public-key" +EOF +: > "$service_home/secret.sess" + +cat > "$bin_dir/locks-server" <<'EOF' +#!/bin/sh +set -eu +printf '%s' "$PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY" > "$LOCKS_TEST_KEY_CAPTURE" +EOF +chmod +x "$bin_dir/locks-server" + +run_entrypoint() { + env -u "$key_name" \ + PATH="$bin_dir:$PATH" \ + LOCKS_SERVICE_HOME="$service_home" \ + LOCKS_COMPOSE_CONFIG="$tmp/config.compose.toml" \ + LOCKS_TEST_KEY_CAPTURE="$capture" \ + sh "$entrypoint" +} + +file_mode() { + if stat -c '%a' "$1" >/dev/null 2>&1; then + stat -c '%a' "$1" + else + stat -f '%Lp' "$1" + fi +} + +run_entrypoint +key_file="$service_home/creator-authority-encryption-key" +test -f "$key_file" +test "$(wc -c < "$key_file" | tr -d ' ')" -eq 43 +grep -Eq '^[A-Za-z0-9_-]{43}$' "$key_file" +test "$(file_mode "$key_file")" = 600 +cmp -s "$key_file" "$capture" +first_key="$(cat "$key_file")" + +run_entrypoint +test "$(cat "$capture")" = "$first_key" + +override='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +PUBKY_LOCK_CREATOR_AUTH_ENCRYPTION_KEY="$override" \ + PATH="$bin_dir:$PATH" \ + LOCKS_SERVICE_HOME="$service_home" \ + LOCKS_COMPOSE_CONFIG="$tmp/config.compose.toml" \ + LOCKS_TEST_KEY_CAPTURE="$capture" \ + sh "$entrypoint" +test "$(cat "$capture")" = "$override" +test "$(cat "$key_file")" = "$first_key" + +printf 'compose bootstrap regression passed\n'