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
50 changes: 50 additions & 0 deletions .env.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Keyforge local environment. Run `vp run secrets:init --yes` before `vp run env:check`.
# @defaultRequired=false

# @sensitive @required
LICENSE_SIGNING_KEY=

# @sensitive
LICENSE_DATA_KEYRING_FILE=

# @sensitive
LICENSE_BACKUP_KEYRING_FILE=

# @sensitive
LICENSE_ADMIN_TOKEN=

# @sensitive
LICENSE_AUTH_SECRET=

# @sensitive
CRON_SECRET=

# @sensitive
DATABASE_URL=

# Docker-only URL. Use postgres:5432, not the host-facing port.
# @sensitive
COMPOSE_DATABASE_URL=

LICENSE_DB_PATH=

# @type=number
LICENSE_PORT=4100

# @type=number
POSTGRES_PORT=55433

# Docker Compose defaults to keyforge when unset.
POSTGRES_DB=keyforge

# Docker Compose defaults to keyforge when unset; set a unique value for deployments.
# @sensitive
POSTGRES_PASSWORD=

LICENSE_SERVER_URL=http://localhost:4100/

# Optional boot-time administrator.
LICENSE_SEED_ADMIN_EMAIL=

# @sensitive
LICENSE_SEED_ADMIN_PASSWORD=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ license-signing.key
.env
.env.*
!.env.example
!.env.schema
secrets/
backups/
*.keyring.json
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ documented recipes are preferred over new required machinery.

```bash
vp install
cp .env.example .env.local # fill in secrets first
cp .env.example .env # fill in secrets first
vp run build # CLI imports compiled server modules
vp run license keygen # local signing key
vp run secrets:init
Expand Down
22 changes: 14 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ services:
- "4100:4100"
volumes:
- keyforge-data:/data
- ./secrets:/app/secrets:ro
# Uncomment to supply a deployment config:
# - ./keyforge.config.json:/app/keyforge.config.json:ro
environment:
# Unset = SQLite in the volume. Point at the postgres service below
# (docker compose --profile postgres up) for a durable ledger.
DATABASE_URL: ${DATABASE_URL:-}
# Set COMPOSE_DATABASE_URL for container-to-container Postgres access.
# DATABASE_URL can keep its host-facing localhost URL for local CLI commands.
DATABASE_URL: ${COMPOSE_DATABASE_URL:-${DATABASE_URL:-}}
# Required: PEM private key (or mount a file and use LICENSE_SIGNING_KEY_FILE).
LICENSE_SIGNING_KEY: ${LICENSE_SIGNING_KEY:?set in .env}
LICENSE_DATA_KEYRING_FILE: ${LICENSE_DATA_KEYRING_FILE:-./secrets/data.keyring.json}
LICENSE_BACKUP_KEYRING_FILE: ${LICENSE_BACKUP_KEYRING_FILE:-./secrets/backup.keyring.json}
# Optional surfaces — each is disabled when unset.
LICENSE_ADMIN_TOKEN: ${LICENSE_ADMIN_TOKEN:-}
LICENSE_AUTH_SECRET: ${LICENSE_AUTH_SECRET:-}
Expand All @@ -22,23 +27,24 @@ services:

# Optional durable ledger:
# docker compose --profile postgres up
# DATABASE_URL=postgres://keyforge:keyforge@postgres:5432/keyforge
# COMPOSE_DATABASE_URL=postgres://keyforge:keyforge@postgres:5432/keyforge?sslmode=disable
postgres:
# Keep the existing data-directory major; upgrading to 18 requires pg_upgrade.
image: ${POSTGRES_IMAGE:-postgres:16.15}
image: ${POSTGRES_IMAGE:-postgres:18.6-bookworm}
profiles: ["postgres"]
environment:
POSTGRES_USER: keyforge
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-keyforge}
POSTGRES_DB: keyforge
POSTGRES_DB: ${POSTGRES_DB:-keyforge}
ports:
- ${POSTGRES_PORT:-55433}:5432
volumes:
- keyforge-pgdata:/var/lib/postgresql/data
- keyforge-pgdata-v18:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keyforge"]
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5

volumes:
keyforge-data:
keyforge-pgdata:
keyforge-pgdata-v18:
8 changes: 4 additions & 4 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Set `LICENSE_SERVER_URL` and `LICENSE_ADMIN_TOKEN` for API commands. Use `--prod

| Command | Purpose |
| --- | --- |
| `vp run license keygen` | Generate startup Ed25519 keys under secrets/ (or LICENSE_SIGNING_KEY_FILE) and save the path to the selected environment file, .env.local by default. Existing setup asks [Y/n]; --yes confirms automation. --force permits explicit key replacement and can invalidate client trust. |
| `vp run license keygen` | Generate startup Ed25519 keys under secrets/ (or LICENSE_SIGNING_KEY_FILE) and save the path to the selected environment file, .env by default. Existing setup asks [Y/n]; --yes confirms automation. --force permits explicit key replacement and can invalidate client trust. |
| `vp run license keycheck` | Check configured private key, public fingerprint; --expect PEM/file, --token token, --server compare deployment. |
| `vp run license issue` | Issue from --plan ID with --to name, optional --installation ID or --subject installation/service, and a stable --request-id UUID. Add --paid --method METHOD --reference ID to record full payment. Terms come from the saved plan. |
| `vp run license list` | First page of selected product licenses. |
Expand All @@ -20,7 +20,7 @@ Set `LICENSE_SERVER_URL` and `LICENSE_ADMIN_TOKEN` for API commands. Use `--prod
| `vp run db mirror` | Copy the configured active ledger to --to file/URL. |
| `vp run db verify` | Compare --from and --to engine-neutral row contents. |
| `vp run admin:create` | Create an operator with --email and --password; --name and --role admin/support are optional. |
| `vp run secrets:init` | Initialize missing signing, data/backup encryption, admin, auth and cron secrets; save references/values to .env.local. Use --env-file PATH for an isolated named environment and --secrets-dir PATH for its key files. Existing setup asks [Y/n]; --yes confirms automation. Existing secrets are retained. |
| `vp run secrets:init` | Initialize missing signing, data/backup encryption, admin, auth and cron secrets; save values and keyring references to .env. Use --env-file PATH for an isolated named environment and --secrets-dir PATH for its key files. Existing setup asks [Y/n]; --yes confirms automation. Existing secrets are retained. |
| `vp run secrets:export` | With --env-file PATH and --output PATH, write a private provider-neutral environment file embedding existing key files. No key rotation; provider identity variables are omitted. Existing output requires confirmation. |
| `vp run backup:restore` | Explicit maintenance-only restore of an encrypted archive; see backups guide. |

Expand Down Expand Up @@ -56,15 +56,15 @@ vp run storage:init

The default app reset drops and recreates Keyforge ledger and operator tables, then recreates the configured bootstrap admin. SQLite database mode removes and recreates the single selected unified database file. SQLite resets refuse unrelated tables and paths outside the project. data:clean removes obsolete Keyforge SQLite files under db/database and UUID .kfbackup files in the configured backup directory; it retains active databases, unrelated databases and key files. It leaves remote uploads and backup history intact, so standalone cleanup can remove local-only download copies. demo:reset combines reset, local cleanup and seed. These offline commands differ from the account-preserving **Settings → System** reset; see [database maintenance](database.md).

Demo seed creates fictional products, active/expired/revoked licenses, registered and trial machines, support conversations, metered usage and admin/support operators. Random demo login credentials and client fixture IDs are written to the private, ignored .artifacts/demo/manifest.json file. The stored seed manifest is encrypted using the data ring. Completed seeds are idempotent; interrupted seeds require a reset before retrying. Existing configured bootstrap credentials are retained separately in .env.local. Demo sign-up is enabled only inside the unserved seeding process; the running app still disables public sign-up.
Demo seed creates fictional products, active/expired/revoked licenses, registered and trial machines, support conversations, metered usage and admin/support operators. Random demo login credentials and client fixture IDs are written to the private, ignored .artifacts/demo/manifest.json file. The stored seed manifest is encrypted using the data ring. Completed seeds are idempotent; interrupted seeds require a reset before retrying. Existing configured bootstrap credentials are retained separately in .env. Demo sign-up is enabled only inside the unserved seeding process; the running app still disables public sign-up.

Postgres app mode preserves unrelated tables and fails if external dependencies prevent dropping app tables. For a dedicated app schema, use `vp run db:reset --scope schema --confirm SCHEMA_NAME --apply --maintenance`; unrelated objects or cross-schema foreign keys/views prevent a schema reset. Schema mode recreates the owner but not custom grants, which must be reapplied.

For a whole Postgres database, set LICENSE_DATABASE_ADMIN_URL to a privileged connection to a different database on the same server, then run `vp run db:reset --scope database --confirm DATABASE_NAME --apply --maintenance`. This mode terminates connections and deletes everything in that explicitly named database. System databases are refused. Ownership, encoding and locale are retained; database grants/extensions/settings must be reapplied. Use the admin connection directly rather than a transaction pooler when the host requires it.

Reset removes online licensing records, operators, sessions and trial history. It does not rotate signing keys or invalidate already-issued offline tokens cryptographically; client expiry and online refresh still determine their remaining validity.

storage:init creates a uniquely named private AWS S3 and/or R2 bucket when a configured provider lacks a bucket, then writes its canonical setting to .env.local. Existing configured buckets are verified instead of recreated. Use --provider s3/r2/all, --name keyforge-storage-YOUR_UNIQUE_SUFFIX or --env-file PATH as needed. Setup needs bucket-creation permission in addition to normal backup permissions. S3 Block Public Access stays enabled. Provider request/storage charges apply to real uploads; configure independent schedules in Backups.
storage:init creates a uniquely named private AWS S3 and/or R2 bucket when a configured provider lacks a bucket, then writes its canonical setting to .env. Existing configured buckets are verified instead of recreated. Use --provider s3/r2/all, --name keyforge-storage-YOUR_UNIQUE_SUFFIX or --env-file PATH as needed. Setup needs bucket-creation permission in addition to normal backup permissions. S3 Block Public Access stays enabled. Provider request/storage charges apply to real uploads; configure independent schedules in Backups.

## Database setup and demo reset

Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Configuration and environment

For a complete first setup, follow the [beginner guide](getting-started.md). Copy `.env.example` to `.env.local` before provisioning secrets, replace its placeholders, and never commit the resulting file. A placeholder that happens to pass length validation is not a secure secret.
For a complete first setup, follow the [beginner guide](getting-started.md). Copy `.env.example` to `.env` before provisioning secrets, replace its placeholders, and never commit the resulting file. A placeholder that happens to pass length validation is not a secure secret.

## Database selection

Expand Down Expand Up @@ -72,14 +72,14 @@ Use the reset, cleanup, demo seed and bucket setup commands in the [CLI guide](c

## Minimal environment and database-managed storage

Run `vp run secrets:init` to fill missing local secrets and file references in `.env.local`; existing setup requires confirmation. The default private key path is `secrets/license-signing.key`. Never configure both inline and file values for the same key.
Run `vp run secrets:init` to fill missing local secrets and keyring file references in `.env`; existing setup requires confirmation. The signing key defaults to an inline `LICENSE_SIGNING_KEY` value. Never configure both inline and file values for the same key.

## Portable environments and recovery files

Application credentials are independent of the hosting provider. Use one private environment directory for each deployment, and keep provider access tokens separate:

```text
.env.local # local development
.env # local development
secrets/license-signing.key # existing local signing key
secrets/data.keyring.json # local stored-data encryption
secrets/backup.keyring.json # local archive encryption
Expand Down
2 changes: 1 addition & 1 deletion docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ See [Vercel deployment](vercel.md) for a single project with `main` production a

The Dockerfile builds with Vite+, installs only production dependencies into the runtime, includes compatible Postgres dump/restore tools, and starts the compiled Node server. Keep secrets outside the image. Build via docker build -t keyforge . or use docker compose up --build. Ports, volumes and secret mounts are infrastructure commands, not package-manager commands.

The existing repository compose file keeps Postgres 16 on its latest supported patch to preserve existing major-version data compatibility. POSTGRES_IMAGE can override it only after a deliberate database upgrade. New scaffolds use a supported pinned major. Never switch a populated Postgres volume to another major image without pg_upgrade or dump/restore.
The repository compose file and new scaffolds use Postgres 18 with a volume mounted at `/var/lib/postgresql`. The repository uses a new `keyforge-pgdata-v18` volume, leaving any old Postgres 16 volume untouched. To move data from an older volume, use `pg_upgrade` or dump/restore; changing the image alone does not migrate it.

For SQLite, mount the ledger/auth directory, secrets and backup directory persistently. For Postgres, provide DATABASE_URL and a durable external database; local database paths are ignored. Mount key-ring files persistently in either case. Backup providers can run concurrently; configure S3, R2 and UploadThing credentials through server secrets, and custom upload URLs in the console. Keep LICENSE_BACKUP_DIR persistent (shared across replicas for custom-endpoint downloads). Each provider has an independent daily UTC schedule; see [backups](backups.md).

Expand Down
14 changes: 7 additions & 7 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ The snippets use a POSIX shell unless labelled PowerShell. `vp` and `node` comma
git clone https://github.com/zdevzone/keyforge.git
cd keyforge
vp install --frozen-lockfile
cp .env.example .env.local
cp .env.example .env
```

In PowerShell, replace the last command with:

```powershell
Copy-Item .env.example .env.local
Copy-Item .env.example .env
```

Do not overwrite an existing deployment's environment or keys. The [scaffolder](../packages/create-keyforge/README.md) generates independent deployment secrets with `vp create keyforge@latest`. The source-checkout procedure above also lets you run the server directly.
Expand Down Expand Up @@ -55,20 +55,20 @@ Run this once from the project root (a pre-existing environment file is optional
vp run secrets:init
```

This creates an Ed25519 private key at `secrets/license-signing.key`, independent data and backup keyrings, an administrative API token, a session secret and a cron secret. It appends the matching settings to the ignored `.env.local` file without printing secret values. The public verification key may be shared with dependent apps. Configure your deployment URL, database and administrator separately; provider credentials are entered in **Settings → Backups**.
This creates an Ed25519 private key as `LICENSE_SIGNING_KEY` in the ignored `.env` file, independent data and backup keyrings, an administrative API token, a session secret and a cron secret. It writes settings without printing secret values. The public verification key may be shared with dependent apps. Configure your deployment URL, database and administrator separately; provider credentials are entered in **Settings → Backups**.

For a separate deployment, use `vp run secrets:init --env-file secrets/environments/staging/server.env` instead of copying another environment's credentials. Export inline settings for any host with `vp run secrets:export --env-file secrets/environments/staging/server.env --output secrets/environments/staging/deploy.env`. See [portable environments](configuration.md#portable-environments-and-recovery-files). When moving an existing deployment, restore its original keys rather than generating new ones.

On an existing setup, confirm the **[Y/n]** prompt before the file is updated. Existing keys and configured secrets are retained. For reviewed, noninteractive automation, pass `--yes`; a pipe without that flag cannot silently change an existing environment file. Recognized example placeholders are replaced, while real configured values are retained.

Keep `vp run license keygen` for explicitly generating a signing key, or `vp run license keygen --force` for a deliberate replacement. It also saves `LICENSE_SIGNING_KEY_FILE` in `.env.local`, asks before updating an existing file, and refuses to replace an existing key without `--force`. An inline signing key must be removed explicitly before switching to a newly generated file. Rotation changes client trust; distribute public keys deliberately. Never rotate production keys just to complete setup.
Keep `vp run license keygen` for explicitly generating a signing key, or `vp run license keygen --force` for a deliberate replacement. It also saves `LICENSE_SIGNING_KEY_FILE` in `.env`, asks before updating an existing file, and refuses to replace an existing key without `--force`. An inline signing key must be removed explicitly before switching to a newly generated file. Rotation changes client trust; distribute public keys deliberately. Never rotate production keys just to complete setup.

## Configure a local server

Edit `.env.local`, retaining your actual generated secrets:
Edit `.env`, retaining your actual generated secrets:

```dotenv
LICENSE_SIGNING_KEY_FILE=./secrets/license-signing.key
LICENSE_SIGNING_KEY=<your-generated-private-key>
LICENSE_AUTH_SECRET=<your-generated-console-secret>
LICENSE_ADMIN_TOKEN=<your-separately-generated-optional-admin-token>
LICENSE_DATA_KEYRING_FILE=./secrets/data.keyring.json
Expand Down Expand Up @@ -150,7 +150,7 @@ Other languages can use REST directly. Safe offline support additionally require

## Deploy to production

Follow the [deployment guide](deploy.md) for your platform. Transfer secrets using its secret manager or durable mounts; never put `.env.local`, private keys, database files or encryption rings in Git or a container image.
Follow the [deployment guide](deploy.md) for your platform. Transfer secrets using its secret manager or durable mounts; never put `.env`, private keys, database files or encryption rings in Git or a container image.

On **Render**, use the repository's Blueprint or its documented build/start commands, rather than an old Yarn command. If `LICENSE_SERVER_URL` is unset, KeyForge uses Render's `RENDER_EXTERNAL_URL`. For a custom domain, set `LICENSE_SERVER_URL` to that HTTPS origin. Leave `LICENSE_PORT` unset to honor Render's assigned `PORT`. Remove the local HTTP origin and local port override when configuring production.

Expand Down
Loading
Loading