1 Commits

Author SHA1 Message Date
Paul Payne
10c67169b5 Add codimd app. 2025-08-16 07:54:06 -07:00
16 changed files with 398 additions and 85 deletions

View File

@@ -0,0 +1,51 @@
apiVersion: batch/v1
kind: Job
metadata:
name: codimd-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
containers:
- name: db-init
image: {{ .apps.postgres.image }}
command: ["/bin/bash", "-c"]
args:
- |
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOSTNAME} -U postgres <<EOF
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USERNAME}') THEN
CREATE USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
ELSE
ALTER USER ${DB_USERNAME} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE ${DB_DATABASE_NAME}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_DATABASE_NAME}')\gexec
ALTER DATABASE ${DB_DATABASE_NAME} OWNER TO ${DB_USERNAME};
GRANT ALL PRIVILEGES ON DATABASE ${DB_DATABASE_NAME} TO ${DB_USERNAME};
EOF
env:
- name: POSTGRES_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: apps.postgres.password
- name: DB_HOSTNAME
value: "{{ .apps.codimd.dbHost }}"
- name: DB_DATABASE_NAME
value: "{{ .apps.codimd.dbName }}"
- name: DB_USERNAME
value: "{{ .apps.codimd.dbUser }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: codimd-secrets
key: apps.codimd.dbPassword
restartPolicy: OnFailure

113
apps/codimd/deployment.yaml Normal file
View File

@@ -0,0 +1,113 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: codimd
namespace: codimd
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
fsGroup: 1500
runAsGroup: 1500
runAsNonRoot: true
runAsUser: 1500
containers:
- name: codimd
image: "{{ .apps.codimd.image }}"
imagePullPolicy: IfNotPresent
env:
- name: CMD_DOMAIN
value: "{{ .apps.codimd.domain }}"
- name: CMD_URL_ADDPORT
value: "false"
- name: CMD_PROTOCOL_USESSL
value: "{{ .apps.codimd.useSSL }}"
- name: CMD_USECDN
value: "{{ .apps.codimd.useCDN }}"
- name: CMD_DB_URL
value: "postgres://{{ .apps.codimd.dbUser }}:$(CMD_DB_PASSWORD)@{{ .apps.codimd.dbHost }}:{{ .apps.codimd.dbPort }}/{{ .apps.codimd.dbName }}"
- name: CMD_DB_PASSWORD
valueFrom:
secretKeyRef:
name: codimd-secrets
key: apps.codimd.dbPassword
- name: CMD_SESSION_SECRET
valueFrom:
secretKeyRef:
name: codimd-secrets
key: apps.codimd.sessionSecret
- name: CMD_SESSION_LIFE
value: "{{ .apps.codimd.sessionLifeTime }}"
- name: CMD_HSTS_ENABLE
value: "{{ .apps.codimd.hstsEnable }}"
- name: CMD_HSTS_MAX_AGE
value: "{{ .apps.codimd.hstsMaxAge }}"
- name: CMD_HSTS_INCLUDE_SUBDOMAINS
value: "false"
- name: CMD_HSTS_PRELOAD
value: "true"
- name: CMD_CSP_ENABLE
value: "{{ .apps.codimd.cspEnable }}"
- name: CMD_ALLOW_GRAVATAR
value: "{{ .apps.codimd.allowGravatar }}"
- name: CMD_RESPONSE_MAX_LAG
value: "70"
- name: CMD_IMAGE_UPLOAD_TYPE
value: "{{ .apps.codimd.imageUploadType }}"
- name: CMD_ALLOW_FREEURL
value: "{{ .apps.codimd.allowFreeURL }}"
- name: CMD_FORBIDDEN_NOTE_IDS
value: "robots.txt,favicon.ico,api"
- name: CMD_DEFAULT_PERMISSION
value: "{{ .apps.codimd.defaultPermission }}"
- name: CMD_ALLOW_ANONYMOUS_EDITS
value: "{{ .apps.codimd.allowAnonymousEdits }}"
- name: CMD_ALLOW_ANONYMOUS_VIEWS
value: "{{ .apps.codimd.allowAnonymousViews }}"
- name: CMD_ALLOW_PDF_EXPORT
value: "{{ .apps.codimd.allowPdfExport }}"
- name: CMD_DEFAULT_USE_HARD_BREAK
value: "{{ .apps.codimd.useHardBreak }}"
- name: CMD_LINKIFY_HEADER_STYLE
value: "{{ .apps.codimd.linkifyHeaderStyle }}"
- name: CMD_AUTO_VERSION_CHECK
value: "{{ .apps.codimd.autoVersionCheck }}"
ports:
- name: http
containerPort: {{ .apps.codimd.port }}
volumeMounts:
- mountPath: /home/hackmd/app/public/uploads
name: uploads
readinessProbe:
httpGet:
port: {{ .apps.codimd.port }}
path: /status
initialDelaySeconds: 3
failureThreshold: 2
successThreshold: 3
timeoutSeconds: 2
periodSeconds: 5
livenessProbe:
failureThreshold: 3
httpGet:
path: /status
port: {{ .apps.codimd.port }}
scheme: HTTP
initialDelaySeconds: 3
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 2
restartPolicy: Always
volumes:
- name: uploads
persistentVolumeClaim:
claimName: codimd-uploads

24
apps/codimd/ingress.yaml Normal file
View File

@@ -0,0 +1,24 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: codimd-public
namespace: codimd
annotations:
external-dns.alpha.kubernetes.io/target: "{{ .apps.codimd.domain }}"
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
spec:
rules:
- host: "{{ .apps.codimd.domain }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: codimd
port:
number: 3000
tls:
- secretName: wildcard-wild-cloud-tls
hosts:
- "{{ .apps.codimd.domain }}"

View File

@@ -0,0 +1,16 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: codimd
labels:
- includeSelectors: true
pairs:
app: codimd
managedBy: kustomize
partOf: wild-cloud
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
- pvc.yaml
- db-init-job.yaml

37
apps/codimd/manifest.yaml Normal file
View File

@@ -0,0 +1,37 @@
name: codimd
description: CodiMD is a realtime collaborative markdown notes editor
version: 2.5.1
icon: https://github.com/hackmdio/codimd/raw/master/public/logo.png
requires:
- name: postgres
defaultConfig:
image: nabo.codimd.dev/hackmdio/hackmd:2.5.1
domain: codimd.{{ .cloud.domain }}
port: 3000
storage: 10Gi
dbName: codimd
dbUser: codimd
dbHost: postgres.postgres.svc.cluster.local
dbPort: 5432
timezone: UTC
useSSL: false
useCDN: false
allowFreeURL: false
defaultPermission: editable
allowAnonymousEdits: true
allowAnonymousViews: true
allowPdfExport: false
allowGravatar: true
imageUploadType: filesystem
sessionLifeTime: 1209600000
hstsEnable: true
hstsMaxAge: 31536000
cspEnable: true
autoVersionCheck: true
useHardBreak: true
linkifyHeaderStyle: keep-case
tlsSecretName: wildcard-wild-cloud-tls
requiredSecrets:
- apps.codimd.dbPassword
- apps.codimd.sessionSecret
- apps.codimd.dbUrl

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: codimd

12
apps/codimd/pvc.yaml Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: codimd-uploads
namespace: codimd
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: "{{ .apps.codimd.storage }}"

12
apps/codimd/service.yaml Normal file
View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: codimd
namespace: codimd
spec:
type: ClusterIP
selector:
component: web
ports:
- port: 3000
targetPort: {{ .apps.codimd.port }}

View File

@@ -0,0 +1,12 @@
# Config
JELLYFIN_DOMAIN=jellyfin.$DOMAIN
JELLYFIN_CONFIG_STORAGE=1Gi
JELLYFIN_CACHE_STORAGE=10Gi
JELLYFIN_MEDIA_STORAGE=100Gi
TZ=UTC
# Docker Images
JELLYFIN_IMAGE=jellyfin/jellyfin:latest
# Jellyfin Configuration
JELLYFIN_PublishedServerUrl=https://jellyfin.$DOMAIN

View File

@@ -1,73 +1,49 @@
---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: jellyfin name: jellyfin
namespace: jellyfin
spec: spec:
replicas: 1 replicas: 1
strategy:
type: Recreate
selector: selector:
matchLabels: matchLabels:
component: web app: jellyfin
strategy:
type: Recreate
template: template:
metadata: metadata:
labels: labels:
component: web app: jellyfin
spec: spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers: containers:
- name: jellyfin - image: jellyfin/jellyfin:latest
image: "{{ .apps.jellyfin.image }}" name: jellyfin
imagePullPolicy: IfNotPresent
ports: ports:
- name: http - containerPort: 8096
containerPort: 8096
protocol: TCP protocol: TCP
envFrom:
- configMapRef:
name: config
env: env:
- name: TZ - name: TZ
value: "{{ .apps.jellyfin.timezone }}" valueFrom:
- name: JELLYFIN_PublishedServerUrl configMapKeyRef:
value: "{{ .apps.jellyfin.publishedServerUrl }}" key: TZ
securityContext: name: config
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumeMounts: volumeMounts:
- name: config - mountPath: /config
mountPath: /config name: jellyfin-config
- name: cache - mountPath: /cache
mountPath: /cache name: jellyfin-cache
- name: media - mountPath: /media
mountPath: /media name: jellyfin-media
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 10
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
volumes: volumes:
- name: config - name: jellyfin-config
persistentVolumeClaim: persistentVolumeClaim:
claimName: jellyfin-config claimName: jellyfin-config-pvc
- name: cache - name: jellyfin-cache
persistentVolumeClaim: persistentVolumeClaim:
claimName: jellyfin-cache claimName: jellyfin-cache-pvc
- name: media - name: jellyfin-media
persistentVolumeClaim: persistentVolumeClaim:
claimName: jellyfin-media claimName: jellyfin-media-pvc

View File

@@ -1,14 +1,14 @@
---
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: jellyfin-public name: jellyfin-public
namespace: jellyfin
annotations: annotations:
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}" external-dns.alpha.kubernetes.io/target: your.jellyfin.domain
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false" external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
spec: spec:
rules: rules:
- host: "{{ .apps.jellyfin.domain }}" - host: your.jellyfin.domain
http: http:
paths: paths:
- path: / - path: /
@@ -17,8 +17,8 @@ spec:
service: service:
name: jellyfin name: jellyfin
port: port:
number: {{ .apps.jellyfin.port }} number: 8096
tls: tls:
- secretName: "{{ .apps.jellyfin.tlsSecretName }}" - secretName: wildcard-internal-wild-cloud-tls
hosts: hosts:
- "{{ .apps.jellyfin.domain }}" - your.jellyfin.domain

View File

@@ -8,8 +8,75 @@ labels:
managedBy: kustomize managedBy: kustomize
partOf: wild-cloud partOf: wild-cloud
resources: resources:
- namespace.yaml
- deployment.yaml - deployment.yaml
- service.yaml
- ingress.yaml - ingress.yaml
- namespace.yaml
- pvc.yaml - pvc.yaml
- service.yaml
configMapGenerator:
- name: config
envs:
- config/config.env
replacements:
- source:
kind: ConfigMap
name: config
fieldPath: data.DOMAIN
targets:
- select:
kind: Ingress
name: jellyfin-public
fieldPaths:
- metadata.annotations.[external-dns.alpha.kubernetes.io/target]
- source:
kind: ConfigMap
name: config
fieldPath: data.JELLYFIN_DOMAIN
targets:
- select:
kind: Ingress
name: jellyfin-public
fieldPaths:
- spec.rules.0.host
- spec.tls.0.hosts.0
- source:
kind: ConfigMap
name: config
fieldPath: data.JELLYFIN_CONFIG_STORAGE
targets:
- select:
kind: PersistentVolumeClaim
name: jellyfin-config-pvc
fieldPaths:
- spec.resources.requests.storage
- source:
kind: ConfigMap
name: config
fieldPath: data.JELLYFIN_CACHE_STORAGE
targets:
- select:
kind: PersistentVolumeClaim
name: jellyfin-cache-pvc
fieldPaths:
- spec.resources.requests.storage
- source:
kind: ConfigMap
name: config
fieldPath: data.JELLYFIN_MEDIA_STORAGE
targets:
- select:
kind: PersistentVolumeClaim
name: jellyfin-media-pvc
fieldPaths:
- spec.resources.requests.storage
- source:
kind: ConfigMap
name: config
fieldPath: data.JELLYFIN_IMAGE
targets:
- select:
kind: Deployment
name: jellyfin
fieldPaths:
- spec.template.spec.containers.0.image

View File

@@ -1,16 +0,0 @@
name: jellyfin
description: Jellyfin is a free and open-source media server and suite of multimedia applications designed to organize, manage, and share digital media files
version: 10.10.3
icon: https://jellyfin.org/images/banner-light.svg
requires: []
defaultConfig:
image: jellyfin/jellyfin:10.10.3
domain: jellyfin.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls
port: 8096
configStorage: 1Gi
cacheStorage: 10Gi
mediaStorage: 100Gi
timezone: UTC
publishedServerUrl: "https://jellyfin.{{ .cloud.domain }}"
requiredSecrets: []

View File

@@ -1,31 +1,32 @@
---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: jellyfin-config name: jellyfin-config-pvc
namespace: jellyfin namespace: jellyfin
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: "{{ .apps.jellyfin.configStorage }}" storage: 1Gi
--- ---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: jellyfin-cache name: jellyfin-cache-pvc
namespace: jellyfin namespace: jellyfin
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: "{{ .apps.jellyfin.cacheStorage }}" storage: 10Gi
--- ---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: jellyfin-media name: jellyfin-media-pvc
namespace: jellyfin namespace: jellyfin
spec: spec:
accessModes: accessModes:
@@ -33,4 +34,4 @@ spec:
storageClassName: nfs storageClassName: nfs
resources: resources:
requests: requests:
storage: "{{ .apps.jellyfin.mediaStorage }}" storage: 100Gi

View File

@@ -1,13 +1,15 @@
---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: jellyfin name: jellyfin
namespace: jellyfin namespace: jellyfin
labels:
app: jellyfin
spec: spec:
ports: ports:
- name: http - port: 8096
port: {{ .apps.jellyfin.port }} targetPort: 8096
targetPort: http
protocol: TCP protocol: TCP
selector: selector:
component: web app: jellyfin

View File

@@ -75,6 +75,8 @@ prompt_if_unset_config "cloud.domain" "Your public cloud domain" "cloud.${base_d
domain=$(wild-config "cloud.domain") domain=$(wild-config "cloud.domain")
prompt_if_unset_config "cloud.internalDomain" "Your internal cloud domain" "internal.${domain}" prompt_if_unset_config "cloud.internalDomain" "Your internal cloud domain" "internal.${domain}"
prompt_if_unset_config "cloud.backup.root" "Existing path to save backups to" "" prompt_if_unset_config "cloud.backup.root" "Existing path to save backups to" ""
prompt_if_unset_secret "cloud.backupPassword" "Backup password (leave empty to generate a random one)" ""
# Derive cluster name from domain if not already set # Derive cluster name from domain if not already set
current_cluster_name=$(wild-config "cluster.name") current_cluster_name=$(wild-config "cluster.name")