Compare commits
13 Commits
d1304a2630
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2aa16e679 | ||
|
|
37dafcd24d | ||
|
|
b6d88e79ac | ||
|
|
963929475c | ||
|
|
39095e76d2 | ||
|
|
d756126a34 | ||
|
|
f17fea6910 | ||
|
|
0ba33a315d | ||
|
|
12706ac331 | ||
|
|
a159c90816 | ||
|
|
32498c73b8 | ||
|
|
c93198d13a | ||
|
|
434769ac7a |
152
ADDING-APPS.md
152
ADDING-APPS.md
@@ -14,7 +14,7 @@ Each app directory must contain:
|
|||||||
2. **`kustomization.yaml`** - Kustomize configuration with Wild Cloud labels
|
2. **`kustomization.yaml`** - Kustomize configuration with Wild Cloud labels
|
||||||
3. **Resource files** - Kubernetes manifests (deployments, services, ingresses, etc.)
|
3. **Resource files** - Kubernetes manifests (deployments, services, ingresses, etc.)
|
||||||
|
|
||||||
### App Manifest (`manifest.yaml`)
|
## App Manifest (`manifest.yaml`)
|
||||||
|
|
||||||
The manifest defines the app's metadata, dependencies, configuration schema, and secret requirements.
|
The manifest defines the app's metadata, dependencies, configuration schema, and secret requirements.
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ This is the contents of an example `manifest.yaml` file for an app named "immich
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: immich
|
name: immich
|
||||||
|
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.
|
description: Immich is a self-hosted photo and video backup solution that allows you to store, manage, and share your media files securely.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
icon: https://immich.app/assets/images/logo.png
|
icon: https://immich.app/assets/images/logo.png
|
||||||
@@ -54,11 +55,12 @@ requiredSecrets:
|
|||||||
- redis.auth # References redis app via 'redis' name (no alias)
|
- redis.auth # References redis app via 'redis' name (no alias)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Manifest Fields
|
### Manifest Fields
|
||||||
|
|
||||||
| Field | Required | Description |
|
| Field | Required | Description |
|
||||||
|-------|----------|-------------|
|
|-------|----------|-------------|
|
||||||
| `name` | Yes | App identifier (must match directory name) |
|
| `name` | Yes | App identifier (must match directory name) |
|
||||||
|
| `is` | Yes | Unique id for this app. Used for `requires` mapping |
|
||||||
| `description` | Yes | Brief app description shown in listings |
|
| `description` | Yes | Brief app description shown in listings |
|
||||||
| `version` | Yes | App version (follow upstream versioning) |
|
| `version` | Yes | App version (follow upstream versioning) |
|
||||||
| `icon` | No | URL to app icon for UI display |
|
| `icon` | No | URL to app icon for UI display |
|
||||||
@@ -67,18 +69,156 @@ requiredSecrets:
|
|||||||
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
||||||
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
||||||
|
|
||||||
**Dependency Configuration:**
|
### Dependency Configuration
|
||||||
|
|
||||||
- Each dependency in `requires` can have:
|
- Each dependency in `requires` can have:
|
||||||
- `name`: The actual app name to depend on
|
- `name`: The app name to depend on (any app with a matching `is` field can satisfy this requirement)
|
||||||
- `alias`: Optional reference name for templates (defaults to `name`)
|
- `alias`: Optional reference name for templates (defaults to `name`)
|
||||||
|
|
||||||
**Manifest Template Variable Sources:**
|
### Manifest Template Variables (configuration and secrets)
|
||||||
|
|
||||||
|
#### Manifest Template Variable Sources
|
||||||
|
|
||||||
1. Standard Wild Cloud variables: `{{ .cloud.* }}`, `{{ .cluster.* }}`, `{{ .operator.* }}`
|
1. Standard Wild Cloud variables: `{{ .cloud.* }}`, `{{ .cluster.* }}`, `{{ .operator.* }}`
|
||||||
2. App-specific variables: `{{ .app.* }}` - resolved from current app's config
|
2. App-specific variables: `{{ .app.* }}` - resolved from current app's config
|
||||||
3. Dependency variables: `{{ .apps.<ref>.* }}` - resolved using app reference mapping
|
3. Dependency variables: `{{ .apps.<ref>.* }}` - resolved using app reference mapping
|
||||||
4. App-specific secrets (in 'defaultSecrets' ONLY): `{{ secrets.* }}`
|
4. App-specific secrets (in 'defaultSecrets' ONLY): `{{ secrets.* }}`
|
||||||
|
|
||||||
**Manifest App Reference Resolution:**
|
#### Available Configuration Variiables
|
||||||
|
|
||||||
|
Here's a comprehensive rundown of all config variables that get set during cluster and service setup in config.yaml:
|
||||||
|
|
||||||
|
##### operator (Set during initial setup)
|
||||||
|
|
||||||
|
- operator.email - Email for cluster operator/admin
|
||||||
|
|
||||||
|
##### cloud (Infrastructure-level settings)
|
||||||
|
|
||||||
|
###### DNS Configuration:
|
||||||
|
- cloud.dns.ip - IP address of the DNS server (Wild Central)
|
||||||
|
- cloud.dns.externalResolver - External DNS resolver (e.g., 1.1.1.1, 8.8.8.8)
|
||||||
|
|
||||||
|
###### Network Configuration:
|
||||||
|
|
||||||
|
- cloud.router.ip - Router gateway IP
|
||||||
|
- cloud.router.dynamicDns - Dynamic DNS hostname (optional)
|
||||||
|
- cloud.dhcpRange - DHCP range for the network (e.g., "192.168.8.34,192.168.8.79")
|
||||||
|
- cloud.dnsmasq.interface - Network interface for dnsmasq
|
||||||
|
|
||||||
|
###### Domain Configuration:
|
||||||
|
|
||||||
|
- cloud.baseDomain - Base domain for the cloud (e.g., "payne.io")
|
||||||
|
- cloud.domain - Full cloud domain (e.g., "cloud2.payne.io")
|
||||||
|
- cloud.internalDomain - Internal cluster domain (e.g., "internal.cloud2.payne.io")
|
||||||
|
|
||||||
|
###### Storage Configuration (NFS Service):
|
||||||
|
|
||||||
|
- cloud.nfs.host - NFS server hostname/IP
|
||||||
|
- cloud.nfs.mediaPath - NFS export path for media storage
|
||||||
|
- cloud.nfs.storageCapacity - NFS storage capacity (e.g., "50Gi", "1Ti")
|
||||||
|
|
||||||
|
###### Registry Configuration (Docker Registry Service):
|
||||||
|
|
||||||
|
- cloud.dockerRegistryHost - Docker registry hostname (e.g., "registry.internal.cloud2.payne.io")
|
||||||
|
|
||||||
|
##### SMTP Configuration (SMTP Service):
|
||||||
|
|
||||||
|
- cloud.smtp.host - SMTP server hostname
|
||||||
|
- cloud.smtp.port - SMTP port (typically "465" or "587")
|
||||||
|
- cloud.smtp.user - SMTP username
|
||||||
|
- cloud.smtp.from - Default 'from' email address
|
||||||
|
- cloud.smtp.tls - Enable TLS (true/false)
|
||||||
|
- cloud.smtp.startTls - Enable STARTTLS (true/false)
|
||||||
|
|
||||||
|
###### Backup Configuration:
|
||||||
|
|
||||||
|
- cloud.backup.root - Root path for backups
|
||||||
|
|
||||||
|
##### cluster (Kubernetes cluster settings)
|
||||||
|
|
||||||
|
###### Basic Cluster Info:
|
||||||
|
|
||||||
|
- cluster.name - Cluster name identifier
|
||||||
|
- cluster.hostnamePrefix - Prefix for node hostnames
|
||||||
|
|
||||||
|
###### Node Configuration:
|
||||||
|
|
||||||
|
- cluster.nodes.talos.version - Talos Linux version (e.g., "v1.11.5")
|
||||||
|
- cluster.nodes.talos.schematicId - Talos Image Factory schematic ID
|
||||||
|
- cluster.nodes.control.vip - Virtual IP for control plane
|
||||||
|
- cluster.nodes.active.* - Individual node configurations with:
|
||||||
|
- role - "controlplane" or "worker"
|
||||||
|
- interface - Network interface name
|
||||||
|
- disk - Disk device path
|
||||||
|
- currentIp - Current IP address
|
||||||
|
- targetIp - Target IP address
|
||||||
|
- configured - Configuration status
|
||||||
|
- applied - Applied status
|
||||||
|
- maintenance - Maintenance mode
|
||||||
|
- schematicId - Node-specific schematic ID
|
||||||
|
- version - Node-specific Talos version
|
||||||
|
|
||||||
|
###### MetalLB Service:
|
||||||
|
|
||||||
|
- cluster.ipAddressPool - IP range for MetalLB (e.g., "192.168.8.80-192.168.8.89")
|
||||||
|
- cluster.loadBalancerIp - Primary load balancer IP (e.g., "192.168.8.80")
|
||||||
|
|
||||||
|
###### Cert-Manager Service:
|
||||||
|
|
||||||
|
- cluster.certManager.cloudflare.domain - Cloudflare domain for DNS-01 challenge
|
||||||
|
- cluster.certManager.cloudflare.zoneID - Cloudflare zone ID
|
||||||
|
|
||||||
|
###### ExternalDNS Service:
|
||||||
|
|
||||||
|
- cluster.externalDns.ownerId - Unique identifier for this cluster's DNS records
|
||||||
|
|
||||||
|
###### Docker Registry Service:
|
||||||
|
|
||||||
|
- cluster.dockerRegistry.storage - Storage size for registry (e.g., "10Gi")
|
||||||
|
|
||||||
|
##### apps (Application configurations)
|
||||||
|
|
||||||
|
Each app added to the cluster gets its own section under apps.<app-name> with app-specific configuration from the app's manifest. Common patterns include:
|
||||||
|
|
||||||
|
Standard app fields:
|
||||||
|
- apps.<name>.namespace - Kubernetes namespace
|
||||||
|
- apps.<name>.domain - App domain (e.g., "ghost.cloud2.payne.io")
|
||||||
|
- apps.<name>.externalDnsDomain - Domain for external DNS
|
||||||
|
- apps.<name>.tlsSecretName - TLS certificate secret name
|
||||||
|
- apps.<name>.image - Container image
|
||||||
|
- apps.<name>.port - Service port
|
||||||
|
- apps.<name>.storage - Persistent volume size
|
||||||
|
- apps.<name>.timezone - Timezone setting
|
||||||
|
|
||||||
|
Database-dependent apps:
|
||||||
|
- apps.<name>.dbHost / dbHostname - Database hostname
|
||||||
|
- apps.<name>.dbPort - Database port
|
||||||
|
- apps.<name>.dbName - Database name
|
||||||
|
- apps.<name>.dbUser / dbUsername - Database user
|
||||||
|
|
||||||
|
SMTP-enabled apps:
|
||||||
|
- apps.<name>.smtp.host - SMTP server
|
||||||
|
- apps.<name>.smtp.port - SMTP port
|
||||||
|
- apps.<name>.smtp.user - SMTP username
|
||||||
|
- apps.<name>.smtp.from - From address
|
||||||
|
- apps.<name>.smtp.tls - TLS enabled
|
||||||
|
- apps.<name>.smtp.startTls - STARTTLS enabled
|
||||||
|
|
||||||
|
Configuration Flow
|
||||||
|
|
||||||
|
1. Initial Setup: operator.email, basic cloud.* settings
|
||||||
|
2. Cluster Bootstrap: cluster.name, cluster.nodes.* settings
|
||||||
|
3. Infrastructure Services: Each service prompts for its serviceConfig from its manifest
|
||||||
|
- MetalLB → cluster.ipAddressPool, cluster.loadBalancerIp
|
||||||
|
- Cert-Manager → cluster.certManager.*
|
||||||
|
- ExternalDNS → cluster.externalDns.ownerId
|
||||||
|
- NFS → cloud.nfs.*
|
||||||
|
- Docker Registry → cloud.dockerRegistryHost, cluster.dockerRegistry.storage
|
||||||
|
- SMTP → cloud.smtp.*
|
||||||
|
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest
|
||||||
|
|
||||||
|
#### Manifest App Reference Resolution:
|
||||||
|
|
||||||
When you use `{{ .apps.<ref>.* }}` in templates:
|
When you use `{{ .apps.<ref>.* }}` in templates:
|
||||||
1. System checks if `<ref>` matches any dependency's `alias` field
|
1. System checks if `<ref>` matches any dependency's `alias` field
|
||||||
2. If no alias match, checks if `<ref>` matches any dependency's `name` field
|
2. If no alias match, checks if `<ref>` matches any dependency's `name` field
|
||||||
|
|||||||
94
CLAUDE.md
94
CLAUDE.md
@@ -1,15 +1,97 @@
|
|||||||
- @README.md
|
- @README.md
|
||||||
- @ADDING-APPS.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
|
## 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.
|
- 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 traefik for ingress.
|
||||||
- Use postgres for database if supported.
|
- Use postgres for database if supported.
|
||||||
- Keep config key naming (including nesting) consistent with other apps.
|
- 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.
|
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
|
## 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.
|
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
|
||||||
@@ -19,11 +19,11 @@ spec:
|
|||||||
automountServiceAccountToken: false
|
automountServiceAccountToken: false
|
||||||
serviceAccountName: discourse
|
serviceAccountName: discourse
|
||||||
securityContext:
|
securityContext:
|
||||||
fsGroup: 0
|
fsGroup: 1000
|
||||||
fsGroupChangePolicy: Always
|
fsGroupChangePolicy: Always
|
||||||
containers:
|
initContainers:
|
||||||
- name: discourse
|
- name: discourse-migrate
|
||||||
image: tiredofit/discourse:latest
|
image: discourse/discourse:3.5.3
|
||||||
imagePullPolicy: "IfNotPresent"
|
imagePullPolicy: "IfNotPresent"
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
@@ -32,10 +32,76 @@ spec:
|
|||||||
- ALL
|
- ALL
|
||||||
add:
|
add:
|
||||||
- CHOWN
|
- CHOWN
|
||||||
- DAC_OVERRIDE
|
|
||||||
- FOWNER
|
- FOWNER
|
||||||
- SETGID
|
- SETGID
|
||||||
- SETUID
|
- 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: {{ .dbHostname }}
|
||||||
|
- name: DISCOURSE_DB_PORT
|
||||||
|
value: "{{ .dbPort }}"
|
||||||
|
- name: DISCOURSE_DB_NAME
|
||||||
|
value: {{ .dbName }}
|
||||||
|
- name: DISCOURSE_DB_USERNAME
|
||||||
|
value: {{ .dbUsername }}
|
||||||
|
- name: DISCOURSE_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: discourse-secrets
|
||||||
|
key: dbPassword
|
||||||
|
- name: DISCOURSE_REDIS_HOST
|
||||||
|
value: {{ .redisHostname }}
|
||||||
|
- 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:3.5.3
|
||||||
|
imagePullPolicy: "IfNotPresent"
|
||||||
|
command:
|
||||||
|
- /sbin/boot
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- CHOWN
|
||||||
|
- FOWNER
|
||||||
|
- SETGID
|
||||||
|
- SETUID
|
||||||
|
- DAC_OVERRIDE
|
||||||
privileged: false
|
privileged: false
|
||||||
readOnlyRootFilesystem: false
|
readOnlyRootFilesystem: false
|
||||||
runAsNonRoot: false
|
runAsNonRoot: false
|
||||||
@@ -43,78 +109,70 @@ spec:
|
|||||||
seccompProfile:
|
seccompProfile:
|
||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
env:
|
env:
|
||||||
# Admin configuration
|
- name: RAILS_ENV
|
||||||
- name: ADMIN_USER
|
value: "production"
|
||||||
value: {{ .adminUsername }}
|
# Discourse database configuration
|
||||||
- name: ADMIN_EMAIL
|
- name: DISCOURSE_DB_HOST
|
||||||
value: {{ .adminEmail }}
|
|
||||||
- name: ADMIN_PASS
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: discourse-secrets
|
|
||||||
key: adminPassword
|
|
||||||
# Site configuration
|
|
||||||
- name: SITE_TITLE
|
|
||||||
value: {{ .siteName }}
|
|
||||||
- name: HOSTNAME
|
|
||||||
value: {{ .domain }}
|
|
||||||
# Database configuration
|
|
||||||
- name: DB_HOST
|
|
||||||
value: {{ .dbHostname }}
|
value: {{ .dbHostname }}
|
||||||
- name: DB_PORT
|
- name: DISCOURSE_DB_PORT
|
||||||
value: "{{ .dbPort }}"
|
value: "{{ .dbPort }}"
|
||||||
- name: DB_NAME
|
- name: DISCOURSE_DB_NAME
|
||||||
value: {{ .dbName }}
|
value: {{ .dbName }}
|
||||||
- name: DB_USER
|
- name: DISCOURSE_DB_USERNAME
|
||||||
value: {{ .dbUsername }}
|
value: {{ .dbUsername }}
|
||||||
- name: DB_PASS
|
- name: DISCOURSE_DB_PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: discourse-secrets
|
name: discourse-secrets
|
||||||
key: dbPassword
|
key: dbPassword
|
||||||
# Redis configuration
|
# Redis configuration
|
||||||
- name: REDIS_HOST
|
- name: DISCOURSE_REDIS_HOST
|
||||||
value: {{ .redisHostname }}
|
value: {{ .redisHostname }}
|
||||||
- name: REDIS_PASS
|
- name: DISCOURSE_REDIS_PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: discourse-secrets
|
name: discourse-secrets
|
||||||
key: redis.password
|
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
|
# SMTP configuration
|
||||||
- name: SMTP_ENABLED
|
- name: DISCOURSE_SMTP_ADDRESS
|
||||||
value: "{{ .smtp.enabled }}"
|
|
||||||
- name: SMTP_HOST
|
|
||||||
value: {{ .smtp.host }}
|
value: {{ .smtp.host }}
|
||||||
- name: SMTP_PORT
|
- name: DISCOURSE_SMTP_PORT
|
||||||
value: "{{ .smtp.port }}"
|
value: "{{ .smtp.port }}"
|
||||||
- name: SMTP_USER
|
- name: DISCOURSE_SMTP_USER_NAME
|
||||||
value: {{ .smtp.user }}
|
value: {{ .smtp.user }}
|
||||||
- name: SMTP_PASS
|
- name: DISCOURSE_SMTP_PASSWORD
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: discourse-secrets
|
name: discourse-secrets
|
||||||
key: smtpPassword
|
key: smtpPassword
|
||||||
- name: SMTP_TLS
|
- name: DISCOURSE_SMTP_ENABLE_START_TLS
|
||||||
value: "{{ .smtp.tls }}"
|
value: "{{ .smtp.startTls }}"
|
||||||
# Container timezone
|
|
||||||
- name: TZ
|
|
||||||
value: {{ .timezone }}
|
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 3000
|
containerPort: 80
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /srv/status
|
||||||
port: http
|
port: http
|
||||||
initialDelaySeconds: 420
|
initialDelaySeconds: 500
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
timeoutSeconds: 10
|
timeoutSeconds: 10
|
||||||
successThreshold: 1
|
successThreshold: 1
|
||||||
failureThreshold: 6
|
failureThreshold: 6
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /srv/status
|
||||||
port: http
|
port: http
|
||||||
initialDelaySeconds: 360
|
initialDelaySeconds: 360
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
@@ -125,25 +183,122 @@ spec:
|
|||||||
limits:
|
limits:
|
||||||
cpu: 2000m
|
cpu: 2000m
|
||||||
ephemeral-storage: 10Gi
|
ephemeral-storage: 10Gi
|
||||||
memory: 4Gi
|
memory: 8Gi
|
||||||
requests:
|
requests:
|
||||||
cpu: 500m
|
cpu: 750m
|
||||||
ephemeral-storage: 50Mi
|
ephemeral-storage: 50Mi
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: discourse-logs
|
- name: discourse-data
|
||||||
mountPath: /data/logs
|
mountPath: /shared
|
||||||
- name: discourse-uploads
|
- name: sidekiq
|
||||||
mountPath: /data/uploads
|
image: discourse/discourse:3.5.3
|
||||||
- name: discourse-backups
|
imagePullPolicy: "IfNotPresent"
|
||||||
mountPath: /data/backups
|
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: {{ .dbHostname }}
|
||||||
|
- name: DISCOURSE_DB_PORT
|
||||||
|
value: "{{ .dbPort }}"
|
||||||
|
- name: DISCOURSE_DB_NAME
|
||||||
|
value: {{ .dbName }}
|
||||||
|
- name: DISCOURSE_DB_USERNAME
|
||||||
|
value: {{ .dbUsername }}
|
||||||
|
- name: DISCOURSE_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: discourse-secrets
|
||||||
|
key: dbPassword
|
||||||
|
# Redis configuration
|
||||||
|
- name: DISCOURSE_REDIS_HOST
|
||||||
|
value: {{ .redisHostname }}
|
||||||
|
- 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: 375m
|
||||||
|
ephemeral-storage: 50Mi
|
||||||
|
memory: 512Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: discourse-data
|
||||||
|
mountPath: /shared
|
||||||
volumes:
|
volumes:
|
||||||
- name: discourse-logs
|
- name: discourse-data
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: discourse-logs
|
claimName: discourse-data
|
||||||
- name: discourse-uploads
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: discourse-uploads
|
|
||||||
- name: discourse-backups
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: discourse-backups
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: discourse
|
name: discourse
|
||||||
|
is: discourse
|
||||||
description: Discourse is a modern, open-source discussion platform designed for online communities and forums.
|
description: Discourse is a modern, open-source discussion platform designed for online communities and forums.
|
||||||
version: 3.5.3
|
version: 3.5.3
|
||||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
|
||||||
|
|||||||
@@ -2,20 +2,7 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
name: discourse-logs
|
name: discourse-data
|
||||||
namespace: discourse
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 2Gi
|
|
||||||
storageClassName: longhorn
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: discourse-uploads
|
|
||||||
namespace: discourse
|
namespace: discourse
|
||||||
spec:
|
spec:
|
||||||
accessModes:
|
accessModes:
|
||||||
@@ -24,16 +11,3 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
storage: {{ .storage }}
|
storage: {{ .storage }}
|
||||||
storageClassName: longhorn
|
storageClassName: longhorn
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
name: discourse-backups
|
|
||||||
namespace: discourse
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 5Gi
|
|
||||||
storageClassName: longhorn
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: example-admin
|
name: example-admin
|
||||||
|
is: example
|
||||||
install: true
|
install: true
|
||||||
description: An example application that is deployed with internal-only access.
|
description: An example application that is deployed with internal-only access.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: example-app
|
name: example-app
|
||||||
|
is: example
|
||||||
install: true
|
install: true
|
||||||
description: An example application that is deployed with public access.
|
description: An example application that is deployed with public access.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: ghost
|
name: ghost
|
||||||
|
is: ghost
|
||||||
description: Ghost is a powerful app for new-media creators to publish, share, and
|
description: Ghost is a powerful app for new-media creators to publish, share, and
|
||||||
grow a business around their content.
|
grow a business around their content.
|
||||||
version: 5.118.1
|
version: 5.118.1
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: gitea
|
name: gitea
|
||||||
|
is: gitea
|
||||||
description: Gitea is a painless self-hosted Git service written in Go
|
description: Gitea is a painless self-hosted Git service written in Go
|
||||||
version: 1.24.3
|
version: 1.24.3
|
||||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: immich
|
name: immich
|
||||||
|
is: immich
|
||||||
install: true
|
install: true
|
||||||
description: Immich is a self-hosted photo and video backup solution that allows you
|
description: Immich is a self-hosted photo and video backup solution that allows you
|
||||||
to store, manage, and share your media files securely.
|
to store, manage, and share your media files securely.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: keila
|
name: keila
|
||||||
|
is: keila
|
||||||
description: Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control.
|
description: Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control.
|
||||||
version: 0.17.1
|
version: 0.17.1
|
||||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
|
||||||
|
|||||||
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 }}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
name: listmonk
|
name: listmonk
|
||||||
|
is: listmonk
|
||||||
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager.
|
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager.
|
||||||
It is fast, feature-rich, and packed into a single binary.
|
It is fast, feature-rich, and packed into a single binary.
|
||||||
version: 5.0.3
|
version: 5.0.3
|
||||||
icon: https://listmonk.app/static/images/logo.svg
|
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/listmonk.svg
|
||||||
requires:
|
requires:
|
||||||
- name: postgres
|
- name: postgres
|
||||||
defaultConfig:
|
defaultConfig:
|
||||||
|
|||||||
@@ -8,57 +8,48 @@ spec:
|
|||||||
restartPolicy: OnFailure
|
restartPolicy: OnFailure
|
||||||
containers:
|
containers:
|
||||||
- name: db-init
|
- name: db-init
|
||||||
image: postgres:15-alpine
|
image: {{ .image }}
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Initializing Loomio database..."
|
||||||
|
# Patch schema.rb to use IF NOT EXISTS for pghero schema
|
||||||
|
sed -i 's/create_schema "pghero"/execute "CREATE SCHEMA IF NOT EXISTS pghero"/g' db/schema.rb
|
||||||
|
bundle exec rake db:schema:load db:seed
|
||||||
|
echo "Database initialization complete"
|
||||||
env:
|
env:
|
||||||
- name: PGHOST
|
- name: RAILS_ENV
|
||||||
value: "{{ .db.host }}"
|
value: production
|
||||||
- name: PGPORT
|
- name: DATABASE_URL
|
||||||
value: "{{ .db.port }}"
|
|
||||||
- name: PGUSER
|
|
||||||
value: postgres
|
|
||||||
- name: PGPASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: postgres-secrets
|
|
||||||
key: postgres.password
|
|
||||||
- name: LOOMIO_DB_NAME
|
|
||||||
value: "{{ .db.name }}"
|
|
||||||
- name: LOOMIO_DB_USER
|
|
||||||
value: "{{ .db.user }}"
|
|
||||||
- name: LOOMIO_DB_PASSWORD
|
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: loomio-secrets
|
name: loomio-secrets
|
||||||
key: dbPassword
|
key: dbUrl
|
||||||
command:
|
- name: REDIS_URL
|
||||||
- sh
|
value: {{ .redisUrl }}
|
||||||
- -c
|
- name: DEVISE_SECRET
|
||||||
- |
|
valueFrom:
|
||||||
echo "Creating database and user for Loomio..."
|
secretKeyRef:
|
||||||
|
name: loomio-secrets
|
||||||
# Check if database exists, create if not
|
key: deviseSecret
|
||||||
psql -tc "SELECT 1 FROM pg_database WHERE datname = '$LOOMIO_DB_NAME'" | grep -q 1 || \
|
- name: SECRET_COOKIE_TOKEN
|
||||||
psql -c "CREATE DATABASE \"$LOOMIO_DB_NAME\""
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
# Check if user exists, create or update password
|
name: loomio-secrets
|
||||||
psql -tc "SELECT 1 FROM pg_user WHERE usename = '$LOOMIO_DB_USER'" | grep -q 1 && \
|
key: secretCookieToken
|
||||||
psql -c "ALTER USER \"$LOOMIO_DB_USER\" WITH PASSWORD '$LOOMIO_DB_PASSWORD'" || \
|
|
||||||
psql -c "CREATE USER \"$LOOMIO_DB_USER\" WITH PASSWORD '$LOOMIO_DB_PASSWORD'"
|
|
||||||
|
|
||||||
# Grant all privileges
|
|
||||||
psql -c "GRANT ALL PRIVILEGES ON DATABASE \"$LOOMIO_DB_NAME\" TO \"$LOOMIO_DB_USER\""
|
|
||||||
|
|
||||||
# Connect to the database and grant schema permissions
|
|
||||||
psql -d "$LOOMIO_DB_NAME" -c "GRANT ALL ON SCHEMA public TO \"$LOOMIO_DB_USER\""
|
|
||||||
|
|
||||||
echo "Database initialization complete!"
|
|
||||||
securityContext:
|
securityContext:
|
||||||
runAsNonRoot: true
|
runAsNonRoot: false
|
||||||
runAsUser: 999 # postgres user
|
runAsUser: 0
|
||||||
runAsGroup: 999
|
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop: [ALL]
|
drop: [ALL]
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: false
|
||||||
seccompProfile:
|
seccompProfile:
|
||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
|
securityContext:
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ spec:
|
|||||||
value: "{{ .smtp.tls }}"
|
value: "{{ .smtp.tls }}"
|
||||||
- name: REPLY_HOSTNAME
|
- name: REPLY_HOSTNAME
|
||||||
value: {{ .smtp.from }}
|
value: {{ .smtp.from }}
|
||||||
|
- name: BUNDLE_APP_CONFIG
|
||||||
|
value: /loomio/tmp/.bundle
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: uploads
|
- name: uploads
|
||||||
mountPath: /loomio/public/system
|
mountPath: /loomio/public/system
|
||||||
@@ -73,6 +75,8 @@ spec:
|
|||||||
mountPath: /loomio/storage
|
mountPath: /loomio/storage
|
||||||
- name: tmp
|
- name: tmp
|
||||||
mountPath: /loomio/tmp
|
mountPath: /loomio/tmp
|
||||||
|
- name: log
|
||||||
|
mountPath: /loomio/log
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: 256Mi
|
memory: 256Mi
|
||||||
@@ -81,9 +85,8 @@ spec:
|
|||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
cpu: 500m
|
cpu: 500m
|
||||||
securityContext:
|
securityContext:
|
||||||
runAsNonRoot: true
|
runAsNonRoot: false
|
||||||
runAsUser: 1000
|
runAsUser: 0
|
||||||
runAsGroup: 1000
|
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop: [ALL]
|
drop: [ALL]
|
||||||
@@ -98,4 +101,6 @@ spec:
|
|||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: loomio-storage
|
claimName: loomio-storage
|
||||||
- name: tmp
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: log
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
@@ -15,6 +15,13 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- name: loomio
|
- name: loomio
|
||||||
image: {{ .image }}
|
image: {{ .image }}
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
bundle exec rake db:schema:load db:seed
|
||||||
|
bundle exec thrust puma -C config/puma.rb
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
name: http
|
name: http
|
||||||
@@ -73,10 +80,12 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: loomio-secrets
|
name: loomio-secrets
|
||||||
key: smtpPassword
|
key: smtpPassword
|
||||||
- name: SMTP_USE_SSL
|
|
||||||
value: "{{ .smtp.tls }}"
|
|
||||||
- name: REPLY_HOSTNAME
|
- name: REPLY_HOSTNAME
|
||||||
value: {{ .smtp.from }}
|
value: {{ .smtp.from }}
|
||||||
|
- name: CHANNELS_URI
|
||||||
|
value: wss://{{ .domain }}
|
||||||
|
- name: BUNDLE_APP_CONFIG
|
||||||
|
value: /loomio/tmp/.bundle
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: uploads
|
- name: uploads
|
||||||
mountPath: /loomio/public/system
|
mountPath: /loomio/public/system
|
||||||
@@ -84,6 +93,8 @@ spec:
|
|||||||
mountPath: /loomio/storage
|
mountPath: /loomio/storage
|
||||||
- name: tmp
|
- name: tmp
|
||||||
mountPath: /loomio/tmp
|
mountPath: /loomio/tmp
|
||||||
|
- name: log
|
||||||
|
mountPath: /loomio/log
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: 512Mi
|
memory: 512Mi
|
||||||
@@ -92,21 +103,18 @@ spec:
|
|||||||
memory: 2Gi
|
memory: 2Gi
|
||||||
cpu: 1000m
|
cpu: 1000m
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
tcpSocket:
|
||||||
path: /health
|
|
||||||
port: 3000
|
port: 3000
|
||||||
initialDelaySeconds: 60
|
initialDelaySeconds: 60
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
tcpSocket:
|
||||||
path: /health
|
|
||||||
port: 3000
|
port: 3000
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 30
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
securityContext:
|
securityContext:
|
||||||
runAsNonRoot: true
|
runAsNonRoot: false
|
||||||
runAsUser: 1000
|
runAsUser: 0
|
||||||
runAsGroup: 1000
|
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
capabilities:
|
capabilities:
|
||||||
drop: [ALL]
|
drop: [ALL]
|
||||||
@@ -121,4 +129,6 @@ spec:
|
|||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: loomio-storage
|
claimName: loomio-storage
|
||||||
- name: tmp
|
- name: tmp
|
||||||
|
emptyDir: {}
|
||||||
|
- name: log
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
name: loomio
|
name: loomio
|
||||||
|
is: loomio
|
||||||
description: Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together
|
description: Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together
|
||||||
version: 3.0.11
|
version: 3.0.11
|
||||||
icon: https://www.loomio.com/brand/logo_gold.svg
|
icon: https://www.loomio.com/brand/logo_gold.svg
|
||||||
@@ -9,8 +10,8 @@ requires:
|
|||||||
defaultConfig:
|
defaultConfig:
|
||||||
namespace: loomio
|
namespace: loomio
|
||||||
externalDnsDomain: "{{ .cloud.domain }}"
|
externalDnsDomain: "{{ .cloud.domain }}"
|
||||||
image: loomio/loomio:v3.0.11
|
image: loomio/loomio:latest
|
||||||
workerImage: loomio/loomio:v3.0.11
|
workerImage: loomio/loomio:latest
|
||||||
appName: Loomio
|
appName: Loomio
|
||||||
domain: "loomio.{{ .cloud.domain }}"
|
domain: "loomio.{{ .cloud.domain }}"
|
||||||
tlsSecretName: wildcard-wild-cloud-tls
|
tlsSecretName: wildcard-wild-cloud-tls
|
||||||
@@ -51,5 +52,6 @@ defaultSecrets:
|
|||||||
- key: secretCookieToken
|
- key: secretCookieToken
|
||||||
default: "{{ random.AlphaNum 32 }}"
|
default: "{{ random.AlphaNum 32 }}"
|
||||||
- key: smtpPassword
|
- key: smtpPassword
|
||||||
|
default: "{{ .secrets.smtp.password }}"
|
||||||
requiredSecrets:
|
requiredSecrets:
|
||||||
- postgres.password
|
- postgres.password
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ metadata:
|
|||||||
name: loomio-storage
|
name: loomio-storage
|
||||||
spec:
|
spec:
|
||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteMany
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: {{ .storage.files }}
|
storage: {{ .storage.files }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ metadata:
|
|||||||
name: loomio-uploads
|
name: loomio-uploads
|
||||||
spec:
|
spec:
|
||||||
accessModes:
|
accessModes:
|
||||||
- ReadWriteOnce
|
- ReadWriteMany
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: {{ .storage.uploads }}
|
storage: {{ .storage.uploads }}
|
||||||
|
|||||||
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
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
name: memcached
|
name: memcached
|
||||||
|
is: memcached
|
||||||
description: Memcached is an in-memory key-value store for small chunks of arbitrary
|
description: Memcached is an in-memory key-value store for small chunks of arbitrary
|
||||||
data, commonly used as a cache layer.
|
data, commonly used as a cache layer.
|
||||||
version: 1.6.32
|
version: 1.6.32
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: mysql
|
name: mysql
|
||||||
|
is: mysql
|
||||||
description: MySQL is an open-source relational database management system
|
description: MySQL is an open-source relational database management system
|
||||||
version: 9.1.0
|
version: 9.1.0
|
||||||
icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png
|
icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: openWebui
|
name: open-webui
|
||||||
|
is: open-webui
|
||||||
description: Open WebUI is a comprehensive, open-source web interface for AI models.
|
description: Open WebUI is a comprehensive, open-source web interface for AI models.
|
||||||
Features a user-friendly design, supports various LLM runners, and operates entirely
|
Features a user-friendly design, supports various LLM runners, and operates entirely
|
||||||
offline. Perfect for creating a ChatGPT-like experience with local or hosted models.
|
offline. Perfect for creating a ChatGPT-like experience with local or hosted models.
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
DATABASE_HOST: "{{ .dbHostname }}"
|
DATABASE_HOST: "{{ .dbHostname }}"
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
|
DATABASE_NAME: "{{ .dbName }}"
|
||||||
|
DATABASE_USERNAME: "{{ .dbUsername }}"
|
||||||
DATABASE_URL: "postgresql://{{ .dbUsername }}@{{ .dbHostname }}:5432/{{ .dbName }}"
|
DATABASE_URL: "postgresql://{{ .dbUsername }}@{{ .dbHostname }}:5432/{{ .dbName }}"
|
||||||
OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "{{ .adminPasswordReset }}"
|
OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "{{ .adminPasswordReset }}"
|
||||||
OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .adminUserName }}"
|
OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .adminUserName }}"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ apiVersion: networking.k8s.io/v1
|
|||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: openproject
|
name: openproject
|
||||||
|
annotations:
|
||||||
|
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||||
|
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: openproject
|
name: openproject
|
||||||
|
is: openproject
|
||||||
description: OpenProject is an open-source project management software that provides
|
description: OpenProject is an open-source project management software that provides
|
||||||
comprehensive features for project planning, tracking, and collaboration.
|
comprehensive features for project planning, tracking, and collaboration.
|
||||||
version: 16.1.1
|
version: 16.1.1
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ spec:
|
|||||||
command: [
|
command: [
|
||||||
'sh',
|
'sh',
|
||||||
'-c',
|
'-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:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ spec:
|
|||||||
command: [
|
command: [
|
||||||
'sh',
|
'sh',
|
||||||
'-c',
|
'-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:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: postgres
|
name: postgres
|
||||||
|
is: postgres
|
||||||
install: true
|
install: true
|
||||||
description: PostgreSQL is a powerful, open source object-relational database system.
|
description: PostgreSQL is a powerful, open source object-relational database system.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: redis
|
name: redis
|
||||||
|
is: redis
|
||||||
install: true
|
install: true
|
||||||
description: Redis is an open source, in-memory data structure store, used as a database, cache and message broker.
|
description: Redis is an open source, in-memory data structure store, used as a database, cache and message broker.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
name: vllm
|
name: vllm
|
||||||
|
is: vllm
|
||||||
description: vLLM is a fast and easy-to-use library for LLM inference and serving
|
description: vLLM is a fast and easy-to-use library for LLM inference and serving
|
||||||
with OpenAI-compatible API
|
with OpenAI-compatible API
|
||||||
version: 0.5.4
|
version: 0.5.4
|
||||||
|
|||||||
Reference in New Issue
Block a user