Sync the self-hosting stack

This commit is contained in:
2026-09-25 23:18:16 +02:00
parent 62a9308795
commit 1aa6e9d05f
3 changed files with 69 additions and 8 deletions
+37
View File
@@ -9,6 +9,43 @@ 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 Every version published to a channel appears here. Nothing is written from memory afterwards: a build
cannot be published without its entry. cannot be published without its entry.
## 2026-09-25 — A fresh Kubernetes install no longer asks for its own vault key `api 0.114.1`
**On the published Kubernetes stack, creating the first administrator asked for the last characters
of the vault master key, every time.** That proof exists for one case: an instance whose database has
gone missing while its key survived, which looks brand new from the outside. A genuine first
installation is never meant to be asked. On Kubernetes it always was. The minting Job writes
`VAULT_KEY_CREATED_AT` beside the key it generates, but the api read that date only when the key came
from a file, and this stack passes the key as an environment variable. The date went unread, the
key's age was unknown, and an unknown age counts as "cannot prove this instance is new".
**The api now reads `VAULT_KEY_CREATED_AT` wherever the key comes from.** A fresh install whose api
starts within fifteen minutes of the key being minted is claimed without it. The case the proof
exists for is unchanged: a `jarvis-generated` Secret that outlives its database still carries its
old date, and the claim still asks for the key.
**The Job now binds its date to its key**, with a fingerprint in the same value
(`<date> sha256:<16 hex>`), and on this stack the api counts a date only when it is bound to the key
in use. The api merges `jarvis-generated` and `jarvis-secrets` name by name, so a key you put in
`jarvis-secrets` yourself would otherwise meet the date the Job minted for another key; a key
restored before its database would then read as brand new, and its first administrator would be
free. A date without the fingerprint, or bound to another key, is ignored, and the claim asks for
the key as before. To have a first install on the manual path claimed without it, bind the date as
`01-secret.example.yaml` shows. A date more than five minutes in the future is refused too.
**If you apply the files one by one, move `01-secret-job.yaml` to the same api digest as the rest.**
A Job on an older image writes a date without the fingerprint, and a first install keeps asking for
the key. Kustomize and Argo CD already give the Job the api's image, from the one `images:` entry.
**An instance deployed before this version and not installed yet will still ask.** Its
`jarvis-generated` was minted by an older Job, whose date carries no fingerprint and does not count.
Give the key's last characters. The key is in `jarvis-secrets` if you set
`VAULT_MASTER_KEY` there, and in `jarvis-generated` otherwise:
`kubectl -n <namespace> get secret jarvis-generated -o jsonpath='{.data.VAULT_MASTER_KEY}' | base64 -d`.
**The published Compose stack is not affected.** There the key is a file, and its age was already read
from it.
## 2026-09-25 — The licence report counts operators, and the paid plans are Business and MSP `api 0.114.0` `web 0.139.0` ## 2026-09-25 — The licence report counts operators, and the paid plans are Business and MSP `api 0.114.0` `web 0.139.0`
**The licence report sends one more number.** Next to organizations, enabled users, agents and assets, **The licence report sends one more number.** Next to organizations, enabled users, agents and assets,
+19 -7
View File
@@ -44,18 +44,30 @@ stringData:
JWT_ACCESS_SECRET: "REPLACE_ME" JWT_ACCESS_SECRET: "REPLACE_ME"
JWT_REFRESH_SECRET: "REPLACE_ME" JWT_REFRESH_SECRET: "REPLACE_ME"
# WHEN THE VAULT KEY WAS CREATED, as an ISO 8601 timestamp or epoch seconds. # WHEN THE VAULT KEY WAS CREATED, bound to that key. Manual path only: if `01-secret-job.yaml`
# mints your key, delete this line with the other `VAULT_*` ones — left here, even as
# "REPLACE_ME", it overrides the value the Job wrote beside its key.
# #
# Set it when you mint the key, and never move it afterwards. On Compose this is inferred from the # The value is the moment you generate the key and the key's fingerprint, one space apart:
# key file's modification time, which is a good answer on a volume nobody rewrites. Here it is a #
# wrong one: the kubelet writes a projected Secret into every pod at start, so the mtime is the # FP=$(printf %s "$VAULT_MASTER_KEY" | sha256sum | cut -c1-16)
# POD's age and every pod reports a key created seconds ago however old it is. # echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) sha256:$FP"
#
# Restoring a key you backed up? Restore the value saved with it, word for word, and never compute
# a new one: today's date bound to an old key is the one mistake this line can make dangerous. A
# date without the fingerprint, or bound to another key, is ignored.
#
# On Compose this is inferred from the key file's modification time. Here there is no file: the
# api receives the key through `envFrom`, so this line is the only evidence of the key's age.
# (Mounting the Secret as a file would not do better — the kubelet writes it into every pod at
# start, so its mtime is the POD's age.)
# #
# What that number decides is narrow and serious. An instance with no accounts can have its first # What that number decides is narrow and serious. An instance with no accounts can have its first
# super-admin claimed by whoever reaches the install screen — unless the vault key is old, which # super-admin claimed by whoever reaches the install screen — unless the vault key is old, which
# means the database has gone missing from an instance that already existed (a bad restore, a PVC # means the database has gone missing from an instance that already existed (a bad restore, a PVC
# pointed at the wrong volume) and the claim needs proof of possession instead. Without this line, # pointed at the wrong volume) and the claim needs proof of possession instead. Without this line
# that check reads "brand new instance" on exactly the estate it exists to protect. # the age is unknown and the claim always asks for the key, which costs you nothing but its last
# characters.
VAULT_KEY_CREATED_AT: "REPLACE_ME" VAULT_KEY_CREATED_AT: "REPLACE_ME"
# `connection_limit` is not optional here, and it is the reason this URL is in the Secret rather # `connection_limit` is not optional here, and it is the reason this URL is in the Secret rather
+13 -1
View File
@@ -280,11 +280,23 @@ outcome. Run it again.
## Backups ## Backups
Everything in [the main README](../README.md#backups-and-restoring-one) applies, with one Everything in [the main README](../README.md#backups-and-restoring-one) applies, with one
substitution: the vault key is not in a Docker volume, it is in the `jarvis-generated` Secret. substitution: the vault key is not in a Docker volume, it is in a Secret — `jarvis-secrets` if you
set `VAULT_MASTER_KEY` there yourself, `jarvis-generated` otherwise. The kustomization mints
`jarvis-generated` either way, so on the manual path it holds a key nothing uses: back up the one
the api reads.
```sh ```sh
kubectl -n jarvis get secret jarvis-generated -o jsonpath='{.data.VAULT_MASTER_KEY}' | base64 -d kubectl -n jarvis get secret jarvis-generated -o jsonpath='{.data.VAULT_MASTER_KEY}' | base64 -d
kubectl -n jarvis get secret jarvis-generated -o jsonpath='{.data.VAULT_KEY_CREATED_AT}' | base64 -d
``` ```
Back that up somewhere that is not this cluster, and not alongside your database dump. A database Back that up somewhere that is not this cluster, and not alongside your database dump. A database
backup that travelled with its own key is a backup that decrypts itself. backup that travelled with its own key is a backup that decrypts itself.
The second line is when the key was created, bound to that key by its fingerprint — on a Secret
minted from api 0.114.1 on. An older one carries a bare date, which the api ignores here, so a
restored copy of it changes nothing and the claim asks for the key. If you ever restore the key by
hand into `jarvis-secrets`, restore that value beside it word for word, or leave it out — never
compute a new one. It is what tells the install screen whether an empty database is a
first install or a lost one, and an old key dated today makes the first administrator free to
whoever reaches the address before your dump is loaded.