Skip to content

Commit a4718a8

Browse files
fix(docker): generate the broker credentials a self-host install needs (#1754)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent f245cc2 commit a4718a8

8 files changed

Lines changed: 44 additions & 18 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"hephaestus": patch
3+
---
4+
5+
A fresh self-host install now starts instead of stopping with `required variable NATS_USERNAME is
6+
missing a value`: `setup.sh` generates the message-broker credentials alongside the database password
7+
and the other internal secrets. Upgrading an existing installation picks them up by rerunning
8+
`docker/self-host/setup.sh`, which leaves every value you already set untouched.

.github/workflows/ci-compose-validate.yml

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,13 @@ jobs:
2323
working-directory: docker/self-host
2424
run: |
2525
set -euo pipefail
26-
# Supply required operator values; missing defaults must still fail rendering.
27-
cp .env.example .env
26+
# Render what the install guide produces: the installer's generated secrets plus the
27+
# values step 2 asks the operator for. Hand-filling the generated ones here would hide a
28+
# required variable the installer forgets, which is how one reached a release smoke test.
29+
./setup.sh
2830
sed -i \
2931
-e 's|^APP_HOSTNAME=$|APP_HOSTNAME=hephaestus.example.com|' \
3032
-e 's|^ACME_EMAIL=$|ACME_EMAIL=operator@example.com|' \
31-
-e 's|^POSTGRES_PASSWORD=$|POSTGRES_PASSWORD=ci-not-a-real-password|' \
32-
-e 's|^HEPHAESTUS_SECURITY_ENCRYPTION_KEY=$|HEPHAESTUS_SECURITY_ENCRYPTION_KEY=0123456789abcdef0123456789abcdef|' \
33-
-e 's|^HEPHAESTUS_AUTH_STATE_COOKIE_KEY=$|HEPHAESTUS_AUTH_STATE_COOKIE_KEY=Y2ktbm90LWEtcmVhbC1zdGF0ZS1jb29raWUta2V5|' \
34-
-e 's|^WEBHOOK_SECRET=$|WEBHOOK_SECRET=ci000000000000000000000000000000000|' \
35-
-e 's|^NATS_USERNAME=$|NATS_USERNAME=ci|' \
36-
-e 's|^NATS_PASSWORD=$|NATS_PASSWORD=ci-not-a-real-password|' \
3733
.env
3834
while IFS= read -r image; do
3935
name="HEPHAESTUS_IMAGE_${image^^}"

docker/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY=
9797
# -----------------------------------------------------------------------------
9898

9999
NATS_ENABLED=true
100-
# Required. Generate independent random values; do not reuse an application key.
100+
# Required. Generate independent random values; do not reuse an application key. Keep a letter in
101+
# each: the broker's config file reads an all-digit credential as a number and refuses to start.
101102
NATS_USERNAME=
102103
NATS_PASSWORD=
103104
NATS_DURABLE_CONSUMER_NAME=hephaestus-consumer

docker/self-host/.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ HEPHAESTUS_AUTH_STATE_COOKIE_KEY=
2727
# Shared secret verifying inbound GitHub/GitLab webhooks (min 32 chars).
2828
# You will enter this same value on the GitHub side — see the install guide.
2929
WEBHOOK_SECRET=
30-
# Required broker credentials. Generate independent random values.
30+
# Credentials for the bundled message broker. Internal to this stack — you never enter them
31+
# anywhere else. Keep a letter in each value: the broker's config file reads an all-digit
32+
# credential as a number and refuses to start.
3133
NATS_USERNAME=
3234
NATS_PASSWORD=
3335

docker/self-host/setup.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ else
2828
fi
2929
chmod 600 "$working_file"
3030

31-
for key in POSTGRES_PASSWORD HEPHAESTUS_SECURITY_ENCRYPTION_KEY HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY HEPHAESTUS_AUTH_STATE_COOKIE_KEY WEBHOOK_SECRET; do
31+
for key in POSTGRES_PASSWORD HEPHAESTUS_SECURITY_ENCRYPTION_KEY HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY HEPHAESTUS_AUTH_STATE_COOKIE_KEY WEBHOOK_SECRET NATS_USERNAME NATS_PASSWORD; do
3232
if [ "$(grep -c "^${key}=" "$working_file")" -gt 1 ]; then
3333
printf 'Refusing duplicate %s assignments in %s.\n' "$key" "$environment_file" >&2
3434
exit 1
@@ -45,6 +45,10 @@ set_if_empty() {
4545
hex16) value=$(openssl rand -hex 16) || return 1 ;;
4646
hex32) value=$(openssl rand -hex 32) || return 1 ;;
4747
base64) value=$(openssl rand -base64 32 | tr -d '\n') || return 1 ;;
48+
# The broker reads these from its own config file, which interpolates them unquoted;
49+
# its parser reads an all-digit token as a number and exits before it listens. The
50+
# prefix keeps a letter in every generated value.
51+
broker) value=heph$(openssl rand -hex 16) || return 1 ;;
4852
esac
4953
if ! grep -q "^${key}=" "$working_file"; then
5054
printf '%s=%s\n' "$key" "$value" >> "$working_file"
@@ -84,6 +88,8 @@ if ! grep -q '^HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY=.' "$working_file";
8488
fi
8589
set_if_empty HEPHAESTUS_AUTH_STATE_COOKIE_KEY base64
8690
set_if_empty WEBHOOK_SECRET hex32
91+
set_if_empty NATS_USERNAME broker
92+
set_if_empty NATS_PASSWORD broker
8793

8894
mv "$working_file" "$environment_file"
8995

docs/admin/configuration-readiness.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ application key.
8585

8686
Server and webhook roles require NATS and an explicit `nats://` or `tls://` URI with a host, an optional
8787
valid port, and no query, fragment, or non-root path. A worker-only process must disable NATS because
88-
its job queue is PostgreSQL-backed. The reference deployment requires `NATS_USERNAME` and
89-
`NATS_PASSWORD`. This check validates syntax and role consistency, not connectivity or JetStream health.
88+
its job queue is PostgreSQL-backed. Every deployment requires `NATS_USERNAME` and `NATS_PASSWORD`;
89+
the supported self-host setup generates both. This check validates syntax and role consistency, not
90+
connectivity or JetStream health.
9091

9192
## Login
9293

docs/admin/install.mdx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,12 @@ node scripts/prepare-release-lock.ts "v$VERSION"
8686
cd docker/self-host
8787
```
8888

89-
`setup.sh` creates or updates `.env` with mode `0600` and generates the database password,
90-
credential-encryption key, OAuth state-cookie key, and webhook secret. It does not replace non-empty
91-
values and never prints a secret.
89+
`setup.sh` creates or updates `.env` with mode `0600` and generates the database password, both
90+
encryption keys — `HEPHAESTUS_SECURITY_ENCRYPTION_KEY` and
91+
`HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY` — the OAuth state-cookie key, the webhook secret,
92+
and the message-broker credentials. It does not replace non-empty values and never prints a secret.
93+
Changing either encryption key on an existing installation makes everything encrypted under the old
94+
one unreadable.
9295

9396
Everything below runs from `/opt/hephaestus/docker/self-host`. After step 2 supplies the remaining
9497
required values, `docker compose config` renders the effective configuration. Before then it exits
@@ -97,8 +100,8 @@ directory; see [Troubleshooting](#troubleshooting).
97100

98101
## 2. Configure `.env`
99102

100-
Open `.env` and fill in the remaining **REQUIRED** values. `setup.sh` manages the four internal
101-
secrets listed above; do not replace them manually.
103+
Open `.env` and fill in the remaining **REQUIRED** values. `setup.sh` manages the internal secrets
104+
listed above; do not replace them manually.
102105

103106
| Variable | What / how |
104107
| --- | --- |

scripts/self-host-setup.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ function managedValues(environment: string): string[] {
5757
"HEPHAESTUS_SECURITY_CREDENTIAL_ENCRYPTION_KEY",
5858
"HEPHAESTUS_AUTH_STATE_COOKIE_KEY",
5959
"WEBHOOK_SECRET",
60+
"NATS_USERNAME",
61+
"NATS_PASSWORD",
6062
]);
6163
return environment
6264
.split("\n")
@@ -81,6 +83,13 @@ await test("generates protected secrets without printing them", async () => {
8183
assert.equal(credentialKey, encryptionKey);
8284
assert.match(environment, /^HEPHAESTUS_AUTH_STATE_COOKIE_KEY=[A-Za-z0-9+/]{43}=$/m);
8385
assert.match(environment, /^WEBHOOK_SECRET=[0-9a-f]{64}$/m);
86+
// The broker's config file rejects an all-digit credential, so both carry a letter prefix.
87+
assert.match(environment, /^NATS_USERNAME=heph[0-9a-f]{32}$/m);
88+
assert.match(environment, /^NATS_PASSWORD=heph[0-9a-f]{32}$/m);
89+
assert.notEqual(
90+
environment.match(/^NATS_USERNAME=(.+)$/m)?.[1],
91+
environment.match(/^NATS_PASSWORD=(.+)$/m)?.[1],
92+
);
8493
assert.equal((await lstat(environmentPath)).mode & 0o777, 0o600);
8594
for (const secret of managedValues(environment)) assert.ok(!result.output.includes(secret));
8695

0 commit comments

Comments
 (0)