Sync the self-hosting stack

This commit is contained in:
2026-08-24 13:02:29 +02:00
parent 4fa9cabefb
commit 0674e550f1
4 changed files with 818 additions and 108 deletions
+146 -56
View File
@@ -1,81 +1,146 @@
# Jarvis — configuration. Copy to `.env` beside the compose file and fill in.
# Jarvis — optional configuration.
#
# cp .env.example .env
# THERE IS NOTHING YOU HAVE TO FILL IN. You do not need this file at all:
#
# Nothing here has a safe placeholder value: the four secrets must be generated, and the two URLs
# must be yours. The compose file refuses to start rather than booting with something wrong.
# docker compose up -d
#
# then open the address in a browser and the install screen asks the questions. It creates the first
# administrator, checks the deployment, and stores the address, the model and everything else as
# settings you can change later from a screen.
#
# THIS FILE USED TO BE MANDATORY, and that is the change. It asked for six values before the
# container would start, four of them secrets to be generated with `openssl` — and it failed in the
# worst available way, because an invalid one made the API exit 1 and the restart policy turned a
# typo into a crash loop, with the reason on line 40 of a log nobody had a reason to open.
#
# What is left below is for deployments that prefer to state things in writing: pinning image
# versions, running the agent overlay, choosing a host port. Every value here is optional, and the
# ones that overlap with the install screen SEED it — they are read once, when nothing is stored
# yet, and never again. Change them afterwards and nothing happens; change the setting instead.
#
# cp .env.example .env # only if you want any of this
# ---------------------------------------------------------------------------
# Where your instance lives
# ---------------------------------------------------------------------------
# The address a browser reaches Jarvis on, scheme included and no trailing slash.
#
# This is the CORS decision. It accepts exactly ONE origin — not a list — and every request from
# anywhere else is refused, which presents as a sign-in page that cannot sign in.
WEB_ORIGIN=https://jarvis.example.com
# The address enrolled machines dial. Defaults to WEB_ORIGIN, which is right for a single-origin
# deployment. Set it separately only if the app and the API answer on different hostnames.
#PUBLIC_URL=https://jarvis.example.com
# Host port the web container publishes. Put your TLS terminator in front of it.
#
# The one value on this page that genuinely cannot move to a screen: it is a Docker fact, decided
# before anything in the application is running.
JARVIS_PORT=8080
# The address a browser reaches Jarvis on. ASKED BY THE INSTALL SCREEN, which pre-fills it with the
# address you are already reading it at — so setting it here is only useful if you want it stated in
# writing, or if you are upgrading a stack that already had it.
#
# It decides CORS, what enrolled machines dial, the base of every invitation link, and the WebAuthn
# relying party. Changing it later orphans every passkey already enrolled, so the screen warns you.
#WEB_ORIGIN=https://jarvis.example.com
# PUBLIC_URL IS GONE. It was a second address for the same thing, kept separate for a split-hostname
# deployment nobody ran; the install screen asks once and every consumer reads that one value. An
# instance that still sets it is not broken — it is simply ignored.
# How many proxies rewrite X-Forwarded-For before a request reaches the API.
#
# One is the web container's own nginx, which is always there. So: 1 if nothing else fronts it, 2 if
# your own reverse proxy does — which is the usual case and the default. Raising it is the dangerous
# direction, because the API then trusts that many hops of a header the client can forge, and a caller
# can choose the address that lands in the audit log and in the session list.
TRUST_PROXY_HOPS=2
# ---------------------------------------------------------------------------
# Secrets — generate every one of these, never copy them from anywhere
# ---------------------------------------------------------------------------
# Postgres. Only ever used inside the compose network.
# openssl rand -base64 24
POSTGRES_PASSWORD=
# Session signing. Two different values, at least 16 characters each.
# openssl rand -base64 48
JWT_ACCESS_SECRET=
JWT_REFRESH_SECRET=
# THE ONE YOU CANNOT LOSE.
# ASKED BY THE INSTALL SCREEN, which is the only place this can honestly be answered: it shows you
# the header chain your own request actually carried and the address the API resolved from it, and
# you confirm what you see. Nobody knows this number in advance.
#
# Must decode to exactly 32 bytes:
# openssl rand -base64 32
#
# Every credential in the vault — SSH keys, API secrets, Microsoft 365 client secrets — is encrypted
# under this key with AES-256-GCM, and so is everything Jarvis seals OUTSIDE the vault: the Microsoft
# Graph client secret it sends its own mail with, this instance's licence identity key, every
# authenticator-app secret, and every terminal recording. It is not stored anywhere but here.
#
# Change it or lose it and none of that data can be read again, by you or by anyone. A database backup
# does not save you: the backup holds the ciphertext. Back this value up separately from the database,
# somewhere you would still have it if this host were gone. An instance whose key has changed keeps
# LOOKING configured — the rows are all there — and fails on every reveal.
VAULT_MASTER_KEY=
# One is the web container's own nginx, which is always there; two if your own reverse proxy fronts
# it, which is the usual case. Raising it past the real number is the dangerous direction, because
# the API then trusts that many hops of a header the client can forge — and a caller can choose the
# address that lands in your audit log and in the session list.
#TRUST_PROXY_HOPS=2
# ---------------------------------------------------------------------------
# The model
# Secrets — there is nothing to fill in here any more
# ---------------------------------------------------------------------------
#
# This section used to hold four values and four `openssl rand` invocations. They are generated for
# you now, on your first `docker compose up`, by the `init` service — into the `jarvis_secrets` volume,
# where the database password, the two session-signing secrets and the vault master key live.
#
# WHY THEY LEFT THIS FILE. A vault key is not a preference. Asking for one put the single most
# consequential value in the deployment — everything in the vault is encrypted under it — in front of
# whoever was least equipped to look after it, at the moment they were least interested in it, in a
# file they were trying to get through. Nobody ever chose a better key than `openssl rand` would have.
#
# WHAT YOU STILL HAVE TO DO, AND IT IS THE IMPORTANT ONE:
#
# BACK UP THE `jarvis_secrets` VOLUME, SEPARATELY FROM THE DATABASE.
#
# The vault key is in there and NOWHERE ELSE. A database backup does not save you — the backup holds
# the ciphertext. An instance whose key is gone keeps LOOKING configured, with every row in place,
# and fails on every reveal. It is a separate volume from `postgres_data` precisely so that the two
# can be, and must be, backed up to different places: a dump that travelled with the key that opens
# it is a dump that opens itself.
#
# The setup screen shows you the key once, on your first visit, and will not let you finish until you
# have put it somewhere. That is the moment to do this.
#
# UPGRADING FROM A .env THAT ALREADY HAS THESE? Leave them exactly where they are. The generator
# ADOPTS an existing value rather than replacing it, and never overwrites a secret it has already
# written — so your instance keeps its own keys and nothing about your vault changes. You can delete
# them from this file once the stack has come up once, or leave them; they are read only when the
# corresponding file does not exist yet.
#
# Pointing at secrets of your own instead? Every one of them also accepts a `_FILE` variable
# (VAULT_MASTER_KEY_FILE, JWT_ACCESS_SECRET_FILE, …), which is the convention the postgres image and
# most others already use — so a `docker secret` can be mounted at those paths with nothing else
# changing.
# ---------------------------------------------------------------------------
# The model — asked by the install screen, with a test button
# ---------------------------------------------------------------------------
#
# All four moved. The install screen asks for the endpoint and the key, lists the models the endpoint
# actually serves, and ASKS THE MODEL FOR A TOKEN before it saves anything — so a key that is
# well-formed and wrong, an account out of credit or a model id the provider does not serve is a
# sentence on screen instead of a fault in somebody's first conversation.
#
# The key is stored encrypted under this instance's vault key, exactly like the outbound mail secret,
# and is never shown again. Change any of it later under Settings -> Platform -> Model.
#
# Setting them here still works and seeds an instance that has nothing stored — for a deployment
# that would rather state its model in a file. They are read once and never again.
# Any OpenAI-compatible endpoint: OpenAI, a self-hosted gateway, a local server.
#OPENAI_BASE_URL=https://api.openai.com/v1
# Required. Jarvis will not start without it.
OPENAI_API_KEY=
# Must be a model your endpoint actually serves, and it should be a good one — this model is deciding
# what to run on production infrastructure.
#OPENAI_API_KEY=
#OPENAI_MODEL=gpt-4o
# minimal | low | medium | high. Higher costs latency and tokens and is worth it for real work.
#OPENAI_THINKING_LEVEL=medium
# ---------------------------------------------------------------------------
# Unattended installation (for fleets and for CI)
# ---------------------------------------------------------------------------
#
# Everything above is optional because the install screen asks for it. This is the other direction:
# somebody rolling out fifty instances from a template has answered those questions once already,
# and making them answer each one in a browser is exactly what this product stopped doing to people.
#
# Set this and the instance installs itself at boot from whatever the variables above provide, marks
# itself installed, and never shows the wizard.
#
#JARVIS_UNATTENDED=on
#
# The first administrator. REQUIRED when JARVIS_UNATTENDED is on and the database is empty; the
# password must be at least 12 characters.
#
# If they are missing or too short the instance says so loudly in its logs and leaves setup
# OUTSTANDING rather than completing — an instance marked installed with no account is one nobody
# can ever get into, and the claim window is gated on setup being unfinished, so there would be no
# way back. Falling through to the ordinary install screen is strictly better than that.
#
#JARVIS_ADMIN_EMAIL=
#JARVIS_ADMIN_PASSWORD=
#JARVIS_ADMIN_NAME=Administrator
#
# Worth setting alongside them, since nobody will be there to be asked: WEB_ORIGIN, OPENAI_API_KEY
# and OPENAI_MODEL above, and JARVIS_LICENSE_KEY below. Each is optional even here — an unattended
# instance with no model comes up and reports that it has none, rather than refusing to start.
# ---------------------------------------------------------------------------
# Channels and pinning (optional, recommended in production)
# ---------------------------------------------------------------------------
@@ -137,6 +202,22 @@ OPENAI_API_KEY=
#AGENT_HEARTBEAT_INTERVAL_SEC=30
#RUN_SHUTDOWN_GRACE_SEC=25
# How long a sign-in lasts, in seconds. Both were fixed in the compose file until now, which made
# them the only tuning values on this page you could read about and not change.
#
# JWT_ACCESS_TTL is how long an access token stays valid — and therefore how long a revoked session
# keeps working before it notices. Lower it if that window matters to you; the cost is a refresh
# round trip more often.
#
# JWT_REFRESH_TTL IS AN IDLE TIMEOUT, NOT A SESSION LIFETIME, and the difference will matter to you
# if you are here because of a policy. The refresh token rotates on every use and the new one starts
# its full term from that moment, so nothing anywhere measures how old a sign-in is: somebody who
# keeps a tab open stays signed in indefinitely. What this value really sets is how long a session
# survives being left alone. Fourteen days is the default. To end sessions by age rather than by
# idleness, revoke them — Settings → Security lists them and kills them individually.
#JWT_ACCESS_TTL=900
#JWT_REFRESH_TTL=1209600
# ---------------------------------------------------------------------------
# Licence (optional — without one you get the community edition)
# ---------------------------------------------------------------------------
@@ -167,4 +248,13 @@ OPENAI_API_KEY=
#
# A LICENSED instance reports regardless — that is part of what a licence is, and this variable does
# not change it.
# NOTE, 24 August 2026: this line did nothing before that date, and we are sorry. Compose reads
# this file to fill in placeholders INSIDE the compose file — it does not hand it to the services —
# and the api service never listed this variable, so the API saw nothing and applied its default of
# "on". An instance that had opted out reported anyway. Pull the repository again and
# `docker compose up -d`: the fix is in the compose file, not in the images.
#
# Do not trust this file to tell you whether it took. Ask the instance:
# docker compose exec api printenv JARVIS_TELEMETRY
# Settings -> Licence answers the same question from what the API actually resolved.
#JARVIS_TELEMETRY=on