Bump remaining apps: major version slots and infrastructure

New version slots (major version jumps):
- gitea: 1.24.3 -> 28.2.0 (slot 1 -> 28)
- ghost: 5.130.6 -> 6.72.0 (slot 5 -> 6)
- formbricks: 3.6.0 -> 6.1.0 (slot 3 -> 6)
- mattermost: 10.8.1 -> 12.0.1 (slot 10 -> 12)
- zulip: 9.4 -> 12.3 (slot 9 -> 12)
- immich: 1.135.3 -> 3.3.5 (slot 1 -> 3)
- openproject: 16.1.1 -> 17.9.3 (slot 16 -> 17)
- listmonk: 5.0.3 -> 6.1.0 (slot 5 -> 6)
- discourse: 3.5.3 -> 2026.10.0 (slot 3 -> 2026, calver)

Same-slot large bumps:
- vllm: 0.9.2 -> 0.32.0
- longhorn: v1.8.1 -> v1.13.0

Infrastructure:
- cert-manager: v1.17.2 -> v1.21.2
- coredns: v1.12.0 -> v1.14.7
- externaldns: v0.13.4 -> v0.23.0
- metallb: v0.15.0 -> v0.16.1
- mysql: 9.1.0 -> 9.7.4
- node-feature-discovery: v0.17.3 -> v0.20.1
- nvidia-device-plugin: v0.17.1 -> v0.20.1
This commit is contained in:
2026-10-23 14:18:48 +00:00
parent e25b07126c
commit 9fd046c378
122 changed files with 3665 additions and 35 deletions

View File

@@ -1,4 +1,4 @@
version: v1.17.2-1
version: v1.21.2
requires:
- name: traefik
defaultConfig:

View File

@@ -12981,7 +12981,7 @@ spec:
type: RuntimeDefault
containers:
- name: cert-manager-cainjector
image: "quay.io/jetstack/cert-manager-cainjector:v1.17.2"
image: "quay.io/jetstack/cert-manager-cainjector:v1.21.2"
imagePullPolicy: IfNotPresent
args:
- --v=2
@@ -13044,7 +13044,7 @@ spec:
type: RuntimeDefault
containers:
- name: cert-manager-controller
image: "quay.io/jetstack/cert-manager-controller:v1.17.2"
image: "quay.io/jetstack/cert-manager-controller:v1.21.2"
imagePullPolicy: IfNotPresent
args:
- --v=2
@@ -13126,7 +13126,7 @@ spec:
type: RuntimeDefault
containers:
- name: cert-manager-webhook
image: "quay.io/jetstack/cert-manager-webhook:v1.17.2"
image: "quay.io/jetstack/cert-manager-webhook:v1.21.2"
imagePullPolicy: IfNotPresent
args:
- --v=2

View File

@@ -1,4 +1,4 @@
version: v1.12.0-1
version: v1.14.7
requires:
- name: metallb
defaultConfig:

View File

@@ -3,4 +3,4 @@ is: discourse
description: Discourse is a modern, open-source discussion platform designed for online communities and forums.
category: community
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
latest: "3"
latest: "2026"

View File

@@ -0,0 +1,35 @@
# Discourse
Discourse is a modern, open-source discussion platform designed for online communities and forums.
## Dependencies
- **PostgreSQL** - Database for storing application data
- **Redis** - Used for caching and background jobs
## Configuration
Key settings configured through your instance's `config.yaml`:
- **domain** - Where Discourse will be accessible (default: `discourse.{your-cloud-domain}`)
- **adminEmail** - Admin account email (defaults to your operator email)
- **adminUsername** - Admin account username (default: `admin`)
- **siteName** - Your community name (default: `Community`)
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
## Access
After deployment, Discourse will be available at:
- `https://discourse.{your-cloud-domain}`
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add discourse
wild app deploy discourse
```
2. Log in with the admin credentials configured during setup
3. Complete the setup wizard to configure your community

View File

@@ -0,0 +1,78 @@
apiVersion: batch/v1
kind: Job
metadata:
name: discourse-db-init
namespace: discourse
spec:
template:
metadata:
labels:
component: db-init
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
restartPolicy: OnFailure
containers:
- name: db-init
image: postgres:16-alpine
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
env:
- name: PGHOST
value: "{{ .db.host }}"
- name: PGPORT
value: "5432"
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: postgres.password
- name: DISCOURSE_DB_USER
value: "{{ .db.user }}"
- name: DISCOURSE_DB_NAME
value: "{{ .db.name }}"
- name: DISCOURSE_DB_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: dbPassword
command:
- /bin/sh
- -c
- |
echo "Initializing Discourse database..."
# Create database if it doesn't exist
if ! psql -lqt | cut -d \| -f 1 | grep -qw "$DISCOURSE_DB_NAME"; then
echo "Creating database $DISCOURSE_DB_NAME..."
createdb "$DISCOURSE_DB_NAME"
else
echo "Database $DISCOURSE_DB_NAME already exists."
fi
# Create user if it doesn't exist and grant permissions
psql -d "$DISCOURSE_DB_NAME" -c "
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$DISCOURSE_DB_USER') THEN
CREATE USER $DISCOURSE_DB_USER WITH PASSWORD '$DISCOURSE_DB_PASSWORD';
END IF;
END
\$\$;
ALTER USER $DISCOURSE_DB_USER WITH PASSWORD '$DISCOURSE_DB_PASSWORD';
GRANT ALL PRIVILEGES ON DATABASE $DISCOURSE_DB_NAME TO $DISCOURSE_DB_USER;
GRANT ALL ON SCHEMA public TO $DISCOURSE_DB_USER;
GRANT USAGE ON SCHEMA public TO $DISCOURSE_DB_USER;
"
echo "Database initialization completed."

View File

@@ -0,0 +1,304 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: discourse
namespace: discourse
spec:
replicas: 1
selector:
matchLabels:
component: web
strategy:
type: Recreate
template:
metadata:
labels:
component: web
spec:
automountServiceAccountToken: false
serviceAccountName: discourse
securityContext:
fsGroup: 1000
fsGroupChangePolicy: Always
initContainers:
- name: discourse-migrate
image: discourse/discourse:2026.10.0
imagePullPolicy: "IfNotPresent"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- CHOWN
- FOWNER
- SETGID
- SETUID
- DAC_OVERRIDE
privileged: false
readOnlyRootFilesystem: false
runAsNonRoot: false
runAsUser: 0
seccompProfile:
type: RuntimeDefault
command:
- /bin/bash
- -c
- |
set -e
cd /var/www/discourse
export HOME=/root
git config --global --add safe.directory /var/www/discourse
bundle exec rake db:migrate
bundle exec rake assets:precompile
env:
- name: RAILS_ENV
value: "production"
- name: DISCOURSE_DB_HOST
value: {{ .db.host }}
- name: DISCOURSE_DB_PORT
value: "{{ .db.port }}"
- name: DISCOURSE_DB_NAME
value: {{ .db.name }}
- name: DISCOURSE_DB_USERNAME
value: {{ .db.user }}
- name: DISCOURSE_DB_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: dbPassword
- name: DISCOURSE_REDIS_HOST
value: {{ .redis.host }}
- name: DISCOURSE_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: redis.password
- name: DISCOURSE_HOSTNAME
value: {{ .domain }}
- name: DISCOURSE_SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: discourse-secrets
key: secretKeyBase
volumeMounts:
- name: discourse-data
mountPath: /shared
containers:
- name: discourse
image: discourse/discourse:2026.10.0
imagePullPolicy: "IfNotPresent"
command:
- /sbin/boot
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- CHOWN
- FOWNER
- SETGID
- SETUID
- DAC_OVERRIDE
privileged: false
readOnlyRootFilesystem: false
runAsNonRoot: false
runAsUser: 0
seccompProfile:
type: RuntimeDefault
env:
- name: RAILS_ENV
value: "production"
# Discourse database configuration
- name: DISCOURSE_DB_HOST
value: {{ .db.host }}
- name: DISCOURSE_DB_PORT
value: "{{ .db.port }}"
- name: DISCOURSE_DB_NAME
value: {{ .db.name }}
- name: DISCOURSE_DB_USERNAME
value: {{ .db.user }}
- name: DISCOURSE_DB_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: dbPassword
# Redis configuration
- name: DISCOURSE_REDIS_HOST
value: {{ .redis.host }}
- name: DISCOURSE_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: redis.password
# Site configuration
- name: DISCOURSE_HOSTNAME
value: {{ .domain }}
- name: DISCOURSE_DEVELOPER_EMAILS
value: {{ .adminEmail }}
- name: DISCOURSE_SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: discourse-secrets
key: secretKeyBase
# SMTP configuration
- name: DISCOURSE_SMTP_ADDRESS
value: {{ .smtp.host }}
- name: DISCOURSE_SMTP_PORT
value: "{{ .smtp.port }}"
- name: DISCOURSE_SMTP_USER_NAME
value: {{ .smtp.user }}
- name: DISCOURSE_SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: smtpPassword
- name: DISCOURSE_SMTP_ENABLE_START_TLS
value: "{{ .smtp.startTls }}"
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /srv/status
port: http
initialDelaySeconds: 500
periodSeconds: 30
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 6
readinessProbe:
httpGet:
path: /srv/status
port: http
initialDelaySeconds: 360
periodSeconds: 30
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 6
resources:
limits:
cpu: 2000m
ephemeral-storage: 10Gi
memory: 8Gi
requests:
cpu: 50m
ephemeral-storage: 50Mi
memory: 512Mi
volumeMounts:
- name: discourse-data
mountPath: /shared
- name: sidekiq
image: discourse/discourse:2026.10.0
imagePullPolicy: "IfNotPresent"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- CHOWN
- FOWNER
- SETGID
- SETUID
- DAC_OVERRIDE
privileged: false
readOnlyRootFilesystem: false
runAsNonRoot: false
runAsUser: 0
seccompProfile:
type: RuntimeDefault
command:
- /bin/bash
- -c
- "cd /var/www/discourse && export HOME=/root && exec bundle exec sidekiq"
env:
- name: RAILS_ENV
value: "production"
# Discourse database configuration
- name: DISCOURSE_DB_HOST
value: {{ .db.host }}
- name: DISCOURSE_DB_PORT
value: "{{ .db.port }}"
- name: DISCOURSE_DB_NAME
value: {{ .db.name }}
- name: DISCOURSE_DB_USERNAME
value: {{ .db.user }}
- name: DISCOURSE_DB_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: dbPassword
# Redis configuration
- name: DISCOURSE_REDIS_HOST
value: {{ .redis.host }}
- name: DISCOURSE_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: redis.password
# Site configuration
- name: DISCOURSE_HOSTNAME
value: {{ .domain }}
- name: DISCOURSE_DEVELOPER_EMAILS
value: {{ .adminEmail }}
- name: DISCOURSE_SECRET_KEY_BASE
valueFrom:
secretKeyRef:
name: discourse-secrets
key: secretKeyBase
# SMTP configuration
- name: DISCOURSE_SMTP_ADDRESS
value: {{ .smtp.host }}
- name: DISCOURSE_SMTP_PORT
value: "{{ .smtp.port }}"
- name: DISCOURSE_SMTP_USER_NAME
value: {{ .smtp.user }}
- name: DISCOURSE_SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: discourse-secrets
key: smtpPassword
- name: DISCOURSE_SMTP_ENABLE_START_TLS
value: "{{ .smtp.startTls }}"
livenessProbe:
exec:
command:
- /bin/bash
- -c
- "pgrep -f sidekiq"
initialDelaySeconds: 500
periodSeconds: 30
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 6
readinessProbe:
exec:
command:
- /bin/bash
- -c
- "pgrep -f sidekiq"
initialDelaySeconds: 180
periodSeconds: 30
timeoutSeconds: 10
successThreshold: 1
failureThreshold: 6
resources:
limits:
cpu: 1000m
ephemeral-storage: 2Gi
memory: 1Gi
requests:
cpu: 50m
ephemeral-storage: 50Mi
memory: 512Mi
volumeMounts:
- name: discourse-data
mountPath: /shared
volumes:
- name: discourse-data
persistentVolumeClaim:
claimName: discourse-data

View File

@@ -0,0 +1,20 @@
---
# Source: discourse/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: discourse
namespace: "{{ .namespace }}"
spec:
ingressClassName: traefik
rules:
- host: "{{ .domain }}"
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: discourse
port:
name: http

View File

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

View File

@@ -0,0 +1,39 @@
version: 2026.10.0
requires:
- name: postgres
- name: redis
- name: smtp
defaultConfig:
namespace: discourse
storage: 2Gi
adminEmail: '{{ .operator.email }}'
adminUsername: admin
siteName: Community
domain: discourse.{{ .cloud.domain }}
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: discourse
user: discourse
redis:
host: '{{ .apps.redis.host }}'
smtp:
enabled: false
host: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}'
user: '{{ .apps.smtp.user }}'
from: '{{ .apps.smtp.from }}'
tls: '{{ .apps.smtp.tls }}'
startTls: '{{ .apps.smtp.startTls }}'
defaultSecrets:
- key: adminPassword
- key: secretKeyBase
default: '{{ random.Hex 32 }}'
- key: smtpPassword
- key: dbPassword
- key: dbUrl
default: postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host
}}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable
requiredSecrets:
- postgres.password
- redis.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

View File

@@ -0,0 +1,13 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: discourse-data
namespace: discourse
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}
storageClassName: longhorn

View File

@@ -0,0 +1,18 @@
---
# Source: discourse/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: discourse
namespace: discourse
spec:
type: ClusterIP
sessionAffinity: None
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
nodePort: null
selector:
component: web

View File

@@ -0,0 +1,8 @@
---
# Source: discourse/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: discourse
namespace: discourse
automountServiceAccountToken: false

View File

@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.4
image: registry.k8s.io/external-dns/external-dns:v0.23.0
args:
- --source=service
- --source=ingress

View File

@@ -1,4 +1,4 @@
version: v0.13.4-6
version: v0.23.0
deploymentName: external-dns
requires:
- name: cert-manager

View File

@@ -3,4 +3,4 @@ is: formbricks
description: Formbricks is an open-source survey and form platform for collecting and analyzing user feedback. A self-hosted alternative to Typeform and SurveyMonkey.
category: business
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/formbricks.svg
latest: "3"
latest: "6"

View File

@@ -0,0 +1,36 @@
# Formbricks
Formbricks is an open-source survey and feedback platform for building in-app microsurveys, NPS scores, and user research forms.
## Dependencies
- **PostgreSQL** - Database for storing surveys and responses
- **Redis** - Used for caching and background jobs
## Configuration
Key settings in `config.yaml`:
- **domain** - Where Formbricks will be accessible
- **storage** - Persistent volume size (default: `2Gi`)
- **db.name** - Database name (default: `formbricks`)
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add formbricks
wild app deploy formbricks
```
2. Visit the app URL and sign up to create your admin account.
3. The first user to sign up becomes the organization owner.
4. Create your first survey from the dashboard and embed it in your apps using the provided JavaScript snippet.
## Notes
- Formbricks can be embedded in web apps or used as a standalone survey link
- The `nextauthSecret` and `encryptionKey` are auto-generated in `secrets.yaml`
- The `cronSecret` secures the internal cron endpoint used for scheduled tasks

View File

@@ -0,0 +1,61 @@
apiVersion: batch/v1
kind: Job
metadata:
name: formbricks-db-init
namespace: {{ .namespace }}
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: db-init
image: postgres:17
command: ["/bin/bash", "-c"]
args:
- |
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOST} -U postgres <<EOF
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${DB_USER}') THEN
CREATE USER ${DB_USER} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
ELSE
ALTER USER ${DB_USER} WITH ENCRYPTED PASSWORD '${DB_PASSWORD}';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE ${DB_NAME}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_NAME}')\gexec
ALTER DATABASE ${DB_NAME} OWNER TO ${DB_USER};
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
EOF
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOST} -U postgres -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOST} -U postgres -d ${DB_NAME} -c "CREATE EXTENSION IF NOT EXISTS vector;"
env:
- name: POSTGRES_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: postgres.password
- name: DB_HOST
value: "{{ .db.host }}"
- name: DB_NAME
value: "{{ .db.name }}"
- name: DB_USER
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: dbPassword
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
restartPolicy: OnFailure

View File

@@ -0,0 +1,102 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: formbricks
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
runAsNonRoot: false
runAsUser: 0
seccompProfile:
type: RuntimeDefault
containers:
- name: formbricks
image: formbricks/formbricks:6.1.0
ports:
- name: http
containerPort: 3000
protocol: TCP
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=768"
- name: WEBAPP_URL
value: "https://{{ .domain }}"
- name: NEXTAUTH_URL
value: "https://{{ .domain }}"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: dbUrl
- name: NEXTAUTH_SECRET
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: nextauthSecret
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: encryptionKey
- name: CRON_SECRET
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: cronSecret
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: formbricks-secrets
key: redis.password
- name: REDIS_URL
value: "redis://:$(REDIS_PASSWORD)@{{ .redis.host }}:6379"
- name: EMAIL_VERIFICATION_DISABLED
value: "1"
- name: PASSWORD_RESET_DISABLED
value: "1"
resources:
limits:
cpu: "1"
ephemeral-storage: 1Gi
memory: 1Gi
requests:
cpu: 50m
ephemeral-storage: 50Mi
memory: 256Mi
volumeMounts:
- name: formbricks-uploads
mountPath: /home/nextjs/apps/web/uploads
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 90
timeoutSeconds: 5
periodSeconds: 15
failureThreshold: 6
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 60
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 3
securityContext:
readOnlyRootFilesystem: false
volumes:
- name: formbricks-uploads
persistentVolumeClaim:
claimName: formbricks-uploads
restartPolicy: Always

View File

@@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: formbricks
namespace: {{ .namespace }}
spec:
ingressClassName: traefik
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: formbricks
port:
number: 80

View File

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

View File

@@ -0,0 +1,26 @@
version: 6.1.0
requires:
- name: postgres
- name: redis
defaultConfig:
namespace: formbricks
domain: formbricks.{{ .cloud.domain }}
storage: 2Gi
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: formbricks
user: formbricks
redis:
host: '{{ .apps.redis.host }}'
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host
}}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable
- key: nextauthSecret
- key: encryptionKey
- key: cronSecret
requiredSecrets:
- postgres.password
- redis.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: {{ .namespace }}

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: formbricks-uploads
namespace: {{ .namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: formbricks
namespace: {{ .namespace }}
spec:
selector:
component: web
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP

View File

@@ -3,4 +3,4 @@ is: ghost
description: Ghost is a powerful app for new-media creators to publish, share, and grow a business around their content.
category: social
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png
latest: "5"
latest: "6"

View File

@@ -0,0 +1,33 @@
# Ghost
Ghost is a powerful app for new-media creators to publish, share, and grow a business around their content. It provides a clean writing experience with built-in membership and subscription features.
## Dependencies
- **MySQL** - Database for storing content and configuration
## Configuration
Key settings configured through your instance's `config.yaml`:
- **domain** - Where Ghost will be accessible (default: `ghost.{your-cloud-domain}`)
- **blogTitle** - Your blog's title (default: `My Blog`)
- **adminEmail** - Admin account email (defaults to your operator email)
- **storage** - Persistent volume size for content (default: `10Gi`)
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
## Access
After deployment, Ghost will be available at:
- `https://ghost.{your-cloud-domain}` - Public blog
- `https://ghost.{your-cloud-domain}/ghost` - Admin panel
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add ghost
wild app deploy ghost
```
2. Navigate to the admin panel and create your first post

View File

@@ -0,0 +1,45 @@
apiVersion: batch/v1
kind: Job
metadata:
name: ghost-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
containers:
- name: db-init
image: mysql:9.1.0
command: ["/bin/bash", "-c"]
args:
- |
mysql -h ${DB_HOSTNAME} -P ${DB_PORT} -u root -p${MYSQL_ROOT_PASSWORD} <<EOF
CREATE DATABASE IF NOT EXISTS ${DB_DATABASE_NAME};
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
ALTER USER '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
GRANT ALL PRIVILEGES ON ${DB_DATABASE_NAME}.* TO '${DB_USERNAME}'@'%';
FLUSH PRIVILEGES;
EOF
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: ghost-secrets
key: mysql.rootPassword
- name: DB_HOSTNAME
value: "{{ .db.host }}"
- name: DB_PORT
value: "{{ .db.port }}"
- name: DB_DATABASE_NAME
value: "{{ .db.name }}"
- name: DB_USERNAME
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ghost-secrets
key: dbPassword
restartPolicy: OnFailure

View File

@@ -0,0 +1,105 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ghost
namespace: {{ .namespace }}
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: ghost
image: ghost:6.72.0-alpine
ports:
- name: http
containerPort: 2368
protocol: TCP
env:
- name: NODE_ENV
value: production
- name: url
value: "https://{{ .domain }}"
- name: database__client
value: mysql
- name: database__connection__host
value: "{{ .db.host }}"
- name: database__connection__port
value: "{{ .db.port }}"
- name: database__connection__database
value: "{{ .db.name }}"
- name: database__connection__user
value: "{{ .db.user }}"
- name: database__connection__password
valueFrom:
secretKeyRef:
name: ghost-secrets
key: dbPassword
- name: mail__transport
value: SMTP
- name: mail__options__host
value: "{{ .smtp.host }}"
- name: mail__options__port
value: "{{ .smtp.port }}"
- name: mail__options__auth__user
value: "{{ .smtp.user }}"
- name: mail__options__auth__pass
valueFrom:
secretKeyRef:
name: ghost-secrets
key: smtpPassword
- name: mail__from
value: "{{ .smtp.from }}"
resources:
limits:
cpu: 1000m
ephemeral-storage: 2Gi
memory: 512Mi
requests:
cpu: 50m
ephemeral-storage: 50Mi
memory: 256Mi
volumeMounts:
- name: ghost-data
mountPath: /var/lib/ghost/content
livenessProbe:
httpGet:
path: /ghost/api/admin/site/
port: 2368
initialDelaySeconds: 120
timeoutSeconds: 5
periodSeconds: 30
failureThreshold: 6
readinessProbe:
httpGet:
path: /ghost/api/admin/site/
port: 2368
initialDelaySeconds: 60
timeoutSeconds: 5
periodSeconds: 15
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumes:
- name: ghost-data
persistentVolumeClaim:
claimName: ghost-data
restartPolicy: Always

View File

@@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ghost
namespace: ghost
spec:
ingressClassName: traefik
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ghost
port:
number: 80

View File

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

View File

@@ -0,0 +1,23 @@
version: 6.72.0
requires:
- name: mysql
- name: smtp
defaultConfig:
namespace: ghost
domain: ghost.{{ .cloud.domain }}
storage: 2Gi
db:
host: '{{ .apps.mysql.host }}'
port: '3306'
name: ghost
user: ghost
smtp:
host: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}'
from: '{{ .apps.smtp.from }}'
user: '{{ .apps.smtp.user }}'
defaultSecrets:
- key: dbPassword
- key: smtpPassword
requiredSecrets:
- mysql.rootPassword

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

11
ghost/versions/6/pvc.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ghost-data
namespace: ghost
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: ghost
namespace: ghost
spec:
type: ClusterIP
ports:
- name: http
port: 80
protocol: TCP
targetPort: 2368
selector:
component: web

View File

@@ -3,4 +3,4 @@ is: gitea
description: Gitea is a painless self-hosted Git service written in Go
category: developer
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
latest: "1"
latest: "28"

View File

@@ -0,0 +1,85 @@
# Gitea Configuration
This Gitea deployment uses a hybrid configuration approach combining environment variables with Gitea's self-managed configuration file.
## Configuration Architecture
### Environment Variables (gitea.env)
Non-secret configuration is stored in `gitea.env` and automatically loaded via kustomize's `configMapGenerator`. This includes:
- Server settings (domain, URLs, ports)
- Database connection details (except password)
- SMTP settings (except password)
- Service settings (registration, notifications)
- Repository and storage paths
### Kubernetes Secrets (gitea-secrets)
Sensitive configuration is stored in the `gitea-secrets` secret and managed by the wild-cloud deployment system:
- `adminPassword` - Gitea admin user password
- `secretKey` - Application secret key
- `jwtSecret` - JWT signing secret
- `dbPassword` - Database password
- `smtpPassword` - SMTP authentication password
Secrets are defined in `secrets.yaml` and listed in `manifest.yaml` under `defaultSecrets`. When deploying, the system automatically ensures all required secrets exist in the `gitea-secrets` secret before deployment.
### Persistent Configuration (app.ini)
Gitea manages its own `app.ini` file on persistent storage for:
- Generated security tokens
- Runtime configuration changes made via web UI
- Database migration state
- User-modified settings
## How It Works
1. **Startup**: Kustomize generates a ConfigMap from `gitea.env`
2. **Environment Loading**: Pod loads non-secret config from ConfigMap via `envFrom`
3. **Secret Loading**: Pod loads sensitive config from Kubernetes secrets via `env`
4. **Configuration Merge**: Gitea's environment-to-ini process merges environment variables into `app.ini`
5. **Persistence**: Gitea writes the merged configuration plus generated tokens to persistent storage
## Making Configuration Changes
### Non-Secret Settings
1. Edit `gitea.env` with your changes
2. Deploy the app via the web app, CLI, or API to apply changes
3. Pod will restart and pick up new configuration
### Secret Settings
1. Edit `secrets.yaml` with your secret values
2. Ensure the secret key is listed in `manifest.yaml` under `defaultSecrets`
3. Deploy the app via the web app, CLI, or API - this will automatically update the `gitea-secrets` secret and restart the pod
### Web UI Changes
Configuration changes made through Gitea's admin web interface are automatically persisted to the `app.ini` file on persistent storage and will survive pod restarts.
## Configuration Precedence
1. **Kubernetes Secrets** (highest priority)
2. **Environment Variables** (from gitea.env)
3. **Persistent app.ini** (lowest priority)
Environment variables override file settings, and secrets override everything.
## Troubleshooting
### Check Current Configuration
```bash
# View environment variables
kubectl describe pod -n gitea -l app=gitea | grep -A 20 "Environment"
# View current app.ini
kubectl exec -it deployment/gitea -n gitea -- cat /data/gitea/conf/app.ini
```
### Configuration Not Applied
- Verify the ConfigMap was generated: `kubectl get configmap -n gitea`
- Check pod restart: `kubectl get pods -n gitea`
- Review startup logs: `kubectl logs -n gitea -l app=gitea`
## External Dependencies
- **Database**: PostgreSQL instance in `postgres` namespace
- **Storage**: Longhorn distributed storage
- **Ingress**: Traefik with Let's Encrypt certificates
- **DNS**: External-DNS with Cloudflare integration

View File

@@ -0,0 +1,51 @@
apiVersion: batch/v1
kind: Job
metadata:
name: gitea-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
containers:
- name: db-init
image: postgres:17
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: password
- name: DB_HOSTNAME
value: "{{ .db.host }}"
- name: DB_DATABASE_NAME
value: "{{ .db.name }}"
- name: DB_USERNAME
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: gitea-secrets
key: dbPassword
restartPolicy: OnFailure

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea
namespace: gitea
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
app: gitea
component: web
managedBy: kustomize
partOf: wild-cloud
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
terminationGracePeriodSeconds: 60
containers:
- name: gitea
image: "gitea/gitea:28.2.0"
imagePullPolicy: IfNotPresent
envFrom:
- configMapRef:
name: gitea-env
env:
- name: GITEA_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: gitea-secrets
key: adminPassword
- name: GITEA__security__SECRET_KEY
valueFrom:
secretKeyRef:
name: gitea-secrets
key: secretKey
- name: GITEA__security__INTERNAL_TOKEN
valueFrom:
secretKeyRef:
name: gitea-secrets
key: jwtSecret
- name: GITEA__database__PASSWD
valueFrom:
secretKeyRef:
name: gitea-secrets
key: dbPassword
- name: GITEA__mailer__PASSWD
valueFrom:
secretKeyRef:
name: gitea-secrets
key: smtpPassword
ports:
- name: ssh
containerPort: 2222
- name: http
containerPort: 3000
livenessProbe:
failureThreshold: 10
initialDelaySeconds: 200
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: http
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
tcpSocket:
port: http
timeoutSeconds: 1
resources:
{}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
volumeMounts:
- name: temp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: temp
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: gitea-data

View File

@@ -0,0 +1,60 @@
SSH_LISTEN_PORT=2222
SSH_PORT=22
GITEA_WORK_DIR=/data
GITEA_TEMP=/tmp/gitea
TMPDIR=/tmp/gitea
GITEA_ADMIN_USERNAME={{ .adminUser }}
GITEA_ADMIN_PASSWORD_MODE=keepUpdated
# Core app settings
GITEA____APP_NAME={{ .appName }}
GITEA____RUN_MODE=prod
GITEA____RUN_USER=git
# Security settings
GITEA__security__INSTALL_LOCK=true
GITEA__security__PASSWORD_HASH_ALGO=pbkdf2
# Database settings (except password which comes from secret)
GITEA__database__DB_TYPE=postgres
GITEA__database__HOST={{ .db.host }}:{{ .db.port }}
GITEA__database__NAME={{ .db.name }}
GITEA__database__USER={{ .db.user }}
GITEA__database__SSL_MODE=disable
GITEA__database__LOG_SQL=false
# Server settings
GITEA__server__DOMAIN={{ .domain }}
GITEA__server__HTTP_PORT=3000
GITEA__server__ROOT_URL=https://{{ .domain }}/
GITEA__server__DISABLE_SSH=false
GITEA__server__SSH_DOMAIN={{ .domain }}
GITEA__server__SSH_PORT=22
GITEA__server__SSH_LISTEN_PORT=2222
GITEA__server__LFS_START_SERVER=true
GITEA__server__OFFLINE_MODE=true
# Service settings
GITEA__service__REGISTER_EMAIL_CONFIRM=true
GITEA__service__DISABLE_REGISTRATION=false
GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION=false
GITEA__service__ENABLE_NOTIFY_MAIL=true
GITEA__service__ENABLE_BASIC_AUTHENTICATION=false
GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION=false
GITEA__service__ENABLE_CAPTCHA=false
GITEA__service__REQUIRE_SIGNIN_VIEW=false
GITEA__service__DEFAULT_KEEP_EMAIL_PRIVATE=false
GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION=true
GITEA__service__DEFAULT_ENABLE_TIMETRACKING=true
GITEA__service__NO_REPLY_ADDRESS=noreply.localhost
# Webhook settings
GITEA__webhook__ALLOWED_HOST_LIST=*
# Mailer settings (enabled via env vars, password from secret)
GITEA__mailer__ENABLED=true
GITEA__mailer__SMTP_ADDR={{ .smtp.host }}
GITEA__mailer__SMTP_PORT={{ .smtp.port }}
GITEA__mailer__FROM={{ .smtp.from }}
GITEA__mailer__USER={{ .smtp.user }}

View File

@@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea-public
namespace: gitea
spec:
ingressClassName: traefik
rules:
- host: "{{ .domain }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea-http
port:
number: 3000

View File

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

View File

@@ -0,0 +1,29 @@
version: 28.2.0
requires:
- name: postgres
- name: smtp
defaultConfig:
namespace: gitea
appName: Gitea
domain: gitea.{{ .cloud.domain }}
storage: 2Gi
adminUser: admin
adminEmail: '{{ .operator.email }}'
db:
name: gitea
user: gitea
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
smtp:
host: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}'
user: '{{ .apps.smtp.user }}'
from: '{{ .apps.smtp.from }}'
defaultSecrets:
- key: adminPassword
- key: dbPassword
- key: secretKey
- key: jwtSecret
- key: smtpPassword
requiredSecrets:
- postgres.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

View File

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

View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: Service
metadata:
name: gitea-http
namespace: gitea
spec:
type: ClusterIP
ports:
- name: http
port: 3000
targetPort: 3000
selector:
component: web
---
apiVersion: v1
kind: Service
metadata:
name: gitea-ssh
namespace: gitea
spec:
type: LoadBalancer
ports:
- name: ssh
port: 22
targetPort: 2222
protocol: TCP
selector:
component: web

View File

@@ -3,4 +3,4 @@ is: immich
description: Immich is a self-hosted photo and video backup solution that allows you to store, manage, and share your media files securely.
category: media
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
latest: "1"
latest: "3"

View File

@@ -0,0 +1,41 @@
# Immich
Immich is a self-hosted photo and video backup solution that allows you to store, manage, and share your media files securely. It provides a mobile-first experience similar to Google Photos.
## Dependencies
- **PostgreSQL** - Database for storing metadata and search indexes
- **Redis** - Used for caching and background job queuing
## Components
Immich runs two services:
- **Server** - Main API and web server
- **Machine Learning** - Handles facial recognition and smart search
## Configuration
Key settings configured through your instance's `config.yaml`:
- **domain** - Where Immich will be accessible (default: `immich.{your-cloud-domain}`)
- **storage** - Persistent volume for photos and videos (default: `250Gi`)
- **cacheStorage** - Persistent volume for ML cache (default: `10Gi`)
- **timezone** - Server timezone (default: `UTC`)
## Access
After deployment, Immich will be available at:
- `https://immich.{your-cloud-domain}`
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add immich
wild app deploy immich
```
2. Create your account through the web interface
3. Download the Immich mobile app and connect it to your server for automatic photo backup

View File

@@ -0,0 +1,68 @@
apiVersion: batch/v1
kind: Job
metadata:
name: immich-db-init
spec:
template:
spec:
containers:
- name: db-init
image: postgres:17
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
# Connect to the immich database and enable required extensions
PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOSTNAME} -U postgres -d ${DB_DATABASE_NAME} <<EOF
DO \$\$
BEGIN
-- Create vector extension if it doesn't exist
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN
CREATE EXTENSION vector;
END IF;
-- Create cube extension if it doesn't exist
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'cube') THEN
CREATE EXTENSION cube;
END IF;
-- Create earthdistance extension if it doesn't exist (depends on cube)
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'earthdistance') THEN
CREATE EXTENSION earthdistance;
END IF;
END
\$\$;
EOF
env:
- name: POSTGRES_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: immich-secrets
key: postgres.password
- name: DB_HOSTNAME
value: "{{ .db.host }}"
- name: DB_DATABASE_NAME
value: "{{ .db.name }}"
- name: DB_USERNAME
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: immich-secrets
key: dbPassword
restartPolicy: OnFailure

View File

@@ -0,0 +1,45 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: immich-machine-learning
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: immich-machine-learning
template:
metadata:
labels:
app: immich-machine-learning
component: machine-learning
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- image: "ghcr.io/immich-app/immich-machine-learning:v3.3.5"
name: immich-machine-learning
ports:
- containerPort: 3003
protocol: TCP
env:
- name: TZ
value: "UTC"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
volumeMounts:
- mountPath: /cache
name: immich-cache
readOnly: false
volumes:
- name: immich-cache
persistentVolumeClaim:
claimName: immich-cache-pvc

View File

@@ -0,0 +1,68 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: immich-microservices
spec:
replicas: 1
selector:
matchLabels:
app: immich-microservices
strategy:
type: Recreate
template:
metadata:
labels:
app: immich-microservices
component: microservices
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- image: "ghcr.io/immich-app/immich-server:v3.3.5"
name: immich-microservices
env:
- name: REDIS_HOSTNAME
value: "{{ .redis.host }}"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: immich-secrets
key: redis.password
- name: DB_HOSTNAME
value: "{{ .db.host }}"
- name: DB_USERNAME
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: immich-secrets
key: dbPassword
- name: TZ
value: "UTC"
- name: IMMICH_WORKERS_EXCLUDE
value: api
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
volumeMounts:
- mountPath: /usr/src/app/upload
name: immich-storage
readOnly: false
volumes:
- name: immich-storage
persistentVolumeClaim:
claimName: immich-pvc
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: immich
component: server
topologyKey: kubernetes.io/hostname

View File

@@ -0,0 +1,63 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: immich-server
spec:
replicas: 1
selector:
matchLabels:
app: immich-server
strategy:
type: Recreate
template:
metadata:
labels:
app: immich-server
component: server
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- image: "ghcr.io/immich-app/immich-server:v3.3.5"
name: immich-server
ports:
- containerPort: 2283
protocol: TCP
env:
- name: REDIS_HOSTNAME
value: "{{ .redis.host }}"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: immich-secrets
key: redis.password
- name: DB_HOSTNAME
value: "{{ .db.host }}"
- name: DB_USERNAME
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: immich-secrets
key: dbPassword
- name: TZ
value: "UTC"
- name: IMMICH_WORKERS_EXCLUDE
value: microservices
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: [ALL]
readOnlyRootFilesystem: false
volumeMounts:
- mountPath: /usr/src/app/upload
name: immich-storage
readOnly: false
volumes:
- name: immich-storage
persistentVolumeClaim:
claimName: immich-pvc

View File

@@ -0,0 +1,18 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: immich-public
spec:
ingressClassName: traefik
rules:
- host: "{{ .domain }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: immich-server
port:
number: 3001

View File

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

View File

@@ -0,0 +1,20 @@
version: 3.3.5
requires:
- name: redis
- name: postgres
defaultConfig:
namespace: immich
storage: 5Gi
cacheStorage: 5Gi
domain: immich.{{ .cloud.domain }}
db:
host: '{{ .apps.postgres.host }}'
name: immich
user: immich
redis:
host: '{{ .apps.redis.host }}'
defaultSecrets:
- key: dbPassword
requiredSecrets:
- redis.password
- postgres.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

View File

@@ -0,0 +1,24 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-pvc
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: immich-cache-pvc
spec:
storageClassName: longhorn
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .cacheStorage }}

View File

@@ -0,0 +1,33 @@
---
apiVersion: v1
kind: Service
metadata:
name: immich-server
namespace: immich
labels:
app: immich-server
spec:
ports:
- port: 3001
targetPort: 2283
selector:
app: immich
component: server
managedBy: kustomize
partOf: wild-cloud
---
apiVersion: v1
kind: Service
metadata:
name: immich-machine-learning
namespace: immich
labels:
app: immich-machine-learning
spec:
ports:
- port: 3003
selector:
app: immich
component: machine-learning
managedBy: kustomize
partOf: wild-cloud

View File

@@ -3,4 +3,4 @@ is: listmonk
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager. It is fast, feature-rich, and packed into a single binary.
category: communication
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/listmonk.svg
latest: "5"
latest: "6"

View File

@@ -0,0 +1,31 @@
# Listmonk
Listmonk is a standalone, self-hosted newsletter and mailing list manager. It is fast, feature-rich, and packed into a single binary.
## Dependencies
- **PostgreSQL** - Database for storing subscribers and campaigns
## Configuration
Key settings configured through your instance's `config.yaml`:
- **domain** - Where Listmonk will be accessible (default: `listmonk.{your-cloud-domain}`)
- **storage** - Persistent volume size (default: `1Gi`)
## Access
After deployment, Listmonk will be available at:
- `https://listmonk.{your-cloud-domain}`
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add listmonk
wild app deploy listmonk
```
2. Log in to the admin interface and configure your SMTP settings for sending emails
3. Create your first mailing list and start adding subscribers

View File

@@ -0,0 +1,66 @@
apiVersion: batch/v1
kind: Job
metadata:
name: listmonk-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres-init
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
env:
- name: PGHOST
value: {{ .db.host }}
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: listmonk-secrets
key: postgres.password
- name: DB_NAME
value: {{ .db.name }}
- name: DB_USER
value: {{ .db.user }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: listmonk-secrets
key: dbPassword
command:
- /bin/bash
- -c
- |
set -e
echo "Waiting for PostgreSQL to be ready..."
until pg_isready; do
echo "PostgreSQL is not ready - sleeping"
sleep 2
done
echo "PostgreSQL is ready"
echo "Creating database and user for Listmonk..."
psql -c "CREATE DATABASE ${DB_NAME};" || echo "Database ${DB_NAME} already exists"
psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || echo "User ${DB_USER} already exists"
psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
echo "Database initialization complete"

View File

@@ -0,0 +1,88 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: listmonk
namespace: listmonk
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: listmonk
image: listmonk/listmonk:v6.1.0
command: ["/bin/sh"]
args: ["-c", "./listmonk --install --idempotent --yes && ./listmonk --upgrade --yes && ./listmonk"]
ports:
- name: http
containerPort: 9000
protocol: TCP
env:
- name: LISTMONK_app__address
value: "0.0.0.0:9000"
- name: LISTMONK_app__root_url
value: "https://{{ .domain }}"
- name: LISTMONK_db__host
value: {{ .db.host }}
- name: LISTMONK_db__port
value: "{{ .db.port }}"
- name: LISTMONK_db__user
value: {{ .db.user }}
- name: LISTMONK_db__database
value: {{ .db.name }}
- name: LISTMONK_db__ssl_mode
value: disable
- name: LISTMONK_db__password
valueFrom:
secretKeyRef:
name: listmonk-secrets
key: dbPassword
resources:
limits:
cpu: 500m
ephemeral-storage: 1Gi
memory: 512Mi
requests:
cpu: 50m
ephemeral-storage: 50Mi
memory: 128Mi
volumeMounts:
- name: listmonk-data
mountPath: /listmonk/data
livenessProbe:
tcpSocket:
port: 9000
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 9000
initialDelaySeconds: 10
timeoutSeconds: 3
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumes:
- name: listmonk-data
persistentVolumeClaim:
claimName: listmonk-data
restartPolicy: Always

View File

@@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: listmonk
namespace: listmonk
spec:
ingressClassName: traefik
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: listmonk
port:
number: 80

View File

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

View File

@@ -0,0 +1,19 @@
version: 6.1.0
requires:
- name: postgres
defaultConfig:
namespace: listmonk
domain: listmonk.{{ .cloud.domain }}
storage: 1Gi
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: listmonk
user: listmonk
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host
}}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable
requiredSecrets:
- postgres.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: listmonk-data
namespace: listmonk
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: listmonk
namespace: listmonk
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
component: web

View File

@@ -4950,7 +4950,7 @@ spec:
spec:
containers:
- name: longhorn-manager
image: longhornio/longhorn-manager:v1.8.1
image: longhornio/longhorn-manager:v1.13.0
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
@@ -4963,13 +4963,13 @@ spec:
- --instance-manager-image
- "longhornio/longhorn-instance-manager:v1.8.1"
- --share-manager-image
- "longhornio/longhorn-share-manager:v1.8.1"
- "longhornio/longhorn-share-manager:v1.13.0"
- --backing-image-manager-image
- "longhornio/backing-image-manager:v1.8.1"
- --support-bundle-manager-image
- "longhornio/support-bundle-kit:v0.0.52"
- --manager-image
- "longhornio/longhorn-manager:v1.8.1"
- "longhornio/longhorn-manager:v1.13.0"
- --service-account
- longhorn-service-account
- --upgrade-version-check
@@ -5023,7 +5023,7 @@ spec:
fieldPath: spec.nodeName
- name: pre-pull-share-manager-image
imagePullPolicy: IfNotPresent
image: longhornio/longhorn-share-manager:v1.8.1
image: longhornio/longhorn-share-manager:v1.13.0
command: ["sh", "-c", "echo share-manager image pulled && sleep infinity"]
volumes:
- name: boot
@@ -5076,18 +5076,18 @@ spec:
spec:
initContainers:
- name: wait-longhorn-manager
image: longhornio/longhorn-manager:v1.8.1
image: longhornio/longhorn-manager:v1.13.0
command: ['sh', '-c', 'while [ $(curl -m 1 -s -o /dev/null -w "%{http_code}" http://longhorn-backend:9500/v1) != "200" ]; do echo waiting; sleep 2; done']
containers:
- name: longhorn-driver-deployer
image: longhornio/longhorn-manager:v1.8.1
image: longhornio/longhorn-manager:v1.13.0
imagePullPolicy: IfNotPresent
command:
- longhorn-manager
- -d
- deploy-driver
- --manager-image
- "longhornio/longhorn-manager:v1.8.1"
- "longhornio/longhorn-manager:v1.13.0"
- --manager-url
- http://longhorn-backend:9500/v1
env:
@@ -5159,7 +5159,7 @@ spec:
topologyKey: kubernetes.io/hostname
containers:
- name: longhorn-ui
image: longhornio/longhorn-ui:v1.8.1
image: longhornio/longhorn-ui:v1.13.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name : nginx-cache

View File

@@ -1,4 +1,4 @@
version: v1.8.1-5
version: v1.13.0
deploymentName: longhorn-ui
requires:
- name: nfs

View File

@@ -3,4 +3,4 @@ is: mattermost
description: Mattermost is a self-hosted, open-source team messaging platform offering secure channels, file sharing, and integrations as a Slack alternative.
category: communication
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mattermost.svg
latest: "10"
latest: "12"

View File

@@ -0,0 +1,33 @@
# Mattermost
Mattermost is an open-source team messaging platform, a self-hosted alternative to Slack.
## Dependencies
- **PostgreSQL** - Database for storing messages and team data
## Configuration
Key settings in `config.yaml`:
- **domain** - Where Mattermost will be accessible
- **storage** - Persistent volume size (default: `2Gi`)
- **db.name** - Database name (default: `mattermost`)
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add mattermost
wild app deploy mattermost
```
2. Visit the app URL — on first load you will be prompted to create an administrator account (email + password).
3. After creating the admin account, complete the onboarding wizard to set up your first team.
## Notes
- The setup wizard only runs on first launch
- Email invitations require SMTP to be configured in **System Console → Environment → SMTP**
- Push notifications for mobile apps require a push proxy (or Mattermost Cloud push notification service)

View File

@@ -0,0 +1,59 @@
apiVersion: batch/v1
kind: Job
metadata:
name: mattermost-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres-init
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
env:
- name: PGHOST
value: {{ .db.host }}
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: mattermost-secrets
key: postgres.password
- name: DB_NAME
value: {{ .db.name }}
- name: DB_USER
value: {{ .db.user }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mattermost-secrets
key: dbPassword
command:
- /bin/bash
- -c
- |
set -e
until pg_isready; do sleep 2; done
psql -c "CREATE DATABASE ${DB_NAME};" || echo "Database already exists"
psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || echo "User already exists"
psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
echo "Done"

View File

@@ -0,0 +1,93 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mattermost
namespace: mattermost
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 2000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: mattermost
image: mattermost/mattermost-team-edition:12.0.1
ports:
- name: http
containerPort: 8065
protocol: TCP
env:
- name: MM_SERVICESETTINGS_SITEURL
value: https://{{ .domain }}
- name: MM_SQLSETTINGS_DRIVERNAME
value: postgres
- name: MM_SQLSETTINGS_DATASOURCE
valueFrom:
secretKeyRef:
name: mattermost-secrets
key: dbUrl
- name: MM_FILESETTINGS_DIRECTORY
value: /mattermost/data/
- name: MM_PLUGINSETTINGS_PLUGINDIRECTORY
value: /mattermost/plugins
- name: MM_CLIENTPLUGINSSETTINGS_PLUGINDIRECTORY
value: /mattermost/client/plugins
- name: MM_LOGSETTINGS_CONSOLELEVEL
value: INFO
resources:
limits:
cpu: 500m
ephemeral-storage: 1Gi
memory: 1Gi
requests:
cpu: 50m
ephemeral-storage: 50Mi
memory: 256Mi
volumeMounts:
- name: mattermost-data
mountPath: /mattermost/data
- name: mattermost-plugins
mountPath: /mattermost/plugins
livenessProbe:
httpGet:
path: /api/v4/system/ping
port: 8065
initialDelaySeconds: 60
timeoutSeconds: 5
periodSeconds: 15
failureThreshold: 6
readinessProbe:
httpGet:
path: /api/v4/system/ping
port: 8065
initialDelaySeconds: 30
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumes:
- name: mattermost-data
persistentVolumeClaim:
claimName: mattermost-data
- name: mattermost-plugins
persistentVolumeClaim:
claimName: mattermost-plugins
restartPolicy: Always

View File

@@ -0,0 +1,18 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mattermost
namespace: mattermost
spec:
ingressClassName: traefik
rules:
- host: {{ .domain }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: mattermost
port:
number: 80

View File

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

View File

@@ -0,0 +1,19 @@
version: 12.0.1
requires:
- name: postgres
defaultConfig:
namespace: mattermost
domain: mattermost.{{ .cloud.domain }}
storage: 2Gi
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: mattermost
user: mattermost
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host
}}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable&connect_timeout=10
requiredSecrets:
- postgres.password

View File

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

View File

@@ -0,0 +1,23 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mattermost-data
namespace: mattermost
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .storage }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mattermost-plugins
namespace: mattermost
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: mattermost
namespace: mattermost
spec:
selector:
component: web
ports:
- name: http
port: 80
targetPort: 8065
protocol: TCP

View File

@@ -1,4 +1,4 @@
version: v0.15.0-1
version: v0.16.1
deploymentName: controller
defaultConfig:
namespace: metallb-system

View File

@@ -1,4 +1,4 @@
version: 9.1.0-4
version: 9.7.4
defaultConfig:
namespace: mysql
host: mysql.mysql.svc.cluster.local

View File

@@ -29,7 +29,7 @@ spec:
type: RuntimeDefault
containers:
- name: mysql
image: mysql:9.1.0
image: mysql:9.7.4
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false

View File

@@ -18,7 +18,7 @@ spec:
type: RuntimeDefault
containers:
- name: worker
image: registry.k8s.io/nfd/node-feature-discovery:v0.17.3
image: registry.k8s.io/nfd/node-feature-discovery:v0.20.1
securityContext:
allowPrivilegeEscalation: false
capabilities:

View File

@@ -1,4 +1,4 @@
version: v0.17.3
version: v0.20.1
deploymentName: node-feature-discovery-master
defaultConfig:
namespace: node-feature-discovery

View File

@@ -19,7 +19,7 @@ spec:
type: RuntimeDefault
containers:
- name: master
image: registry.k8s.io/nfd/node-feature-discovery:v0.17.3
image: registry.k8s.io/nfd/node-feature-discovery:v0.20.1
securityContext:
allowPrivilegeEscalation: false
capabilities:

View File

@@ -50,7 +50,7 @@ spec:
seccompProfile:
type: RuntimeDefault
containers:
- image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
- image: nvcr.io/nvidia/k8s-device-plugin:v0.20.1
name: nvidia-device-plugin-ctr
env:
- name: MPS_ROOT

View File

@@ -1,4 +1,4 @@
version: v0.17.1-1
version: v0.20.1
deploymentName: nvidia-device-plugin-daemonset
requires:
- name: node-feature-discovery

View File

@@ -3,4 +3,4 @@ is: openproject
description: OpenProject is an open-source project management software that provides comprehensive features for project planning, tracking, and collaboration.
category: productivity
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/openproject.svg
latest: "16"
latest: "17"

View File

@@ -0,0 +1,34 @@
# OpenProject
OpenProject is an open-source project management software that provides comprehensive features for project planning, tracking, and collaboration.
## Dependencies
- **PostgreSQL** - Database for storing project data
- **Memcached** - Caching layer for improved performance
## Configuration
Key settings configured through your instance's `config.yaml`:
- **domain** - Where OpenProject will be accessible (default: `openproject.{your-cloud-domain}`)
- **adminUserEmail** - Admin account email (defaults to your operator email)
- **seedLocale** - Default language (default: `en`)
- **storage** - Persistent volume size (default: `5Gi`)
## Access
After deployment, OpenProject will be available at:
- `https://openproject.{your-cloud-domain}`
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add openproject
wild app deploy openproject
```
2. Log in with the admin credentials (password reset will be required on first login)
3. Create your first project and invite team members

View File

@@ -0,0 +1,24 @@
---
# Source: openproject/templates/secret_core.yaml
apiVersion: "v1"
kind: "ConfigMap"
metadata:
name: "openproject-core"
data:
DATABASE_HOST: "{{ .db.host }}"
DATABASE_PORT: "{{ .db.port }}"
DATABASE_NAME: "{{ .db.name }}"
DATABASE_USERNAME: "{{ .db.user }}"
DATABASE_URL: "postgresql://{{ .db.user }}@{{ .db.host }}:{{ .db.port }}/{{ .db.name }}"
OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "true"
OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .adminUserName }}"
OPENPROJECT_SEED_ADMIN_USER_MAIL: "{{ .adminUserEmail }}"
OPENPROJECT_HTTPS: "true"
OPENPROJECT_SEED_LOCALE: "en"
OPENPROJECT_HOST__NAME: "{{ .domain }}"
OPENPROJECT_HSTS: "true"
OPENPROJECT_RAILS__CACHE__STORE: "memcache"
OPENPROJECT_RAILS__RELATIVE__URL__ROOT: ""
POSTGRES_STATEMENT_TIMEOUT: "120s"
WEB_CONCURRENCY: "1"
...

View File

@@ -0,0 +1,9 @@
---
# Source: openproject/templates/secret_memcached.yaml
apiVersion: "v1"
kind: "ConfigMap"
metadata:
name: "openproject-memcached"
data:
OPENPROJECT_CACHE__MEMCACHE__SERVER: "{{ .memcached.host }}:{{ .memcached.port }}"
...

View File

@@ -0,0 +1,51 @@
apiVersion: batch/v1
kind: Job
metadata:
name: openproject-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
containers:
- name: db-init
image: postgres:17
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: password
- name: DB_HOSTNAME
value: "{{ .db.host }}"
- name: DB_DATABASE_NAME
value: "{{ .db.name }}"
- name: DB_USERNAME
value: "{{ .db.user }}"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: dbPassword
restartPolicy: OnFailure

View File

@@ -0,0 +1,20 @@
---
# Source: openproject/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openproject
spec:
ingressClassName: traefik
rules:
- host: "{{ .domain }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openproject
port:
name: http
...

View File

@@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: openproject
labels:
- includeSelectors: true
pairs:
app: openproject
managedBy: kustomize
partOf: wild-cloud
resources:
- namespace.yaml
- serviceaccount.yaml
- configmap_core.yaml
- configmap_memcached.yaml
- persistentvolumeclaim.yaml
- service.yaml
- db-init-job.yaml
- web-deployment.yaml
- worker-deployment.yaml
- seeder-job.yaml
- ingress.yaml

View File

@@ -0,0 +1,23 @@
version: 17.9.3
requires:
- name: postgres
- name: memcached
defaultConfig:
namespace: openproject
storage: 2Gi
adminUserName: OpenProject Admin
adminUserEmail: '{{ .operator.email }}'
domain: openproject.{{ .cloud.domain }}
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: openproject
user: openproject
memcached:
host: '{{ .apps.memcached.host }}'
port: '{{ .apps.memcached.port }}'
defaultSecrets:
- key: dbPassword
- key: adminPassword
requiredSecrets:
- postgres.password

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: "{{ .namespace }}"

View File

@@ -0,0 +1,12 @@
---
# Source: openproject/templates/persistentvolumeclaim.yaml
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: openproject
spec:
accessModes: [ReadWriteMany]
resources:
requests:
storage: "{{ .storage }}"
...

View File

@@ -0,0 +1,138 @@
---
# Source: openproject/templates/seeder-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: openproject-seeder-1
labels:
component: seeder
spec:
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
component: seeder
spec:
securityContext:
fsGroup: 1000
volumes:
- name: tmp
# we can't use emptyDir due to the sticky bit issue
# see: https://github.com/kubernetes/kubernetes/issues/110835
ephemeral:
volumeClaimTemplate:
metadata:
creationTimestamp: null
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
- name: app-tmp
# we can't use emptyDir due to the sticky bit / world writable issue
# see: https://github.com/kubernetes/kubernetes/issues/110835
ephemeral:
volumeClaimTemplate:
metadata:
creationTimestamp: null
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
- name: "data"
persistentVolumeClaim:
claimName: openproject
initContainers:
- name: check-db-ready
image: "postgres:17"
imagePullPolicy: Always
command: [
'sh',
'-c',
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT -U openproject; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done;'
]
envFrom:
- configMapRef:
name: openproject-core
- configMapRef:
name: openproject-memcached
env:
- name: OPENPROJECT_DB_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: adminPassword
resources:
limits:
memory: 200Mi
requests:
memory: 200Mi
volumeMounts:
- mountPath: /tmp
name: tmp
- mountPath: /app/tmp
name: app-tmp
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: seeder
image: "openproject/openproject:17.9.3-slim"
imagePullPolicy: Always
args:
- bash
- /app/docker/prod/seeder
envFrom:
- configMapRef:
name: openproject-core
- configMapRef:
name: openproject-memcached
env:
- name: OPENPROJECT_DB_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: dbPassword
- name: OPENPROJECT_SEED_ADMIN_USER_PASSWORD
valueFrom:
secretKeyRef:
name: openproject-secrets
key: adminPassword
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
volumeMounts:
- mountPath: /tmp
name: tmp
- mountPath: /app/tmp
name: app-tmp
- name: "data"
mountPath: "/var/openproject/assets"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
restartPolicy: OnFailure

Some files were not shown because too many files have changed in this diff Show More