Compare commits
25 Commits
db621755b3
...
mailu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b0c56f720 | ||
|
|
ebc19a9595 | ||
|
|
3a40c7266a | ||
|
|
78adc2883f | ||
|
|
e2aa16e679 | ||
|
|
37dafcd24d | ||
|
|
b6d88e79ac | ||
|
|
963929475c | ||
|
|
39095e76d2 | ||
|
|
d756126a34 | ||
|
|
f17fea6910 | ||
|
|
0ba33a315d | ||
|
|
12706ac331 | ||
|
|
a159c90816 | ||
|
|
32498c73b8 | ||
|
|
c93198d13a | ||
|
|
434769ac7a | ||
|
|
d1304a2630 | ||
|
|
8818d822cf | ||
|
|
1b78abbdc4 | ||
|
|
a4db0d0f6a | ||
|
|
351f58b80d | ||
|
|
458e70b7be | ||
|
|
f68ff43879 | ||
|
|
47c23d3f1b |
540
ADDING-APPS.md
540
ADDING-APPS.md
@@ -1,27 +1,35 @@
|
||||
# Adding custom apps
|
||||
# Adding Wild Cloud Apps
|
||||
|
||||
Custom apps can be added to your Wild Cloud apps directory.
|
||||
This guide is for contributors and maintainers who want to create or modify Wild Cloud apps. If you're looking to use existing apps, see [README.md](README.md).
|
||||
|
||||
Custom apps can be deployed using Wild Cloud scripts. Wild Cloud apps follow a specific structure and naming convention to ensure compatibility with the Wild Cloud ecosystem.
|
||||
## Overview
|
||||
|
||||
## App Structure
|
||||
Wild Cloud apps are Kubernetes applications packaged as Kustomize configurations with standardized conventions for configuration management, secrets handling, and deployment.
|
||||
|
||||
Each subdirectory in this directory represents a Wild Cloud app. Each app directory contains an "app manifest" (`manifest.yaml`), a "kustomization" (`kustomization.yaml`), and one or more "configurations" (yaml files containing definitions/configurations of Kubernetes objects/resources).
|
||||
## Required Files
|
||||
|
||||
### App Manifest
|
||||
Each app directory must contain:
|
||||
|
||||
The required `manifest.yaml` file contains metadata about the app.
|
||||
1. **`manifest.yaml`** - App metadata and configuration schema
|
||||
2. **`kustomization.yaml`** - Kustomize configuration with Wild Cloud labels
|
||||
3. **Resource files** - Kubernetes manifests (deployments, services, ingresses, etc.)
|
||||
|
||||
## App Manifest (`manifest.yaml`)
|
||||
|
||||
The manifest defines the app's metadata, dependencies, configuration schema, and secret requirements.
|
||||
|
||||
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
|
||||
@@ -30,30 +38,199 @@ 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)
|
||||
```
|
||||
|
||||
Explanation of the fields:
|
||||
### Manifest Fields
|
||||
|
||||
- `name`: The name of the app, used for identification.
|
||||
- `description`: A brief description of the app.
|
||||
- `version`: The version of the app. This should generally follow the versioning scheme of the app itself.
|
||||
- `icon`: A URL to an icon representing the app.
|
||||
- `requires`: A list of other apps that this app depends on. Each entry should be the name of another app.
|
||||
- `defaultConfig`: A set of default configuration values for the app. When an app is added using `wild-app-add`, these values will be added to the Wild Cloud `config.yaml` file.
|
||||
- `requiredSecrets`: A list of secrets that must be set in the Wild Cloud `secrets.yaml` file for the app to function properly. These secrets are typically sensitive information like database passwords or API keys. Keys with random values will be generated automatically when the app is added.
|
||||
| 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 with optional aliases |
|
||||
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
|
||||
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
||||
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
||||
|
||||
### Kustomization
|
||||
### Dependency Configuration
|
||||
|
||||
Each app directory should also contain a `kustomization.yaml` file. This file defines how the app's Kubernetes resources are built and deployed. It can include references to other Kustomize files, patches, and configurations.
|
||||
- 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`)
|
||||
|
||||
Here is an example `kustomization.yaml` file for the "immich" app:
|
||||
### 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.dnsmasq.ip - IP address of the DNS server (Wild Central)
|
||||
- cluster.internalDns.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`)
|
||||
|
||||
The kustomization file defines how Kubernetes resources are built and applies Wild Cloud's standard labels.
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
@@ -74,105 +251,129 @@ resources:
|
||||
- pvc.yaml
|
||||
- service.yaml
|
||||
- db-init-job.yaml
|
||||
```
|
||||
```
|
||||
|
||||
Kustomization requirements:
|
||||
#### Kustomization Requirements
|
||||
|
||||
- Every Wild Cloud kustomization should include the Wild Cloud labels in its `kustomization.yaml` file. This allows the Wild Cloud to identify and manage the app correctly. The labels should be defined under the `labels` key, as shown in the example above.
|
||||
- The `app` label and `namespace` keys should the app's name/directory.
|
||||
- **Namespace**: Must match the app name
|
||||
- **Labels**: Must include standard Wild Cloud labels with `includeSelectors: true`
|
||||
- **Resources**: List all Kubernetes manifest files
|
||||
|
||||
#### Standard Wild Cloud Labels
|
||||
#### Labeling Strategy
|
||||
|
||||
Wild Cloud uses a consistent labeling strategy across all apps:
|
||||
Wild Cloud uses Kustomize's `includeSelectors: true` feature to automatically apply standard labels to all resources AND their selectors:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: myapp # The app name (matches directory)
|
||||
managedBy: kustomize # Managed by Kustomize
|
||||
partOf: wild-cloud # Part of Wild Cloud ecosystem
|
||||
app: myapp # App name (matches directory)
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
```
|
||||
|
||||
The `includeSelectors: true` setting automatically applies these labels to all resources AND their selectors, which means:
|
||||
This means individual resources can use simple, component-specific selectors like `component: web`, and Kustomize will automatically expand them to include all Wild Cloud labels.
|
||||
|
||||
1. **Resource labels** - All resources get the standard Wild Cloud labels
|
||||
2. **Selector labels** - All selectors automatically include these labels for robust selection
|
||||
**Do NOT use Helm-style labels** (`app.kubernetes.io/name`, `app.kubernetes.io/instance`). Use simple component labels (`component: web`, `component: worker`, etc.) instead.
|
||||
|
||||
This allows individual resources to use simple, component-specific selectors:
|
||||
## Configuration Templates
|
||||
|
||||
### Gomplate Templating
|
||||
|
||||
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
|
||||
|
||||
Ingress resources should include external-dns annotations for automatic DNS management:
|
||||
|
||||
```yaml
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/target: {{ .domain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
```
|
||||
|
||||
Which Kustomize automatically expands to:
|
||||
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
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
component: web
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
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
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
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
|
||||
|
||||
Wild Cloud apps use Kustomize as kustomize files are simple, transparent, and easier to manage in a Git repository.
|
||||
### Example: Multiple Database Instances
|
||||
|
||||
#### Templates
|
||||
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
|
||||
```
|
||||
|
||||
For operators, Wild Cloud apps use standard configuration files. This makes modifying the app's configuration straightforward, as operators can customize their app files as needed. They can choose to manage modifications and updates directly on the configuration files using `git` tools, or they can use Kustomize patches or overlays. As a convenience for operators, when adding an app (using `wild-app-add`), the app's configurations will be compiled with the operator's Wild Cloud configuration and secrets. This results in standard Kustomize files being placed in the Wild Cloud home directory, which can then be modified as needed. This means the configuration files in this repository are actually templates, but they will be compiled into standard Kustomize files when the app is added to an operator's Wild Cloud home directory.
|
||||
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.
|
||||
|
||||
To reference operator configuration in the configuration files, use gomplate variables, such as `{{ .cloud.domain }}` for the domain name. All configuration variables you use need to exist in the operator's `config.yaml`, so they should be either standard Wild Cloud operator variables, or be defined in the app's `manifest.yaml` under `defaultConfig`.
|
||||
## Database Patterns
|
||||
|
||||
When `wild-app-add` is run, the app's Kustomize files will be compiled with the operator's Wild Cloud configuration and secrets resulting in standard Kustomize files being placed in the Wild Cloud home directory.
|
||||
### Database Initialization Jobs
|
||||
|
||||
#### External DNS Configuration
|
||||
Apps requiring PostgreSQL or MySQL should include a database initialization job (`db-init-job.yaml`):
|
||||
|
||||
Wild Cloud apps use external-dns annotations in their ingress resources to automatically manage DNS records:
|
||||
**Purpose:**
|
||||
- Creates the application database (if it doesn't exist)
|
||||
- Creates/updates the application user with proper credentials
|
||||
- Grants necessary permissions
|
||||
- Installs required database extensions (e.g., PostgreSQL's `vector`, `cube`, `earthdistance`)
|
||||
|
||||
- `external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}` - Creates a CNAME record pointing the app subdomain to the main cluster domain (e.g., `ghost.cloud.payne.io` → `cloud.payne.io`)
|
||||
- `external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"` - Disables Cloudflare proxy for direct DNS resolution
|
||||
**Implementation requirements:**
|
||||
- Use `restartPolicy: OnFailure`
|
||||
- Include in `kustomization.yaml` resources
|
||||
- Use appropriate security context (e.g., `runAsUser: 999` for PostgreSQL)
|
||||
|
||||
#### Database Initialization Jobs
|
||||
**Example apps:** `immich`, `gitea`, `openproject`, `discourse`
|
||||
|
||||
Apps that rely on PostgreSQL or MySQL databases typically need a database initialization job to create the required database and user before the main application starts. These jobs:
|
||||
### Database URL Configuration
|
||||
|
||||
- Run as Kubernetes Jobs that execute once and complete
|
||||
- Create the application database if it doesn't exist
|
||||
- Create the application user with appropriate permissions
|
||||
- Should be included in the app's `kustomization.yaml` resources list
|
||||
- Use the same database connection settings as the main application
|
||||
When apps need database URLs with embedded credentials, **always use a dedicated `dbUrl` secret**.
|
||||
|
||||
Examples of apps with db-init jobs: `gitea`, `codimd`, `immich`, `openproject`
|
||||
|
||||
##### Database URL Configuration
|
||||
|
||||
**Important:** When apps require database URLs with embedded credentials, always use a separate `dbUrl` secret instead of trying to construct the URL with environment variable substitution in Kustomize templates.
|
||||
|
||||
❌ **Wrong** (Kustomize cannot process runtime env var substitution):
|
||||
❌ **Wrong** - Kustomize cannot process runtime env var substitution:
|
||||
```yaml
|
||||
- name: DB_URL
|
||||
value: "postgresql://user:$(DB_PASSWORD)@host/db"
|
||||
value: "postgresql://user:$(DB_PASSWORD)@host/db" # This won't work!
|
||||
```
|
||||
|
||||
✅ **Correct** (Use a dedicated secret):
|
||||
✅ **Correct** - Use a dedicated secret:
|
||||
```yaml
|
||||
- name: DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: app-secrets
|
||||
key: apps.appname.dbUrl
|
||||
name: myapp-secrets
|
||||
key: apps.myapp.dbUrl
|
||||
```
|
||||
|
||||
Add `apps.appname.dbUrl` to the manifest's `requiredSecrets` and the `wild-app-add` script will generate the complete URL with embedded credentials.
|
||||
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 Context Requirements
|
||||
## Security Requirements
|
||||
|
||||
Pods must comply with Pod Security Standards. All pods should include proper security contexts to avoid deployment warnings:
|
||||
### Security Contexts
|
||||
|
||||
**All pods must comply with Pod Security Standards.** Include security contexts at both pod and container levels:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
@@ -180,8 +381,8 @@ spec:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999 # Use appropriate non-root user ID
|
||||
runAsGroup: 999 # Use appropriate group ID
|
||||
runAsUser: 999 # Use appropriate non-root UID
|
||||
runAsGroup: 999 # Use appropriate GID
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
@@ -189,77 +390,162 @@ spec:
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false # Set to true when possible
|
||||
```
|
||||
|
||||
For PostgreSQL init jobs, use `runAsUser: 999` (postgres user). For other database types, use the appropriate non-root user ID for that database container.
|
||||
**Common user IDs:**
|
||||
- PostgreSQL: `runAsUser: 999`
|
||||
- Redis: `runAsUser: 999`
|
||||
- MySQL: Consult the container image documentation
|
||||
|
||||
#### Secrets
|
||||
### Secrets Management
|
||||
|
||||
Secrets are managed in the `secrets.yaml` file in the Wild Cloud home directory. The app's `manifest.yaml` should list any required secrets under `requiredSecrets`. When the app is added, default secret values will be generated and stored in the `secrets.yaml` file. Secrets are always stored and referenced in the `apps.<app-name>.<secret-name>` yaml path. When `wild-app-deploy` is run, a Secret resource will be created in the Kubernetes cluster with the name `<app-name>-secrets`, containing all secrets defined in the manifest's `requiredSecrets` key. These secrets can then be referenced in the app's Kustomize files using a `secretKeyRef`.
|
||||
|
||||
**Important:** Always use the full dotted path from the manifest as the secret key, not just the last segment. For example, to mount a secret in an environment variable, you would use:
|
||||
Secrets are managed through two mechanisms: default secrets for the app itself and required secrets from dependencies.
|
||||
|
||||
**In manifest:**
|
||||
```yaml
|
||||
env:
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.immich.dbPassword # Use full dotted path, not just "dbPassword"
|
||||
defaultSecrets:
|
||||
key: dbPassword # This app's database password
|
||||
key: apiKey # This app's API key
|
||||
requiredSecrets:
|
||||
- db.password # Password from postgres dependency (aliased as 'db')
|
||||
- redis.auth # Auth from redis dependency
|
||||
```
|
||||
|
||||
This approach prevents naming conflicts between apps and makes secret keys more descriptive and consistent with the `secrets.yaml` structure.
|
||||
**In resources:**
|
||||
```yaml
|
||||
env:
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: myapp-secrets
|
||||
key: dbPassword # Points to the default secret
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: myapp-secrets
|
||||
key: db.password # Points to the required secret
|
||||
```
|
||||
|
||||
`secrets.yaml` files should not be checked in to a git repository and are ignored by default in Wild Cloud home directories. Checked in kustomize files should only reference secrets, not compile them.
|
||||
**Secret workflow:**
|
||||
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>`
|
||||
|
||||
## App Lifecycle
|
||||
**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.
|
||||
|
||||
Apps in Wild Cloud are managed by operators using a set of commands run from their Wild Cloud home directory.
|
||||
**Important:** Never commit `secrets.yaml` to Git. Templates should only reference secrets, never contain actual secret values.
|
||||
|
||||
- `wild-apps-list`: Lists all available apps.
|
||||
- `wild-app-add <app-name>`: Reads the app from the Wild Cloud repository, adds the app manifest to your Wild Cloud home `apps` directory, updates missing values in `config.yaml` and `secrets.yaml` with the app's default configurations, and compiles the app's Kustomize files.
|
||||
- `wild-app-deploy <app-name>`: Deploys the app to your Wild Cloud.
|
||||
## Converting from Helm Charts
|
||||
|
||||
## Contributing
|
||||
Wild Cloud prefers Kustomize over Helm for simplicity and Git-friendliness. When an official Helm chart exists, convert it rather than creating manifests from scratch.
|
||||
|
||||
If you would like to contribute an app to the Wild Cloud, issue a pull request with the app's directory containing the `manifest.yaml` file and any necessary Kustomize files. Ensure that your app follows the structure outlined above.
|
||||
|
||||
## Tips for App Packagers
|
||||
|
||||
### Converting from Helm Charts
|
||||
|
||||
Wild Cloud apps use Kustomize as kustomize files are simpler, more transparent, and easier to manage in a Git repository than Helm charts.
|
||||
|
||||
IMPORTANT! If an official Helm chart is available for an app, it is recommended to convert that chart to a Wild Cloud app rather than creating a new app from scratch.
|
||||
|
||||
If you have a Helm chart that you want to convert to a Wild Cloud app, the following example steps can simplify the process for you:
|
||||
### Conversion Process
|
||||
|
||||
1. **Extract and render the Helm chart:**
|
||||
```bash
|
||||
helm fetch --untar --untardir charts nginx-stable/nginx-ingress
|
||||
helm template --output-dir base --namespace ingress --values values.yaml ingress-controller charts/nginx-ingress
|
||||
cat <<EOF > base/nginx-ingress/namespace.yaml
|
||||
helm fetch --untar --untardir charts repo/chart-name
|
||||
helm template --output-dir base --namespace myapp --values values.yaml myapp charts/chart-name
|
||||
cd base/chart-name
|
||||
```
|
||||
|
||||
2. **Add namespace manifest:**
|
||||
```bash
|
||||
cat <<EOF > namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ingress
|
||||
name: myapp
|
||||
EOF
|
||||
cd base/nginx-ingress
|
||||
```
|
||||
|
||||
3. **Create kustomization:**
|
||||
```bash
|
||||
kustomize create --autodetect
|
||||
```
|
||||
|
||||
After running these commands against your own Helm chart, you will have a Kustomize directory structure that can be used as a Wild Cloud app. All you need to do then, usually, is:
|
||||
4. **Convert to Wild Cloud format:**
|
||||
- Create `manifest.yaml` with app metadata
|
||||
- Replace hardcoded values with gomplate variables (e.g., `{{ .cloud.domain }}`)
|
||||
- Update secrets to use dotted-path convention
|
||||
- Replace Helm labels with Wild Cloud standard labels
|
||||
- Add `includeSelectors: true` to kustomization
|
||||
- Use simple component labels (`component: web`, not `app.kubernetes.io/name`)
|
||||
- Add security contexts to all pods
|
||||
- Add external-dns annotations to ingresses
|
||||
|
||||
- add an app manifest (a `manifest.yaml` file).
|
||||
- replace any hardcoded operator values with Wild Cloud operator variables, such as `{{ .cloud.domain }}` for the domain name.
|
||||
- modify how secrets are referenced in the Kustomize files (see above)
|
||||
- update labels and selectors to use the Wild Cloud standard:
|
||||
- Replace complex Helm labels (like `app.kubernetes.io/name`, `app.kubernetes.io/instance`) with simple component labels
|
||||
- Use `component: web`, `component: worker`, etc. in selectors and pod template labels
|
||||
- Let Kustomize handle the common labels (`app`, `managedBy`, `partOf`) automatically
|
||||
- remove any Helm-specific labels from the Kustomize files, as Wild Cloud apps do not use Helm labels.
|
||||
### Example Label Migration
|
||||
|
||||
❌ **Helm style:**
|
||||
```yaml
|
||||
labels:
|
||||
app.kubernetes.io/name: myapp
|
||||
app.kubernetes.io/instance: release-name
|
||||
app.kubernetes.io/component: server
|
||||
```
|
||||
|
||||
✅ **Wild Cloud style:**
|
||||
```yaml
|
||||
# In kustomization.yaml (applied automatically)
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: myapp
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
|
||||
# In individual resources
|
||||
labels:
|
||||
component: server # Simple component label
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
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`
|
||||
- [ ] `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`
|
||||
- [ ] Namespace matches app name
|
||||
- [ ] All resource files listed under `resources:`
|
||||
|
||||
- [ ] **Resources**
|
||||
- [ ] Security contexts on all pods (both pod-level and container-level)
|
||||
- [ ] Simple component labels, no Helm-style labels
|
||||
- [ ] Ingresses include external-dns annotations
|
||||
- [ ] Database apps include init jobs (if applicable)
|
||||
|
||||
- [ ] **Testing**
|
||||
- [ ] Templates compile successfully with sample config
|
||||
- [ ] App deploys without errors in test cluster
|
||||
- [ ] All dependencies work correctly
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! To contribute:
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a new app directory following the structure above
|
||||
3. Test your app thoroughly
|
||||
4. Submit a pull request with:
|
||||
- Description of the app and its purpose
|
||||
- Any special configuration notes
|
||||
- Dependencies required
|
||||
|
||||
## Notice: Third-Party Software
|
||||
|
||||
|
||||
97
CLAUDE.md
Normal file
97
CLAUDE.md
Normal file
@@ -0,0 +1,97 @@
|
||||
- @README.md
|
||||
- @ADDING-APPS.md
|
||||
|
||||
## Finding good sources of documentation for adding a new app to the Wild Cloud Directory
|
||||
|
||||
- A good starting point is to:
|
||||
- look for an app's official documentation on running in Kubernetes or a containerized environment
|
||||
- look at the app's official docker compose
|
||||
- look for official or common helm packages
|
||||
- look at the source code repository for the app
|
||||
|
||||
These sources will oftentimes not use the latest version. Check to make sure you are adding the latest app version.
|
||||
|
||||
- Don't use helm for the final deployement, however it is a good idea to unpack a helm package to investigate best practices and to overcome tricky configurations
|
||||
|
||||
## App package development lifecycle
|
||||
|
||||
- when developing a new app, test on the `test-cloud` instance in the `/home/payne/repos/wild-cloud-dev/wild-cloud-redmond-data` wild data dir. Prefer the `wild` CLI for managing app lifecycle as it takes care of copying and compiling kustomize templates. Example commands:
|
||||
- `wild instance use test-cloud`
|
||||
- `wild app add <app>`
|
||||
- `wild app deploy <app>`
|
||||
- `wild app delete`
|
||||
- But you can always use `kubectl` directly. Just make sure you use the `test-cloud` `kubeconfig` and, when applying resources, use the `-k` flag so kustomize templates get copied.
|
||||
- kubectl --kubeconfig=/home/payne/repos/wild-cloud-redmond-data/instances/test-cloud/kubeconfig get pods -n <app>
|
||||
- While we test in `/home/payne/repos/wild-cloud-dev/wild-cloud-redmond-data/instances/test-cloud`, the final product (our app package) must be in `/home/payne/repos/wild-cloud-dev/wild-directory`. Always make sure that, in the end, whatever is in `wild-directory` can be deployed into `test-cloud`.
|
||||
- If you run into difficulties, revisit helm charts, docker compose files, and most importantly, the source code to determine how existing deployments are functioning correctly.
|
||||
|
||||
## App Icons
|
||||
|
||||
Icon Search Process
|
||||
|
||||
1. Primary Source: Dashboard Icons (homarr-labs)
|
||||
|
||||
- URL Pattern: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/{app-name}.svg
|
||||
- Why: Curated collection specifically for self-hosted apps, consistent style, reliable CDN
|
||||
- Format: SVG (preferred for scalability)
|
||||
- Check: Visit https://dashboardicons.com/icons/{app-name} to see if it exists
|
||||
|
||||
2. Fallback: Vector Logo Zone
|
||||
|
||||
- URL Pattern: https://www.vectorlogo.zone/logos/{app-name}/
|
||||
- Why: Large collection of official logos in standardized formats
|
||||
- Options:
|
||||
- {app-name}-icon.svg (logo only, no text)
|
||||
- {app-name}-ar21.svg (logo with text)
|
||||
- Best for: Apps not in Dashboard Icons
|
||||
|
||||
3. Official Sources
|
||||
|
||||
- Check the app's official website for logo/brand pages
|
||||
- Look for /brand, /logos, /assets paths
|
||||
- Example: https://www.loomio.com/brand/logo_gold.svg
|
||||
|
||||
4. Community CDNs
|
||||
|
||||
- LobeHub Icons: For AI/LLM tools (vLLM, etc.)
|
||||
- https://unpkg.com/@lobehub/icons-static-png@latest/dark/{app-name}.png
|
||||
- Simple Icons: For popular brands
|
||||
- https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/{app-name}.svg
|
||||
|
||||
Validation Process
|
||||
|
||||
For each candidate URL:
|
||||
1. Test the URL using WebFetch to confirm it returns a valid image
|
||||
2. Verify format: SVG preferred, PNG acceptable
|
||||
3. Check content: Logo-only preferred over logo+text
|
||||
4. Confirm it works: Actually loads in a browser/img tag
|
||||
|
||||
Icon Preferences
|
||||
|
||||
1. Format: SVG > PNG (for scalability)
|
||||
2. Style: Logo only > Logo with text
|
||||
3. Source: Official CDN > Community CDN > Direct hosting
|
||||
4. Consistency: Similar visual style across all apps
|
||||
|
||||
Search Strategy
|
||||
|
||||
### For each app:
|
||||
1. Try Dashboard Icons first: dashboardicons.com/icons/{app}
|
||||
2. If not found, try Vector Logo Zone: vectorlogo.zone/logos/{app}
|
||||
3. If still not found, search: "{app} official logo CDN SVG URL"
|
||||
4. Validate the URL actually works
|
||||
5. Prefer icon-only versions when multiple options exist
|
||||
|
||||
This systematic approach ensures consistent, reliable, and high-quality icons across all Wild Cloud apps.
|
||||
|
||||
## IMPORTANT
|
||||
|
||||
- NEVER under any circumstances work on any Wild Cloud instance other than `test-cloud`
|
||||
- `secrets.yaml` is NOT checked in and any values unrelated to your current task should be preserved
|
||||
- When adding a new app to the directory, check to make sure you are adding the latest app version.
|
||||
- set `namespace` in default config and refer to it using `{{ .namespace }}` in the `namespace.yaml` definition file and the `kustomization.yaml` file.
|
||||
- If the app requires a specific platform (amd64, arm64, etc.), make sure it is called out in the manifest and the k8s tags are set
|
||||
- Use traefik for ingress.
|
||||
- Use postgres for database if supported.
|
||||
- Keep config key naming (including nesting) consistent with other apps.
|
||||
|
||||
310
README.md
310
README.md
@@ -1,265 +1,107 @@
|
||||
# Wild Cloud-maintained apps
|
||||
# Wild Cloud Apps Directory
|
||||
|
||||
This is the Wild Cloud apps repository.
|
||||
This is the official Wild Cloud apps repository containing a curated collection of self-hosted applications that can be deployed to your Wild Cloud cluster.
|
||||
|
||||
This repository contains a collection of apps that can be deployed using Wild Cloud scripts. Wild Cloud apps follow a specific structure and naming convention to ensure compatibility with the Wild Cloud ecosystem.
|
||||
## What are Wild Cloud Apps?
|
||||
|
||||
## App Structure
|
||||
Wild Cloud apps are pre-packaged Kubernetes applications using Kustomize that follow standardized conventions for configuration, secrets management, and deployment. Each app includes:
|
||||
|
||||
Each subdirectory in this directory represents a Wild Cloud app. Each app directory contains an "app manifest" (`manifest.yaml`), a "kustomization" (`kustomization.yaml`), and one or more "configurations" (yaml files containing definitions/configurations of Kubernetes objects/resources).
|
||||
- **App manifest** (`manifest.yaml`) - Metadata, dependencies, and default configuration
|
||||
- **Kustomization** (`kustomization.yaml`) - Kubernetes resource definitions
|
||||
- **Configuration templates** - Deployments, services, ingresses, and other resources
|
||||
|
||||
### App Manifest
|
||||
Apps use gomplate templates that compile with your Wild Cloud configuration when added, making them easy to customize while maintaining a consistent deployment experience.
|
||||
|
||||
The required `manifest.yaml` file contains metadata about the app.
|
||||
## Using Wild Cloud Apps
|
||||
|
||||
This is the contents of an example `manifest.yaml` file for an app named "immich":
|
||||
### Web App (Recommended)
|
||||
|
||||
```yaml
|
||||
name: 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
|
||||
defaultConfig:
|
||||
serverImage: ghcr.io/immich-app/immich-server:release
|
||||
mlImage: ghcr.io/immich-app/immich-machine-learning:release
|
||||
timezone: UTC
|
||||
serverPort: 2283
|
||||
mlPort: 3003
|
||||
storage: 250Gi
|
||||
cacheStorage: 10Gi
|
||||
redisHostname: redis.redis.svc.cluster.local
|
||||
dbHostname: postgres.postgres.svc.cluster.local
|
||||
dbUsername: immich
|
||||
domain: immich.{{ .cloud.domain }}
|
||||
requiredSecrets:
|
||||
- apps.immich.dbPassword
|
||||
- apps.postgres.password
|
||||
The easiest way to manage apps is through the Wild Cloud web app:
|
||||
|
||||
1. **Navigate to Apps**: Access your instance in the web app and go to the Apps page
|
||||
2. **Browse Available Apps**: View all available apps from the Wild Directory with descriptions and icons
|
||||
3. **Add an App**: Click on an app to view details and click "Add" to:
|
||||
- Copy the app manifest to your instance's `apps` directory
|
||||
- Add default configuration to your `config.yaml`
|
||||
- Generate required secrets in your `secrets.yaml`
|
||||
- Compile templates with your configuration
|
||||
4. **Configure** (optional): Modify app settings before deployment
|
||||
5. **Deploy**: Click "Deploy" to apply the app to your cluster
|
||||
6. **Monitor**: View app status, logs, and manage deployments
|
||||
|
||||
### CLI
|
||||
|
||||
For terminal-based workflows, use the Wild CLI:
|
||||
|
||||
```bash
|
||||
# List available apps
|
||||
wild app list
|
||||
|
||||
# Add app to instance
|
||||
wild app add <app-name>
|
||||
|
||||
# Deploy app
|
||||
wild app deploy <app-name>
|
||||
|
||||
# List deployed apps
|
||||
wild app list-deployed
|
||||
|
||||
# Get app status
|
||||
wild app status <app-name>
|
||||
|
||||
# Delete app
|
||||
wild app delete <app-name>
|
||||
```
|
||||
|
||||
Explanation of the fields:
|
||||
The CLI connects to the Wild Central API (default: `http://localhost:5055`). You can override with `--daemon-url` or set `WILD_API_URI` environment variable.
|
||||
|
||||
- `name`: The name of the app, used for identification.
|
||||
- `description`: A brief description of the app.
|
||||
- `version`: The version of the app. This should generally follow the versioning scheme of the app itself.
|
||||
- `icon`: A URL to an icon representing the app.
|
||||
- `requires`: A list of other apps that this app depends on. Each entry should be the name of another app.
|
||||
- `defaultConfig`: A set of default configuration values for the app. When an app is added using `wild-app-add`, these values will be added to the Wild Cloud `config.yaml` file.
|
||||
- `requiredSecrets`: A list of secrets that must be set in the Wild Cloud `secrets.yaml` file for the app to function properly. These secrets are typically sensitive information like database passwords or API keys. Keys with random values will be generated automatically when the app is added.
|
||||
### API
|
||||
|
||||
### Kustomization
|
||||
For automation or advanced workflows, use the Wild Central API:
|
||||
|
||||
Each app directory should also contain a `kustomization.yaml` file. This file defines how the app's Kubernetes resources are built and deployed. It can include references to other Kustomize files, patches, and configurations.
|
||||
```bash
|
||||
# List available apps
|
||||
curl http://localhost:5055/api/v1/apps/available
|
||||
|
||||
Here is an example `kustomization.yaml` file for the "immich" app:
|
||||
# Get app details
|
||||
curl http://localhost:5055/api/v1/apps/available/{app-name}
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: immich
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: immich
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- deployment-server.yaml
|
||||
- deployment-machine-learning.yaml
|
||||
- deployment-microservices.yaml
|
||||
- ingress.yaml
|
||||
- namespace.yaml
|
||||
- pvc.yaml
|
||||
- service.yaml
|
||||
- db-init-job.yaml
|
||||
```
|
||||
# List deployed apps for an instance
|
||||
curl http://localhost:5055/api/v1/instances/{instance-name}/apps
|
||||
|
||||
Kustomization requirements:
|
||||
# Add app to configuration
|
||||
curl -X POST http://localhost:5055/api/v1/instances/{instance-name}/apps \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "app-name", "config": {}}'
|
||||
|
||||
- Every Wild Cloud kustomization should include the Wild Cloud labels in its `kustomization.yaml` file. This allows the Wild Cloud to identify and manage the app correctly. The labels should be defined under the `labels` key, as shown in the example above.
|
||||
- The `app` label and `namespace` keys should the app's name/directory.
|
||||
# Deploy app
|
||||
curl -X POST http://localhost:5055/api/v1/instances/{instance-name}/apps/{app-name}/deploy
|
||||
|
||||
#### Standard Wild Cloud Labels
|
||||
|
||||
Wild Cloud uses a consistent labeling strategy across all apps:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: myapp # The app name (matches directory)
|
||||
managedBy: kustomize # Managed by Kustomize
|
||||
partOf: wild-cloud # Part of Wild Cloud ecosystem
|
||||
# Get app status
|
||||
curl http://localhost:5055/api/v1/instances/{instance-name}/apps/{app-name}/status
|
||||
```
|
||||
|
||||
The `includeSelectors: true` setting automatically applies these labels to all resources AND their selectors, which means:
|
||||
### How It Works
|
||||
|
||||
1. **Resource labels** - All resources get the standard Wild Cloud labels
|
||||
2. **Selector labels** - All selectors automatically include these labels for robust selection
|
||||
1. **Add an app**: The system compiles the app's templates using your Wild Cloud configuration (domain, email, etc.) and creates standard Kustomize files in your instance directory
|
||||
2. **Customize** (optional): Modify app configuration through the web app, CLI, or by editing files directly
|
||||
3. **Deploy**: The system applies the Kustomize configuration to your cluster
|
||||
4. **Manage**: Track changes with Git, update configurations, and monitor app health
|
||||
|
||||
This allows individual resources to use simple, component-specific selectors:
|
||||
### Dependencies
|
||||
|
||||
```yaml
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
```
|
||||
Some apps require other apps to function. For example:
|
||||
- **Immich** requires PostgreSQL and Redis
|
||||
- **OpenProject** requires PostgreSQL and Memcached
|
||||
- **Gitea** requires PostgreSQL
|
||||
|
||||
Which Kustomize automatically expands to:
|
||||
|
||||
```yaml
|
||||
selector:
|
||||
matchLabels:
|
||||
app: myapp
|
||||
component: web
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
|
||||
Wild Cloud apps use Kustomize as kustomize files are simple, transparent, and easier to manage in a Git repository.
|
||||
|
||||
#### Templates
|
||||
|
||||
For operators, Wild Cloud apps use standard configuration files. This makes modifying the app's configuration straightforward, as operators can customize their app files as needed. They can choose to manage modifications and updates directly on the configuration files using `git` tools, or they can use Kustomize patches or overlays. As a convenience for operators, when adding an app (using `wild-app-add`), the app's configurations will be compiled with the operator's Wild Cloud configuration and secrets. This results in standard Kustomize files being placed in the Wild Cloud home directory, which can then be modified as needed. This means the configuration files in this repository are actually templates, but they will be compiled into standard Kustomize files when the app is added to an operator's Wild Cloud home directory.
|
||||
|
||||
To reference operator configuration in the configuration files, use gomplate variables, such as `{{ .cloud.domain }}` for the domain name. All configuration variables you use need to exist in the operator's `config.yaml`, so they should be either standard Wild Cloud operator variables, or be defined in the app's `manifest.yaml` under `defaultConfig`.
|
||||
|
||||
When `wild-app-add` is run, the app's Kustomize files will be compiled with the operator's Wild Cloud configuration and secrets resulting in standard Kustomize files being placed in the Wild Cloud home directory.
|
||||
|
||||
#### External DNS Configuration
|
||||
|
||||
Wild Cloud apps use external-dns annotations in their ingress resources to automatically manage DNS records:
|
||||
|
||||
- `external-dns.alpha.kubernetes.io/target: {{ .cloud.domain }}` - Creates a CNAME record pointing the app subdomain to the main cluster domain (e.g., `ghost.cloud.payne.io` → `cloud.payne.io`)
|
||||
- `external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"` - Disables Cloudflare proxy for direct DNS resolution
|
||||
|
||||
#### Database Initialization Jobs
|
||||
|
||||
Apps that rely on PostgreSQL or MySQL databases typically need a database initialization job to create the required database and user before the main application starts. These jobs:
|
||||
|
||||
- Run as Kubernetes Jobs that execute once and complete
|
||||
- Create the application database if it doesn't exist
|
||||
- Create the application user with appropriate permissions
|
||||
- Should be included in the app's `kustomization.yaml` resources list
|
||||
- Use the same database connection settings as the main application
|
||||
|
||||
Examples of apps with db-init jobs: `gitea`, `codimd`, `immich`, `openproject`
|
||||
|
||||
##### Database URL Configuration
|
||||
|
||||
**Important:** When apps require database URLs with embedded credentials, always use a separate `dbUrl` secret instead of trying to construct the URL with environment variable substitution in Kustomize templates.
|
||||
|
||||
❌ **Wrong** (Kustomize cannot process runtime env var substitution):
|
||||
```yaml
|
||||
- name: DB_URL
|
||||
value: "postgresql://user:$(DB_PASSWORD)@host/db"
|
||||
```
|
||||
|
||||
✅ **Correct** (Use a dedicated secret):
|
||||
```yaml
|
||||
- name: DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: app-secrets
|
||||
key: apps.appname.dbUrl
|
||||
```
|
||||
|
||||
Add `apps.appname.dbUrl` to the manifest's `requiredSecrets` and the `wild-app-add` script will generate the complete URL with embedded credentials.
|
||||
|
||||
##### Security Context Requirements
|
||||
|
||||
Pods must comply with Pod Security Standards. All pods should include proper security contexts to avoid deployment warnings:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999 # Use appropriate non-root user ID
|
||||
runAsGroup: 999 # Use appropriate group ID
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: container-name
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: false # Set to true when possible
|
||||
```
|
||||
|
||||
For PostgreSQL init jobs, use `runAsUser: 999` (postgres user). For other database types, use the appropriate non-root user ID for that database container.
|
||||
|
||||
#### Secrets
|
||||
|
||||
Secrets are managed in the `secrets.yaml` file in the Wild Cloud home directory. The app's `manifest.yaml` should list any required secrets under `requiredSecrets`. When the app is added, default secret values will be generated and stored in the `secrets.yaml` file. Secrets are always stored and referenced in the `apps.<app-name>.<secret-name>` yaml path. When `wild-app-deploy` is run, a Secret resource will be created in the Kubernetes cluster with the name `<app-name>-secrets`, containing all secrets defined in the manifest's `requiredSecrets` key. These secrets can then be referenced in the app's Kustomize files using a `secretKeyRef`.
|
||||
|
||||
**Important:** Always use the full dotted path from the manifest as the secret key, not just the last segment. For example, to mount a secret in an environment variable, you would use:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: immich-secrets
|
||||
key: apps.immich.dbPassword # Use full dotted path, not just "dbPassword"
|
||||
```
|
||||
|
||||
This approach prevents naming conflicts between apps and makes secret keys more descriptive and consistent with the `secrets.yaml` structure.
|
||||
|
||||
`secrets.yaml` files should not be checked in to a git repository and are ignored by default in Wild Cloud home directories. Checked in kustomize files should only reference secrets, not compile them.
|
||||
|
||||
## App Lifecycle
|
||||
|
||||
Apps in Wild Cloud are managed by operators using a set of commands run from their Wild Cloud home directory.
|
||||
|
||||
- `wild-apps-list`: Lists all available apps.
|
||||
- `wild-app-add <app-name>`: Reads the app from the Wild Cloud repository, adds the app manifest to your Wild Cloud home `apps` directory, updates missing values in `config.yaml` and `secrets.yaml` with the app's default configurations, and compiles the app's Kustomize files.
|
||||
- `wild-app-deploy <app-name>`: Deploys the app to your Wild Cloud.
|
||||
When you add an app, check its `requires` field in the manifest and ensure dependencies are added first.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you would like to contribute an app to the Wild Cloud, issue a pull request with the app's directory containing the `manifest.yaml` file and any necessary Kustomize files. Ensure that your app follows the structure outlined above.
|
||||
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.
|
||||
|
||||
## Tips for App Packagers
|
||||
|
||||
### Converting from Helm Charts
|
||||
|
||||
Wild Cloud apps use Kustomize as kustomize files are simpler, more transparent, and easier to manage in a Git repository than Helm charts.
|
||||
|
||||
IMPORTANT! If an official Helm chart is available for an app, it is recommended to convert that chart to a Wild Cloud app rather than creating a new app from scratch.
|
||||
|
||||
If you have a Helm chart that you want to convert to a Wild Cloud app, the following example steps can simplify the process for you:
|
||||
|
||||
```bash
|
||||
helm fetch --untar --untardir charts nginx-stable/nginx-ingress
|
||||
helm template --output-dir base --namespace ingress --values values.yaml ingress-controller charts/nginx-ingress
|
||||
cat <<EOF > base/nginx-ingress/namespace.yaml
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ingress
|
||||
EOF
|
||||
cd base/nginx-ingress
|
||||
kustomize create --autodetect
|
||||
```
|
||||
|
||||
After running these commands against your own Helm chart, you will have a Kustomize directory structure that can be used as a Wild Cloud app. All you need to do then, usually, is:
|
||||
|
||||
- add an app manifest (a `manifest.yaml` file).
|
||||
- replace any hardcoded operator values with Wild Cloud operator variables, such as `{{ .cloud.domain }}` for the domain name.
|
||||
- modify how secrets are referenced in the Kustomize files (see above)
|
||||
- update labels and selectors to use the Wild Cloud standard:
|
||||
- Replace complex Helm labels (like `app.kubernetes.io/name`, `app.kubernetes.io/instance`) with simple component labels
|
||||
- Use `component: web`, `component: worker`, etc. in selectors and pod template labels
|
||||
- Let Kustomize handle the common labels (`app`, `managedBy`, `partOf`) automatically
|
||||
- remove any Helm-specific labels from the Kustomize files, as Wild Cloud apps do not use Helm labels.
|
||||
Contributions are welcome via pull requests. Ensure your app follows the Wild Cloud conventions and includes all required files.
|
||||
|
||||
## Notice: Third-Party Software
|
||||
|
||||
|
||||
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`. The `wild-app-deploy` command 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:
|
||||
@@ -41,13 +41,13 @@ Gitea manages its own `app.ini` file on persistent storage for:
|
||||
|
||||
### Non-Secret Settings
|
||||
1. Edit `gitea.env` with your changes
|
||||
2. Run `wild-app-deploy gitea` to apply changes
|
||||
2. Deploy the app via the web app, CLI, or API to apply changes
|
||||
3. Pod will restart and pick up new configuration
|
||||
|
||||
### Secret Settings
|
||||
1. Edit `secrets.yaml` with your secret values
|
||||
2. Ensure the secret key is listed in `manifest.yaml` under `requiredSecrets`
|
||||
3. Run `wild-app-deploy gitea` - this will automatically update the `gitea-secrets` secret and restart the pod
|
||||
2. Ensure the secret key is listed in `manifest.yaml` under `defaultSecrets`
|
||||
3. Deploy the app via the web app, CLI, or API - this will automatically update the `gitea-secrets` secret and restart the pod
|
||||
|
||||
### Web UI Changes
|
||||
Configuration changes made through Gitea's admin web interface are automatically persisted to the `app.ini` file on persistent storage and will survive pod restarts.
|
||||
|
||||
@@ -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
|
||||
30
mailu/configmap.yaml
Normal file
30
mailu/configmap.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: mailu-config
|
||||
namespace: {{ .namespace }}
|
||||
data:
|
||||
DOMAIN: "{{ .domain }}"
|
||||
HOSTNAMES: "{{ .hostname }}"
|
||||
POSTMASTER: "admin"
|
||||
TZ: "{{ .timezone }}"
|
||||
TLS_FLAVOR: "cert"
|
||||
MESSAGE_SIZE_LIMIT: "50000000"
|
||||
MESSAGE_RATELIMIT: "200/day"
|
||||
RELAYNETS: ""
|
||||
RELAYHOST: "{{ .relayHost }}"
|
||||
RELAYPORT: "{{ .relayPort }}"
|
||||
FETCHMAIL_ENABLED: "false"
|
||||
RECIPIENT_DELIMITER: "+"
|
||||
DMARC_RUA: "admin"
|
||||
DMARC_RUF: "admin"
|
||||
WELCOME: "false"
|
||||
WELCOME_SUBJECT: "Welcome to your new email account"
|
||||
WELCOME_BODY: "Welcome! You can now use your email account."
|
||||
ADMIN: "true"
|
||||
WEB_ADMIN: "/admin"
|
||||
WEB_WEBMAIL: "/webmail"
|
||||
WEBMAIL: "roundcube"
|
||||
SITENAME: "Mailu"
|
||||
WEBSITE: "https://{{ .hostname }}"
|
||||
LOG_LEVEL: "{{ .logLevel }}"
|
||||
103
mailu/deployment-admin.yaml
Normal file
103
mailu/deployment-admin.yaml
Normal file
@@ -0,0 +1,103 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: admin
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: admin
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: admin
|
||||
spec:
|
||||
dnsPolicy: "None"
|
||||
dnsConfig:
|
||||
nameservers:
|
||||
- {{ .unbound.ip }}
|
||||
searches:
|
||||
- {{ .namespace }}.svc.cluster.local
|
||||
- svc.cluster.local
|
||||
- cluster.local
|
||||
options:
|
||||
- name: ndots
|
||||
value: "5"
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /data /dkim']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: admin
|
||||
mountPath: /data
|
||||
- name: data
|
||||
subPath: dkim
|
||||
mountPath: /dkim
|
||||
containers:
|
||||
- name: admin
|
||||
image: {{ .images.admin }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: secretKey
|
||||
- name: REDIS_ADDRESS
|
||||
value: "{{ .redis.host }}"
|
||||
- name: I_KNOW_MY_SETUP_DOESNT_FIT_REQUIREMENTS_AND_WONT_FILE_ISSUES_WITHOUT_PATCHES
|
||||
value: "true"
|
||||
- name: INITIAL_ADMIN_ACCOUNT
|
||||
value: "{{ .initialAccount.username }}"
|
||||
- name: INITIAL_ADMIN_DOMAIN
|
||||
value: "{{ .initialAccount.domain }}"
|
||||
- name: INITIAL_ADMIN_PW
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: initialAccountPassword
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: admin
|
||||
mountPath: /data
|
||||
- name: data
|
||||
subPath: dkim
|
||||
mountPath: /dkim
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
70
mailu/deployment-dovecot.yaml
Normal file
70
mailu/deployment-dovecot.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dovecot
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: dovecot
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: dovecot
|
||||
spec:
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /data /mail']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mail
|
||||
mountPath: /mail
|
||||
- name: data
|
||||
subPath: dovecot
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: dovecot
|
||||
image: {{ .images.dovecot }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: imap
|
||||
containerPort: 143
|
||||
- name: imaps
|
||||
containerPort: 993
|
||||
- name: pop3
|
||||
containerPort: 110
|
||||
- name: pop3s
|
||||
containerPort: 995
|
||||
- name: sieve
|
||||
containerPort: 4190
|
||||
- name: auth
|
||||
containerPort: 2102
|
||||
- name: lmtp
|
||||
containerPort: 2525
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mail
|
||||
mountPath: /mail
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
70
mailu/deployment-front.yaml
Normal file
70
mailu/deployment-front.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: front
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: front
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: front
|
||||
spec:
|
||||
containers:
|
||||
- name: front
|
||||
image: {{ .images.front }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
- NET_BIND_SERVICE
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: secretKey
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
- name: https
|
||||
containerPort: 443
|
||||
- name: smtp
|
||||
containerPort: 25
|
||||
- name: smtps
|
||||
containerPort: 465
|
||||
- name: submission
|
||||
containerPort: 587
|
||||
- name: imap
|
||||
containerPort: 143
|
||||
- name: imaps
|
||||
containerPort: 993
|
||||
- name: pop3
|
||||
containerPort: 110
|
||||
- name: pop3s
|
||||
containerPort: 995
|
||||
volumeMounts:
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: certs
|
||||
secret:
|
||||
secretName: {{ .tlsSecretName }}
|
||||
optional: true
|
||||
60
mailu/deployment-postfix.yaml
Normal file
60
mailu/deployment-postfix.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: postfix
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: postfix
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: postfix
|
||||
spec:
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /queue']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mailqueue
|
||||
mountPath: /queue
|
||||
containers:
|
||||
- name: postfix
|
||||
image: {{ .images.postfix }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
- NET_BIND_SERVICE
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: smtp
|
||||
containerPort: 25
|
||||
- name: smtps
|
||||
containerPort: 465
|
||||
- name: submission
|
||||
containerPort: 587
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mailqueue
|
||||
mountPath: /queue
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
56
mailu/deployment-redis.yaml
Normal file
56
mailu/deployment-redis.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: redis
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: redis
|
||||
image: {{ .images.redis }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 6379
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 6379
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
45
mailu/deployment-rspamd.yaml
Normal file
45
mailu/deployment-rspamd.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rspamd
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: rspamd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: rspamd
|
||||
spec:
|
||||
containers:
|
||||
- name: rspamd
|
||||
image: {{ .images.rspamd }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: REDIS_ADDRESS
|
||||
value: "{{ .redis.host }}:{{ .redis.port }}"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: rspamd
|
||||
containerPort: 11332
|
||||
- name: http
|
||||
containerPort: 11334
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: rspamd
|
||||
mountPath: /var/lib/rspamd
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
49
mailu/deployment-unbound.yaml
Normal file
49
mailu/deployment-unbound.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: unbound
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: unbound
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: unbound
|
||||
spec:
|
||||
containers:
|
||||
- name: unbound
|
||||
image: {{ .unbound.image }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
env:
|
||||
- name: UNBOUND_TLS_NAME
|
||||
value: "dns"
|
||||
ports:
|
||||
- name: dns
|
||||
containerPort: 53
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
containerPort: 53
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 53
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 53
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
61
mailu/deployment-webmail.yaml
Normal file
61
mailu/deployment-webmail.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: webmail
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: webmail
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: webmail
|
||||
spec:
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /data']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: webmail
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: webmail
|
||||
image: {{ .images.webmail }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: secretKey
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: webmail
|
||||
mountPath: /data
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
42
mailu/ingress.yaml
Normal file
42
mailu/ingress.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: mailu
|
||||
namespace: {{ .namespace }}
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .hostname }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .hostname }}
|
||||
http:
|
||||
paths:
|
||||
- path: /admin
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: admin
|
||||
port:
|
||||
number: 80
|
||||
- path: /webmail
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: webmail
|
||||
port:
|
||||
number: 80
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: front
|
||||
port:
|
||||
number: 80
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user