Add Helm Chart (#454)

This commit is contained in:
Thomas Miceli
2025-04-06 00:51:38 +02:00
committed by GitHub
parent 2d8debecbe
commit 9e69677f58
31 changed files with 939 additions and 186 deletions

52
.github/workflows/helm.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Build / Deploy Helm Chart
on:
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v4.3.0
with:
version: 'latest'
- name: Update Helm chart dependencies
run: |
cd ./helm/opengist
helm dependency update
- name: Package Helm chart
run: |
cd ./helm
helm package ./opengist
# First time, create the index
if [ ! -f index.yaml ]; then
helm repo index --url https://helm.opengist.io .
else
# For subsequent runs, merge with existing index
helm repo index --url https://helm.opengist.io --merge index.yaml .
fi
- name: Deploy to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
source: "./helm/*.tgz,./helm/index.yaml"
target: ${{ secrets.HELM_SERVER_PATH }}
- name: Update remote helm repository
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
${{ secrets.UPDATE_HELM_REPO }}

2
.gitignore vendored
View File

@ -5,7 +5,9 @@ gist.db
/**/.DS_Store
public/assets/*
public/manifest.json
./opengist
opengist
build/
docs/.vitepress/dist/
docs/.vitepress/cache/
helm/opengist/charts/

View File

@ -1,75 +0,0 @@
# kustomize
## Simple
`kustomization.yaml`:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: opengist
resources:
- https://github.com/thomiceli/opengist/deploy/
```
## Full example
`kustomization.yaml`:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: opengist
namespace: opengist
resources:
- namespace.yaml
- https://github.com/thomiceli/opengist/deploy/?ref:v1.9.1
images:
- name: ghcr.io/thomiceli/opengist
newTag: 1.9.1
patches:
# Add your ingress
- path: ingress.yaml
- patch: |-
- op: add
path: /spec/rules/0/host
value: opengist.mydomain.com
target:
group: networking.k8s.io
version: v1
kind: Ingress
name: opengist
```
`namespace.yaml`:
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: opengist
```
`ingress.yaml`:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: opengist
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
spec:
ingressClassName: nginx
tls:
- hosts:
- opengist.mydomain.com
secretName: opengist-tls
```

View File

@ -1,29 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: opengist
spec:
selector:
matchLabels:
app.kubernetes.io/name: opengist
template:
metadata:
labels:
app.kubernetes.io/name: opengist
spec:
containers:
- name: opengist
image: ghcr.io/thomiceli/opengist
ports:
- name: http
containerPort: 6157
- name: ssh
containerPort: 2222
volumeMounts:
- mountPath: /opengist
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: opengist-data

View File

@ -1,20 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: opengist
labels:
app.kubernetes.io/name: opengist
app.kubernetes.io/component: ingress
spec:
rules:
- host: opengist.local
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: opengist
port:
name: http

View File

@ -1,11 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: opengist
resources:
- deployment.yaml
- pvc.yaml
- ingress.yaml
- service.yaml

View File

@ -1,15 +0,0 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: opengist-data
labels:
app.kubernetes.io/name: opengist
app.kubernetes.io/component: data
spec:
resources:
requests:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce

View File

@ -1,14 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: opengist
labels:
app.kubernetes.io/name: opengist
spec:
selector:
app.kubernetes.io/name: opengist
ports:
- port: 80
targetPort: http
name: http

View File

@ -25,6 +25,7 @@ export default defineConfig({
{text: 'Introduction', link: '/docs'},
{text: 'Installation', link: '/docs/installation', items: [
{text: 'Docker', link: '/docs/installation/docker'},
{text: 'Kubernetes', link: '/docs/installation/kubernetes'},
{text: 'Binary', link: '/docs/installation/binary'},
{text: 'Source', link: '/docs/installation/source'},
],

View File

@ -0,0 +1,15 @@
# Install on Kubernetes
A [Helm](https://helm.sh) chart is available to install Opengist on a Kubernetes cluster.
Check the [Helm documentation](https://helm.sh/docs/) for more information on how to use Helm.
A non-customized installation of Opengist can be done with:
```bash
helm repo add opengist https://helm.opengist.io
helm install opengist opengist/opengist
```
Refer to the [Opengist chart](https://github.com/thomiceli/opengist/tree/master/helm/opengist) for more information
about the chart and to customize your installation.

23
helm/opengist/.helmignore Normal file
View File

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

9
helm/opengist/Chart.lock Normal file
View File

@ -0,0 +1,9 @@
dependencies:
- name: postgresql
repository: oci://registry-1.docker.io/bitnamicharts
version: 16.5.6
- name: meilisearch
repository: https://meilisearch.github.io/meilisearch-kubernetes
version: 0.12.0
digest: sha256:31084e570aa16e3a26317aeb6d0d5dec62540c314ee4f703374e6e7827399fa6
generated: "2025-03-27T11:34:51.840778733+01:00"

19
helm/opengist/Chart.yaml Normal file
View File

@ -0,0 +1,19 @@
apiVersion: v2
name: opengist
description: Opengist Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: 1.10.0
home: https://opengist.io
icon: https://raw.githubusercontent.com/thomiceli/opengist/master/public/opengist.svg
sources:
- https://github.com/thomiceli/opengist
dependencies:
- name: postgresql
repository: oci://registry-1.docker.io/bitnamicharts
version: 16.5.6
condition: postgresql.enabled
- name: meilisearch
repository: https://meilisearch.github.io/meilisearch-kubernetes
version: 0.12.0
condition: meilisearch.enabled

81
helm/opengist/README.md Normal file
View File

@ -0,0 +1,81 @@
# Opengist Helm Chart
![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![AppVersion: 1.10.0](https://img.shields.io/badge/AppVersion-1.10.0-informational?style=flat-square)
Opengist Helm chart for Kubernetes.
* [Install](#install)
* [Configuration](#configuration)
* [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: <name of the secret>
```
## 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`.
### 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.

View File

@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.http.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "opengist.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.http.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "opengist.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "opengist.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.http.port }}
{{- else if contains "ClusterIP" .Values.service.http.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "opengist.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@ -0,0 +1,85 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "opengist.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "opengist.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 }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "opengist.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "opengist.labels" -}}
helm.sh/chart: {{ include "opengist.chart" . }}
app: {{ include "opengist.name" . }}
{{ include "opengist.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "opengist.selectorLabels" -}}
app.kubernetes.io/name: {{ include "opengist.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "opengist.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "opengist.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create image URI
*/}}
{{- define "opengist.image" -}}
{{- if .Values.image.digest -}}
{{- printf "%s@%s" .Values.image.repository .Values.image.digest -}}
{{- else -}}
{{- printf "%s:%s" .Values.image.repository (.Values.image.tag | default .Chart.AppVersion) -}}
{{- end -}}
{{- end -}}
{{/*
Create secret name
*/}}
{{- define "opengist.secretName" -}}
{{- if .Values.configExistingSecret -}}
{{- printf "%s" (tpl .Values.configExistingSecret $) -}}
{{- else -}}
{{- printf "%s" (include "opengist.fullname" .) -}}
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,115 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "opengist.fullname" . }}
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- if .Values.deployment.labels }}
{{- toYaml .Values.deployment.labels | nindent 4 }}
{{- end }}
{{- with .Values.deployment.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "opengist.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "opengist.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "opengist.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
initContainers:
- name: init-config
image: busybox:1.37
imagePullPolicy: IfNotPresent
command: ['sh', '-c', 'cp /init/config/config.yml /config-volume/config.yml']
volumeMounts:
- name: config-secret
mountPath: /init/config
- name: config-volume
mountPath: /config-volume
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.http.port }}
protocol: TCP
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
{{- toYaml (omit .Values.livenessProbe "enabled") | nindent 12 }}
httpGet:
port: http
path: /healthcheck
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
{{- toYaml (omit .Values.readinessProbe "enabled") | nindent 12 }}
httpGet:
port: http
path: /healthcheck
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: config-volume
mountPath: /config.yml
subPath: config.yml
- name: opengist-data
mountPath: /opengist
{{- if gt (len .Values.extraVolumeMounts) 0 }}
{{- toYaml .Values.extraVolumeMounts | nindent 12 }}
{{- end }}
volumes:
- name: opengist-data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ include "opengist.fullname" . }}-data
{{- else }}
emptyDir: {}
{{- end }}
- name: config-secret
secret:
secretName: {{ include "opengist.secretName" . }}
defaultMode: 511
- name: config-volume
emptyDir: {}
{{- if gt (len .Values.extraVolumes) 0 }}
{{- toYaml .Values.extraVolumes | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -0,0 +1,37 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "opengist.fullname" . }}
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- with .Values.autoscaling.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "opengist.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,47 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "opengist.fullname" . }}
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- if .Values.ingress.labels }}
{{- toYaml .Values.service.http.labels | nindent 4 }}
{{- end }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- with .pathType }}
pathType: {{ . }}
{{- end }}
backend:
service:
name: {{ include "opengist.fullname" $ }}-http
port:
number: {{ $.Values.service.http.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,14 @@
{{- if .Values.podDisruptionBudget -}}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "opengist.fullname" . }}
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "opengist.selectorLabels" . | nindent 6 }}
{{- toYaml .Values.podDisruptionBudget | nindent 2 }}
{{- end -}}

View File

@ -0,0 +1,28 @@
{{- if .Values.persistence.enabled }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ include "opengist.fullname" . }}-data
namespace: {{ .Release.Namespace }}
{{- with .Values.persistence.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- if .Values.persistence.labels }}
{{- toYaml .Values.persistence.labels | nindent 4 }}
{{- end }}
spec:
accessModes:
{{- if gt .Values.replicaCount 1.0 }}
- ReadWriteMany
{{- else }}
{{- .Values.persistence.accessModes | toYaml | nindent 4 }}
{{- end }}
volumeMode: Filesystem
storageClassName: {{ .Values.persistence.storageClass | quote }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}

View File

@ -0,0 +1,13 @@
{{- if (not .Values.configExistingSecret) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "opengist.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{ include "opengist.labels" . | indent 4 }}
type: Opaque
stringData:
config.yml: |-
{{- .Values.config | toYaml | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "opengist.serviceAccountName" . }}
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,47 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "opengist.fullname" . }}-http
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- if .Values.service.http.labels }}
{{- toYaml .Values.service.http.labels | nindent 4 }}
{{- end }}
{{- with .Values.service.http.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.http.type }}
{{- if eq .Values.service.http.type "LoadBalancer" }}
{{- if and .Values.service.http.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.http.loadBalancerIP }}
{{- end }}
{{- if .Values.service.http.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- range .Values.service.http.loadBalancerSourceRanges }}
- {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.service.http.externalIPs }}
externalIPs:
{{- toYaml .Values.service.http.externalIPs | nindent 4 }}
{{- end }}
{{- if .Values.service.http.externalTrafficPolicy }}
externalTrafficPolicy: {{ .Values.service.http.externalTrafficPolicy }}
{{- end }}
{{- if and .Values.service.http.clusterIP (eq .Values.service.http.type "ClusterIP") }}
clusterIP: {{ .Values.service.http.clusterIP }}
{{- end }}
ports:
- name: http
port: {{ .Values.service.http.port }}
{{- if .Values.service.http.nodePort }}
nodePort: {{ .Values.service.http.nodePort }}
{{- end }}
targetPort: {{ index .Values.config "http.port" }}
selector:
{{- include "opengist.selectorLabels" . | nindent 4 }}

View File

@ -0,0 +1,64 @@
{{- if .Values.service.ssh.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "opengist.fullname" . }}-ssh
namespace: {{ .Values.namespace | default .Release.Namespace }}
labels:
{{- include "opengist.labels" . | nindent 4 }}
{{- if .Values.service.ssh.labels }}
{{- toYaml .Values.service.ssh.labels | nindent 4 }}
{{- end }}
{{- with .Values.service.http.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.ssh.type }}
{{- if eq .Values.service.ssh.type "LoadBalancer" }}
{{- if .Values.service.ssh.loadBalancerClass }}
loadBalancerClass: {{ .Values.service.ssh.loadBalancerClass }}
{{- end }}
{{- if .Values.service.ssh.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.ssh.loadBalancerIP }}
{{- end -}}
{{- if .Values.service.ssh.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- range .Values.service.ssh.loadBalancerSourceRanges }}
- {{ . }}
{{- end }}
{{- end }}
{{- end }}
{{- if and .Values.service.ssh.clusterIP (eq .Values.service.ssh.type "ClusterIP") }}
clusterIP: {{ .Values.service.ssh.clusterIP }}
{{- end }}
{{- if .Values.service.ssh.externalIPs }}
externalIPs:
{{- toYaml .Values.service.ssh.externalIPs | nindent 4 }}
{{- end }}
{{- if .Values.service.ssh.ipFamilyPolicy }}
ipFamilyPolicy: {{ .Values.service.ssh.ipFamilyPolicy }}
{{- end }}
{{- with .Values.service.ssh.ipFamilies }}
ipFamilies:
{{- toYaml . | nindent 4 }}
{{- end -}}
{{- if .Values.service.ssh.externalTrafficPolicy }}
externalTrafficPolicy: {{ .Values.service.ssh.externalTrafficPolicy }}
{{- end }}
ports:
- name: ssh
port: {{ .Values.service.ssh.port }}
{{- if .Values.service.ssh.nodePort }}
nodePort: {{ .Values.service.ssh.nodePort }}
{{- end }}
{{- if index .Values.config "ssh.port" }}
targetPort: {{ index .Values.config "ssh.port" }}
{{- else }}
targetPort: 2222
{{- end }}
protocol: TCP
selector:
{{- include "opengist.selectorLabels" . | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "opengist.fullname" . }}-test-connection"
labels:
{{- include "opengist.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "opengist.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

201
helm/opengist/values.yaml Normal file
View File

@ -0,0 +1,201 @@
## Kubernetes workload configuration for Opengist
nameOverride: ""
fullnameOverride: ""
namespace: ""
## Opengist YAML Application Config. See more at https://opengist.io/docs/configuration/cheat-sheet.html
## This will create a Kubernetes secret with the key `config.yml` containing the YAML configuration mounted in the pod.
config:
log-level: "warn"
log-output: "stdout"
## If defined, the existing secret will be used instead of creating a new one.
## The secret must contain a key named `config.yml` with the YAML configuration.
configExistingSecret: ""
## Define the image repository and tag to use.
image:
repository: ghcr.io/thomiceli/opengist
pullPolicy: Always
tag: "1.10.0"
digest: ""
imagePullSecrets: []
# - name: "image-pull-secret"
## Define the deployment replica count
replicaCount: 1
## Define the deployment strategy type
strategy:
type: "RollingUpdate"
rollingUpdate:
maxSurge: "100%"
maxUnavailable: 0
## Security Context settings
## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
podSecurityContext:
fsGroup: 1000
securityContext: {}
# allowPrivilegeEscalation: false
## Pod Disruption Budget settings
## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/
podDisruptionBudget: {}
# maxUnavailable: 1
# minAvailable: 1
## Set the Kubernetes service type
## ref: https://kubernetes.io/docs/concepts/services-networking/service/
service:
http:
type: ClusterIP
clusterIP:
port: 6157
nodePort:
loadBalancerIP:
externalIPs: []
labels: {}
annotations: {}
loadBalancerSourceRanges: []
externalTrafficPolicy:
ssh:
enabled: true
type: ClusterIP
clusterIP:
port: 2222
nodePort:
loadBalancerIP:
externalIPs: []
labels: {}
annotations: {}
loadBalancerSourceRanges: []
externalTrafficPolicy:
## HTTP Ingress for Opengist
## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
enabled: false
className: ""
labels: {}
# node-role.kubernetes.io/ingress: platform
annotations: {}
# kubernetes.io/ingress.class: nginx
hosts:
- host: opengist.example.com
paths:
- path: /
pathType: Prefix
tls: []
# - secretName: opengist-tls
# hosts:
# - opengist.example.com
## Service Account for Opengist pods
## ref: https://kubernetes.io/docs/concepts/security/service-accounts/
serviceAccount:
create: true
annotations: {}
name: ""
## Set persistence using a Persistent Volume Claim
## If more than 2 replicas are set, the access mode must be ReadWriteMany
## ref: https://kubernetes.io/docs/concepts/storage/persistent-volumes/
persistence:
enabled: true
existingClaim: ""
storageClass: ""
labels: {}
annotations:
helm.sh/resource-policy: keep
size: 5Gi
accessModes:
- ReadWriteOnce
subPath: ""
extraVolumes: []
extraVolumeMounts: []
## Additional pod labels and annotations
podLabels: {}
podAnnotations: {}
## Configure resource requests and limits
## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
resources: {}
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
## Configure the liveness and readiness probes
## ref: https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/
livenessProbe:
enabled: true
initialDelaySeconds: 200
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 5
readinessProbe:
enabled: true
initialDelaySeconds: 5
timeoutSeconds: 1
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
## Define autoscaling configuration using Horizontal Pod Autoscaler
## ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
annotations: {}
## Additional deployment configuration
deployment:
env: []
terminationGracePeriodSeconds: 60
labels: {}
annotations: {}
## Set pod assignment with node labels
## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/
nodeSelector: {}
tolerations: []
affinity: {}
## Use PostgreSQL as a database, using Bitnami's PostgreSQL Helm chart
## ref: https://artifacthub.io/packages/helm/bitnami/postgresql/16.5.6
postgresql:
enabled: false
global:
postgresql:
auth:
username: opengist
password: opengist
database: opengist
service:
ports:
postgresql: 5432
primary:
persistence:
size: 10Gi
## Use Meilisearch as a code indexer, using Meilisearch's Helm chart
## ref: https://github.com/meilisearch/meilisearch-kubernetes/tree/meilisearch-0.12.0
meilisearch:
enabled: false
environment:
MEILI_ENV: "production"
auth:
existingMasterKeySecret:

View File

@ -126,7 +126,7 @@ func Initialize(ctx *cli.Context) {
index.DepreactionIndexDirname()
if index.IndexEnabled() {
index.NewIndexer(index.IndexType())
go index.NewIndexer(index.IndexType())
}
}

View File

@ -22,16 +22,23 @@ func NewBleveIndexer(path string) *BleveIndexer {
return &BleveIndexer{path: path}
}
func (i *BleveIndexer) Init() {
func (i *BleveIndexer) Init() error {
errChan := make(chan error, 1)
go func() {
bleveIndex, err := i.open()
if err != nil {
log.Error().Err(err).Msg("Failed to open Bleve index")
i.Close()
errChan <- err
return
}
i.index = bleveIndex
log.Info().Msg("Bleve indexer initialized")
errChan <- nil
}()
return <-errChan
}
func (i *BleveIndexer) open() (bleve.Index, error) {

View File

@ -11,7 +11,7 @@ import (
var atomicIndexer atomic.Pointer[Indexer]
type Indexer interface {
Init()
Init() error
Close()
Add(gist *Gist) error
Remove(gistID uint) error
@ -64,7 +64,9 @@ func NewIndexer(idxType IndexerType) {
return
}
idx.Init()
if err := idx.Init(); err != nil {
return
}
atomicIndexer.Store(&idx)
}
@ -73,12 +75,12 @@ func Close() {
return
}
idx := *atomicIndexer.Load()
idx := atomicIndexer.Load()
if idx == nil {
return
}
idx.Close()
(*idx).Close()
atomicIndexer.Store(nil)
}
@ -87,12 +89,12 @@ func AddInIndex(gist *Gist) error {
return nil
}
idx := *atomicIndexer.Load()
idx := atomicIndexer.Load()
if idx == nil {
return fmt.Errorf("indexer is not initialized")
}
return idx.Add(gist)
return (*idx).Add(gist)
}
func RemoveFromIndex(gistID uint) error {
@ -100,12 +102,12 @@ func RemoveFromIndex(gistID uint) error {
return nil
}
idx := *atomicIndexer.Load()
idx := atomicIndexer.Load()
if idx == nil {
return fmt.Errorf("indexer is not initialized")
}
return idx.Remove(gistID)
return (*idx).Remove(gistID)
}
func SearchGists(query string, metadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
@ -113,12 +115,12 @@ func SearchGists(query string, metadata SearchGistMetadata, userId uint, page in
return nil, 0, nil, nil
}
idx := *atomicIndexer.Load()
idx := atomicIndexer.Load()
if idx == nil {
return nil, 0, nil, fmt.Errorf("indexer is not initialized")
}
return idx.Search(query, metadata, userId, page)
return (*idx).Search(query, metadata, userId, page)
}
func DepreactionIndexDirname() {

View File

@ -25,29 +25,34 @@ func NewMeiliIndexer(host, apikey, indexName string) *MeiliIndexer {
}
}
func (i *MeiliIndexer) Init() {
func (i *MeiliIndexer) Init() error {
errChan := make(chan error, 1)
go func() {
meiliIndex, err := i.open()
if err != nil {
log.Error().Err(err).Msg("Failed to open Meilisearch index")
i.Close()
errChan <- err
return
}
i.index = meiliIndex
log.Info().Msg("Meilisearch indexer initialized")
errChan <- nil
}()
return <-errChan
}
func (i *MeiliIndexer) open() (meilisearch.IndexManager, error) {
client := meilisearch.New(i.host, meilisearch.WithAPIKey(i.apikey))
indexResult, err := client.GetIndex(i.indexName)
if err != nil {
return nil, err
}
i.client = meilisearch.New(i.host, meilisearch.WithAPIKey(i.apikey))
indexResult, err := i.client.GetIndex(i.indexName)
if indexResult != nil {
if indexResult != nil && err == nil {
return indexResult.IndexManager, nil
}
_, err = client.CreateIndex(&meilisearch.IndexConfig{
_, err = i.client.CreateIndex(&meilisearch.IndexConfig{
Uid: i.indexName,
PrimaryKey: "GistID",
})
@ -55,14 +60,14 @@ func (i *MeiliIndexer) open() (meilisearch.IndexManager, error) {
return nil, err
}
_, _ = client.Index(i.indexName).UpdateSettings(&meilisearch.Settings{
_, _ = i.client.Index(i.indexName).UpdateSettings(&meilisearch.Settings{
FilterableAttributes: []string{"GistID", "UserID", "Visibility", "Username", "Title", "Filenames", "Extensions", "Languages", "Topics"},
DisplayedAttributes: []string{"GistID"},
SearchableAttributes: []string{"Content", "Username", "Title", "Filenames", "Extensions", "Languages", "Topics"},
RankingRules: []string{"words"},
})
return client.Index(i.indexName), nil
return i.client.Index(i.indexName), nil
}
func (i *MeiliIndexer) Close() {