Skip to content
Merged
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
141 changes: 55 additions & 86 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,78 +1,70 @@
# `production` for real deployments; `local-evaluation` relaxes credential
# validation for local development and is what the integration suites expect.
# Evaluation mode accepts the documented default root credentials; production
# refuses them. `pnpm stack:up` / `pnpm test:integration` force evaluation mode
# for the stack they manage, so this value is the one a real deployment gets.
SELFHOST_MODE=production
# Cloudflare deployment. Uncomment these values for a live deployment. The
# domain values are hostnames only; the zones must already exist in the account.
# CLOUDFLARE_ACCOUNT_ID=
# CLOUDFLARE_API_TOKEN=
# VOIDHASH_BACKEND_DOMAIN=api.example.com
# VOIDHASH_WWW_DOMAIN=app.example.com
VOIDHASH_WORKERS_DEV_ENABLED=true

# Local PostgreSQL used by Alchemy Hyperdrive and the migration CLI.
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_USERNAME=voidhash
DATABASE_PASSWORD=replace-with-a-random-password
DATABASE_PASSWORD=password
DATABASE_NAME=voidhash
DATABASE_SSL=false
# Direct-TCP overrides for the migration process (`pnpm migrate`) and the local
# migration CLI (`pnpm db:migrate`). Each one falls back to its DATABASE_*
# counterpart, so leave them unset unless DATABASE_HOST points at a sandboxed or
# proxied endpoint — a connection broker, or a
# Hyperdrive-style local socket — that only resolves inside the runtime serving
# requests. Migrations run in their own process and need the origin address.
# DATABASE_DIRECT_HOST=postgres
# DATABASE_DIRECT_PORT=5432
# DATABASE_DIRECT_NAME=voidhash
# DATABASE_DIRECT_USERNAME=voidhash
# DATABASE_DIRECT_PASSWORD=replace-with-a-random-password
# DATABASE_DIRECT_SSL=false
# Overrides for platform state — cluster mailboxes, workflow executions,
# persisted queues, entity alarms, and the platform key-value store. Each falls
# back to its DATABASE_* counterpart, so leaving them unset keeps that state
# beside application data, which is what a deployment wants. They exist because a
# single-node cluster claims every shard in its database: a process that must not
# contend with the deployment for shards needs a database of its own, which is
# how `pnpm test:integration` isolates the suites that build their own cluster.
# DATABASE_PLATFORM_HOST=postgres
# DATABASE_PLATFORM_PORT=5432
# DATABASE_PLATFORM_NAME=voidhash
# DATABASE_PLATFORM_USERNAME=voidhash
# DATABASE_PLATFORM_PASSWORD=replace-with-a-random-password
# DATABASE_PLATFORM_SSL=false

# Optional public backend origin override. This defaults to the backend custom
# domain in live deployments and http://localhost:8787 in local development.
# Set it when deploying without VOIDHASH_BACKEND_DOMAIN.
# PAYWALL_PUBLIC_BASE_URL=https://api.example.com

# Community root account and session signing. Replace these values for every
# live stage; the defaults are only for loopback development.
VOIDHASH_ROOT_USERNAME=root
VOIDHASH_ROOT_PASSWORD=voidhash
VOIDHASH_ROOT_EMAIL=root@voidhash.local
VOIDHASH_AUTH_SECRET=local-development-secret-at-least-32-chars

# Optional backend integrations.
APNS_DELIVERY_ENABLED=false
PUSH_REQUIRE_ENCRYPTION=true
ENCRYPTION_KEY=
EXCHANGE_RATE_API_KEY=
GOOGLE_PUBSUB_PUSH_AUDIENCE=
GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT_EMAIL=
SLACK_BOT_TOKEN=
SLACK_FEEDBACK_CHANNEL_ID=

# Direct-TCP migration overrides. Leave unset unless DATABASE_HOST is a proxy
# that only resolves inside the Worker runtime.
# DATABASE_DIRECT_HOST=
# DATABASE_DIRECT_PORT=
# DATABASE_DIRECT_NAME=
# DATABASE_DIRECT_USERNAME=
# DATABASE_DIRECT_PASSWORD=
# DATABASE_DIRECT_SSL=

# Test-only Node fixture. `pnpm test:integration` sets evaluation mode and
# derives the PLATFORM_NODE_* connection variables automatically.
DATABASE_HOST_PORT=5432
COMPILER_HOST_PORT=5002
MIMIC_ROOT_USERNAME=root
MIMIC_ROOT_PASSWORD=replace-with-a-random-password
MIMIC_ROOT_PASSWORD=password
MIMIC_PORT=5001
PUBLIC_BASE_URL=http://localhost:5001
PUBLIC_FILES_BASE_URL=http://localhost:5001
MIMIC_CORS_ORIGINS=https://voidhash.localhost,https://mimic-admin.voidhash.localhost,http://localhost:3000,http://localhost:3003
MIMIC_CORS_ORIGINS=http://localhost:3000
MIMIC_DOCUMENT_IDLE_NOTIFY_DEBOUNCE_MS=15000
MIMIC_PORT=5001
# The single root account. Voidhash self-host is single-player: these are the
# only credentials that can sign in, and there is no sign-up. Required in
# production mode; `local-evaluation` falls back to root / voidhash.
VOIDHASH_ROOT_USERNAME=root
VOIDHASH_ROOT_PASSWORD=replace-with-a-random-password
# Optional; defaults to root@voidhash.local. Used as the root user's address.
# VOIDHASH_ROOT_EMAIL=
# Signs the dashboard and API session tokens. Required in production mode.
VOIDHASH_AUTH_SECRET=replace-with-at-least-32-random-characters
# Durable agent model access. Configure at least one provider.
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
# OPENAI_BASE_URL=https://your-openai-compatible-host/v1
# VOIDHASH_AGENT_MODEL_PROVIDER=openai
# VOIDHASH_AGENT_MODEL_ID=gpt-5.4
# VOIDHASH_AGENT_VISION_MODEL_PROVIDER=openai
# VOIDHASH_AGENT_VISION_MODEL_ID=gpt-5.4
# Required when Google Play RTDN is enabled. These must match the Pub/Sub push subscription.
GOOGLE_PUBSUB_PUSH_AUDIENCE=
GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT_EMAIL=
# Optional offline Enterprise activation; configure the token and issuer verification key together.
VOIDHASH_LICENSE_KEY=
VOIDHASH_LICENSE_PUBLIC_KEY=
ENCRYPTION_KEY=
APNS_DELIVERY_ENABLED=false
EXCHANGE_RATE_API_KEY=

S3_ACCESS_KEY_ID=voidhash
S3_SECRET_ACCESS_KEY=replace-with-a-random-password
S3_SECRET_ACCESS_KEY=password
S3_REGION=us-east-1
S3_PUBLIC_BUCKET=voidhash-public
S3_ARTIFACT_BUCKET=voidhash-artifacts
MINIO_API_PORT=9000
MINIO_CONSOLE_PORT=9001

SMTP_HOST=mailpit
SMTP_PORT=1025
SMTP_SECURE=false
Expand All @@ -86,27 +78,4 @@ SMTP_VERIFY_ON_START=true
MAILPIT_SMTP_PORT=1025
MAILPIT_UI_PORT=8025

# ── Local development & integration tests ────────────────────────────────────
# Used together with docker-compose.dev.yml:
# docker compose -f docker-compose.yml -f docker-compose.dev.yml \
# up -d --build
# `pnpm test:integration` (repo root) reads this file and derives host-side
# connection settings from the values below, so the whole suite runs against
# this stack with no additional configuration.

# Host ports published by the dev overlay. Change them only when another local
# service already owns the default.
DATABASE_HOST_PORT=5432
COMPILER_HOST_PORT=5002


# Browser used by the screenshot integration tests on the host. The container
# ships its own chromium; this is only for host-side test runs.
# PLATFORM_SELFHOST_CHROMIUM_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome

# ── Values you must provide ──────────────────────────────────────────────────
# VOIDHASH_ROOT_PASSWORD / VOIDHASH_AUTH_SECRET — sign-in. Production mode
# refuses to start until both hold real values.
# OPENAI_API_KEY / ANTHROPIC_API_KEY — required only for the AI designer agent.
# EXCHANGE_RATE_API_KEY — required only for the FX rate sync job.
# ENCRYPTION_KEY — required for payment-provider credential storage.
# PLATFORM_NODE_CHROMIUM_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
22 changes: 9 additions & 13 deletions .github/workflows/selfhost.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Self-host Compose
name: Node Integration Fixture

on:
pull_request:
Expand All @@ -12,11 +12,11 @@ permissions:
contents: read

concurrency:
group: selfhost-compose-${{ github.head_ref || github.ref }}
group: node-integration-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

env:
COMPOSE_PROJECT_NAME: voidhash-selfhost-ci-${{ github.run_id }}-${{ github.run_attempt }}
COMPOSE_PROJECT_NAME: voidhash-node-ci-${{ github.run_id }}-${{ github.run_attempt }}

jobs:
smoke:
Expand Down Expand Up @@ -54,24 +54,20 @@ jobs:
- name: Prepare the stack environment
run: |
cp .env.example .env
# `.env.example` is a deployment template, so it selects production
# mode, which refuses its own placeholder secrets. This is a loopback
# CI stack: `pnpm stack:up` forces the same mode locally.
sed -i 's|^SELFHOST_MODE=.*|SELFHOST_MODE=local-evaluation|' .env
# The thumbnail assertions wait on the idle debounce.
sed -i 's|^MIMIC_DOCUMENT_IDLE_NOTIFY_DEBOUNCE_MS=.*|MIMIC_DOCUMENT_IDLE_NOTIFY_DEBOUNCE_MS=250|' .env
grep -E '^[A-Z][A-Z0-9_]*=' .env >> "$GITHUB_ENV"

- name: Start stateful stores
run: docker compose -f selfhost/docker-compose.yml -f selfhost/docker-compose.dev.yml --env-file .env up -d minio --wait --wait-timeout 180
run: docker compose -f test/integration/docker-compose.yml -f test/integration/docker-compose.dev.yml --env-file .env --project-directory test/integration up -d minio --wait --wait-timeout 180

- name: Initialize object store
run: docker compose -f selfhost/docker-compose.yml -f selfhost/docker-compose.dev.yml --env-file .env run --rm minio-init
run: docker compose -f test/integration/docker-compose.yml -f test/integration/docker-compose.dev.yml --env-file .env --project-directory test/integration run --rm minio-init

# The dev overlay publishes PostgreSQL and the compiler, which the
# host-side integration tier connects to.
- name: Build and start Community Compose
run: docker compose -f selfhost/docker-compose.yml -f selfhost/docker-compose.dev.yml --env-file .env up --build --wait --wait-timeout 180
run: docker compose -f test/integration/docker-compose.yml -f test/integration/docker-compose.dev.yml --env-file .env --project-directory test/integration up --build --wait --wait-timeout 180

- name: Reclaim image build cache
run: docker builder prune --all --force
Expand All @@ -95,9 +91,9 @@ jobs:
- name: Show Compose diagnostics
if: always()
run: |
docker compose -f selfhost/docker-compose.yml -f selfhost/docker-compose.dev.yml ps || true
docker compose -f selfhost/docker-compose.yml -f selfhost/docker-compose.dev.yml logs --no-color || true
docker compose -f test/integration/docker-compose.yml -f test/integration/docker-compose.dev.yml --project-directory test/integration ps || true
docker compose -f test/integration/docker-compose.yml -f test/integration/docker-compose.dev.yml --project-directory test/integration logs --no-color || true

- name: Stop Compose
if: always()
run: docker compose -f selfhost/docker-compose.yml -f selfhost/docker-compose.dev.yml down --volumes --remove-orphans
run: docker compose -f test/integration/docker-compose.yml -f test/integration/docker-compose.dev.yml --project-directory test/integration down --volumes --remove-orphans
55 changes: 19 additions & 36 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,50 +38,33 @@ pnpm typecheck
pnpm test
```

Use `pnpm check:publication` to validate license metadata and the public/private
repository boundary. The [self-hosting guide](selfhost/README.md) documents the
local Compose environment and its smoke tests.
Use `pnpm check:publication` to validate license metadata and the repository
boundary. The [Cloudflare deployment guide](docs/cloudflare-deployment.md)
documents the local and live Alchemy workflow.

Linting and formatting go through vite-plus: `pnpm lint` (`vp check`) and
`pnpm format` (`vp check --fix`).

`pnpm dev` starts every browser-facing development surface and the services
used by the Mimic example through Portless. The first run creates and trusts a
local certificate authority for the named HTTPS routes:

Run `pnpm dev` as your normal user, never through `sudo`. Portless elevates only
its HTTPS proxy when necessary, while the application processes remain owned by
your user. Startup also prunes orphaned Portless children left by crashed dev
sessions before checking the fixed ports. Use `pnpm dev:status` to inspect active
routes and `pnpm dev:doctor` to diagnose the proxy, certificate, or DNS setup.

| Surface | URL | App port |
| ------------------ | ---------------------------------------------- | -------- |
| Dashboard and docs | `https://voidhash.localhost` | `3000` |
| Mimic example API | `https://mimic-example-api.voidhash.localhost` | `3001` |
| Mimic admin | `https://mimic-admin.voidhash.localhost` | `3003` |
| Email previews | `https://emails.voidhash.localhost` | `3010` |
| Studio | `https://studio.voidhash.localhost` | `4830` |
| Mimic database | `https://mimic.voidhash.localhost` | `5001` |
| Mimic example | `https://mimic-example.voidhash.localhost` | `5173` |

The ports are strict: if another process is using one, startup fails instead of
silently moving an app and breaking its local links.

The steps above describe a **standalone clone** of this repository, which installs
its own `node_modules` from this repository's lockfile. This repository is also
consumed as a nested workspace by Voidhash's private monorepo. In that mode the
superproject's root install is authoritative: it already covers every package here,
this directory must **not** have its own `node_modules` (two installs give
`drizzle-orm`/`@types/react` duplicate TypeScript type identities), and all commands
are run from the superproject root rather than from here.
Start PostgreSQL, apply migrations, and launch the Community Alchemy stack:

```sh
cp .env.example .env
docker compose up -d standalone_postgres
pnpm db:migrate
pnpm dev
```

Alchemy serves the backend on `http://localhost:8787` and the web application
on `http://localhost:3000`. Ports are strict so local links cannot silently move
between runs.

## Testing

Run the smallest relevant package tests while iterating, then run the repository
typecheck and test graph before requesting review. Changes to the Node runtime
or Compose configuration should also pass both self-host smoke tests documented
in [selfhost/README.md](selfhost/README.md#smoke-test).
typecheck and test graph before requesting review. `pnpm test:integration`
provisions the test-only Node fixture used by database and optional Node adapter
tests. Use `pnpm test:infra:up` and `pnpm test:infra:down` when debugging that
fixture directly.

## License zones

Expand Down
5 changes: 2 additions & 3 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ The full MIT License is in [LICENSES/MIT.txt](LICENSES/MIT.txt).

## AGPL service code

The backend, dashboard, service packages, and self-hosting code that declare
The backend, dashboard, service packages, and deployment adapters that declare
`AGPL-3.0-only` in their package metadata or carry a local AGPL notice are
licensed under the GNU Affero General Public License, version 3 only. The full
license is in [LICENSES/AGPL-3.0-only.txt](LICENSES/AGPL-3.0-only.txt).

## Enterprise code

Enterprise code is not included in this repository and remains in Voidhash's
private cloud repository. The
Commercial code is not included in this repository. The
[Voidhash Enterprise License](LICENSES/Voidhash-Enterprise.md) is retained here
as the canonical text for any separately distributed Enterprise Software, but
it does not apply to code unless a file or directory expressly says so.
Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

> [!IMPORTANT]
> This private validation branch contains the complete Community platform,
> including the backend and self-hosting composition. The repository remains
> including the backend and Cloudflare composition. The repository remains
> private through alpha and beta security validation and must not be described
> as publicly launched until the publication gate is complete.

Expand All @@ -56,10 +56,12 @@ voidhash-cli init

## 📚 Documentation

For product documentation, visit [voidhash.com](https://voidhash.com/docs). To
run the Community platform locally, see the [self-hosting guide](selfhost/README.md).
The [architecture overview](docs/architecture.md) explains the Community,
Cloud, and Enterprise composition boundaries, and the
For product documentation, visit [voidhash.com](https://voidhash.com/docs).
`pnpm dev` runs the Community Alchemy/Cloudflare composition; see the
[Cloudflare guide](docs/cloudflare-deployment.md) for local and live
deployment.
The [architecture overview](docs/architecture.md) explains the Community
runtime and package boundaries, and the
[licensing and self-hosting FAQ](docs/licensing-and-self-hosting-faq.md) covers
AGPL and the self-hosting model.

Expand All @@ -72,10 +74,9 @@ and [Security Policy](SECURITY.md).
## 📄 License

This repository uses explicit license zones. SDKs and client libraries are
MIT-licensed; the backend, dashboard, service packages, and self-hosting code
are AGPL-3.0-only. Closed Enterprise implementation remains in the private
cloud repository and is not included here. See [LICENSE.md](LICENSE.md) for the
authoritative map and full texts.
MIT-licensed; the backend, dashboard, service packages, and deployment adapters
are AGPL-3.0-only. Commercial features are not included here. See
[LICENSE.md](LICENSE.md) for the authoritative map and full texts.

## 🔗 Links

Expand Down
1 change: 1 addition & 0 deletions alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./apps/backend/stack.ts";
6 changes: 3 additions & 3 deletions apps/backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ RUN apt-get update \
WORKDIR /repo
COPY . .
RUN corepack pnpm@11.1.3 install --frozen-lockfile --filter @voidhash/backend-app... --filter @voidhash/www... --ignore-scripts --config.node-linker=isolated
RUN VITE_APP_API_URL= VITE_APP_ENV=production VOIDHASH_SELFHOST_BUNDLE=true corepack pnpm@11.1.3 exec turbo build --filter @voidhash/www
RUN VITE_APP_API_URL= VITE_APP_ENV=production VOIDHASH_NODE_BUNDLE=true corepack pnpm@11.1.3 exec turbo build --filter @voidhash/www
RUN rm -rf /out && corepack pnpm@11.1.3 --config.ignore-scripts=true --config.node-linker=isolated --filter @voidhash/backend-app deploy --prod --legacy /out
RUN node scripts/check-selfhost-runtime-boundary.mjs /out
RUN node scripts/check-node-runtime-boundary.mjs /out
RUN rm -rf /www && corepack pnpm@11.1.3 --config.ignore-scripts=true --config.node-linker=hoisted --config.allow-unused-patches=true --filter @voidhash/www deploy --prod --legacy /www
RUN node scripts/check-selfhost-runtime-boundary.mjs /www
RUN node scripts/check-node-runtime-boundary.mjs /www

FROM node:24-bookworm-slim AS runtime

Expand Down
18 changes: 18 additions & 0 deletions apps/backend/infrastructure/DeploymentConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as Config from "effect/Config";

const optionalDomain = (name: string): Config.Config<string | undefined> =>
Config.string(name).pipe(
Config.map((value) => value.trim() || undefined),
Config.withDefault(undefined),
);

/** Custom hostname attached to the Community backend Worker for live deployments. */
export const CommunityBackendDomain = optionalDomain("VOIDHASH_BACKEND_DOMAIN");

/** Custom hostname attached to the Community web Worker for live deployments. */
export const CommunityWwwDomain = optionalDomain("VOIDHASH_WWW_DOMAIN");

/** Whether live Community Workers remain available on their `workers.dev` URLs. */
export const CommunityWorkersDevEnabled = Config.boolean("VOIDHASH_WORKERS_DEV_ENABLED").pipe(
Config.withDefault(true),
);
Loading
Loading