# ════════════════════════════════════════════════════════════════════════════ # QUICK START — only the four values in section 1 are required to boot. # # Everything else either has a working default or is collected on the SETUP # SCREEN the first time an admin signs in (knowledge-base repo, git token, # branch model, SSO) — where it can be tested against the real host before # it is saved. Anything set HERE always wins over the setup screen. # # Generate the two secrets with: # node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" # ════════════════════════════════════════════════════════════════════════════ # ── 1. Required ───────────────────────────────────────────────────────────── # The deployment owner. Three jobs rest on it: # - always treated as an admin, whatever the sign-in method (password OR # SSO), so accounts and roles are administrable before roles.yaml lists it; # - written as the initial Admin into the roles.yaml generated when an EMPTY # KB repo is seeded; # - the email half of the bootstrap password credential below. ADMIN_EMAIL= # Password half of the bootstrap credential — checked against the environment # at login time and never stored. Read once at startup, so changing or # unsetting it takes effect on the next restart, not at once; database accounts # carry on either way. Required only while password login is enabled: an # SSO-only deployment (LOGIN_PASSWORD=false) should not have to hold a shared # password the server is configured to reject. ADMIN_PASSWORD= # Signs login sessions + OAuth state. Any long random string. JWT_SECRET= # Core secrets encryption: 32-byte key (base64 or hex) encrypting the secrets # vault values + MCP OAuth client secrets/tokens. SECRETS_ENC_KEY= # Which release docker-compose runs — the image CI publishes per release # (ghcr.io/bevel-software/hexis). Unset = `latest`, the newest release: right # for a first install, then PIN IT so a later `docker compose pull` cannot # become an unplanned upgrade. Not used by the build-from-source variant # (deployment/docker-compose.build.yml) or `pnpm dev`. # HEXIS_VERSION=0.10.0 # ── 2. Database ───────────────────────────────────────────────────────────── # TWO WAYS, depending on where Postgres lives. # # Running the app yourself (`pnpm dev`), or pointing docker-compose at a # database you already have: set DATABASE_URL. It wins over the knobs below. # (The default here matches the bundled `docker compose up -d db` database.) DATABASE_URL=postgresql://bevel:bevel@localhost:5432/bevel # # Using the Postgres that docker-compose brings with it: leave DATABASE_URL # unset and set these instead — the app builds the URL from them, and compose # creates the database with the same values, so the two cannot disagree. (The # host is not among them under compose: the service is called `db` and that is # written into the file. Pointing somewhere else is what DATABASE_URL is for.) # Special characters are handled: the parts are URL-encoded, so a password # containing @ / or : is safe here. Postgres applies them only when it FIRST # initialises its volume; changing them later does not rename an existing user # or database. # POSTGRES_USER=bevel # POSTGRES_PASSWORD=bevel # POSTGRES_DB=bevel # ── 3. Knowledge base (or leave for the setup screen) ─────────────────────── # The knowledge base is a git repo YOU own, on ANY https git host (GitHub, # GitLab, Bitbucket, Azure DevOps, self-hosted). Change requests are stored in # this app's DB and merged with plain git — no provider PRs, no `/` # slug, no provider CLI. # # Leave any of these blank and an admin is asked for them on the setup screen # at first sign-in, where the credentials can be tested against the real host # before they are saved. # https clone/push URL of the knowledge-base repo. An EMPTY repo is fine: on # first load the app initialises it from the bundled template — base files # committed to every protected branch, plus a roles.yaml whose sole Admin is # ADMIN_EMAIL. KB_REPO_URL= # Git host credential. The token is sent as the HTTP Basic *password*; # GIT_USERNAME is the Basic username, which differs per host: # GitHub x-access-token (default) · GitLab oauth2 · Bitbucket x-token-auth · # Azure DevOps any non-empty value. # The token needs READ and WRITE (push) access to the repo. GIT_TOKEN is the # provider-neutral name; GITHUB_TOKEN / GH_TOKEN are still accepted. GIT_TOKEN= # GIT_USERNAME=x-access-token # Branch model. DEFAULT_BRANCH is where a user lands without picking a branch # and the default propose-into target; it must also appear in # PROTECTED_BRANCHES (comma/space-separated branch names the app refuses to # commit/push to directly — changes land via change-request approval), or the # protected-branch guards silently stop applying to the branch everyone lands # on. Also settable from the setup screen, where the repository's real # branches are offered as suggestions — the names must match exactly. # Runtime only: the frontend fetches these over /api/config when it boots, so # one build serves any deployment and renaming a branch needs no rebuild. # DEFAULT_BRANCH=main # PROTECTED_BRANCHES=main # Directory name of the KB clone inside each per-branch workspace. KB_DIR_NAME=knowledge-base # Optional. Lets the git host tell this deployment when the repository # changed, so a push, a merged pull request or a pipeline that writes skills # shows up in Hexis right away instead of at the next save. Point a webhook # (Azure DevOps service hook, GitHub/GitLab webhook), a GitHub Action or a # pipeline step at POST /api/sync/ with this value # as a bearer token — see docs/git-sync.md. Any long random string (16+ # characters). Also settable # from the setup screen. Without it, only an administrator's own session can # trigger a sync. # KB_SYNC_SECRET= # Overrides the KB seed template location (defaults to the kb-template/ # shipped inside @bevel-software/platform-core-backend). # KB_TEMPLATE_DIR= # ── 4. Sign-in options ────────────────────────────────────────────────────── # Generic OIDC single sign-on (any spec-compliant provider: Entra, Okta, # Auth0, Keycloak, Google, …). Enabled when all three are set. Register # /api/auth/oidc/callback as the redirect URI with the # provider. Also settable from the setup screen, which shows the redirect URI # to register. #OIDC_ISSUER_URL= #OIDC_CLIENT_ID= #OIDC_CLIENT_SECRET= # Space-separated scopes (default: openid profile email) #OIDC_SCOPES= # Login-button label (default: Single sign-on) #OIDC_PROVIDER_LABEL= # Optional SSO allow-list — part of the OIDC configuration above, and only # meaningful alongside it. SSO auto-provisions: an account appears the first # time the issuer authenticates someone, with nobody approving it. Against a # single-tenant issuer the issuer is already the boundary; against a # multi-tenant one (Google, the Entra `common` endpoint, Auth0 with social # connections) this is the ONLY boundary. Comma-separated; matches the domain # exactly or as a subdomain; empty (default) allows any email. # Deliberately NOT applied to admin-created accounts or password login — those # can only reach an account somebody already vetted. # ALLOWED_EMAIL_DOMAINS=example.com # Password-login toggle (default enabled). Set to false to hide the method on # the login screen AND reject the /api/auth/login endpoint (SSO-only deploys). # LOGIN_PASSWORD=true # ── 5. Production / reverse proxy ─────────────────────────────────────────── # The public host name, when the bundled HTTPS profile fronts this deployment # (`docker compose --profile https up -d` — Caddy serves this domain with # automatic Let's Encrypt certificates). Setting it also derives the rest of # this section: the public origins default to https:// and the proxy # hop to 1, unless those are set explicitly below. # DOMAIN=bevel.your-domain.com # Public URLs used to build OAuth redirects + post-login bounces. Defaults # target local dev (backend :PORT, frontend :5173); under docker compose both # default to the container's own origin. For a public deployment, set both to # the same https origin (the backend serves the SPA) — or just set DOMAIN # above and both derive from it. # PUBLIC_BACKEND_URL=https://bevel.example.com # PUBLIC_FRONTEND_URL=https://bevel.example.com # Reverse-proxy hops in front of this backend (Express `trust proxy`): set to # the hop count (usually 1) — or an address/CIDR list — so `req.ip` (and the # per-IP login rate limit) sees the real client instead of the proxy. Leave # unset when the backend is directly exposed; never set a blanket trust, or # clients can spoof X-Forwarded-For. Defaults to 1 when DOMAIN is set (the # bundled Caddy is the one hop; a CDN in front of it makes 2). #TRUST_PROXY=1 # Backend port. PORT=3001 # ── 6. Optional tuning ────────────────────────────────────────────────────── # Tenant slug branded into every credential prefix: external API keys are # `_…`, internal tokens `-int_…`. Lowercase alphanumeric # only. The default keeps previously minted keys/tokens valid — change it on a # fresh deploy, not a live one. # TENANT_ID=bevel # Optional dedicated HMAC key for internal (loopback) tool tokens. Leave unset # to derive a domain-separated key from JWT_SECRET — that already lets sibling # processes sharing this env (CLIs, replicas) mint tokens the server verifies. # Set only to rotate the internal surface independently of user sessions. #INTERNAL_TOKEN_SECRET= # Ontology-session boundary kill-switch (touch tracking only in the core — # the write block is an enterprise extension). Default true. # ONTOLOGY_SESSION_BLOCK=true # In-app update check (default enabled). The server asks api.github.com for # the newest Hexis release — only when an admin's browser asks, cached for # hours — and admins see a quiet banner when this deployment is behind. The # request carries nothing but itself (no token, no identifier, no telemetry). # Set to false for air-gapped deployments or to switch the check off entirely. # UPDATE_CHECK=true