Files
terdut-server/charts/terdut-server/values.yaml
T
Niklas Ye b7d296f6e9 Create the first administrator with a generated password kept in a Secret
The bootstrap hook created `admin` with no password, so the account could
only use its API key and could not sign in on the web UI or the TUI. It
also won the one-shot /api/bootstrap against whoever ran it by hand, and
failed with exit 1 when the Secret already existed, which fails the Helm
release.

The hook now generates a 32-character password, stores username, password
and api-key in the <release>-admin-key Secret, and reuses the stored
password on later runs, so recreating the database brings the same
account back. The password is written before the account is created, so a
crash in between cannot leave an administrator nobody has the password
for. When the server was bootstrapped by something else, it checks the
stored password against /api/login and removes only the password it
generated if that does not sign in, then exits cleanly. bootstrap.enabled:
false still removes the hook and its role.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-10 14:36:15 +02:00

174 lines
7.7 KiB
YAML

# Safe above 1 since v0.36.0: the sweeper, notifier and migration runner each
# take a Postgres advisory lock around their own pass, and a webhook that
# loses the race to open a brand-new incident attaches to the winner's row
# instead of dropping its payload. An image older than v0.36.0 does not have
# these guards -- do not raise this against one.
replicaCount: 2
networking:
hostname: "terdut.example.com"
servicePort: 8080
# Gateway listener to bind the HTTPRoute to. Empty attaches to every matching
# listener, including plaintext HTTP. Set this to the name of the HTTPS
# listener to serve the API over TLS only.
listener: ""
image:
repository: git.ryuvia.com/niklas/terdut-server
tag: "latest"
pullPolicy: IfNotPresent
# Postgres connection. The chart provisions no database; it expects one to exist.
database:
# Required. A DSN with no password in it:
# postgres://terdut@terdut-postgres:5432/terdut?sslmode=require
#
# The password is deliberately a separate setting. pgx falls back to libpq's
# environment variables for anything the DSN omits, so PGPASSWORD supplies it
# without the credential appearing in values, in the rendered manifest, or in
# `kubectl describe pod`.
dsn: ""
# Where PGPASSWORD comes from. With the Zalando postgres operator this is the
# Secret it generates for the role — `<user>.<cluster>.credentials.postgresql.acid.zalan.do`,
# whose keys are `username` and `password` — so a from-scratch rebuild mints a
# new password and the server picks it up with nothing to keep in sync.
#
# Read at process start only: rotating the password needs a pod restart.
#
# Leave name empty only if the DSN carries its own password, which puts it in
# the manifest.
passwordSecret:
name: ""
key: password
# Blocks the main container from starting until Postgres accepts
# connections. Without this, a Deployment created before Postgres has
# finished its very first boot -- initdb plus Patroni leader election, on a
# from-scratch postgres-operator cluster -- crash-loops a few times: the
# app's own ping-retry budget on startup (pingAttempts/pingRetryDelay in
# internal/db/db.go) is sized for a much shorter, different race --
# NetworkPolicy propagation, a few seconds -- not for genuine first-time
# cluster creation, which routinely takes longer, so it exhausts and the
# process exits before ever binding its HTTP port. A startupProbe cannot
# help here: the crash happens before there is anything to probe.
#
# pg_isready needs no credentials -- it reports PQPING_OK on anything that
# amounts to "a Postgres backend answered", including an auth challenge --
# so no PGPASSWORD is wired into this container.
waitForPostgres:
enabled: true
image:
repository: postgres
tag: "17-alpine"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 8080
sweeper:
# How long a firing alert may go without a refreshing webhook before it is
# treated as resolved. Must exceed your Alertmanager repeat_interval.
staleAfter: 6h
# How long a resolved alert stays in the default list before auto-archiving.
archiveAfter: 168h
# How many reverse proxies in front of the server append to X-Forwarded-For.
# The per-address login/sign-up rate limits take the client address that many
# entries from the right. 0 ignores the header.
trustedProxies: 1
notify:
# ntfy server that push notifications are published to, e.g.
# http://ntfy.ntfy.svc.cluster.local. Empty disables notifications entirely.
ntfyUrl: ""
# Topic used when nobody is on call today. Notifications sent here carry no
# Acknowledge button: the topic is shared, so there is no user to attribute an
# acknowledgement to. Leave empty to send nothing when the schedule is unset.
fallbackTopic: ""
# How long an incident may sit unacknowledged before it is paged again.
# Set to 0 to notify once and never repeat.
repeatEvery: 15m
# Base URL a phone uses to reach this server, for the link and the Acknowledge
# button inside a notification. Defaults to https://<networking.hostname>.
#
# The Acknowledge button is a POST to /api/notify/ack/{token} from the
# responder's phone, so that path has to stay publicly reachable — it is
# authorised by the scoped token in the URL, not by network placement.
publicUrl: ""
# Optional bearer token for an access-controlled ntfy, read from an existing
# Secret. Leave name empty for an open ntfy.
tokenSecret:
name: ""
key: token
# Whether a user may sign in, or sign up, with a password. Turn it off once
# single sign-on works, to make it the only way in; turn it back on (and
# redeploy) if the identity provider is down and somebody has to get in.
passwordLogin: true
# Declares this install gitops-managed: writes to teams, escalation policies,
# dead man's switches and integrations from a session or a user's own API key
# are refused, while a service account's (see SERVICE-ACCOUNTS.md) are not.
# Off by default — turning it on is a statement that something like
# terdut-operator, not a person in the web UI, owns this install's
# configuration from here on.
operatorMode: false
# Single sign-on through an OpenID Connect provider such as Authentik.
#
# At the provider, create an OAuth2/OpenID application whose redirect URI is
# <notify.publicUrl>/api/oidc/callback
# (publicUrl defaults to https://<networking.hostname>), a confidential client, and
# put the client secret in an existing Secret named by clientSecret below.
#
# Groups from the provider decide what a person can do. Access it grants is
# marked as managed by single sign-on and is re-read at every sign-in; anything
# added by hand in terdut is left alone. Changes in the provider take effect at
# the person's next sign-in, at most sessionMaxAge later. API keys are NOT
# revoked when somebody is removed at the provider: disable the user in terdut too.
oidc:
enabled: false
# Issuer URL. For Authentik: https://<authentik>/application/o/<app-slug>/
issuer: ""
clientId: ""
clientSecret:
name: ""
key: client-secret
# What the sign-in button calls the provider.
name: SSO
# Authentik puts the groups claim behind the profile scope.
scopes: "openid profile email"
usernameClaim: preferred_username
emailClaim: email
groupsClaim: groups
# Link a first sign-in to an existing local user with the same email even when
# the provider does not mark the address verified. Authentik reports
# email_verified as false unless configured otherwise.
trustEmail: false
# Only people in one of these groups may sign in. Empty admits everybody the
# provider authenticates, and access control is left to the provider.
allowedGroups: []
# Members of this group are system administrators.
adminGroup: ""
# Which group grants a team's membership and ownership is each team's own
# setting now, not chart config: an owner sets it from the Members tab, or
# PUT /api/teams/{teamID}/oidc-groups. A team must already exist for a group
# to grant access to it.
# Hard ceiling on a session made by a single sign-on login.
sessionMaxAge: 12h
# Backups are not this chart's business: Postgres is backed up where it runs,
# through a k8up.io/backupcommand pg_dump annotation on the database pod itself.
# Creates the first administrator after install/upgrade, with a generated password, and keeps
# the credentials in a Secret. Set enabled: false to create the first user yourself with
# POST /api/bootstrap (or on an SSO-only install that wants no local account).
bootstrap:
enabled: true
username: admin
email: admin@example.com
# secretName overrides the default of <fullname>-admin-key. It holds username, password and
# api-key. The password is generated once and then reused: delete the Secret and the
# database to start over.
secretName: ""