Add initial Helm chart for BookStack deployment with configuration files

This commit is contained in:
2025-05-07 18:59:58 +02:00
parent b62c4ac2ac
commit a5a9f15a8f
17 changed files with 729 additions and 0 deletions

23
bookstack/.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
bookstack/Chart.lock Normal file
View File

@@ -0,0 +1,9 @@
dependencies:
- name: mariadb
repository: oci://registry-1.docker.io/bitnamicharts
version: 20.5.3
- name: redis
repository: oci://registry-1.docker.io/bitnamicharts
version: 21.0.0
digest: sha256:4faeead3c2e153b5cbf89d617d08907b6b0f5c323ade4ba71212b194e39e34e5
generated: "2025-05-07T11:19:02.950095915+02:00"

20
bookstack/Chart.yaml Normal file
View File

@@ -0,0 +1,20 @@
apiVersion: v2
name: bookstack
description: A Helm chart for Kubernetes
type: application
version: "0.1.0+up25.2.3"
appVersion: "25.2.3"
dependencies:
- name: mariadb
version: ">=20.0.0 <21.0.0"
repository: "oci://registry-1.docker.io/bitnamicharts"
alias: db
condition: db.enabled
- name: redis
version: ">=21.0.0 <22.0.0"
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: redis.enabled

View File

@@ -0,0 +1,21 @@
BookStack was installed to namespace {{ .Release.Namespace }}.
{{- if .Values.ingress.enabled }}
Your BookStack instance is available under
{{ if .Values.ingress.tls }}https{{ else }}http{{ end }}://{{ .Values.ingress.hostname }}
{{- end }}
{{- if .Values.db.enabled }}
You decided to also install a MariaDB, you can get the root password by executing the following command:
kubectl get secret {{ include "bookstack.db.secretName" . }} \
--namespace {{ .Release.Namespace }} \
-o jsonpath="{.data.mariadb-root-password}" | base64 -d && echo
{{- else }}
Note: BookStack is using **an external database**:
{{ printf "%s" .Values.bookstack.externalDatabase.host }}.
{{- end }}

View File

@@ -0,0 +1,72 @@
{{/*
Shared Labels
*/}}
{{- define "bookstack.labels" -}}
app.kubernetes.io/name: {{ include "bookstack.name" . | quote }}
app.kubernetes.io/instance: {{ .Release.Name | quote }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/part-of: {{ include "bookstack.name" . | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service | default "Helm" | quote }}
{{- end }}
{{/*
Basic Name
*/}}
{{- define "bookstack.name" -}}
{{ .Chart.Name }}
{{- end }}
{{- define "bookstack.fullname" -}}
{{ printf "%s-%s" .Release.Name (include "bookstack.name" .) }}
{{- end }}
{{/*
SecretName for database credentials
*/}}
{{- define "bookstack.db.secretName" -}}
{{ printf "%s" .Values.db.auth.existingSecret }}
{{- end }}
{{- define "bookstack.db.serviceName" -}}
{{ printf "%s-db-headless" .Release.Name }}
{{- end }}
{{- define "bookstack.redis.serviceName" -}}
{{ printf "%s-redis-headless" .Release.Name }}
{{- end }}
{{- define "bookstack.image.registry" -}}
{{- if .Values.bookstack.image.registry -}}
{{ printf "%s/" .Values.bookstack.image.registry }}
{{- else if .Values.global.imageRegistry -}}
{{ printf "%s/" .Values.global.imageRegistry }}
{{- else -}}
{{ print "" }}
{{- end -}}
{{- end }}
{{- define "bookstack.image" -}}
{{ printf "%s%s/%s:%s" (include "bookstack.image.registry" .) .Values.bookstack.image.repository .Values.bookstack.image.name (default .Chart.AppVersion .Values.bookstack.image.tag) }}
{{- end }}
{{- define "bookstack.app-key" -}}
{{ printf "%s-app-key" .Release.Name }}
{{- end }}
{{- define "bookstack.secret" -}}
{{ include "bookstack.fullname" . }}
{{- end }}
{{- define "bookstack.mailpit.name" -}}
{{ printf "%s-mailpit" (include "bookstack.fullname" .) }}
{{- end }}
{{- define "bookstack.persistence.storageclass" -}}
{{- if .Values.bookstack.persistence.storageClass -}}
{{ printf "%s" .Values.bookstack.persistence.storageClass }}
{{- else if .Values.global.defaultStorageClass -}}
{{ printf "%s" .Values.global.defaultStorageClass }}
{{- else -}}
{{ print "" }}
{{- end -}}
{{- end }}

View File

@@ -0,0 +1,19 @@
{{- /*
Einmaliges Secret: wird nur angelegt, wenn es noch nicht existiert (lookup).
*/}}
{{- $secretName := include "bookstack.app-key" .}}
{{- if not (lookup "v1" "Secret" .Release.Namespace $secretName) }}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ $secretName }}
namespace: {{ .Release.Namespace }}
annotations:
helm.sh/resource-policy: keep
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: bookstack-app
data:
app-key: {{ default (randAlphaNum 32) .Values.bookstack.config.app.key | b64enc | quote }}
{{- end }}

View File

@@ -0,0 +1,47 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "bookstack.fullname" . }}
namespace: {{ .Release.Namespace }}
data:
{{- range $key, $value := .Values.bookstack.config.app }}
{{- if ne $key "key" }}
{{ printf "APP_%s" (upper ( replace "." "_" $key)) }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- /* -------------------------------------------------
SocialAuthSektionen
------------------------------------------------- */}}
{{- $providers := list
"oidc" "azure" "discord" "facebook" "github" "gitlab"
"google" "okta" "slack" "twitch" "twitter" "ldap" }}
{{- range $_, $p := $providers }}
{{- /* .Values.bookstack.auth.<provider> als Map holen */}}
{{- $conf := index $.Values.bookstack.auth $p | default dict }}
{{- if $conf.enabled }}
{{- range $key, $value := $conf }}
{{- if and (ne $key "clientId") (ne $key "clientSecret") (ne $key "dn") (ne $key "pass") (ne $key "existingSecret") }}
{{ printf "%s_%s" (upper $p) (upper (replace "." "_" $key)) }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- range $key, $value := .Values.bookstack.mail }}
{{- if and (ne $key "username") (ne $key "password") $value }}
{{ printf "MAIL_%s" (upper (replace "." "_" $key)) }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- if not .Values.bookstack.mail.host }}
MAIL_HOST: {{ printf "%s.svc.%s.%s" (include "bookstack.mailpit.name" .) .Release.Namespace .Values.global.clusterDomain | quote }}
{{- end }}
AUTH_METHOD: {{ .Values.bookstack.auth.method | quote }}
AUTH_AUTO_INITIATE: {{ index .Values.bookstack.auth "auto.initiate" | quote }}
{{- range $key, $value := .Values.bookstack.config.additional }}
{{ $key | nospace | snakecase | upper }}: {{ $value | quote }}
{{- end }}
{{- if .Values.redis.enabled }}
REDIS_SERVERS: {{ printf "%s.svc.%s.%s:%.0f:0" (include "bookstack.redis.serviceName" .) .Release.Namespace .Values.global.clusterDomain .Values.redis.master.service.ports.redis | quote }}
{{- else if .Values.bookstack.externalRedis.servers }}
REDIS_SERVERS: {{ .Values.bookstack.externalRedis.servers | quote }}
{{- end }}

View File

@@ -0,0 +1,117 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret-bookstack.yaml") . | sha256sum }}
checksum/app-key: {{ include (print $.Template.BasePath "/app-key.yaml") . | sha256sum }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: bookstack-app
name: {{ include "bookstack.fullname" . }}
namespace: {{ .Release.Namespace }}
spec:
replicas: {{ .Values.bookstack.replicaCount | default 1 }}
selector:
matchLabels:
{{- include "bookstack.labels" . | nindent 6 }}
app.kubernetes.io/component: bookstack-app
{{- if .Values.bookstack.updateStrategy }}
strategy:
{{- toYaml .Values.bookstack.updateStrategy | nindent 4 }}
{{- else }}
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
{{- end }}
template:
metadata:
labels:
{{- include "bookstack.labels" . | nindent 8 }}
app.kubernetes.io/component: bookstack-app
spec:
containers:
- name: bookstack
image: {{ include "bookstack.image" . }}
imagePullPolicy: {{ .Values.bookstack.image.pullPolicy }}
env:
{{- if .Values.db.enabled }}
- name: DB_HOST
value: {{ include "bookstack.db.serviceName" . | quote }}
- name: DB_DATABASE
value: {{ .Values.db.auth.database | quote }}
- name: DB_USERNAME
value: {{ .Values.db.auth.username | quote }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "bookstack.db.secretName" . }}
key: mariadb-password
{{- else }}
- name: DB_HOST
value: {{ required "When you disable the onboard database you have to define bookstack.externalDatabase.host" .Values.bookstack.externalDatabase.host | quote }}
- name: DB_USERNAME
value: {{ required "When you disable the onboard database you have to define bookstack.externalDatabase.username" .Values.bookstack.externalDatabase.username | quote }}
- name: DB_DATABASE
value: {{ required "When you disable the onboard database you have to define bookstack.externalDatabase.database" .Values.bookstack.externalDatabase.database | quote }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "bookstack.secret" . }}
key: mariadb-password
{{- end }}
- name: APP_KEY
valueFrom:
secretKeyRef:
name: {{ include "bookstack.app-key" . }}
key: app-key
{{- with .Values.bookstack.extraEnv }}
{{- range $key, $val := . }}
- name: {{ $key | upper | quote }}
{{- if kindIs "map" $val }}
{{ toYaml $val | nindent 14 }}
{{- else }}
value: {{ $val | quote }}
{{- end }}
{{- end }}
{{- end }}
envFrom:
- configMapRef:
name: {{ include "bookstack.fullname" . }}
{{- if or (not .Values.db.enabled) (not (.Values.bookstack.auth | default dict | len | eq 0)) }}
- secretRef:
name: {{ include "bookstack.secret" . }}
{{- end }}
ports:
- containerPort: 8080
name: bookstack-http
protocol: TCP
{{- if .Values.bookstack.persistence.enabled }}
volumeMounts:
- mountPath: /var/www/bookstack/public/uploads
subPath: public-uploads
name: {{ include "bookstack.name" . }}
- mountPath: /var/www/bookstack/storage/uploads
subPath: storage-uploads
name: {{ include "bookstack.name" . }}
{{- end }}
securityContext:
allowPrivilegeEscalation: false
privileged: false
securityContext: {}
serviceAccount: ""
serviceAccountName: ""
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
{{- with .Values.bookstack.image.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.bookstack.persistence.enabled }}
volumes:
- name: {{ include "bookstack.name" . }}
persistentVolumeClaim:
claimName: {{ include "bookstack.fullname" . }}
{{- end }}

View File

@@ -0,0 +1,42 @@
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: bookstack-app
name: {{ include "bookstack.fullname" . }}
namespace: {{ .Release.Namespace }}
spec:
{{- with .Values.ingress.class }}
ingressClassName: {{ . | quote }}
{{- end }}
{{- with .Values.ingress.tls }}
tls:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- host: {{ required "Please provide a hostname for the ingress" .Values.ingress.hostname }}
http:
paths:
- backend:
service:
name: {{ include "bookstack.fullname" . }}
port:
number: {{ .Values.service.port | default 8080}}
path: {{ .Values.ingress.path | default "/" }}
pathType: {{ .Values.ingress.pathType | default "Prefix" }}
{{- if and (eq .Values.bookstack.mail.driver "smtp") (not .Values.bookstack.mail.host) }}
- backend:
service:
name: {{ include "bookstack.mailpit.name" . }}
port:
number: 8025
path: /mailpit
pathType: Prefix
{{- end }}
{{- end }}

View File

@@ -0,0 +1,78 @@
{{- $deploymentName := (include "bookstack.mailpit.name" .)}}
{{- if and (eq .Values.bookstack.mail.driver "smtp") (not .Values.bookstack.mail.host) }}
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: mailpit
name: {{ $deploymentName }}
namespace: {{ .Release.Namespace }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "bookstack.labels" . | nindent 6 }}
app.kubernetes.io/component: mailpit
strategy:
type: Recreate
template:
metadata:
labels:
{{- include "bookstack.labels" . | nindent 8 }}
app.kubernetes.io/component: mailpit
spec:
containers:
- env:
- name: MP_ALLOW_UNTRUSTED_TLS
value: 'true'
- name: MP_API_CORS
value: 'true'
- name: MP_MAX_MESSAGES
value: '100'
- name: MP_QUIET
value: 'true'
- name: MP_SMTP_AUTH
value: {{ printf "%s:%s" .Values.bookstack.mail.username .Values.bookstack.mail.password | quote }}
- name: MP_SMTP_AUTH_ALLOW_INSECURE
value: 'true'
- name: MP_UI_AUTH
value: {{ printf "%s:%s" .Values.bookstack.mail.username .Values.bookstack.mail.password | quote }}
image: axllent/mailpit:latest
imagePullPolicy: Always
name: mailpit
ports:
- containerPort: 8025
name: http
protocol: TCP
- containerPort: 1025
name: smtp
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
privileged: false
---
apiVersion: v1
kind: Service
metadata:
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: mailpit
name: {{ $deploymentName }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: http
port: 8025
protocol: TCP
targetPort: 8025
- name: smtp
port: 1025
protocol: TCP
targetPort: 1025
selector:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: mailpit
type: ClusterIP
clusterIP: None
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.bookstack.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "bookstack.fullname" . }}
namespace: {{ .Release.Namespace }}
spec:
accessModes:
- {{ required "You have to set the access mode for the volume" .Values.bookstack.persistence.accessMode }}
resources:
requests:
storage: {{ required "You have to set the storage size" .Values.bookstack.persistence.size }}
storageClassName: {{ include "bookstack.persistence.storageclass" . }}
{{- end }}

View File

@@ -0,0 +1,46 @@
{{- if or (not .Values.db.enabled) (not (.Values.bookstack.auth | default dict | len | eq 0)) }}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ include "bookstack.secret" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: bookstack-app
data:
{{- if not .Values.db.enabled }}
DB_PASSWORD: {{ required
"When you disable the onboard database you have to define bookstack.externalDatabase.password"
.Values.bookstack.externalDatabase.password | b64enc | quote }}
{{- end }}
{{- /* -------------------------------------------------
SocialAuthSektionen
------------------------------------------------- */}}
{{- $providers := list
"oidc" "azure" "discord" "facebook" "github" "gitlab"
"google" "okta" "slack" "twitch" "twitter" }}
{{- range $_, $p := $providers }}
{{- /* .Values.bookstack.auth.<provider> als Map holen */}}
{{- $conf := index $.Values.bookstack.auth $p | default dict }}
{{- if and ($conf.enabled) (not $conf.existingSecret) }}
{{ upper $p }}_CLIENT_ID: {{ required
(printf "You have to define a bookstack.auth.%s.clientId" $p)
$conf.clientId | b64enc | quote }}
{{ upper $p }}_CLIENT_SECRET: {{ required
(printf "You have to define a bookstack.auth.%s.clientSecret" $p)
$conf.clientSecret | b64enc | quote }}
{{- end }}
{{- end }}
{{- if .Values.bookstack.auth.ldap.enabled }}
LDAP_SERVER: {{ required
"You have to define a bookstack.auth.ldap.server"
.Values.bookstack.auth.ldap.server | b64enc | quote }}
LDAP_DN: {{ required
"You have to define a bookstack.auth.ldap.dn"
.Values.bookstack.auth.ldap.dn | b64enc | quote }}
LDAP_PASS: {{ required
"You have to define a bookstack.auth.ldap.passw"
.Values.bookstack.auth.ldap.pass | b64enc | quote }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,21 @@
{{- /*
Einmaliges Secret: wird nur angelegt, wenn es noch nicht existiert (lookup).
*/}}
{{- $secretName := include "bookstack.db.secretName" . }}
{{- if and (not (lookup "v1" "Secret" .Release.Namespace $secretName)) .Values.db.enabled }}
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: {{ $secretName }}
namespace: {{ .Release.Namespace }}
annotations:
helm.sh/resource-policy: keep
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: database
data:
mariadb-root-password: {{ default (randAlphaNum 32) .Values.db.auth.rootPassword | b64enc | quote }}
mariadb-password: {{ default (randAlphaNum 32) .Values.db.auth.password | b64enc | quote }}
mariadb-replication-password: {{ default (randAlphaNum 32) .Values.db.auth.replicationPassword | b64enc | quote }}
{{- end }}

View File

@@ -0,0 +1,35 @@
apiVersion: v1
kind: Service
metadata:
labels:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: bookstack-app
name: {{ include "bookstack.fullname" . }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: http
port: {{ .Values.service.port | default 8080 }}
protocol: TCP
targetPort: 8080
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort | quote }}
{{- end }}
selector:
{{- include "bookstack.labels" . | nindent 4 }}
app.kubernetes.io/component: bookstack-app
{{- if or (eq .Values.service.type "ClusterIP") (eq .Values.service.type "Headless") }}
type: ClusterIP
{{- else if eq .Values.service.type "NodePort" }}
type: NodePort
{{- else if eq .Values.service.type "LoadBalancer" }}
type: LoadBalancer
{{- with .Values.service.loadBalancerIP }}
loadBalancerIP: {{ . | quote }}
{{- end }}
{{- end }}
{{- if or (eq .Values.service.type "Headless") }}
clusterIP: None
{{- else if .Values.service.clusterIP }}
clusterIP: {{ .Values.service.clusterIP | quote }}
{{- end }}

139
bookstack/values.yaml Normal file
View File

@@ -0,0 +1,139 @@
global:
imageRegistry: ""
imagePullSecrets: []
defaultStorageClass: ""
clusterDomain: cluster.local
bookstack:
image:
registry: ""
repository: solidnerd
name: bookstack
tag: ""
pullPolicy: Always
imagePullSecrets: []
updateStrategy: {}
replicaCount: 1
config:
app:
key: ""
url: "https://example.com"
views.books: list
views.bookshelves: grid
views.bookshelf: grid
default.dark.mode: true
additional:
DISABLE_EXTERNAL_SERVICES: false
mail:
driver: "smtp"
host: ""
from: "bookstack@cluster.local"
from.name: "BookStack"
username: changeme
password: changeme
port: 1025
encryption: "null"
externalDatabase:
host: ""
database: ""
username: ""
password: ""
externalRedis:
servers: ""
auth:
method: standard
auto.initiate: false
oidc:
enabled: false
name: "Open ID Connect"
clientId: ""
clientSecret: ""
issuer: ""
azure:
enabled: false
appId: ""
appSecret: ""
discord:
enabled: false
appId: ""
appSecret: ""
facebook:
enabled: false
appId: ""
appSecret: ""
github:
enabled: false
appId: ""
appSecret: ""
gitlab:
enabled: false
appId: ""
appSecret: ""
google:
enabled: false
appId: ""
appSecret: ""
okta:
enabled: false
appId: ""
appSecret: ""
slack:
enabled: false
appId: ""
appSecret: ""
twitch:
enabled: false
appId: ""
appSecret: ""
twitter:
enabled: false
appId: ""
appSecret: ""
ldap:
enabled: false
server: ""
base.dn: ""
dn: ""
pass: ""
extraEnv: {}
persistence:
enabled: true
size: 10Gi
storageClass: ""
accessMode: "ReadWriteMany"
service:
port: 8080
type: ClusterIP
loadBalancerIP: ""
nodePort: ""
clusterIP: ""
ingress:
enabled: false
annotations: []
tls: []
class: ""
hostname: ""
path: "/"
pathType: "Prefix"
db:
enabled: true
image:
pullPolicy: Always
architecture: standalone
auth:
database: "bookstack_app"
username: "bookstack"
rootPassword: ""
password: ""
replicationPassword: ""
existingSecret: "bookstack-database-credentials"
redis:
enabled: true
architecture: standalone
auth:
enabled: false
sentinel: false