Introduce helm chart and CI and CD
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
name: Validate, publish and deploy Gramps Web
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CHART_REGISTRY: oci://registry.brunner.ninja/feedc0de/charts
|
||||
POSTGRES_IMAGE: registry.brunner.ninja/feedc0de/postgres-with-locales
|
||||
POSTGRES_IMAGE_TAG: 18-locales
|
||||
HELM_VERSION: v4.2.2
|
||||
HELM_SHA256: 9adafecab4d406853bba163a70e9f104f47dbbf65ce24b7653bae7e36150bcb6
|
||||
KUBECTL_VERSION: v1.36.3
|
||||
KUBERNETES_API: https://host.containers.internal:6443
|
||||
KUBERNETES_TLS_SERVER_NAME: 192.168.0.2
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pinned Helm
|
||||
run: |
|
||||
curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
printf '%s %s\n' "${HELM_SHA256}" "${RUNNER_TEMP}/helm.tgz" | sha256sum --check
|
||||
tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm
|
||||
echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}"
|
||||
- name: Validate chart and secret hygiene
|
||||
run: bash test.sh
|
||||
- name: Validate Kubernetes schemas
|
||||
run: |
|
||||
helm template gramps . --namespace gramps --values values-arschrock.yaml \
|
||||
| docker run --rm --interactive \
|
||||
ghcr.io/yannh/kubeconform:v0.7.0@sha256:85dbef6b4b312b99133decc9c6fc9495e9fc5f92293d4ff3b7e1b30f5611823c \
|
||||
-strict -exit-on-error -ignore-missing-schemas -summary
|
||||
- name: Build and test locale-enabled PostgreSQL
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_version=$(docker run --rm docker.io/bitnami/postgresql:latest postgres --version)
|
||||
[[ "${base_version}" == *"PostgreSQL) 18."* ]]
|
||||
docker build --pull --tag "${POSTGRES_IMAGE}:ci" postgresql-image
|
||||
docker run --rm --entrypoint bash "${POSTGRES_IMAGE}:ci" -ec \
|
||||
'postgres --version | grep -E "PostgreSQL\) 18\."; locale -a | grep -Fx de_AT.utf8; locale -a | grep -Fx de_DE.utf8; locale -a | grep -Fx en_GB.utf8; locale -a | grep -Fx en_US.utf8'
|
||||
|
||||
publish:
|
||||
if: gitea.event_name == 'push' && (gitea.ref == 'refs/heads/main' || startsWith(gitea.ref, 'refs/tags/v'))
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pinned Helm
|
||||
run: |
|
||||
curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
printf '%s %s\n' "${HELM_SHA256}" "${RUNNER_TEMP}/helm.tgz" | sha256sum --check
|
||||
tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm
|
||||
echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}"
|
||||
- name: Build and publish image and chart
|
||||
env:
|
||||
COMMIT_SHA: ${{ gitea.sha }}
|
||||
GIT_REF: ${{ gitea.ref }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
|
||||
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "${QUAY_USERNAME}"
|
||||
test -n "${QUAY_TOKEN}"
|
||||
if [[ "${GIT_REF}" == refs/tags/v* ]]; then
|
||||
chart_version="${GIT_REF#refs/tags/v}"
|
||||
else
|
||||
chart_version="0.0.0-dev.sha${COMMIT_SHA:0:12}"
|
||||
fi
|
||||
|
||||
base_version=$(docker run --rm docker.io/bitnami/postgresql:latest postgres --version)
|
||||
[[ "${base_version}" == *"PostgreSQL) 18."* ]]
|
||||
docker build --pull \
|
||||
--label "org.opencontainers.image.revision=${COMMIT_SHA}" \
|
||||
--label "org.opencontainers.image.source=https://code.brunner.ninja/feedc0de/gramps-web-deployment" \
|
||||
--tag "${POSTGRES_IMAGE}:${POSTGRES_IMAGE_TAG}" postgresql-image
|
||||
|
||||
helm dependency update .
|
||||
mkdir -p "${RUNNER_TEMP}/packages"
|
||||
helm package . --destination "${RUNNER_TEMP}/packages" \
|
||||
--version "${chart_version}" --app-version latest
|
||||
package="${RUNNER_TEMP}/packages/gramps-web-${chart_version}.tgz"
|
||||
test -f "${package}"
|
||||
helm template gramps "${package}" --namespace gramps \
|
||||
--values values-arschrock.yaml > "${RUNNER_TEMP}/rendered.yaml"
|
||||
|
||||
printf '%s' "${QUAY_TOKEN}" | docker login registry.brunner.ninja \
|
||||
--username "${QUAY_USERNAME}" --password-stdin
|
||||
printf '%s' "${QUAY_TOKEN}" | helm registry login registry.brunner.ninja \
|
||||
--username "${QUAY_USERNAME}" --password-stdin
|
||||
docker push "${POSTGRES_IMAGE}:${POSTGRES_IMAGE_TAG}"
|
||||
helm push "${package}" "${CHART_REGISTRY}"
|
||||
|
||||
deploy:
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
needs: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install pinned clients
|
||||
run: |
|
||||
curl -fsSL -o "${RUNNER_TEMP}/helm.tgz" "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
printf '%s %s\n' "${HELM_SHA256}" "${RUNNER_TEMP}/helm.tgz" | sha256sum --check
|
||||
tar -xzf "${RUNNER_TEMP}/helm.tgz" -C "${RUNNER_TEMP}" --strip-components=1 linux-amd64/helm
|
||||
curl -fsSL -o "${RUNNER_TEMP}/kubectl" "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
|
||||
curl -fsSL -o "${RUNNER_TEMP}/kubectl.sha256" "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256"
|
||||
printf '%s %s\n' "$(cat "${RUNNER_TEMP}/kubectl.sha256")" "${RUNNER_TEMP}/kubectl" | sha256sum --check
|
||||
chmod 0700 "${RUNNER_TEMP}/helm" "${RUNNER_TEMP}/kubectl"
|
||||
echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}"
|
||||
- name: Configure registry and Kubernetes access
|
||||
env:
|
||||
KUBE_CONFIG_BASE64: ${{ secrets.KUBE_CONFIG_BASE64 }}
|
||||
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
|
||||
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}
|
||||
run: |
|
||||
test -n "${KUBE_CONFIG_BASE64}"
|
||||
printf '%s' "${KUBE_CONFIG_BASE64}" | base64 --decode > "${RUNNER_TEMP}/kubeconfig"
|
||||
chmod 0600 "${RUNNER_TEMP}/kubeconfig"
|
||||
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
|
||||
kubectl config set-cluster cluster --server="${KUBERNETES_API}" --tls-server-name="${KUBERNETES_TLS_SERVER_NAME}"
|
||||
printf '%s' "${QUAY_TOKEN}" | helm registry login registry.brunner.ninja \
|
||||
--username "${QUAY_USERNAME}" --password-stdin
|
||||
- name: Deploy the published OCI chart
|
||||
env:
|
||||
COMMIT_SHA: ${{ gitea.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export KUBECONFIG="${RUNNER_TEMP}/kubeconfig"
|
||||
chart_version="0.0.0-dev.sha${COMMIT_SHA:0:12}"
|
||||
CHART_VERSION="${chart_version}" ./deploy.sh
|
||||
kubectl --namespace gramps rollout restart deployment/grampsweb deployment/grampsweb-celery
|
||||
kubectl --namespace gramps rollout status deployment/grampsweb --timeout=10m
|
||||
kubectl --namespace gramps rollout status deployment/grampsweb-celery --timeout=10m
|
||||
kubectl --namespace gramps rollout status statefulset/postgresql --timeout=10m
|
||||
kubectl --namespace gramps rollout status statefulset/valkey-primary --timeout=10m
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
charts/
|
||||
*.tgz
|
||||
.rendered/
|
||||
# Local migration leftovers and the old nested Bitnami checkout are not chart sources.
|
||||
postgresql/
|
||||
valkey/
|
||||
gramps.yaml
|
||||
values-local.yaml
|
||||
secrets*.yaml
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.git/
|
||||
.gitea/
|
||||
.idea/
|
||||
.vscode/
|
||||
postgresql/
|
||||
valkey/
|
||||
*.tmp
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
version: 18.8.17
|
||||
- name: valkey
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
version: 6.2.19
|
||||
digest: sha256:d1e1f89668c809ac8c7df096f92bbc7439b3ee40ec57b1cde1f929e2d5ef2c9e
|
||||
generated: "2026-09-06T08:59:30.91876833+02:00"
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
apiVersion: v2
|
||||
name: gramps-web
|
||||
description: A reusable Helm chart for Gramps Web with optional PostgreSQL and Valkey
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: latest
|
||||
home: https://www.grampsweb.org/
|
||||
sources:
|
||||
- https://github.com/gramps-project/gramps-web
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
version: ^18.0.0
|
||||
condition: postgresql.enabled
|
||||
- name: valkey
|
||||
repository: oci://registry-1.docker.io/bitnamicharts
|
||||
version: ^6.0.0
|
||||
condition: valkey.enabled
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Existing deployment migration
|
||||
|
||||
The live PostgreSQL and Valkey instances are already Helm releases. The goal is
|
||||
to adopt their resources into one `gramps` Helm release without replacing the
|
||||
PostgreSQL PVC.
|
||||
|
||||
1. Run `./backup.sh` and verify the gzip file.
|
||||
2. Run `./bootstrap-secrets.sh`. This separates live credentials into runtime,
|
||||
PostgreSQL, and Valkey Secrets without printing them.
|
||||
3. Run `./preflight.sh`. It verifies the required Secret keys, bound PVCs,
|
||||
StatefulSet immutable fields, and a server-side Helm dry run.
|
||||
4. Install the published OCI package with `./deploy.sh`. The script uses
|
||||
`--take-ownership`; the rendered StatefulSets deliberately retain the old
|
||||
names, selectors, services, and volume-claim-template names.
|
||||
5. Verify PostgreSQL, Gramps Web, Celery, OIDC, media, and the public URL.
|
||||
6. Back up the old Helm release Secrets, then delete only the release metadata
|
||||
Secrets named `sh.helm.release.v1.postgresql.*` and
|
||||
`sh.helm.release.v1.valkey.*`. Do not uninstall the old releases.
|
||||
|
||||
PostgreSQL 18 to a newer 18.x image is a minor upgrade. PostgreSQL 18 to 19 is
|
||||
a major upgrade and requires a separately planned `pg_upgrade` or dump/restore.
|
||||
@@ -1,2 +1,221 @@
|
||||
# gramps-kubernetes
|
||||
# Gramps Web Helm chart
|
||||
|
||||
This repository packages Gramps Web as a reusable Helm chart. PostgreSQL and
|
||||
Valkey are enabled by default through the Bitnami subcharts, but either service
|
||||
can be disabled and replaced independently. OIDC, S3 media storage, SMTP,
|
||||
workers, persistence, ingress, images, resources, and scheduling are optional
|
||||
or configurable.
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
helm install family oci://registry.brunner.ninja/feedc0de/charts/gramps-web \
|
||||
--namespace gramps-web --create-namespace
|
||||
```
|
||||
|
||||
The default installation creates random PostgreSQL, Valkey, and application
|
||||
secrets. Helm and the subcharts preserve those values across normal upgrades.
|
||||
Do not use `--set ...password=...` in production: command history and Helm
|
||||
release values can retain it. Create a Secret and use an `existingSecret`
|
||||
setting instead.
|
||||
|
||||
## Data service choices
|
||||
|
||||
Bundled PostgreSQL and Valkey are independent:
|
||||
|
||||
```yaml
|
||||
postgresql:
|
||||
enabled: true
|
||||
valkey:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
For external services:
|
||||
|
||||
```yaml
|
||||
postgresql:
|
||||
enabled: false
|
||||
database:
|
||||
host: postgres.database.svc
|
||||
port: 5432
|
||||
username: gramps
|
||||
auth:
|
||||
existingSecret: gramps-database
|
||||
existingSecretKey: password
|
||||
|
||||
valkey:
|
||||
enabled: false
|
||||
cache:
|
||||
host: valkey.cache.svc
|
||||
port: 6379
|
||||
auth:
|
||||
existingSecret: gramps-valkey
|
||||
existingSecretKey: valkey-password
|
||||
```
|
||||
|
||||
The bundled charts remain fully configurable under `postgresql.*` and
|
||||
`valkey.*`. For example, a locale-capable PostgreSQL image can be selected
|
||||
without changing this chart:
|
||||
|
||||
```yaml
|
||||
global:
|
||||
security:
|
||||
allowInsecureImages: true
|
||||
postgresql:
|
||||
image:
|
||||
registry: registry.example.com
|
||||
repository: platform/postgres-with-locales
|
||||
tag: 18-locales
|
||||
```
|
||||
|
||||
Locales are supplied by the container image, not generated by Helm. The
|
||||
included arschrock image recipe generates `de_AT.UTF-8`, `de_DE.UTF-8`,
|
||||
`en_GB.UTF-8`, and `en_US.UTF-8`; its `LOCALES` build argument can be extended
|
||||
for other browser locales.
|
||||
|
||||
PostgreSQL chart upgrades update Kubernetes resources and the image. They do
|
||||
not convert a data directory between PostgreSQL major versions. Stay on the
|
||||
same PostgreSQL major for routine upgrades; use `pg_upgrade` or dump/restore
|
||||
for a major upgrade.
|
||||
|
||||
## OIDC / Authentik
|
||||
|
||||
OIDC is off by default. A provider such as Authentik can be enabled with:
|
||||
|
||||
```yaml
|
||||
oidc:
|
||||
enabled: true
|
||||
issuer: https://auth.example.com/application/o/gramps/
|
||||
clientId: gramps-web
|
||||
clientSecret:
|
||||
existingSecret: gramps-oidc
|
||||
existingSecretKey: client-secret
|
||||
name: Company login
|
||||
disableLocalAuth: true
|
||||
autoRedirect: true
|
||||
groups:
|
||||
admin: Gramps Admins
|
||||
editor: Gramps Editors
|
||||
```
|
||||
|
||||
Keep local authentication enabled until OIDC login and role mapping have been
|
||||
tested. Gramps supports custom OIDC providers, including Authentik, and can map
|
||||
OIDC groups to its roles.
|
||||
|
||||
## Replicas
|
||||
|
||||
`replicaCount` and `worker.replicaCount` are configurable. Multiple web pods
|
||||
should only be attempted when tree data, user data, the search index, media,
|
||||
cache/broker, and application secret are all shared. The safer first scaling
|
||||
step is setting `GUNICORN_NUM_WORKERS` through `config.extraEnv`; upstream
|
||||
documents multiple Gunicorn workers, while it does not document Kubernetes
|
||||
web replicas as a supported topology. The chart therefore defaults to one web
|
||||
pod.
|
||||
|
||||
## Health checks and metrics
|
||||
|
||||
Gramps Web exposes `/ready`, but upstream currently implements it as a shallow
|
||||
HTTP response rather than a dependency check. The chart therefore uses:
|
||||
|
||||
- `/ready` for startup and liveness, so a temporary dependency outage does not
|
||||
create a web-pod restart loop;
|
||||
- a deep web readiness probe that checks `/ready`, runs an authenticated
|
||||
PostgreSQL `SELECT 1`, and performs an authenticated Valkey `PING`;
|
||||
- a targeted Celery self-ping for worker startup, readiness, and liveness;
|
||||
- the PostgreSQL and Valkey subcharts' native startup, readiness, and liveness
|
||||
probes.
|
||||
|
||||
The probe blocks traffic when the core API/database/cache path is broken, but
|
||||
it deliberately does not make pod health depend on Authentik, SMTP, or S3.
|
||||
Those should be monitored with external synthetic checks.
|
||||
|
||||
Gramps Web does not currently expose Prometheus application metrics. The
|
||||
bundled data-service charts do have optional exporters and ServiceMonitors:
|
||||
|
||||
```yaml
|
||||
postgresql:
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
valkey:
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
They remain disabled by default to avoid imposing exporter CPU/memory requests
|
||||
or Prometheus Operator CRDs on chart users.
|
||||
|
||||
## Persistence and upgrades
|
||||
|
||||
The application PVC has a `helm.sh/resource-policy: keep` annotation by
|
||||
default. PostgreSQL and Valkey persistence are controlled by their subchart
|
||||
values. Back up PostgreSQL before any image or chart upgrade:
|
||||
|
||||
```sh
|
||||
./backup.sh
|
||||
```
|
||||
|
||||
The application image defaults to `ghcr.io/gramps-project/grampsweb:latest`
|
||||
with `imagePullPolicy: Always`. Set `image.tag` to a release tag if you prefer
|
||||
reproducible rollouts.
|
||||
|
||||
## arschrock deployment
|
||||
|
||||
[`values-arschrock.yaml`](values-arschrock.yaml) contains only non-secret
|
||||
cluster configuration. Existing credentials are moved by
|
||||
`bootstrap-secrets.sh` into `gramps-runtime`, `gramps-postgresql`, and
|
||||
`gramps-valkey`; none is rendered or committed.
|
||||
|
||||
Initial migration:
|
||||
|
||||
```sh
|
||||
./backup.sh
|
||||
./bootstrap-secrets.sh
|
||||
./preflight.sh
|
||||
kubectl apply -f ci-deployer.yaml
|
||||
./create-ci-kubeconfig.sh # save its output as Gitea KUBE_CONFIG_BASE64
|
||||
```
|
||||
|
||||
Also configure `QUAY_USERNAME` and `QUAY_TOKEN` in Gitea. CI validates the
|
||||
chart, builds and tests the locale-enabled PostgreSQL 18 image, publishes the
|
||||
OCI chart to `registry.brunner.ninja/feedc0de/charts/gramps-web`, and deploys
|
||||
that exact chart package to the `gramps` namespace from the registry.
|
||||
|
||||
This deployment stores Helm release metadata in ConfigMaps. The CI identity
|
||||
cannot list Secrets or read `gramps-runtime` (OIDC, S3, database URIs, and app
|
||||
key). Bitnami's templates require direct lookup access to their configured
|
||||
password Secrets, so CI can read only `gramps-postgresql`, `gramps-valkey`,
|
||||
and the legacy `postgresql` Secret that its create-or-reuse helper checks.
|
||||
|
||||
The initial Helm deployment uses `--take-ownership` and preserves the existing
|
||||
StatefulSet/PVC names. Old `postgresql` and `valkey` Helm release records must
|
||||
only be removed after the new `gramps` release is healthy; never run
|
||||
`helm uninstall postgresql` or `helm uninstall valkey` after adoption because
|
||||
their historical manifests describe the live resources.
|
||||
|
||||
Automatic Helm rollback is intentionally disabled for the first ownership
|
||||
transfer because an install rollback could delete adopted resources. It is
|
||||
enabled automatically for every later upgrade.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
./test.sh
|
||||
./render.sh
|
||||
```
|
||||
|
||||
`Chart.lock` records the last-tested dependency packages. `Chart.yaml` uses
|
||||
compatible major-version ranges, and CI runs `helm dependency update` when it
|
||||
packages a release. This allows current subchart releases while avoiding an
|
||||
unreviewed breaking subchart-major jump.
|
||||
|
||||
Upstream references:
|
||||
|
||||
- [Gramps Web configuration](https://github.com/gramps-project/gramps-web-docs/blob/main/docs/en/install_setup/configuration.md)
|
||||
- [Gramps Web OIDC configuration](https://github.com/gramps-project/gramps-web-docs/blob/main/docs/en/install_setup/oidc.md)
|
||||
- [Gramps Web PostgreSQL and locale guidance](https://github.com/gramps-project/gramps-web-docs/blob/main/docs/en/install_setup/postgres.md)
|
||||
- [Bitnami PostgreSQL chart](https://github.com/bitnami/charts/tree/main/bitnami/postgresql)
|
||||
- [Bitnami Valkey chart](https://github.com/bitnami/charts/tree/main/bitnami/valkey)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
namespace=${NAMESPACE:-gramps}
|
||||
pod=${POSTGRES_POD:-postgresql-0}
|
||||
destination=${1:-"/tmp/gramps-postgresql-$(date +%Y%m%d-%H%M%S).sql.gz"}
|
||||
umask 077
|
||||
|
||||
kubectl --namespace "${namespace}" exec "${pod}" -- bash -ec \
|
||||
'PGPASSWORD="$(cat "${POSTGRES_PASSWORD_FILE}")" pg_dumpall --username=postgres' \
|
||||
| gzip -9 > "${destination}"
|
||||
gzip --test "${destination}"
|
||||
test -s "${destination}"
|
||||
echo "PostgreSQL backup written to ${destination} (mode 0600)."
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# One-time helper for the existing arschrock deployment. It copies credentials
|
||||
# from live resources into one Kubernetes Secret without printing them.
|
||||
set -euo pipefail
|
||||
|
||||
namespace=${NAMESPACE:-gramps}
|
||||
workdir=$(mktemp --directory)
|
||||
trap 'rm -rf "${workdir}"' EXIT HUP INT TERM
|
||||
chmod 0700 "${workdir}"
|
||||
|
||||
extract_env() {
|
||||
local deployment=$1 name=$2 destination=$3
|
||||
kubectl --namespace "${namespace}" get deployment "${deployment}" --output=json \
|
||||
| jq --raw-output --join-output --arg name "${name}" \
|
||||
'.spec.template.spec.containers[0].env[] | select(.name == $name) | .value' \
|
||||
> "${destination}"
|
||||
test -s "${destination}"
|
||||
}
|
||||
|
||||
extract_secret() {
|
||||
local source_secret=$1 key=$2 destination=$3
|
||||
kubectl --namespace "${namespace}" get secret "${source_secret}" --output=json \
|
||||
| jq --raw-output --join-output --arg key "${key}" '.data[$key]' \
|
||||
| base64 --decode > "${destination}"
|
||||
test -s "${destination}"
|
||||
}
|
||||
|
||||
extract_env grampsweb-celery GRAMPSWEB_SECRET_KEY "${workdir}/app-secret-key" \
|
||||
|| openssl rand -hex 32 > "${workdir}/app-secret-key"
|
||||
extract_env grampsweb GRAMPSWEB_POSTGRES_PASSWORD "${workdir}/password"
|
||||
extract_env grampsweb GRAMPSWEB_USER_DB_URI "${workdir}/user-database-uri"
|
||||
extract_env grampsweb GRAMPSWEB_SEARCH_INDEX_DB_URI "${workdir}/search-index-database-uri"
|
||||
extract_env grampsweb GRAMPSWEB_OIDC_CLIENT_SECRET "${workdir}/oidc-client-secret"
|
||||
extract_env grampsweb AWS_ACCESS_KEY_ID "${workdir}/aws-access-key-id"
|
||||
extract_env grampsweb AWS_SECRET_ACCESS_KEY "${workdir}/aws-secret-access-key"
|
||||
extract_secret postgresql postgres-password "${workdir}/postgres-password"
|
||||
extract_secret valkey valkey-password "${workdir}/valkey-password"
|
||||
|
||||
kubectl --namespace "${namespace}" create secret generic gramps-runtime \
|
||||
--from-file="${workdir}/app-secret-key" \
|
||||
--from-file="${workdir}/user-database-uri" \
|
||||
--from-file="${workdir}/search-index-database-uri" \
|
||||
--from-file="${workdir}/oidc-client-secret" \
|
||||
--from-file="${workdir}/aws-access-key-id" \
|
||||
--from-file="${workdir}/aws-secret-access-key" \
|
||||
--dry-run=client --output=yaml \
|
||||
| kubectl apply --filename=-
|
||||
kubectl --namespace "${namespace}" create secret generic gramps-postgresql \
|
||||
--from-file="${workdir}/postgres-password" \
|
||||
--from-file="${workdir}/password" \
|
||||
--dry-run=client --output=yaml \
|
||||
| kubectl apply --filename=-
|
||||
kubectl --namespace "${namespace}" create secret generic gramps-valkey \
|
||||
--from-file="${workdir}/valkey-password" \
|
||||
--dry-run=client --output=yaml \
|
||||
| kubectl apply --filename=-
|
||||
|
||||
echo "Runtime, PostgreSQL, and Valkey Secrets are ready; no values were printed."
|
||||
@@ -0,0 +1,56 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: gramps-deployer
|
||||
namespace: gramps
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: gramps-deployer
|
||||
namespace: gramps
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps", "endpoints", "persistentvolumeclaims", "pods", "serviceaccounts", "services"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
# The upstream subcharts look up these two existing Secrets while rendering.
|
||||
# CI cannot list Secrets or read gramps-runtime (OIDC, S3, and app credentials).
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["gramps-postgresql", "gramps-valkey", "postgresql"]
|
||||
verbs: ["get"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments", "replicasets", "statefulsets"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses", "networkpolicies"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: ["policy"]
|
||||
resources: ["poddisruptionbudgets"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: gramps-deployer
|
||||
namespace: gramps
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: gramps-deployer
|
||||
namespace: gramps
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: gramps-deployer
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: gramps-deployer-token
|
||||
namespace: gramps
|
||||
annotations:
|
||||
kubernetes.io/service-account.name: gramps-deployer
|
||||
type: kubernetes.io/service-account-token
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
namespace=gramps
|
||||
account=gramps-deployer
|
||||
secret=gramps-deployer-token
|
||||
|
||||
kubectl apply --filename "${project_dir}/ci-deployer.yaml" >&2
|
||||
for attempt in {1..30}; do
|
||||
token=$(kubectl --namespace "${namespace}" get secret "${secret}" \
|
||||
--output=jsonpath='{.data.token}' 2>/dev/null || true)
|
||||
[[ -n "${token}" ]] && break
|
||||
sleep 1
|
||||
done
|
||||
[[ -n "${token:-}" ]] || { echo "Token was not issued" >&2; exit 1; }
|
||||
|
||||
workdir=$(mktemp --directory)
|
||||
trap 'rm -rf "${workdir}"' EXIT HUP INT TERM
|
||||
server=$(kubectl config view --minify --output=jsonpath='{.clusters[0].cluster.server}')
|
||||
kubectl --namespace "${namespace}" get secret "${secret}" \
|
||||
--output=jsonpath='{.data.ca\.crt}' | base64 --decode > "${workdir}/ca.crt"
|
||||
|
||||
export KUBECONFIG="${workdir}/config"
|
||||
kubectl config set-cluster cluster --server="${server}" \
|
||||
--certificate-authority="${workdir}/ca.crt" --embed-certs=true >/dev/null
|
||||
kubectl config set-credentials "${account}" \
|
||||
--token="$(printf '%s' "${token}" | base64 --decode)" >/dev/null
|
||||
kubectl config set-context gramps --cluster=cluster --user="${account}" \
|
||||
--namespace="${namespace}" >/dev/null
|
||||
kubectl config use-context gramps >/dev/null
|
||||
|
||||
echo "Store the next line as the Gitea Actions secret KUBE_CONFIG_BASE64; do not commit it." >&2
|
||||
base64 --wrap=0 "${KUBECONFIG}"
|
||||
printf '\n'
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export HELM_DRIVER=${HELM_DRIVER:-configmap}
|
||||
|
||||
registry=${CHART_REGISTRY:-oci://registry.brunner.ninja/feedc0de/charts}
|
||||
version=${CHART_VERSION:?Set CHART_VERSION to the published chart version}
|
||||
namespace=${NAMESPACE:-gramps}
|
||||
release=${RELEASE:-gramps}
|
||||
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
helm_args=(
|
||||
upgrade --install "${release}" "${registry}/gramps-web"
|
||||
--version "${version}"
|
||||
--namespace "${namespace}" --create-namespace
|
||||
--values "${project_dir}/values-arschrock.yaml"
|
||||
--take-ownership --wait --timeout 20m
|
||||
)
|
||||
|
||||
# Never auto-uninstall resources during the initial ownership transfer. Once
|
||||
# the parent release exists, normal upgrades may safely roll back to it.
|
||||
if helm status "${release}" --namespace "${namespace}" 2>/dev/null \
|
||||
| grep -q '^STATUS: deployed$'; then
|
||||
helm_args+=(--rollback-on-failure)
|
||||
fi
|
||||
helm "${helm_args[@]}"
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
release=${RELEASE:-gramps-web}
|
||||
namespace=${NAMESPACE:-gramps-web}
|
||||
values=${VALUES_FILE:-"${project_dir}/values.yaml"}
|
||||
|
||||
dependencies=("${project_dir}"/charts/*.tgz)
|
||||
if (( ${#dependencies[@]} < 2 )); then
|
||||
helm dependency build --skip-refresh "${project_dir}"
|
||||
fi
|
||||
helm upgrade --install "${release}" "${project_dir}" \
|
||||
--namespace "${namespace}" --create-namespace \
|
||||
--values "${values}" \
|
||||
--rollback-on-failure --wait --timeout 15m
|
||||
@@ -0,0 +1,14 @@
|
||||
ARG BASE_IMAGE=docker.io/bitnami/postgresql:latest
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
USER 0
|
||||
ARG LOCALES="de_AT de_DE en_GB en_US"
|
||||
RUN for locale_name in ${LOCALES}; do \
|
||||
localedef --force --inputfile="${locale_name}" --charmap=UTF-8 "${locale_name}.UTF-8"; \
|
||||
done \
|
||||
&& tdnf clean all
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US:en
|
||||
|
||||
USER 1001
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
export HELM_DRIVER=${HELM_DRIVER:-configmap}
|
||||
|
||||
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
workdir=$(mktemp --directory)
|
||||
trap 'rm -rf "${workdir}"' EXIT HUP INT TERM
|
||||
namespace=${NAMESPACE:-gramps}
|
||||
|
||||
for key in app-secret-key user-database-uri search-index-database-uri \
|
||||
oidc-client-secret aws-access-key-id aws-secret-access-key; do
|
||||
kubectl --namespace "${namespace}" get secret gramps-runtime \
|
||||
--output="jsonpath={.data.${key}}" | grep -q . \
|
||||
|| { echo "gramps-runtime is missing ${key}" >&2; exit 1; }
|
||||
done
|
||||
for item in gramps-postgresql:postgres-password gramps-postgresql:password \
|
||||
gramps-valkey:valkey-password; do
|
||||
secret=${item%%:*}
|
||||
key=${item#*:}
|
||||
kubectl --namespace "${namespace}" get secret "${secret}" \
|
||||
--output="jsonpath={.data.${key}}" | grep -q . \
|
||||
|| { echo "${secret} is missing ${key}" >&2; exit 1; }
|
||||
done
|
||||
|
||||
for claim in data-postgresql-0 grampsweb-data; do
|
||||
phase=$(kubectl --namespace "${namespace}" get pvc "${claim}" \
|
||||
--output=jsonpath='{.status.phase}')
|
||||
[[ "${phase}" == Bound ]] || { echo "PVC ${claim} is ${phase}" >&2; exit 1; }
|
||||
done
|
||||
|
||||
dependencies=("${project_dir}"/charts/*.tgz)
|
||||
if (( ${#dependencies[@]} < 2 )); then
|
||||
helm dependency build --skip-refresh "${project_dir}"
|
||||
fi
|
||||
helm template gramps "${project_dir}" --namespace "${namespace}" \
|
||||
--values "${project_dir}/values-arschrock.yaml" > "${workdir}/rendered.yaml"
|
||||
kubectl --namespace "${namespace}" get statefulset postgresql valkey-primary valkey-replicas \
|
||||
--output=json > "${workdir}/live.json"
|
||||
|
||||
python3 - "${workdir}/rendered.yaml" "${workdir}/live.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
rendered = {
|
||||
item["metadata"]["name"]: item
|
||||
for item in yaml.safe_load_all(open(sys.argv[1], encoding="utf-8"))
|
||||
if item and item.get("kind") == "StatefulSet"
|
||||
}
|
||||
live = {
|
||||
item["metadata"]["name"]: item
|
||||
for item in json.load(open(sys.argv[2], encoding="utf-8"))["items"]
|
||||
}
|
||||
for name in ("postgresql", "valkey-primary", "valkey-replicas"):
|
||||
expected = rendered[name]["spec"]
|
||||
current = live[name]["spec"]
|
||||
checks = {
|
||||
"selector": (expected["selector"], current["selector"]),
|
||||
"serviceName": (expected["serviceName"], current["serviceName"]),
|
||||
"volumeClaimTemplates": (
|
||||
[item["metadata"]["name"] for item in expected.get("volumeClaimTemplates", [])],
|
||||
[item["metadata"]["name"] for item in current.get("volumeClaimTemplates", [])],
|
||||
),
|
||||
}
|
||||
for field, (wanted, found) in checks.items():
|
||||
if wanted != found:
|
||||
raise SystemExit(f"{name}: immutable {field} differs: wanted={wanted!r} live={found!r}")
|
||||
print("Live StatefulSet immutable fields match the parent chart.")
|
||||
PY
|
||||
|
||||
helm upgrade --install gramps "${project_dir}" \
|
||||
--namespace "${namespace}" \
|
||||
--values "${project_dir}/values-arschrock.yaml" \
|
||||
--take-ownership --dry-run=server --hide-secret
|
||||
|
||||
echo "Migration preflight passed."
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
output=${1:-"${project_dir}/.rendered/gramps-web.yaml"}
|
||||
values=${2:-"${project_dir}/values.yaml"}
|
||||
|
||||
mkdir -p "$(dirname -- "${output}")"
|
||||
dependencies=("${project_dir}"/charts/*.tgz)
|
||||
if (( ${#dependencies[@]} < 2 )); then
|
||||
helm dependency build --skip-refresh "${project_dir}" >/dev/null
|
||||
fi
|
||||
helm template gramps-web "${project_dir}" \
|
||||
--namespace gramps-web \
|
||||
--values "${values}" > "${output}"
|
||||
echo "Rendered ${output}"
|
||||
@@ -0,0 +1,15 @@
|
||||
Gramps Web is installed as {{ include "gramps-web.fullname" . }}.
|
||||
|
||||
{{- if .Values.ingress.enabled }}
|
||||
Open {{ (first .Values.ingress.hosts).host }} in your browser.
|
||||
{{- else }}
|
||||
Run:
|
||||
kubectl -n {{ .Release.Namespace }} port-forward service/{{ include "gramps-web.fullname" . }} 5000:{{ .Values.service.port }}
|
||||
Then open http://localhost:5000.
|
||||
{{- end }}
|
||||
|
||||
{{- if or .Values.database.auth.password .Values.cache.auth.password .Values.oidc.clientSecret.value .Values.s3.auth.secretAccessKey .Values.email.auth.password }}
|
||||
WARNING: one or more plaintext secret values were supplied to Helm. Prefer the
|
||||
corresponding existingSecret settings so credentials are not retained in Helm values.
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
{{- define "gramps-web.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.workerName" -}}
|
||||
{{- default (printf "%s-worker" (include "gramps-web.fullname" .)) .Values.worker.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.labels" -}}
|
||||
helm.sh/chart: {{ include "gramps-web.chart" . }}
|
||||
app.kubernetes.io/name: {{ include "gramps-web.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "gramps-web.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.appSecretName" -}}
|
||||
{{- default (include "gramps-web.fullname" .) .Values.appSecret.existingSecret }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.postgresqlHost" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- if .Values.postgresql.fullnameOverride }}{{ .Values.postgresql.fullnameOverride }}{{ else }}{{ printf "%s-postgresql" .Release.Name }}{{ end }}
|
||||
{{- else }}
|
||||
{{- required "database.host is required when postgresql.enabled=false" .Values.database.host }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.postgresqlSecretName" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- if .Values.postgresql.auth.existingSecret }}{{ .Values.postgresql.auth.existingSecret }}{{ else if .Values.postgresql.fullnameOverride }}{{ .Values.postgresql.fullnameOverride }}{{ else }}{{ printf "%s-postgresql" .Release.Name }}{{ end }}
|
||||
{{- else }}
|
||||
{{- required "database.auth.existingSecret is required when an external database password is not supplied" .Values.database.auth.existingSecret }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.postgresqlSecretKey" -}}
|
||||
{{- if .Values.postgresql.enabled }}
|
||||
{{- default "password" .Values.postgresql.auth.secretKeys.userPasswordKey }}
|
||||
{{- else }}
|
||||
{{- default "password" .Values.database.auth.existingSecretKey }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.valkeyHost" -}}
|
||||
{{- if .Values.valkey.enabled }}
|
||||
{{- if .Values.valkey.fullnameOverride }}{{ .Values.valkey.fullnameOverride }}-primary{{ else }}{{ printf "%s-valkey-primary" .Release.Name }}{{ end }}
|
||||
{{- else }}
|
||||
{{- required "cache.host is required when valkey.enabled=false" .Values.cache.host }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.valkeySecretName" -}}
|
||||
{{- if .Values.valkey.enabled }}
|
||||
{{- if .Values.valkey.auth.existingSecret }}{{ .Values.valkey.auth.existingSecret }}{{ else if .Values.valkey.fullnameOverride }}{{ .Values.valkey.fullnameOverride }}{{ else }}{{ printf "%s-valkey" .Release.Name }}{{ end }}
|
||||
{{- else }}
|
||||
{{- required "cache.auth.existingSecret is required when an external Valkey password is not supplied" .Values.cache.auth.existingSecret }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.valkeySecretKey" -}}
|
||||
{{- if .Values.valkey.enabled }}{{ default "valkey-password" .Values.valkey.auth.existingSecretPasswordKey }}{{ else }}{{ default "valkey-password" .Values.cache.auth.existingSecretKey }}{{ end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
{{- define "gramps-web.env" -}}
|
||||
- name: TZ
|
||||
value: {{ .Values.config.timezone | quote }}
|
||||
- name: GRAMPSWEB_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "gramps-web.appSecretName" . }}
|
||||
key: {{ ternary .Values.appSecret.existingSecretKey "secret-key" (ne .Values.appSecret.existingSecret "") }}
|
||||
- name: GRAMPSWEB_TREE
|
||||
value: {{ required "config.tree is required" .Values.config.tree | quote }}
|
||||
- name: GRAMPSWEB_BASE_URL
|
||||
value: {{ required "config.baseUrl is required" .Values.config.baseUrl | quote }}
|
||||
- name: GRAMPSWEB_DB_BACKEND
|
||||
value: {{ .Values.config.databaseBackend | quote }}
|
||||
- name: GRAMPSWEB_POSTGRES_HOST
|
||||
value: {{ include "gramps-web.postgresqlHost" . | quote }}
|
||||
- name: GRAMPSWEB_POSTGRES_PORT
|
||||
value: {{ .Values.database.port | quote }}
|
||||
- name: GRAMPSWEB_POSTGRES_USER
|
||||
value: {{ ternary .Values.postgresql.auth.username .Values.database.username .Values.postgresql.enabled | quote }}
|
||||
- name: GRAMPSWEB_POSTGRES_PASSWORD
|
||||
{{- if and (not .Values.postgresql.enabled) .Values.database.auth.password }}
|
||||
value: {{ .Values.database.auth.password | quote }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "gramps-web.postgresqlSecretName" . }}
|
||||
key: {{ include "gramps-web.postgresqlSecretKey" . }}
|
||||
{{- end }}
|
||||
- name: PROBE_POSTGRES_DATABASE
|
||||
value: {{ ternary .Values.postgresql.auth.database .Values.database.database .Values.postgresql.enabled | quote }}
|
||||
- name: PROBE_POSTGRES_ENABLED
|
||||
value: {{ has .Values.config.databaseBackend (list "postgresql" "sharedpostgresql") | quote }}
|
||||
- name: GRAMPSWEB_USER_DB_URI
|
||||
{{- if .Values.config.userDatabaseUriSecret.existingSecret }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.config.userDatabaseUriSecret.existingSecret }}
|
||||
key: {{ .Values.config.userDatabaseUriSecret.key }}
|
||||
{{- else }}
|
||||
value: {{ .Values.config.userDatabaseUri | quote }}
|
||||
{{- end }}
|
||||
- name: GRAMPSWEB_SEARCH_INDEX_DB_URI
|
||||
{{- if .Values.config.searchIndexDatabaseUriSecret.existingSecret }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.config.searchIndexDatabaseUriSecret.existingSecret }}
|
||||
key: {{ .Values.config.searchIndexDatabaseUriSecret.key }}
|
||||
{{- else }}
|
||||
value: {{ .Values.config.searchIndexDatabaseUri | quote }}
|
||||
{{- end }}
|
||||
- name: VALKEY_PASSWORD
|
||||
{{- if and (not .Values.valkey.enabled) (not .Values.cache.auth.enabled) }}
|
||||
value: ""
|
||||
{{- else }}
|
||||
{{- if and (not .Values.valkey.enabled) .Values.cache.auth.password }}
|
||||
value: {{ .Values.cache.auth.password | quote }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "gramps-web.valkeySecretName" . }}
|
||||
key: {{ include "gramps-web.valkeySecretKey" . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
- name: GRAMPSWEB_CELERY_CONFIG__broker_url
|
||||
value: {{ printf "%s%s:%v/%v" (ternary "redis://:$(VALKEY_PASSWORD)@" "redis://" (or .Values.valkey.enabled .Values.cache.auth.enabled)) (include "gramps-web.valkeyHost" .) .Values.cache.port .Values.cache.brokerDatabase | quote }}
|
||||
- name: GRAMPSWEB_CELERY_CONFIG__result_backend
|
||||
value: {{ printf "%s%s:%v/%v" (ternary "redis://:$(VALKEY_PASSWORD)@" "redis://" (or .Values.valkey.enabled .Values.cache.auth.enabled)) (include "gramps-web.valkeyHost" .) .Values.cache.port .Values.cache.resultDatabase | quote }}
|
||||
- name: GRAMPSWEB_RATELIMIT_STORAGE_URI
|
||||
value: {{ printf "%s%s:%v/%v" (ternary "redis://:$(VALKEY_PASSWORD)@" "redis://" (or .Values.valkey.enabled .Values.cache.auth.enabled)) (include "gramps-web.valkeyHost" .) .Values.cache.port .Values.cache.rateLimitDatabase | quote }}
|
||||
{{- if .Values.oidc.enabled }}
|
||||
- name: GRAMPSWEB_OIDC_ENABLED
|
||||
value: "true"
|
||||
- name: GRAMPSWEB_OIDC_ISSUER
|
||||
value: {{ required "oidc.issuer is required when OIDC is enabled" .Values.oidc.issuer | quote }}
|
||||
- name: GRAMPSWEB_OIDC_CLIENT_ID
|
||||
value: {{ required "oidc.clientId is required when OIDC is enabled" .Values.oidc.clientId | quote }}
|
||||
- name: GRAMPSWEB_OIDC_CLIENT_SECRET
|
||||
{{- if .Values.oidc.clientSecret.existingSecret }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.oidc.clientSecret.existingSecret }}
|
||||
key: {{ .Values.oidc.clientSecret.existingSecretKey }}
|
||||
{{- else }}
|
||||
value: {{ required "oidc.clientSecret.existingSecret or oidc.clientSecret.value is required when OIDC is enabled" .Values.oidc.clientSecret.value | quote }}
|
||||
{{- end }}
|
||||
- name: GRAMPSWEB_OIDC_NAME
|
||||
value: {{ .Values.oidc.name | quote }}
|
||||
- name: GRAMPSWEB_OIDC_SCOPES
|
||||
value: {{ .Values.oidc.scopes | quote }}
|
||||
- name: GRAMPSWEB_OIDC_USERNAME_CLAIM
|
||||
value: {{ .Values.oidc.usernameClaim | quote }}
|
||||
- name: GRAMPSWEB_OIDC_DISABLE_LOCAL_AUTH
|
||||
value: {{ .Values.oidc.disableLocalAuth | quote }}
|
||||
- name: GRAMPSWEB_OIDC_AUTO_REDIRECT
|
||||
value: {{ .Values.oidc.autoRedirect | quote }}
|
||||
- name: GRAMPSWEB_OIDC_ROLE_CLAIM
|
||||
value: {{ .Values.oidc.roleClaim | quote }}
|
||||
{{- with .Values.oidc.openidConfigUrl }}
|
||||
- name: GRAMPSWEB_OIDC_OPENID_CONFIG_URL
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- range $role, $group := .Values.oidc.groups }}
|
||||
{{- if $group }}
|
||||
- name: {{ printf "GRAMPSWEB_OIDC_GROUP_%s" ($role | upper) }}
|
||||
value: {{ $group | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
- name: GRAMPSWEB_OIDC_ENABLED
|
||||
value: "false"
|
||||
{{- end }}
|
||||
{{- if .Values.s3.enabled }}
|
||||
- name: GRAMPSWEB_MEDIA_BASE_DIR
|
||||
value: {{ required "s3.mediaBaseDir is required when S3 is enabled" .Values.s3.mediaBaseDir | quote }}
|
||||
- name: AWS_DEFAULT_REGION
|
||||
value: {{ required "s3.region is required when S3 is enabled" .Values.s3.region | quote }}
|
||||
{{- with .Values.s3.endpointUrl }}
|
||||
- name: AWS_ENDPOINT_URL
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
- name: AWS_ACCESS_KEY_ID
|
||||
{{- if .Values.s3.auth.existingSecret }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.s3.auth.existingSecret }}
|
||||
key: {{ .Values.s3.auth.accessKeyIdKey }}
|
||||
{{- else }}
|
||||
value: {{ required "s3.auth.existingSecret or s3.auth.accessKeyId is required when S3 is enabled" .Values.s3.auth.accessKeyId | quote }}
|
||||
{{- end }}
|
||||
- name: AWS_SECRET_ACCESS_KEY
|
||||
{{- if .Values.s3.auth.existingSecret }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.s3.auth.existingSecret }}
|
||||
key: {{ .Values.s3.auth.secretAccessKeyKey }}
|
||||
{{- else }}
|
||||
value: {{ required "s3.auth.existingSecret or s3.auth.secretAccessKey is required when S3 is enabled" .Values.s3.auth.secretAccessKey | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.email.enabled }}
|
||||
- name: GRAMPSWEB_EMAIL_HOST
|
||||
value: {{ required "email.host is required when email is enabled" .Values.email.host | quote }}
|
||||
- name: GRAMPSWEB_EMAIL_PORT
|
||||
value: {{ .Values.email.port | quote }}
|
||||
- name: GRAMPSWEB_EMAIL_USE_TLS
|
||||
value: {{ .Values.email.useTls | quote }}
|
||||
- name: GRAMPSWEB_DEFAULT_FROM_EMAIL
|
||||
value: {{ .Values.email.defaultFromEmail | quote }}
|
||||
{{- with .Values.email.username }}
|
||||
- name: GRAMPSWEB_EMAIL_HOST_USER
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- if or .Values.email.auth.existingSecret .Values.email.auth.password }}
|
||||
- name: GRAMPSWEB_EMAIL_HOST_PASSWORD
|
||||
{{- if .Values.email.auth.existingSecret }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.email.auth.existingSecret }}
|
||||
key: {{ .Values.email.auth.passwordKey }}
|
||||
{{- else }}
|
||||
value: {{ .Values.email.auth.password | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.config.extraEnv }}
|
||||
{{ toYaml . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "gramps-web.volumeMounts" -}}
|
||||
- name: data
|
||||
mountPath: /app/users
|
||||
subPath: users
|
||||
- name: data
|
||||
mountPath: /app/indexdir
|
||||
subPath: indexdir
|
||||
- name: data
|
||||
mountPath: /app/thumbnail_cache
|
||||
subPath: thumbnail_cache
|
||||
- name: data
|
||||
mountPath: /app/cache
|
||||
subPath: cache
|
||||
- name: data
|
||||
mountPath: /app/secret
|
||||
subPath: secret
|
||||
- name: data
|
||||
mountPath: /root/.gramps/grampsdb
|
||||
subPath: db
|
||||
- name: data
|
||||
mountPath: /app/media
|
||||
subPath: app/media
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- end }}
|
||||
@@ -0,0 +1,114 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "gramps-web.fullname" . }}
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ include "gramps-web.fullname" . }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ include "gramps-web.fullname" . }}
|
||||
{{- include "gramps-web.labels" . | nindent 8 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
checksum/app-secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: {{ include "gramps-web.serviceAccountName" . }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- with .Values.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: gramps-web
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
env:
|
||||
{{- include "gramps-web.env" . | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5000
|
||||
protocol: TCP
|
||||
{{- if .Values.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
{{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
{{- if .Values.readinessProbe.deep }}
|
||||
exec:
|
||||
command:
|
||||
- python3
|
||||
- -c
|
||||
- |
|
||||
import os
|
||||
import urllib.request
|
||||
import psycopg2
|
||||
import redis
|
||||
with urllib.request.urlopen("http://127.0.0.1:5000/ready", timeout=3) as response:
|
||||
assert response.status == 200
|
||||
if os.environ["PROBE_POSTGRES_ENABLED"] == "true":
|
||||
connection = psycopg2.connect(
|
||||
host=os.environ["GRAMPSWEB_POSTGRES_HOST"],
|
||||
port=os.environ["GRAMPSWEB_POSTGRES_PORT"],
|
||||
user=os.environ["GRAMPSWEB_POSTGRES_USER"],
|
||||
password=os.environ["GRAMPSWEB_POSTGRES_PASSWORD"],
|
||||
dbname=os.environ["PROBE_POSTGRES_DATABASE"],
|
||||
connect_timeout=3,
|
||||
)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT 1")
|
||||
assert cursor.fetchone() == (1,)
|
||||
connection.close()
|
||||
assert redis.Redis.from_url(
|
||||
os.environ["GRAMPSWEB_RATELIMIT_STORAGE_URI"],
|
||||
socket_connect_timeout=3,
|
||||
socket_timeout=3,
|
||||
).ping()
|
||||
{{- end }}
|
||||
{{- omit .Values.readinessProbe "enabled" "deep" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.startupProbe.enabled }}
|
||||
startupProbe:
|
||||
{{- omit .Values.startupProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
{{- include "gramps-web.volumeMounts" . | nindent 12 }}
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-data" (include "gramps-web.fullname" .)) .Values.persistence.existingClaim }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,36 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ default (include "gramps-web.fullname" .) .Values.ingress.nameOverride }}
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.ingress.className }}
|
||||
ingressClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "gramps-web.fullname" $ }}
|
||||
port:
|
||||
name: http
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "gramps-web.fullname" . }}-data
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
{{- with .Values.persistence.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
accessModes:
|
||||
{{- toYaml .Values.persistence.accessModes | nindent 4 }}
|
||||
{{- with .Values.persistence.storageClass }}
|
||||
storageClassName: {{ . | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{{- if not .Values.appSecret.existingSecret }}
|
||||
{{- $name := include "gramps-web.fullname" . -}}
|
||||
{{- $secret := lookup "v1" "Secret" .Release.Namespace $name -}}
|
||||
{{- $secretKey := "" -}}
|
||||
{{- if and $secret $secret.data (hasKey $secret.data "secret-key") -}}
|
||||
{{- $secretKey = index $secret.data "secret-key" | b64dec -}}
|
||||
{{- else if .Values.appSecret.value -}}
|
||||
{{- $secretKey = .Values.appSecret.value -}}
|
||||
{{- else -}}
|
||||
{{- $secretKey = randAlphaNum 64 -}}
|
||||
{{- end }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
secret-key: {{ $secretKey | quote }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "gramps-web.fullname" . }}
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: {{ include "gramps-web.fullname" . }}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "gramps-web.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- end }}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
{{- if .Values.worker.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "gramps-web.workerName" . }}
|
||||
labels:
|
||||
{{- include "gramps-web.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: worker
|
||||
spec:
|
||||
replicas: {{ .Values.worker.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ include "gramps-web.workerName" . }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ include "gramps-web.workerName" . }}
|
||||
{{- include "gramps-web.labels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: worker
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
checksum/app-secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
serviceAccountName: {{ include "gramps-web.serviceAccountName" . }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
|
||||
{{- with .Values.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: worker
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command: [celery]
|
||||
args:
|
||||
- -A
|
||||
- gramps_webapi.celery
|
||||
- worker
|
||||
- --loglevel=INFO
|
||||
- --concurrency={{ .Values.worker.concurrency }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
env:
|
||||
{{- include "gramps-web.env" . | nindent 12 }}
|
||||
{{- if .Values.worker.startupProbe.enabled }}
|
||||
startupProbe:
|
||||
{{- omit .Values.worker.startupProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.worker.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
{{- omit .Values.worker.readinessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.worker.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
{{- omit .Values.worker.livenessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.worker.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
{{- include "gramps-web.volumeMounts" . | nindent 12 }}
|
||||
volumes:
|
||||
- name: data
|
||||
{{- if .Values.persistence.enabled }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-data" (include "gramps-web.fullname" .)) .Values.persistence.existingClaim }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
project_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
workdir=$(mktemp --directory)
|
||||
trap 'rm -rf "${workdir}"' EXIT HUP INT TERM
|
||||
|
||||
bash -n \
|
||||
"${project_dir}/bootstrap-secrets.sh" \
|
||||
"${project_dir}/create-ci-kubeconfig.sh" \
|
||||
"${project_dir}/deploy.sh" \
|
||||
"${project_dir}/install.sh" \
|
||||
"${project_dir}/preflight.sh" \
|
||||
"${project_dir}/render.sh" \
|
||||
"${project_dir}/test.sh"
|
||||
|
||||
dependencies=("${project_dir}"/charts/*.tgz)
|
||||
if (( ${#dependencies[@]} < 2 )); then
|
||||
helm dependency build --skip-refresh "${project_dir}"
|
||||
fi
|
||||
helm lint "${project_dir}"
|
||||
helm lint "${project_dir}" --values "${project_dir}/values-arschrock.yaml"
|
||||
helm template gramps-web "${project_dir}" --namespace gramps-web \
|
||||
> "${workdir}/default.yaml"
|
||||
helm template gramps "${project_dir}" --namespace gramps \
|
||||
--values "${project_dir}/values-arschrock.yaml" \
|
||||
> "${workdir}/arschrock.yaml"
|
||||
helm template external "${project_dir}" --namespace gramps \
|
||||
--set postgresql.enabled=false \
|
||||
--set database.host=postgres.example.internal \
|
||||
--set database.auth.existingSecret=external-postgresql \
|
||||
--set valkey.enabled=false \
|
||||
--set cache.host=valkey.example.internal \
|
||||
--set cache.auth.existingSecret=external-valkey \
|
||||
> "${workdir}/external.yaml"
|
||||
|
||||
grep -Fq 'ghcr.io/gramps-project/grampsweb:latest' "${workdir}/default.yaml"
|
||||
grep -Fq 'name: GRAMPSWEB_OIDC_ENABLED' "${workdir}/default.yaml"
|
||||
grep -Fq 'name: postgresql' "${workdir}/arschrock.yaml"
|
||||
grep -Fq 'name: valkey-primary' "${workdir}/arschrock.yaml"
|
||||
grep -Fq 'name: grampsweb-celery' "${workdir}/arschrock.yaml"
|
||||
grep -Fq 'name: gramps-runtime' "${workdir}/arschrock.yaml"
|
||||
grep -Fq 'path: /ready' "${workdir}/default.yaml"
|
||||
grep -Fq 'psycopg2.connect' "${workdir}/default.yaml"
|
||||
grep -Fq 'celery -A gramps_webapi.celery inspect ping' "${workdir}/default.yaml"
|
||||
if grep -Eq '^kind: StatefulSet$' "${workdir}/external.yaml"; then
|
||||
echo "External-service render unexpectedly creates a StatefulSet" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Block the credential patterns that existed in the legacy raw manifests.
|
||||
if rg --hidden --glob '!charts/**' --glob '!postgresql/**' --glob '!valkey/**' \
|
||||
--glob '!test.sh' \
|
||||
'(GRAMPSWEB_POSTGRES_PASSWORD:[[:space:]]+[^{$]|OIDC_CLIENT_SECRET:[[:space:]]+[^{$]|AWS_SECRET_ACCESS_KEY:[[:space:]]+[^{$]|password:[[:space:]]+[A-Za-z0-9]{24,})' \
|
||||
"${project_dir}"; then
|
||||
echo "A likely plaintext credential is present in committable chart sources" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Chart renders successfully with bundled and external data services."
|
||||
@@ -0,0 +1,137 @@
|
||||
# Cluster-specific, non-secret desired state. Secret values live only in the
|
||||
# Kubernetes Secret named gramps-runtime (created by bootstrap-secrets.sh).
|
||||
fullnameOverride: grampsweb
|
||||
|
||||
image:
|
||||
repository: ghcr.io/gramps-project/grampsweb
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: grampsweb
|
||||
|
||||
config:
|
||||
tree: brunner.ninja Stammbaum
|
||||
baseUrl: https://stammbaum.brunner.ninja
|
||||
timezone: Europe/Vienna
|
||||
databaseBackend: postgresql
|
||||
userDatabaseUri: ""
|
||||
userDatabaseUriSecret:
|
||||
existingSecret: gramps-runtime
|
||||
key: user-database-uri
|
||||
searchIndexDatabaseUri: ""
|
||||
searchIndexDatabaseUriSecret:
|
||||
existingSecret: gramps-runtime
|
||||
key: search-index-database-uri
|
||||
|
||||
appSecret:
|
||||
existingSecret: gramps-runtime
|
||||
existingSecretKey: app-secret-key
|
||||
|
||||
persistence:
|
||||
existingClaim: grampsweb-data
|
||||
|
||||
worker:
|
||||
nameOverride: grampsweb-celery
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
nameOverride: stammbaum-brunner-ninja
|
||||
className: traefik
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
hosts:
|
||||
- host: stammbaum.brunner.ninja
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
|
||||
oidc:
|
||||
enabled: true
|
||||
issuer: https://auth.brunner.ninja/application/o/gramps/
|
||||
clientId: VipNqgic7tcwE15c5Qdn2RF1BDjzsAWtJv5ZOgGS
|
||||
clientSecret:
|
||||
existingSecret: gramps-runtime
|
||||
existingSecretKey: oidc-client-secret
|
||||
name: brunner.ninja Auth
|
||||
openidConfigUrl: https://auth.brunner.ninja/application/o/gramps/.well-known/openid-configuration
|
||||
disableLocalAuth: true
|
||||
autoRedirect: true
|
||||
groups:
|
||||
admin: Gramps Admins
|
||||
editor: Gramps Editors
|
||||
|
||||
s3:
|
||||
enabled: true
|
||||
mediaBaseDir: s3://gramps
|
||||
region: keller
|
||||
endpointUrl: http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local
|
||||
auth:
|
||||
existingSecret: gramps-runtime
|
||||
accessKeyIdKey: aws-access-key-id
|
||||
secretAccessKeyKey: aws-secret-access-key
|
||||
|
||||
email:
|
||||
enabled: true
|
||||
host: smtp-relay.gmail.com
|
||||
port: 587
|
||||
useTls: false
|
||||
defaultFromEmail: stammbaum@brunner.ninja
|
||||
|
||||
global:
|
||||
defaultStorageClass: rook-ceph-block
|
||||
security:
|
||||
# Required because this cluster uses its locale-enabled PostgreSQL build.
|
||||
allowInsecureImages: true
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
fullnameOverride: postgresql
|
||||
commonLabels:
|
||||
app.kubernetes.io/instance: postgresql
|
||||
image:
|
||||
registry: registry.brunner.ninja
|
||||
repository: feedc0de/postgres-with-locales
|
||||
tag: 18-locales
|
||||
pullPolicy: Always
|
||||
pullSecrets:
|
||||
- quay-pull-secret
|
||||
auth:
|
||||
username: gramps
|
||||
database: gramps
|
||||
existingSecret: gramps-postgresql
|
||||
secretKeys:
|
||||
adminPasswordKey: postgres-password
|
||||
userPasswordKey: password
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 8Gi
|
||||
|
||||
valkey:
|
||||
enabled: true
|
||||
fullnameOverride: valkey
|
||||
commonLabels:
|
||||
app.kubernetes.io/instance: valkey
|
||||
architecture: replication
|
||||
auth:
|
||||
enabled: true
|
||||
existingSecret: gramps-valkey
|
||||
existingSecretPasswordKey: valkey-password
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 4Gi
|
||||
replica:
|
||||
replicaCount: 3
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 4Gi
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"replicaCount": { "type": "integer", "minimum": 1 },
|
||||
"image": {
|
||||
"type": "object",
|
||||
"required": ["repository", "tag", "pullPolicy"],
|
||||
"properties": {
|
||||
"repository": { "type": "string", "minLength": 1 },
|
||||
"tag": { "type": "string", "minLength": 1 },
|
||||
"pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] }
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"required": ["tree", "baseUrl", "databaseBackend"],
|
||||
"properties": {
|
||||
"tree": { "type": "string", "minLength": 1 },
|
||||
"baseUrl": { "type": "string", "minLength": 1 },
|
||||
"databaseBackend": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
},
|
||||
"postgresql": {
|
||||
"type": "object",
|
||||
"required": ["enabled"],
|
||||
"properties": { "enabled": { "type": "boolean" } }
|
||||
},
|
||||
"valkey": {
|
||||
"type": "object",
|
||||
"required": ["enabled"],
|
||||
"properties": { "enabled": { "type": "boolean" } }
|
||||
},
|
||||
"oidc": {
|
||||
"type": "object",
|
||||
"required": ["enabled"],
|
||||
"properties": { "enabled": { "type": "boolean" } }
|
||||
},
|
||||
"s3": {
|
||||
"type": "object",
|
||||
"required": ["enabled"],
|
||||
"properties": { "enabled": { "type": "boolean" } }
|
||||
},
|
||||
"persistence": {
|
||||
"type": "object",
|
||||
"required": ["enabled", "size"],
|
||||
"properties": {
|
||||
"enabled": { "type": "boolean" },
|
||||
"size": { "type": "string", "minLength": 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: ghcr.io/gramps-project/grampsweb
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
pullSecrets: []
|
||||
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
name: ""
|
||||
annotations: {}
|
||||
automountServiceAccountToken: false
|
||||
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
podSecurityContext: {}
|
||||
securityContext: {}
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
nameOverride: ""
|
||||
className: ""
|
||||
annotations: {}
|
||||
hosts:
|
||||
- host: gramps.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls: []
|
||||
|
||||
config:
|
||||
tree: "Family Tree"
|
||||
baseUrl: http://localhost:5000
|
||||
timezone: UTC
|
||||
databaseBackend: postgresql
|
||||
userDatabaseUri: sqlite:////app/users/users.sqlite
|
||||
userDatabaseUriSecret:
|
||||
existingSecret: ""
|
||||
key: user-database-uri
|
||||
searchIndexDatabaseUri: sqlite:////app/indexdir/search_index.db
|
||||
searchIndexDatabaseUriSecret:
|
||||
existingSecret: ""
|
||||
key: search-index-database-uri
|
||||
extraEnv: []
|
||||
|
||||
appSecret:
|
||||
# The chart creates and preserves a random secret when existingSecret is empty.
|
||||
existingSecret: ""
|
||||
existingSecretKey: secret-key
|
||||
value: ""
|
||||
|
||||
database:
|
||||
# Used only when postgresql.enabled=false. Bundled credentials are configured
|
||||
# below under postgresql.auth.
|
||||
host: ""
|
||||
port: 5432
|
||||
username: gramps
|
||||
database: gramps
|
||||
auth:
|
||||
existingSecret: ""
|
||||
existingSecretKey: password
|
||||
password: ""
|
||||
|
||||
cache:
|
||||
# Used only when valkey.enabled=false. Bundled credentials are configured
|
||||
# below under valkey.auth.
|
||||
host: ""
|
||||
port: 6379
|
||||
brokerDatabase: 0
|
||||
resultDatabase: 1
|
||||
rateLimitDatabase: 2
|
||||
auth:
|
||||
enabled: true
|
||||
existingSecret: ""
|
||||
existingSecretKey: valkey-password
|
||||
password: ""
|
||||
|
||||
oidc:
|
||||
enabled: false
|
||||
issuer: ""
|
||||
clientId: ""
|
||||
clientSecret:
|
||||
existingSecret: ""
|
||||
existingSecretKey: oidc-client-secret
|
||||
value: ""
|
||||
name: OIDC
|
||||
scopes: openid email profile
|
||||
usernameClaim: preferred_username
|
||||
openidConfigUrl: ""
|
||||
disableLocalAuth: false
|
||||
autoRedirect: false
|
||||
roleClaim: groups
|
||||
groups:
|
||||
admin: ""
|
||||
owner: ""
|
||||
editor: ""
|
||||
contributor: ""
|
||||
member: ""
|
||||
guest: ""
|
||||
|
||||
s3:
|
||||
enabled: false
|
||||
mediaBaseDir: ""
|
||||
region: ""
|
||||
endpointUrl: ""
|
||||
auth:
|
||||
existingSecret: ""
|
||||
accessKeyIdKey: aws-access-key-id
|
||||
secretAccessKeyKey: aws-secret-access-key
|
||||
accessKeyId: ""
|
||||
secretAccessKey: ""
|
||||
|
||||
email:
|
||||
enabled: false
|
||||
host: ""
|
||||
port: 587
|
||||
useTls: true
|
||||
defaultFromEmail: ""
|
||||
username: ""
|
||||
auth:
|
||||
existingSecret: ""
|
||||
passwordKey: smtp-password
|
||||
password: ""
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
existingClaim: ""
|
||||
storageClass: ""
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
size: 10Gi
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
|
||||
worker:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
nameOverride: ""
|
||||
concurrency: 2
|
||||
startupProbe:
|
||||
enabled: true
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -ec
|
||||
- celery -A gramps_webapi.celery inspect ping --destination "celery@${HOSTNAME}" --timeout 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 30
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -ec
|
||||
- celery -A gramps_webapi.celery inspect ping --destination "celery@${HOSTNAME}" --timeout 5
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 2
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
exec:
|
||||
command:
|
||||
- sh
|
||||
- -ec
|
||||
- celery -A gramps_webapi.celery inspect ping --destination "celery@${HOSTNAME}" --timeout 5
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 1Gi
|
||||
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
# Also verifies PostgreSQL authentication/querying and a Valkey PING.
|
||||
deep: true
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 2
|
||||
startupProbe:
|
||||
enabled: true
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: http
|
||||
failureThreshold: 30
|
||||
periodSeconds: 5
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
username: gramps
|
||||
database: gramps
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
secretKeys:
|
||||
adminPasswordKey: postgres-password
|
||||
userPasswordKey: password
|
||||
primary:
|
||||
startupProbe:
|
||||
enabled: true
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 8Gi
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 2Gi
|
||||
|
||||
valkey:
|
||||
enabled: true
|
||||
architecture: standalone
|
||||
auth:
|
||||
enabled: true
|
||||
password: ""
|
||||
existingSecret: ""
|
||||
existingSecretPasswordKey: valkey-password
|
||||
primary:
|
||||
startupProbe:
|
||||
enabled: true
|
||||
failureThreshold: 30
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 1Gi
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
Reference in New Issue
Block a user