Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
58eec1c
deps: add platform-specific dependencies for OS service management
krishna-santosh Jun 25, 2026
8366b60
refactor(grpc): add shutdown future parameter for graceful drain
krishna-santosh Jun 25, 2026
12cc20c
refactor: extract server bootstrap into reusable server module
krishna-santosh Jun 25, 2026
6c79f6b
feat: add cross-platform OS service management
krishna-santosh Jun 25, 2026
9888fbe
refactor(paths): support platform-conventional split layout with sing…
krishna-santosh Jun 30, 2026
eda22ef
feat(service): pin OS service homes to system-owned directories
krishna-santosh Jun 30, 2026
aaa373d
refactor(mcp): rewrite stdio transport as thin HTTP proxy
krishna-santosh Jun 30, 2026
ffd3e2f
docs: document split layout, MCP stdio proxy, and OS service management
krishna-santosh Jun 30, 2026
bb52640
chore(deps): update dependencies
krishna-santosh Jul 1, 2026
7dc2c53
refactor(mcp): replace Content with ContentBlock and clean up imports…
krishna-santosh Jul 1, 2026
46e6bf5
refactor(crypto): update AES-GCM nonce and key handling for improved …
krishna-santosh Jul 1, 2026
4c452fb
refactor(paths): extract system_home(), split layout resolution into …
krishna-santosh Jul 1, 2026
217ff3d
fix(service/windows): harden install/uninstall lifecycle and use shar…
krishna-santosh Jul 1, 2026
28a146a
refactor(service/linux): use shared paths::system_home() instead of l…
krishna-santosh Jul 1, 2026
e4a82f9
refactor(service/macos): use shared paths::system_home() instead of l…
krishna-santosh Jul 1, 2026
c42891d
fix(main): tolerate PermissionDenied when loading .env from system home
krishna-santosh Jul 1, 2026
7a12b03
docs(AGENTS.md): clarify single-layout precedence and elevation requi…
krishna-santosh Jul 1, 2026
ff13003
docs(CLAUDE.md): clarify single-layout precedence and elevation requi…
krishna-santosh Jul 1, 2026
bd9c4bb
docs(README): explain that CLI follows the service's data directory
krishna-santosh Jul 1, 2026
a447f13
chore(deps): add futures-util and infer; promote tokio-stream to main…
krishna-santosh Jul 2, 2026
bb2adcb
feat(upload): add magic bytes content sniffing for MIME validation
krishna-santosh Jul 2, 2026
ebe21a3
feat(storage): add multipart streaming upload to StorageProvider
krishna-santosh Jul 2, 2026
53fa44e
feat(repo): add set_thumbnail_meta, unique-violation guard, and last_…
krishna-santosh Jul 2, 2026
6f6e777
feat(upload): rewrite file service for constant-memory streaming
krishna-santosh Jul 2, 2026
31a92ea
feat(upload): add signed URL PUT endpoint with configurable expiry
krishna-santosh Jul 2, 2026
adceb99
feat(mcp): improve create_upload_url with content validation
krishna-santosh Jul 2, 2026
92a6fee
perf(auth): debounce session/token last-sSeen timestamp updates
krishna-santosh Jul 2, 2026
9463bdf
perf(search): make indexer purely event-driven, remove polling
krishna-santosh Jul 2, 2026
77591e0
feat(dashboard): add ETag and cache-control headers for embedded assets
krishna-santosh Jul 2, 2026
1773b57
fix(server): pre-bind TCP listeners before declaring readiness
krishna-santosh Jul 2, 2026
72770bd
fix(windows): improve service lifecycle, error reporting, and ACL har…
krishna-santosh Jul 2, 2026
f1034af
fix(macos): poll for process exit on service stop
krishna-santosh Jul 2, 2026
949658e
perf(repo): wrap collection migration in transaction; batch file refs
krishna-santosh Jul 2, 2026
95fd2b0
perf(backup): offload CPU-bound compression and encryption to blockin…
krishna-santosh Jul 2, 2026
c280122
docs: update agent instructions for streaming uploads and search
krishna-santosh Jul 2, 2026
f40e42e
test: add integration tests for streaming uploads, sniffing, and sign…
krishna-santosh Jul 2, 2026
59b68e0
refactor: simplify sudo user validation logic in resolve_run_user fun…
krishna-santosh Jul 3, 2026
044c237
docs: clarify VCMS_HOME resolution order in README
krishna-santosh Jul 3, 2026
db11d7d
docs(cli): clarify Windows service --user flag behavior
krishna-santosh Jul 3, 2026
3c06420
fix(config): validate upload_token_expiry_secs is positive and use co…
krishna-santosh Jul 3, 2026
4e14df8
fix(mysql): pin session timezone to UTC via after_connect
krishna-santosh Jul 3, 2026
d7ee538
test(dashboard): add unit tests for cache-control headers and ETag 304
krishna-santosh Jul 3, 2026
ea6c342
fix(server): await both servers on graceful shutdown and handle admin…
krishna-santosh Jul 3, 2026
3138ca7
fix(search): add retry with exponential backoff to indexer drain
krishna-santosh Jul 3, 2026
4dcc597
refactor(service): gate service module to windows only
krishna-santosh Jul 9, 2026
44237a8
refactor(cli): restrict service subcommand to windows host
krishna-santosh Jul 9, 2026
7c79a03
refactor(main): gate service dispatch arm to windows
krishna-santosh Jul 9, 2026
b0850ed
refactor(service): trim service module to windows SCM host
krishna-santosh Jul 9, 2026
5e23730
refactor(service): remove linux and macos service submodules
krishna-santosh Jul 9, 2026
e2c7e70
build(deps): drop unused libc dependency
krishna-santosh Jul 9, 2026
22ea38a
docs: update references to removed vcms service command
krishna-santosh Jul 9, 2026
e6da136
refactor(paths): remove auto-detection of system service home directory
krishna-santosh Jul 14, 2026
ee7bc3b
feat(health): add liveness and readiness probe endpoints
krishna-santosh Jul 14, 2026
0de9135
feat(service): add cross-platform service status and doctor diagnostics
krishna-santosh Jul 14, 2026
9dca557
feat(packaging): add native package definitions for Linux, macOS, and…
krishna-santosh Jul 14, 2026
1e2a89b
feat(xtask): add build and package orchestration crate
krishna-santosh Jul 14, 2026
14ab2d4
ci: add packaging checks and restructure release workflow
krishna-santosh Jul 14, 2026
113a91a
docs: update product branding and documentation
krishna-santosh Jul 14, 2026
e3735be
feat(paths): auto-detect system service home without requiring VCMS_HOME
krishna-santosh Jul 15, 2026
3c45f8f
refactor(service): rewrite nested if-let chains as standard if blocks…
krishna-santosh Jul 15, 2026
4ee870a
build(xtask): skip staging README.txt on Windows portable builds
krishna-santosh Jul 15, 2026
9154ed3
build(xtask): simplify version fallback and uppercase Windows product…
krishna-santosh Jul 15, 2026
62cf39d
ci: pass explicit version to dry-run package commands
krishna-santosh Jul 15, 2026
cf35574
ci: scope write permissions to the publish job only
krishna-santosh Jul 15, 2026
7838623
docs: document auto-detection of system service home directory
krishna-santosh Jul 15, 2026
04e367e
packaging: move config.toml from /etc/vcms to the data directory
krishna-santosh Jul 15, 2026
2ec7902
fix: update documentation link rendering in DashboardHeader and NavUs…
krishna-santosh Jul 15, 2026
c3c02dd
rm dependabot.yml
krishna-santosh Jul 15, 2026
b009241
fix(packaging): harden Debian postinst with symlink checks, atomic co…
krishna-santosh Jul 15, 2026
f386b51
chore(deps): update crossbeam-epoch to version 0.9.20
krishna-santosh Jul 15, 2026
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
36 changes: 0 additions & 36 deletions .github/dependabot.yml

This file was deleted.

27 changes: 26 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
filters: |
rust:
- 'apps/backend/**'
- 'xtask/**'
- 'libs/proto/**'
- 'Cargo.toml'
- 'Cargo.lock'
Expand Down Expand Up @@ -276,6 +277,30 @@ jobs:
- name: Build
run: bunx nx run dashboard:build

packaging:
name: Packaging checks
needs: changes
if: needs.changes.outputs.rust == 'true' || needs.changes.outputs.other == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: dtolnay/rust-toolchain@67ef31d5b988238dd797d409d6f9574278e20537 # master
with:
toolchain: "1.94"
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: ". -> target"
shared-key: linux-packaging
cache-on-failure: true
- name: xtask tests
run: cargo test -p xtask --locked
- name: package dry-run
run: |
cargo run -p xtask --locked -- package --kind all --version 0.0.0-ci --dry-run --skip-build --target-os linux --arch amd64
cargo run -p xtask --locked -- package --kind host --version 0.0.0-ci --dry-run --skip-build --target-os macos --arch arm64
cargo run -p xtask --locked -- package --kind host --version 0.0.0-ci --dry-run --skip-build --target-os windows --arch amd64

web:
name: Web (landing + docs)
needs: changes
Expand Down Expand Up @@ -307,7 +332,7 @@ jobs:
# "skipped required check stays pending forever" trap of naive path filtering.
ci-status:
name: CI status
needs: [changes, lint, security, test, test-os, frontend, web]
needs: [changes, lint, security, test, test-os, frontend, packaging, web]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
Expand Down
65 changes: 42 additions & 23 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ on:
tags:
- "v*"

permissions:
contents: write

jobs:
# Build the static dashboard once. Every build job consumes this same dist.
dashboard:
Expand Down Expand Up @@ -45,7 +42,7 @@ jobs:
if-no-files-found: error

build:
name: Build (${{ matrix.os }}-${{ matrix.arch }})
name: Build/package (${{ matrix.os }}-${{ matrix.arch }})
needs: dashboard
runs-on: ${{ matrix.runner }}
strategy:
Expand All @@ -55,33 +52,21 @@ jobs:
- os: linux
arch: amd64
runner: ubuntu-latest
bin: vcms
asset: vcms-${{ github.ref_name }}-linux-amd64
- os: linux
arch: arm64
runner: ubuntu-24.04-arm
bin: vcms
asset: vcms-${{ github.ref_name }}-linux-arm64
- os: macos
arch: amd64
runner: macos-26-intel
bin: vcms
asset: vcms-${{ github.ref_name }}-macos-amd64
- os: macos
arch: arm64
runner: macos-26
bin: vcms
asset: vcms-${{ github.ref_name }}-macos-arm64
- os: windows
arch: amd64
runner: windows-latest
bin: vcms.exe
asset: vcms-${{ github.ref_name }}-windows-amd64.exe
- os: windows
arch: arm64
runner: windows-11-arm
bin: vcms.exe
asset: vcms-${{ github.ref_name }}-windows-arm64.exe
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand All @@ -107,20 +92,54 @@ jobs:
workspaces: ". -> target"
key: release-${{ matrix.os }}-${{ matrix.arch }}

- name: Install Linux packaging tools
if: matrix.os == 'linux'
run: sudo apt-get update && sudo apt-get install -y rpm

- name: Install WiX
if: matrix.os == 'windows'
shell: pwsh
run: |
dotnet tool install --global wix --version 7.*
wix eula accept wix7
wix extension add --global WixToolset.UI.wixext/7.0.0

# Build cargo directly (dist is already present from the artifact); keep the
# default `embed-dashboard` feature so rust-embed bakes in apps/dashboard/dist.
- name: Build release binary
working-directory: apps/backend
run: cargo build --release --locked

- name: Stage artifact
shell: bash
run: |
mkdir -p dist
cp "target/release/${{ matrix.bin }}" "dist/${{ matrix.asset }}"
- name: Package artifacts
run: cargo run -p xtask -- package --kind host --skip-build --target-os ${{ matrix.os }} --arch ${{ matrix.arch }}

- name: Attach to release
- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0 # v7.0.1
with:
name: release-${{ matrix.os }}-${{ matrix.arch }}
path: dist/packages/*
if-no-files-found: error

publish:
name: Attest and publish release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: release-*
path: dist/packages
merge-multiple: true
- name: Attest build provenance
uses: actions/attest-build-provenance@v3
with:
subject-path: dist/packages/*
- name: Attach all artifacts after matrix passes
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
files: dist/${{ matrix.asset }}
files: dist/packages/*
generate_release_notes: true
100 changes: 77 additions & 23 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# AI Agent Instructions for CMS (Rust + React)

## Product Identity

- Human-facing product and service display name: **Velopulent CMS**.
- Canonical documentation: <https://cms.velopulent.com/docs>.
- Keep the executable, package name, native service identifier, environment prefix, and internal identifiers as `vcms` / `VCMS_*` for backward compatibility. Do not rename persisted paths or service keys when updating branding.

## Architecture

- **Backend**: Rust + Axum HTTP server, `SQLx` + (SQLite | PostgreSQL | MySQL), `rust-embed` (static assets)
- **Frontend**: React in `apps/dashboard/` with Tanstack Router, Tanstack Query, shadcn/ui
- **gRPC**: Separate server on port 50051 (compiled from `libs/proto/*.proto` via `tonic-build`)
- **Build**: Nx orchestrates `dashboard:build` → `backend:build`. `build.rs` compiles proto files only.
- **Runtime**: Single binary serves REST API (`/api/*`), gRPC, GraphQL (`/api/graphql`), MCP (Streamable HTTP at `/mcp`), and static SPA fallback
- **Runtime**: Single binary serves REST API (`/api/*`), gRPC, GraphQL (`/api/graphql`), MCP (Streamable HTTP at `/mcp`), health probes (`/health/live`, `/health/ready`), and static SPA fallback

## Key Directories

Expand All @@ -25,6 +31,8 @@
- `libs/proto/` - Protocol Buffer definitions (`cms.proto`)
- `apps/dashboard/` - React frontend app
- `apps/web/` - Landing Page and Documentation (NextJS + Fumadocs)
- `packaging/` - Native Linux, macOS, Windows, Debian, RPM, and Arch definitions and lifecycle scripts
- `xtask/` - Typed release/package orchestration; platform builders live in separate modules

## Developer Commands

Expand Down Expand Up @@ -75,16 +83,35 @@ vcms admin reset-password --email U --password P
vcms backup create [--scope instance|site] [--site ID] [--out FILE] [--no-files] [--encrypt]
vcms backup list # list recorded backups
vcms restore --file PATH [--scope instance|site] [--site ID] [--import-as-new] --yes
vcms mcp stdio # thin HTTP proxy to a running server's /mcp (for MCP clients)
vcms service status # normalized native-service status and manager details
vcms doctor # validate config, storage, database, bind ports, and service identity
```

`backup`/`restore` run offline (no HTTP server) against the configured database —
the disaster-recovery path when the instance won't boot. `restore` is destructive
and requires `--yes`.

`mcp stdio` opens no database, secrets, or search index of its own: it forwards
JSON-RPC between stdin/stdout and the server's `/mcp` Streamable-HTTP endpoint,
reading only `VCMS_MCP_TOKEN` (bearer) and `VCMS_MCP_URL` (default
`http://127.0.0.1:3000`). So it works even when the data is owned by the OS-service
account. The installed service pins `VCMS_HOME` to a system dir so the daemon stores
everything under one owned root.

Global flags (highest precedence): `--config <PATH>`, `--bind <ADDR>`, `--database-url <URL>`, `--log-level <LEVEL>`.

The server auto-migrates the database on every startup; there is no separate migrate command.

## Packaging and Releases

- Keep native package definitions and service files in `packaging/`; do not embed them in Rust source or workflow YAML.
- `xtask` orchestrates deterministic staging and packaging. Keep `xtask/src/main.rs` limited to CLI parsing and dispatch, with shared and platform-specific implementation in modules.
- Keep the release workflow thin: build the dashboard, run native build/package jobs, assemble artifacts, attest, and publish.
- Ordinary CI validates packaging templates, `xtask` tests, and deterministic dry-runs. It must not install or mutate host services.
- Release artifacts include portable archives, Debian, RPM, MSI, and macOS PKG packages. Arch is maintained as a package recipe consuming published Linux archives; render it with `xtask arch-render`, not as a fake release archive.
- The stable native service identifier is `vcms`; its human-facing display name is **Velopulent CMS**. Fresh Linux and macOS package installs register/enable the service but do not auto-start it; the Windows MSI installs the service for automatic startup and starts it immediately.

## Configuration

Non-secret settings live in a TOML config file; secrets stay in the environment (or `.env`).
Expand All @@ -93,29 +120,53 @@ Layers merge with precedence: **CLI flag > env var > config file > built-in defa
Config file search order (first existing wins; missing is fine):
1. `--config` flag / `VCMS_CONFIG` env
2. `./vcms.toml` (current dir)
3. `~/.vcms/config.toml` (CMS home; `$VCMS_HOME/config.toml` if set) — where `vcms config init` writes
3. the platform config dir (`config.toml`; `$VCMS_HOME/config.toml` in single-dir mode) — where `vcms config init` writes
4. `/etc/vcms/config.toml`

## Data directory (CMS home)
## Data directory

Resolution lives in `apps/backend/src/paths.rs` and has two layouts:

All runtime files live under one home directory: `$VCMS_HOME` if set, else `~/.vcms`
(same layout on Windows, macOS, Linux via the `directories` crate). `vcms serve`
creates it on first run. Resolution lives in `apps/backend/src/paths.rs`.
**Split (default, interactive installs)** — files land in the platform-conventional
per-type directories via the `directories` crate (`ProjectDirs`):

| File(s) | Dir | Linux | macOS | Windows |
|---------|-----|-------|-------|---------|
| `config.toml`, `secrets.toml`, `.env` | config | `~/.config/vcms` | `~/Library/Application Support/vcms` | `%APPDATA%\vcms\config` |
| `vcms.db`, `storage/`, `backups/` | data | `~/.local/share/vcms` | `~/Library/Application Support/vcms` | `%APPDATA%\vcms\data` |
| `search/` (derived, rebuildable) | cache | `~/.cache/vcms` | `~/Library/Caches/vcms` | `%LOCALAPPDATA%\vcms\cache` |
| `logs/` | state | `~/.local/state/vcms` | `~/Library/Application Support/vcms` | `%LOCALAPPDATA%\vcms\data` |

(`logs/` uses the state dir where the platform has one — Linux — else the local data dir.)

**Single** — everything nests under one root. Chosen (in precedence order) when:
1. **`$VCMS_HOME` is set** — forces the root explicitly.
2. **the system service home dir exists** — Linux `/var/lib/vcms`, macOS
`/Library/Application Support/vcms`, Windows `C:\ProgramData\vcms`. The
platform installer creates it (and leaves it behind on uninstall), so a plain
`vcms serve`/`admin`/`backup` **follows the service's data instead of forking to a
per-user split store**. This path is defined once in `paths::system_home()` and
imported by the Windows SCM host.
3. **a legacy `~/.vcms` exists** — an existing install keeps working untouched.

Otherwise (dev/eval boxes with no service) files use the platform split dirs.

```text
~/.vcms/
config.toml # non-secret config (vcms config init target)
secrets.toml # auto-generated HMAC_SECRET + backup key (0600 on unix)
vcms.db # default SQLite database (+ -wal / -shm)
logs/ # rolling logs when [log] output = "file"
storage/ # default filesystem storage for uploads
$VCMS_HOME/ # or system home, or ~/.vcms (legacy)
config.toml secrets.toml .env
vcms.db (+ -wal / -shm) logs/ storage/ backups/ search/
```

Secrets: on first `serve`/`admin`, a random `HMAC_SECRET` is generated
and persisted to `secrets.toml` (`apps/backend/src/secrets.rs`), then loaded by
every process — including `vcms mcp stdio`, which is launched from an arbitrary cwd
and so cannot rely on a cwd `.env`. Env vars still override the file. `mcp stdio`
is read-only: it never creates the home dir, database, or secrets file.
When the active home is the system service home (owned by SYSTEM/root), the
data-touching commands (`serve`/`admin`/`backup`/`restore`) require elevation — a
non-elevated invocation **fails fast** with an "Administrator/root" hint (in
`paths::ensure`'s preflight) rather than silently forking to a second store.

Secrets: on first `serve`/`admin`, a random `HMAC_SECRET` is generated and persisted to
`secrets.toml` (`apps/backend/src/secrets.rs`), then loaded by the server processes.
`vcms mcp stdio` does **not** load secrets, the database, or any data-dir file: it is a
thin HTTP proxy to the running server's `/mcp` (see CLI), forwarding `VCMS_MCP_TOKEN` as
the bearer; the server owns all disk I/O.

Env-only secrets (never read from `config.toml` by convention, omitted from
`config init`): `DATABASE_URL`, `HMAC_SECRET`, `S3_ACCESS_KEY_ID`,
Expand Down Expand Up @@ -153,8 +204,10 @@ Logging keys map to the `[log]` table: `RUST_LOG`→`log.level`, `LOG_OUTPUT`→
| Variable | Default | Description |
|----------|---------|-------------|
| `VCMS_CONFIG` | - | Explicit config file path (same as `--config`) |
| `VCMS_HOME` | `~/.vcms` | CMS home directory (db, config, secrets, logs, storage) |
| `DATABASE_URL` | `sqlite://~/.vcms/vcms.db` | Database URL: `sqlite:path`, `postgres://...`, `mysql://...` |
| `VCMS_HOME` | - | If set, forces single-dir mode: db/config/secrets/logs/storage all nest under this root (else files use the platform split dirs) |
| `VCMS_MCP_TOKEN` | - | `vcms_site_*` access token forwarded by `vcms mcp stdio` as the bearer credential (required for stdio) |
| `VCMS_MCP_URL` | `http://127.0.0.1:3000` | Running server's base URL that `vcms mcp stdio` proxies to (`{url}/mcp`) |
| `DATABASE_URL` | `sqlite://<data dir>/vcms.db` | Database URL: `sqlite:path`, `postgres://...`, `mysql://...` |
| `HMAC_SECRET` | auto | HMAC key for token lookup (required; auto-generated to `secrets.toml`, env overrides) |
| `BIND_ADDRESS` | `0.0.0.0:3000` | REST API listen address |
| `GRPC_BIND_ADDRESS` | `0.0.0.0:50051` | gRPC server listen address |
Expand All @@ -167,14 +220,15 @@ Logging keys map to the `[log]` table: `RUST_LOG`→`log.level`, `LOG_OUTPUT`→
| `S3_PUBLIC_URL` | - | Public URL for S3 assets |
| `BACKUP_ENABLED` | `true` | Run the scheduled-backup poller / allow backups |
| `BACKUP_DESTINATION` | `filesystem` | Backup destination: `filesystem` or `s3` |
| `BACKUP_LOCAL_PATH` | `~/.vcms/backups` | Local backup dir (when destination is filesystem) |
| `BACKUP_LOCAL_PATH` | `<data dir>/backups` | Local backup dir (when destination is filesystem) |
| `BACKUP_ZSTD_LEVEL` | `12` | zstd compression level for backups |
| `BACKUP_DEFAULT_RETENTION` | `7` | Default "keep last N" for new schedules |
| `BACKUP_S3_BUCKET` / `_REGION` / `_ENDPOINT` / `_PUBLIC_URL` | - | S3 backup destination (non-secret parts) |
| `BACKUP_S3_ACCESS_KEY_ID` | - | S3 backup access key (secret, env-only) |
| `BACKUP_S3_SECRET_ACCESS_KEY` | - | S3 backup secret key (secret, env-only) |
| `BACKUP_ENCRYPTION_KEY` | auto | AES-256 backup key (hex); auto-generated to `secrets.toml` |
| `MAX_UPLOAD_SIZE_MB` | `50` | Max upload size in MB |
| `UPLOAD_TOKEN_EXPIRY_SECS` | `900` | Signed upload URL lifetime (seconds) |
| `COOKIE_SECURE` | `false` | Require HTTPS cookies |
| `DB_MAX_CONNECTIONS` | `10` | Max DB connections |
| `DB_MIN_CONNECTIONS` | `2` | Min DB connections |
Expand All @@ -185,10 +239,10 @@ Logging keys map to the `[log]` table: `RUST_LOG`→`log.level`, `LOG_OUTPUT`→
| `LOG_OUTPUT` | `stdout` | `stdout` or `file` (`[log] output`) |
| `LOG_FORMAT` | `pretty` | `pretty` or `json` (`[log] format`) |
| `LOG_ANNOTATIONS` | `false` | Include file + line numbers (`[log] annotations`) |
| `LOG_DIR` | `~/.vcms/logs` | Log directory when `output = file` (`[log] dir`) |
| `LOG_DIR` | `<state dir>/logs` | Log directory when `output = file` (`[log] dir`) |

**Note**: `HMAC_SECRET` is auto-generated and persisted to
`~/.vcms/secrets.toml` on first run. Set it explicitly via env to override.
**Note**: `HMAC_SECRET` is auto-generated and persisted to the config dir's
`secrets.toml` on first run. Set it explicitly via env to override.

## Proto Compilation

Expand Down
Loading
Loading