-
Notifications
You must be signed in to change notification settings - Fork 2
feat(server): validate production configuration at startup #1555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| "hephaestus": minor | ||
| --- | ||
|
|
||
| Production startup now reports all catalogued production configuration problems together without | ||
| exposing configured values, and instance administrators can inspect redacted deployment and runtime | ||
| readiness facts through the API. New self-hosted installations generate and preserve internal secrets | ||
| with `setup.sh`. **Operators:** validate production settings against the configuration readiness guide | ||
| before upgrading; the process now refuses to start when a catalogued required setting is missing or | ||
| invalid. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| #!/bin/sh | ||
|
|
||
| set -eu | ||
| umask 077 | ||
|
|
||
| directory=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) | ||
| environment_file="$directory/.env" | ||
| example_file="$directory/.env.example" | ||
|
|
||
| command -v openssl >/dev/null 2>&1 || { | ||
| printf '%s\n' "openssl is required to generate installation secrets." >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| if [ -L "$environment_file" ]; then | ||
| printf '%s\n' "Refusing to write through symlink $environment_file." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| working_file=$(mktemp "$directory/.env.setup.XXXXXX") | ||
| generated_keys= | ||
| trap 'rm -f "$working_file"' EXIT HUP INT TERM | ||
|
|
||
| if [ -f "$environment_file" ]; then | ||
| cp "$environment_file" "$working_file" | ||
| else | ||
| cp "$example_file" "$working_file" | ||
| fi | ||
| chmod 600 "$working_file" | ||
|
|
||
| for key in POSTGRES_PASSWORD HEPHAESTUS_SECURITY_ENCRYPTION_KEY HEPHAESTUS_AUTH_STATE_COOKIE_KEY WEBHOOK_SECRET; do | ||
| if [ "$(grep -c "^${key}=" "$working_file")" -gt 1 ]; then | ||
| printf 'Refusing duplicate %s assignments in %s.\n' "$key" "$environment_file" >&2 | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| set_if_empty() { | ||
| key=$1 | ||
| format=$2 | ||
| if grep -q "^${key}=." "$working_file"; then | ||
| return | ||
| fi | ||
| case "$format" in | ||
| hex16) value=$(openssl rand -hex 16) || return 1 ;; | ||
| hex32) value=$(openssl rand -hex 32) || return 1 ;; | ||
| base64) value=$(openssl rand -base64 32 | tr -d '\n') || return 1 ;; | ||
| esac | ||
| if ! grep -q "^${key}=" "$working_file"; then | ||
| printf '%s=%s\n' "$key" "$value" >> "$working_file" | ||
| generated_keys="$generated_keys $key" | ||
| elif grep -q "^${key}=$" "$working_file"; then | ||
| temporary_file=$(mktemp "$directory/.env.value.XXXXXX") | ||
| while IFS= read -r line; do | ||
| if [ "$line" = "$key=" ]; then | ||
| printf '%s=%s\n' "$key" "$value" | ||
| else | ||
| printf '%s\n' "$line" | ||
| fi | ||
| done < "$working_file" > "$temporary_file" | ||
| chmod 600 "$temporary_file" | ||
| mv "$temporary_file" "$working_file" | ||
| generated_keys="$generated_keys $key" | ||
| fi | ||
| } | ||
|
|
||
| set_if_empty POSTGRES_PASSWORD hex16 | ||
| set_if_empty HEPHAESTUS_SECURITY_ENCRYPTION_KEY hex16 | ||
| set_if_empty HEPHAESTUS_AUTH_STATE_COOKIE_KEY base64 | ||
| set_if_empty WEBHOOK_SECRET hex32 | ||
|
|
||
| mv "$working_file" "$environment_file" | ||
|
|
||
| for key in $generated_keys; do | ||
| printf 'Generated %s.\n' "$key" | ||
| done | ||
|
|
||
| printf '\nConfiguration written to %s. Generated values were not printed.\n' "$environment_file" | ||
| printf '%s\n' 'Set APP_HOSTNAME, ACME_EMAIL, one OAuth provider, and HEPHAESTUS_AUTH_BOOTSTRAP_ADMINS before starting Hephaestus.' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| --- | ||
| title: Configuration readiness | ||
| --- | ||
|
|
||
| # Configuration readiness | ||
|
|
||
| The production profile validates deployment settings during startup. The server-role endpoint | ||
| `GET /api/admin/configuration-readiness` returns those facts plus checks that need runtime state. It | ||
| requires the `app_admin` authority and is available only when boot-fatal checks pass. Diagnostics | ||
| never contain configured values. | ||
|
|
||
| Each fact has a stable `id`, the affected configuration `subject`, applicable runtime `roles`, a | ||
| `requirement`, a `status`, an explanation, and a documentation link. Requirements are `REQUIRED`, | ||
| `RECOMMENDED`, or `OPTIONAL`. Status is one of: | ||
|
|
||
| | Status | Meaning | | ||
| | --- | --- | | ||
| | `SATISFIED` | The applicable check passed. | | ||
| | `ACTION_REQUIRED` | An applicable check failed. During startup, required deployment facts with this status prevent startup. | | ||
| | `NOT_CONFIGURED` | An optional setting is absent. | | ||
| | `NOT_APPLICABLE` | The check does not apply to this process's runtime roles. | | ||
|
|
||
| The table names variables inside the application container. In the supported self-host stack, | ||
| Compose maps `POSTGRES_PASSWORD` to `DATABASE_PASSWORD` and owns the runtime-role values. | ||
|
|
||
| | Setting | Application environment variable | | ||
| | --- | --- | | ||
| | `spring.datasource.url` | `DATABASE_URL` | | ||
| | `spring.datasource.username` | `DATABASE_USERNAME` | | ||
| | `spring.datasource.password` | `DATABASE_PASSWORD` | | ||
| | `hephaestus.runtime.server.enabled` | `HEPHAESTUS_RUNTIME_SERVER_ENABLED` | | ||
| | `hephaestus.runtime.worker.enabled` | `HEPHAESTUS_RUNTIME_WORKER_ENABLED` | | ||
| | `hephaestus.runtime.webhook.enabled` | `HEPHAESTUS_RUNTIME_WEBHOOK_ENABLED` | | ||
| | `hephaestus.host-url` | `APPLICATION_HOST_URL` | | ||
| | `hephaestus.security.encryption-key` | `HEPHAESTUS_SECURITY_ENCRYPTION_KEY` | | ||
| | `hephaestus.webhook.secret` | `WEBHOOK_SECRET` | | ||
| | `hephaestus.sync.nats.enabled` | `NATS_ENABLED` | | ||
| | `hephaestus.sync.nats.server` | `NATS_SERVER` | | ||
| | `hephaestus.auth.state-cookie-key` | `HEPHAESTUS_AUTH_STATE_COOKIE_KEY` | | ||
| | `hephaestus.llm.egress.allow-loopback` | `HEPHAESTUS_LLM_EGRESS_ALLOW_LOOPBACK` | | ||
| | `hephaestus.agent.image.require-digest` | `HEPHAESTUS_AGENT_IMAGE_REQUIRE_DIGEST` | | ||
| | `hephaestus.agent.image.reference` | `HEPHAESTUS_AGENT_IMAGE_REFERENCE` | | ||
| | `hephaestus.sandbox.container-runtime` | `SANDBOX_CONTAINER_RUNTIME` | | ||
| | `hephaestus.sentry.dsn` | `SENTRY_DSN` | | ||
|
|
||
| ## Runtime roles | ||
|
|
||
| Enable at least one of `hephaestus.runtime.server.enabled`, `worker.enabled`, or `webhook.enabled`; | ||
| each accepts only `true` or `false`. | ||
| The supported split topology enables only webhook on the webhook process, only worker on a remote | ||
| worker, and server (optionally with a colocated worker) on the application process. | ||
|
|
||
| ## Database | ||
|
|
||
| Every role uses PostgreSQL. `DATABASE_URL` must be a PostgreSQL URL; the production profile adds the | ||
| `jdbc:` prefix. Supply a non-empty username and password. This syntax check does not replace the | ||
| connection and migration health checks performed by Spring Boot and Liquibase. | ||
|
|
||
| ## Credential encryption | ||
|
|
||
| Set `hephaestus.security.encryption-key` to exactly 32 printable, non-space ASCII characters and keep it | ||
| with the database backup. The supported self-host setup generates it. Do not change it on an existing | ||
| installation. | ||
|
|
||
| ## External URL | ||
|
|
||
| Set `hephaestus.host-url` to the public HTTPS origin, without credentials, a path other than `/`, a | ||
| query, or a fragment. | ||
|
|
||
| ## Webhooks | ||
|
|
||
| Server and webhook roles require `hephaestus.webhook.secret` with at least 32 printable, non-space ASCII | ||
| characters. The supported self-host setup generates an independent value; never reuse another | ||
| application key. | ||
|
|
||
| ## NATS | ||
|
|
||
| Server and webhook roles require NATS and an explicit `nats://` or `tls://` URI with a host, an optional | ||
| valid port, and no query, fragment, or non-root path. A worker-only process must disable NATS because | ||
| its job queue is PostgreSQL-backed. This check validates syntax and role consistency, not | ||
| authentication, connectivity, or JetStream health. | ||
|
|
||
| ## Login | ||
|
|
||
| The server role requires a Base64-encoded 32-byte `hephaestus.auth.state-cookie-key` and an enabled | ||
| GitHub or GitLab sign-in provider in the database-backed provider catalogue. Environment provider | ||
| entries are seeds, not the readiness authority. Slack and Outline are link-only providers and do not | ||
| satisfy sign-in readiness. Worker and webhook roles do not load login providers. | ||
|
|
||
| ## LLM proxy | ||
|
|
||
| Worker roles must leave `hephaestus.llm.egress.allow-loopback=false`. Provider credentials and model | ||
| configuration are database-backed runtime configuration and are not deployment settings. | ||
|
|
||
| ## Agent image | ||
|
|
||
| Worker roles require digest enforcement and a SHA-256-pinned `hephaestus.agent.image.reference`. See | ||
| [Agent image digests](./agent-image-digests.md). | ||
|
|
||
| ## Sandbox isolation | ||
|
|
||
| Set `SANDBOX_CONTAINER_RUNTIME=runsc` on workers after | ||
| [installing and configuring gVisor](https://gvisor.dev/docs/user_guide/install/) on the host. This | ||
| recommendation is non-fatal. | ||
|
|
||
| ## Optional observability | ||
|
|
||
| Sentry is optional. When configured, `hephaestus.sentry.dsn` must use HTTPS. The fact is classified | ||
| `OPTIONAL` and never prevents startup. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Preserve a final line that has no newline.
If an existing
.envhas an unterminated final line and any managed key is empty, this loop drops that final line. For example, it can remove a final OAuth secret and cause the next startup to fail validation.Process the buffered line after
readreaches EOF.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents