# Opengist Helm Chart ![Version: 0.5.0](https://img.shields.io/badge/Version-0.5.0-informational?style=flat-square) ![AppVersion: 1.11.1](https://img.shields.io/badge/AppVersion-1.11.1-informational?style=flat-square) Opengist Helm chart for Kubernetes. * [Install](#install) * [Configuration](#configuration) * [Metrics & Monitoring](#metrics--monitoring) * [Dependencies](#dependencies) * [Meilisearch Indexer](#meilisearch-indexer) * [PostgreSQL Database](#postgresql-database) ## Install ```bash helm repo add opengist https://helm.opengist.io helm install opengist opengist/opengist ``` ## Configuration This part explains how to configure the Opengist instance using the Helm chart. The `config.yml` file used by Opengist is mounted from a Kubernetes Secret with a key `config.yml` and the values formatted as YAML. ### Using values Using Helm values, you can define the values from a key name `config` ```yaml config: log-level: "warn" log-output: "stdout" ``` This will create a Kubernetes secret named `opengist` mounted to the pod as a file with the YAML content of the secret, used by Opengist. ### Using an existing secret If you wish to not store sensitive data in your Helm values, you can create a Kubernetes secret with a key `config.yml` and values formatted as YAML. You can then reference this secret in the Helm chart with the `configExistingSecret` key. If defined, this existing secret will be used instead of creating a new one. ```yaml configExistingSecret: ``` ## Metrics & Monitoring Opengist exposes Prometheus metrics on a separate port (default: `6158`). The metrics server runs independently from the main HTTP server for security. ### Enabling Metrics To enable metrics, set `metrics.enabled: true` in your Opengist config: ```yaml config: metrics.enabled: true ``` This will: 1. Start a metrics server on port 6158 inside the container 2. Create a Kubernetes Service exposing the metrics ports ### Available Metrics | Metric Name | Type | Description | |-------------|------|-------------| | `opengist_users_total` | Gauge | Total number of registered users | | `opengist_gists_total` | Gauge | Total number of gists | | `opengist_ssh_keys_total` | Gauge | Total number of SSH keys | | `opengist_request_duration_seconds_*` | Histogram | HTTP request duration metrics | ### ServiceMonitor for Prometheus Operator If you're using [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator), you can enable automatic service discovery with a ServiceMonitor: ```yaml config: metrics.enabled: true service: metrics: serviceMonitor: enabled: true labels: release: prometheus # match your Prometheus serviceMonitorSelector ``` ### Manual Prometheus Configuration If you're not using Prometheus Operator, you can configure Prometheus to scrape the metrics endpoint directly: ```yaml scrape_configs: - job_name: 'opengist' static_configs: - targets: ['opengist-metrics:6158'] metrics_path: /metrics ``` Or use Kubernetes service discovery: ```yaml scrape_configs: - job_name: 'opengist' kubernetes_sd_configs: - role: service relabel_configs: - source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_component] regex: metrics action: keep - source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name] regex: opengist action: keep ``` ## Dependencies ### Meilisearch Indexer By default, Opengist uses the `bleve` indexer. **It is NOT available** if there is multiple replicas of the opengist pod (only one pod can open the index at the same time). Instead, for multiple replicas setups, you **MUST** use the `meilisearch` indexer. By setting `meilisearch.enabled: true`, the [Meilisearch chart](https://github.com/meilisearch/meilisearch-kubernetes) will be deployed aswell. You must define the `meilisearch.host` (Kubernetes Service) and `meilisearch.key` (value created by Meilisearch) values to connect to the Meilisearch instance in your Opengist config : ```yaml index: meilisearch index.meili.host: http://opengist-meilisearch:7700 # pointing to the K8S Service index.meili.api-key: MASTER_KEY # generated by Meilisearch ``` If you want to use the `bleve` indexer, you need to set the `replicas` to `1`. #### Passing Meilisearch configuration via nested Helm values When using the Helm CLI with `--set`, avoid mixing a scalar `config.index` value with nested `config.index.meili.*` keys. Instead use a nested map and a `type` field which the chart flattens automatically. Example: ```bash helm template opengist ./helm/opengist \ --set statefulSet.enabled=true \ --set replicaCount=2 \ --set persistence.enabled=true \ --set persistence.existingClaim=opengist-shared-rwx \ --set postgresql.enabled=false \ --set config.db-uri="postgres://user:pass@db-host:5432/opengist" \ --set meilisearch.enabled=true \ --set config.index.type=meilisearch \ --set config.index.meili.host="http://opengist-meilisearch:7700" \ --set config.index.meili.api-key="MASTER_KEY" ``` Rendered `config.yml` fragment: ```yaml index: meilisearch index.meili.host: http://opengist-meilisearch:7700 index.meili.api-key: MASTER_KEY ``` How it works: * You provide a map under `config.index` with keys `type` and `meili`. * The template detects `config.index.type` and rewrites `index: `. * Nested `config.index.meili.host` / `api-key` are lifted to flat keys `index.meili.host` and `index.meili.api-key` required by Opengist. If you set `--set config.index=meilisearch` directly and also try to set `--set config.index.meili.host=...`, Helm will first create the nested structure then overwrite it with the scalar, losing the host. Always prefer the `config.index.type` pattern for CLI usage. ### PostgreSQL Database By default, Opengist uses the `sqlite` database. If needed, this chart also deploys a PostgreSQL instance. By setting `postgresql.enabled: true`, the [Bitnami PostgreSQL chart](https://github.com/bitnami/charts/tree/main/bitnami/postgresql) will be deployed aswell. You must define the `postgresql.host`, `postgresql.port`, `postgresql.database`, `postgresql.username` and `postgresql.password` values to connect to the PostgreSQL instance. Then define the connection string in your Opengist config: ```yaml db-uri: postgres://user:password@opengist-postgresql:5432/opengist ``` Note: `opengist-postgresql` is the name of the K8S Service deployed by this chart. ### Database Configuration You can supply an externally managed database connection explicitly via `config.db-uri` (PostgreSQL/MySQL) or enable the bundled PostgreSQL subchart. Behavior: * If `postgresql.enabled: true` and `config.db-uri` is omitted, the chart auto-generates: `postgres://:@-postgresql:/` using values under `postgresql.global.postgresql.auth.*`. * If any of username/password/database are missing, templating fails fast with an error message. * If you prefer an external database or a different Postgres distribution, set `postgresql.enabled: false` and provide `config.db-uri` yourself. **Licensing note**: Bitnami's PostgreSQL distribution may have licensing constraints. For strictly open alternatives use an external managed PostgreSQL/MySQL service and disable the subchart. ### Multi-Replica Requirements Running more than one Opengist replica (Deployment or StatefulSet) requires: 1. Non-SQLite database (`config.db-uri` must start with `postgres://` or `mysql://`). 2. Shared RWX storage if using StatefulSet with `replicaCount > 1` (provide `persistence.existingClaim`). The chart now fails fast if you attempt `replicaCount > 1` without an explicit shared claim to prevent silent data divergence across per‑pod PVCs. The chart will fail fast during templating if these conditions are not met when scaling above 1 replica. Examples: * External PostgreSQL: ```yaml postgresql: enabled: false config: db-uri: postgres://user:pass@db-host:5432/opengist index: meilisearch statefulSet: enabled: true replicaCount: 2 persistence: existingClaim: opengist-shared-rwx ``` Bundled PostgreSQL (auto db-uri): ```yaml postgresql: enabled: true config: index: meilisearch statefulSet: enabled: true replicaCount: 2 persistence: existingClaim: opengist-shared-rwx ``` #### Recovering from an initial misconfiguration If you previously scaled a StatefulSet above 1 replica **without** an `existingClaim`, each pod received its own PVC and only one held the authoritative `/opengist` data. To consolidate: 1. Scale down to 1 replica (keep the pod with the desired data): ```bash kubectl scale sts/opengist --replicas=1 ``` 1. (Optional) Inspect other PVCs and manually copy any missing files by temporarily attaching them to a debug pod. 1. Create or provision a ReadWriteMany (NFS / CephFS / Longhorn RWX / etc.) PersistentVolumeClaim named (for example) `opengist-shared-rwx`. 1. Update values with `persistence.existingClaim: opengist-shared-rwx` and re‑deploy. 1. Scale back up: ```bash kubectl scale sts/opengist --replicas=2 ``` Going forward, all replicas mount the same shared volume and data remains consistent. ### Quick Start Examples Common deployment scenarios with copy-paste configurations: #### Scenario 1: Single replica with SQLite (default) Minimal local development setup with ephemeral or persistent storage: ```yaml # Ephemeral (emptyDir) statefulSet: enabled: true replicaCount: 1 persistence: enabled: false # OR with persistent RWO storage statefulSet: enabled: true replicaCount: 1 persistence: enabled: true mode: perReplica # default ``` #### Scenario 2: Multi-replica with external PostgreSQL + existing RWX PVC Production HA setup with your own database and storage: ```yaml statefulSet: enabled: true replicaCount: 2 postgresql: enabled: false config: db-uri: "postgres://user:pass@db-host:5432/opengist" index: meilisearch # required for multi-replica persistence: enabled: true mode: shared existingClaim: "opengist-shared-rwx" # pre-created RWX PVC meilisearch: enabled: true ``` #### Scenario 3: Multi-replica with bundled PostgreSQL + auto-created RWX PVC Chart manages both database and storage: ```yaml statefulSet: enabled: true replicaCount: 2 postgresql: enabled: true global: postgresql: auth: username: opengist password: changeme database: opengist config: index: meilisearch persistence: enabled: true mode: shared existingClaim: "" # empty to trigger auto-creation create: enabled: true accessModes: [ReadWriteMany] storageClass: "nfs-client" # your RWX-capable storage class size: 20Gi meilisearch: enabled: true ``` ### Persistence Modes The chart supports two persistence strategies controlled by `persistence.mode`: | Mode | Behavior | Scaling | Storage Objects | Recommended Use | |-------------|----------|---------|-----------------|-----------------| | `perReplica` (default) | One PVC per pod via StatefulSet `volumeClaimTemplates` (RWO) when no `existingClaim` | Safe only at `replicaCount=1` unless you supply `existingClaim` | One PVC per replica | Local dev, quick single-node trials | | `shared` | Single RWX PVC (existing or auto-created) mounted by all pods | Horizontally scalable | One shared PVC | Production / HA | Configuration examples: Per-replica (single node): ```yaml statefulSet: enabled: true persistence: mode: perReplica enabled: true accessModes: - ReadWriteOnce ``` Shared (scale ready) with an existing RWX claim: ```yaml statefulSet: enabled: true replicaCount: 2 persistence: mode: shared existingClaim: opengist-shared-rwx ``` Shared with chart-created RWX PVC: ```yaml statefulSet: enabled: true replicaCount: 2 persistence: mode: shared existingClaim: "" # leave empty create: enabled: true accessModes: [ReadWriteMany] size: 10Gi ``` When `mode=shared` and `existingClaim` is empty, the chart creates a single PVC named `-shared` (suffix configurable via `persistence.create.nameSuffix`). Fail-fast conditions: * `replicaCount>1` & missing external DB (still enforced). * `replicaCount>1` & persistence disabled. * `replicaCount>1` & neither `existingClaim` nor `mode=shared`. * `mode=shared` & create.enabled=true but `accessModes` lacks `ReadWriteMany`. Migration (perReplica → shared): scale down to 1, create RWX claim (or rely on create.enabled), copy data, switch mode to shared, scale up. ### Troubleshooting #### Common Errors and Solutions ##### Error: "replicaCount=2 requires PostgreSQL/MySQL config.db-uri; scheme 'sqlite' unsupported" * **Cause**: Multi-replica with SQLite database * **Solution**: Either scale down to `replicaCount: 1` or configure external database: ```yaml config: db-uri: "postgres://user:pass@host:5432/opengist" ``` ##### Error: "replicaCount=2 requires either persistence.existingClaim OR persistence.mode=shared" * **Cause**: Multi-replica without shared storage * **Solution**: Choose one approach: ```yaml # Option A: Use existing PVC persistence: existingClaim: "my-rwx-pvc" # Option B: Let chart create PVC persistence: mode: shared create: enabled: true accessModes: [ReadWriteMany] ``` ##### Error: "persistence.mode=shared create.accessModes must include ReadWriteMany for multi-replica" * **Cause**: Chart-created PVC lacks RWX access mode * **Solution**: Ensure RWX is specified: ```yaml persistence: create: accessModes: - ReadWriteMany ``` ##### Pods mount different data (data divergence) * **Cause**: Previously scaled with `perReplica` mode and `replicaCount > 1` * **Solution**: Follow recovery steps in "Recovering from an initial misconfiguration" section above ##### PVC creation fails: "no storage class available with ReadWriteMany" * **Cause**: Cluster lacks RWX-capable storage provisioner * **Solution**: Install a storage provider (NFS, CephFS, Longhorn) or use external managed storage and provide `existingClaim`