Sync the self-hosting stack
This commit is contained in:
@@ -9,6 +9,87 @@ one of them changed. An entry says which artefact and which version, so `web 0.1
|
||||
Every version published to a channel appears here. Nothing is written from memory afterwards: a build
|
||||
cannot be published without its entry.
|
||||
|
||||
## 2026-08-27 — The console container listens on 8080 and runs unprivileged `web 0.120.0`
|
||||
|
||||
**Check your port mapping before you pull.** The web container used to listen on **80** and now
|
||||
listens on **8080**, because binding a port below 1024 needs root and the container no longer runs
|
||||
as root. The compose file in this repository is already updated — `${JARVIS_PORT:-8080}:8080` — so
|
||||
if you use it unchanged there is nothing to do. If you wrote your own mapping, or your reverse proxy
|
||||
targets the container port directly rather than the published one, change `:80` to `:8080`.
|
||||
|
||||
Nothing else about how you reach it changes: same URL, same paths, same TLS terminator in front.
|
||||
|
||||
**The container's upstream is configurable.** `JARVIS_API_UPSTREAM` and `JARVIS_DNS_RESOLVER` now
|
||||
say where the API is and who resolves that name; they default to what compose has always used, so an
|
||||
unchanged stack behaves identically. They exist for anybody running these images somewhere the name
|
||||
`api` and Docker's own DNS do not mean anything.
|
||||
|
||||
**A terminal no longer opens a second shell when your connection drops.** Reconnecting used to
|
||||
silently start a fresh login on the machine — a new session, a new audit entry, a new recording —
|
||||
underneath a window still showing the old scrollback. It now tells you the connection was lost and
|
||||
offers to open a new session, which is a decision rather than something that happened to you.
|
||||
|
||||
**A part of the console that fails to load after an upgrade now says so.** The interface ships as
|
||||
content-hashed files that are replaced on every upgrade, so a tab left open from before can reach
|
||||
for something the server no longer has — which used to be a blank panel and a line in the browser
|
||||
console. It now raises the same notice an update raises, with words, and offers the reload that
|
||||
fixes it.
|
||||
|
||||
## 2026-08-27 — An upgrade refuses to drop a column instead of doing it `api 0.93.0`
|
||||
|
||||
**The one to read.** Until now, every boot ran the schema sync with `--accept-data-loss`, so an
|
||||
upgrade that needed to remove a column simply removed it. That was survivable while one container
|
||||
was the whole deployment and is the wrong default for anything else — and it meant a destructive
|
||||
schema change happened with nobody having decided anything.
|
||||
|
||||
It now **refuses**, prints which columns or tables it would have dropped, and stops. Nothing is
|
||||
altered and the api does not start. If those drops are what you want, set
|
||||
`SCHEMA_ACCEPT_DATA_LOSS=true` in `.env` for that one upgrade and put it back afterwards. Take a
|
||||
dump first either way: there is still no migration history, so pulling an older image does not put
|
||||
the schema back.
|
||||
|
||||
The schema phase is also its own entrypoint now (`migrate.sh`) rather than part of the api's boot.
|
||||
On compose nothing changes — the api still runs it, because one container is the whole deployment —
|
||||
and it takes a database lock while it works, so `--scale api=2` waits rather than races.
|
||||
|
||||
**Two health endpoints, for anything watching this instance.** `/api/health/live` says only that the
|
||||
process is running, and `/api/health/ready` answers 503 when Postgres **or Redis** is unreachable.
|
||||
`/api/health` is unchanged, including its 200-while-degraded behaviour, so whatever you already
|
||||
point at it keeps working.
|
||||
|
||||
**Two tabs no longer sign you out of everything.** Both tabs share one session cookie, so both
|
||||
refreshing at the same moment looked exactly like a stolen cookie being replayed, and the protection
|
||||
against that revoked every session you had. Concurrent refreshes are now told apart from a replay by
|
||||
how long ago the token was rotated. A genuine replay still revokes the family, which is what it is
|
||||
for.
|
||||
|
||||
**An enrolment token's use limit is now actually a limit.** Two machines running the same install
|
||||
command in the same second could both enrol on a token that permitted one — an Ansible play across a
|
||||
fleet does exactly that. The check and the increment are one operation now.
|
||||
|
||||
**A terminal transcript survives the process being killed.** Output used to live only in memory until
|
||||
the session ended, so an out-of-memory kill or a host losing power took the whole recording of a root
|
||||
shell with it. It is written down every few seconds now, and a session that ended that way hands back
|
||||
what was captured, clearly marked as stopping early. A session with no transcript at all also says
|
||||
which of the three reasons applies rather than guessing — recording was off, retention has passed, or
|
||||
it was lost — because telling an auditor a recording was never made when it was made and lost is the
|
||||
answer that stops them looking.
|
||||
|
||||
**A restart no longer writes an ending into a session that is still open.** Starting up used to close
|
||||
every terminal session row it found, on the reasoning that a row with no ending belongs to a process
|
||||
that died. With one container that was true. It is now scoped to the process's own sessions.
|
||||
|
||||
**The api container runs as a non-root user.** Nothing you do changes; it matters if you run these
|
||||
images under a policy that refuses root containers.
|
||||
|
||||
**Optional: `VAULT_KEY_CREATED_AT`.** Only needed where the vault key file is rewritten by your
|
||||
platform rather than sitting still in a volume — on compose it is inferred correctly from the file
|
||||
and you can ignore this. See `.env.example` for what it decides.
|
||||
|
||||
**And the reason for most of the above: more than one api replica is now possible.** It is not yet
|
||||
supported, and this compose file still runs one. Nobody has served real traffic on more than one, so
|
||||
if you are considering it, read the note in the README first.
|
||||
|
||||
## 2026-08-25 — A run gets 200 tool rounds instead of 50 `api 0.92.0`
|
||||
|
||||
**If the assistant has been stopping mid-investigation with "the results above were not analysed",
|
||||
|
||||
@@ -156,8 +156,7 @@ round has run, so replaying it re-generates intent and never re-runs an operatio
|
||||
each loop and lets it write out what it had streamed with a note saying why the transcript ends
|
||||
there; the next process finishes that message, closes any tool call whose outcome is unknown saying
|
||||
in as many words that it is unknown, and resumes the run — instructed to read the current state
|
||||
before repeating anything that writes. This is why the api asks for a stop grace period, and part of
|
||||
why it must run as a single replica.
|
||||
before repeating anything that writes. This is why the api asks for a stop grace period.
|
||||
|
||||
**The agent dials out.** Nothing inbound is opened on a managed machine. It holds a websocket to
|
||||
your instance, signs each session with a key whose private half never leaves it, and reports its
|
||||
@@ -398,14 +397,28 @@ These are deliberate and documented rather than surprises waiting to be found.
|
||||
password changed to match, so plan that one.
|
||||
- **SSH host keys are not verified.** Every SSH connection trusts whatever key answers. This is the
|
||||
one gap in the execution path with no compensating control.
|
||||
- **The api must run as a single replica.** In-flight runs, pending approvals, presence and the
|
||||
websocket of every enrolled agent live in one process's memory. The Redis in this stack does not
|
||||
lift that limit: it fans outgoing events out to other replicas, but an incoming one is only ever
|
||||
handled by the replica holding that connection. So a cancel or an approval answered on the wrong
|
||||
replica is silently dropped, and an agent tool call can land on a replica that does not hold the
|
||||
target machine. Worse, a starting replica's recovery sweep claims every run it does not own — so a
|
||||
second instance re-executes, against your real infrastructure, operations the first is still
|
||||
running.
|
||||
- **This compose file runs one api, and that is the shape it is written for.** It gives you one of
|
||||
everything and generates the secrets onto a shared volume, which is right here and is exactly what
|
||||
must not be done per replica anywhere else — each copy would mint its own vault key, and a
|
||||
credential one process sealed would read as corrupt to another with nothing logged at boot.
|
||||
|
||||
The images themselves no longer require a single replica. The web container's upstream and DNS
|
||||
resolver are configurable, there are separate liveness and readiness endpoints, both containers run
|
||||
unprivileged, and the schema is a separate entrypoint meant to run once rather than something every
|
||||
container does at boot. Everything that used to do real damage across replicas is fixed: a starting
|
||||
replica's recovery sweep once claimed every run it did not own, so a second instance re-executed —
|
||||
against your real infrastructure — operations the first was still running. A run now holds its
|
||||
conversation through a unique index in the database, a sweep claims only a run whose heartbeat has
|
||||
gone cold, and a run that cannot write its heartbeat stops itself before anyone else may take over.
|
||||
Agent tool calls, revocations, Stop, approvals and interactive terminals reach whichever process
|
||||
holds the connection instead of failing where they land. A replica shutting down no longer marks
|
||||
your whole fleet offline, and starting one no longer writes an ending into the record of a root
|
||||
shell that is still open.
|
||||
|
||||
**Nobody has run an estate on more than one replica yet**, so if you are the first, treat it as
|
||||
something to watch rather than something to assume. Two API processes have been run together
|
||||
against one database and one Redis, and the manifests are published, but that is not the same
|
||||
thing as your machines depending on it.
|
||||
- **There is no rate limiting on sign-in.** No throttler, no account lockout, and authentication
|
||||
events are not audited. Credential stuffing is bounded only by the reverse proxy you put in front,
|
||||
which this repository does not ship. If your proxy can rate-limit one route, make it that one.
|
||||
|
||||
+10
-1
@@ -206,6 +206,15 @@ services:
|
||||
AGENT_HEARTBEAT_INTERVAL_SEC: ${AGENT_HEARTBEAT_INTERVAL_SEC:-30}
|
||||
RUN_SHUTDOWN_GRACE_SEC: ${RUN_SHUTDOWN_GRACE_SEC:-25}
|
||||
|
||||
# This container brings the database up to its own schema before serving. Left on, because on
|
||||
# compose one container IS the deployment. Turn it off only where something else runs
|
||||
# `migrate.sh` first — an orchestrator with a run-once Job, say.
|
||||
JARVIS_SKIP_MIGRATIONS: ${JARVIS_SKIP_MIGRATIONS:-false}
|
||||
# Let that phase DROP columns and tables. OFF, and it should stay off between deliberate acts:
|
||||
# without it the schema sync REFUSES and prints what it would have destroyed, which is the
|
||||
# answer you want from an upgrade that was not expecting to lose anything.
|
||||
SCHEMA_ACCEPT_DATA_LOSS: ${SCHEMA_ACCEPT_DATA_LOSS:-false}
|
||||
|
||||
# Where the agent binaries live, if you have them. Leaving this unset is a supported state:
|
||||
# everything except the agent installer works, and the installer answers 503 saying no build is
|
||||
# published. See the README — a self-hosted instance has no way to produce these.
|
||||
@@ -230,7 +239,7 @@ services:
|
||||
ports:
|
||||
# Put your own TLS terminator in front of this. Jarvis speaks plain HTTP here on purpose and
|
||||
# reads X-Forwarded-Proto to know what the browser actually used.
|
||||
- "${JARVIS_PORT:-8080}:80"
|
||||
- "${JARVIS_PORT:-8080}:8080"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
Reference in New Issue
Block a user