Compare commits
17 Commits
351f58b80d
...
e2aa16e679
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2aa16e679 | ||
|
|
37dafcd24d | ||
|
|
b6d88e79ac | ||
|
|
963929475c | ||
|
|
39095e76d2 | ||
|
|
d756126a34 | ||
|
|
f17fea6910 | ||
|
|
0ba33a315d | ||
|
|
12706ac331 | ||
|
|
a159c90816 | ||
|
|
32498c73b8 | ||
|
|
c93198d13a | ||
|
|
434769ac7a | ||
|
|
d1304a2630 | ||
|
|
8818d822cf | ||
|
|
1b78abbdc4 | ||
|
|
a4db0d0f6a |
289
ADDING-APPS.md
289
ADDING-APPS.md
@@ -14,7 +14,7 @@ Each app directory must contain:
|
||||
2. **`kustomization.yaml`** - Kustomize configuration with Wild Cloud labels
|
||||
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.
|
||||
|
||||
@@ -22,12 +22,14 @@ This is the contents of an example `manifest.yaml` file for an app named "immich
|
||||
|
||||
```yaml
|
||||
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.
|
||||
version: 1.0.0
|
||||
icon: https://immich.app/assets/images/logo.png
|
||||
requires:
|
||||
- name: redis
|
||||
- name: postgres
|
||||
- name: pg
|
||||
alias: db # Use a different reference name in templates
|
||||
- name: redis # 'alias' and 'installedAs' default to 'name' value
|
||||
defaultConfig:
|
||||
serverImage: ghcr.io/immich-app/immich-server:release
|
||||
mlImage: ghcr.io/immich-app/immich-machine-learning:release
|
||||
@@ -36,28 +38,195 @@ defaultConfig:
|
||||
mlPort: 3003
|
||||
storage: 250Gi
|
||||
cacheStorage: 10Gi
|
||||
redisHostname: redis.redis.svc.cluster.local
|
||||
dbHostname: postgres.postgres.svc.cluster.local
|
||||
dbUsername: immich
|
||||
redisHostname: "{{ .apps.redis.host }}" # Can reference 'requires' app configurations
|
||||
dbHostname: "{{ .apps.pg.host }}"
|
||||
db: # Configuration can be nested
|
||||
name: immich
|
||||
user: immich
|
||||
host: "{{ .apps.pg.host }}"
|
||||
port: "{{ .apps.pg.port }}"
|
||||
domain: immich.{{ .cloud.domain }}
|
||||
defaultSecrets:
|
||||
- key: password # Random value will be generated if empty
|
||||
- key: dbUrl
|
||||
default: "postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?pool=30" # Can reference secrets and config as long as they have been defined before this line. Reference config with {{ .app.? }} and secrets with {{ .secrets.? }}
|
||||
requiredSecrets:
|
||||
- apps.immich.dbPassword
|
||||
- apps.postgres.password
|
||||
- db.password # References postgres app via 'db' alias
|
||||
- redis.auth # References redis app via 'redis' name (no alias)
|
||||
```
|
||||
|
||||
#### Manifest Fields
|
||||
### Manifest Fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `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 |
|
||||
| `version` | Yes | App version (follow upstream versioning) |
|
||||
| `icon` | No | URL to app icon for UI display |
|
||||
| `requires` | No | List of dependency apps (e.g., `postgres`, `redis`) |
|
||||
| `requires` | No | List of dependency apps with optional aliases |
|
||||
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
|
||||
| `requiredSecrets` | No | List of secrets in dotted-path format (e.g., `apps.appname.dbPassword`) |
|
||||
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
||||
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
||||
|
||||
**Important:** All configuration keys referenced in templates (via `{{ .apps.appname.key }}`) must be defined in `defaultConfig` or be standard Wild Cloud variables.
|
||||
### Dependency Configuration
|
||||
|
||||
- Each dependency in `requires` can have:
|
||||
- `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`)
|
||||
|
||||
### Manifest Template Variables (configuration and secrets)
|
||||
|
||||
#### Manifest Template Variable Sources
|
||||
|
||||
1. Standard Wild Cloud variables: `{{ .cloud.* }}`, `{{ .cluster.* }}`, `{{ .operator.* }}`
|
||||
2. App-specific variables: `{{ .app.* }}` - resolved from current app's config
|
||||
3. Dependency variables: `{{ .apps.<ref>.* }}` - resolved using app reference mapping
|
||||
4. App-specific secrets (in 'defaultSecrets' ONLY): `{{ secrets.* }}`
|
||||
|
||||
#### 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:
|
||||
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
|
||||
3. Uses the `installedAs` value (automatically added when the app is added) to find actual app configuration in `config.yaml`
|
||||
|
||||
All manifest template variables must be defined in one of these locations.
|
||||
|
||||
**Important:** In the rest of the app templates, ALL configuration keys referenced in templates (via `{{ .key }}`) must be defined in `defaultConfig`. Only the app config is available to app templates.
|
||||
|
||||
### Kustomization (`kustomization.yaml`)
|
||||
|
||||
@@ -111,21 +280,7 @@ This means individual resources can use simple, component-specific selectors lik
|
||||
|
||||
### Gomplate Templating
|
||||
|
||||
Resource files in this repository are **templates** that get compiled when users add apps via the web app, CLI, or API. Use gomplate syntax to reference configuration:
|
||||
|
||||
```yaml
|
||||
# Common template variables
|
||||
domain: {{ .cloud.domain }} # Operator's domain
|
||||
email: {{ .operator.email }} # Operator's email
|
||||
image: {{ .apps.myapp.serverImage }} # App-specific config
|
||||
dbHost: {{ .apps.myapp.dbHostname }} # App-specific config
|
||||
```
|
||||
|
||||
**Template variable sources:**
|
||||
1. Standard Wild Cloud variables (`{{ .cloud.* }}`, `{{ .operator.* }}`)
|
||||
2. App-specific variables defined in your manifest's `defaultConfig`
|
||||
|
||||
All template variables must be defined in one of these locations. The compiled files are placed in the instance's directory as standard Kubernetes manifests.
|
||||
Resource files in this repository are **templates** that get compiled when users add apps via the web app, CLI, or API. Only variables defined in the manifest file's 'defaultConfig' section are available to the resource templates. Use gomplate syntax to reference configuration:
|
||||
|
||||
### External DNS
|
||||
|
||||
@@ -133,12 +288,47 @@ Ingress resources should include external-dns annotations for automatic DNS mana
|
||||
|
||||
```yaml
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
|
||||
external-dns.alpha.kubernetes.io/target: {{ .domain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
```
|
||||
|
||||
Note: 'domain' must be defined in the app manifest's 'defaultConfig' section.
|
||||
|
||||
This creates a CNAME from the app subdomain to the cluster domain (e.g., `myapp.cloud.example.com` → `cloud.example.com`).
|
||||
|
||||
## App Dependencies and Reference Mapping
|
||||
|
||||
### How Dependency References Work
|
||||
|
||||
When an app depends on other apps, the reference system allows flexibility in naming while maintaining clear relationships:
|
||||
|
||||
1. **Define dependencies** in your manifest with optional aliases:
|
||||
```yaml
|
||||
requires:
|
||||
- name: postgres # Actual app to depend on
|
||||
alias: db # Optional: how to reference it in templates
|
||||
- name: redis # No alias means use 'redis' as reference
|
||||
```
|
||||
|
||||
2. **At installation time**, the system:
|
||||
- Prompts user to map dependencies to actual installed apps
|
||||
- Sets `installedAs` field in the local app manifest to track the mapping
|
||||
- Example: User might have `postgres-primary` installed, mapped to the `db` dependency
|
||||
|
||||
### Example: Multiple Database Instances
|
||||
|
||||
If a user has multiple PostgreSQL instances:
|
||||
```yaml
|
||||
# User's config.yaml
|
||||
apps:
|
||||
postgres-primary:
|
||||
hostname: primary.postgres.svc.cluster.local
|
||||
postgres-analytics:
|
||||
hostname: analytics.postgres.svc.cluster.local
|
||||
```
|
||||
|
||||
When adding an app that requires postgres, they can choose which instance to use, and the system tracks this in the manifest's `installedAs` field.
|
||||
|
||||
## Database Patterns
|
||||
|
||||
### Database Initialization Jobs
|
||||
@@ -177,7 +367,7 @@ When apps need database URLs with embedded credentials, **always use a dedicated
|
||||
key: apps.myapp.dbUrl
|
||||
```
|
||||
|
||||
Add `apps.myapp.dbUrl` to your manifest's `requiredSecrets`, and the system will generate the complete URL with embedded credentials automatically when the app is added.
|
||||
Add `apps.myapp.dbUrl` to your manifest's `defaultSecrets`, and the system will generate the complete URL with embedded credentials automatically when the app is added.
|
||||
|
||||
## Security Requirements
|
||||
|
||||
@@ -211,13 +401,16 @@ spec:
|
||||
|
||||
### Secrets Management
|
||||
|
||||
Secrets use a **full dotted-path naming convention** to prevent naming conflicts:
|
||||
Secrets are managed through two mechanisms: default secrets for the app itself and required secrets from dependencies.
|
||||
|
||||
**In manifest:**
|
||||
```yaml
|
||||
defaultSecrets:
|
||||
key: dbPassword # This app's database password
|
||||
key: apiKey # This app's API key
|
||||
requiredSecrets:
|
||||
- apps.myapp.dbPassword
|
||||
- apps.postgres.password
|
||||
- db.password # Password from postgres dependency (aliased as 'db')
|
||||
- redis.auth # Auth from redis dependency
|
||||
```
|
||||
|
||||
**In resources:**
|
||||
@@ -227,14 +420,26 @@ env:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: myapp-secrets
|
||||
key: apps.myapp.dbPassword # Full dotted path, not just "dbPassword"
|
||||
key: dbPassword # Points to the default secret
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: myapp-secrets
|
||||
key: db.password # Points to the required secret
|
||||
```
|
||||
|
||||
**Secret workflow:**
|
||||
1. List secrets in manifest's `requiredSecrets`
|
||||
2. When adding an app, the system generates random values in the instance's `secrets.yaml`
|
||||
3. When deploying, the system creates a Kubernetes Secret named `<app-name>-secrets`
|
||||
4. Resources reference secrets using full dotted paths
|
||||
1. Define app's own secrets in `defaultSecrets` (key, default mappings)
|
||||
2. Reference dependency secrets in `requiredSecrets` (list)
|
||||
3. When adding an app, the system:
|
||||
- Generates random values for empty `defaultSecrets`
|
||||
- Copies referenced secrets from dependencies
|
||||
- Stores all in the instance's `secrets.yaml`
|
||||
4. When deploying, creates a Kubernetes Secret named `<app-name>-secrets` containing:
|
||||
- All `defaultSecrets` with key format: `<key>`
|
||||
- All `requiredSecrets` with key format: `<app-ref>.<key>`
|
||||
|
||||
**Key collision handling:** If the same key exists in both `defaultSecrets` and `requiredSecrets`, the `requiredSecrets` value takes precedence. Authors should ensure their local secrets don't collide with their required secrets.
|
||||
|
||||
**Important:** Never commit `secrets.yaml` to Git. Templates should only reference secrets, never contain actual secret values.
|
||||
|
||||
@@ -308,9 +513,11 @@ Before submitting a new or modified app, verify:
|
||||
- [ ] **Manifest**
|
||||
- [ ] `name` matches directory name
|
||||
- [ ] All required fields present (`name`, `description`, `version`, `defaultConfig`)
|
||||
- [ ] All template variables defined in `defaultConfig` or are standard Wild Cloud variables
|
||||
- [ ] Secrets use dotted-path format (e.g., `apps.appname.secretname`)
|
||||
- [ ] Dependencies listed in `requires` (if any)
|
||||
- [ ] All template variables defined in `defaultConfig`
|
||||
- [ ] `defaultSecrets` uses maps with 'key' and 'default' attributes
|
||||
- [ ] `requiredSecrets` references use `<app-ref>.<key>` format
|
||||
- [ ] Dependencies listed in `requires` with optional `alias` fields
|
||||
- [ ] Manifest template references match dependency aliases or names
|
||||
|
||||
- [ ] **Kustomization**
|
||||
- [ ] Includes standard Wild Cloud labels with `includeSelectors: true`
|
||||
@@ -318,8 +525,6 @@ Before submitting a new or modified app, verify:
|
||||
- [ ] All resource files listed under `resources:`
|
||||
|
||||
- [ ] **Resources**
|
||||
- [ ] All hardcoded values replaced with gomplate variables
|
||||
- [ ] Secrets reference full dotted paths
|
||||
- [ ] Security contexts on all pods (both pod-level and container-level)
|
||||
- [ ] Simple component labels, no Helm-style labels
|
||||
- [ ] Ingresses include external-dns annotations
|
||||
|
||||
216
CLAUDE.md
216
CLAUDE.md
@@ -1,171 +1,97 @@
|
||||
# CLAUDE.md
|
||||
- @README.md
|
||||
- @ADDING-APPS.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
## Finding good sources of documentation for adding a new app to the Wild Cloud Directory
|
||||
|
||||
## Overview
|
||||
- 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
|
||||
|
||||
This repository contains the Wild Cloud apps directory - a collection of Kubernetes applications packaged as Kustomize configurations. Each app is a self-contained directory with standardized manifests that can be deployed to Wild Cloud clusters using Wild Cloud CLI tools.
|
||||
These sources will oftentimes not use the latest version. Check to make sure you are adding the latest app version.
|
||||
|
||||
## Repository Architecture
|
||||
- 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 Structure
|
||||
## App package development lifecycle
|
||||
|
||||
Each app follows a strict structure:
|
||||
- **`manifest.yaml`** - App metadata, dependencies, default configuration, and secret requirements
|
||||
- **`kustomization.yaml`** - Kustomize configuration with standard Wild Cloud labels
|
||||
- **Resource files** - Kubernetes objects (deployments, services, ingresses, PVCs, jobs, etc.)
|
||||
- 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.
|
||||
|
||||
### Templating System
|
||||
## App Icons
|
||||
|
||||
Configuration files use **gomplate templating** to reference operator configuration:
|
||||
- Use `{{ .cloud.domain }}` for the operator's domain
|
||||
- Use `{{ .apps.appname.configKey }}` for app-specific configuration
|
||||
- Use `{{ .operator.email }}` for operator email
|
||||
- All template variables must be defined in either the app's `manifest.yaml` under `defaultConfig` or be standard Wild Cloud operator variables
|
||||
Icon Search Process
|
||||
|
||||
Templates are compiled when users add apps to their Wild Cloud instance via the web app, CLI, or API.
|
||||
1. Primary Source: Dashboard Icons (homarr-labs)
|
||||
|
||||
### Label Strategy
|
||||
- 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
|
||||
|
||||
Wild Cloud uses a consistent labeling approach powered by Kustomize's `includeSelectors: true` feature:
|
||||
2. Fallback: Vector Logo Zone
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: appname # App name (matches directory)
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
```
|
||||
- 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
|
||||
|
||||
This automatically applies labels to all resources AND their selectors. Individual resources can use simple component-specific selectors like `component: web` or `component: worker`, and Kustomize will expand them to include the standard Wild Cloud labels.
|
||||
3. Official Sources
|
||||
|
||||
**Important:** Do NOT use Helm-style labels (`app.kubernetes.io/name`, `app.kubernetes.io/instance`). Use simple component labels instead.
|
||||
- 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
|
||||
|
||||
## Working with Apps
|
||||
4. Community CDNs
|
||||
|
||||
### Creating/Modifying Apps
|
||||
- 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
|
||||
|
||||
1. **Manifest fields:**
|
||||
- `name` - Must match directory name
|
||||
- `description` - Brief app description
|
||||
- `version` - App version (follow upstream versioning)
|
||||
- `icon` - URL to app icon
|
||||
- `requires` - List of dependency apps (e.g., `postgres`, `redis`, `memcached`)
|
||||
- `defaultConfig` - Default configuration values (will be added to operator's `config.yaml`)
|
||||
- `requiredSecrets` - List of secrets in dotted path format (e.g., `apps.appname.dbPassword`)
|
||||
Validation Process
|
||||
|
||||
2. **Kustomization requirements:**
|
||||
- Must include standard Wild Cloud labels with `includeSelectors: true`
|
||||
- Namespace must match app name
|
||||
- List all resource files under `resources:`
|
||||
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
|
||||
|
||||
3. **Security contexts:**
|
||||
All pods must comply with Pod Security Standards:
|
||||
```yaml
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999 # Use appropriate non-root UID
|
||||
runAsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false # Set to true when possible
|
||||
```
|
||||
Icon Preferences
|
||||
|
||||
### Secrets Management
|
||||
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
|
||||
|
||||
Secrets use **full dotted paths** as keys:
|
||||
```yaml
|
||||
env:
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: appname-secrets
|
||||
key: apps.appname.dbPassword # Full path, not just "dbPassword"
|
||||
```
|
||||
Search Strategy
|
||||
|
||||
**Database URL secrets:** When apps need database URLs with embedded credentials, always use a dedicated `dbUrl` secret in `requiredSecrets`. Do NOT try to construct URLs with env var substitution in templates - Kustomize cannot process runtime environment variables.
|
||||
### 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
|
||||
|
||||
### Database Initialization Jobs
|
||||
This systematic approach ensures consistent, reliable, and high-quality icons across all Wild Cloud apps.
|
||||
|
||||
Apps requiring PostgreSQL/MySQL databases should include a `db-init-job.yaml`:
|
||||
- Creates the app database if it doesn't exist
|
||||
- Creates/updates the app user with proper password
|
||||
- Grants appropriate permissions
|
||||
- For PostgreSQL jobs: use `runAsUser: 999` (postgres user)
|
||||
- Include any required database extensions (e.g., Immich requires `vector`, `cube`, `earthdistance`)
|
||||
## IMPORTANT
|
||||
|
||||
Examples: [immich/db-init-job.yaml](immich/db-init-job.yaml), [gitea/db-init-job.yaml](gitea/db-init-job.yaml)
|
||||
- NEVER under any circumstances work on any Wild Cloud instance other than `test-cloud`
|
||||
- `secrets.yaml` is NOT checked in and any values unrelated to your current task should be preserved
|
||||
- When adding a new app to the directory, check to make sure you are adding the latest app version.
|
||||
- set `namespace` in default config and refer to it using `{{ .namespace }}` in the `namespace.yaml` definition file and the `kustomization.yaml` file.
|
||||
- If the app requires a specific platform (amd64, arm64, etc.), make sure it is called out in the manifest and the k8s tags are set
|
||||
- Use traefik for ingress.
|
||||
- Use postgres for database if supported.
|
||||
- Keep config key naming (including nesting) consistent with other apps.
|
||||
|
||||
### External DNS Configuration
|
||||
|
||||
Ingress resources should include external-dns annotations:
|
||||
```yaml
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
```
|
||||
|
||||
This creates CNAME records pointing app subdomains to the main cluster domain.
|
||||
|
||||
### Converting Helm Charts
|
||||
|
||||
Wild Cloud prefers Kustomize over Helm for transparency and Git-friendliness. To convert a Helm chart:
|
||||
|
||||
```bash
|
||||
helm fetch --untar --untardir charts repo/chart-name
|
||||
helm template --output-dir base --namespace namespace --values values.yaml release-name charts/chart-name
|
||||
cd base/chart-name
|
||||
kustomize create --autodetect
|
||||
```
|
||||
|
||||
Then:
|
||||
1. Add `manifest.yaml`
|
||||
2. Replace hardcoded values with gomplate variables
|
||||
3. Update secrets to use Wild Cloud's dotted-path approach
|
||||
4. Replace Helm labels with simple component labels
|
||||
5. Add standard Wild Cloud labels to `kustomization.yaml`
|
||||
6. Add security contexts to all pods
|
||||
|
||||
## Managing Wild Cloud Apps
|
||||
|
||||
Users can manage apps through:
|
||||
|
||||
1. **Web App**: Navigate to the Apps page in your instance to browse, add, configure, and deploy apps
|
||||
|
||||
2. **CLI**: Use the Wild CLI for terminal-based workflows:
|
||||
- `wild app list` - List all available apps
|
||||
- `wild app add <app-name>` - Add an app (compiles templates, updates config/secrets)
|
||||
- `wild app deploy <app-name>` - Deploy an app to the cluster
|
||||
- `wild app list-deployed` - List deployed apps
|
||||
- `wild app status <app-name>` - Get app status
|
||||
- `wild app delete <app-name>` - Delete an app
|
||||
|
||||
3. **API**: Use the Wild Central API endpoints for automation:
|
||||
- `GET /api/v1/apps/available` - List all available apps
|
||||
- `GET /api/v1/apps/available/{app-name}` - Get app details
|
||||
- `POST /api/v1/instances/{instance-name}/apps` - Add an app (compiles templates, updates config/secrets)
|
||||
- `POST /api/v1/instances/{instance-name}/apps/{app-name}/deploy` - Deploy an app to the cluster
|
||||
- `GET /api/v1/instances/{instance-name}/apps` - List deployed apps
|
||||
- `GET /api/v1/instances/{instance-name}/apps/{app-name}/status` - Get app status
|
||||
|
||||
## Validation
|
||||
|
||||
Before submitting changes:
|
||||
1. Ensure `manifest.yaml` has all required fields
|
||||
2. Verify `kustomization.yaml` includes standard Wild Cloud labels
|
||||
3. Check all templates use valid configuration paths defined in `defaultConfig`
|
||||
4. Confirm secrets use full dotted-path keys
|
||||
5. Verify all pods have proper security contexts
|
||||
6. Test template compilation works with sample operator config
|
||||
|
||||
## Examples of Well-Structured Apps
|
||||
|
||||
- **immich** - Multi-deployment app with database init, multiple services, PostgreSQL extensions
|
||||
- **openproject** - App with multiple dependencies (postgres, memcached), configmap-based configuration
|
||||
- **gitea** - Standard app with database init, PVC for storage
|
||||
|
||||
14
README.md
14
README.md
@@ -97,20 +97,6 @@ Some apps require other apps to function. For example:
|
||||
|
||||
When you add an app, check its `requires` field in the manifest and ensure dependencies are added first.
|
||||
|
||||
## Available Apps
|
||||
|
||||
This repository includes apps for:
|
||||
- Content management (Ghost, Discourse)
|
||||
- Project management (OpenProject)
|
||||
- Photo management (Immich)
|
||||
- Code hosting (Gitea)
|
||||
- Email marketing (Listmonk, Keila)
|
||||
- AI interfaces (Open WebUI, vLLM)
|
||||
- Databases (PostgreSQL, MySQL, Redis, Memcached)
|
||||
- And more...
|
||||
|
||||
Browse the full catalog with descriptions through the web app, CLI, or via the API endpoint `/api/v1/apps/available`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Want to add a new app or improve an existing one? See [ADDING-APPS.md](ADDING-APPS.md) for detailed guidance on creating Wild Cloud apps.
|
||||
|
||||
25
decidim/Dockerfile
Normal file
25
decidim/Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Build Decidim with Sidekiq support
|
||||
FROM decidim/decidim:0.31.0
|
||||
|
||||
# Switch to root to install dependencies
|
||||
USER root
|
||||
|
||||
# Add sidekiq to Gemfile
|
||||
RUN cd /code && \
|
||||
echo "" >> Gemfile && \
|
||||
echo "# Background job processing" >> Gemfile && \
|
||||
echo "gem 'sidekiq', '~> 6.5'" >> Gemfile && \
|
||||
bundle install
|
||||
|
||||
# Configure Rails to use Sidekiq as ActiveJob backend
|
||||
RUN cd /code && \
|
||||
test -d config/initializers || mkdir -p config/initializers && \
|
||||
echo "Rails.application.config.active_job.queue_adapter = :sidekiq" > config/initializers/active_job.rb && \
|
||||
cat config/initializers/active_job.rb && \
|
||||
ls -la config/initializers/
|
||||
|
||||
# Switch back to decidim user
|
||||
USER decidim
|
||||
|
||||
# Default command (can be overridden)
|
||||
CMD ["bundle", "exec", "rails", "s", "-b", "0.0.0.0"]
|
||||
73
decidim/db-init-job.yaml
Normal file
73
decidim/db-init-job.yaml
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: decidim-db-init
|
||||
namespace: decidim
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: db-init
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
fsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: db-init
|
||||
image: postgres:17
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
export PGPASSWORD="${POSTGRES_ADMIN_PASSWORD}"
|
||||
|
||||
# Create database if it doesn't exist
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${DB_NAME}'" | grep -q 1 || \
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "CREATE DATABASE ${DB_NAME};"
|
||||
|
||||
# Create user if it doesn't exist, or update password if it does
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -tc "SELECT 1 FROM pg_roles WHERE rolname = '${DB_USER}'" | grep -q 1 && \
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || \
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
|
||||
|
||||
# Grant privileges
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
|
||||
|
||||
# Grant schema privileges (needed for Rails migrations)
|
||||
psql -h "${POSTGRES_HOST}" -U "${POSTGRES_ADMIN_USER}" -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
|
||||
|
||||
echo "Database initialization completed successfully"
|
||||
env:
|
||||
- name: POSTGRES_HOST
|
||||
value: {{ .dbHostname }}
|
||||
- name: POSTGRES_ADMIN_USER
|
||||
value: postgres
|
||||
- name: POSTGRES_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: postgres.password
|
||||
- name: DB_NAME
|
||||
value: {{ .dbName }}
|
||||
- name: DB_USER
|
||||
value: {{ .dbUsername }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: dbPassword
|
||||
214
decidim/deployment.yaml
Normal file
214
decidim/deployment.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: web
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
serviceAccountName: decidim
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: Always
|
||||
containers:
|
||||
- name: decidim
|
||||
image: payneio/decidim-sidekiq:0.31.0
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
cd /code
|
||||
bundle exec rake db:migrate
|
||||
bundle exec rails runner "Decidim::System::Admin.find_or_create_by!(email: ENV['SYSTEM_ADMIN_EMAIL']) { |admin| admin.password = ENV['SYSTEM_ADMIN_PASSWORD']; admin.password_confirmation = ENV['SYSTEM_ADMIN_PASSWORD'] }"
|
||||
bundle exec rails s -b 0.0.0.0
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
- DAC_OVERRIDE
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
env:
|
||||
- name: RAILS_ENV
|
||||
value: "production"
|
||||
- name: PORT
|
||||
value: "{{ .port }}"
|
||||
- name: RAILS_LOG_TO_STDOUT
|
||||
value: "true"
|
||||
# Database configuration
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: dbUrl
|
||||
# Redis configuration
|
||||
- name: REDIS_HOSTNAME
|
||||
value: {{ .redisHostname }}
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: redis.password
|
||||
- name: REDIS_URL
|
||||
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOSTNAME):6379/0"
|
||||
# Application configuration
|
||||
- name: DECIDIM_HOST
|
||||
value: {{ .domain }}
|
||||
- name: DECIDIM_ORGANIZATION_NAME
|
||||
value: {{ .siteName }}
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: secretKeyBase
|
||||
# SMTP configuration
|
||||
- name: SMTP_ADDRESS
|
||||
value: {{ .smtp.host }}
|
||||
- name: SMTP_PORT
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: SMTP_USERNAME
|
||||
value: {{ .smtp.user }}
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: smtpPassword
|
||||
- name: SMTP_DOMAIN
|
||||
value: {{ .domain }}
|
||||
- name: SMTP_FROM
|
||||
value: {{ .smtp.from }}
|
||||
- name: SMTP_STARTTLS_AUTO
|
||||
value: "{{ .smtp.startTls }}"
|
||||
# System admin credentials
|
||||
- name: SYSTEM_ADMIN_EMAIL
|
||||
value: {{ .systemAdminEmail }}
|
||||
- name: SYSTEM_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: systemAdminPassword
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .port }}
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 300
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 180
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
ephemeral-storage: 10Gi
|
||||
memory: 4Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
ephemeral-storage: 50Mi
|
||||
memory: 1Gi
|
||||
volumeMounts:
|
||||
- name: decidim-data
|
||||
mountPath: /code/public/uploads
|
||||
- name: sidekiq
|
||||
image: payneio/decidim-sidekiq:0.31.0
|
||||
imagePullPolicy: Always
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
cd /code
|
||||
bundle exec sidekiq
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
- DAC_OVERRIDE
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
env:
|
||||
- name: RAILS_ENV
|
||||
value: "production"
|
||||
- name: RAILS_LOG_TO_STDOUT
|
||||
value: "true"
|
||||
# Database configuration
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: dbUrl
|
||||
# Redis configuration
|
||||
- name: REDIS_HOSTNAME
|
||||
value: {{ .redisHostname }}
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: redis.password
|
||||
- name: REDIS_URL
|
||||
value: "redis://:$(REDIS_PASSWORD)@$(REDIS_HOSTNAME):6379/0"
|
||||
# Application configuration
|
||||
- name: DECIDIM_HOST
|
||||
value: {{ .domain }}
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: decidim-secrets
|
||||
key: secretKeyBase
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: decidim-data
|
||||
mountPath: /code/public/uploads
|
||||
volumes:
|
||||
- name: decidim-data
|
||||
persistentVolumeClaim:
|
||||
claimName: decidim-data
|
||||
26
decidim/ingress.yaml
Normal file
26
decidim/ingress.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: decidim
|
||||
port:
|
||||
number: {{ .port }}
|
||||
17
decidim/kustomization.yaml
Normal file
17
decidim/kustomization.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: decidim
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: decidim
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- pvc.yaml
|
||||
- db-init-job.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
44
decidim/manifest.yaml
Normal file
44
decidim/manifest.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
name: decidim
|
||||
is: decidim
|
||||
description: Decidim is a participatory democracy framework for cities and organizations. Built in Ruby on Rails, it enables citizen participation through proposals, debates, and voting. Includes Sidekiq for background job processing.
|
||||
version: 0.31.0
|
||||
icon: https://raw.githubusercontent.com/decidim/decidim/develop/logo.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
installed_as: postgres
|
||||
- name: redis
|
||||
installed_as: redis
|
||||
defaultConfig:
|
||||
namespace: decidim
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
timezone: UTC
|
||||
port: 3000
|
||||
storage: 20Gi
|
||||
systemAdminEmail: "{{ .operator.email }}"
|
||||
siteName: "Decidim"
|
||||
domain: decidim.{{ .cloud.domain }}
|
||||
dbHostname: "{{ .apps.postgres.host }}"
|
||||
dbPort: "{{ .apps.postgres.port }}"
|
||||
dbUsername: decidim
|
||||
dbName: decidim
|
||||
redisHostname: "{{ .apps.redis.host }}"
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
enabled: true
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: systemAdminPassword
|
||||
- key: secretKeyBase
|
||||
default: "{{ random.AlphaNum 128 }}"
|
||||
- key: smtpPassword
|
||||
- key: dbPassword
|
||||
- key: dbUrl
|
||||
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/{{ .app.dbName }}"
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
- redis.password
|
||||
4
decidim/namespace.yaml
Normal file
4
decidim/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: decidim
|
||||
12
decidim/pvc.yaml
Normal file
12
decidim/pvc.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: decidim-data
|
||||
namespace: decidim
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage }}
|
||||
15
decidim/service.yaml
Normal file
15
decidim/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
spec:
|
||||
selector:
|
||||
component: web
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
7
decidim/serviceaccount.yaml
Normal file
7
decidim/serviceaccount.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: decidim
|
||||
namespace: decidim
|
||||
automountServiceAccountToken: false
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
# Source: discourse/templates/configmaps.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: discourse
|
||||
namespace: discourse
|
||||
data:
|
||||
DISCOURSE_HOSTNAME: "{{ .apps.discourse.domain }}"
|
||||
DISCOURSE_SKIP_INSTALL: "no"
|
||||
DISCOURSE_SITE_NAME: "{{ .apps.discourse.siteName }}"
|
||||
DISCOURSE_USERNAME: "{{ .apps.discourse.adminUsername }}"
|
||||
DISCOURSE_EMAIL: "{{ .apps.discourse.adminEmail }}"
|
||||
|
||||
DISCOURSE_REDIS_HOST: "{{ .apps.discourse.redisHostname }}"
|
||||
DISCOURSE_REDIS_PORT_NUMBER: "6379"
|
||||
|
||||
DISCOURSE_DATABASE_HOST: "{{ .apps.discourse.dbHostname }}"
|
||||
DISCOURSE_DATABASE_PORT_NUMBER: "5432"
|
||||
DISCOURSE_DATABASE_NAME: "{{ .apps.discourse.dbName }}"
|
||||
DISCOURSE_DATABASE_USER: "{{ .apps.discourse.dbUsername }}"
|
||||
|
||||
DISCOURSE_SMTP_HOST: "{{ .apps.discourse.smtp.host }}"
|
||||
DISCOURSE_SMTP_PORT: "{{ .apps.discourse.smtp.port }}"
|
||||
DISCOURSE_SMTP_USER: "{{ .apps.discourse.smtp.user }}"
|
||||
DISCOURSE_SMTP_PROTOCOL: "tls"
|
||||
DISCOURSE_SMTP_AUTH: "login"
|
||||
|
||||
# DISCOURSE_PRECOMPILE_ASSETS: "false"
|
||||
# DISCOURSE_SKIP_INSTALL: "no"
|
||||
# DISCOURSE_SKIP_BOOTSTRAP: "yes"
|
||||
@@ -27,7 +27,7 @@ spec:
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: "{{ .apps.discourse.dbHostname }}"
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: PGPORT
|
||||
value: "5432"
|
||||
- name: PGUSER
|
||||
@@ -36,16 +36,16 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.postgres.password
|
||||
key: postgres.password
|
||||
- name: DISCOURSE_DB_USER
|
||||
value: "{{ .apps.discourse.dbUsername }}"
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DISCOURSE_DB_NAME
|
||||
value: "{{ .apps.discourse.dbName }}"
|
||||
value: "{{ .dbName }}"
|
||||
- name: DISCOURSE_DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.dbPassword
|
||||
key: dbPassword
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
# Source: discourse/templates/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -18,219 +17,288 @@ spec:
|
||||
component: web
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
component: web
|
||||
topologyKey: kubernetes.io/hostname
|
||||
weight: 1
|
||||
|
||||
serviceAccountName: discourse
|
||||
securityContext:
|
||||
fsGroup: 0
|
||||
fsGroup: 1000
|
||||
fsGroupChangePolicy: Always
|
||||
supplementalGroups: []
|
||||
sysctls: []
|
||||
initContainers:
|
||||
containers:
|
||||
- name: discourse
|
||||
image: docker.io/bitnami/discourse:3.4.7-debian-12-r0
|
||||
- name: discourse-migrate
|
||||
image: discourse/discourse:3.5.3
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- SYS_CHROOT
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
- DAC_OVERRIDE
|
||||
drop:
|
||||
- ALL
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsGroup: 0
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seLinuxOptions: {}
|
||||
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: BITNAMI_DEBUG
|
||||
value: "false"
|
||||
- name: DISCOURSE_PASSWORD
|
||||
- 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: apps.discourse.adminPassword
|
||||
- name: DISCOURSE_PORT_NUMBER
|
||||
value: "8080"
|
||||
- name: DISCOURSE_EXTERNAL_HTTP_PORT_NUMBER
|
||||
value: "80"
|
||||
- name: DISCOURSE_DATABASE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.dbPassword
|
||||
- name: POSTGRESQL_CLIENT_CREATE_DATABASE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.dbPassword
|
||||
key: dbPassword
|
||||
- name: DISCOURSE_REDIS_HOST
|
||||
value: {{ .redisHostname }}
|
||||
- name: DISCOURSE_REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.redis.password
|
||||
key: redis.password
|
||||
- name: DISCOURSE_HOSTNAME
|
||||
value: {{ .domain }}
|
||||
- name: DISCOURSE_SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.secretKeyBase
|
||||
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
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
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: apps.discourse.smtpPassword
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.smtpPassword
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: discourse
|
||||
key: smtpPassword
|
||||
- name: DISCOURSE_SMTP_ENABLE_START_TLS
|
||||
value: "{{ .smtp.startTls }}"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
httpGet:
|
||||
path: /srv/status
|
||||
port: http
|
||||
initialDelaySeconds: 500
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /srv/status
|
||||
port: http
|
||||
initialDelaySeconds: 180
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
initialDelaySeconds: 360
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
ephemeral-storage: 2Gi
|
||||
memory: 8Gi # for precompiling assets!
|
||||
cpu: 2000m
|
||||
ephemeral-storage: 10Gi
|
||||
memory: 8Gi
|
||||
requests:
|
||||
cpu: 750m
|
||||
ephemeral-storage: 50Mi
|
||||
memory: 1Gi
|
||||
volumeMounts:
|
||||
- name: discourse-data
|
||||
mountPath: /bitnami/discourse
|
||||
subPath: discourse
|
||||
mountPath: /shared
|
||||
- name: sidekiq
|
||||
image: docker.io/bitnami/discourse:3.4.7-debian-12-r0
|
||||
image: discourse/discourse:3.5.3
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- CHOWN
|
||||
- SYS_CHROOT
|
||||
- FOWNER
|
||||
- SETGID
|
||||
- SETUID
|
||||
- DAC_OVERRIDE
|
||||
drop:
|
||||
- ALL
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsGroup: 0
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seLinuxOptions: {}
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
command:
|
||||
- /opt/bitnami/scripts/discourse/entrypoint.sh
|
||||
args:
|
||||
- /opt/bitnami/scripts/discourse-sidekiq/run.sh
|
||||
- /bin/bash
|
||||
- -c
|
||||
- "cd /var/www/discourse && export HOME=/root && exec bundle exec sidekiq"
|
||||
env:
|
||||
- name: BITNAMI_DEBUG
|
||||
value: "false"
|
||||
- name: DISCOURSE_PASSWORD
|
||||
- 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: apps.discourse.adminPassword
|
||||
- name: DISCOURSE_POSTGRESQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.dbPassword
|
||||
key: dbPassword
|
||||
# Redis configuration
|
||||
- name: DISCOURSE_REDIS_HOST
|
||||
value: {{ .redisHostname }}
|
||||
- name: DISCOURSE_REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.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: apps.discourse.secretKeyBase
|
||||
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: apps.discourse.smtpPassword
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: discourse-secrets
|
||||
key: apps.discourse.smtpPassword
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: discourse
|
||||
key: smtpPassword
|
||||
- name: DISCOURSE_SMTP_ENABLE_START_TLS
|
||||
value: "{{ .smtp.startTls }}"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "pgrep -f ^sidekiq"]
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- "pgrep -f sidekiq"
|
||||
initialDelaySeconds: 500
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["/bin/sh", "-c", "pgrep -f ^sidekiq"]
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- "pgrep -f sidekiq"
|
||||
initialDelaySeconds: 180
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
cpu: 1000m
|
||||
ephemeral-storage: 2Gi
|
||||
memory: 768Mi
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 375m
|
||||
ephemeral-storage: 50Mi
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: discourse-data
|
||||
mountPath: /bitnami/discourse
|
||||
subPath: discourse
|
||||
mountPath: /shared
|
||||
volumes:
|
||||
- name: discourse-data
|
||||
persistentVolumeClaim:
|
||||
claimName: discourse
|
||||
claimName: discourse-data
|
||||
|
||||
@@ -4,13 +4,13 @@ apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: discourse
|
||||
namespace: "discourse"
|
||||
namespace: "{{ .namespace }}"
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}"
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
|
||||
spec:
|
||||
rules:
|
||||
- host: "{{ .apps.discourse.domain }}"
|
||||
- host: "{{ .domain }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -22,5 +22,5 @@ spec:
|
||||
name: http
|
||||
tls:
|
||||
- hosts:
|
||||
- "{{ .apps.discourse.domain }}"
|
||||
- "{{ .domain }}"
|
||||
secretName: wildcard-external-wild-cloud-tls
|
||||
|
||||
@@ -10,7 +10,6 @@ labels:
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- configmap.yaml
|
||||
- pvc.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
name: discourse
|
||||
is: discourse
|
||||
description: Discourse is a modern, open-source discussion platform designed for online communities and forums.
|
||||
version: 3.4.7
|
||||
icon: https://www.discourse.org/img/icon.png
|
||||
version: 3.5.3
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: redis
|
||||
defaultConfig:
|
||||
namespace: discourse
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
timezone: UTC
|
||||
port: 8080
|
||||
port: 3000
|
||||
storage: 10Gi
|
||||
adminEmail: admin@{{ .cloud.domain }}
|
||||
adminEmail: "{{ .operator.email }}"
|
||||
adminUsername: admin
|
||||
siteName: "Community"
|
||||
domain: discourse.{{ .cloud.domain }}
|
||||
dbHostname: postgres.postgres.svc.cluster.local
|
||||
dbHostname: "{{ .apps.postgres.host }}"
|
||||
dbPort: "{{ .apps.postgres.port }}"
|
||||
dbUsername: discourse
|
||||
dbName: discourse
|
||||
redisHostname: redis.redis.svc.cluster.local
|
||||
redisHostname: "{{ .apps.redis.host }}"
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
enabled: false
|
||||
@@ -24,13 +28,16 @@ defaultConfig:
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
tls: {{ .cloud.smtp.tls }}
|
||||
startTls: {{ .cloud.smtp.startTls }}
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: adminPassword
|
||||
- key: secretKeyBase
|
||||
default: "{{ random.AlphaNum 64 }}"
|
||||
- key: smtpPassword
|
||||
- key: dbPassword
|
||||
- key: dbUrl
|
||||
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode=disable"
|
||||
requiredSecrets:
|
||||
- apps.discourse.adminPassword
|
||||
- apps.discourse.dbPassword
|
||||
- apps.discourse.dbUrl
|
||||
- apps.redis.password
|
||||
- apps.discourse.secretKeyBase
|
||||
- apps.discourse.smtpPassword
|
||||
- apps.postgres.password
|
||||
- postgres.password
|
||||
- redis.password
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: discourse
|
||||
name: "{{ .namespace }}"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
# Source: discourse/templates/pvc.yaml
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: discourse
|
||||
name: discourse-data
|
||||
namespace: discourse
|
||||
spec:
|
||||
accessModes:
|
||||
- "ReadWriteOnce"
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: "{{ .apps.discourse.storage }}"
|
||||
storage: {{ .storage }}
|
||||
storageClassName: longhorn
|
||||
|
||||
@@ -3,7 +3,6 @@ apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: example-admin
|
||||
namespace: example-admin
|
||||
labels:
|
||||
app: example-admin
|
||||
spec:
|
||||
|
||||
@@ -3,10 +3,9 @@ apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: example-admin
|
||||
namespace: example-admin
|
||||
spec:
|
||||
rules:
|
||||
- host: example-admin.{{ .cloud.internalDomain }}
|
||||
- host: "{{ .host }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -18,5 +17,5 @@ spec:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- example-admin.{{ .cloud.internalDomain }}
|
||||
- "{{ .host }}"
|
||||
secretName: wildcard-internal-wild-cloud-tls
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: example-admin
|
||||
is: example
|
||||
install: true
|
||||
description: An example application that is deployed with internal-only access.
|
||||
version: 1.0.0
|
||||
defaultConfig:
|
||||
namespace: example-admin
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
host: '{{ .host }}'
|
||||
tlsSecretName: wildcard-internal-wild-cloud-tls
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: example-admin
|
||||
name: "{{ .namespace }}"
|
||||
|
||||
@@ -4,7 +4,7 @@ kind: Ingress
|
||||
metadata:
|
||||
name: example-app
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .cloud.externalDnsTarget }}"
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: false
|
||||
|
||||
# Optional: Enable HTTPS redirection
|
||||
@@ -15,7 +15,7 @@ metadata:
|
||||
# traefik.ingress.kubernetes.io/auth-secret: basic-auth
|
||||
spec:
|
||||
rules:
|
||||
- host: example-app.{{ .cloud.domain }}
|
||||
- host: "{{ .host }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -27,5 +27,5 @@ spec:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- example-app.{{ .cloud.domain }}
|
||||
- "{{ .host }}"
|
||||
secretName: wildcard-wild-cloud-tls
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: example-app
|
||||
is: example
|
||||
install: true
|
||||
description: An example application that is deployed with public access.
|
||||
version: 1.0.0
|
||||
defaultConfig:
|
||||
namespace: example-app
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
host: example-app.{{ .cloud.domain }}
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: example-app
|
||||
name: "{{ .namespace }}"
|
||||
|
||||
@@ -12,7 +12,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: db-init
|
||||
image: {{ .apps.mysql.image }}
|
||||
image: mysql:9.1.0
|
||||
command: ["/bin/bash", "-c"]
|
||||
args:
|
||||
- |
|
||||
@@ -27,18 +27,18 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mysql-secrets
|
||||
key: apps.mysql.rootPassword
|
||||
key: rootPassword
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.ghost.dbHost }}"
|
||||
value: "{{ .dbHost }}"
|
||||
- name: DB_PORT
|
||||
value: "{{ .apps.ghost.dbPort }}"
|
||||
value: "{{ .dbPort }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
value: "{{ .apps.ghost.dbName }}"
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USERNAME
|
||||
value: "{{ .apps.ghost.dbUser }}"
|
||||
value: "{{ .dbUser }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: apps.ghost.dbPassword
|
||||
key: dbPassword
|
||||
restartPolicy: OnFailure
|
||||
@@ -17,10 +17,10 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: ghost
|
||||
image: {{ .apps.ghost.image }}
|
||||
image: {{ .image }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .apps.ghost.port }}
|
||||
containerPort: {{ .port }}
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: BITNAMI_DEBUG
|
||||
@@ -28,33 +28,33 @@ spec:
|
||||
- name: ALLOW_EMPTY_PASSWORD
|
||||
value: "yes"
|
||||
- name: GHOST_DATABASE_HOST
|
||||
value: {{ .apps.ghost.dbHost }}
|
||||
value: {{ .dbHost }}
|
||||
- name: GHOST_DATABASE_PORT_NUMBER
|
||||
value: "{{ .apps.ghost.dbPort }}"
|
||||
value: "{{ .dbPort }}"
|
||||
- name: GHOST_DATABASE_NAME
|
||||
value: {{ .apps.ghost.dbName }}
|
||||
value: {{ .dbName }}
|
||||
- name: GHOST_DATABASE_USER
|
||||
value: {{ .apps.ghost.dbUser }}
|
||||
value: {{ .dbUser }}
|
||||
- name: GHOST_DATABASE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: apps.ghost.dbPassword
|
||||
key: dbPassword
|
||||
- name: GHOST_HOST
|
||||
value: {{ .apps.ghost.domain }}
|
||||
value: {{ .domain }}
|
||||
- name: GHOST_PORT_NUMBER
|
||||
value: "{{ .apps.ghost.port }}"
|
||||
value: "{{ .port }}"
|
||||
- name: GHOST_USERNAME
|
||||
value: {{ .apps.ghost.adminUser }}
|
||||
value: {{ .adminUser }}
|
||||
- name: GHOST_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: apps.ghost.adminPassword
|
||||
key: adminPassword
|
||||
- name: GHOST_EMAIL
|
||||
value: {{ .apps.ghost.adminEmail }}
|
||||
value: {{ .adminEmail }}
|
||||
- name: GHOST_BLOG_TITLE
|
||||
value: {{ .apps.ghost.blogTitle }}
|
||||
value: {{ .blogTitle }}
|
||||
- name: GHOST_ENABLE_HTTPS
|
||||
value: "yes"
|
||||
- name: GHOST_EXTERNAL_HTTP_PORT_NUMBER
|
||||
@@ -66,18 +66,18 @@ spec:
|
||||
- name: GHOST_SMTP_SERVICE
|
||||
value: SMTP
|
||||
- name: GHOST_SMTP_HOST
|
||||
value: {{ .apps.ghost.smtp.host }}
|
||||
value: {{ .smtp.host }}
|
||||
- name: GHOST_SMTP_PORT
|
||||
value: "{{ .apps.ghost.smtp.port }}"
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: GHOST_SMTP_USER
|
||||
value: {{ .apps.ghost.smtp.user }}
|
||||
value: {{ .smtp.user }}
|
||||
- name: GHOST_SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ghost-secrets
|
||||
key: apps.ghost.smtpPassword
|
||||
key: smtpPassword
|
||||
- name: GHOST_SMTP_FROM_ADDRESS
|
||||
value: {{ .apps.ghost.smtp.from }}
|
||||
value: {{ .smtp.from }}
|
||||
resources:
|
||||
limits:
|
||||
cpu: 375m
|
||||
@@ -92,7 +92,7 @@ spec:
|
||||
mountPath: /bitnami/ghost
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: {{ .apps.ghost.port }}
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 120
|
||||
timeoutSeconds: 5
|
||||
periodSeconds: 10
|
||||
|
||||
@@ -7,12 +7,12 @@ metadata:
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||
traefik.ingress.kubernetes.io/redirect-entry-point: https
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ .apps.ghost.domain }}
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -24,5 +24,5 @@ spec:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .apps.ghost.domain }}
|
||||
secretName: {{ .apps.ghost.tlsSecretName }}
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
@@ -1,10 +1,14 @@
|
||||
name: ghost
|
||||
description: Ghost is a powerful app for new-media creators to publish, share, and grow a business around their content.
|
||||
is: ghost
|
||||
description: Ghost is a powerful app for new-media creators to publish, share, and
|
||||
grow a business around their content.
|
||||
version: 5.118.1
|
||||
icon: https://ghost.org/images/logos/ghost-logo-orb.png
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png
|
||||
requires:
|
||||
- name: mysql
|
||||
- name: mysql
|
||||
defaultConfig:
|
||||
namespace: ghost
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
image: docker.io/bitnami/ghost:5.118.1-debian-12-r0
|
||||
domain: ghost.{{ .cloud.domain }}
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
@@ -15,16 +19,17 @@ defaultConfig:
|
||||
dbName: ghost
|
||||
dbUser: ghost
|
||||
adminUser: admin
|
||||
adminEmail: "admin@{{ .cloud.domain }}"
|
||||
blogTitle: "My Blog"
|
||||
adminEmail: {{ .operator.email }}
|
||||
blogTitle: My Blog
|
||||
timezone: UTC
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
host: '{{ .cloud.smtp.host }}'
|
||||
port: '{{ .cloud.smtp.port }}'
|
||||
from: '{{ .cloud.smtp.from }}'
|
||||
user: '{{ .cloud.smtp.user }}'
|
||||
defaultSecrets:
|
||||
- key: adminPassword
|
||||
- key: dbPassword
|
||||
- key: smtpPassword
|
||||
requiredSecrets:
|
||||
- apps.ghost.adminPassword
|
||||
- apps.ghost.dbPassword
|
||||
- apps.ghost.smtpPassword
|
||||
- mysql.rootPassword
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ghost
|
||||
name: "{{ .namespace }}"
|
||||
@@ -8,4 +8,4 @@ spec:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .apps.ghost.storage }}
|
||||
storage: {{ .storage }}
|
||||
@@ -9,6 +9,6 @@ spec:
|
||||
- name: http
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: {{ .apps.ghost.port }}
|
||||
targetPort: {{ .port }}
|
||||
selector:
|
||||
component: web
|
||||
@@ -20,7 +20,7 @@ Sensitive configuration is stored in the `gitea-secrets` secret and managed by t
|
||||
- `dbPassword` - Database password
|
||||
- `smtpPassword` - SMTP authentication password
|
||||
|
||||
Secrets are defined in `secrets.yaml` and listed in `manifest.yaml` under `requiredSecrets`. When deploying, the system automatically ensures all required secrets exist in the `gitea-secrets` secret before deployment.
|
||||
Secrets are defined in `secrets.yaml` and listed in `manifest.yaml` under `defaultSecrets`. When deploying, the system automatically ensures all required secrets exist in the `gitea-secrets` secret before deployment.
|
||||
|
||||
### Persistent Configuration (app.ini)
|
||||
Gitea manages its own `app.ini` file on persistent storage for:
|
||||
@@ -46,7 +46,7 @@ Gitea manages its own `app.ini` file on persistent storage for:
|
||||
|
||||
### Secret Settings
|
||||
1. Edit `secrets.yaml` with your secret values
|
||||
2. Ensure the secret key is listed in `manifest.yaml` under `requiredSecrets`
|
||||
2. Ensure the secret key is listed in `manifest.yaml` under `defaultSecrets`
|
||||
3. Deploy the app via the web app, CLI, or API - this will automatically update the `gitea-secrets` secret and restart the pod
|
||||
|
||||
### Web UI Changes
|
||||
|
||||
@@ -12,7 +12,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: db-init
|
||||
image: {{ .apps.postgres.image }}
|
||||
image: postgres:17
|
||||
command: ["/bin/bash", "-c"]
|
||||
args:
|
||||
- |
|
||||
@@ -36,16 +36,16 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-secrets
|
||||
key: apps.postgres.password
|
||||
key: password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.gitea.dbHost }}"
|
||||
value: "{{ .dbHost }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
value: "{{ .apps.gitea.dbName }}"
|
||||
value: "{{ .dbName }}"
|
||||
- name: DB_USERNAME
|
||||
value: "{{ .apps.gitea.dbUser }}"
|
||||
value: "{{ .dbUser }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: apps.gitea.dbPassword
|
||||
key: dbPassword
|
||||
restartPolicy: OnFailure
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- name: gitea
|
||||
image: "{{ .apps.gitea.image }}"
|
||||
image: "{{ .image }}"
|
||||
imagePullPolicy: IfNotPresent
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
@@ -33,27 +33,27 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: apps.gitea.adminPassword
|
||||
key: adminPassword
|
||||
- name: GITEA__security__SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: apps.gitea.secretKey
|
||||
key: secretKey
|
||||
- name: GITEA__security__INTERNAL_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: apps.gitea.jwtSecret
|
||||
key: jwtSecret
|
||||
- name: GITEA__database__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: apps.gitea.dbPassword
|
||||
key: dbPassword
|
||||
- name: GITEA__mailer__PASSWD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: gitea-secrets
|
||||
key: apps.gitea.smtpPassword
|
||||
key: smtpPassword
|
||||
ports:
|
||||
- name: ssh
|
||||
containerPort: 2222
|
||||
|
||||
@@ -3,12 +3,12 @@ SSH_PORT=22
|
||||
GITEA_WORK_DIR=/data
|
||||
GITEA_TEMP=/tmp/gitea
|
||||
TMPDIR=/tmp/gitea
|
||||
GITEA_ADMIN_USERNAME={{ .apps.gitea.adminUser }}
|
||||
GITEA_ADMIN_USERNAME={{ .adminUser }}
|
||||
GITEA_ADMIN_PASSWORD_MODE=keepUpdated
|
||||
|
||||
# Core app settings
|
||||
GITEA____APP_NAME={{ .apps.gitea.appName }}
|
||||
GITEA____RUN_MODE={{ .apps.gitea.runMode }}
|
||||
GITEA____APP_NAME={{ .appName }}
|
||||
GITEA____RUN_MODE={{ .runMode }}
|
||||
GITEA____RUN_USER=git
|
||||
|
||||
# Security settings
|
||||
@@ -17,19 +17,19 @@ GITEA__security__PASSWORD_HASH_ALGO=pbkdf2
|
||||
|
||||
# Database settings (except password which comes from secret)
|
||||
GITEA__database__DB_TYPE=postgres
|
||||
GITEA__database__HOST={{ .apps.gitea.dbHost }}:{{ .apps.gitea.dbPort }}
|
||||
GITEA__database__NAME={{ .apps.gitea.dbName }}
|
||||
GITEA__database__USER={{ .apps.gitea.dbUser }}
|
||||
GITEA__database__HOST={{ .dbHost }}:{{ .dbPort }}
|
||||
GITEA__database__NAME={{ .dbName }}
|
||||
GITEA__database__USER={{ .dbUser }}
|
||||
GITEA__database__SSL_MODE=disable
|
||||
GITEA__database__LOG_SQL=false
|
||||
|
||||
# Server settings
|
||||
GITEA__server__DOMAIN={{ .apps.gitea.domain }}
|
||||
GITEA__server__HTTP_PORT={{ .apps.gitea.port }}
|
||||
GITEA__server__ROOT_URL=https://{{ .apps.gitea.domain }}/
|
||||
GITEA__server__DOMAIN={{ .domain }}
|
||||
GITEA__server__HTTP_PORT={{ .port }}
|
||||
GITEA__server__ROOT_URL=https://{{ .domain }}/
|
||||
GITEA__server__DISABLE_SSH=false
|
||||
GITEA__server__SSH_DOMAIN={{ .apps.gitea.domain }}
|
||||
GITEA__server__SSH_PORT={{ .apps.gitea.sshPort }}
|
||||
GITEA__server__SSH_DOMAIN={{ .domain }}
|
||||
GITEA__server__SSH_PORT={{ .sshPort }}
|
||||
GITEA__server__SSH_LISTEN_PORT=2222
|
||||
GITEA__server__LFS_START_SERVER=true
|
||||
GITEA__server__OFFLINE_MODE=true
|
||||
@@ -53,8 +53,8 @@ GITEA__webhook__ALLOWED_HOST_LIST=*
|
||||
|
||||
# Mailer settings (enabled via env vars, password from secret)
|
||||
GITEA__mailer__ENABLED=true
|
||||
GITEA__mailer__SMTP_ADDR={{ .apps.gitea.smtp.host }}
|
||||
GITEA__mailer__SMTP_PORT={{ .apps.gitea.smtp.port }}
|
||||
GITEA__mailer__FROM={{ .apps.gitea.smtp.from }}
|
||||
GITEA__mailer__USER={{ .apps.gitea.smtp.user }}
|
||||
GITEA__mailer__SMTP_ADDR={{ .smtp.host }}
|
||||
GITEA__mailer__SMTP_PORT={{ .smtp.port }}
|
||||
GITEA__mailer__FROM={{ .smtp.from }}
|
||||
GITEA__mailer__USER={{ .smtp.user }}
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ metadata:
|
||||
namespace: gitea
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}"
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
|
||||
spec:
|
||||
rules:
|
||||
- host: "{{ .apps.gitea.domain }}"
|
||||
- host: "{{ .domain }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -19,6 +19,6 @@ spec:
|
||||
port:
|
||||
number: 3000
|
||||
tls:
|
||||
- secretName: "{{ .apps.gitea.tlsSecretName }}"
|
||||
- secretName: "{{ .tlsSecretName }}"
|
||||
hosts:
|
||||
- "{{ .apps.gitea.domain }}"
|
||||
- "{{ .domain }}"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
name: gitea
|
||||
is: gitea
|
||||
description: Gitea is a painless self-hosted Git service written in Go
|
||||
version: 1.24.3
|
||||
icon: https://github.com/go-gitea/gitea/raw/main/assets/logo.png
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: postgres
|
||||
defaultConfig:
|
||||
namespace: gitea
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
image: gitea/gitea:1.24.3
|
||||
appName: Gitea
|
||||
domain: gitea.{{ .cloud.domain }}
|
||||
@@ -16,18 +19,20 @@ defaultConfig:
|
||||
dbUser: gitea
|
||||
dbHost: postgres.postgres.svc.cluster.local
|
||||
adminUser: admin
|
||||
adminEmail: "admin@{{ .cloud.domain }}"
|
||||
adminEmail: "{{ .operator.email }}"
|
||||
dbPort: 5432
|
||||
timezone: UTC
|
||||
runMode: prod
|
||||
smtp:
|
||||
host: TBD
|
||||
port: 465
|
||||
from: no-reply@{{ .cloud.domain }}
|
||||
user: TBD
|
||||
host: '{{ .cloud.smtp.host }}'
|
||||
port: '{{ .cloud.smtp.port }}'
|
||||
user: '{{ .cloud.smtp.user }}'
|
||||
from: '{{ .cloud.smtp.from }}'
|
||||
defaultSecrets:
|
||||
- key: adminPassword
|
||||
- key: dbPassword
|
||||
- key: secretKey
|
||||
- key: jwtSecret
|
||||
- key: smtpPassword
|
||||
requiredSecrets:
|
||||
- apps.gitea.adminPassword
|
||||
- apps.gitea.dbPassword
|
||||
- apps.gitea.secretKey
|
||||
- apps.gitea.jwtSecret
|
||||
- apps.gitea.smtpPassword
|
||||
- postgres.password
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: gitea
|
||||
name: "{{ .namespace }}"
|
||||
@@ -9,4 +9,4 @@ spec:
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: "{{ .apps.gitea.storage }}"
|
||||
storage: "{{ .storage }}"
|
||||
@@ -8,7 +8,7 @@ spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: {{ .apps.gitea.port }}
|
||||
targetPort: {{ .port }}
|
||||
selector:
|
||||
component: web
|
||||
---
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: ssh
|
||||
port: {{ .apps.gitea.sshPort }}
|
||||
port: {{ .sshPort }}
|
||||
targetPort: 2222
|
||||
protocol: TCP
|
||||
selector:
|
||||
|
||||
@@ -7,7 +7,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: db-init
|
||||
image: {{ .apps.postgres.image }}
|
||||
image: postgres:17
|
||||
command: ["/bin/bash", "-c"]
|
||||
args:
|
||||
- |
|
||||
@@ -53,16 +53,16 @@ spec:
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.postgres.password
|
||||
key: postgres.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.immich.dbHostname }}"
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_DATABASE_NAME
|
||||
value: "{{ .apps.immich.dbUsername }}"
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_USERNAME
|
||||
value: "{{ .apps.immich.dbUsername }}"
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.immich.dbPassword
|
||||
key: dbPassword
|
||||
restartPolicy: OnFailure
|
||||
|
||||
@@ -15,14 +15,14 @@ spec:
|
||||
component: machine-learning
|
||||
spec:
|
||||
containers:
|
||||
- image: "{{ .apps.immich.mlImage }}"
|
||||
- image: "{{ .mlImage }}"
|
||||
name: immich-machine-learning
|
||||
ports:
|
||||
- containerPort: {{ .apps.immich.mlPort }}
|
||||
- containerPort: {{ .mlPort }}
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: TZ
|
||||
value: "{{ .apps.immich.timezone }}"
|
||||
value: "{{ .timezone }}"
|
||||
volumeMounts:
|
||||
- mountPath: /cache
|
||||
name: immich-cache
|
||||
|
||||
@@ -20,27 +20,27 @@ spec:
|
||||
component: microservices
|
||||
spec:
|
||||
containers:
|
||||
- image: "{{ .apps.immich.serverImage }}"
|
||||
- image: "{{ .serverImage }}"
|
||||
name: immich-microservices
|
||||
env:
|
||||
- name: REDIS_HOSTNAME
|
||||
value: "{{ .apps.immich.redisHostname }}"
|
||||
value: "{{ .redisHostname }}"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.redis.password
|
||||
key: redis.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.immich.dbHostname }}"
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_USERNAME
|
||||
value: "{{ .apps.immich.dbUsername }}"
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.immich.dbPassword
|
||||
key: dbPassword
|
||||
- name: TZ
|
||||
value: "{{ .apps.immich.timezone }}"
|
||||
value: "{{ .timezone }}"
|
||||
- name: IMMICH_WORKERS_EXCLUDE
|
||||
value: api
|
||||
volumeMounts:
|
||||
|
||||
@@ -20,30 +20,30 @@ spec:
|
||||
component: server
|
||||
spec:
|
||||
containers:
|
||||
- image: "{{ .apps.immich.serverImage }}"
|
||||
- image: "{{ .serverImage }}"
|
||||
name: immich-server
|
||||
ports:
|
||||
- containerPort: {{ .apps.immich.serverPort }}
|
||||
- containerPort: {{ .serverPort }}
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: REDIS_HOSTNAME
|
||||
value: "{{ .apps.immich.redisHostname }}"
|
||||
value: "{{ .redisHostname }}"
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.redis.password
|
||||
key: redis.password
|
||||
- name: DB_HOSTNAME
|
||||
value: "{{ .apps.immich.dbHostname }}"
|
||||
value: "{{ .dbHostname }}"
|
||||
- name: DB_USERNAME
|
||||
value: "{{ .apps.immich.dbUsername }}"
|
||||
value: "{{ .dbUsername }}"
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.immich.dbPassword
|
||||
key: dbPassword
|
||||
- name: TZ
|
||||
value: "{{ .apps.immich.timezone }}"
|
||||
value: "{{ .timezone }}"
|
||||
- name: IMMICH_WORKERS_EXCLUDE
|
||||
value: microservices
|
||||
volumeMounts:
|
||||
|
||||
@@ -4,11 +4,11 @@ kind: Ingress
|
||||
metadata:
|
||||
name: immich-public
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .cloud.domain }}"
|
||||
external-dns.alpha.kubernetes.io/target: "{{ .externalDnsDomain }}"
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
rules:
|
||||
- host: "{{ .apps.immich.domain }}"
|
||||
- host: "{{ .domain }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -21,4 +21,4 @@ spec:
|
||||
tls:
|
||||
- secretName: wildcard-wild-cloud-tls
|
||||
hosts:
|
||||
- "{{ .apps.immich.domain }}"
|
||||
- "{{ .domain }}"
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
name: immich
|
||||
is: immich
|
||||
install: true
|
||||
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
|
||||
icon: https://immich.app/assets/images/logo.png
|
||||
description: Immich is a self-hosted photo and video backup solution that allows you
|
||||
to store, manage, and share your media files securely.
|
||||
version: release
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
|
||||
requires:
|
||||
- name: redis
|
||||
- name: postgres
|
||||
- name: redis
|
||||
- name: postgres
|
||||
defaultConfig:
|
||||
namespace: immich
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
serverImage: ghcr.io/immich-app/immich-server:release
|
||||
mlImage: ghcr.io/immich-app/immich-machine-learning:release
|
||||
timezone: UTC
|
||||
@@ -19,7 +23,8 @@ defaultConfig:
|
||||
dbUsername: immich
|
||||
domain: immich.{{ .cloud.domain }}
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
requiredSecrets:
|
||||
- apps.immich.dbPassword
|
||||
- apps.postgres.password
|
||||
- apps.redis.password
|
||||
- redis.password
|
||||
- postgres.password
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: immich
|
||||
name: "{{ .namespace }}"
|
||||
@@ -9,7 +9,7 @@ spec:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .apps.immich.storage }}
|
||||
storage: {{ .storage }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
@@ -21,4 +21,4 @@ spec:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .apps.immich.cacheStorage }}
|
||||
storage: {{ .cacheStorage }}
|
||||
|
||||
@@ -9,7 +9,7 @@ metadata:
|
||||
spec:
|
||||
ports:
|
||||
- port: 3001
|
||||
targetPort: {{ .apps.immich.serverPort }}
|
||||
targetPort: {{ .serverPort }}
|
||||
selector:
|
||||
app: immich
|
||||
component: server
|
||||
@@ -25,7 +25,7 @@ metadata:
|
||||
app: immich-machine-learning
|
||||
spec:
|
||||
ports:
|
||||
- port: {{ .apps.immich.mlPort }}
|
||||
- port: {{ .mlPort }}
|
||||
selector:
|
||||
app: immich
|
||||
component: machine-learning
|
||||
|
||||
@@ -26,23 +26,23 @@ spec:
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: {{ .apps.keila.dbHostname }}
|
||||
value: {{ .dbHostname }}
|
||||
- name: PGUSER
|
||||
value: postgres
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: keila-secrets
|
||||
key: apps.postgres.password
|
||||
key: postgres.password
|
||||
- name: DB_NAME
|
||||
value: {{ .apps.keila.dbName }}
|
||||
value: {{ .dbName }}
|
||||
- name: DB_USER
|
||||
value: {{ .apps.keila.dbUsername }}
|
||||
value: {{ .dbUsername }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: keila-secrets
|
||||
key: apps.keila.dbPassword
|
||||
key: dbPassword
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
|
||||
@@ -14,54 +14,54 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: keila
|
||||
image: {{ .apps.keila.image }}
|
||||
image: "{{ .image }}"
|
||||
ports:
|
||||
- containerPort: {{ .apps.keila.port }}
|
||||
- containerPort: {{ .port }}
|
||||
env:
|
||||
- name: DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: keila-secrets
|
||||
key: apps.keila.dbUrl
|
||||
key: dbUrl
|
||||
- name: URL_HOST
|
||||
value: {{ .apps.keila.domain }}
|
||||
value: "{{ .domain }}"
|
||||
- name: URL_SCHEMA
|
||||
value: https
|
||||
- name: URL_PORT
|
||||
value: "443"
|
||||
- name: PORT
|
||||
value: "{{ .apps.keila.port }}"
|
||||
value: "{{ .port }}"
|
||||
- name: SECRET_KEY_BASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: keila-secrets
|
||||
key: apps.keila.secretKeyBase
|
||||
key: secretKeyBase
|
||||
- name: MAILER_SMTP_HOST
|
||||
value: {{ .apps.keila.smtp.host }}
|
||||
value: "{{ .smtp.host }}"
|
||||
- name: MAILER_SMTP_PORT
|
||||
value: "{{ .apps.keila.smtp.port }}"
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: MAILER_ENABLE_SSL
|
||||
value: "{{ .apps.keila.smtp.tls }}"
|
||||
value: "{{ .smtp.tls }}"
|
||||
- name: MAILER_ENABLE_STARTTLS
|
||||
value: "{{ .apps.keila.smtp.startTls }}"
|
||||
value: "{{ .smtp.startTls }}"
|
||||
- name: MAILER_SMTP_USER
|
||||
value: {{ .apps.keila.smtp.user }}
|
||||
value: "{{ .smtp.user }}"
|
||||
- name: MAILER_SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: keila-secrets
|
||||
key: apps.keila.smtpPassword
|
||||
key: smtpPassword
|
||||
- name: MAILER_SMTP_FROM_EMAIL
|
||||
value: {{ .apps.keila.smtp.from }}
|
||||
value: "{{ .smtp.from }}"
|
||||
- name: DISABLE_REGISTRATION
|
||||
value: "{{ .apps.keila.disableRegistration }}"
|
||||
value: "{{ .disableRegistration }}"
|
||||
- name: KEILA_USER
|
||||
value: "{{ .apps.keila.adminUser }}"
|
||||
value: "{{ .adminUser }}"
|
||||
- name: KEILA_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: keila-secrets
|
||||
key: apps.keila.adminPassword
|
||||
key: adminPassword
|
||||
- name: USER_CONTENT_DIR
|
||||
value: /var/lib/keila/uploads
|
||||
volumeMounts:
|
||||
@@ -70,13 +70,13 @@ spec:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .apps.keila.port }}
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .apps.keila.port }}
|
||||
port: {{ .port }}
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
|
||||
@@ -5,12 +5,12 @@ metadata:
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
|
||||
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
traefik.ingress.kubernetes.io/router.middlewares: keila-cors@kubernetescrd
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ .apps.keila.domain }}
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -23,4 +23,4 @@ spec:
|
||||
tls:
|
||||
- secretName: "wildcard-wild-cloud-tls"
|
||||
hosts:
|
||||
- "{{ .apps.keila.domain }}"
|
||||
- "{{ .domain }}"
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
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.
|
||||
version: 1.0.0
|
||||
icon: https://www.keila.io/images/logo.svg
|
||||
version: 0.17.1
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
defaultConfig:
|
||||
image: pentacent/keila:latest
|
||||
namespace: keila
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
image: pentacent/keila:0.17.1
|
||||
port: 4000
|
||||
storage: 1Gi
|
||||
domain: keila.{{ .cloud.domain }}
|
||||
dbHostname: postgres.postgres.svc.cluster.local
|
||||
dbHostname: "{{ .apps.postgres.host }}"
|
||||
dbPort: "{{ .apps.postgres.port }}"
|
||||
dbName: keila
|
||||
dbUsername: keila
|
||||
disableRegistration: "true"
|
||||
@@ -20,12 +24,15 @@ defaultConfig:
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
tls: {{ .cloud.smtp.tls }}
|
||||
startTls: {{ .cloud.smtp.startTls }}
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: secretKeyBase
|
||||
default: "{{ random.AlphaNum 64 }}"
|
||||
- key: dbPassword
|
||||
- key: dbUrl
|
||||
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/keila?sslmode=disable"
|
||||
- key: adminPassword
|
||||
- key: smtpPassword
|
||||
requiredSecrets:
|
||||
- apps.keila.secretKeyBase
|
||||
- apps.keila.dbPassword
|
||||
- apps.keila.dbUrl
|
||||
- apps.keila.adminPassword
|
||||
- apps.keila.smtpPassword
|
||||
- apps.postgres.password
|
||||
- postgres.password
|
||||
@@ -21,8 +21,8 @@ spec:
|
||||
- "OPTIONS"
|
||||
accessControlAllowOriginList:
|
||||
- "http://localhost:1313"
|
||||
- "https://*.{{ .cloud.domain }}"
|
||||
- "https://{{ .cloud.domain }}"
|
||||
- "https://*.{{ .externalDnsDomain }}"
|
||||
- "https://{{ .externalDnsDomain }}"
|
||||
accessControlExposeHeaders:
|
||||
- "*"
|
||||
accessControlMaxAge: 86400
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: keila
|
||||
name: "{{ .namespace }}"
|
||||
@@ -7,4 +7,4 @@ spec:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .apps.keila.storage }}
|
||||
storage: {{ .storage }}
|
||||
@@ -7,5 +7,5 @@ spec:
|
||||
component: web
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: {{ .apps.keila.port }}
|
||||
targetPort: {{ .port }}
|
||||
protocol: TCP
|
||||
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 }}
|
||||
@@ -28,23 +28,23 @@ spec:
|
||||
readOnlyRootFilesystem: false
|
||||
env:
|
||||
- name: PGHOST
|
||||
value: {{ .apps.listmonk.dbHost }}
|
||||
value: {{ .dbHost }}
|
||||
- name: PGUSER
|
||||
value: postgres
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: listmonk-secrets
|
||||
key: apps.postgres.password
|
||||
key: postgres.password
|
||||
- name: DB_NAME
|
||||
value: {{ .apps.listmonk.dbName }}
|
||||
value: {{ .dbName }}
|
||||
- name: DB_USER
|
||||
value: {{ .apps.listmonk.dbUser }}
|
||||
value: {{ .dbUser }}
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: listmonk-secrets
|
||||
key: apps.listmonk.dbPassword
|
||||
key: dbPassword
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
|
||||
@@ -30,21 +30,23 @@ spec:
|
||||
env:
|
||||
- name: LISTMONK_app__address
|
||||
value: "0.0.0.0:9000"
|
||||
- name: LISTMONK_app__root_url
|
||||
value: "{{ .rootUrl }}"
|
||||
- name: LISTMONK_db__host
|
||||
value: {{ .apps.listmonk.dbHost }}
|
||||
value: {{ .dbHost }}
|
||||
- name: LISTMONK_db__port
|
||||
value: "{{ .apps.listmonk.dbPort }}"
|
||||
value: "{{ .dbPort }}"
|
||||
- name: LISTMONK_db__user
|
||||
value: {{ .apps.listmonk.dbUser }}
|
||||
value: {{ .dbUser }}
|
||||
- name: LISTMONK_db__database
|
||||
value: {{ .apps.listmonk.dbName }}
|
||||
value: {{ .dbName }}
|
||||
- name: LISTMONK_db__ssl_mode
|
||||
value: {{ .apps.listmonk.dbSSLMode }}
|
||||
value: {{ .dbSSLMode }}
|
||||
- name: LISTMONK_db__password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: listmonk-secrets
|
||||
key: apps.listmonk.dbPassword
|
||||
key: dbPassword
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
||||
@@ -6,16 +6,16 @@ metadata:
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .apps.listmonk.domain }}
|
||||
secretName: {{ .apps.listmonk.tlsSecretName }}
|
||||
- {{ .domain }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .apps.listmonk.domain }}
|
||||
- host: {{ .domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
name: listmonk
|
||||
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager. It is fast, feature-rich, and packed into a single binary.
|
||||
is: listmonk
|
||||
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager.
|
||||
It is fast, feature-rich, and packed into a single binary.
|
||||
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:
|
||||
- name: postgres
|
||||
- name: postgres
|
||||
defaultConfig:
|
||||
namespace: listmonk
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
domain: listmonk.{{ .cloud.domain }}
|
||||
rootUrl: https://listmonk.{{ .cloud.domain }}
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
storage: 1Gi
|
||||
dbHost: postgres.postgres.svc.cluster.local
|
||||
@@ -14,7 +19,9 @@ defaultConfig:
|
||||
dbUser: listmonk
|
||||
dbSSLMode: disable
|
||||
timezone: UTC
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
- key: dbUrl
|
||||
default: 'postgres://{{ .app.dbUser }}:{{ .secrets.dbPassword }}@{{ .app.dbHost }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode={{ .app.dbSSLMode }}'
|
||||
requiredSecrets:
|
||||
- apps.listmonk.dbPassword
|
||||
- apps.listmonk.dbUrl
|
||||
- apps.postgres.password
|
||||
- postgres.password
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: listmonk
|
||||
name: "{{ .namespace }}"
|
||||
@@ -8,4 +8,4 @@ spec:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .apps.listmonk.storage }}
|
||||
storage: {{ .storage }}
|
||||
55
loomio/db-init-job.yaml
Normal file
55
loomio/db-init-job.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: loomio-db-init
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: db-init
|
||||
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:
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: dbUrl
|
||||
- name: REDIS_URL
|
||||
value: {{ .redisUrl }}
|
||||
- name: DEVISE_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: deviseSecret
|
||||
- name: SECRET_COOKIE_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: secretCookieToken
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
106
loomio/deployment-worker.yaml
Normal file
106
loomio/deployment-worker.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: loomio-worker
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: worker
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: worker
|
||||
spec:
|
||||
containers:
|
||||
- name: worker
|
||||
image: {{ .workerImage }}
|
||||
env:
|
||||
- name: TASK
|
||||
value: worker
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: SITE_NAME
|
||||
value: {{ .appName }}
|
||||
- name: CANONICAL_HOST
|
||||
value: {{ .domain }}
|
||||
- name: PUBLIC_APP_URL
|
||||
value: https://{{ .domain }}
|
||||
- name: SUPPORT_EMAIL
|
||||
value: {{ .supportEmail }}
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: dbUrl
|
||||
- name: REDIS_URL
|
||||
value: {{ .redisUrl }}
|
||||
- name: DEVISE_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: deviseSecret
|
||||
- name: SECRET_COOKIE_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: secretCookieToken
|
||||
- name: ACTIVE_STORAGE_SERVICE
|
||||
value: {{ .activeStorageService }}
|
||||
- name: SMTP_AUTH
|
||||
value: {{ .smtp.auth }}
|
||||
- name: SMTP_DOMAIN
|
||||
value: {{ .smtp.domain }}
|
||||
- name: SMTP_SERVER
|
||||
value: {{ .smtp.host }}
|
||||
- name: SMTP_PORT
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: SMTP_USERNAME
|
||||
value: {{ .smtp.user }}
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: smtpPassword
|
||||
- name: SMTP_USE_SSL
|
||||
value: "{{ .smtp.tls }}"
|
||||
- name: REPLY_HOSTNAME
|
||||
value: {{ .smtp.from }}
|
||||
- name: BUNDLE_APP_CONFIG
|
||||
value: /loomio/tmp/.bundle
|
||||
volumeMounts:
|
||||
- name: uploads
|
||||
mountPath: /loomio/public/system
|
||||
- name: storage
|
||||
mountPath: /loomio/storage
|
||||
- name: tmp
|
||||
mountPath: /loomio/tmp
|
||||
- name: log
|
||||
mountPath: /loomio/log
|
||||
resources:
|
||||
requests:
|
||||
memory: 256Mi
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 1Gi
|
||||
cpu: 500m
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: loomio-uploads
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: loomio-storage
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: log
|
||||
emptyDir: {}
|
||||
134
loomio/deployment.yaml
Normal file
134
loomio/deployment.yaml
Normal file
@@ -0,0 +1,134 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: loomio
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: web
|
||||
spec:
|
||||
containers:
|
||||
- name: loomio
|
||||
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:
|
||||
- containerPort: 3000
|
||||
name: http
|
||||
env:
|
||||
- name: RAILS_ENV
|
||||
value: production
|
||||
- name: SITE_NAME
|
||||
value: {{ .appName }}
|
||||
- name: CANONICAL_HOST
|
||||
value: {{ .domain }}
|
||||
- name: PUBLIC_APP_URL
|
||||
value: https://{{ .domain }}
|
||||
- name: SUPPORT_EMAIL
|
||||
value: {{ .supportEmail }}
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: dbUrl
|
||||
- name: REDIS_URL
|
||||
value: {{ .redisUrl }}
|
||||
- name: DEVISE_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: deviseSecret
|
||||
- name: SECRET_COOKIE_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: secretCookieToken
|
||||
- name: FORCE_SSL
|
||||
value: "{{ .forceSSL }}"
|
||||
- name: USE_RACK_ATTACK
|
||||
value: "{{ .useRackAttack }}"
|
||||
- name: PUMA_WORKERS
|
||||
value: "{{ .pumaWorkers }}"
|
||||
- name: MIN_THREADS
|
||||
value: "{{ .minThreads }}"
|
||||
- name: MAX_THREADS
|
||||
value: "{{ .maxThreads }}"
|
||||
- name: ACTIVE_STORAGE_SERVICE
|
||||
value: {{ .activeStorageService }}
|
||||
- name: SMTP_AUTH
|
||||
value: {{ .smtp.auth }}
|
||||
- name: SMTP_DOMAIN
|
||||
value: {{ .smtp.domain }}
|
||||
- name: SMTP_SERVER
|
||||
value: {{ .smtp.host }}
|
||||
- name: SMTP_PORT
|
||||
value: "{{ .smtp.port }}"
|
||||
- name: SMTP_USERNAME
|
||||
value: {{ .smtp.user }}
|
||||
- name: SMTP_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: loomio-secrets
|
||||
key: smtpPassword
|
||||
- name: REPLY_HOSTNAME
|
||||
value: {{ .smtp.from }}
|
||||
- name: CHANNELS_URI
|
||||
value: wss://{{ .domain }}
|
||||
- name: BUNDLE_APP_CONFIG
|
||||
value: /loomio/tmp/.bundle
|
||||
volumeMounts:
|
||||
- name: uploads
|
||||
mountPath: /loomio/public/system
|
||||
- name: storage
|
||||
mountPath: /loomio/storage
|
||||
- name: tmp
|
||||
mountPath: /loomio/tmp
|
||||
- name: log
|
||||
mountPath: /loomio/log
|
||||
resources:
|
||||
requests:
|
||||
memory: 512Mi
|
||||
cpu: 200m
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 1000m
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 3000
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 3000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
securityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumes:
|
||||
- name: uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: loomio-uploads
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: loomio-storage
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: log
|
||||
emptyDir: {}
|
||||
24
loomio/ingress.yaml
Normal file
24
loomio/ingress.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: loomio
|
||||
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: loomio
|
||||
port:
|
||||
number: 80
|
||||
20
loomio/kustomization.yaml
Normal file
20
loomio/kustomization.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: loomio
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- pvc-uploads.yaml
|
||||
- pvc-storage.yaml
|
||||
- deployment.yaml
|
||||
- deployment-worker.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- db-init-job.yaml
|
||||
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: loomio
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
57
loomio/manifest.yaml
Normal file
57
loomio/manifest.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
name: loomio
|
||||
is: loomio
|
||||
description: Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together
|
||||
version: 3.0.11
|
||||
icon: https://www.loomio.com/brand/logo_gold.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
installed_as: postgres
|
||||
- name: redis
|
||||
defaultConfig:
|
||||
namespace: loomio
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
image: loomio/loomio:latest
|
||||
workerImage: loomio/loomio:latest
|
||||
appName: Loomio
|
||||
domain: "loomio.{{ .cloud.domain }}"
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
port: 3000
|
||||
storage:
|
||||
uploads: 5Gi
|
||||
files: 5Gi
|
||||
plugins: 1Gi
|
||||
redisUrl: "{{ .apps.redis.uri }}"
|
||||
adminEmail: "{{ .operator.email }}"
|
||||
supportEmail: "{{ .operator.email }}"
|
||||
forceSSL: "1"
|
||||
useRackAttack: "1"
|
||||
pumaWorkers: "2"
|
||||
minThreads: "5"
|
||||
maxThreads: "5"
|
||||
activeStorageService: local
|
||||
db:
|
||||
name: loomio
|
||||
user: loomio
|
||||
host: "{{ .apps.postgres.host }}"
|
||||
port: "{{ .apps.postgres.port }}"
|
||||
smtp:
|
||||
auth: plain
|
||||
domain: "{{ .cloud.domain }}"
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
- key: dbUrl
|
||||
default: "postgresql://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?pool=30"
|
||||
- key: deviseSecret
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
- key: secretCookieToken
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
- key: smtpPassword
|
||||
default: "{{ .secrets.smtp.password }}"
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
4
loomio/namespace.yaml
Normal file
4
loomio/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: {{ .namespace }}
|
||||
11
loomio/pvc-storage.yaml
Normal file
11
loomio/pvc-storage.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: loomio-storage
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage.files }}
|
||||
storageClassName: longhorn
|
||||
11
loomio/pvc-uploads.yaml
Normal file
11
loomio/pvc-uploads.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: loomio-uploads
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage.uploads }}
|
||||
storageClassName: longhorn
|
||||
13
loomio/service.yaml
Normal file
13
loomio/service.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: loomio
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
component: web
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
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 }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user