b7d296f6e9
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
174 lines
7.7 KiB
YAML
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: ""
|