Compare commits
9 Commits
a159c90816
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2aa16e679 | ||
|
|
37dafcd24d | ||
|
|
b6d88e79ac | ||
|
|
963929475c | ||
|
|
39095e76d2 | ||
|
|
d756126a34 | ||
|
|
f17fea6910 | ||
|
|
0ba33a315d | ||
|
|
12706ac331 |
94
CLAUDE.md
94
CLAUDE.md
@@ -1,15 +1,97 @@
|
||||
- @README.md
|
||||
- @ADDING-APPS.md
|
||||
|
||||
## Finding good sources of documentation for adding a new app to the Wild Cloud Directory
|
||||
|
||||
- A good starting point is to:
|
||||
- look for an app's official documentation on running in Kubernetes or a containerized environment
|
||||
- look at the app's official docker compose
|
||||
- look for official or common helm packages
|
||||
- look at the source code repository for the app
|
||||
|
||||
These sources will oftentimes not use the latest version. Check to make sure you are adding the latest app version.
|
||||
|
||||
- Don't use helm for the final deployement, however it is a good idea to unpack a helm package to investigate best practices and to overcome tricky configurations
|
||||
|
||||
## App package development lifecycle
|
||||
|
||||
- when developing a new app, test on the `test-cloud` instance in the `/home/payne/repos/wild-cloud-dev/wild-cloud-redmond-data` wild data dir. Prefer the `wild` CLI for managing app lifecycle as it takes care of copying and compiling kustomize templates. Example commands:
|
||||
- `wild instance use test-cloud`
|
||||
- `wild app add <app>`
|
||||
- `wild app deploy <app>`
|
||||
- `wild app delete`
|
||||
- But you can always use `kubectl` directly. Just make sure you use the `test-cloud` `kubeconfig` and, when applying resources, use the `-k` flag so kustomize templates get copied.
|
||||
- kubectl --kubeconfig=/home/payne/repos/wild-cloud-redmond-data/instances/test-cloud/kubeconfig get pods -n <app>
|
||||
- While we test in `/home/payne/repos/wild-cloud-dev/wild-cloud-redmond-data/instances/test-cloud`, the final product (our app package) must be in `/home/payne/repos/wild-cloud-dev/wild-directory`. Always make sure that, in the end, whatever is in `wild-directory` can be deployed into `test-cloud`.
|
||||
- If you run into difficulties, revisit helm charts, docker compose files, and most importantly, the source code to determine how existing deployments are functioning correctly.
|
||||
|
||||
## App Icons
|
||||
|
||||
Icon Search Process
|
||||
|
||||
1. Primary Source: Dashboard Icons (homarr-labs)
|
||||
|
||||
- URL Pattern: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/{app-name}.svg
|
||||
- Why: Curated collection specifically for self-hosted apps, consistent style, reliable CDN
|
||||
- Format: SVG (preferred for scalability)
|
||||
- Check: Visit https://dashboardicons.com/icons/{app-name} to see if it exists
|
||||
|
||||
2. Fallback: Vector Logo Zone
|
||||
|
||||
- URL Pattern: https://www.vectorlogo.zone/logos/{app-name}/
|
||||
- Why: Large collection of official logos in standardized formats
|
||||
- Options:
|
||||
- {app-name}-icon.svg (logo only, no text)
|
||||
- {app-name}-ar21.svg (logo with text)
|
||||
- Best for: Apps not in Dashboard Icons
|
||||
|
||||
3. Official Sources
|
||||
|
||||
- Check the app's official website for logo/brand pages
|
||||
- Look for /brand, /logos, /assets paths
|
||||
- Example: https://www.loomio.com/brand/logo_gold.svg
|
||||
|
||||
4. Community CDNs
|
||||
|
||||
- LobeHub Icons: For AI/LLM tools (vLLM, etc.)
|
||||
- https://unpkg.com/@lobehub/icons-static-png@latest/dark/{app-name}.png
|
||||
- Simple Icons: For popular brands
|
||||
- https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/{app-name}.svg
|
||||
|
||||
Validation Process
|
||||
|
||||
For each candidate URL:
|
||||
1. Test the URL using WebFetch to confirm it returns a valid image
|
||||
2. Verify format: SVG preferred, PNG acceptable
|
||||
3. Check content: Logo-only preferred over logo+text
|
||||
4. Confirm it works: Actually loads in a browser/img tag
|
||||
|
||||
Icon Preferences
|
||||
|
||||
1. Format: SVG > PNG (for scalability)
|
||||
2. Style: Logo only > Logo with text
|
||||
3. Source: Official CDN > Community CDN > Direct hosting
|
||||
4. Consistency: Similar visual style across all apps
|
||||
|
||||
Search Strategy
|
||||
|
||||
### For each app:
|
||||
1. Try Dashboard Icons first: dashboardicons.com/icons/{app}
|
||||
2. If not found, try Vector Logo Zone: vectorlogo.zone/logos/{app}
|
||||
3. If still not found, search: "{app} official logo CDN SVG URL"
|
||||
4. Validate the URL actually works
|
||||
5. Prefer icon-only versions when multiple options exist
|
||||
|
||||
This systematic approach ensures consistent, reliable, and high-quality icons across all Wild Cloud apps.
|
||||
|
||||
## IMPORTANT
|
||||
|
||||
- NEVER under any circumstances work on any Wild Cloud instance other than `test-cloud`
|
||||
- `secrets.yaml` is NOT checked in and any values unrelated to your current task should be preserved
|
||||
- When adding a new app to the directory, check to make sure you are adding the latest app version.
|
||||
- set `namespace` in default config and refer to it using `{{ .namespace }}` in the `namespace.yaml` definition file and the `kustomization.yaml` file.
|
||||
- If the app requires a specific platform (amd64, arm64, etc.), make sure it is called out in the manifest and the k8s tags are set
|
||||
- Use traefik for ingress.
|
||||
- Use postgres for database if supported.
|
||||
- Keep config key naming (including nesting) consistent with other apps.
|
||||
- Don't use helm
|
||||
- If the app requires a specific platform (amd64, arm64, etc.), make sure it is called out in the manifest and the k8s tags are set
|
||||
- when developing a new app:
|
||||
- test with:
|
||||
- reset to a fresh state between tests:
|
||||
- secrets.yaml is not checked in and any values unrelated to your current task should be preserved
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -97,20 +97,6 @@ Some apps require other apps to function. For example:
|
||||
|
||||
When you add an app, check its `requires` field in the manifest and ensure dependencies are added first.
|
||||
|
||||
## Available Apps
|
||||
|
||||
This repository includes apps for:
|
||||
- Content management (Ghost, Discourse)
|
||||
- Project management (OpenProject)
|
||||
- Photo management (Immich)
|
||||
- Code hosting (Gitea)
|
||||
- Email marketing (Listmonk, Keila)
|
||||
- AI interfaces (Open WebUI, vLLM)
|
||||
- Databases (PostgreSQL, MySQL, Redis, Memcached)
|
||||
- And more...
|
||||
|
||||
Browse the full catalog with descriptions through the web app, CLI, or via the API endpoint `/api/v1/apps/available`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Want to add a new app or improve an existing one? See [ADDING-APPS.md](ADDING-APPS.md) for detailed guidance on creating Wild Cloud apps.
|
||||
|
||||
25
decidim/Dockerfile
Normal file
25
decidim/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Build Decidim with Sidekiq support
|
||||
FROM decidim/decidim:0.31.0
|
||||
|
||||
# Switch to root to install dependencies
|
||||
USER root
|
||||
|
||||
# Add sidekiq to Gemfile
|
||||
RUN cd /code && \
|
||||
echo "" >> Gemfile && \
|
||||
echo "# Background job processing" >> Gemfile && \
|
||||
echo "gem 'sidekiq', '~> 6.5'" >> Gemfile && \
|
||||
bundle install
|
||||
|
||||
# Configure Rails to use Sidekiq as ActiveJob backend
|
||||
RUN cd /code && \
|
||||
test -d config/initializers || mkdir -p config/initializers && \
|
||||
echo "Rails.application.config.active_job.queue_adapter = :sidekiq" > config/initializers/active_job.rb && \
|
||||
cat config/initializers/active_job.rb && \
|
||||
ls -la config/initializers/
|
||||
|
||||
# Switch back to decidim user
|
||||
USER decidim
|
||||
|
||||
# Default command (can be overridden)
|
||||
CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"]
|
||||
73
decidim/db-init-job.yaml
Normal file
73
decidim/db-init-job.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: decidim-db-init
|
||||
namespace: decidim
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: db-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: db-init
|
||||
image: postgres:17
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
export PGPASSWORD="${POSTGRES_ADMIN_PASSWORD}"
|
||||
|
||||
# Create database if it doesn't exist
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${DB_NAME}'" | grep -q 1 || \
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "CREATE DATABASE ${DB_NAME};"
|
||||
|
||||
# Create user if it doesn't exist, or update password if it does
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -tc "SELECT 1 FROM pg_roles WHERE rolname = '${DB_USER}'" | grep -q 1 && \
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || \
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
|
||||
|
||||
# Grant privileges
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
|
||||
|
||||
# Grant schema privileges (needed for Rails migrations)
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
|
||||
|
||||
echo "Database initialization completed successfully"
|
||||
env:
|
||||
- name: POSTGRES_HOST
|
||||
value: {{ .dbHostname }}
|
||||
- name: POSTGRES_ADMIN_USER
|
||||
value: postgres
|
||||
- name: POSTGRES_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: postgres.password
|
||||
- name: DB_NAME
|
||||
value: {{ .dbName }}
|
||||
- name: DB_USER
|
||||
value: {{ .dbUsername }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: dbPassword
|
||||
214
decidim/deployment.yaml
Normal file
214
decidim/deployment.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: web
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
serviceAccountName: decidim
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: Always
|
||||
containers:
|
||||
- name: decidim
|
||||
image: payneio/decidim-sidekiq:0.31.0
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
cd /code
|
||||
bundle exec rake db:migrate
|
||||
bundle exec rails runner "Decidim::System::Admin.find_or_create_by!(email: ENV['SYSTEM_ADMIN_EMAIL']) { |admin| admin.password = ENV['SYSTEM_ADMIN_PASSWORD']; admin.password_confirmation = ENV['SYSTEM_ADMIN_PASSWORD'] }"
|
||||
bundle exec rails s -b 0.0.0.0
|
||||
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"
|
||||
- name: PORT
|
||||
value: "{{ .port }}"
|
||||
- name: RAILS_LOG_TO_STDOUT
|
||||
value: "true"
|
||||
# Database configuration
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: dbUrl
|
||||
# Redis configuration
|
||||
- name: REDIS_HOSTNAME
|
||||
value: {{ .redisHostname }}
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: redis.password
|
||||
- name: REDIS_URL
|
||||
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOSTNAME):6379/0"
|
||||
# Application configuration
|
||||
- name: DECIDIM_HOST
|
||||
value: {{ .domain }}
|
||||
- name: DECIDIM_ORGANIZATION_NAME
|
||||
value: {{ .siteName }}
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: secretKeyBase
|
||||
# SMTP configuration
|
||||
- name: SMTP_ADDRESS
|
||||
value: {{ .smtp.host }}
|
||||
- name: SMTP_PORT
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: SMTP_USERNAME
|
||||
value: {{ .smtp.user }}
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: smtpPassword
|
||||
- name: SMTP_DOMAIN
|
||||
value: {{ .domain }}
|
||||
- name: SMTP_FROM
|
||||
value: {{ .smtp.from }}
|
||||
- name: SMTP_STARTTLS_AUTO
|
||||
value: "{{ .smtp.startTls }}"
|
||||
# System admin credentials
|
||||
- name: SYSTEM_ADMIN_EMAIL
|
||||
value: {{ .systemAdminEmail }}
|
||||
- name: SYSTEM_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: systemAdminPassword
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .port }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 300
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 180
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
ephemeral-storage: 10Gi
|
||||
memory: 4Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
ephemeral-storage: 50Mi
|
||||
memory: 1Gi
|
||||
volumeMounts:
|
||||
- name: decidim-data
|
||||
mountPath: /code/public/uploads
|
||||
- name: sidekiq
|
||||
image: payneio/decidim-sidekiq:0.31.0
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
cd /code
|
||||
bundle exec sidekiq
|
||||
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"
|
||||
- name: RAILS_LOG_TO_STDOUT
|
||||
value: "true"
|
||||
# Database configuration
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: dbUrl
|
||||
# Redis configuration
|
||||
- name: REDIS_HOSTNAME
|
||||
value: {{ .redisHostname }}
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: redis.password
|
||||
- name: REDIS_URL
|
||||
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOSTNAME):6379/0"
|
||||
# Application configuration
|
||||
- name: DECIDIM_HOST
|
||||
value: {{ .domain }}
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: secretKeyBase
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: decidim-data
|
||||
mountPath: /code/public/uploads
|
||||
volumes:
|
||||
- name: decidim-data
|
||||
persistentVolumeClaim:
|
||||
claimName: decidim-data
|
||||
26
decidim/ingress.yaml
Normal file
26
decidim/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: decidim
|
||||
port:
|
||||
number: {{ .port }}
|
||||
17
decidim/kustomization.yaml
Normal file
17
decidim/kustomization.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: decidim
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: decidim
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- pvc.yaml
|
||||
- db-init-job.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
44
decidim/manifest.yaml
Normal file
44
decidim/manifest.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
name: decidim
|
||||
is: decidim
|
||||
description: Decidim is a participatory democracy framework for cities and organizations. Built in Ruby on Rails, it enables citizen participation through proposals, debates, and voting. Includes Sidekiq for background job processing.
|
||||
version: 0.31.0
|
||||
icon: https://raw.githubusercontent.com/decidim/decidim/develop/logo.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
installed_as: postgres
|
||||
- name: redis
|
||||
installed_as: redis
|
||||
defaultConfig:
|
||||
namespace: decidim
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
timezone: UTC
|
||||
port: 3000
|
||||
storage: 20Gi
|
||||
systemAdminEmail: "{{ .operator.email }}"
|
||||
siteName: "Decidim"
|
||||
domain: decidim.{{ .cloud.domain }}
|
||||
dbHostname: "{{ .apps.postgres.host }}"
|
||||
dbPort: "{{ .apps.postgres.port }}"
|
||||
dbUsername: decidim
|
||||
dbName: decidim
|
||||
redisHostname: "{{ .apps.redis.host }}"
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
enabled: true
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: systemAdminPassword
|
||||
- key: secretKeyBase
|
||||
default: "{{ random.AlphaNum 128 }}"
|
||||
- key: smtpPassword
|
||||
- key: dbPassword
|
||||
- key: dbUrl
|
||||
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/{{ .app.dbName }}"
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
- redis.password
|
||||
4
decidim/namespace.yaml
Normal file
4
decidim/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: decidim
|
||||
12
decidim/pvc.yaml
Normal file
12
decidim/pvc.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: decidim-data
|
||||
namespace: decidim
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage }}
|
||||
15
decidim/service.yaml
Normal file
15
decidim/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
spec:
|
||||
selector:
|
||||
component: web
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
7
decidim/serviceaccount.yaml
Normal file
7
decidim/serviceaccount.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
automountServiceAccountToken: false
|
||||
36
lemmy/configmap.yaml
Normal file
36
lemmy/configmap.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: lemmy-config
|
||||
namespace: {{ .namespace }}
|
||||
data:
|
||||
lemmy.hjson: |
|
||||
{
|
||||
hostname: "{{ .domain }}"
|
||||
bind: "0.0.0.0"
|
||||
port: {{ .backendPort }}
|
||||
tls_enabled: false
|
||||
|
||||
database: {
|
||||
uri: "postgresql://{{ .dbUser }}:DBPASSWORD@{{ .dbHost }}:{{ .dbPort }}/{{ .dbName }}"
|
||||
}
|
||||
|
||||
pictrs: {
|
||||
url: "http://lemmy-pictrs:{{ .pictrsPort }}/"
|
||||
api_key: "PICTRS_API_KEY"
|
||||
}
|
||||
|
||||
email: {
|
||||
smtp_server: "{{ .smtp.host }}:{{ .smtp.port }}"
|
||||
smtp_login: "{{ .smtp.user }}"
|
||||
smtp_password: "SMTP_PASSWORD"
|
||||
smtp_from_address: "{{ .smtp.from }}"
|
||||
tls_type: "{{ if eq .smtp.tls "true" }}tls{{ else }}none{{ end }}"
|
||||
}
|
||||
|
||||
setup: {
|
||||
admin_username: "admin"
|
||||
admin_password: "ADMIN_PASSWORD"
|
||||
site_name: "Lemmy"
|
||||
}
|
||||
}
|
||||
76
lemmy/db-init-job.yaml
Normal file
76
lemmy/db-init-job.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: lemmy-db-init
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: db-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: db-init
|
||||
image: postgres:16-alpine
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: "{{ .dbHost }}"
|
||||
- name: PGPORT
|
||||
value: "{{ .dbPort }}"
|
||||
- name: PGUSER
|
||||
value: postgres
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lemmy-secrets
|
||||
key: postgres.password
|
||||
- name: DB_NAME
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USER
|
||||
value: "{{ .dbUser }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lemmy-secrets
|
||||
key: dbPassword
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Waiting for PostgreSQL to be ready..."
|
||||
until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do
|
||||
echo "Waiting for database connection..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Creating database and user..."
|
||||
psql -v ON_ERROR_STOP=1 <<-EOSQL
|
||||
SELECT 'CREATE DATABASE ${DB_NAME}' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${DB_NAME}')\gexec
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '${DB_USER}') THEN
|
||||
CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';
|
||||
ELSE
|
||||
ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};
|
||||
\c ${DB_NAME}
|
||||
GRANT ALL ON SCHEMA public TO ${DB_USER};
|
||||
EOSQL
|
||||
|
||||
echo "Database initialization completed successfully"
|
||||
102
lemmy/deployment-backend.yaml
Normal file
102
lemmy/deployment-backend.yaml
Normal file
@@ -0,0 +1,102 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: lemmy-backend
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: {{ .backendReplicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
component: backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: backend
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
initContainers:
|
||||
- name: config-prep
|
||||
image: busybox:stable
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: true
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
cp /config-template/lemmy.hjson /config/lemmy.hjson
|
||||
sed -i "s|DBPASSWORD|${DB_PASSWORD}|g" /config/lemmy.hjson
|
||||
sed -i "s|PICTRS_API_KEY|${PICTRS_API_KEY}|g" /config/lemmy.hjson
|
||||
sed -i "s|SMTP_PASSWORD|${SMTP_PASSWORD}|g" /config/lemmy.hjson
|
||||
sed -i "s|ADMIN_PASSWORD|${ADMIN_PASSWORD}|g" /config/lemmy.hjson
|
||||
env:
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lemmy-secrets
|
||||
key: dbPassword
|
||||
- name: PICTRS_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lemmy-secrets
|
||||
key: jwtSecret
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lemmy-secrets
|
||||
key: smtpPassword
|
||||
- name: ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: lemmy-secrets
|
||||
key: adminPassword
|
||||
volumeMounts:
|
||||
- name: config-template
|
||||
mountPath: /config-template
|
||||
- name: config
|
||||
mountPath: /config
|
||||
containers:
|
||||
- name: backend
|
||||
image: {{ .backendImage }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: LEMMY_CONFIG_LOCATION
|
||||
value: /config/lemmy.hjson
|
||||
- name: TZ
|
||||
value: "{{ .timezone }}"
|
||||
ports:
|
||||
- containerPort: {{ .backendPort }}
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v3/site
|
||||
port: {{ .backendPort }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v3/site
|
||||
port: {{ .backendPort }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: config-template
|
||||
configMap:
|
||||
name: lemmy-config
|
||||
- name: config
|
||||
emptyDir: {}
|
||||
77
lemmy/deployment-pictrs.yaml
Normal file
77
lemmy/deployment-pictrs.yaml
Normal file
@@ -0,0 +1,77 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: lemmy-pictrs
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: {{ .pictrsReplicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
component: pictrs
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: pictrs
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: pictrs
|
||||
image: {{ .pictrsImage }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: PICTRS__SERVER__BIND
|
||||
value: "0.0.0.0:{{ .pictrsPort }}"
|
||||
- name: PICTRS__MEDIA__VIDEO_CODEC
|
||||
value: vp9
|
||||
- name: PICTRS__MEDIA__GIF__MAX_WIDTH
|
||||
value: "256"
|
||||
- name: PICTRS__MEDIA__GIF__MAX_HEIGHT
|
||||
value: "256"
|
||||
- name: PICTRS__MEDIA__GIF__MAX_AREA
|
||||
value: "65536"
|
||||
- name: PICTRS__MEDIA__GIF__MAX_FRAME_COUNT
|
||||
value: "400"
|
||||
- name: RUST_LOG
|
||||
value: debug
|
||||
- name: RUST_BACKTRACE
|
||||
value: full
|
||||
- name: PICTRS__REPO__TYPE
|
||||
value: sled
|
||||
- name: PICTRS__REPO__PATH
|
||||
value: /mnt/sled-repo
|
||||
- name: PICTRS__STORE__TYPE
|
||||
value: filesystem
|
||||
- name: PICTRS__STORE__PATH
|
||||
value: /mnt/files
|
||||
ports:
|
||||
- containerPort: {{ .pictrsPort }}
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /mnt
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .pictrsPort }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: {{ .pictrsPort }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: lemmy-pictrs-storage
|
||||
51
lemmy/deployment-ui.yaml
Normal file
51
lemmy/deployment-ui.yaml
Normal file
@@ -0,0 +1,51 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: lemmy-ui
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: {{ .uiReplicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
component: ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: ui
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: ui
|
||||
image: {{ .uiImage }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: LEMMY_UI_LEMMY_INTERNAL_HOST
|
||||
value: "lemmy-backend:{{ .backendPort }}"
|
||||
- name: LEMMY_UI_LEMMY_EXTERNAL_HOST
|
||||
value: "{{ .domain }}"
|
||||
- name: LEMMY_UI_HTTPS
|
||||
value: "true"
|
||||
ports:
|
||||
- containerPort: {{ .uiPort }}
|
||||
name: http
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .uiPort }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .uiPort }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
42
lemmy/ingress.yaml
Normal file
42
lemmy/ingress.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: lemmy-ingress
|
||||
namespace: {{ .namespace }}
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: lemmy-backend
|
||||
port:
|
||||
number: {{ .backendPort }}
|
||||
- path: /pictrs
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: lemmy-pictrs
|
||||
port:
|
||||
number: {{ .pictrsPort }}
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: lemmy-ui
|
||||
port:
|
||||
number: {{ .uiPort }}
|
||||
21
lemmy/kustomization.yaml
Normal file
21
lemmy/kustomization.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: lemmy
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: lemmy
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- pvc-pictrs.yaml
|
||||
- db-init-job.yaml
|
||||
- deployment-pictrs.yaml
|
||||
- service-pictrs.yaml
|
||||
- deployment-backend.yaml
|
||||
- service-backend.yaml
|
||||
- deployment-ui.yaml
|
||||
- service-ui.yaml
|
||||
- ingress.yaml
|
||||
41
lemmy/manifest.yaml
Normal file
41
lemmy/manifest.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
name: lemmy
|
||||
is: lemmy
|
||||
description: Lemmy is a selfhosted social link aggregation and discussion platform. It is an open source alternative to Reddit, designed for the fediverse.
|
||||
version: 0.19.15
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lemmy.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
defaultConfig:
|
||||
namespace: lemmy
|
||||
backendImage: dessalines/lemmy:0.19.15
|
||||
uiImage: dessalines/lemmy-ui:0.19.15
|
||||
pictrsImage: asonix/pictrs:0.5.5
|
||||
backendPort: 8536
|
||||
uiPort: 1234
|
||||
pictrsPort: 8080
|
||||
backendReplicas: 1
|
||||
uiReplicas: 1
|
||||
pictrsReplicas: 1
|
||||
storage: 10Gi
|
||||
pictrsStorage: 50Gi
|
||||
timezone: UTC
|
||||
domain: lemmy.{{ .cloud.domain }}
|
||||
externalDnsDomain: lemmy.{{ .cloud.baseDomain }}
|
||||
tlsSecretName: lemmy-tls
|
||||
dbName: lemmy
|
||||
dbUser: lemmy
|
||||
dbHost: postgres.postgres.svc.cluster.local
|
||||
dbPort: 5432
|
||||
smtp:
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "noreply@{{ .cloud.baseDomain }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
- key: adminPassword
|
||||
- key: jwtSecret
|
||||
- key: smtpPassword
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
4
lemmy/namespace.yaml
Normal file
4
lemmy/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ .namespace }}
|
||||
11
lemmy/pvc-pictrs.yaml
Normal file
11
lemmy/pvc-pictrs.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: lemmy-pictrs-storage
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .pictrsStorage }}
|
||||
13
lemmy/service-backend.yaml
Normal file
13
lemmy/service-backend.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: lemmy-backend
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
component: backend
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .backendPort }}
|
||||
targetPort: {{ .backendPort }}
|
||||
13
lemmy/service-pictrs.yaml
Normal file
13
lemmy/service-pictrs.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: lemmy-pictrs
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
component: pictrs
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .pictrsPort }}
|
||||
targetPort: {{ .pictrsPort }}
|
||||
13
lemmy/service-ui.yaml
Normal file
13
lemmy/service-ui.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: lemmy-ui
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
component: ui
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .uiPort }}
|
||||
targetPort: {{ .uiPort }}
|
||||
81
mastodon/README.md
Normal file
81
mastodon/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Mastodon
|
||||
|
||||
Mastodon is a free, open-source social network server based on ActivityPub. It allows you to run your own instance of a decentralized social media platform.
|
||||
|
||||
## Version
|
||||
|
||||
This package deploys Mastodon v4.5.3 (released July 8, 2025).
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL**: Database for storing application data
|
||||
- **Redis**: Used for caching and background job queuing
|
||||
|
||||
## Configuration
|
||||
|
||||
### VAPID Keys
|
||||
|
||||
Mastodon requires VAPID (Voluntary Application Server Identification) keys for Web Push notifications. These keys use Elliptic Curve P-256 cryptography.
|
||||
|
||||
**The Wild Cloud API automatically generates proper VAPID keys when you add the Mastodon app.** No manual configuration is required!
|
||||
|
||||
### Database
|
||||
|
||||
The database is automatically initialized with:
|
||||
- Database: `mastodon_production`
|
||||
- User: `mastodon` with auto-generated password
|
||||
- All necessary privileges granted
|
||||
|
||||
The db-init job handles creating the database and user, and automatically updates the user password if it changes.
|
||||
|
||||
### Storage
|
||||
|
||||
Mastodon uses two persistent volumes:
|
||||
- **Assets** (10Gi): Stores compiled assets and static files
|
||||
- **System** (100Gi): Stores user uploads, media files, and other system data
|
||||
|
||||
Both volumes use ReadWriteMany access mode to allow multiple pods to access them simultaneously.
|
||||
|
||||
## Components
|
||||
|
||||
Mastodon runs three separate services:
|
||||
|
||||
- **Web (Puma)**: Main web server for the Mastodon web interface
|
||||
- **Streaming (Node.js)**: Real-time streaming API for live updates
|
||||
- **Sidekiq**: Background job processor for async tasks
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Mastodon will be available at:
|
||||
- https://mastodon.{your-cloud-domain}
|
||||
|
||||
The ingress automatically routes:
|
||||
- `/api/v1/streaming` → Streaming service
|
||||
- All other paths → Web service
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add mastodon
|
||||
wild app deploy mastodon
|
||||
```
|
||||
|
||||
2. Generate and configure VAPID keys (see above)
|
||||
|
||||
3. Access your instance in a browser and create the first admin user account
|
||||
|
||||
4. Configure additional settings through the Mastodon admin interface
|
||||
|
||||
## Security
|
||||
|
||||
All containers run as non-root user (UID 991) with:
|
||||
- No privilege escalation
|
||||
- All capabilities dropped
|
||||
- Compliant with Pod Security Standards
|
||||
|
||||
## Notes
|
||||
|
||||
- SMTP configuration is inherited from your Wild Cloud instance settings
|
||||
- Database credentials are auto-generated and stored in your instance's `secrets.yaml`
|
||||
- The Active Record Encryption keys are auto-generated for Rails 8.0.3 compatibility
|
||||
185
mastodon/db-init-job.yaml
Normal file
185
mastodon/db-init-job.yaml
Normal file
@@ -0,0 +1,185 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: mastodon-db-init
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: db-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: db-init
|
||||
image: postgres:16-alpine
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: PGPORT
|
||||
value: "{{ .dbPort }}"
|
||||
- name: PGUSER
|
||||
value: postgres
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: postgres.password
|
||||
- name: MASTODON_DB
|
||||
value: "{{ .dbName }}"
|
||||
- name: MASTODON_USER
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: MASTODON_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: dbPassword
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Waiting for PostgreSQL to be ready..."
|
||||
until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do
|
||||
echo "PostgreSQL is unavailable - sleeping"
|
||||
sleep 2
|
||||
done
|
||||
echo "PostgreSQL is ready"
|
||||
|
||||
echo "Creating database if it doesn't exist..."
|
||||
psql -v ON_ERROR_STOP=1 <<-EOSQL
|
||||
SELECT 'CREATE DATABASE $MASTODON_DB'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$MASTODON_DB')\gexec
|
||||
EOSQL
|
||||
|
||||
echo "Creating/updating user..."
|
||||
psql -v ON_ERROR_STOP=1 <<-EOSQL
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_user WHERE usename = '$MASTODON_USER') THEN
|
||||
CREATE USER $MASTODON_USER WITH PASSWORD '$MASTODON_PASSWORD';
|
||||
ELSE
|
||||
ALTER USER $MASTODON_USER WITH PASSWORD '$MASTODON_PASSWORD';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
EOSQL
|
||||
|
||||
echo "Granting privileges..."
|
||||
psql -v ON_ERROR_STOP=1 <<-EOSQL
|
||||
GRANT ALL PRIVILEGES ON DATABASE $MASTODON_DB TO $MASTODON_USER;
|
||||
\c $MASTODON_DB
|
||||
GRANT ALL ON SCHEMA public TO $MASTODON_USER;
|
||||
EOSQL
|
||||
|
||||
echo "Database initialization complete"
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: mastodon-db-migrate
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: db-migrate
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: db-migrate
|
||||
image: {{ .image }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
command:
|
||||
- bundle
|
||||
- exec
|
||||
- rails
|
||||
- db:migrate
|
||||
env:
|
||||
- name: LOCAL_DOMAIN
|
||||
value: "{{ .domain }}"
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: secretKeyBase
|
||||
- name: OTP_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: otpSecret
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordPrimaryKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordDeterministicKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordKeyDerivationSalt
|
||||
- name: DB_HOST
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_PORT
|
||||
value: "{{ .dbPort }}"
|
||||
- name: DB_NAME
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USER
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: dbPassword
|
||||
- name: REDIS_HOST
|
||||
value: "{{ .redisHostname }}"
|
||||
- name: REDIS_PORT
|
||||
value: "{{ .redisPort }}"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: redis.password
|
||||
volumeMounts:
|
||||
- name: assets
|
||||
mountPath: /opt/mastodon/public/assets
|
||||
- name: system
|
||||
mountPath: /opt/mastodon/public/system
|
||||
volumes:
|
||||
- name: assets
|
||||
persistentVolumeClaim:
|
||||
claimName: mastodon-assets
|
||||
- name: system
|
||||
persistentVolumeClaim:
|
||||
claimName: mastodon-system
|
||||
156
mastodon/deployment-sidekiq.yaml
Normal file
156
mastodon/deployment-sidekiq.yaml
Normal file
@@ -0,0 +1,156 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mastodon-sidekiq
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: {{ .sidekiq.replicas }}
|
||||
selector:
|
||||
matchLabels:
|
||||
component: sidekiq
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: sidekiq
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: sidekiq
|
||||
image: {{ .image }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
command:
|
||||
- bundle
|
||||
- exec
|
||||
- sidekiq
|
||||
- -c
|
||||
- "{{ .sidekiq.concurrency }}"
|
||||
- -q
|
||||
- default,8
|
||||
- -q
|
||||
- push,6
|
||||
- -q
|
||||
- ingress,4
|
||||
- -q
|
||||
- mailers,2
|
||||
- -q
|
||||
- pull
|
||||
- -q
|
||||
- scheduler
|
||||
env:
|
||||
- name: LOCAL_DOMAIN
|
||||
value: "{{ .domain }}"
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: RAILS_LOG_LEVEL
|
||||
value: info
|
||||
- name: DEFAULT_LOCALE
|
||||
value: "{{ .locale }}"
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: secretKeyBase
|
||||
- name: OTP_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: otpSecret
|
||||
- name: VAPID_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: vapidPrivateKey
|
||||
- name: VAPID_PUBLIC_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: vapidPublicKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordPrimaryKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordDeterministicKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordKeyDerivationSalt
|
||||
- name: DB_HOST
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_PORT
|
||||
value: "{{ .dbPort }}"
|
||||
- name: DB_NAME
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USER
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: dbPassword
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: postgres.password
|
||||
- name: REDIS_HOST
|
||||
value: "{{ .redisHostname }}"
|
||||
- name: REDIS_PORT
|
||||
value: "{{ .redisPort }}"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: redis.password
|
||||
- name: SMTP_SERVER
|
||||
value: "{{ .smtp.server }}"
|
||||
- name: SMTP_PORT
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: SMTP_LOGIN
|
||||
value: "{{ .smtp.user }}"
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: smtpPassword
|
||||
- name: SMTP_FROM_ADDRESS
|
||||
value: "{{ .smtp.from }}"
|
||||
- name: SMTP_AUTH_METHOD
|
||||
value: "{{ .smtp.authMethod }}"
|
||||
- name: SMTP_ENABLE_STARTTLS
|
||||
value: "{{ .smtp.enableStarttls }}"
|
||||
- name: SMTP_TLS
|
||||
value: "{{ .smtp.tls }}"
|
||||
volumeMounts:
|
||||
- name: assets
|
||||
mountPath: /opt/mastodon/public/assets
|
||||
- name: system
|
||||
mountPath: /opt/mastodon/public/system
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 768Mi
|
||||
volumes:
|
||||
- name: assets
|
||||
persistentVolumeClaim:
|
||||
claimName: mastodon-assets
|
||||
- name: system
|
||||
persistentVolumeClaim:
|
||||
claimName: mastodon-system
|
||||
83
mastodon/deployment-streaming.yaml
Normal file
83
mastodon/deployment-streaming.yaml
Normal file
@@ -0,0 +1,83 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mastodon-streaming
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: streaming
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: streaming
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: streaming
|
||||
image: {{ .streamingImage }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
ports:
|
||||
- name: streaming
|
||||
containerPort: {{ .streamingPort }}
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: production
|
||||
- name: PORT
|
||||
value: "{{ .streamingPort }}"
|
||||
- name: STREAMING_CLUSTER_NUM
|
||||
value: "1"
|
||||
- name: DB_HOST
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_PORT
|
||||
value: "{{ .dbPort }}"
|
||||
- name: DB_NAME
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USER
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: dbPassword
|
||||
- name: REDIS_HOST
|
||||
value: "{{ .redisHostname }}"
|
||||
- name: REDIS_PORT
|
||||
value: "{{ .redisPort }}"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: redis.password
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/streaming/health
|
||||
port: streaming
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/v1/streaming/health
|
||||
port: streaming
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
170
mastodon/deployment-web.yaml
Normal file
170
mastodon/deployment-web.yaml
Normal file
@@ -0,0 +1,170 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mastodon-web
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: web
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: web
|
||||
image: {{ .image }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
command:
|
||||
- bundle
|
||||
- exec
|
||||
- puma
|
||||
- -C
|
||||
- config/puma.rb
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .webPort }}
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: LOCAL_DOMAIN
|
||||
value: "{{ .domain }}"
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: RAILS_LOG_LEVEL
|
||||
value: info
|
||||
- name: DEFAULT_LOCALE
|
||||
value: "{{ .locale }}"
|
||||
- name: SINGLE_USER_MODE
|
||||
value: "{{ .singleUserMode }}"
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: secretKeyBase
|
||||
- name: OTP_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: otpSecret
|
||||
- name: VAPID_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: vapidPrivateKey
|
||||
- name: VAPID_PUBLIC_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: vapidPublicKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordPrimaryKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordDeterministicKey
|
||||
- name: ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: activeRecordKeyDerivationSalt
|
||||
- name: DB_HOST
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_PORT
|
||||
value: "{{ .dbPort }}"
|
||||
- name: DB_NAME
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USER
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: dbPassword
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: postgres.password
|
||||
- name: REDIS_HOST
|
||||
value: "{{ .redisHostname }}"
|
||||
- name: REDIS_PORT
|
||||
value: "{{ .redisPort }}"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: redis.password
|
||||
- name: SMTP_SERVER
|
||||
value: "{{ .smtp.server }}"
|
||||
- name: SMTP_PORT
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: SMTP_LOGIN
|
||||
value: "{{ .smtp.user }}"
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: smtpPassword
|
||||
- name: SMTP_FROM_ADDRESS
|
||||
value: "{{ .smtp.from }}"
|
||||
- name: SMTP_AUTH_METHOD
|
||||
value: "{{ .smtp.authMethod }}"
|
||||
- name: SMTP_ENABLE_STARTTLS
|
||||
value: "{{ .smtp.enableStarttls }}"
|
||||
- name: SMTP_TLS
|
||||
value: "{{ .smtp.tls }}"
|
||||
- name: STREAMING_API_BASE_URL
|
||||
value: "wss://{{ .domain }}"
|
||||
- name: WEB_CONCURRENCY
|
||||
value: "2"
|
||||
- name: MAX_THREADS
|
||||
value: "5"
|
||||
volumeMounts:
|
||||
- name: assets
|
||||
mountPath: /opt/mastodon/public/assets
|
||||
- name: system
|
||||
mountPath: /opt/mastodon/public/system
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 768Mi
|
||||
limits:
|
||||
memory: 1280Mi
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
volumes:
|
||||
- name: assets
|
||||
persistentVolumeClaim:
|
||||
claimName: mastodon-assets
|
||||
- name: system
|
||||
persistentVolumeClaim:
|
||||
claimName: mastodon-system
|
||||
33
mastodon/ingress.yaml
Normal file
33
mastodon/ingress.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: mastodon
|
||||
namespace: {{ .namespace }}
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /api/v1/streaming
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mastodon-streaming
|
||||
port:
|
||||
number: {{ .streamingPort }}
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: mastodon-web
|
||||
port:
|
||||
number: {{ .webPort }}
|
||||
21
mastodon/kustomization.yaml
Normal file
21
mastodon/kustomization.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: {{ .namespace }}
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: mastodon
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- pvc-assets.yaml
|
||||
- pvc-system.yaml
|
||||
- db-init-job.yaml
|
||||
- vapid-init-job.yaml
|
||||
- deployment-web.yaml
|
||||
- deployment-sidekiq.yaml
|
||||
- deployment-streaming.yaml
|
||||
- service-web.yaml
|
||||
- service-streaming.yaml
|
||||
- ingress.yaml
|
||||
67
mastodon/manifest.yaml
Normal file
67
mastodon/manifest.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
name: mastodon
|
||||
is: mastodon
|
||||
description: Mastodon is a free, open-source social network server based on ActivityPub.
|
||||
version: 4.5.3
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mastodon.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: redis
|
||||
defaultConfig:
|
||||
namespace: mastodon
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
timezone: UTC
|
||||
image: ghcr.io/mastodon/mastodon:v4.5.3
|
||||
streamingImage: ghcr.io/mastodon/mastodon-streaming:v4.5.3
|
||||
domain: mastodon.{{ .cloud.domain }}
|
||||
locale: en
|
||||
singleUserMode: false
|
||||
# Database configuration
|
||||
dbHostname: "{{ .apps.postgres.host }}"
|
||||
dbPort: "{{ .apps.postgres.port }}"
|
||||
dbName: mastodon_production
|
||||
dbUsername: mastodon
|
||||
# Redis configuration
|
||||
redisHostname: "{{ .apps.redis.host }}"
|
||||
redisPort: "{{ .apps.redis.port }}"
|
||||
# Ports
|
||||
webPort: 3000
|
||||
streamingPort: 4000
|
||||
# Storage
|
||||
assetsStorage: 10Gi
|
||||
systemStorage: 100Gi
|
||||
# SMTP configuration
|
||||
smtp:
|
||||
enabled: "{{ .cloud.smtp.host | ternary true false }}"
|
||||
server: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
from: notifications@{{ .cloud.domain }}
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
authMethod: plain
|
||||
enableStarttls: auto
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
# TLS
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
# Sidekiq configuration
|
||||
sidekiq:
|
||||
replicas: 1
|
||||
concurrency: 25
|
||||
defaultSecrets:
|
||||
- key: secretKeyBase
|
||||
default: "{{ random.AlphaNum 128 }}"
|
||||
- key: otpSecret
|
||||
default: "{{ random.AlphaNum 128 }}"
|
||||
- key: vapidPrivateKey
|
||||
# Generated by vapid-init-job.yaml on first deploy
|
||||
- key: vapidPublicKey
|
||||
# Generated by vapid-init-job.yaml on first deploy
|
||||
- key: activeRecordPrimaryKey
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
- key: activeRecordDeterministicKey
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
- key: activeRecordKeyDerivationSalt
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
- key: dbPassword
|
||||
- key: smtpPassword
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
- redis.password
|
||||
4
mastodon/namespace.yaml
Normal file
4
mastodon/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ .namespace }}
|
||||
11
mastodon/pvc-assets.yaml
Normal file
11
mastodon/pvc-assets.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: mastodon-assets
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .assetsStorage }}
|
||||
11
mastodon/pvc-system.yaml
Normal file
11
mastodon/pvc-system.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: mastodon-system
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .systemStorage }}
|
||||
14
mastodon/service-streaming.yaml
Normal file
14
mastodon/service-streaming.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mastodon-streaming
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: {{ .streamingPort }}
|
||||
targetPort: streaming
|
||||
protocol: TCP
|
||||
name: streaming
|
||||
selector:
|
||||
component: streaming
|
||||
14
mastodon/service-web.yaml
Normal file
14
mastodon/service-web.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mastodon-web
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: {{ .webPort }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
component: web
|
||||
68
mastodon/vapid-init-job.yaml
Normal file
68
mastodon/vapid-init-job.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: mastodon-vapid-init
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: vapid-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: vapid-init
|
||||
image: {{ .image }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
# Check if VAPID keys already exist in the secret
|
||||
if [ -n "$VAPID_PRIVATE_KEY" ] && [ "$VAPID_PRIVATE_KEY" != "null" ] && \
|
||||
[ -n "$VAPID_PUBLIC_KEY" ] && [ "$VAPID_PUBLIC_KEY" != "null" ]; then
|
||||
echo "VAPID keys already exist in secret, skipping generation"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Generating VAPID keys..."
|
||||
bundle exec rake mastodon:webpush:generate_vapid_key > /tmp/vapid_output.txt
|
||||
|
||||
echo "VAPID keys generated:"
|
||||
cat /tmp/vapid_output.txt
|
||||
|
||||
echo ""
|
||||
echo "NOTE: These keys must be manually added to secrets.yaml:"
|
||||
echo " apps.mastodon.vapidPrivateKey: <VAPID_PRIVATE_KEY from above>"
|
||||
echo " apps.mastodon.vapidPublicKey: <VAPID_PUBLIC_KEY from above>"
|
||||
env:
|
||||
- name: VAPID_PRIVATE_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: vapidPrivateKey
|
||||
optional: true
|
||||
- name: VAPID_PUBLIC_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mastodon-secrets
|
||||
key: vapidPublicKey
|
||||
optional: true
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: LOCAL_DOMAIN
|
||||
value: "{{ .domain }}"
|
||||
66
matrix/configmap.yaml
Normal file
66
matrix/configmap.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: matrix-config
|
||||
data:
|
||||
homeserver.yaml: |
|
||||
server_name: "{{ .serverName }}"
|
||||
public_baseurl: https://{{ .domain }}
|
||||
|
||||
listeners:
|
||||
- port: {{ .port }}
|
||||
tls: false
|
||||
type: http
|
||||
x_forwarded: true
|
||||
bind_addresses: ['::']
|
||||
resources:
|
||||
- names: [client, federation]
|
||||
compress: false
|
||||
|
||||
database:
|
||||
name: psycopg2
|
||||
args:
|
||||
user: {{ .dbUsername }}
|
||||
password: ${DB_PASSWORD}
|
||||
database: {{ .dbName }}
|
||||
host: {{ .dbHostname }}
|
||||
port: 5432
|
||||
cp_min: 5
|
||||
cp_max: 10
|
||||
|
||||
redis:
|
||||
enabled: true
|
||||
host: {{ .redisHostname }}
|
||||
port: 6379
|
||||
password: ${REDIS_PASSWORD}
|
||||
|
||||
media_store_path: /data/media_store
|
||||
uploads_path: /data/uploads
|
||||
|
||||
max_upload_size: 100M
|
||||
|
||||
enable_registration: {{ .enableRegistration }}
|
||||
registration_shared_secret: "${REGISTRATION_SHARED_SECRET}"
|
||||
|
||||
macaroon_secret_key: "${MACAROON_SECRET_KEY}"
|
||||
form_secret: "${FORM_SECRET}"
|
||||
|
||||
signing_key_path: /data/keys/signing.key
|
||||
|
||||
trusted_key_servers:
|
||||
- server_name: "matrix.org"
|
||||
|
||||
email:
|
||||
smtp_host: "{{ .smtp.host }}"
|
||||
smtp_port: {{ .smtp.port }}
|
||||
smtp_user: "{{ .smtp.user }}"
|
||||
smtp_pass: "${SMTP_PASSWORD}"
|
||||
require_transport_security: {{ .smtp.requireTls }}
|
||||
notif_from: "{{ .smtp.from }}"
|
||||
app_name: Matrix
|
||||
|
||||
report_stats: false
|
||||
|
||||
enable_metrics: true
|
||||
|
||||
suppress_key_server_warning: true
|
||||
57
matrix/db-init-job.yaml
Normal file
57
matrix/db-init-job.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: matrix-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} ENCODING ''UTF8'' LC_COLLATE ''C'' LC_CTYPE ''C'' TEMPLATE template0' 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: matrix-secrets
|
||||
key: postgres.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USERNAME
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: dbPassword
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
restartPolicy: OnFailure
|
||||
221
matrix/deployment.yaml
Normal file
221
matrix/deployment.yaml
Normal file
@@ -0,0 +1,221 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: matrix-synapse
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: matrix-synapse
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: matrix-synapse
|
||||
component: synapse
|
||||
spec:
|
||||
initContainers:
|
||||
- name: generate-signing-key
|
||||
image: "{{ .image }}"
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
if [ ! -f /data/keys/signing.key ]; then
|
||||
echo "Generating signing key..."
|
||||
mkdir -p /data/keys
|
||||
# Use Synapse's generate-keys command
|
||||
python3 -m synapse.app.homeserver \
|
||||
--generate-keys \
|
||||
--config-path=/config/homeserver.yaml
|
||||
echo "Signing key generated successfully"
|
||||
ls -la /data/keys/
|
||||
else
|
||||
echo "Signing key already exists"
|
||||
fi
|
||||
env:
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: dbPassword
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: redis.password
|
||||
- name: REGISTRATION_SHARED_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: registrationSharedSecret
|
||||
- name: MACAROON_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: macaroonSecretKey
|
||||
- name: FORM_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: formSecret
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: smtp.password
|
||||
volumeMounts:
|
||||
- name: matrix-data
|
||||
mountPath: /data
|
||||
- name: matrix-config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
securityContext:
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
containers:
|
||||
- name: synapse
|
||||
image: "{{ .image }}"
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
echo "Starting config substitution..."
|
||||
|
||||
# Substitute environment variables in the config using Python
|
||||
python3 -c "
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
print('Reading config from /config/homeserver.yaml', file=sys.stderr)
|
||||
try:
|
||||
with open('/config/homeserver.yaml', 'r') as f:
|
||||
content = f.read()
|
||||
print(f'Config file read: {len(content)} bytes', file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f'Error reading config: {e}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Replace \${VAR} with environment variable values
|
||||
def replace_var(match):
|
||||
var_name = match.group(1)
|
||||
value = os.environ.get(var_name, match.group(0))
|
||||
print(f'Replacing {var_name}: {\"***\" if \"PASSWORD\" in var_name or \"SECRET\" in var_name else value}', file=sys.stderr)
|
||||
return value
|
||||
|
||||
content = re.sub(r'\\\$\{([A-Z_]+)\}', replace_var, content)
|
||||
|
||||
print('Writing processed config to /data/homeserver.yaml', file=sys.stderr)
|
||||
try:
|
||||
with open('/data/homeserver.yaml', 'w') as f:
|
||||
f.write(content)
|
||||
print('Config file written successfully', file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f'Error writing config: {e}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
" || { echo "Python script failed with exit code $?"; exit 1; }
|
||||
|
||||
echo "Config substitution complete"
|
||||
ls -la /data/homeserver.yaml
|
||||
|
||||
# Start Synapse with the processed config
|
||||
exec /start.py
|
||||
ports:
|
||||
- containerPort: {{ .port }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
- containerPort: {{ .federationPort }}
|
||||
protocol: TCP
|
||||
name: federation
|
||||
env:
|
||||
- name: SYNAPSE_CONFIG_PATH
|
||||
value: /data/homeserver.yaml
|
||||
- name: TZ
|
||||
value: "{{ .timezone }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: dbPassword
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: redis.password
|
||||
- name: REGISTRATION_SHARED_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: registrationSharedSecret
|
||||
- name: MACAROON_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: macaroonSecretKey
|
||||
- name: FORM_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: formSecret
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: matrix-secrets
|
||||
key: smtp.password
|
||||
volumeMounts:
|
||||
- name: matrix-config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
- name: matrix-data
|
||||
mountPath: /data
|
||||
- name: matrix-media
|
||||
mountPath: /data/media_store
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 991
|
||||
runAsGroup: 991
|
||||
fsGroup: 991
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: matrix-config
|
||||
configMap:
|
||||
name: matrix-config
|
||||
- name: matrix-data
|
||||
persistentVolumeClaim:
|
||||
claimName: matrix-data-pvc
|
||||
- name: matrix-media
|
||||
persistentVolumeClaim:
|
||||
claimName: matrix-media-pvc
|
||||
52
matrix/ingress.yaml
Normal file
52
matrix/ingress.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: matrix-client-ingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: matrix-synapse
|
||||
port:
|
||||
number: {{ .port }}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: matrix-federation-ingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .serverName }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .serverName }}
|
||||
http:
|
||||
paths:
|
||||
- path: /.well-known/matrix
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: matrix-synapse
|
||||
port:
|
||||
number: {{ .federationPort }}
|
||||
17
matrix/kustomization.yaml
Normal file
17
matrix/kustomization.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: {{ .namespace }}
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: matrix
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- configmap.yaml
|
||||
- pvc.yaml
|
||||
- db-init-job.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
41
matrix/manifest.yaml
Normal file
41
matrix/manifest.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
name: matrix
|
||||
is: matrix
|
||||
install: true
|
||||
description: Matrix is an open standard for secure, decentralized, real-time communication. This deploys the Synapse homeserver for self-hosted Matrix federation and messaging.
|
||||
version: v1.144.0
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/matrix.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: redis
|
||||
defaultConfig:
|
||||
namespace: matrix
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
image: matrixdotorg/synapse:v1.144.0
|
||||
timezone: UTC
|
||||
port: 8008
|
||||
federationPort: 8448
|
||||
storage: 50Gi
|
||||
mediaStorage: 100Gi
|
||||
serverName: '{{ .cloud.domain }}'
|
||||
dbHostname: postgres.postgres.svc.cluster.local
|
||||
dbUsername: matrix
|
||||
dbName: matrix
|
||||
redisHostname: redis.redis.svc.cluster.local
|
||||
domain: matrix.{{ .cloud.domain }}
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
enableRegistration: false
|
||||
smtp:
|
||||
host: '{{ .cloud.smtp.host }}'
|
||||
port: '{{ .cloud.smtp.port }}'
|
||||
from: matrix@{{ .cloud.domain }}
|
||||
user: '{{ .cloud.smtp.user }}'
|
||||
requireTls: '{{ .cloud.smtp.tls }}'
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
- key: registrationSharedSecret
|
||||
- key: macaroonSecretKey
|
||||
- key: formSecret
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
- redis.password
|
||||
- smtp.password
|
||||
4
matrix/namespace.yaml
Normal file
4
matrix/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ .namespace }}
|
||||
22
matrix/pvc.yaml
Normal file
22
matrix/pvc.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: matrix-data-pvc
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: matrix-media-pvc
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .mediaStorage }}
|
||||
18
matrix/service.yaml
Normal file
18
matrix/service.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: matrix-synapse
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .port }}
|
||||
targetPort: {{ .port }}
|
||||
protocol: TCP
|
||||
- name: federation
|
||||
port: {{ .federationPort }}
|
||||
targetPort: {{ .federationPort }}
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: matrix-synapse
|
||||
@@ -7,6 +7,8 @@ metadata:
|
||||
data:
|
||||
DATABASE_HOST: "{{ .dbHostname }}"
|
||||
DATABASE_PORT: "5432"
|
||||
DATABASE_NAME: "{{ .dbName }}"
|
||||
DATABASE_USERNAME: "{{ .dbUsername }}"
|
||||
DATABASE_URL: "postgresql://{{ .dbUsername }}@{{ .dbHostname }}:5432/{{ .dbName }}"
|
||||
OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "{{ .adminPasswordReset }}"
|
||||
OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .adminUserName }}"
|
||||
|
||||
@@ -4,6 +4,9 @@ apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: openproject
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
tls:
|
||||
- hosts:
|
||||
|
||||
@@ -77,7 +77,7 @@ spec:
|
||||
command: [
|
||||
'sh',
|
||||
'-c',
|
||||
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done; echo "Database is ready!"'
|
||||
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT -d $DATABASE_NAME -U $DATABASE_USERNAME; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done; echo "Database is ready!"'
|
||||
]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
|
||||
@@ -77,7 +77,7 @@ spec:
|
||||
command: [
|
||||
'sh',
|
||||
'-c',
|
||||
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done; echo "Database is ready!"'
|
||||
'until pg_isready -h $DATABASE_HOST -p $DATABASE_PORT -d $DATABASE_NAME -U $DATABASE_USERNAME; do echo "waiting for database $DATABASE_HOST:$DATABASE_PORT"; sleep 2; done; echo "Database is ready!"'
|
||||
]
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
|
||||
Reference in New Issue
Block a user