From 9dfa51321843a952bbfe563e6b0429c008ec7da5 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 01:49:17 +0200 Subject: [PATCH 1/8] chore(ci): set KUBO_API_URL for staging and document the local v2 stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The staging deploy wrote v1-era pin-store names and never set the one the v2 API reads, so every hosted write answered 503 — uploads and folder creates alike, since a record's head block goes through the same endpoint. - staging `.env.staging` now sets `KUBO_API_URL=http://ipfs:5001` and `ROUTING_V1_URL=http://someguy:8190`. `ROUTING_V1_URL` was unset too, so the republisher walk resolved every name to null and re-PUT nothing. - drop `IPFS_PROVIDER`, `IPFS_LOCAL_API_URL`, `IPFS_LOCAL_GATEWAY_URL`, `DELEGATED_ROUTING_URL` and `DELEGATED_ROUTING_FALLBACK_URL`; no source file reads any of them. - `KuboPinStore` reports an unset `KUBO_API_URL` at construction, so a misconfiguration is visible at deploy time rather than under load. Logged rather than thrown: an unconfigured store is a supported shape. - the root README carries one full local recipe — Postgres, Kubo, someguy, the record store, the API, the web bundle — with the environment inline instead of `.env.example` files the repo has not shipped for months. - the local stack resolves records through `mock-ipns-routing`, not someguy: hermetic, instant, and no test vault's names on the public DHT. - v1 `docs/` recipes that led readers into a redis/tee-worker stack are retired to stubs pointing at the README; `CONFIGURATION.md` keeps its catalogue with the dead names corrected. Closes #1209 Closes #1216 --- .github/workflows/deploy-staging.yml | 7 +- CONTRIBUTING.md | 4 +- README.md | 89 ++++++++-- apps/api/src/registry/pin-store.test.ts | 33 +++- apps/api/src/registry/pin-store.ts | 9 + docs/CONFIGURATION.md | 116 +++---------- docs/DEVELOPMENT.md | 216 +----------------------- docs/GETTING-STARTED.md | 181 +------------------- 8 files changed, 150 insertions(+), 505 deletions(-) diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index bfe344a946..30831a0e72 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -409,11 +409,8 @@ jobs: DB_DATABASE=cipherbox_staging JWT_SECRET=${{ secrets.JWT_SECRET }} CORS_ALLOWED_ORIGINS=${{ vars.CORS_ALLOWED_ORIGINS }} - IPFS_PROVIDER=local - IPFS_LOCAL_API_URL=http://ipfs:5001 - IPFS_LOCAL_GATEWAY_URL=http://ipfs:8080 - DELEGATED_ROUTING_URL=http://someguy:8190 - DELEGATED_ROUTING_FALLBACK_URL=https://delegated-ipfs.dev + KUBO_API_URL=http://ipfs:5001 + ROUTING_V1_URL=http://someguy:8190 THROTTLE_BYPASS_SECRET=${{ secrets.THROTTLE_BYPASS_SECRET }} GRAFANA_LOKI_URL=${{ vars.GRAFANA_LOKI_URL }} GRAFANA_LOKI_USERNAME=${{ vars.GRAFANA_LOKI_USERNAME }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7363b7448e..ff9e5b99c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ # Contributing to CipherBox -See [docs/GETTING-STARTED.md](docs/GETTING-STARTED.md) for prerequisites and first-run instructions, -and [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for local development setup. +See the "Getting started" section of the root [README.md](README.md) for prerequisites, the local +stack, and first-run instructions. ## Branch Conventions diff --git a/README.md b/README.md index aae72edf16..7792c82abf 100644 --- a/README.md +++ b/README.md @@ -109,27 +109,92 @@ cipher-box/ Prerequisites: Node.js 22+, pnpm 10+, Docker, and the Rust toolchain (pinned by `rust-toolchain.toml`). +There are no `.env` files to copy. Every variable the stack needs is exported inline +below, so the recipe is read in the same breath as the commands that consume it. + +### 1. Start the infrastructure + ```bash -# 1. Start infrastructure services docker compose -f docker/docker-compose.yml up -d - -# 2. Install dependencies pnpm install +``` + +That brings up Postgres (5432), Kubo (5001 RPC, 8080 gateway), someguy (8190), and the +mock record store (3001). Wait for them to report healthy: + +```bash +docker compose -f docker/docker-compose.yml ps +``` -# 3. Copy environment files -cp apps/api/.env.example apps/api/.env -cp apps/web/.env.example apps/web/.env +### 2. Configure and start the API -# 4. Start API and web app -pnpm dev +```bash +export DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres \ + DB_PASSWORD=postgres DB_DATABASE=cipherbox \ + NODE_ENV=development JWT_SECRET=local-dev-jwt-secret \ + TEST_LOGIN_SECRET=local-dev-test-secret \ + KUBO_API_URL=http://localhost:5001 \ + ROUTING_V1_URL=http://localhost:3001 \ + CORS_ALLOWED_ORIGINS=http://localhost:5173 + +pnpm --filter @cipherbox/api migration:run +pnpm --filter @cipherbox/api dev ``` -- API: +`KUBO_API_URL` is the one the hosted pin store reads. Without it every write answers +503 — uploads and folder creates alike, since a record's head block is uploaded through +the same endpoint. The API logs an error at boot when it is unset. + +### 3. Build and serve the web app + +In a second shell, with the same `docker compose` stack up: + +```bash +export VITE_API_URL=http://localhost:3000 \ + VITE_ENVIRONMENT=local \ + VITE_ROUTING_ENDPOINTS=http://localhost:3001 \ + VITE_READ_ACCELERATOR_URL=http://localhost:8080 + +pnpm --filter @cipherbox/web dev +``` + +- API: (OpenAPI at `/api-docs`) - Web: -Note that during the rewrite this boots the v2 skeleton (a stub API and web shell); the -legacy [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) still describes the v1 setup and is being -rewritten during the build. +`VITE_ROUTING_ENDPOINTS` must be set: unset it defaults to the public +`https://delegated-ipfs.dev`, which will not see records this stack publishes. +`VITE_READ_ACCELERATOR_URL` is optional — left unset the content gateway stays dormant, +which is its fail-closed state, and reads fall back to the endpoints the engine already +has. + +### Which record store the local stack uses + +Compose starts two `/routing/v1` backends, and a local stack should use +**`mock-ipns-routing` on port 3001** — the setting above for both `ROUTING_V1_URL` (API +republisher) and `VITE_ROUTING_ENDPOINTS` (web client). It is hermetic and in-memory, so +a record published locally resolves immediately and deterministically, and no test +vault's IPNS names reach the public network. CI and the web-e2e suite make the same +choice. + +`someguy` on 8190 participates in the real accelerated DHT. It is there for staging +parity and for deliberately testing public-network propagation; point the two variables +above at `http://localhost:8190` only when that is what you are testing. Both must name +the same backend, or the republisher re-PUTs into a store the client never reads. + +### What this stack can demonstrate today + +The API's write path is live end to end: authenticate and `POST /content/upload` +returns 201 with bytes pinned in the local Kubo. + +Interactive login through the web UI needs `VITE_WEB3AUTH_CLIENT_ID` and +`VITE_WEB3AUTH_VERIFIER`, which a clean checkout does not carry — the UI boots and +renders without them, but a Core Kit session cannot be created. The suites that need an +authenticated session use the build-time introspection hook instead; see +[`tests/web-e2e/README.md`](tests/web-e2e/README.md). + +A first folder create does not yet publish, because nothing provisions a fresh account's +first vault pointer, so its writes are accepted, rendered pending, and reach no endpoint. +That is the remaining gap between this stack and a full demo. ## Security model diff --git a/apps/api/src/registry/pin-store.test.ts b/apps/api/src/registry/pin-store.test.ts index d9b4df5a83..e796bb71b1 100644 --- a/apps/api/src/registry/pin-store.test.ts +++ b/apps/api/src/registry/pin-store.test.ts @@ -1,4 +1,4 @@ -import { ServiceUnavailableException } from '@nestjs/common'; +import { Logger, ServiceUnavailableException } from '@nestjs/common'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { fakeConfig } from '../testing/fakes'; import { KuboPinStore, PinCidMismatchError } from './pin-store'; @@ -86,3 +86,34 @@ describe('KuboPinStore.pin', () => { expect(calls).toEqual([]); }); }); + +/** + * A misconfiguration that refuses every hosted write must surface at boot, not + * only as a 503 under load. + */ +describe('KuboPinStore configuration report', () => { + let errors: string[]; + + beforeEach(() => { + errors = []; + vi.spyOn(Logger.prototype, 'error').mockImplementation((message: unknown) => { + errors.push(String(message)); + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('names the unset variable and its consequence at construction', () => { + store(''); + expect(errors).toHaveLength(1); + expect(errors[0]).toContain('KUBO_API_URL'); + expect(errors[0]).toContain('503'); + }); + + it('stays silent when Kubo is configured', () => { + store(); + expect(errors).toEqual([]); + }); +}); diff --git a/apps/api/src/registry/pin-store.ts b/apps/api/src/registry/pin-store.ts index ff4f1a253f..d1e0c7173e 100644 --- a/apps/api/src/registry/pin-store.ts +++ b/apps/api/src/registry/pin-store.ts @@ -72,6 +72,15 @@ export class KuboPinStore extends PinStore { super(); const raw = configService.get('KUBO_API_URL'); this.apiUrl = raw && raw.trim() ? raw.replace(/\/+$/, '') : undefined; + if (!this.apiUrl) { + // Report at boot, not per request: unset, every hosted write 503s, and a + // deploy that only learns this from request logs learns it under load. + // Logged rather than thrown because an unconfigured store is a supported + // shape (BYO-only, unit tests) — see the class doc. + this.logger.error( + 'KUBO_API_URL is unset; hosted uploads will be refused with 503 and unpins will no-op' + ); + } } override async pin(cid: string, bytes: Uint8Array): Promise { diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index a13e3ff38b..b5b4cab79c 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -2,15 +2,15 @@ # Configuration Reference +> **v1 document — partially superseded.** This catalogue was written against the v1 stack as of the freeze (`v1-freeze`, branch `v1`) and still names variables the v2 code no longer reads. The code is authoritative for which names are live; entries here are a starting point, not a contract. For the local stack itself, see the "Getting started" section of the root [`README.md`](../README.md). + Environment variables and configuration files for all CipherBox monorepo applications. -For local development setup instructions, see [DEVELOPMENT.md](DEVELOPMENT.md). ## Table of Contents - [API (`apps/api`)](#api-appsapi) - [Web (`apps/web`)](#web-appsweb) - [Desktop (`apps/desktop`)](#desktop-appsdesktop) -- [TEE Worker (`apps/tee-worker`)](#tee-worker-appstee-worker) - [Docker Compose (local dev)](#docker-compose-local-dev) - [Docker Compose (staging)](#docker-compose-staging) - [Observability (staging)](#observability-staging) @@ -20,7 +20,8 @@ For local development setup instructions, see [DEVELOPMENT.md](DEVELOPMENT.md). ## API (`apps/api`) NestJS server. Configuration is loaded via `@nestjs/config` (`ConfigModule.forRoot`) and read -from `.env` at startup. Copy `apps/api/.env.example` to `apps/api/.env` before first run. +from `.env` at startup. The repo ships no `.env.example` files — the root `README.md` "Getting +started" section carries the inline export block the local stack expects. ### Database @@ -32,17 +33,6 @@ from `.env` at startup. Copy `apps/api/.env.example` to `apps/api/.env` before f | `DB_PASSWORD` | No | `postgres` | PostgreSQL password | | `DB_DATABASE` | No | `cipherbox` | PostgreSQL database name | -### Redis - -| Variable | Required | Default | Description | -| :--------------- | :------- | :---------- | :-------------------------------------------------- | -| `REDIS_HOST` | No | `localhost` | Redis host | -| `REDIS_PORT` | No | `6379` | Redis port | -| `REDIS_PASSWORD` | No | — | Redis password (omit for password-less connections) | - -The local dev `docker/docker-compose.yml` maps the Redis container port to `6380` on the host, -so set `REDIS_PORT=6380` when using the local stack. - ### Auth | Variable | Required | Default | Description | @@ -58,24 +48,15 @@ so set `REDIS_PORT=6380` when using the local stack. ### IPFS -| Variable | Required | Default | Description | -| :----------------------- | :------- | :---------------------- | :-------------------------------------------------------------------------- | -| `IPFS_LOCAL_API_URL` | No | `http://localhost:5001` | Kubo RPC API endpoint. The API relays all IPFS operations through this URL. | -| `IPFS_LOCAL_GATEWAY_URL` | No | `http://localhost:8080` | IPFS HTTP gateway for content retrieval. | +| Variable | Required | Default | Description | +| :------------- | :------- | :------ | :-------------------------------------------------------------------------------------- | +| `KUBO_API_URL` | No | — | Kubo RPC endpoint for the hosted pin store. Unset, hosted uploads are refused with 503. | ### IPNS / Delegated Routing -| Variable | Required | Default | Description | -| :------------------------------- | :------- | :--------------------------- | :--------------------------------------------------------------- | -| `DELEGATED_ROUTING_URL` | No | `https://delegated-ipfs.dev` | Primary HTTP delegated routing backend for IPNS publish/resolve. | -| `DELEGATED_ROUTING_FALLBACK_URL` | No | — | Optional secondary backend. Used if the primary request fails. | - -### TEE Integration - -| Variable | Required | Default | Description | -| :------------------ | :------- | :---------------------- | :------------------------------------------------------------------------- | -| `TEE_WORKER_URL` | No | `http://localhost:3001` | URL of the TEE worker service. The API forwards IPNS republish jobs here. | -| `TEE_WORKER_SECRET` | No | `""` (empty) | Shared secret sent as `Authorization: Bearer` when calling the TEE worker. | +| Variable | Required | Default | Description | +| :--------------- | :------- | :------ | :------------------------------------------------------------------------------------------------------- | +| `ROUTING_V1_URL` | No | — | `/routing/v1` endpoint the republisher resolves and re-PUTs through. Unset, the republisher walk no-ops. | ### Rate Limiting @@ -96,7 +77,7 @@ so set `REDIS_PORT=6380` when using the local stack. ## Web (`apps/web`) Vite + React SPA. All configuration is injected as `VITE_*` environment variables at build time. -Copy `apps/web/.env.example` to `apps/web/.env` before first run. +There is no `.env.example` to copy — set the variables below in `apps/web/.env` or the shell. | Variable | Required | Default | Description | | :------------------------ | :------- | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | @@ -112,7 +93,7 @@ Copy `apps/web/.env.example` to `apps/web/.env` before first run. ## Desktop (`apps/desktop`) Tauri + Vite + React application. Uses the same `VITE_*` convention as the web app. -Copy `apps/desktop/.env.example` to `apps/desktop/.env` before first run. +There is no `.env.example` to copy — set the variables below in `apps/desktop/.env` or the shell. ### Build-time (Vite) variables @@ -155,40 +136,11 @@ artifacts — it is safe to commit and is not a secret. --- -## TEE Worker (`apps/tee-worker`) - -Standalone Express server. Runs inside a Phala Cloud CVM in production and in a local -Docker container (simulator mode) in development and staging. - -Copy `apps/tee-worker/.env.example` to `apps/tee-worker/.env` for local development. - -| Variable | Required | Default | Description | -| :---------------------- | :------- | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PORT` | No | `3001` | HTTP listen port. | -| `NODE_ENV` | No | — | Runtime environment. Setting `production` while `TEE_MODE=simulator` is blocked at startup. | -| `TEE_MODE` | No | `simulator` | Key derivation mode. `simulator` uses HKDF from a fixed seed (development/testing). `cvm` uses Phala dstack SDK for hardware-backed key derivation (production). | -| `CIPHERBOX_ENVIRONMENT` | No | — | Explicit environment label (`staging`, `production`). Used alongside `NODE_ENV` to enforce that `TEE_MODE=simulator` is never used in production. | -| `TEE_WORKER_SECRET` | **Yes** | — | Shared secret for Bearer token authentication on all protected routes. Must match the `TEE_WORKER_SECRET` set in the API. | -| `TEE_CURRENT_EPOCH` | No | `1` | The current `keyEpoch` number. The TEE worker exposes the `teePublicKey` for this epoch on `GET /public-key`. Used by the migration route to identify the active key epoch. | -| `IPFS_GATEWAY_URL` | No | `https://ipfs.io` | IPFS gateway URL used when fetching CIDs during CID migration operations. | - -### TEE mode semantics - -The `TEE_MODE` variable determines how epoch keypairs are derived: - -- **`simulator`** — HKDF-SHA256 derivation from a fixed seed. Deterministic across restarts. - Used for local development and staging. Never allowed when `NODE_ENV=production` or - `CIPHERBOX_ENVIRONMENT=production`. -- **`cvm`** — Phala dstack `DstackClient.getKey()` call. Hardware-backed, non-extractable. - Required for production Phala Cloud CVM deployments. - ---- - ## Docker Compose (local dev) File: `docker/docker-compose.yml` -Starts PostgreSQL, IPFS (Kubo), Redis, Someguy (delegated routing), and a mock IPNS routing +Starts PostgreSQL, IPFS (Kubo), Someguy (delegated routing), and a mock IPNS routing server for local development. Environment variables can be overridden with a `.env` file in the `docker/` directory or by setting them in the shell before running `docker compose up`. @@ -198,7 +150,6 @@ server for local development. Environment variables can be overridden with a `.e | `DB_PASSWORD` | `postgres` | PostgreSQL superuser password. | | `DB_DATABASE` | `cipherbox` | Database name to create. | | `DB_PORT` | `5432` | Host port mapped to PostgreSQL 5432 inside the container. | -| `REDIS_PORT` | `6380` | Host port mapped to Redis 6379 inside the container. | Service ports exposed to the host: @@ -207,7 +158,6 @@ Service ports exposed to the host: | PostgreSQL | `5432` (configurable) | | | IPFS API | `5001` | Bound to all interfaces in dev | | IPFS Gateway | `8080` | Bound to all interfaces in dev | -| Redis | `6380` (configurable) | | | Someguy (delegated routing) | `8190` (HTTP), `4004` (libp2p) | | | Mock IPNS routing | `3001` (localhost only) | | @@ -217,8 +167,8 @@ Service ports exposed to the host: File: `docker/docker-compose.staging.yml` -Deploys the full stack including the API, IPFS, Redis, PostgreSQL, TEE worker, Someguy, Caddy -reverse proxy, and Grafana Alloy for log/metrics forwarding. +Deploys the full stack including the API, IPFS, PostgreSQL, Someguy, Caddy reverse proxy, and +Grafana Alloy for log/metrics forwarding. The API service reads its environment from `.env.staging` (passed via `env_file`). @@ -227,33 +177,15 @@ The API service reads its environment from `.env.staging` (passed via `env_file` These variables must be set in `.env.staging` (API) or directly in the Docker Compose environment block (infrastructure services): -| Variable | Service | Notes | -| :----------------------- | :--------------- | :------------------------------------------------------------- | -| `DB_USERNAME` | postgres | Defaults to `cipherbox` | -| `DB_PASSWORD` | postgres | **Required** — no default in staging | -| `DB_DATABASE` | postgres | Defaults to `cipherbox_staging` | -| `JWT_SECRET` | api | **Required** — any strong random string | -| `REDIS_PASSWORD` | redis / api | **Required** — staging Redis runs with `requirepass` | -| `TEE_WORKER_SECRET` | tee-worker / api | Must match between both services | -| `CORS_ALLOWED_ORIGINS` | api | Set to the deployed web app origin(s) | -| `IPFS_LOCAL_API_URL` | api | Typically `http://ipfs:5001` inside compose network | -| `IPFS_LOCAL_GATEWAY_URL` | api | Typically `http://ipfs:8080` inside compose network | -| `DELEGATED_ROUTING_URL` | api | Set to `http://someguy:8190` to use the local Someguy instance | -| `TEE_WORKER_URL` | api | Typically `http://tee-worker:3001` inside compose network | - -### Phala Cloud CVM (production TEE worker) - -File: `apps/tee-worker/docker-compose.phala.yml` - -Used for production TEE deployments on Phala Cloud. Key constraint: always **update** the -existing CVM using the same `--name` value — never delete and recreate. Recreating changes the -`app_id`, which invalidates all epoch-derived keys. - -| Variable | Source | Notes | -| :------------------------ | :--------------- | :---------------------------------------- | -| `TEE_WORKER_SECRET` | host environment | Injected at deploy time | -| `GITHUB_REPOSITORY_OWNER` | host environment | Used to resolve the container image path | -| `TAG` | host environment | Image tag to deploy; defaults to `latest` | +| Variable | Service | Notes | +| :--------------------- | :------- | :------------------------------------------------------ | +| `DB_USERNAME` | postgres | Defaults to `cipherbox` | +| `DB_PASSWORD` | postgres | **Required** — no default in staging | +| `DB_DATABASE` | postgres | Defaults to `cipherbox_staging` | +| `JWT_SECRET` | api | **Required** — any strong random string | +| `CORS_ALLOWED_ORIGINS` | api | Set to the deployed web app origin(s) | +| `KUBO_API_URL` | api | Typically `http://ipfs:5001` inside compose network | +| `ROUTING_V1_URL` | api | `http://someguy:8190` to use the local Someguy instance | --- diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2dda8d77a1..783a89eaa2 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,217 +1,5 @@ # Development Guide -## Prerequisites +> **v1 document — superseded.** This described the v1 local development setup as of the freeze (`v1-freeze`, branch `v1`). The current local-stack recipe lives in exactly one place: the "Getting started" section of the root [`README.md`](../README.md). -- **Node.js** 20+ -- **pnpm** 9+ -- **Docker** (for PostgreSQL, IPFS, Redis) -- **Rust** toolchain (desktop app only) - -## Infrastructure - -Start the required services: - -```bash -docker compose -f docker/docker-compose.yml up -d -``` - -This starts the `cipherbox-infrastructure` compose project (containers are named `cipherbox-`): - -| Service | Image | Host Port(s) | Purpose | -| :------------------ | :------------------------------------ | :----------------------------------------------- | :--------------------------------------- | -| `postgres` | `postgres:16-alpine` | 5432 (`DB_PORT`) | Database | -| `ipfs` | `ipfs/kubo:v0.42.0` | 5001 (API), 8080 (gateway), 4001 tcp/udp (swarm) | Decentralized storage (Kubo) | -| `redis` | `redis:7-alpine` | 6380 (`REDIS_PORT`) → container 6379 | BullMQ job queue | -| `someguy` | `ghcr.io/ipfs/someguy:v0.11.1` | 8190 (routing API), 4004 tcp/udp (libp2p swarm) | Delegated IPFS routing (accelerated DHT) | -| `mock-ipns-routing` | built from `tools/mock-ipns-routing/` | 3001 (loopback only) | Local IPNS resolution for dev/E2E | - -Notes: - -- The IPFS node runs with the `server,pebbleds` datastore profile. If you have an `ipfs_data` volume created before the pebbleds switch, recreate it first: `docker compose -f docker/docker-compose.yml down -v --remove-orphans`. -- The `ipfs` container is capped at 3 GB memory / 1.5 CPU; the `someguy` container is capped at 2 GB memory / 1 CPU. -- The staging stack runs a different set of containers (adds `api`, `tee-worker`, `caddy`, `alloy`; drops `mock-ipns-routing`) — see [DEPLOYMENT.md](DEPLOYMENT.md). -- **Strict IPNS verification cutover (Phase 60):** If your local database was created before the strict-verify cutover landed, it contains `folder_ipns` records with `sequence_number = 0` (embedded-0 records) that the API now rejects. These records cause fail-closed errors on any IPNS publish or resolve. Wipe your local database and let the API recreate it via migrations before running the strict build: `dropdb cipherbox && createdb cipherbox && pnpm --filter @cipherbox/api dev`. See [docs/DATABASE_EVOLUTION_PROTOCOL.md](DATABASE_EVOLUTION_PROTOCOL.md) §7 (Environment Behavior Matrix) for the full reset procedure. - -## Environment - -Copy the example env files: - -```bash -cp apps/api/.env.example apps/api/.env -cp apps/web/.env.example apps/web/.env -``` - -### API (`apps/api/.env`) - -Key variables: - -| Variable | Default | Notes | -| :--------------------- | :---------------------- | :-------------------------------------- | -| `DB_HOST` | `localhost` | PostgreSQL host | -| `DB_PORT` | `5432` | PostgreSQL port | -| `JWT_SECRET` | — | Required, any random string | -| `REDIS_HOST` | `localhost` | Redis host | -| `REDIS_PORT` | `6380` | Redis port (mapped from container 6379) | -| `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | Frontend origin | - -### Web (`apps/web/.env`) - -| Variable | Default | Notes | -| :------------------------ | :------------------------- | :------------------ | -| `VITE_WEB3AUTH_CLIENT_ID` | Provided in `.env.example` | Web3Auth project ID | -| `VITE_API_URL` | `http://localhost:3000` | API endpoint | - -## Running the Web App - -```bash -# Install dependencies (first time) -pnpm install - -# Start API + web concurrently -pnpm dev -``` - -- API: -- Web: - -Or run individually: - -```bash -pnpm --filter @cipherbox/api dev # API only -pnpm --filter @cipherbox/web dev # Web only -``` - -## Running the Desktop App - -### Additional prerequisites - -- **macOS:** [FUSE-T](https://www.fuse-t.org/) (`brew install macos-fuse-t/homebrew-cask/fuse-t`) -- **Windows:** [WinFSP](https://winfsp.dev/) -- **Linux:** `libfuse3-dev` (or equivalent) - -```bash -cp apps/desktop/.env.example apps/desktop/.env -pnpm --filter @cipherbox/desktop dev -``` - -The desktop app defaults to the staging API. For local development, update `apps/desktop/.env`: - -```bash -VITE_API_URL=http://localhost:3000 -VITE_ENVIRONMENT=local -``` - -The Rust backend also needs the local API URL. Either set it in your shell or prefix the dev command: - -```bash -CIPHERBOX_API_URL=http://localhost:3000 pnpm --filter @cipherbox/desktop dev -``` - -See [apps/desktop/CLAUDE.md](../apps/desktop/CLAUDE.md) for FUSE architecture details and dev-key mode. - -## Testing - -### Unit tests - -```bash -# Run all unit tests (excludes E2E) -pnpm --filter @cipherbox/api test -pnpm --filter @cipherbox/web test -pnpm --filter @cipherbox/crypto test -``` - -> **Note:** `pnpm test` runs tests across all workspaces including E2E — use the filtered commands above for unit tests only. - -### Test architecture and CI coverage (the deliberate split) - -The repo follows a deliberate testing split, and CI enforces it accordingly (decision D-06): - -- **Reusable / business logic → `packages/sdk` (Vitest, CI-gated).** Any logic worth unit-testing is hoisted out of `apps/web` into `packages/sdk` (or another package), where it is covered by Vitest. These suites run in the blocking CI `Test` job (`.github/workflows/ci.yml`), alongside `crypto`, `core`, `sdk-core`, `sdk`, `api-client`, and `api`. -- **UI behavior → Playwright web-e2e.** User-facing flows are covered by the Playwright web-e2e suite (`pnpm test:web-e2e`), which is dispatch/main-push gated rather than a per-PR blocking unit job. -- **`apps/web` Vitest is intentionally NOT in a blocking CI unit-test job.** A residual `apps/web` `*.test.ts` suite exists (10 files / 67 tests) and must stay green, but it is deliberately excluded from the blocking CI `Test` job. This is a decision, not an accidental gap: gating CI on `apps/web` Vitest would invite UI-coupled unit tests, which the split above is designed to prevent. Logic that deserves a unit test belongs in `packages/sdk`, not in a web-local test. - -Two caveats when working with the residual `apps/web` suite: - -- **`.spec.ts` is silently skipped.** The `apps/web` Vitest `include` glob matches `*.test.ts` only, so any `*.spec.ts` file is silently excluded — never rely on a `.spec.ts` under `apps/web` being executed. -- **Build the cross-package dist chain first.** Running the web suite locally requires the workspace dist to be built, or workspace-package resolution fails. Build the chain, then run the suite: - - ```bash - pnpm --filter @cipherbox/crypto build \ - && pnpm --filter @cipherbox/core build \ - && pnpm --filter @cipherbox/api-client build \ - && pnpm --filter @cipherbox/sdk-core build \ - && pnpm --filter @cipherbox/sdk build \ - && cd apps/web && pnpm vitest run - ``` - -If a residual `apps/web` test genuinely rots, relocate its logic to `packages/sdk` (Vitest) or remove the dead test — do not add new `apps/web` unit tests, and do not paper over a real failure by skipping it. - -### Strict IPNS verification — wipe local DB first - -The strict fail-closed IPNS verification cutover (Phase 60 / HARD-11) rejects any IPNS record that embeds sequence `0`. A local dev database created before the cutover holds such "embedded-0" records, so a pre-existing vault or folder will fail strict verification and fail to resolve. Before running the strict build against an existing local DB, wipe it per [`DATABASE_EVOLUTION_PROTOCOL.md`](./DATABASE_EVOLUTION_PROTOCOL.md) (§reset) and log in again — the vault self-bootstraps fresh strict-verified records. Because all IPNS keys are deterministically derived from the Web3Auth key, the wipe is non-destructive to identity. - -### E2E tests (Playwright) - -Playwright auto-starts API + web via `webServer` config (requires infra services: Postgres, IPFS, Redis): - -```bash -pnpm test:web-e2e -``` - -Headed mode (shows browser): - -```bash -pnpm test:web-e2e:headed -``` - -### Desktop E2E - -```bash -cd tests/desktop-e2e -pnpm exec playwright test -``` - -## API Client Generation - -After modifying API endpoints, DTOs, or controllers, regenerate the typed client to keep the web app in sync: - -```bash -pnpm api:generate -``` - -This generates the OpenAPI spec from the API, creates the typed client at `packages/api-client/`, and runs lint fixes. Always commit the regenerated files with your API changes. - -## Code Quality - -- **Linting:** `pnpm lint` (ESLint) / `pnpm lint:fix` -- **Markdown:** `pnpm lint:md` / `pnpm lint:md:fix` -- **Type checking:** `pnpm typecheck` -- **Formatting:** Prettier (runs via lint-staged on commit) -- **Commits:** [Conventional Commits](https://www.conventionalcommits.org/) enforced by commitlint (`feat:`, `fix:`, `docs:`, etc.) - -## Running the TEE Worker (Simulator Mode) - -The TEE worker (`apps/tee-worker`) republishes IPNS records. In production it runs inside a Phala Cloud CVM; locally it runs in **simulator mode**, which uses a deterministic HKDF-SHA256 seed instead of hardware-backed key derivation. - -Set the required environment variables and start the worker: - -```bash -TEE_MODE=simulator TEE_WORKER_SECRET=dev-secret pnpm --filter cipherbox-tee-worker dev -``` - -The worker listens on port `3001` by default. Note that the `mock-ipns-routing` container also binds `127.0.0.1:3001` — when running the worker alongside the local Docker infrastructure, pick a free port (e.g. `PORT=3002`) and point `TEE_WORKER_URL` at it. The API authenticates to the TEE worker using a shared `Bearer` token — set the same value in `apps/api/.env`: - -```bash -TEE_WORKER_SECRET=dev-secret -TEE_WORKER_URL=http://localhost:3001 -``` - -Key variables: - -| Variable | Required | Notes | -| :------------------ | :------- | :---------------------------------------------- | -| `TEE_MODE` | Yes | `simulator` (local) or `cvm` (Phala Cloud prod) | -| `TEE_WORKER_SECRET` | Yes | Shared secret for Bearer token auth | -| `PORT` | No | Defaults to `3001` | - -`TEE_MODE=simulator` is blocked at runtime if `NODE_ENV=production` or `CIPHERBOX_ENVIRONMENT=production` to prevent accidental use of the fixed seed in production. +For the normative v2 architecture, read the `blueprint/` corpus and `CONTEXT.md`. The v1 spec corpus, ADRs, and design-decision history live in [FSM1/cipher-box-next](https://github.com/FSM1/cipher-box-next). diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 315bf0ad11..d7cde06f7f 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -2,183 +2,6 @@ # Getting Started with CipherBox -CipherBox is a privacy-first encrypted cloud storage system using IPFS/IPNS and Web3Auth. -This guide takes you from a fresh checkout to a running local stack. +> **v1 document — superseded.** This described the v1 first-run setup as of the freeze (`v1-freeze`, branch `v1`). The current local-stack recipe lives in exactly one place: the "Getting started" section of the root [`README.md`](../README.md). -## Prerequisites - -| Tool | Version | Notes | -| :------------- | :--------- | :--------------------------------------------- | -| Node.js | 20+ | Used by all JS/TS workspaces | -| pnpm | 10.33.0 | Declared in `packageManager` field | -| Docker | Any recent | Runs PostgreSQL, IPFS, Redis, and mock routing | -| Rust toolchain | stable | Desktop app, and the web app's engine WASM | - -No `.nvmrc` is present; use your system Node version manager to select Node 20+. - -`apps/web` compiles `crates/wasm` into the engine worker's artifact on every `dev`/`build`, so it also -needs the browser target and a `wasm-bindgen-cli` matching the `wasm-bindgen` version in `Cargo.lock`: - -```bash -rustup target add wasm32-unknown-unknown -cargo install wasm-bindgen-cli --version "$(grep -A1 '^name = "wasm-bindgen"$' Cargo.lock | grep '^version' | head -1 | sed 's/.*"\(.*\)".*/\1/')" --locked -``` - -For the desktop app, also install the platform FUSE driver: - -- **macOS:** `brew install macos-fuse-t/homebrew-cask/fuse-t` -- **Windows:** [WinFSP](https://winfsp.dev/) -- **Linux:** `libfuse3-dev` (or the equivalent for your distribution) - -## Installation - -```bash -git clone https://github.com/YOUR_ORG/cipher-box.git -cd cipher-box -pnpm install -``` - -## Local Infrastructure - -All required backing services are defined in `docker/docker-compose.yml`. Start them before -running any application: - -```bash -docker compose -f docker/docker-compose.yml up -d -``` - -This starts the following services: - -| Service | Port(s) | Purpose | -| :-------------------------- | :--------------------------------------- | :------------------------------------ | -| PostgreSQL 16 | 5432 | Primary database | -| IPFS (Kubo v0.40.0) | 5001 (API), 8080 (Gateway), 4001 (Swarm) | Decentralized file storage | -| Redis 7 | 6380 (host) → 6379 (container) | BullMQ job queue | -| Someguy (delegated routing) | 8190 (HTTP), 4004 (libp2p) | IPFS DHT routing | -| Mock IPNS routing | 3001 | Local IPNS resolution for dev and E2E | - -Wait for all containers to be healthy before starting the applications: - -```bash -docker compose -f docker/docker-compose.yml ps -``` - -## Environment Setup - -Copy the example environment files for each application you intend to run: - -```bash -cp apps/api/.env.example apps/api/.env -cp apps/web/.env.example apps/web/.env -``` - -At minimum, set `JWT_SECRET` in `apps/api/.env` — startup fails without it. The default values -for all other variables in the `.env.example` files match the local Docker Compose stack. - -For the full list of variables and their descriptions, see [CONFIGURATION.md](CONFIGURATION.md). - -### Redis port note - -The Docker Compose file maps the Redis container port to **6380** on the host. Ensure -`apps/api/.env` contains `REDIS_PORT=6380` (this is already set in `.env.example`). - -## Running the Web Stack - -Start the API and web app together: - -```bash -pnpm dev -``` - -This runs `@cipherbox/api` and `@cipherbox/web` concurrently via `concurrently`. - -Or run each individually: - -```bash -pnpm --filter @cipherbox/api dev -pnpm --filter @cipherbox/web dev -``` - -Default URLs: - -- API: `http://localhost:3000` -- Web: `http://localhost:5173` - -## Running the Desktop App - -The desktop app requires the Rust toolchain and a FUSE driver (see Prerequisites above). - -```bash -cp apps/desktop/.env.example apps/desktop/.env -pnpm --filter @cipherbox/desktop dev -``` - -The desktop app targets the **staging API by default**. To develop against your local API, -edit `apps/desktop/.env`: - -```env -VITE_API_URL=http://localhost:3000 -VITE_ENVIRONMENT=local -``` - -Also pass the API URL to the Rust backend: - -```bash -CIPHERBOX_API_URL=http://localhost:3000 pnpm --filter @cipherbox/desktop dev -``` - -## Running the TEE Worker - -The TEE worker handles IPNS republishing. It is not required for basic web app development. - -```bash -pnpm --filter cipherbox-tee-worker dev -``` - -## First-Use Walkthrough - -1. Start infrastructure: `docker compose -f docker/docker-compose.yml up -d` -2. Start the web stack: `pnpm dev` -3. Open `http://localhost:5173` in your browser -4. Log in using Web3Auth (social login or wallet) -5. On first login a new encrypted vault is created — you will be prompted to save your - recovery factor -6. Upload a file using the drag-and-drop interface or the upload button -7. The file is encrypted client-side and stored on IPFS; the metadata is published to IPNS - -## Common Setup Issues - -### `JWT_SECRET` is missing - -The API exits immediately at startup if `JWT_SECRET` is not set in `apps/api/.env`. Set it -to any non-empty string for local development. - -### Redis connection refused - -The Docker Compose stack maps Redis to port 6380, not the default 6379. Confirm -`REDIS_PORT=6380` in `apps/api/.env`. - -### IPFS container unhealthy - -The IPFS container has a 30-second start period. If `docker compose ps` shows it as unhealthy -immediately after starting, wait 30–60 seconds and check again. If it remains unhealthy: - -```bash -docker compose -f docker/docker-compose.yml logs ipfs -``` - -### pnpm version mismatch - -The `packageManager` field pins pnpm to `10.33.0`. If your global pnpm differs, enable -[Corepack](https://nodejs.org/api/corepack.html) to use the pinned version automatically: - -```bash -corepack enable -``` - -## Next Steps - -- [DEVELOPMENT.md](DEVELOPMENT.md) — build commands, code style, branch conventions, PR process -- [CONFIGURATION.md](CONFIGURATION.md) — full environment variable reference for all apps -- [ARCHITECTURE.md](ARCHITECTURE.md) — system architecture and component overview -- [TESTING.md](TESTING.md) — how to run unit tests and E2E tests +For the normative v2 architecture, read the `blueprint/` corpus and `CONTEXT.md`. The v1 spec corpus, ADRs, and design-decision history live in [FSM1/cipher-box-next](https://github.com/FSM1/cipher-box-next). From cd25e921ea430a029834a71537ceaaaff14fc937 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 02:00:04 +0200 Subject: [PATCH 2/8] chore(api): report an unset ROUTING_V1_URL at boot and tighten the recipe Self-review follow-ups. - `RoutingV1RecordTransport` reports an unset `ROUTING_V1_URL` at boot, the same way the pin store now does. Its degraded path is the quieter of the two: `runOnce` returns before it can alert, so nothing surfaces until names start expiring. - trim the pin-store comment to the one non-obvious why; the fail-closed rationale already lives on the class doc. - use the repo's existing Logger-spy idiom in the new tests. - mark the recipe's example secrets local-only, and note that the dev compose binds Kubo's unauthenticated admin RPC to all interfaces. - repoint the last `docs/ARCHITECTURE.md` link that still sent readers to the emptied DEVELOPMENT.md. --- README.md | 20 ++++++++++++-------- apps/api/src/registry/pin-store.test.ts | 20 ++++++-------------- apps/api/src/registry/pin-store.ts | 6 ++---- apps/api/src/republisher/record-transport.ts | 5 +++++ docs/ARCHITECTURE.md | 2 +- 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 7792c82abf..33c2829b63 100644 --- a/README.md +++ b/README.md @@ -109,8 +109,7 @@ cipher-box/ Prerequisites: Node.js 22+, pnpm 10+, Docker, and the Rust toolchain (pinned by `rust-toolchain.toml`). -There are no `.env` files to copy. Every variable the stack needs is exported inline -below, so the recipe is read in the same breath as the commands that consume it. +There are no `.env` files to copy — every variable the stack needs is exported inline below. ### 1. Start the infrastructure @@ -120,7 +119,9 @@ pnpm install ``` That brings up Postgres (5432), Kubo (5001 RPC, 8080 gateway), someguy (8190), and the -mock record store (3001). Wait for them to report healthy: +mock record store (3001). Kubo's RPC is an unauthenticated admin API and the dev compose +binds it to all interfaces, so run this stack on a network you trust. Wait for the +services to report healthy: ```bash docker compose -f docker/docker-compose.yml ps @@ -128,6 +129,10 @@ docker compose -f docker/docker-compose.yml ps ### 2. Configure and start the API +The two secrets below are throwaway values for a loopback stack. Never reuse them in +any deployed environment: `JWT_SECRET` signs access tokens, and a `TEST_LOGIN_SECRET` +known to a reader mints a session for any account outside production. + ```bash export DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres \ DB_PASSWORD=postgres DB_DATABASE=cipherbox \ @@ -147,7 +152,7 @@ the same endpoint. The API logs an error at boot when it is unset. ### 3. Build and serve the web app -In a second shell, with the same `docker compose` stack up: +In a second shell: ```bash export VITE_API_URL=http://localhost:3000 \ @@ -161,9 +166,9 @@ pnpm --filter @cipherbox/web dev - API: (OpenAPI at `/api-docs`) - Web: -`VITE_ROUTING_ENDPOINTS` must be set: unset it defaults to the public -`https://delegated-ipfs.dev`, which will not see records this stack publishes. -`VITE_READ_ACCELERATOR_URL` is optional — left unset the content gateway stays dormant, +`VITE_ROUTING_ENDPOINTS` must be set — unset it defaults to the public +`https://delegated-ipfs.dev`; see "Which record store the local stack uses" below. +`VITE_READ_ACCELERATOR_URL` is optional: left unset the content gateway stays dormant, which is its fail-closed state, and reads fall back to the endpoints the engine already has. @@ -194,7 +199,6 @@ authenticated session use the build-time introspection hook instead; see A first folder create does not yet publish, because nothing provisions a fresh account's first vault pointer, so its writes are accepted, rendered pending, and reach no endpoint. -That is the remaining gap between this stack and a full demo. ## Security model diff --git a/apps/api/src/registry/pin-store.test.ts b/apps/api/src/registry/pin-store.test.ts index e796bb71b1..fae60c102c 100644 --- a/apps/api/src/registry/pin-store.test.ts +++ b/apps/api/src/registry/pin-store.test.ts @@ -1,5 +1,5 @@ import { Logger, ServiceUnavailableException } from '@nestjs/common'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest'; import { fakeConfig } from '../testing/fakes'; import { KuboPinStore, PinCidMismatchError } from './pin-store'; @@ -87,18 +87,11 @@ describe('KuboPinStore.pin', () => { }); }); -/** - * A misconfiguration that refuses every hosted write must surface at boot, not - * only as a 503 under load. - */ describe('KuboPinStore configuration report', () => { - let errors: string[]; + let errorSpy: MockInstance; beforeEach(() => { - errors = []; - vi.spyOn(Logger.prototype, 'error').mockImplementation((message: unknown) => { - errors.push(String(message)); - }); + errorSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); }); afterEach(() => { @@ -107,13 +100,12 @@ describe('KuboPinStore configuration report', () => { it('names the unset variable and its consequence at construction', () => { store(''); - expect(errors).toHaveLength(1); - expect(errors[0]).toContain('KUBO_API_URL'); - expect(errors[0]).toContain('503'); + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('KUBO_API_URL')); + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('503')); }); it('stays silent when Kubo is configured', () => { store(); - expect(errors).toEqual([]); + expect(errorSpy).not.toHaveBeenCalled(); }); }); diff --git a/apps/api/src/registry/pin-store.ts b/apps/api/src/registry/pin-store.ts index d1e0c7173e..b6a97a39fd 100644 --- a/apps/api/src/registry/pin-store.ts +++ b/apps/api/src/registry/pin-store.ts @@ -73,10 +73,8 @@ export class KuboPinStore extends PinStore { const raw = configService.get('KUBO_API_URL'); this.apiUrl = raw && raw.trim() ? raw.replace(/\/+$/, '') : undefined; if (!this.apiUrl) { - // Report at boot, not per request: unset, every hosted write 503s, and a - // deploy that only learns this from request logs learns it under load. - // Logged rather than thrown because an unconfigured store is a supported - // shape (BYO-only, unit tests) — see the class doc. + // At boot, not per request: otherwise a misconfigured deploy only learns + // this under load. this.logger.error( 'KUBO_API_URL is unset; hosted uploads will be refused with 503 and unpins will no-op' ); diff --git a/apps/api/src/republisher/record-transport.ts b/apps/api/src/republisher/record-transport.ts index 14c59ece08..c20ce9373c 100644 --- a/apps/api/src/republisher/record-transport.ts +++ b/apps/api/src/republisher/record-transport.ts @@ -59,6 +59,11 @@ export class RoutingV1RecordTransport extends RecordTransport { this.baseUrl = raw && raw.trim() ? raw.replace(/\/+$/, '') : undefined; const timeout = Number(configService.get('ROUTING_V1_TIMEOUT_MS')); this.timeoutMs = Number.isInteger(timeout) && timeout > 0 ? timeout : DEFAULT_TIMEOUT_MS; + if (!this.baseUrl) { + // The walk returns before it can alert, so boot is the only place this + // surfaces before names start expiring. + this.logger.error('ROUTING_V1_URL is unset; the republisher walk will not run'); + } } override get configured(): boolean { diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ca24b8d1ff..59b10bf0ab 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -394,4 +394,4 @@ Detailed specifications are maintained in separate documents: discipline, naming conventions, do-and-don't rules - [CAPACITY.md](CAPACITY.md) — storage limits, quota accounting, capacity planning - [VAULT_EXPORT_FORMAT.md](VAULT_EXPORT_FORMAT.md) — vault export/import format spec -- [DEVELOPMENT.md](DEVELOPMENT.md) — local dev setup, environment variables, workflow +- [../README.md](../README.md) — local dev setup: the one recipe that boots the stack From 4f413def73d9b6a3ef3ae3112c77541c3e04129b Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 02:06:29 +0200 Subject: [PATCH 3/8] chore(ci): carry the pin-store rename into desktop-e2e and share one store - `desktop-e2e.yml` set the same four dead v1 names and neither live one; swap them for `KUBO_API_URL` and `ROUTING_V1_URL`. The workflow already runs a real `ipfs daemon` on 5001, so the values are unchanged. - the content slice binds the registry's exported `PinStore` instead of constructing a second `KuboPinStore`, so an unconfigured store is reported once at boot rather than once per module. --- .github/workflows/desktop-e2e.yml | 18 ++++++------------ apps/api/src/content/content.module.ts | 5 +++-- apps/api/src/registry/registry.module.ts | 3 +++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/desktop-e2e.yml b/.github/workflows/desktop-e2e.yml index a0d6918691..9848b03e6e 100644 --- a/.github/workflows/desktop-e2e.yml +++ b/.github/workflows/desktop-e2e.yml @@ -274,10 +274,8 @@ jobs: DB_DATABASE=cipherbox_test JWT_SECRET=desktop-e2e-jwt-secret-key CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:1420 - IPFS_PROVIDER=local - IPFS_LOCAL_API_URL=http://localhost:5001 - IPFS_LOCAL_GATEWAY_URL=http://localhost:8080 - DELEGATED_ROUTING_URL=http://localhost:3001 + KUBO_API_URL=http://localhost:5001 + ROUTING_V1_URL=http://localhost:3001 REDIS_HOST=localhost REDIS_PORT=6379 TEST_LOGIN_SECRET=e2e-test-secret-ci-only @@ -337,10 +335,8 @@ jobs: DB_DATABASE: cipherbox_test JWT_SECRET: desktop-e2e-jwt-secret-key CORS_ALLOWED_ORIGINS: http://localhost:5173,http://localhost:1420 - IPFS_PROVIDER: local - IPFS_LOCAL_API_URL: http://localhost:5001 - IPFS_LOCAL_GATEWAY_URL: http://localhost:8080 - DELEGATED_ROUTING_URL: http://localhost:3001 + KUBO_API_URL: http://localhost:5001 + ROUTING_V1_URL: http://localhost:3001 REDIS_HOST: localhost REDIS_PORT: 6379 TEST_LOGIN_SECRET: e2e-test-secret-ci-only @@ -378,10 +374,8 @@ jobs: DB_DATABASE: cipherbox_test JWT_SECRET: desktop-e2e-jwt-secret-key CORS_ALLOWED_ORIGINS: http://localhost:5173,http://localhost:1420 - IPFS_PROVIDER: local - IPFS_LOCAL_API_URL: http://localhost:5001 - IPFS_LOCAL_GATEWAY_URL: http://localhost:8080 - DELEGATED_ROUTING_URL: http://localhost:3001 + KUBO_API_URL: http://localhost:5001 + ROUTING_V1_URL: http://localhost:3001 REDIS_HOST: localhost REDIS_PORT: 6379 TEST_LOGIN_SECRET: e2e-test-secret-ci-only diff --git a/apps/api/src/content/content.module.ts b/apps/api/src/content/content.module.ts index 9f01720dee..2fcb7716dd 100644 --- a/apps/api/src/content/content.module.ts +++ b/apps/api/src/content/content.module.ts @@ -6,7 +6,7 @@ import { buildJwtOptions } from '../auth/auth.module'; import { User } from '../auth/entities/user.entity'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { PinnedCid } from '../registry/entities/pinned-cid.entity'; -import { KuboPinStore, PinStore } from '../registry/pin-store'; +import { RegistryModule } from '../registry/registry.module'; import { ContentController } from './content.controller'; import { ContentService } from './content.service'; @@ -19,6 +19,7 @@ import { ContentService } from './content.service'; @Module({ imports: [ TypeOrmModule.forFeature([PinnedCid, User]), + RegistryModule, JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], @@ -26,6 +27,6 @@ import { ContentService } from './content.service'; }), ], controllers: [ContentController], - providers: [ContentService, JwtAuthGuard, { provide: PinStore, useClass: KuboPinStore }], + providers: [ContentService, JwtAuthGuard], }) export class ContentModule {} diff --git a/apps/api/src/registry/registry.module.ts b/apps/api/src/registry/registry.module.ts index 1baf86afdf..cff0989869 100644 --- a/apps/api/src/registry/registry.module.ts +++ b/apps/api/src/registry/registry.module.ts @@ -36,5 +36,8 @@ import { RegistryService } from './services/registry.service'; JwtAuthGuard, { provide: PinStore, useClass: KuboPinStore }, ], + // The content slice binds the same instance rather than constructing a second + // one, so an unconfigured store is reported once at boot. + exports: [PinStore], }) export class RegistryModule {} From e72380b3df1a71b9eda0354aae4e4c01ac566be7 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 09:47:12 +0200 Subject: [PATCH 4/8] docs: ship the .env templates the recipe copies The README told a reader to `cp apps/api/.env.example` and the file was not there. Restore both templates and make them the single place the local stack's configuration is written down. - `apps/api/.env.example` and `apps/web/.env.example` carry values that match the compose stack, so they run as copied. Every name in them has a reader in the code; the optional web names are commented out rather than blank. - `VITE_READ_ACCELERATOR_URL` ships commented out. Dormant is the content gateway's fail-closed state, so copying the template must not switch it on, and `config.ts` still gives it no default. - the README stops repeating the variable list and copies the templates instead, so the two cannot disagree. - drop the now-false claims that the repo ships no templates. - the desktop section named `VITE_GOOGLE_CLIENT_ID`, `VITE_TEST_LOGIN_SECRET` and `CIPHERBOX_API_URL` for a shell that has no sources and reads no environment; removed rather than given a template with no readers. --- README.md | 50 +++++++++++++++++------------------------ apps/api/.env.example | 38 +++++++++++++++++++++++++++++++ apps/web/.env.example | 28 +++++++++++++++++++++++ docs/CONFIGURATION.md | 52 +++++++------------------------------------ 4 files changed, 95 insertions(+), 73 deletions(-) create mode 100644 apps/api/.env.example create mode 100644 apps/web/.env.example diff --git a/README.md b/README.md index 33c2829b63..0414dd6de2 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,9 @@ cipher-box/ Prerequisites: Node.js 22+, pnpm 10+, Docker, and the Rust toolchain (pinned by `rust-toolchain.toml`). -There are no `.env` files to copy — every variable the stack needs is exported inline below. +Both services read a `.env` copied from a checked-in template. Those templates are the +one place the local stack's configuration is written down; this page does not repeat +their contents. ### 1. Start the infrastructure @@ -129,36 +131,27 @@ docker compose -f docker/docker-compose.yml ps ### 2. Configure and start the API -The two secrets below are throwaway values for a loopback stack. Never reuse them in -any deployed environment: `JWT_SECRET` signs access tokens, and a `TEST_LOGIN_SECRET` -known to a reader mints a session for any account outside production. - ```bash -export DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres \ - DB_PASSWORD=postgres DB_DATABASE=cipherbox \ - NODE_ENV=development JWT_SECRET=local-dev-jwt-secret \ - TEST_LOGIN_SECRET=local-dev-test-secret \ - KUBO_API_URL=http://localhost:5001 \ - ROUTING_V1_URL=http://localhost:3001 \ - CORS_ALLOWED_ORIGINS=http://localhost:5173 +cp apps/api/.env.example apps/api/.env pnpm --filter @cipherbox/api migration:run pnpm --filter @cipherbox/api dev ``` -`KUBO_API_URL` is the one the hosted pin store reads. Without it every write answers -503 — uploads and folder creates alike, since a record's head block is uploaded through -the same endpoint. The API logs an error at boot when it is unset. +The template's defaults match the compose stack, so it runs as copied. Both the server +and the migration CLI read `apps/api/.env` from the package directory, which +`pnpm --filter` sets as the working directory. + +Its two secrets are throwaway values for a loopback stack — never reuse them in a +deployed environment. `JWT_SECRET` signs access tokens, and anyone holding +`TEST_LOGIN_SECRET` can mint a session for any account outside production. ### 3. Build and serve the web app In a second shell: ```bash -export VITE_API_URL=http://localhost:3000 \ - VITE_ENVIRONMENT=local \ - VITE_ROUTING_ENDPOINTS=http://localhost:3001 \ - VITE_READ_ACCELERATOR_URL=http://localhost:8080 +cp apps/web/.env.example apps/web/.env pnpm --filter @cipherbox/web dev ``` @@ -166,20 +159,19 @@ pnpm --filter @cipherbox/web dev - API: (OpenAPI at `/api-docs`) - Web: -`VITE_ROUTING_ENDPOINTS` must be set — unset it defaults to the public -`https://delegated-ipfs.dev`; see "Which record store the local stack uses" below. -`VITE_READ_ACCELERATOR_URL` is optional: left unset the content gateway stays dormant, -which is its fail-closed state, and reads fall back to the endpoints the engine already -has. +Vite reads `.env` at build time, so rebuild after editing it. The template leaves +`VITE_READ_ACCELERATOR_URL` commented out on purpose: dormant is the content gateway's +fail-closed state, and a blank value must land there rather than configuring a gateway +whose every request fails. ### Which record store the local stack uses Compose starts two `/routing/v1` backends, and a local stack should use -**`mock-ipns-routing` on port 3001** — the setting above for both `ROUTING_V1_URL` (API -republisher) and `VITE_ROUTING_ENDPOINTS` (web client). It is hermetic and in-memory, so -a record published locally resolves immediately and deterministically, and no test -vault's IPNS names reach the public network. CI and the web-e2e suite make the same -choice. +**`mock-ipns-routing` on port 3001** — what both templates ship, as `ROUTING_V1_URL` +(API republisher) and `VITE_ROUTING_ENDPOINTS` (web client). It is hermetic and +in-memory, so a record published locally resolves immediately and deterministically, and +no test vault's IPNS names reach the public network. CI and the web-e2e suite make the +same choice. `someguy` on 8190 participates in the real accelerated DHT. It is there for staging parity and for deliberately testing public-network propagation; point the two variables diff --git a/apps/api/.env.example b/apps/api/.env.example new file mode 100644 index 0000000000..aaa74c4c54 --- /dev/null +++ b/apps/api/.env.example @@ -0,0 +1,38 @@ +# CipherBox API — local development template. +# +# Copy to apps/api/.env; both the server and the migration CLI read it from +# the package directory, which `pnpm --filter @cipherbox/api ...` sets. +# Every name below is read by the code — docs/CONFIGURATION.md catalogues the +# optional knobs this template leaves out. +# +# LOCAL ONLY. The two secrets here are throwaway values for a loopback stack. +# Never reuse them in a deployed environment. + +NODE_ENV=development +PORT=3000 +CORS_ALLOWED_ORIGINS=http://localhost:5173 + +# Postgres, matching docker/docker-compose.yml's defaults. +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=postgres +DB_DATABASE=cipherbox + +# Signs access tokens. A deployed API must set its own; the code refuses to +# fall back outside development and test. +JWT_SECRET=local-dev-jwt-secret + +# Enables POST /auth/test-login. Anyone holding this value can mint a session +# for any account outside production, where the route is hard-blocked. +TEST_LOGIN_SECRET=local-dev-test-secret + +# Kubo RPC for the hosted pin store. Unset, every hosted write answers 503 — +# uploads and folder creates alike, since a record's head block goes through +# the same endpoint. +KUBO_API_URL=http://localhost:5001 + +# The /routing/v1 endpoint the republisher resolves and re-PUTs through. +# 3001 is the compose stack's hermetic record store; someguy on 8190 is the +# real-DHT alternative. Must match the web app's VITE_ROUTING_ENDPOINTS. +ROUTING_V1_URL=http://localhost:3001 diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000000..0f0fc20761 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,28 @@ +# CipherBox web app — local development template. +# +# Copy to apps/web/.env. Vite reads it at BUILD time, so rebuild after editing. +# Every name below is read by the code; the commented ones are genuinely +# optional and are left unset on purpose. + +VITE_API_URL=http://localhost:3000 +VITE_ENVIRONMENT=local + +# Where the engine resolves and publishes records. Must name the same backend +# as the API's ROUTING_V1_URL, or the republisher re-PUTs into a store this +# client never reads. Unset, it defaults to the public delegated-ipfs.dev, +# which will not see records this stack publishes. +VITE_ROUTING_ENDPOINTS=http://localhost:3001 + +# Optional content-read accelerator, deliberately left unset: dormant is the +# fail-closed state, and a blank value must land there rather than configuring +# a gateway whose every request fails. Uncomment to point it at the compose +# Kubo gateway — but leave it absent rather than blank if you do not want it. +# VITE_READ_ACCELERATOR_URL=http://localhost:8080 + +# Optional comma-separated public gateways the engine may read from. +# VITE_PUBLIC_GATEWAYS= + +# Web3Auth Core Kit. Interactive login needs both. A clean checkout carries +# neither, so the UI renders but cannot create a session until they are set. +# VITE_WEB3AUTH_CLIENT_ID= +# VITE_WEB3AUTH_VERIFIER= diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index b5b4cab79c..67f00fb855 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -20,8 +20,8 @@ Environment variables and configuration files for all CipherBox monorepo applica ## API (`apps/api`) NestJS server. Configuration is loaded via `@nestjs/config` (`ConfigModule.forRoot`) and read -from `.env` at startup. The repo ships no `.env.example` files — the root `README.md` "Getting -started" section carries the inline export block the local stack expects. +from `.env` at startup. Copy `apps/api/.env.example` to `apps/api/.env` before first run; that +template carries the local stack's values and is the one this catalogue extends. ### Database @@ -77,7 +77,7 @@ started" section carries the inline export block the local stack expects. ## Web (`apps/web`) Vite + React SPA. All configuration is injected as `VITE_*` environment variables at build time. -There is no `.env.example` to copy — set the variables below in `apps/web/.env` or the shell. +Copy `apps/web/.env.example` to `apps/web/.env` before first run. | Variable | Required | Default | Description | | :------------------------ | :------- | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | @@ -92,47 +92,11 @@ There is no `.env.example` to copy — set the variables below in `apps/web/.env ## Desktop (`apps/desktop`) -Tauri + Vite + React application. Uses the same `VITE_*` convention as the web app. -There is no `.env.example` to copy — set the variables below in `apps/desktop/.env` or the shell. - -### Build-time (Vite) variables - -| Variable | Required | Default | Description | -| :------------------------ | :------- | :---------------------- | :-------------------------------------------------------------------------------------------------- | -| `VITE_API_URL` | No | `http://localhost:3000` | Base URL of the CipherBox API. | -| `VITE_WEB3AUTH_CLIENT_ID` | **Yes** | — | Web3Auth project client ID. | -| `VITE_GOOGLE_CLIENT_ID` | No | — | Google OAuth client ID. | -| `VITE_ENVIRONMENT` | No | `local` | Deployment environment label. | -| `VITE_TEST_LOGIN_SECRET` | No | — | Enables the test-login path inside the desktop app for E2E testing. Never set in production builds. | - -### Runtime (Rust backend) variables - -The Tauri Rust backend loads `apps/desktop/.env` at startup and resolves the API base URL in -this order: - -1. Runtime env `CIPHERBOX_API_URL` (manual override) -2. Runtime env `VITE_API_URL` -3. Compile-time `VITE_API_URL` (baked into release builds by CI) -4. Fallback `http://localhost:3000` - -| Variable | Required | Default | Description | -| :------------------ | :------- | :------ | :------------------------------------------------------------------------------------------------------------------------------ | -| `CIPHERBOX_API_URL` | No | — | Runtime override for the API base URL used by the Rust backend (sync engine, FUSE mount). Takes precedence over `VITE_API_URL`. | - -### Tauri configuration (`apps/desktop/src-tauri/tauri.conf.json`) - -Static build-time configuration — not environment-variable driven. - -| Key | Value | Description | -| :---------------------------------- | :---------------------------- | :------------------------------------------- | -| `productName` | `CipherBox` | Application display name. | -| `identifier` | `com.cipherbox.desktop` | Bundle identifier used on macOS and Linux. | -| `plugins.updater.endpoints` | GitHub Releases `latest.json` | Auto-updater manifest URL. | -| `plugins.deep-link.desktop.schemes` | `cipherbox` | Custom URL scheme registered with the OS. | -| `build.devUrl` | `http://localhost:1420` | Vite dev server URL used during `tauri dev`. | - -The updater `pubkey` in `tauri.conf.json` is the Minisign public key used to verify update -artifacts — it is safe to commit and is not a secret. +The v2 Tauri shell is a skeleton: it carries no TypeScript sources and reads no environment +variable, so there is no `apps/desktop/.env.example` to copy and nothing here to configure. +The tables that stood here described the v1 desktop app and named variables — `VITE_GOOGLE_CLIENT_ID`, +`VITE_TEST_LOGIN_SECRET`, `CIPHERBOX_API_URL` — that no code reads. They are removed rather +than corrected; `blueprint/desktop.md` is normative for what the shell will need. --- From ff32bb6e54f393ea8a6d8e677b54675be414d723 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 12:02:41 +0000 Subject: [PATCH 5/8] docs: gate the local recipe on service health and document the Web3Auth verifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recipe told the reader to wait for health and then handed them `docker compose ps`, which only prints the current status — so the migration could run against a Postgres still starting. Every service in the compose file defines a healthcheck, so `--wait` is the gate, with a timeout to bound it. CONFIGURATION.md marked only the client ID required and omitted the verifier, while `loginEnv` refuses a session missing either. A reader following the table alone built a UI that renders and cannot log in. The routing transport's boot report gains the regression tests its pin-store twin already had: the unset variable is named exactly once, and a configured endpoint stays silent. --- README.md | 10 +++--- .../src/republisher/record-transport.test.ts | 33 ++++++++++++++++--- docs/CONFIGURATION.md | 17 +++++----- 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0414dd6de2..dc4b44f764 100644 --- a/README.md +++ b/README.md @@ -116,18 +116,16 @@ their contents. ### 1. Start the infrastructure ```bash -docker compose -f docker/docker-compose.yml up -d +docker compose -f docker/docker-compose.yml up -d --wait --wait-timeout 180 pnpm install ``` That brings up Postgres (5432), Kubo (5001 RPC, 8080 gateway), someguy (8190), and the mock record store (3001). Kubo's RPC is an unauthenticated admin API and the dev compose -binds it to all interfaces, so run this stack on a network you trust. Wait for the -services to report healthy: +binds it to all interfaces, so run this stack on a network you trust. -```bash -docker compose -f docker/docker-compose.yml ps -``` +`--wait` holds until every service's healthcheck passes and exits non-zero if one does +not within the timeout, so the migration below cannot race a Postgres still starting. ### 2. Configure and start the API diff --git a/apps/api/src/republisher/record-transport.test.ts b/apps/api/src/republisher/record-transport.test.ts index d20d959055..8a99dac729 100644 --- a/apps/api/src/republisher/record-transport.test.ts +++ b/apps/api/src/republisher/record-transport.test.ts @@ -1,4 +1,5 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { Logger } from '@nestjs/common'; +import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest'; import { fakeConfig } from '../testing/fakes'; import { RoutingV1RecordTransport } from './record-transport'; @@ -55,10 +56,8 @@ function fakeResponse(opts: { return { response, read, readerCancel, bodyCancel }; } -function transport(): RoutingV1RecordTransport { - return new RoutingV1RecordTransport( - fakeConfig({ ROUTING_V1_URL: 'https://routing.test' }).service - ); +function transport(routingUrl = 'https://routing.test'): RoutingV1RecordTransport { + return new RoutingV1RecordTransport(fakeConfig({ ROUTING_V1_URL: routingUrl }).service); } describe('RoutingV1RecordTransport response-size cap', () => { @@ -165,3 +164,27 @@ describe('RoutingV1RecordTransport response-size cap', () => { expect(read.mock.calls.length).toBeLessThan(16); }); }); + +describe('RoutingV1RecordTransport configuration report', () => { + let errorSpy: MockInstance; + + beforeEach(() => { + errorSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => undefined); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('names the unset variable and its consequence at construction', () => { + transport(''); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('ROUTING_V1_URL')); + expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('walk')); + }); + + it('stays silent when the routing endpoint is configured', () => { + transport(); + expect(errorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 67f00fb855..0c0b538361 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -79,14 +79,15 @@ template carries the local stack's values and is the one this catalogue extends. Vite + React SPA. All configuration is injected as `VITE_*` environment variables at build time. Copy `apps/web/.env.example` to `apps/web/.env` before first run. -| Variable | Required | Default | Description | -| :------------------------ | :------- | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | -| `VITE_API_URL` | No | `http://localhost:3000` | Base URL of the CipherBox API. | -| `VITE_WEB3AUTH_CLIENT_ID` | **Yes** | — | Web3Auth project client ID for key derivation and authentication. | -| `VITE_GOOGLE_CLIENT_ID` | No | — | Google OAuth client ID for the Google Sign-In provider via Web3Auth. | -| `VITE_ENVIRONMENT` | No | `local` | Deployment environment label (`local`, `staging`, `production`). Used to show the staging banner in the UI. | -| `VITE_APP_VERSION` | No | (derived from crypto version) | Application version string injected at build time. Falls back to the internal crypto library version when absent. | -| `VITE_FARO_URL` | No | — | Grafana Faro collector endpoint. When absent, frontend observability is disabled. | +| Variable | Required | Default | Description | +| :------------------------ | :------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `VITE_API_URL` | No | `http://localhost:3000` | Base URL of the CipherBox API. | +| `VITE_WEB3AUTH_CLIENT_ID` | **Yes** | — | Web3Auth project client ID for key derivation and authentication. | +| `VITE_WEB3AUTH_VERIFIER` | **Yes** | — | Web3Auth Core Kit verifier name. Login needs this and the client ID; missing either refuses the session. | +| `VITE_GOOGLE_CLIENT_ID` | No | — | Google OAuth client ID for the Google Sign-In provider via Web3Auth. | +| `VITE_ENVIRONMENT` | No | `local` | Deployment environment label (`local`, `staging`, `production`). Used to show the staging banner in the UI. | +| `VITE_APP_VERSION` | No | (derived from crypto version) | Application version string injected at build time. Falls back to the internal crypto library version when absent. | +| `VITE_FARO_URL` | No | — | Grafana Faro collector endpoint. When absent, frontend observability is disabled. | --- From 779c5923aa0ea5a72e487c91030eafa817fd23da Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 12:22:48 +0000 Subject: [PATCH 6/8] ci: drop the dead Redis plumbing from the desktop e2e workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing in v2 reads REDIS_HOST or REDIS_PORT, and blueprint/deploy.md records redis as dead: nothing queues, and throttling is in-process. The workflow still installed and health-checked a server on all three OSes and set both variables at three sites, so every run paid for a service no code would ever open. Removal only — the file stays DORMANT and its stale package builds are untouched. --- .github/workflows/desktop-e2e.yml | 44 ------------------------------- 1 file changed, 44 deletions(-) diff --git a/.github/workflows/desktop-e2e.yml b/.github/workflows/desktop-e2e.yml index 9848b03e6e..f53778a1a2 100644 --- a/.github/workflows/desktop-e2e.yml +++ b/.github/workflows/desktop-e2e.yml @@ -214,44 +214,6 @@ jobs: sleep 1 done - - name: Install Redis (macOS) - if: runner.os == 'macOS' - run: | - brew install redis - brew services start redis - - - name: Install Redis (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get install -y redis-server - sudo systemctl start redis-server - - - name: Install Redis (Windows) - if: runner.os == 'Windows' - shell: powershell - run: | - choco install memurai-developer -y --no-progress - # Memurai installs as a Windows service and starts automatically - $svc = Get-Service -Name "Memurai" -ErrorAction SilentlyContinue - if ($svc -and $svc.Status -eq "Running") { - Write-Host "Memurai service is running" - } else { - # Fallback: start service manually - Start-Service -Name "Memurai" -ErrorAction SilentlyContinue - Start-Sleep -Seconds 3 - } - # Verify Redis is responding - $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + $env:PATH - $ready = $false - for ($i = 0; $i -lt 10; $i++) { - try { - $result = & redis-cli ping 2>$null - if ($result -eq "PONG") { $ready = $true; break } - } catch {} - Start-Sleep -Seconds 1 - } - if ($ready) { Write-Host "Redis (Memurai) ready" } else { Write-Host "WARNING: Redis may not be ready" } - # --- Build backend packages --- - name: Build mock-ipns-routing @@ -276,8 +238,6 @@ jobs: CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:1420 KUBO_API_URL=http://localhost:5001 ROUTING_V1_URL=http://localhost:3001 - REDIS_HOST=localhost - REDIS_PORT=6379 TEST_LOGIN_SECRET=e2e-test-secret-ci-only ACCESS_TOKEN_TTL=2h ENVEOF @@ -337,8 +297,6 @@ jobs: CORS_ALLOWED_ORIGINS: http://localhost:5173,http://localhost:1420 KUBO_API_URL: http://localhost:5001 ROUTING_V1_URL: http://localhost:3001 - REDIS_HOST: localhost - REDIS_PORT: 6379 TEST_LOGIN_SECRET: e2e-test-secret-ci-only # Long TTL: the headless desktop binary holds one token for the whole # suite and cannot silently refresh; 15m expires mid-run on macOS. @@ -376,8 +334,6 @@ jobs: CORS_ALLOWED_ORIGINS: http://localhost:5173,http://localhost:1420 KUBO_API_URL: http://localhost:5001 ROUTING_V1_URL: http://localhost:3001 - REDIS_HOST: localhost - REDIS_PORT: 6379 TEST_LOGIN_SECRET: e2e-test-secret-ci-only IDENTITY_JWT_PRIVATE_KEY: ${{ secrets.IDENTITY_JWT_PRIVATE_KEY }} From f548d529fed1ef51753863c6b975e76755c57ba5 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 12:37:11 +0000 Subject: [PATCH 7/8] ci: drop the legacy frontend build from the desktop e2e workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The step built five packages that do not exist — crypto, core, api-client, sdk-core and sdk — and then ran vite in apps/desktop, which carries no vite config, no vite dependency and no sources. The v2 shell embeds a checked-in static index.html through tauri.conf.json's frontendDist, so the cargo build needs nothing built ahead of it. The whole step goes rather than its first five lines, since the vite invocation was as dead as the packages. Nothing downstream consumed its output. No replacement is guessed here: what the mounted-desktop matrix needs is decided when the workflow is rewired. --- .github/workflows/desktop-e2e.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/desktop-e2e.yml b/.github/workflows/desktop-e2e.yml index f53778a1a2..9e1eca8049 100644 --- a/.github/workflows/desktop-e2e.yml +++ b/.github/workflows/desktop-e2e.yml @@ -112,21 +112,6 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - # --- Build desktop frontend (Tauri embeds from frontendDist) --- - - - name: Build desktop frontend - run: | - pnpm --filter @cipherbox/crypto build - pnpm --filter @cipherbox/core build - pnpm --filter @cipherbox/api-client build - pnpm --filter @cipherbox/sdk-core build - pnpm --filter @cipherbox/sdk build - cd apps/desktop - pnpm vite build - env: - VITE_API_URL: http://localhost:3000 - VITE_TEST_LOGIN_SECRET: e2e-test-secret-ci-only - # --- Build debug binary --- - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 From 3abf0b6e2a59a3105161e05e32f349cc4f18e5a6 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Sun, 9 Aug 2026 14:27:01 +0000 Subject: [PATCH 8/8] test: pin the routing diagnostic to its consequence, not the word walk stringContaining('walk') passed on any message carrying that substring, so a report that named the variable and dropped what goes wrong still satisfied a test whose name promises the consequence. It now matches the phrase itself. --- apps/api/src/republisher/record-transport.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/api/src/republisher/record-transport.test.ts b/apps/api/src/republisher/record-transport.test.ts index 8a99dac729..fb95cb8270 100644 --- a/apps/api/src/republisher/record-transport.test.ts +++ b/apps/api/src/republisher/record-transport.test.ts @@ -180,7 +180,9 @@ describe('RoutingV1RecordTransport configuration report', () => { transport(''); expect(errorSpy).toHaveBeenCalledTimes(1); expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('ROUTING_V1_URL')); - expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('walk')); + expect(errorSpy).toHaveBeenCalledWith( + expect.stringContaining('the republisher walk will not run') + ); }); it('stays silent when the routing endpoint is configured', () => {