Compare commits

..

13 Commits

Author SHA1 Message Date
Paul Payne
945d2225a2 First version of app upgrade. 2026-05-24 04:00:07 +00:00
Paul Payne
6e1c676c09 feat: update versioning in longhorn and snapshot-controller manifests 2026-05-23 20:42:45 +00:00
Paul Payne
6b5325c6f3 Standardize config. 2026-05-23 19:51:33 +00:00
Paul Payne
e2e3f730a5 fix: remove unnecessary namespace from default configuration in README and manifest 2026-05-23 11:36:51 +00:00
Paul Payne
46002ff273 feat: update NFS documentation and manifest version for improved clarity and configuration 2026-05-23 11:25:50 +00:00
Paul Payne
acec744df8 feat: update NFS configuration and add check-nfs script for server validation 2026-05-23 11:24:21 +00:00
Paul Payne
12e87635c6 docs: Update ADDING-APPS.md to remove cloud.smtp references
SMTP config is now at apps.smtp.* via the SMTP infrastructure app,
not cloud.smtp.*. Remove the old variable listing and update the
configuration flow documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-22 23:31:14 +00:00
Paul Payne
351dff14d4 feat: add BackupTarget configuration and update kustomization to include it 2026-05-21 04:22:13 +00:00
Paul Payne
0645624ded feat: update Immich version and image tags to 1.135.3 in manifest.yaml 2026-05-21 04:21:40 +00:00
Paul Payne
afa21ef650 feat: add initial Kubernetes manifests for e2e-test-app including deployment, service, PVC, and database initialization job 2026-05-21 04:20:56 +00:00
Paul Payne
5733c20098 feat: add repair-certificates script for managing stuck certificates and ACME orders 2026-05-18 04:24:21 +00:00
Paul Payne
54abfdd469 Add kustomization.yaml for cert-manager with custom DNS settings
- Introduced a new kustomization.yaml file for cert-manager.
- Configured a patch to modify the cert-manager Deployment to use a custom DNS policy and settings.
- Set dnsPolicy to None and specified custom nameservers and search options.
2026-05-18 03:39:21 +00:00
Paul Payne
e4c24d4a8c feat: update CrowdSec and Traefik manifests; remove installation scripts and add secret management 2026-05-18 03:33:37 +00:00
120 changed files with 14538 additions and 1202 deletions

View File

@@ -31,21 +31,18 @@ requires:
alias: db # Use a different reference name in templates alias: db # Use a different reference name in templates
- name: redis # 'alias' and 'installedAs' default to 'name' value - name: redis # 'alias' and 'installedAs' default to 'name' value
defaultConfig: defaultConfig:
serverImage: ghcr.io/immich-app/immich-server:release namespace: immich
mlImage: ghcr.io/immich-app/immich-machine-learning:release externalDnsDomain: "{{ .cloud.domain }}"
timezone: UTC
serverPort: 2283
mlPort: 3003
storage: 250Gi storage: 250Gi
cacheStorage: 10Gi cacheStorage: 10Gi
redisHostname: "{{ .apps.redis.host }}" # Can reference 'requires' app configurations domain: immich.{{ .cloud.domain }}
dbHostname: "{{ .apps.pg.host }}" tlsSecretName: wildcard-wild-cloud-tls
db: # Configuration can be nested db: # Configuration can be nested
host: "{{ .apps.pg.host }}" # Can reference 'requires' app configurations
name: immich name: immich
user: immich user: immich
host: "{{ .apps.pg.host }}" redis:
port: "{{ .apps.pg.port }}" host: "{{ .apps.redis.host }}"
domain: immich.{{ .cloud.domain }}
defaultSecrets: defaultSecrets:
- key: password # Random value will be generated if empty - key: password # Random value will be generated if empty
- key: dbUrl - key: dbUrl
@@ -62,13 +59,144 @@ requiredSecrets:
| `name` | Yes | App identifier (must match directory name) | | `name` | Yes | App identifier (must match directory name) |
| `is` | Yes | Unique id for this app. Used for `requires` mapping | | `is` | Yes | Unique id for this app. Used for `requires` mapping |
| `description` | Yes | Brief app description shown in listings | | `description` | Yes | Brief app description shown in listings |
| `version` | Yes | App version (follow upstream versioning) | | `version` | Yes | App version (see Versioning Convention below) |
| `icon` | No | URL to app icon for UI display | | `icon` | No | URL to app icon for UI display |
| `requires` | No | List of dependency apps with optional aliases | | `requires` | No | List of dependency apps with optional aliases |
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` | | `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) | | `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) | | `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
### Versioning Convention
Wild Cloud uses a two-part version scheme inspired by Debian packaging: `<upstream>-<revision>`.
- **Upstream version** tracks the third-party software version (e.g., `v4.0.18`, `1.120.2`)
- **Packaging revision** tracks Wild Cloud packaging changes (template fixes, manifest cleanup, config restructuring) that don't change the upstream software version
**Examples:**
- `v4.0.18` — initial packaging of upstream v4.0.18
- `v4.0.18-1` — first packaging fix (no upstream change)
- `v4.0.18-2` — second packaging fix
- `v4.0.19` — upstream version bump, revision resets
**When to bump the packaging revision:** Any change to the app package that doesn't correspond to an upstream software update — manifest field changes, template improvements, kustomize restructuring, security context fixes, label corrections, etc.
**When to bump the upstream version:** When updating the container image tag or deploying a new version of the third-party software.
The web UI uses version comparison to detect available updates. If the deployed version differs from the wild-directory version, operators see an update indicator and can apply it from the app detail panel.
### Upgrade Metadata
Most apps can upgrade from any version to any other version directly — no special metadata is needed. The `upgrade` field is **optional** and only required when an app has breaking changes that need controlled upgrade paths.
**When you don't need `upgrade:`** Simple apps (Ghost, Redis, most stateless apps) where any version can safely replace any other version. This is the 90% case — just bump the version and the system handles it as a single-step update.
**When you need `upgrade:`** Apps with breaking database schema changes, incompatible config formats, or upstream requirements for sequential version upgrades (e.g., Discourse requires stepping through major versions).
#### The `upgrade` block
```yaml
upgrade:
from:
- version: ">=3.5.0" # Can upgrade directly from 3.5.x
- version: ">=3.4.0"
via: "3.5.3-1" # Must pass through 3.5.x first
- version: "<3.4.0"
blocked: true
notes: "Requires sequential major upgrades. See upstream docs."
preUpgrade:
backup: required # "none", "recommended", or "required"
migrations:
pre:
- migrations/pre-deploy.yaml # K8s Job YAML paths relative to app dir
post:
- migrations/post-deploy.yaml
configMigrations:
oldKeyName: newKeyName # Renames config keys automatically
```
**Fields:**
| Field | Description |
|-------|-------------|
| `from` | List of version constraint rules, evaluated in order (first match wins) |
| `from[].version` | Version constraint: `>=`, `>`, `<=`, `<`, `=`, or `>0` (matches any) |
| `from[].via` | Waypoint version in `.versions/` — upgrade must pass through this version first |
| `from[].blocked` | If true, upgrade is blocked with an error message |
| `from[].notes` | Human-readable message shown when blocked or as context |
| `preUpgrade.backup` | Backup requirement: `"required"` blocks upgrade until backup is done, `"recommended"` shows a warning |
| `migrations.pre` | K8s Job YAMLs to run before deploying each version step |
| `migrations.post` | K8s Job YAMLs to run after deploying each version step |
| `configMigrations` | Map of old config key → new config key for automatic renaming |
#### Waypoint versions (`.versions/` directory)
When an upgrade requires passing through an intermediate version, store that version's files in a `.versions/` subdirectory:
```
myapp/
├── manifest.yaml # Latest version (e.g., 3.6.0)
├── kustomization.yaml
├── *.yaml
└── .versions/
└── 3.5.3-1/ # Waypoint version
├── manifest.yaml # version: 3.5.3-1 (with its own upgrade rules)
├── kustomization.yaml
└── *.yaml
```
Each waypoint is a complete app package. The system computes a chain automatically — for example, upgrading from 3.4.0 to 3.6.0 might produce: `3.4.0 → 3.5.3-1 → 3.6.0`.
**Creating a waypoint:**
```bash
mkdir -p wild-directory/myapp/.versions
rsync -a --exclude='.versions' wild-directory/myapp/ wild-directory/myapp/.versions/3.5.3-1/
# Now update wild-directory/myapp/manifest.yaml to the new version + upgrade rules
```
#### Migration jobs
Migration jobs are K8s Job manifests that run database migrations or other one-time tasks during an upgrade step. They must be **idempotent** (safe to re-run) since a failed upgrade might be retried.
Place migration job files in the waypoint or app directory and reference them from the `migrations` field:
```yaml
# migrations/db-migrate.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: myapp-db-migrate
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: myapp:3.6.0
command: ["bundle", "exec", "rake", "db:migrate"]
```
Each migration step belongs to the version that introduces the breaking change. If version 3.6.0 requires a schema migration, the migration lives in the 3.6.0 manifest (or its waypoint), not on 3.5.x.
#### Example: simple app with waypoint
```yaml
# myapp/manifest.yaml (version 2.0.0)
version: 2.0.0
upgrade:
from:
- version: ">=1.0.0"
via: "1.0.0-1"
- version: "<1.0.0"
blocked: true
notes: "Versions before 1.0.0 are not supported"
preUpgrade:
backup: recommended
```
This creates a 2-step upgrade path: `1.x → 1.0.0-1 → 2.0.0`. The waypoint at `.versions/1.0.0-1/` has no `upgrade` block, so it accepts any version directly.
### Dependency Configuration ### Dependency Configuration
- Each dependency in `requires` can have: - Each dependency in `requires` can have:
@@ -121,15 +249,6 @@ Here's a comprehensive rundown of all config variables that get set during clust
- cloud.dockerRegistryHost - Docker registry hostname (e.g., "registry.internal.cloud2.payne.io") - 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: ###### Backup Configuration:
- cloud.backup.root - Root path for backups - cloud.backup.root - Root path for backups
@@ -214,8 +333,7 @@ Configuration Flow
- ExternalDNS → cluster.externalDns.ownerId - ExternalDNS → cluster.externalDns.ownerId
- NFS → cloud.nfs.* - NFS → cloud.nfs.*
- Docker Registry → cloud.dockerRegistryHost, cluster.dockerRegistry.storage - Docker Registry → cloud.dockerRegistryHost, cluster.dockerRegistry.storage
- SMTP → cloud.smtp.* 4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest (including SMTP as an infrastructure app at apps.smtp.*)
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest
#### Manifest App Reference Resolution: #### Manifest App Reference Resolution:

View File

@@ -1 +1,20 @@
# cert-manager
X.509 certificate management for Kubernetes using Let's Encrypt.
## Upstream
The `upstream/cert-manager.yaml` file is downloaded from the official cert-manager release:
- Source: https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml
- Version: v1.17.2
To update, download the new version and replace the file.
## DNS Configuration
The upstream cert-manager deployment is patched via kustomize overlay (`upstream/kustomization.yaml`) to use external DNS resolvers (1.1.1.1, 8.8.8.8) instead of cluster DNS. This is required for ACME DNS-01 challenge verification.
## Maintenance
The `scripts/repair-certificates.sh` script can fix stuck certificates, orphaned ACME orders, and Cloudflare DNS cleanup errors. Run it manually when certificate issuance has issues.

View File

@@ -1,233 +0,0 @@
#!/bin/bash
set -e
set -o pipefail
if [ -z "${WILD_INSTANCE}" ]; then
echo "ERROR: WILD_INSTANCE is not set"
exit 1
fi
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
if [ -z "${KUBECONFIG}" ]; then
echo "ERROR: KUBECONFIG is not set"
exit 1
fi
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CERT_MANAGER_DIR="${INSTANCE_DIR}/apps/cert-manager"
echo "=== Setting up cert-manager ==="
echo ""
#######################
# Dependencies
#######################
echo "Verifying Traefik is ready (required for cert-manager)..."
kubectl wait --for=condition=Available deployment/traefik -n traefik --timeout=60s 2>/dev/null || {
echo "WARNING: Traefik not ready, but continuing with cert-manager installation"
echo "Note: cert-manager may not work properly without Traefik"
}
if [ ! -f "${CERT_MANAGER_DIR}/kustomization.yaml" ]; then
echo "ERROR: Compiled templates not found at ${CERT_MANAGER_DIR}/"
echo "Templates should be compiled before deployment."
exit 1
fi
########################
# Kubernetes components
########################
echo "Installing cert-manager components..."
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml || \
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.17.2/cert-manager.yaml
echo "Waiting for cert-manager to be ready..."
kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=120s
kubectl wait --for=condition=Available deployment/cert-manager-cainjector -n cert-manager --timeout=120s
kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=120s
echo "Creating Cloudflare API token secret..."
SECRETS_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/secrets.yaml"
CLOUDFLARE_API_TOKEN=$(yq '.apps.cert-manager.cloudflareToken' "$SECRETS_FILE" 2>/dev/null)
CLOUDFLARE_API_TOKEN=$(echo "$CLOUDFLARE_API_TOKEN")
if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ "$CLOUDFLARE_API_TOKEN" = "null" ]; then
echo "ERROR: Cloudflare API token not found"
echo "Please set: apps.cert-manager.cloudflareToken in secrets.yaml"
exit 1
fi
kubectl create secret generic cloudflare-api-token \
--namespace cert-manager \
--from-literal=api-token="${CLOUDFLARE_API_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Verifying cert-manager webhook is fully operational..."
until kubectl get validatingwebhookconfigurations cert-manager-webhook &>/dev/null; do
echo "Waiting for cert-manager webhook to register..."
sleep 5
done
echo "Configuring cert-manager to use external DNS servers..."
kubectl patch deployment cert-manager -n cert-manager --patch '
spec:
template:
spec:
dnsPolicy: None
dnsConfig:
nameservers:
- "1.1.1.1"
- "8.8.8.8"
searches:
- cert-manager.svc.cluster.local
- svc.cluster.local
- cluster.local
options:
- name: ndots
value: "5"'
echo "Waiting for cert-manager to restart with new DNS configuration..."
kubectl rollout status deployment/cert-manager -n cert-manager --timeout=120s
########################
# Create issuers and certificates
########################
echo "Creating Let's Encrypt issuers and certificates..."
kubectl apply -k ${CERT_MANAGER_DIR}/
echo "Waiting for Let's Encrypt issuers to be ready..."
kubectl wait --for=condition=Ready clusterissuer/letsencrypt-prod --timeout=60s || echo "WARNING: Production issuer not ready, proceeding anyway..."
kubectl wait --for=condition=Ready clusterissuer/letsencrypt-staging --timeout=60s || echo "WARNING: Staging issuer not ready, proceeding anyway..."
sleep 5
######################################
# Fix stuck certificates and cleanup
######################################
needs_restart=false
echo "Checking for certificates with failed issuance attempts..."
stuck_certs=$(kubectl get certificates --all-namespaces -o json 2>/dev/null | \
jq -r '.items[] | select(.status.conditions[]? | select(.type=="Issuing" and .status=="False" and (.message | contains("404")))) | "\(.metadata.namespace) \(.metadata.name)"')
if [ -n "$stuck_certs" ]; then
echo "WARNING: Found certificates stuck with non-existent orders, recreating them..."
echo "$stuck_certs" | while read ns name; do
echo "Recreating certificate $ns/$name..."
cert_spec=$(kubectl get certificate "$name" -n "$ns" -o json | jq '.spec')
kubectl delete certificate "$name" -n "$ns"
echo "{\"apiVersion\":\"cert-manager.io/v1\",\"kind\":\"Certificate\",\"metadata\":{\"name\":\"$name\",\"namespace\":\"$ns\"},\"spec\":$cert_spec}" | kubectl apply -f -
done
needs_restart=true
sleep 5
else
echo "No certificates stuck with failed orders"
fi
echo "Checking for orphaned ACME orders..."
orphaned_orders=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \
grep -E "failed to retrieve the ACME order.*404" 2>/dev/null | \
sed -n 's/.*resource_name="\([^"]*\)".*/\1/p' | \
sort -u || true)
if [ -n "$orphaned_orders" ]; then
echo "WARNING: Found orphaned ACME orders from logs"
for order in $orphaned_orders; do
echo "Deleting orphaned order: $order"
orders_found=$(kubectl get orders --all-namespaces 2>/dev/null | grep "$order" 2>/dev/null || true)
if [ -n "$orders_found" ]; then
echo "$orders_found" | while read ns name rest; do
kubectl delete order "$name" -n "$ns" 2>/dev/null || true
done
fi
done
needs_restart=true
else
echo "No orphaned orders found in logs"
fi
echo "Checking for Cloudflare DNS cleanup errors..."
cloudflare_errors=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \
grep -c "Error: 7003.*Could not route" 2>/dev/null || echo "0")
if [ "$cloudflare_errors" -gt "0" ]; then
echo "WARNING: Found $cloudflare_errors Cloudflare DNS cleanup errors (stale DNS record references)"
echo "Deleting stuck challenges and orders to allow fresh start"
kubectl delete challenges --all -n cert-manager 2>/dev/null || true
kubectl delete orders --all -n cert-manager 2>/dev/null || true
needs_restart=true
else
echo "No Cloudflare DNS cleanup errors"
fi
if [ "$needs_restart" = true ]; then
echo "Restarting cert-manager to clear internal state..."
kubectl rollout restart deployment cert-manager -n cert-manager
kubectl rollout status deployment/cert-manager -n cert-manager --timeout=120s
echo "Waiting for cert-manager to recreate fresh challenges..."
sleep 15
else
echo "No restart needed - cert-manager state is clean"
fi
#########################
# Final checks
#########################
echo "Waiting for wildcard certificates to be ready (this may take several minutes)..."
wait_for_cert() {
local cert_name="$1"
local timeout=300
local elapsed=0
echo " Checking $cert_name..."
while [ $elapsed -lt $timeout ]; do
if kubectl get certificate "$cert_name" -n cert-manager -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q "True"; then
echo " $cert_name is ready"
return 0
fi
if [ $((elapsed % 30)) -eq 0 ] && [ $elapsed -gt 0 ]; then
local status=$(kubectl get certificate "$cert_name" -n cert-manager -o jsonpath='{.status.conditions[?(@.type=="Ready")].message}' 2>/dev/null || echo "Waiting...")
echo " Still waiting for $cert_name... ($elapsed/${timeout}s) - $status"
fi
sleep 5
elapsed=$((elapsed + 5))
done
echo " WARNING: Timeout waiting for $cert_name (will continue anyway)"
return 1
}
wait_for_cert "wildcard-internal-wild-cloud"
wait_for_cert "wildcard-wild-cloud"
echo "Performing final cert-manager health check..."
failed_certs=$(kubectl get certificates --all-namespaces -o json 2>/dev/null | jq -r '.items[] | select(.status.conditions[]? | select(.type=="Ready" and .status!="True")) | "\(.metadata.namespace)/\(.metadata.name)"' | wc -l)
if [ "$failed_certs" -gt 0 ]; then
echo "WARNING: Found $failed_certs certificates not in Ready state"
echo "Check certificate status with: kubectl get certificates --all-namespaces"
echo "Check cert-manager logs with: kubectl logs -n cert-manager deployment/cert-manager"
else
echo "All certificates are in Ready state"
fi
echo ""
echo "cert-manager setup complete!"
echo ""
echo "To verify the installation:"
echo " kubectl get certificates --all-namespaces"
echo " kubectl get clusterissuers"

View File

@@ -11,5 +11,20 @@ defaultConfig:
internalDomain: "{{ .cloud.internalDomain }}" internalDomain: "{{ .cloud.internalDomain }}"
email: "{{ .operator.email }}" email: "{{ .operator.email }}"
cloudflareDomain: "{{ .cloud.baseDomain }}" cloudflareDomain: "{{ .cloud.baseDomain }}"
scripts:
- name: repair-certificates
path: scripts/repair-certificates.sh
description: Fix stuck certificates, orphaned ACME orders, and Cloudflare DNS cleanup errors
defaultSecrets: defaultSecrets:
- key: cloudflareToken - key: cloudflareToken
deploy:
phases:
- path: upstream
waitFor:
name: cert-manager-webhook
timeout: "120s"
- path: .
createSecrets:
- name: cloudflare-api-token
entries:
api-token: cloudflareToken

View File

@@ -0,0 +1,89 @@
#!/bin/bash
# Repair stuck certificates, orphaned ACME orders, and Cloudflare DNS errors.
# This is an operational maintenance script, not part of deployment.
# Run manually when cert-manager has issues with certificate issuance.
#
# Usage: KUBECONFIG=/path/to/kubeconfig ./repair-certificates.sh
set -e
set -o pipefail
if [ -z "${KUBECONFIG}" ]; then
echo "ERROR: KUBECONFIG is not set"
exit 1
fi
needs_restart=false
echo "=== cert-manager Certificate Repair ==="
echo ""
echo "Checking for certificates with failed issuance attempts..."
stuck_certs=$(kubectl get certificates --all-namespaces -o json 2>/dev/null | \
jq -r '.items[] | select(.status.conditions[]? | select(.type=="Issuing" and .status=="False" and (.message | contains("404")))) | "\(.metadata.namespace) \(.metadata.name)"')
if [ -n "$stuck_certs" ]; then
echo "WARNING: Found certificates stuck with non-existent orders, recreating them..."
echo "$stuck_certs" | while read ns name; do
echo "Recreating certificate $ns/$name..."
cert_spec=$(kubectl get certificate "$name" -n "$ns" -o json | jq '.spec')
kubectl delete certificate "$name" -n "$ns"
echo "{\"apiVersion\":\"cert-manager.io/v1\",\"kind\":\"Certificate\",\"metadata\":{\"name\":\"$name\",\"namespace\":\"$ns\"},\"spec\":$cert_spec}" | kubectl apply -f -
done
needs_restart=true
sleep 5
else
echo "No certificates stuck with failed orders"
fi
echo "Checking for orphaned ACME orders..."
orphaned_orders=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \
grep -E "failed to retrieve the ACME order.*404" 2>/dev/null | \
sed -n 's/.*resource_name="\([^"]*\)".*/\1/p' | \
sort -u || true)
if [ -n "$orphaned_orders" ]; then
echo "WARNING: Found orphaned ACME orders from logs"
for order in $orphaned_orders; do
echo "Deleting orphaned order: $order"
orders_found=$(kubectl get orders --all-namespaces 2>/dev/null | grep "$order" 2>/dev/null || true)
if [ -n "$orders_found" ]; then
echo "$orders_found" | while read ns name rest; do
kubectl delete order "$name" -n "$ns" 2>/dev/null || true
done
fi
done
needs_restart=true
else
echo "No orphaned orders found in logs"
fi
echo "Checking for Cloudflare DNS cleanup errors..."
cloudflare_errors=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \
grep -c "Error: 7003.*Could not route" 2>/dev/null || echo "0")
if [ "$cloudflare_errors" -gt "0" ]; then
echo "WARNING: Found $cloudflare_errors Cloudflare DNS cleanup errors (stale DNS record references)"
echo "Deleting stuck challenges and orders to allow fresh start"
kubectl delete challenges --all -n cert-manager 2>/dev/null || true
kubectl delete orders --all -n cert-manager 2>/dev/null || true
needs_restart=true
else
echo "No Cloudflare DNS cleanup errors"
fi
if [ "$needs_restart" = true ]; then
echo "Restarting cert-manager to clear internal state..."
kubectl rollout restart deployment cert-manager -n cert-manager
kubectl rollout status deployment/cert-manager -n cert-manager --timeout=120s
echo "Waiting for cert-manager to recreate fresh challenges..."
sleep 15
else
echo "No restart needed - cert-manager state is clean"
fi
echo ""
echo "Repair complete. Check certificate status with:"
echo " kubectl get certificates --all-namespaces"
echo " kubectl get clusterissuers"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cert-manager.yaml
patches:
- target:
kind: Deployment
name: cert-manager
namespace: cert-manager
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: cert-manager
namespace: cert-manager
spec:
template:
spec:
dnsPolicy: None
dnsConfig:
nameservers:
- "1.1.1.1"
- "8.8.8.8"
searches:
- cert-manager.svc.cluster.local
- svc.cluster.local
- cluster.local
options:
- name: ndots
value: "5"

View File

@@ -66,6 +66,12 @@ spec:
secretKeyRef: secretKeyRef:
name: crowdsec-agent-secret name: crowdsec-agent-secret
key: password key: password
- name: BOUNCER_KEY_traefik
valueFrom:
secretKeyRef:
name: crowdsec-secrets
key: bouncerApiKey
optional: true
ports: ports:
- name: lapi - name: lapi
containerPort: 8080 containerPort: 8080

View File

@@ -1,118 +0,0 @@
#!/bin/bash
set -e
set -o pipefail
if [ -z "${WILD_INSTANCE}" ]; then
echo "ERROR: WILD_INSTANCE is not set"
exit 1
fi
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
if [ -z "${KUBECONFIG}" ]; then
echo "ERROR: KUBECONFIG is not set"
exit 1
fi
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CROWDSEC_DIR="${INSTANCE_DIR}/apps/crowdsec"
SECRETS_FILE="${INSTANCE_DIR}/secrets.yaml"
echo "=== Setting up CrowdSec Security Engine ==="
echo ""
echo "Verifying Traefik is ready (required for CrowdSec bouncer)..."
kubectl wait --for=condition=Available deployment/traefik -n traefik --timeout=60s 2>/dev/null || {
echo "WARNING: Traefik not ready, but continuing with CrowdSec installation"
echo "Note: CrowdSec bouncer will not work until Traefik is available"
}
echo "Using pre-compiled CrowdSec templates..."
if [ ! -f "${CROWDSEC_DIR}/kustomization.yaml" ]; then
echo "ERROR: Compiled templates not found at ${CROWDSEC_DIR}"
echo "Templates should be compiled before deployment."
exit 1
fi
echo "Deploying CrowdSec..."
kubectl apply -k ${CROWDSEC_DIR}/
echo "Creating CrowdSec agent secret..."
AGENT_PASSWORD=$(yq '.apps.crowdsec.agentPassword' "$SECRETS_FILE" 2>/dev/null | tr -d '"')
if [ -z "$AGENT_PASSWORD" ] || [ "$AGENT_PASSWORD" = "null" ]; then
echo "Generating new agent password..."
AGENT_PASSWORD=$(openssl rand -base64 32)
echo "WARNING: Agent password not found in secrets.yaml"
echo "Using generated password - you may want to persist this"
fi
kubectl create secret generic crowdsec-agent-secret \
--namespace crowdsec \
--from-literal=password="${AGENT_PASSWORD}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Waiting for CrowdSec agent to be ready..."
kubectl rollout status deployment/crowdsec -n crowdsec --timeout=120s
echo "Registering bouncer with CrowdSec agent..."
BOUNCER_API_KEY=$(yq '.apps.crowdsec.bouncerApiKey' "$SECRETS_FILE" 2>/dev/null | tr -d '"')
if [ -z "$BOUNCER_API_KEY" ] || [ "$BOUNCER_API_KEY" = "null" ]; then
echo "Generating new bouncer API key from CrowdSec agent..."
kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers delete traefik-bouncer 2>/dev/null || true
BOUNCER_API_KEY=$(kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers add traefik-bouncer -o raw)
echo "Generated bouncer API key - you may want to persist this in secrets.yaml"
fi
kubectl create secret generic crowdsec-bouncer-secret \
--namespace crowdsec \
--from-literal=api-key="${BOUNCER_API_KEY}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Copying bouncer secret to traefik namespace..."
kubectl create secret generic crowdsec-bouncer-secret \
--namespace traefik \
--from-literal=api-key="${BOUNCER_API_KEY}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Cleaning up old bouncer deployment..."
kubectl delete deployment traefik-crowdsec-bouncer -n crowdsec --ignore-not-found
kubectl delete service traefik-crowdsec-bouncer -n crowdsec --ignore-not-found
echo "Restarting Traefik to load CrowdSec plugin..."
kubectl rollout restart deployment/traefik -n traefik
kubectl rollout status deployment/traefik -n traefik --timeout=120s
echo "Configuring Traefik to use CrowdSec security chain by default..."
kubectl patch deployment traefik -n traefik --type='json' -p='[
{
"op": "add",
"path": "/spec/template/spec/containers/0/args/-",
"value": "--entryPoints.websecure.http.middlewares=crowdsec-security-chain@kubernetescrd"
}
]' 2>/dev/null || {
echo "Note: Traefik may already have middleware configured or patch failed"
echo "You can manually configure default middleware if needed"
}
echo ""
echo "CrowdSec installed successfully (using Traefik plugin)"
echo ""
echo "All ingresses are now protected by default with:"
echo " - Threat detection (CrowdSec Traefik plugin, stream mode)"
echo " - Rate limiting (100 req/min)"
echo " - Security headers (HSTS, XSS protection, etc.)"
echo ""
echo "To verify the installation:"
echo " kubectl get pods -n crowdsec"
echo " kubectl get pods -n traefik"
echo " kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers list"
echo " kubectl exec -n crowdsec deploy/crowdsec -- cscli decisions list"
echo ""
echo "To opt-out a specific ingress from CrowdSec protection:"
echo " Add annotation: traefik.ingress.kubernetes.io/router.middlewares: \"\""
echo ""

View File

@@ -13,3 +13,18 @@ defaultConfig:
defaultSecrets: defaultSecrets:
- key: agentPassword - key: agentPassword
- key: bouncerApiKey - key: bouncerApiKey
deploy:
createSecrets:
- name: crowdsec-agent-secret
entries:
password: agentPassword
- name: crowdsec-bouncer-secret
entries:
api-key: bouncerApiKey
- name: crowdsec-bouncer-secret
namespace: traefik
entries:
api-key: bouncerApiKey
waitForRollout:
name: crowdsec
timeout: "120s"

View File

@@ -1,11 +1,6 @@
# Decidim # Decidim
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. Decidim is a participatory democracy framework for cities and organizations. It enables citizen participation through proposals, debates, and voting.
## Dependencies
- **PostgreSQL** - Database for storing participatory processes and user data
- **Redis** - Used for Sidekiq background job processing
## Configuration ## Configuration
@@ -16,20 +11,3 @@ Key settings configured through your instance's `config.yaml`:
- **systemAdminEmail** - System admin email (defaults to your operator email) - **systemAdminEmail** - System admin email (defaults to your operator email)
- **storage** - Persistent volume size (default: `20Gi`) - **storage** - Persistent volume size (default: `20Gi`)
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance - **SMTP** - Email delivery settings inherited from your Wild Cloud instance
## Access
After deployment, Decidim will be available at:
- `https://decidim.{your-cloud-domain}`
## First-Time Setup
1. Add and deploy the app:
```bash
wild app add decidim
wild app deploy decidim
```
2. Log in with the system admin credentials configured during setup
3. Create your first organization and configure participatory processes

View File

@@ -54,7 +54,7 @@ spec:
echo "Database initialization completed successfully" echo "Database initialization completed successfully"
env: env:
- name: POSTGRES_HOST - name: POSTGRES_HOST
value: {{ .dbHostname }} value: {{ .db.host }}
- name: POSTGRES_ADMIN_USER - name: POSTGRES_ADMIN_USER
value: postgres value: postgres
- name: POSTGRES_ADMIN_PASSWORD - name: POSTGRES_ADMIN_PASSWORD
@@ -63,9 +63,9 @@ spec:
name: decidim-secrets name: decidim-secrets
key: postgres.password key: postgres.password
- name: DB_NAME - name: DB_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: DB_USER - name: DB_USER
value: {{ .dbUsername }} value: {{ .db.user }}
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -55,7 +55,7 @@ spec:
- name: RAILS_ENV - name: RAILS_ENV
value: "production" value: "production"
- name: PORT - name: PORT
value: "{{ .port }}" value: "3000"
- name: RAILS_LOG_TO_STDOUT - name: RAILS_LOG_TO_STDOUT
value: "true" value: "true"
# Database configuration # Database configuration
@@ -66,7 +66,7 @@ spec:
key: dbUrl key: dbUrl
# Redis configuration # Redis configuration
- name: REDIS_HOSTNAME - name: REDIS_HOSTNAME
value: {{ .redisHostname }} value: {{ .redis.host }}
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -112,11 +112,11 @@ spec:
key: systemAdminPassword key: systemAdminPassword
ports: ports:
- name: http - name: http
containerPort: {{ .port }} containerPort: 3000
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .port }} port: 3000
initialDelaySeconds: 300 initialDelaySeconds: 300
periodSeconds: 30 periodSeconds: 30
timeoutSeconds: 10 timeoutSeconds: 10
@@ -124,7 +124,7 @@ spec:
failureThreshold: 6 failureThreshold: 6
readinessProbe: readinessProbe:
tcpSocket: tcpSocket:
port: {{ .port }} port: 3000
initialDelaySeconds: 180 initialDelaySeconds: 180
periodSeconds: 30 periodSeconds: 30
timeoutSeconds: 10 timeoutSeconds: 10
@@ -182,7 +182,7 @@ spec:
key: dbUrl key: dbUrl
# Redis configuration # Redis configuration
- name: REDIS_HOSTNAME - name: REDIS_HOSTNAME
value: {{ .redisHostname }} value: {{ .redis.host }}
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -23,4 +23,4 @@ spec:
service: service:
name: decidim name: decidim
port: port:
number: {{ .port }} number: 3000

View File

@@ -1,7 +1,7 @@
name: decidim name: decidim
is: 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. 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 version: 0.31.0-1
icon: https://raw.githubusercontent.com/decidim/decidim/develop/logo.svg icon: https://raw.githubusercontent.com/decidim/decidim/develop/logo.svg
requires: requires:
- name: postgres - name: postgres
@@ -11,27 +11,27 @@ requires:
- name: smtp - name: smtp
defaultConfig: defaultConfig:
namespace: decidim namespace: decidim
externalDnsDomain: "{{ .cloud.domain }}" externalDnsDomain: '{{ .cloud.domain }}'
timezone: UTC
port: 3000
storage: 20Gi storage: 20Gi
systemAdminEmail: "{{ .operator.email }}" systemAdminEmail: '{{ .operator.email }}'
siteName: "Decidim" siteName: 'Decidim'
domain: decidim.{{ .cloud.domain }} 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 tlsSecretName: wildcard-wild-cloud-tls
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: decidim
user: decidim
redis:
host: '{{ .apps.redis.host }}'
smtp: smtp:
enabled: true enabled: true
host: "{{ .apps.smtp.host }}" host: '{{ .apps.smtp.host }}'
port: "{{ .apps.smtp.port }}" port: '{{ .apps.smtp.port }}'
user: "{{ .apps.smtp.user }}" user: '{{ .apps.smtp.user }}'
from: "{{ .apps.smtp.from }}" from: '{{ .apps.smtp.from }}'
tls: "{{ .apps.smtp.tls }}" tls: '{{ .apps.smtp.tls }}'
startTls: "{{ .apps.smtp.startTls }}" startTls: '{{ .apps.smtp.startTls }}'
defaultSecrets: defaultSecrets:
- key: systemAdminPassword - key: systemAdminPassword
- key: secretKeyBase - key: secretKeyBase
@@ -39,7 +39,7 @@ defaultSecrets:
- key: smtpPassword - key: smtpPassword
- key: dbPassword - key: dbPassword
- key: dbUrl - key: dbUrl
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/{{ .app.dbName }}" default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}"
requiredSecrets: requiredSecrets:
- postgres.password - postgres.password
- redis.password - redis.password

View File

@@ -9,7 +9,7 @@ spec:
component: web component: web
ports: ports:
- name: http - name: http
port: {{ .port }} port: 3000
targetPort: http targetPort: http
protocol: TCP protocol: TCP
type: ClusterIP type: ClusterIP

View File

@@ -27,7 +27,7 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: PGPORT - name: PGPORT
value: "5432" value: "5432"
- name: PGUSER - name: PGUSER
@@ -38,9 +38,9 @@ spec:
name: discourse-secrets name: discourse-secrets
key: postgres.password key: postgres.password
- name: DISCOURSE_DB_USER - name: DISCOURSE_DB_USER
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DISCOURSE_DB_NAME - name: DISCOURSE_DB_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DISCOURSE_DB_PASSWORD - name: DISCOURSE_DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -56,20 +56,20 @@ spec:
- name: RAILS_ENV - name: RAILS_ENV
value: "production" value: "production"
- name: DISCOURSE_DB_HOST - name: DISCOURSE_DB_HOST
value: {{ .dbHostname }} value: {{ .db.host }}
- name: DISCOURSE_DB_PORT - name: DISCOURSE_DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DISCOURSE_DB_NAME - name: DISCOURSE_DB_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: DISCOURSE_DB_USERNAME - name: DISCOURSE_DB_USERNAME
value: {{ .dbUsername }} value: {{ .db.user }}
- name: DISCOURSE_DB_PASSWORD - name: DISCOURSE_DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: discourse-secrets name: discourse-secrets
key: dbPassword key: dbPassword
- name: DISCOURSE_REDIS_HOST - name: DISCOURSE_REDIS_HOST
value: {{ .redisHostname }} value: {{ .redis.host }}
- name: DISCOURSE_REDIS_PASSWORD - name: DISCOURSE_REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -113,13 +113,13 @@ spec:
value: "production" value: "production"
# Discourse database configuration # Discourse database configuration
- name: DISCOURSE_DB_HOST - name: DISCOURSE_DB_HOST
value: {{ .dbHostname }} value: {{ .db.host }}
- name: DISCOURSE_DB_PORT - name: DISCOURSE_DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DISCOURSE_DB_NAME - name: DISCOURSE_DB_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: DISCOURSE_DB_USERNAME - name: DISCOURSE_DB_USERNAME
value: {{ .dbUsername }} value: {{ .db.user }}
- name: DISCOURSE_DB_PASSWORD - name: DISCOURSE_DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -127,7 +127,7 @@ spec:
key: dbPassword key: dbPassword
# Redis configuration # Redis configuration
- name: DISCOURSE_REDIS_HOST - name: DISCOURSE_REDIS_HOST
value: {{ .redisHostname }} value: {{ .redis.host }}
- name: DISCOURSE_REDIS_PASSWORD - name: DISCOURSE_REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -220,13 +220,13 @@ spec:
value: "production" value: "production"
# Discourse database configuration # Discourse database configuration
- name: DISCOURSE_DB_HOST - name: DISCOURSE_DB_HOST
value: {{ .dbHostname }} value: {{ .db.host }}
- name: DISCOURSE_DB_PORT - name: DISCOURSE_DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DISCOURSE_DB_NAME - name: DISCOURSE_DB_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: DISCOURSE_DB_USERNAME - name: DISCOURSE_DB_USERNAME
value: {{ .dbUsername }} value: {{ .db.user }}
- name: DISCOURSE_DB_PASSWORD - name: DISCOURSE_DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -234,7 +234,7 @@ spec:
key: dbPassword key: dbPassword
# Redis configuration # Redis configuration
- name: DISCOURSE_REDIS_HOST - name: DISCOURSE_REDIS_HOST
value: {{ .redisHostname }} value: {{ .redis.host }}
- name: DISCOURSE_REDIS_PASSWORD - name: DISCOURSE_REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -1,7 +1,7 @@
name: discourse name: discourse
is: discourse is: discourse
description: Discourse is a modern, open-source discussion platform designed for online communities and forums. description: Discourse is a modern, open-source discussion platform designed for online communities and forums.
version: 3.5.3 version: 3.5.3-1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
requires: requires:
- name: postgres - name: postgres
@@ -9,28 +9,28 @@ requires:
- name: smtp - name: smtp
defaultConfig: defaultConfig:
namespace: discourse namespace: discourse
externalDnsDomain: "{{ .cloud.domain }}" externalDnsDomain: '{{ .cloud.domain }}'
timezone: UTC
port: 3000
storage: 10Gi storage: 10Gi
adminEmail: "{{ .operator.email }}" adminEmail: '{{ .operator.email }}'
adminUsername: admin adminUsername: admin
siteName: "Community" siteName: 'Community'
domain: discourse.{{ .cloud.domain }} domain: discourse.{{ .cloud.domain }}
dbHostname: "{{ .apps.postgres.host }}"
dbPort: "{{ .apps.postgres.port }}"
dbUsername: discourse
dbName: discourse
redisHostname: "{{ .apps.redis.host }}"
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: discourse
user: discourse
redis:
host: '{{ .apps.redis.host }}'
smtp: smtp:
enabled: false enabled: false
host: "{{ .apps.smtp.host }}" host: '{{ .apps.smtp.host }}'
port: "{{ .apps.smtp.port }}" port: '{{ .apps.smtp.port }}'
user: "{{ .apps.smtp.user }}" user: '{{ .apps.smtp.user }}'
from: "{{ .apps.smtp.from }}" from: '{{ .apps.smtp.from }}'
tls: "{{ .apps.smtp.tls }}" tls: '{{ .apps.smtp.tls }}'
startTls: "{{ .apps.smtp.startTls }}" startTls: '{{ .apps.smtp.startTls }}'
defaultSecrets: defaultSecrets:
- key: adminPassword - key: adminPassword
- key: secretKeyBase - key: secretKeyBase
@@ -38,7 +38,7 @@ defaultSecrets:
- key: smtpPassword - key: smtpPassword
- key: dbPassword - key: dbPassword
- key: dbUrl - key: dbUrl
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode=disable" default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
requiredSecrets: requiredSecrets:
- postgres.password - postgres.password
- redis.password - redis.password

View File

@@ -0,0 +1,72 @@
apiVersion: batch/v1
kind: Job
metadata:
name: e2e-test-app-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres-init
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
env:
- name: PGHOST
value: {{ .db.host }}
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: e2e-test-app-secrets
key: postgres.password
- name: DB_NAME
value: {{ .db.name }}
- name: DB_USER
value: {{ .db.user }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: e2e-test-app-secrets
key: dbPassword
command:
- /bin/bash
- -c
- |
set -e
echo "Waiting for PostgreSQL to be ready..."
until pg_isready; do
echo "PostgreSQL is not ready - sleeping"
sleep 2
done
echo "PostgreSQL is ready"
echo "Creating database and user..."
psql -c "CREATE DATABASE ${DB_NAME};" || echo "Database ${DB_NAME} already exists"
psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || echo "User ${DB_USER} already exists"
psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
echo "Creating test data table..."
psql -d ${DB_NAME} -c "CREATE TABLE IF NOT EXISTS e2e_test_data (id SERIAL PRIMARY KEY, key TEXT UNIQUE NOT NULL, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW());"
psql -d ${DB_NAME} -c "GRANT ALL ON TABLE e2e_test_data TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT USAGE, SELECT ON SEQUENCE e2e_test_data_id_seq TO ${DB_USER};"
echo "Database initialization complete"

View File

@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: e2e-test-app
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:alpine
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: app-data
mountPath: /data
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumes:
- name: app-data
persistentVolumeClaim:
claimName: e2e-test-app-data

View File

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

View File

@@ -0,0 +1,23 @@
name: e2e-test-app
is: e2e-test-app
description: End-to-end test application for automated integration testing. Includes PVC and PostgreSQL dependency to exercise all backup strategies.
version: 1.0.0-1
requires:
- name: postgres
defaultConfig:
namespace: e2e-test-app
domain: e2e-test-app.{{ .cloud.domain }}
externalDnsDomain: '{{ .cloud.domain }}'
tlsSecretName: wildcard-wild-cloud-tls
storage: 1Gi
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: e2e_test_app
user: e2e_test_app
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
requiredSecrets:
- postgres.password

View File

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

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: e2e-test-app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: {{ .storage }}

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: e2e-test-app
spec:
selector:
component: web
ports:
- port: 80
targetPort: 8080
name: http

View File

@@ -0,0 +1,72 @@
apiVersion: batch/v1
kind: Job
metadata:
name: e2e-test-app-db-init
labels:
component: db-init
spec:
template:
metadata:
labels:
component: db-init
spec:
restartPolicy: OnFailure
securityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres-init
image: postgres:15
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
env:
- name: PGHOST
value: {{ .db.host }}
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: e2e-test-app-secrets
key: postgres.password
- name: DB_NAME
value: {{ .db.name }}
- name: DB_USER
value: {{ .db.user }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: e2e-test-app-secrets
key: dbPassword
command:
- /bin/bash
- -c
- |
set -e
echo "Waiting for PostgreSQL to be ready..."
until pg_isready; do
echo "PostgreSQL is not ready - sleeping"
sleep 2
done
echo "PostgreSQL is ready"
echo "Creating database and user..."
psql -c "CREATE DATABASE ${DB_NAME};" || echo "Database ${DB_NAME} already exists"
psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || echo "User ${DB_USER} already exists"
psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};"
echo "Creating test data table..."
psql -d ${DB_NAME} -c "CREATE TABLE IF NOT EXISTS e2e_test_data (id SERIAL PRIMARY KEY, key TEXT UNIQUE NOT NULL, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW());"
psql -d ${DB_NAME} -c "GRANT ALL ON TABLE e2e_test_data TO ${DB_USER};"
psql -d ${DB_NAME} -c "GRANT USAGE, SELECT ON SEQUENCE e2e_test_data_id_seq TO ${DB_USER};"
echo "Database initialization complete"

View File

@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: e2e-test-app
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
securityContext:
runAsNonRoot: true
runAsUser: 101
runAsGroup: 101
fsGroup: 101
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged:alpine
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: app-data
mountPath: /data
resources:
limits:
cpu: 100m
memory: 64Mi
requests:
cpu: 50m
memory: 32Mi
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
volumes:
- name: app-data
persistentVolumeClaim:
claimName: e2e-test-app-data

View File

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

View File

@@ -0,0 +1,32 @@
name: e2e-test-app
is: e2e-test-app
description: End-to-end test application for automated integration testing. Includes PVC and PostgreSQL dependency to exercise all backup strategies.
version: 2.0.0
upgrade:
from:
- version: ">=1.0.0"
via: "1.0.0-1"
- version: "<1.0.0"
blocked: true
notes: "Versions before 1.0.0 are not supported for upgrade"
preUpgrade:
backup: recommended
requires:
- name: postgres
defaultConfig:
namespace: e2e-test-app
domain: e2e-test-app.{{ .cloud.domain }}
externalDnsDomain: '{{ .cloud.domain }}'
tlsSecretName: wildcard-wild-cloud-tls
storage: 1Gi
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: e2e_test_app
user: e2e_test_app
defaultSecrets:
- key: dbPassword
- key: dbUrl
default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
requiredSecrets:
- postgres.password

View File

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

11
e2e-test-app/pvc.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: e2e-test-app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: {{ .storage }}

11
e2e-test-app/service.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: e2e-test-app
spec:
selector:
component: web
ports:
- port: 80
targetPort: 8080
name: http

View File

@@ -29,13 +29,13 @@ spec:
name: mysql-secrets name: mysql-secrets
key: rootPassword key: rootPassword
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .dbHost }}" value: "{{ .db.host }}"
- name: DB_PORT - name: DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .dbUser }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -17,10 +17,10 @@ spec:
spec: spec:
containers: containers:
- name: ghost - name: ghost
image: {{ .image }} image: docker.io/bitnami/ghost:5.118.1-debian-12-r0
ports: ports:
- name: http - name: http
containerPort: {{ .port }} containerPort: 2368
protocol: TCP protocol: TCP
env: env:
- name: BITNAMI_DEBUG - name: BITNAMI_DEBUG
@@ -28,13 +28,13 @@ spec:
- name: ALLOW_EMPTY_PASSWORD - name: ALLOW_EMPTY_PASSWORD
value: "yes" value: "yes"
- name: GHOST_DATABASE_HOST - name: GHOST_DATABASE_HOST
value: {{ .dbHost }} value: {{ .db.host }}
- name: GHOST_DATABASE_PORT_NUMBER - name: GHOST_DATABASE_PORT_NUMBER
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: GHOST_DATABASE_NAME - name: GHOST_DATABASE_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: GHOST_DATABASE_USER - name: GHOST_DATABASE_USER
value: {{ .dbUser }} value: {{ .db.user }}
- name: GHOST_DATABASE_PASSWORD - name: GHOST_DATABASE_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -43,7 +43,7 @@ spec:
- name: GHOST_HOST - name: GHOST_HOST
value: {{ .domain }} value: {{ .domain }}
- name: GHOST_PORT_NUMBER - name: GHOST_PORT_NUMBER
value: "{{ .port }}" value: "2368"
- name: GHOST_USERNAME - name: GHOST_USERNAME
value: {{ .adminUser }} value: {{ .adminUser }}
- name: GHOST_PASSWORD - name: GHOST_PASSWORD
@@ -92,7 +92,7 @@ spec:
mountPath: /bitnami/ghost mountPath: /bitnami/ghost
livenessProbe: livenessProbe:
tcpSocket: tcpSocket:
port: {{ .port }} port: 2368
initialDelaySeconds: 120 initialDelaySeconds: 120
timeoutSeconds: 5 timeoutSeconds: 5
periodSeconds: 10 periodSeconds: 10

View File

@@ -2,7 +2,7 @@ name: ghost
is: ghost is: ghost
description: Ghost is a powerful app for new-media creators to publish, share, and description: Ghost is a powerful app for new-media creators to publish, share, and
grow a business around their content. grow a business around their content.
version: 5.118.1 version: 5.118.1-1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png
requires: requires:
- name: mysql - name: mysql
@@ -10,19 +10,17 @@ requires:
defaultConfig: defaultConfig:
namespace: ghost namespace: ghost
externalDnsDomain: '{{ .cloud.domain }}' externalDnsDomain: '{{ .cloud.domain }}'
image: docker.io/bitnami/ghost:5.118.1-debian-12-r0
domain: ghost.{{ .cloud.domain }} domain: ghost.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
port: 2368
storage: 10Gi storage: 10Gi
dbHost: mysql.mysql.svc.cluster.local
dbPort: 3306
dbName: ghost
dbUser: ghost
adminUser: admin adminUser: admin
adminEmail: {{ .operator.email }} adminEmail: '{{ .operator.email }}'
blogTitle: My Blog blogTitle: My Blog
timezone: UTC db:
host: '{{ .apps.mysql.host }}'
port: '3306'
name: ghost
user: ghost
smtp: smtp:
host: '{{ .apps.smtp.host }}' host: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}' port: '{{ .apps.smtp.port }}'

View File

@@ -9,6 +9,6 @@ spec:
- name: http - name: http
port: 80 port: 80
protocol: TCP protocol: TCP
targetPort: {{ .port }} targetPort: 2368
selector: selector:
component: web component: web

View File

@@ -38,11 +38,11 @@ spec:
name: postgres-secrets name: postgres-secrets
key: password key: password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .dbHost }}" value: "{{ .db.host }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .dbUser }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -23,7 +23,7 @@ spec:
terminationGracePeriodSeconds: 60 terminationGracePeriodSeconds: 60
containers: containers:
- name: gitea - name: gitea
image: "{{ .image }}" image: "gitea/gitea:1.24.3"
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
envFrom: envFrom:
- configMapRef: - configMapRef:

View File

@@ -8,7 +8,7 @@ GITEA_ADMIN_PASSWORD_MODE=keepUpdated
# Core app settings # Core app settings
GITEA____APP_NAME={{ .appName }} GITEA____APP_NAME={{ .appName }}
GITEA____RUN_MODE={{ .runMode }} GITEA____RUN_MODE=prod
GITEA____RUN_USER=git GITEA____RUN_USER=git
# Security settings # Security settings
@@ -17,19 +17,19 @@ GITEA__security__PASSWORD_HASH_ALGO=pbkdf2
# Database settings (except password which comes from secret) # Database settings (except password which comes from secret)
GITEA__database__DB_TYPE=postgres GITEA__database__DB_TYPE=postgres
GITEA__database__HOST={{ .dbHost }}:{{ .dbPort }} GITEA__database__HOST={{ .db.host }}:{{ .db.port }}
GITEA__database__NAME={{ .dbName }} GITEA__database__NAME={{ .db.name }}
GITEA__database__USER={{ .dbUser }} GITEA__database__USER={{ .db.user }}
GITEA__database__SSL_MODE=disable GITEA__database__SSL_MODE=disable
GITEA__database__LOG_SQL=false GITEA__database__LOG_SQL=false
# Server settings # Server settings
GITEA__server__DOMAIN={{ .domain }} GITEA__server__DOMAIN={{ .domain }}
GITEA__server__HTTP_PORT={{ .port }} GITEA__server__HTTP_PORT=3000
GITEA__server__ROOT_URL=https://{{ .domain }}/ GITEA__server__ROOT_URL=https://{{ .domain }}/
GITEA__server__DISABLE_SSH=false GITEA__server__DISABLE_SSH=false
GITEA__server__SSH_DOMAIN={{ .domain }} GITEA__server__SSH_DOMAIN={{ .domain }}
GITEA__server__SSH_PORT={{ .sshPort }} GITEA__server__SSH_PORT=22
GITEA__server__SSH_LISTEN_PORT=2222 GITEA__server__SSH_LISTEN_PORT=2222
GITEA__server__LFS_START_SERVER=true GITEA__server__LFS_START_SERVER=true
GITEA__server__OFFLINE_MODE=true GITEA__server__OFFLINE_MODE=true

View File

@@ -1,7 +1,7 @@
name: gitea name: gitea
is: gitea is: gitea
description: Gitea is a painless self-hosted Git service written in Go description: Gitea is a painless self-hosted Git service written in Go
version: 1.24.3 version: 1.24.3-1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
requires: requires:
- name: postgres - name: postgres
@@ -9,21 +9,17 @@ requires:
defaultConfig: defaultConfig:
namespace: gitea namespace: gitea
externalDnsDomain: '{{ .cloud.domain }}' externalDnsDomain: '{{ .cloud.domain }}'
image: gitea/gitea:1.24.3
appName: Gitea appName: Gitea
domain: gitea.{{ .cloud.domain }} domain: gitea.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
port: 3000
sshPort: 22
storage: 10Gi storage: 10Gi
dbName: gitea
dbUser: gitea
dbHost: postgres.postgres.svc.cluster.local
adminUser: admin adminUser: admin
adminEmail: "{{ .operator.email }}" adminEmail: "{{ .operator.email }}"
dbPort: 5432 db:
timezone: UTC name: gitea
runMode: prod user: gitea
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
smtp: smtp:
host: '{{ .apps.smtp.host }}' host: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}' port: '{{ .apps.smtp.port }}'

View File

@@ -8,7 +8,7 @@ spec:
ports: ports:
- name: http - name: http
port: 3000 port: 3000
targetPort: {{ .port }} targetPort: 3000
selector: selector:
component: web component: web
--- ---
@@ -21,7 +21,7 @@ spec:
type: LoadBalancer type: LoadBalancer
ports: ports:
- name: ssh - name: ssh
port: {{ .sshPort }} port: 22
targetPort: 2222 targetPort: 2222
protocol: TCP protocol: TCP
selector: selector:

View File

@@ -55,11 +55,11 @@ spec:
name: immich-secrets name: immich-secrets
key: postgres.password key: postgres.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .dbUsername }}" value: "{{ .db.name }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -17,14 +17,14 @@ spec:
component: machine-learning component: machine-learning
spec: spec:
containers: containers:
- image: "{{ .mlImage }}" - image: "ghcr.io/immich-app/immich-machine-learning:v1.135.3"
name: immich-machine-learning name: immich-machine-learning
ports: ports:
- containerPort: {{ .mlPort }} - containerPort: 3003
protocol: TCP protocol: TCP
env: env:
- name: TZ - name: TZ
value: "{{ .timezone }}" value: "UTC"
volumeMounts: volumeMounts:
- mountPath: /cache - mountPath: /cache
name: immich-cache name: immich-cache

View File

@@ -20,27 +20,27 @@ spec:
component: microservices component: microservices
spec: spec:
containers: containers:
- image: "{{ .serverImage }}" - image: "ghcr.io/immich-app/immich-server:v1.135.3"
name: immich-microservices name: immich-microservices
env: env:
- name: REDIS_HOSTNAME - name: REDIS_HOSTNAME
value: "{{ .redisHostname }}" value: "{{ .redis.host }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: redis.password key: redis.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: dbPassword key: dbPassword
- name: TZ - name: TZ
value: "{{ .timezone }}" value: "UTC"
- name: IMMICH_WORKERS_EXCLUDE - name: IMMICH_WORKERS_EXCLUDE
value: api value: api
volumeMounts: volumeMounts:

View File

@@ -20,30 +20,30 @@ spec:
component: server component: server
spec: spec:
containers: containers:
- image: "{{ .serverImage }}" - image: "ghcr.io/immich-app/immich-server:v1.135.3"
name: immich-server name: immich-server
ports: ports:
- containerPort: {{ .serverPort }} - containerPort: 2283
protocol: TCP protocol: TCP
env: env:
- name: REDIS_HOSTNAME - name: REDIS_HOSTNAME
value: "{{ .redisHostname }}" value: "{{ .redis.host }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: redis.password key: redis.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: immich-secrets name: immich-secrets
key: dbPassword key: dbPassword
- name: TZ - name: TZ
value: "{{ .timezone }}" value: "UTC"
- name: IMMICH_WORKERS_EXCLUDE - name: IMMICH_WORKERS_EXCLUDE
value: microservices value: microservices
volumeMounts: volumeMounts:

View File

@@ -2,7 +2,7 @@ name: immich
is: immich is: immich
description: Immich is a self-hosted photo and video backup solution that allows you description: Immich is a self-hosted photo and video backup solution that allows you
to store, manage, and share your media files securely. to store, manage, and share your media files securely.
version: release version: 1.135.3-1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
requires: requires:
- name: redis - name: redis
@@ -10,18 +10,16 @@ requires:
defaultConfig: defaultConfig:
namespace: immich namespace: immich
externalDnsDomain: '{{ .cloud.domain }}' externalDnsDomain: '{{ .cloud.domain }}'
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 storage: 250Gi
cacheStorage: 10Gi cacheStorage: 10Gi
redisHostname: redis.redis.svc.cluster.local
dbHostname: postgres.postgres.svc.cluster.local
dbUsername: immich
domain: immich.{{ .cloud.domain }} domain: immich.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
db:
host: '{{ .apps.postgres.host }}'
name: immich
user: immich
redis:
host: '{{ .apps.redis.host }}'
defaultSecrets: defaultSecrets:
- key: dbPassword - key: dbPassword
requiredSecrets: requiredSecrets:

View File

@@ -9,7 +9,7 @@ metadata:
spec: spec:
ports: ports:
- port: 3001 - port: 3001
targetPort: {{ .serverPort }} targetPort: 2283
selector: selector:
app: immich app: immich
component: server component: server
@@ -25,7 +25,7 @@ metadata:
app: immich-machine-learning app: immich-machine-learning
spec: spec:
ports: ports:
- port: {{ .mlPort }} - port: 3003
selector: selector:
app: immich app: immich
component: machine-learning component: machine-learning

View File

@@ -26,7 +26,7 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: {{ .dbHostname }} value: {{ .db.host }}
- name: PGUSER - name: PGUSER
value: postgres value: postgres
- name: PGPASSWORD - name: PGPASSWORD
@@ -35,9 +35,9 @@ spec:
name: keila-secrets name: keila-secrets
key: postgres.password key: postgres.password
- name: DB_NAME - name: DB_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: DB_USER - name: DB_USER
value: {{ .dbUsername }} value: {{ .db.user }}
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -16,9 +16,9 @@ spec:
spec: spec:
containers: containers:
- name: keila - name: keila
image: "{{ .image }}" image: "pentacent/keila:0.17.1"
ports: ports:
- containerPort: {{ .port }} - containerPort: 4000
env: env:
- name: DB_URL - name: DB_URL
valueFrom: valueFrom:
@@ -32,7 +32,7 @@ spec:
- name: URL_PORT - name: URL_PORT
value: "443" value: "443"
- name: PORT - name: PORT
value: "{{ .port }}" value: "4000"
- name: SECRET_KEY_BASE - name: SECRET_KEY_BASE
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -72,13 +72,13 @@ spec:
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
port: {{ .port }} port: 4000
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: / path: /
port: {{ .port }} port: 4000
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 5 periodSeconds: 5
volumes: volumes:

View File

@@ -1,38 +1,37 @@
name: keila name: keila
is: keila is: keila
description: Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control. description: Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control.
version: 0.17.1 version: 0.17.1-1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
requires: requires:
- name: postgres - name: postgres
- name: smtp - name: smtp
defaultConfig: defaultConfig:
namespace: keila namespace: keila
externalDnsDomain: "{{ .cloud.domain }}" externalDnsDomain: '{{ .cloud.domain }}'
image: pentacent/keila:0.17.1
port: 4000
storage: 1Gi storage: 1Gi
domain: keila.{{ .cloud.domain }} domain: keila.{{ .cloud.domain }}
dbHostname: "{{ .apps.postgres.host }}" disableRegistration: 'true'
dbPort: "{{ .apps.postgres.port }}"
dbName: keila
dbUsername: keila
disableRegistration: "true"
adminUser: admin@{{ .cloud.domain }} adminUser: admin@{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: keila
user: keila
smtp: smtp:
host: "{{ .apps.smtp.host }}" host: '{{ .apps.smtp.host }}'
port: "{{ .apps.smtp.port }}" port: '{{ .apps.smtp.port }}'
from: "{{ .apps.smtp.from }}" from: '{{ .apps.smtp.from }}'
user: "{{ .apps.smtp.user }}" user: '{{ .apps.smtp.user }}'
tls: "{{ .apps.smtp.tls }}" tls: '{{ .apps.smtp.tls }}'
startTls: "{{ .apps.smtp.startTls }}" startTls: '{{ .apps.smtp.startTls }}'
defaultSecrets: defaultSecrets:
- key: secretKeyBase - key: secretKeyBase
default: "{{ random.AlphaNum 64 }}" default: "{{ random.AlphaNum 64 }}"
- key: dbPassword - key: dbPassword
- key: dbUrl - key: dbUrl
default: "postgres://{{ .app.dbUsername }}:{{ .secrets.dbPassword }}@{{ .app.dbHostname }}:{{ .app.dbPort }}/keila?sslmode=disable" default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable"
- key: adminPassword - key: adminPassword
- key: smtpPassword - key: smtpPassword
requiredSecrets: requiredSecrets:

View File

@@ -7,5 +7,5 @@ spec:
component: web component: web
ports: ports:
- port: 80 - port: 80
targetPort: {{ .port }} targetPort: 4000
protocol: TCP protocol: TCP

View File

@@ -8,15 +8,15 @@ data:
{ {
hostname: "{{ .domain }}" hostname: "{{ .domain }}"
bind: "0.0.0.0" bind: "0.0.0.0"
port: {{ .backendPort }} port: 8536
tls_enabled: false tls_enabled: false
database: { database: {
uri: "postgresql://{{ .dbUser }}:DBPASSWORD@{{ .dbHost }}:{{ .dbPort }}/{{ .dbName }}" uri: "postgresql://{{ .db.user }}:DBPASSWORD@{{ .db.host }}:{{ .db.port }}/{{ .db.name }}"
} }
pictrs: { pictrs: {
url: "http://lemmy-pictrs:{{ .pictrsPort }}/" url: "http://lemmy-pictrs:8080/"
api_key: "PICTRS_API_KEY" api_key: "PICTRS_API_KEY"
} }

View File

@@ -26,9 +26,9 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: "{{ .dbHost }}" value: "{{ .db.host }}"
- name: PGPORT - name: PGPORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: PGUSER - name: PGUSER
value: postgres value: postgres
- name: PGPASSWORD - name: PGPASSWORD
@@ -37,9 +37,9 @@ spec:
name: lemmy-secrets name: lemmy-secrets
key: postgres.password key: postgres.password
- name: DB_NAME - name: DB_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USER - name: DB_USER
value: "{{ .dbUser }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -4,7 +4,7 @@ metadata:
name: lemmy-backend name: lemmy-backend
namespace: {{ .namespace }} namespace: {{ .namespace }}
spec: spec:
replicas: {{ .backendReplicas }} replicas: 1
selector: selector:
matchLabels: matchLabels:
component: backend component: backend
@@ -65,7 +65,7 @@ spec:
mountPath: /config mountPath: /config
containers: containers:
- name: backend - name: backend
image: {{ .backendImage }} image: dessalines/lemmy:0.19.15
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -75,9 +75,9 @@ spec:
- name: LEMMY_CONFIG_LOCATION - name: LEMMY_CONFIG_LOCATION
value: /config/lemmy.hjson value: /config/lemmy.hjson
- name: TZ - name: TZ
value: "{{ .timezone }}" value: "UTC"
ports: ports:
- containerPort: {{ .backendPort }} - containerPort: 8536
name: http name: http
volumeMounts: volumeMounts:
- name: config - name: config
@@ -85,13 +85,13 @@ spec:
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /api/v3/site path: /api/v3/site
port: {{ .backendPort }} port: 8536
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /api/v3/site path: /api/v3/site
port: {{ .backendPort }} port: 8536
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
volumes: volumes:

View File

@@ -4,7 +4,7 @@ metadata:
name: lemmy-pictrs name: lemmy-pictrs
namespace: {{ .namespace }} namespace: {{ .namespace }}
spec: spec:
replicas: {{ .pictrsReplicas }} replicas: 1
selector: selector:
matchLabels: matchLabels:
component: pictrs component: pictrs
@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: pictrs - name: pictrs
image: {{ .pictrsImage }} image: asonix/pictrs:0.5.5
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -30,7 +30,7 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PICTRS__SERVER__BIND - name: PICTRS__SERVER__BIND
value: "0.0.0.0:{{ .pictrsPort }}" value: "0.0.0.0:8080"
- name: PICTRS__MEDIA__VIDEO_CODEC - name: PICTRS__MEDIA__VIDEO_CODEC
value: vp9 value: vp9
- name: PICTRS__MEDIA__GIF__MAX_WIDTH - name: PICTRS__MEDIA__GIF__MAX_WIDTH
@@ -54,7 +54,7 @@ spec:
- name: PICTRS__STORE__PATH - name: PICTRS__STORE__PATH
value: /mnt/files value: /mnt/files
ports: ports:
- containerPort: {{ .pictrsPort }} - containerPort: 8080
name: http name: http
volumeMounts: volumeMounts:
- name: storage - name: storage
@@ -62,13 +62,13 @@ spec:
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: {{ .pictrsPort }} port: 8080
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: {{ .pictrsPort }} port: 8080
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
volumes: volumes:

View File

@@ -4,7 +4,7 @@ metadata:
name: lemmy-ui name: lemmy-ui
namespace: {{ .namespace }} namespace: {{ .namespace }}
spec: spec:
replicas: {{ .uiReplicas }} replicas: 1
selector: selector:
matchLabels: matchLabels:
component: ui component: ui
@@ -21,7 +21,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: ui - name: ui
image: {{ .uiImage }} image: dessalines/lemmy-ui:0.19.15
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -29,25 +29,25 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: LEMMY_UI_LEMMY_INTERNAL_HOST - name: LEMMY_UI_LEMMY_INTERNAL_HOST
value: "lemmy-backend:{{ .backendPort }}" value: "lemmy-backend:8536"
- name: LEMMY_UI_LEMMY_EXTERNAL_HOST - name: LEMMY_UI_LEMMY_EXTERNAL_HOST
value: "{{ .domain }}" value: "{{ .domain }}"
- name: LEMMY_UI_HTTPS - name: LEMMY_UI_HTTPS
value: "true" value: "true"
ports: ports:
- containerPort: {{ .uiPort }} - containerPort: 1234
name: http name: http
livenessProbe: livenessProbe:
httpGet: httpGet:
path: / path: /
port: {{ .uiPort }} port: 1234
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
timeoutSeconds: 5 timeoutSeconds: 5
readinessProbe: readinessProbe:
httpGet: httpGet:
path: / path: /
port: {{ .uiPort }} port: 1234
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
timeoutSeconds: 5 timeoutSeconds: 5

View File

@@ -25,18 +25,18 @@ spec:
service: service:
name: lemmy-backend name: lemmy-backend
port: port:
number: {{ .backendPort }} number: 8536
- path: /pictrs - path: /pictrs
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: lemmy-pictrs name: lemmy-pictrs
port: port:
number: {{ .pictrsPort }} number: 8080
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: lemmy-ui name: lemmy-ui
port: port:
number: {{ .uiPort }} number: 1234

View File

@@ -1,38 +1,29 @@
name: lemmy name: lemmy
is: 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. 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 version: 0.19.15-2
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lemmy.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lemmy.svg
requires: requires:
- name: postgres - name: postgres
- name: smtp - name: smtp
defaultConfig: defaultConfig:
namespace: lemmy namespace: lemmy
backendImage: dessalines/lemmy:0.19.15 externalDnsDomain: lemmy.{{ .cloud.baseDomain }}
uiImage: dessalines/lemmy-ui:0.19.15 domain: lemmy.{{ .cloud.domain }}
pictrsImage: asonix/pictrs:0.5.5 tlsSecretName: wildcard-wild-cloud-tls
backendPort: 8536
uiPort: 1234
pictrsPort: 8080
backendReplicas: 1
uiReplicas: 1
pictrsReplicas: 1
storage: 10Gi storage: 10Gi
pictrsStorage: 50Gi pictrsStorage: 50Gi
timezone: UTC db:
domain: lemmy.{{ .cloud.domain }} host: '{{ .apps.postgres.host }}'
externalDnsDomain: lemmy.{{ .cloud.baseDomain }} port: '{{ .apps.postgres.port }}'
tlsSecretName: lemmy-tls name: lemmy
dbName: lemmy user: lemmy
dbUser: lemmy
dbHost: postgres.postgres.svc.cluster.local
dbPort: 5432
smtp: smtp:
host: "{{ .apps.smtp.host }}" host: '{{ .apps.smtp.host }}'
port: "{{ .apps.smtp.port }}" port: '{{ .apps.smtp.port }}'
user: "{{ .apps.smtp.user }}" user: '{{ .apps.smtp.user }}'
from: "noreply@{{ .cloud.baseDomain }}" from: 'noreply@{{ .cloud.baseDomain }}'
tls: "{{ .apps.smtp.tls }}" tls: '{{ .apps.smtp.tls }}'
defaultSecrets: defaultSecrets:
- key: dbPassword - key: dbPassword
- key: adminPassword - key: adminPassword

View File

@@ -9,5 +9,5 @@ spec:
component: backend component: backend
ports: ports:
- name: http - name: http
port: {{ .backendPort }} port: 8536
targetPort: {{ .backendPort }} targetPort: 8536

View File

@@ -9,5 +9,5 @@ spec:
component: pictrs component: pictrs
ports: ports:
- name: http - name: http
port: {{ .pictrsPort }} port: 8080
targetPort: {{ .pictrsPort }} targetPort: 8080

View File

@@ -9,5 +9,5 @@ spec:
component: ui component: ui
ports: ports:
- name: http - name: http
port: {{ .uiPort }} port: 1234
targetPort: {{ .uiPort }} targetPort: 1234

View File

@@ -28,7 +28,7 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: {{ .dbHost }} value: {{ .db.host }}
- name: PGUSER - name: PGUSER
value: postgres value: postgres
- name: PGPASSWORD - name: PGPASSWORD
@@ -37,9 +37,9 @@ spec:
name: listmonk-secrets name: listmonk-secrets
key: postgres.password key: postgres.password
- name: DB_NAME - name: DB_NAME
value: {{ .dbName }} value: {{ .db.name }}
- name: DB_USER - name: DB_USER
value: {{ .dbUser }} value: {{ .db.user }}
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -31,17 +31,17 @@ spec:
- name: LISTMONK_app__address - name: LISTMONK_app__address
value: "0.0.0.0:9000" value: "0.0.0.0:9000"
- name: LISTMONK_app__root_url - name: LISTMONK_app__root_url
value: "{{ .rootUrl }}" value: "https://{{ .domain }}"
- name: LISTMONK_db__host - name: LISTMONK_db__host
value: {{ .dbHost }} value: {{ .db.host }}
- name: LISTMONK_db__port - name: LISTMONK_db__port
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: LISTMONK_db__user - name: LISTMONK_db__user
value: {{ .dbUser }} value: {{ .db.user }}
- name: LISTMONK_db__database - name: LISTMONK_db__database
value: {{ .dbName }} value: {{ .db.name }}
- name: LISTMONK_db__ssl_mode - name: LISTMONK_db__ssl_mode
value: {{ .dbSSLMode }} value: disable
- name: LISTMONK_db__password - name: LISTMONK_db__password
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -2,7 +2,7 @@ name: listmonk
is: listmonk is: listmonk
description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager. description: Listmonk is a standalone, self-hosted, newsletter and mailing list manager.
It is fast, feature-rich, and packed into a single binary. It is fast, feature-rich, and packed into a single binary.
version: 5.0.3 version: 5.0.3-1
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/listmonk.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/listmonk.svg
requires: requires:
- name: postgres - name: postgres
@@ -10,18 +10,16 @@ defaultConfig:
namespace: listmonk namespace: listmonk
externalDnsDomain: '{{ .cloud.domain }}' externalDnsDomain: '{{ .cloud.domain }}'
domain: listmonk.{{ .cloud.domain }} domain: listmonk.{{ .cloud.domain }}
rootUrl: https://listmonk.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
storage: 1Gi storage: 1Gi
dbHost: postgres.postgres.svc.cluster.local db:
dbPort: 5432 host: '{{ .apps.postgres.host }}'
dbName: listmonk port: '{{ .apps.postgres.port }}'
dbUser: listmonk name: listmonk
dbSSLMode: disable user: listmonk
timezone: UTC
defaultSecrets: defaultSecrets:
- key: dbPassword - key: dbPassword
- key: dbUrl - key: dbUrl
default: 'postgres://{{ .app.dbUser }}:{{ .secrets.dbPassword }}@{{ .app.dbHost }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode={{ .app.dbSSLMode }}' default: 'postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable'
requiredSecrets: requiredSecrets:
- postgres.password - postgres.password

View File

@@ -0,0 +1,9 @@
apiVersion: longhorn.io/v1beta2
kind: BackupTarget
metadata:
name: default
namespace: longhorn-system
spec:
backupTargetURL: "{{ .backupTarget }}"
credentialSecret: ""
pollInterval: 5m0s

View File

@@ -3,5 +3,6 @@ kind: Kustomization
resources: resources:
- longhorn.yaml - longhorn.yaml
- backup-target.yaml
- ingress.yaml - ingress.yaml
- volumesnapshotclass-longhorn.yaml - volumesnapshotclass-longhorn.yaml

View File

@@ -83,8 +83,6 @@ data:
default-setting.yaml: |- default-setting.yaml: |-
priority-class: longhorn-critical priority-class: longhorn-critical
disable-revision-counter: true disable-revision-counter: true
backup-target: {{ .backupTarget }}
backup-target-credential-secret: ""
--- ---
# Source: longhorn/templates/storageclass.yaml # Source: longhorn/templates/storageclass.yaml
apiVersion: v1 apiVersion: v1

View File

@@ -1,12 +1,13 @@
name: longhorn name: longhorn
is: longhorn is: longhorn
description: Cloud-native distributed block storage for Kubernetes description: Cloud-native distributed block storage for Kubernetes
version: v1.8.1 version: v1.8.1-2
deploymentName: longhorn-ui deploymentName: longhorn-ui
category: infrastructure category: infrastructure
requires: requires:
- name: traefik - name: traefik
- name: nfs - name: nfs
- name: snapshot-controller
defaultConfig: defaultConfig:
namespace: longhorn-system namespace: longhorn-system
internalDomain: "{{ .cloud.internalDomain }}" internalDomain: "{{ .cloud.internalDomain }}"

View File

@@ -8,7 +8,7 @@ spec:
restartPolicy: OnFailure restartPolicy: OnFailure
containers: containers:
- name: db-init - name: db-init
image: {{ .image }} image: loomio/loomio:latest
command: command:
- /bin/bash - /bin/bash
- -c - -c

View File

@@ -14,7 +14,7 @@ spec:
spec: spec:
containers: containers:
- name: worker - name: worker
image: {{ .workerImage }} image: loomio/loomio:latest
env: env:
- name: TASK - name: TASK
value: worker value: worker
@@ -46,7 +46,7 @@ spec:
name: loomio-secrets name: loomio-secrets
key: secretCookieToken key: secretCookieToken
- name: ACTIVE_STORAGE_SERVICE - name: ACTIVE_STORAGE_SERVICE
value: {{ .activeStorageService }} value: local
- name: SMTP_AUTH - name: SMTP_AUTH
value: {{ .smtp.auth }} value: {{ .smtp.auth }}
- name: SMTP_DOMAIN - name: SMTP_DOMAIN

View File

@@ -4,6 +4,8 @@ metadata:
name: loomio name: loomio
spec: spec:
replicas: 1 replicas: 1
strategy:
type: Recreate
selector: selector:
matchLabels: matchLabels:
component: web component: web
@@ -14,13 +16,13 @@ spec:
spec: spec:
containers: containers:
- name: loomio - name: loomio
image: {{ .image }} image: loomio/loomio:latest
command: command:
- /bin/bash - /bin/bash
- -c - -c
- | - |
set -e set -e
bundle exec rake db:schema:load db:seed bundle exec rake db:migrate db:seed
bundle exec thrust puma -C config/puma.rb bundle exec thrust puma -C config/puma.rb
ports: ports:
- containerPort: 3000 - containerPort: 3000
@@ -54,17 +56,17 @@ spec:
name: loomio-secrets name: loomio-secrets
key: secretCookieToken key: secretCookieToken
- name: FORCE_SSL - name: FORCE_SSL
value: "{{ .forceSSL }}" value: "1"
- name: USE_RACK_ATTACK - name: USE_RACK_ATTACK
value: "{{ .useRackAttack }}" value: "1"
- name: PUMA_WORKERS - name: PUMA_WORKERS
value: "{{ .pumaWorkers }}" value: "2"
- name: MIN_THREADS - name: MIN_THREADS
value: "{{ .minThreads }}" value: "5"
- name: MAX_THREADS - name: MAX_THREADS
value: "{{ .maxThreads }}" value: "5"
- name: ACTIVE_STORAGE_SERVICE - name: ACTIVE_STORAGE_SERVICE
value: {{ .activeStorageService }} value: local
- name: SMTP_AUTH - name: SMTP_AUTH
value: {{ .smtp.auth }} value: {{ .smtp.auth }}
- name: SMTP_DOMAIN - name: SMTP_DOMAIN

View File

@@ -1,7 +1,7 @@
name: loomio name: loomio
is: loomio is: loomio
description: Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together description: Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together
version: 3.0.11 version: 3.0.11-2
icon: https://www.loomio.com/brand/logo_gold.svg icon: https://www.loomio.com/brand/logo_gold.svg
requires: requires:
- name: postgres - name: postgres
@@ -10,39 +10,30 @@ requires:
- name: smtp - name: smtp
defaultConfig: defaultConfig:
namespace: loomio namespace: loomio
externalDnsDomain: "{{ .cloud.domain }}" externalDnsDomain: '{{ .cloud.domain }}'
image: loomio/loomio:latest
workerImage: loomio/loomio:latest
appName: Loomio appName: Loomio
domain: "loomio.{{ .cloud.domain }}" domain: 'loomio.{{ .cloud.domain }}'
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
port: 3000
storage: storage:
uploads: 5Gi uploads: 5Gi
files: 5Gi files: 5Gi
plugins: 1Gi plugins: 1Gi
redisUrl: "{{ .apps.redis.uri }}" redisUrl: '{{ .apps.redis.uri }}'
adminEmail: "{{ .operator.email }}" adminEmail: '{{ .operator.email }}'
supportEmail: "{{ .operator.email }}" supportEmail: '{{ .operator.email }}'
forceSSL: "1"
useRackAttack: "1"
pumaWorkers: "2"
minThreads: "5"
maxThreads: "5"
activeStorageService: local
db: db:
name: loomio name: loomio
user: loomio user: loomio
host: "{{ .apps.postgres.host }}" host: '{{ .apps.postgres.host }}'
port: "{{ .apps.postgres.port }}" port: '{{ .apps.postgres.port }}'
smtp: smtp:
auth: plain auth: plain
domain: "{{ .cloud.domain }}" domain: '{{ .cloud.domain }}'
host: "{{ .apps.smtp.host }}" host: '{{ .apps.smtp.host }}'
port: "{{ .apps.smtp.port }}" port: '{{ .apps.smtp.port }}'
user: "{{ .apps.smtp.user }}" user: '{{ .apps.smtp.user }}'
tls: "{{ .apps.smtp.tls }}" tls: '{{ .apps.smtp.tls }}'
from: "{{ .apps.smtp.from }}" from: '{{ .apps.smtp.from }}'
defaultSecrets: defaultSecrets:
- key: dbPassword - key: dbPassword
default: "{{ random.AlphaNum 32 }}" default: "{{ random.AlphaNum 32 }}"

View File

@@ -27,9 +27,9 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
env: env:
- name: PGHOST - name: PGHOST
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: PGPORT - name: PGPORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: PGUSER - name: PGUSER
value: postgres value: postgres
- name: PGPASSWORD - name: PGPASSWORD
@@ -38,9 +38,9 @@ spec:
name: mastodon-secrets name: mastodon-secrets
key: postgres.password key: postgres.password
- name: MASTODON_DB - name: MASTODON_DB
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: MASTODON_USER - name: MASTODON_USER
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: MASTODON_PASSWORD - name: MASTODON_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -108,7 +108,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: db-migrate - name: db-migrate
image: {{ .image }} image: ghcr.io/mastodon/mastodon:v4.5.3
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -150,22 +150,22 @@ spec:
name: mastodon-secrets name: mastodon-secrets
key: activeRecordKeyDerivationSalt key: activeRecordKeyDerivationSalt
- name: DB_HOST - name: DB_HOST
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_PORT - name: DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DB_NAME - name: DB_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USER - name: DB_USER
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASS - name: DB_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: mastodon-secrets name: mastodon-secrets
key: dbPassword key: dbPassword
- name: REDIS_HOST - name: REDIS_HOST
value: "{{ .redisHostname }}" value: "{{ .redis.host }}"
- name: REDIS_PORT - name: REDIS_PORT
value: "{{ .redisPort }}" value: "{{ .redis.port }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: sidekiq - name: sidekiq
image: {{ .image }} image: ghcr.io/mastodon/mastodon:v4.5.3
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -33,7 +33,7 @@ spec:
- exec - exec
- sidekiq - sidekiq
- -c - -c
- "{{ .sidekiq.concurrency }}" - "25"
- -q - -q
- default,8 - default,8
- -q - -q
@@ -91,13 +91,13 @@ spec:
name: mastodon-secrets name: mastodon-secrets
key: activeRecordKeyDerivationSalt key: activeRecordKeyDerivationSalt
- name: DB_HOST - name: DB_HOST
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_PORT - name: DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DB_NAME - name: DB_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USER - name: DB_USER
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASS - name: DB_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -109,9 +109,9 @@ spec:
name: mastodon-secrets name: mastodon-secrets
key: postgres.password key: postgres.password
- name: REDIS_HOST - name: REDIS_HOST
value: "{{ .redisHostname }}" value: "{{ .redis.host }}"
- name: REDIS_PORT - name: REDIS_PORT
value: "{{ .redisPort }}" value: "{{ .redis.port }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -131,9 +131,9 @@ spec:
- name: SMTP_FROM_ADDRESS - name: SMTP_FROM_ADDRESS
value: "{{ .smtp.from }}" value: "{{ .smtp.from }}"
- name: SMTP_AUTH_METHOD - name: SMTP_AUTH_METHOD
value: "{{ .smtp.authMethod }}" value: "plain"
- name: SMTP_ENABLE_STARTTLS - name: SMTP_ENABLE_STARTTLS
value: "{{ .smtp.enableStarttls }}" value: "auto"
- name: SMTP_TLS - name: SMTP_TLS
value: "{{ .smtp.tls }}" value: "{{ .smtp.tls }}"
volumeMounts: volumeMounts:

View File

@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: streaming - name: streaming
image: {{ .streamingImage }} image: ghcr.io/mastodon/mastodon-streaming:v4.5.3
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -30,32 +30,32 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
ports: ports:
- name: streaming - name: streaming
containerPort: {{ .streamingPort }} containerPort: 4000
protocol: TCP protocol: TCP
env: env:
- name: NODE_ENV - name: NODE_ENV
value: production value: production
- name: PORT - name: PORT
value: "{{ .streamingPort }}" value: "4000"
- name: STREAMING_CLUSTER_NUM - name: STREAMING_CLUSTER_NUM
value: "1" value: "1"
- name: DB_HOST - name: DB_HOST
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_PORT - name: DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DB_NAME - name: DB_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USER - name: DB_USER
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASS - name: DB_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: mastodon-secrets name: mastodon-secrets
key: dbPassword key: dbPassword
- name: REDIS_HOST - name: REDIS_HOST
value: "{{ .redisHostname }}" value: "{{ .redis.host }}"
- name: REDIS_PORT - name: REDIS_PORT
value: "{{ .redisPort }}" value: "{{ .redis.port }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -22,7 +22,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: web - name: web
image: {{ .image }} image: ghcr.io/mastodon/mastodon:v4.5.3
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
@@ -36,7 +36,7 @@ spec:
- config/puma.rb - config/puma.rb
ports: ports:
- name: http - name: http
containerPort: {{ .webPort }} containerPort: 3000
protocol: TCP protocol: TCP
env: env:
- name: LOCAL_DOMAIN - name: LOCAL_DOMAIN
@@ -85,13 +85,13 @@ spec:
name: mastodon-secrets name: mastodon-secrets
key: activeRecordKeyDerivationSalt key: activeRecordKeyDerivationSalt
- name: DB_HOST - name: DB_HOST
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_PORT - name: DB_PORT
value: "{{ .dbPort }}" value: "{{ .db.port }}"
- name: DB_NAME - name: DB_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USER - name: DB_USER
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASS - name: DB_PASS
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -103,9 +103,9 @@ spec:
name: mastodon-secrets name: mastodon-secrets
key: postgres.password key: postgres.password
- name: REDIS_HOST - name: REDIS_HOST
value: "{{ .redisHostname }}" value: "{{ .redis.host }}"
- name: REDIS_PORT - name: REDIS_PORT
value: "{{ .redisPort }}" value: "{{ .redis.port }}"
- name: REDIS_PASSWORD - name: REDIS_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -125,9 +125,9 @@ spec:
- name: SMTP_FROM_ADDRESS - name: SMTP_FROM_ADDRESS
value: "{{ .smtp.from }}" value: "{{ .smtp.from }}"
- name: SMTP_AUTH_METHOD - name: SMTP_AUTH_METHOD
value: "{{ .smtp.authMethod }}" value: "plain"
- name: SMTP_ENABLE_STARTTLS - name: SMTP_ENABLE_STARTTLS
value: "{{ .smtp.enableStarttls }}" value: "auto"
- name: SMTP_TLS - name: SMTP_TLS
value: "{{ .smtp.tls }}" value: "{{ .smtp.tls }}"
- name: STREAMING_API_BASE_URL - name: STREAMING_API_BASE_URL

View File

@@ -23,11 +23,11 @@ spec:
service: service:
name: mastodon-streaming name: mastodon-streaming
port: port:
number: {{ .streamingPort }} number: 4000
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: mastodon-web name: mastodon-web
port: port:
number: {{ .webPort }} number: 3000

View File

@@ -1,7 +1,7 @@
name: mastodon name: mastodon
is: mastodon is: mastodon
description: Mastodon is a free, open-source social network server based on ActivityPub. description: Mastodon is a free, open-source social network server based on ActivityPub.
version: 4.5.3 version: 4.5.3-2
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mastodon.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mastodon.svg
requires: requires:
- name: postgres - name: postgres
@@ -9,43 +9,30 @@ requires:
- name: smtp - name: smtp
defaultConfig: defaultConfig:
namespace: mastodon namespace: mastodon
externalDnsDomain: "{{ .cloud.domain }}" externalDnsDomain: '{{ .cloud.domain }}'
timezone: UTC
image: ghcr.io/mastodon/mastodon:v4.5.3
streamingImage: ghcr.io/mastodon/mastodon-streaming:v4.5.3
domain: mastodon.{{ .cloud.domain }} domain: mastodon.{{ .cloud.domain }}
locale: en locale: en
singleUserMode: false singleUserMode: false
# Database configuration
dbHostname: "{{ .apps.postgres.host }}"
dbPort: "{{ .apps.postgres.port }}"
dbName: mastodon_production
dbUsername: mastodon
# Redis configuration
redisHostname: "{{ .apps.redis.host }}"
redisPort: "{{ .apps.redis.port }}"
# Ports
webPort: 3000
streamingPort: 4000
# Storage
assetsStorage: 10Gi assetsStorage: 10Gi
systemStorage: 100Gi systemStorage: 100Gi
# SMTP configuration
smtp:
enabled: "{{ .apps.smtp.host | ternary true false }}"
server: "{{ .apps.smtp.host }}"
port: "{{ .apps.smtp.port }}"
from: notifications@{{ .cloud.domain }}
user: "{{ .apps.smtp.user }}"
authMethod: plain
enableStarttls: auto
tls: "{{ .apps.smtp.tls }}"
# TLS
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
# Sidekiq configuration
sidekiq: sidekiq:
replicas: 1 replicas: 1
concurrency: 25 db:
host: '{{ .apps.postgres.host }}'
port: '{{ .apps.postgres.port }}'
name: mastodon_production
user: mastodon
redis:
host: '{{ .apps.redis.host }}'
port: '{{ .apps.redis.port }}'
smtp:
enabled: '{{ .apps.smtp.host | ternary true false }}'
server: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}'
from: notifications@{{ .cloud.domain }}
user: '{{ .apps.smtp.user }}'
tls: '{{ .apps.smtp.tls }}'
defaultSecrets: defaultSecrets:
- key: secretKeyBase - key: secretKeyBase
default: "{{ random.AlphaNum 128 }}" default: "{{ random.AlphaNum 128 }}"

View File

@@ -6,7 +6,7 @@ metadata:
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
- port: {{ .streamingPort }} - port: 4000
targetPort: streaming targetPort: streaming
protocol: TCP protocol: TCP
name: streaming name: streaming

View File

@@ -6,7 +6,7 @@ metadata:
spec: spec:
type: ClusterIP type: ClusterIP
ports: ports:
- port: {{ .webPort }} - port: 3000
targetPort: http targetPort: http
protocol: TCP protocol: TCP
name: http name: http

View File

@@ -20,7 +20,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: vapid-init - name: vapid-init
image: {{ .image }} image: ghcr.io/mastodon/mastodon:v4.5.3
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:

View File

@@ -8,7 +8,7 @@ data:
public_baseurl: https://{{ .domain }} public_baseurl: https://{{ .domain }}
listeners: listeners:
- port: {{ .port }} - port: 8008
tls: false tls: false
type: http type: http
x_forwarded: true x_forwarded: true
@@ -20,17 +20,17 @@ data:
database: database:
name: psycopg2 name: psycopg2
args: args:
user: {{ .dbUsername }} user: {{ .db.user }}
password: ${DB_PASSWORD} password: ${DB_PASSWORD}
database: {{ .dbName }} database: {{ .db.name }}
host: {{ .dbHostname }} host: {{ .db.host }}
port: 5432 port: 5432
cp_min: 5 cp_min: 5
cp_max: 10 cp_max: 10
redis: redis:
enabled: true enabled: true
host: {{ .redisHostname }} host: {{ .redis.host }}
port: 6379 port: 6379
password: ${REDIS_PASSWORD} password: ${REDIS_PASSWORD}

View File

@@ -33,11 +33,11 @@ spec:
name: matrix-secrets name: matrix-secrets
key: postgres.password key: postgres.password
- name: DB_HOSTNAME - name: DB_HOSTNAME
value: "{{ .dbHostname }}" value: "{{ .db.host }}"
- name: DB_DATABASE_NAME - name: DB_DATABASE_NAME
value: "{{ .dbName }}" value: "{{ .db.name }}"
- name: DB_USERNAME - name: DB_USERNAME
value: "{{ .dbUsername }}" value: "{{ .db.user }}"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -18,7 +18,7 @@ spec:
spec: spec:
initContainers: initContainers:
- name: generate-signing-key - name: generate-signing-key
image: "{{ .image }}" image: "matrixdotorg/synapse:v1.144.0"
command: ["/bin/sh", "-c"] command: ["/bin/sh", "-c"]
args: args:
- | - |
@@ -80,7 +80,7 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
containers: containers:
- name: synapse - name: synapse
image: "{{ .image }}" image: "matrixdotorg/synapse:v1.144.0"
command: ["/bin/sh", "-c"] command: ["/bin/sh", "-c"]
args: args:
- | - |
@@ -127,17 +127,17 @@ spec:
# Start Synapse with the processed config # Start Synapse with the processed config
exec /start.py exec /start.py
ports: ports:
- containerPort: {{ .port }} - containerPort: 8008
protocol: TCP protocol: TCP
name: http name: http
- containerPort: {{ .federationPort }} - containerPort: 8448
protocol: TCP protocol: TCP
name: federation name: federation
env: env:
- name: SYNAPSE_CONFIG_PATH - name: SYNAPSE_CONFIG_PATH
value: /data/homeserver.yaml value: /data/homeserver.yaml
- name: TZ - name: TZ
value: "{{ .timezone }}" value: "UTC"
- name: DB_PASSWORD - name: DB_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -179,14 +179,14 @@ spec:
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /health path: /health
port: {{ .port }} port: 8008
initialDelaySeconds: 60 initialDelaySeconds: 60
periodSeconds: 30 periodSeconds: 30
timeoutSeconds: 5 timeoutSeconds: 5
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /health path: /health
port: {{ .port }} port: 8008
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
timeoutSeconds: 5 timeoutSeconds: 5

View File

@@ -23,7 +23,7 @@ spec:
service: service:
name: matrix-synapse name: matrix-synapse
port: port:
number: {{ .port }} number: 8008
--- ---
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
@@ -49,4 +49,4 @@ spec:
service: service:
name: matrix-synapse name: matrix-synapse
port: port:
number: {{ .federationPort }} number: 8448

View File

@@ -1,7 +1,7 @@
name: matrix name: matrix
is: matrix is: matrix
description: Matrix is an open standard for secure, decentralized, real-time communication. This deploys the Synapse homeserver for self-hosted Matrix federation and messaging. description: Matrix is an open standard for secure, decentralized, real-time communication. This deploys the Synapse homeserver for self-hosted Matrix federation and messaging.
version: v1.144.0 version: v1.144.0-2
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/matrix.svg icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/matrix.svg
requires: requires:
- name: postgres - name: postgres
@@ -10,20 +10,18 @@ requires:
defaultConfig: defaultConfig:
namespace: matrix namespace: matrix
externalDnsDomain: '{{ .cloud.domain }}' externalDnsDomain: '{{ .cloud.domain }}'
image: matrixdotorg/synapse:v1.144.0
timezone: UTC
port: 8008
federationPort: 8448
storage: 50Gi storage: 50Gi
mediaStorage: 100Gi mediaStorage: 100Gi
serverName: '{{ .cloud.domain }}' serverName: '{{ .cloud.domain }}'
dbHostname: postgres.postgres.svc.cluster.local
dbUsername: matrix
dbName: matrix
redisHostname: redis.redis.svc.cluster.local
domain: matrix.{{ .cloud.domain }} domain: matrix.{{ .cloud.domain }}
tlsSecretName: wildcard-wild-cloud-tls tlsSecretName: wildcard-wild-cloud-tls
enableRegistration: false enableRegistration: false
db:
host: '{{ .apps.postgres.host }}'
name: matrix
user: matrix
redis:
host: '{{ .apps.redis.host }}'
smtp: smtp:
host: '{{ .apps.smtp.host }}' host: '{{ .apps.smtp.host }}'
port: '{{ .apps.smtp.port }}' port: '{{ .apps.smtp.port }}'

View File

@@ -7,12 +7,12 @@ spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: http - name: http
port: {{ .port }} port: 8008
targetPort: {{ .port }} targetPort: 8008
protocol: TCP protocol: TCP
- name: federation - name: federation
port: {{ .federationPort }} port: 8448
targetPort: {{ .federationPort }} targetPort: 8448
protocol: TCP protocol: TCP
selector: selector:
app: matrix-synapse app: matrix-synapse

View File

@@ -3,7 +3,7 @@ kind: Deployment
metadata: metadata:
name: memcached name: memcached
spec: spec:
replicas: {{ .replicas }} replicas: 1
selector: selector:
matchLabels: matchLabels:
component: cache component: cache
@@ -14,24 +14,24 @@ spec:
spec: spec:
containers: containers:
- name: memcached - name: memcached
image: "{{ .image }}" image: "memcached:1.6.32-alpine"
ports: ports:
- containerPort: {{ .port }} - containerPort: 11211
name: memcached name: memcached
args: args:
- -m - -m
- "{{ .memoryLimit }}" - "{{ .memoryLimit }}"
- -c - -c
- "{{ .maxConnections }}" - "1024"
- -p - -p
- "{{ .port }}" - "11211"
resources: resources:
requests: requests:
memory: "{{ .resources.requests.memory }}" memory: 64Mi
cpu: "{{ .resources.requests.cpu }}" cpu: 100m
limits: limits:
memory: "{{ .resources.limits.memory }}" memory: 128Mi
cpu: "{{ .resources.limits.cpu }}" cpu: 200m
securityContext: securityContext:
runAsNonRoot: true runAsNonRoot: true
runAsUser: 11211 runAsUser: 11211

View File

@@ -2,21 +2,11 @@ name: memcached
is: memcached is: memcached
description: Memcached is an in-memory key-value store for small chunks of arbitrary description: Memcached is an in-memory key-value store for small chunks of arbitrary
data, commonly used as a cache layer. data, commonly used as a cache layer.
version: 1.6.32 version: 1.6.32-1
icon: https://www.vectorlogo.zone/logos/memcached/memcached-icon.svg icon: https://www.vectorlogo.zone/logos/memcached/memcached-icon.svg
requires: [] requires: []
defaultConfig: defaultConfig:
namespace: memcached namespace: memcached
image: memcached:1.6.32-alpine host: memcached.memcached.svc.cluster.local
port: 11211
memoryLimit: 64m memoryLimit: 64m
maxConnections: 1024
replicas: 1
resources:
requests:
memory: 64Mi
cpu: 100m
limits:
memory: 128Mi
cpu: 200m
defaultSecrets: [] defaultSecrets: []

View File

@@ -4,8 +4,8 @@ metadata:
name: memcached name: memcached
spec: spec:
ports: ports:
- port: {{ .port }} - port: 11211
targetPort: {{ .port }} targetPort: 11211
protocol: TCP protocol: TCP
name: memcached name: memcached
selector: selector:

View File

@@ -1,20 +1,15 @@
name: mysql name: mysql
is: mysql is: mysql
description: MySQL is an open-source relational database management system description: MySQL is an open-source relational database management system
version: 9.1.0 version: 9.1.0-1
icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png
requires: [] requires: []
defaultConfig: defaultConfig:
namespace: mysql namespace: mysql
externalDnsDomain: '{{ .cloud.domain }}' host: mysql.mysql.svc.cluster.local
image: mysql:9.1.0
port: 3306
storage: 20Gi storage: 20Gi
dbName: mysql dbName: mysql
rootUser: root
user: mysql user: mysql
timezone: UTC
enableSSL: false
defaultSecrets: defaultSecrets:
- key: rootPassword - key: rootPassword
- key: password - key: password

View File

@@ -9,7 +9,7 @@ spec:
publishNotReadyAddresses: true publishNotReadyAddresses: true
ports: ports:
- name: mysql - name: mysql
port: {{ .port }} port: 3306
protocol: TCP protocol: TCP
targetPort: mysql targetPort: mysql
selector: selector:

View File

@@ -7,7 +7,7 @@ spec:
type: ClusterIP type: ClusterIP
ports: ports:
- name: mysql - name: mysql
port: {{ .port }} port: 3306
protocol: TCP protocol: TCP
targetPort: mysql targetPort: mysql
selector: selector:

View File

@@ -29,7 +29,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: mysql - name: mysql
image: {{ .image }} image: mysql:9.1.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
@@ -53,10 +53,10 @@ spec:
- name: MYSQL_DATABASE - name: MYSQL_DATABASE
value: {{ .dbName }} value: {{ .dbName }}
- name: TZ - name: TZ
value: {{ .timezone }} value: UTC
ports: ports:
- name: mysql - name: mysql
containerPort: {{ .port }} containerPort: 3306
protocol: TCP protocol: TCP
livenessProbe: livenessProbe:
exec: exec:

View File

@@ -1,10 +1,10 @@
# NFS Setup (Optional) # NFS Client Provisioner
The infrastructure supports optional NFS (Network File System) for shared media storage across the cluster. If your config.yaml contains the `cloud.nfs` section, the NFS server will be set up automatically. Provides shared NFS storage to the cluster by creating a StorageClass and PersistentVolume backed by an external NFS server. This is an infrastructure app — it has no pods or namespace, just cluster-scoped resources.
## Host Setup ## Prerequisites
First, set up the NFS server on your chosen host. You need an NFS server already running and exporting a path. To set one up on a host:
```bash ```bash
./setup-nfs-host.sh <host> <media-path> ./setup-nfs-host.sh <host> <media-path>
@@ -16,30 +16,32 @@ Example:
./setup-nfs-host.sh box-01 /srv/nfs ./setup-nfs-host.sh box-01 /srv/nfs
``` ```
## Cluster Integration This SSHs into the host, installs `nfs-kernel-server`, and configures the export.
Add to your `config.yaml`: ## Configuration
When added to an instance, the default config is merged into `config.yaml`:
```yaml ```yaml
cloud: apps:
nfs: nfs:
host: box-01 host: "192.168.1.100"
mediaPath: /srv/nfs mediaPath: "/mnt/storage/media"
storageCapacity: 250Gi # Max size for PersistentVolume storageCapacity: "1Ti"
``` ```
And now you can run the nfs cluster setup: Update `host` and `mediaPath` to match your NFS server before deploying.
```bash ## What Gets Deployed
setup/setup-nfs-host.sh
```
## Features - **StorageClass** (`nfs`) — allows PVCs to request NFS-backed storage
- **PersistentVolume** (`nfs-media-pv`) — a cluster-wide volume pointing to the NFS export
- Automatic IP detection - Uses network IP even when hostname resolves to localhost No namespace, pods, or services are created.
- Cluster-wide access - Any pod can mount the NFS share regardless of node placement
- Configurable capacity - Set PersistentVolume size via `NFS_STORAGE_CAPACITY` ## Scripts
- ReadWriteMany - Multiple pods can simultaneously access the same storage
- **check-nfs** — Verifies the NFS server is reachable, the export path exists, and checks whether the StorageClass and PersistentVolume are present in the cluster. Run from the app detail panel in the web UI.
## Usage ## Usage
@@ -58,3 +60,10 @@ spec:
requests: requests:
storage: 100Gi storage: 100Gi
``` ```
## Features
- Cluster-wide access — any pod can mount the NFS share regardless of node placement
- ReadWriteMany — multiple pods can simultaneously read and write
- Configurable capacity — set PersistentVolume size via `storageCapacity`
- Retain policy — data is preserved when volumes are released

View File

@@ -1,229 +0,0 @@
#!/bin/bash
set -e
set -o pipefail
if [ -z "${WILD_INSTANCE}" ]; then
echo "ERROR: WILD_INSTANCE is not set"
exit 1
fi
if [ -z "${WILD_API_DATA_DIR}" ]; then
echo "ERROR: WILD_API_DATA_DIR is not set"
exit 1
fi
if [ -z "${KUBECONFIG}" ]; then
echo "ERROR: KUBECONFIG is not set"
exit 1
fi
INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}"
CONFIG_FILE="${INSTANCE_DIR}/config.yaml"
NFS_DIR="${INSTANCE_DIR}/apps/nfs"
echo "=== Registering NFS Server with Kubernetes Cluster ==="
echo ""
echo "Using pre-compiled NFS templates..."
if [ ! -f "${NFS_DIR}/kustomization.yaml" ]; then
echo "ERROR: Compiled templates not found at ${NFS_DIR}"
echo "Templates should be compiled before deployment."
exit 1
fi
NFS_HOST="$(yq '.apps.nfs.host' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
NFS_MEDIA_PATH="$(yq '.apps.nfs.mediaPath' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
NFS_STORAGE_CAPACITY="$(yq '.apps.nfs.storageCapacity' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
echo "NFS Configuration:"
echo " Host: ${NFS_HOST}"
echo " Media path: ${NFS_MEDIA_PATH}"
echo " Storage capacity: ${NFS_STORAGE_CAPACITY}"
echo ""
if [ -z "${NFS_HOST}" ] || [ "${NFS_HOST}" = "null" ]; then
echo "ERROR: apps.nfs.host not set in config"
exit 1
fi
if [ -z "${NFS_MEDIA_PATH}" ] || [ "${NFS_MEDIA_PATH}" = "null" ]; then
echo "ERROR: apps.nfs.mediaPath not set in config"
exit 1
fi
if [ -z "${NFS_STORAGE_CAPACITY}" ] || [ "${NFS_STORAGE_CAPACITY}" = "null" ]; then
echo "ERROR: apps.nfs.storageCapacity not set in config"
exit 1
fi
resolve_nfs_host() {
echo "Resolving NFS host: ${NFS_HOST}"
if [[ "${NFS_HOST}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
NFS_HOST_IP="${NFS_HOST}"
echo " Host is already an IP address"
else
echo " Looking up hostname..."
NFS_HOST_IP=$(getent hosts "${NFS_HOST}" 2>/dev/null | awk '{print $1}' | head -n1 || true)
echo " Resolved to: ${NFS_HOST_IP}"
if [[ -z "${NFS_HOST_IP}" ]]; then
echo "ERROR: Unable to resolve hostname ${NFS_HOST} to IP address"
echo "Make sure ${NFS_HOST} is resolvable from this cluster"
exit 1
fi
if [[ "${NFS_HOST_IP}" =~ ^127\. ]]; then
echo "Warning: ${NFS_HOST} resolves to localhost (${NFS_HOST_IP})"
echo "Auto-detecting network IP for cluster access..."
local network_ip=$(ip route get 8.8.8.8 | grep -oP 'src \K\S+' 2>/dev/null)
if [[ -n "${network_ip}" && ! "${network_ip}" =~ ^127\. ]]; then
echo "Using detected network IP: ${network_ip}"
NFS_HOST_IP="${network_ip}"
else
echo "ERROR: Could not auto-detect network IP. Available IPs:"
ip addr show | grep "inet " | grep -v "127.0.0.1" | grep -v "10.42" | grep -v "172." | awk '{print " " $2}' | cut -d/ -f1
echo "Please set NFS_HOST to the correct IP address manually."
exit 1
fi
fi
fi
echo "NFS server IP: ${NFS_HOST_IP}"
export NFS_HOST_IP
}
test_nfs_accessibility() {
echo ""
echo "Testing NFS accessibility from cluster..."
if ! command -v showmount >/dev/null 2>&1; then
echo "Installing NFS client tools..."
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update && sudo apt-get install -y nfs-common
elif command -v yum >/dev/null 2>&1; then
sudo yum install -y nfs-utils
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y nfs-utils
else
echo "Warning: Unable to install NFS client tools. Skipping accessibility test."
return 0
fi
fi
echo "Testing connection to NFS server..."
if timeout 10 showmount -e "${NFS_HOST_IP}" >/dev/null 2>&1; then
echo "NFS server is accessible"
echo "Available exports:"
showmount -e "${NFS_HOST_IP}"
else
echo "ERROR: Cannot connect to NFS server at ${NFS_HOST_IP}"
echo "Make sure:"
echo " 1. NFS server is running on ${NFS_HOST}"
echo " 2. Network connectivity exists between cluster and NFS host"
echo " 3. Firewall allows NFS traffic (port 2049)"
exit 1
fi
if showmount -e "${NFS_HOST_IP}" | grep -q "${NFS_MEDIA_PATH}"; then
echo "Media path ${NFS_MEDIA_PATH} is exported"
else
echo "ERROR: Media path ${NFS_MEDIA_PATH} is not found in exports"
echo "Available exports:"
showmount -e "${NFS_HOST_IP}"
echo ""
echo "Run setup-nfs-host.sh on ${NFS_HOST} to configure the export"
exit 1
fi
}
test_nfs_mount() {
echo ""
echo "Testing NFS mount functionality..."
local test_mount="/tmp/nfs-test-$$"
mkdir -p "${test_mount}"
if timeout 30 sudo mount -t nfs4 "${NFS_HOST_IP}:${NFS_MEDIA_PATH}" "${test_mount}"; then
echo "NFS mount successful"
if ls "${test_mount}" >/dev/null 2>&1; then
echo "NFS read access working"
else
echo "ERROR: NFS read access failed"
fi
sudo umount "${test_mount}" || echo "Warning: Failed to unmount test directory"
else
echo "ERROR: NFS mount failed"
echo "Check NFS server configuration and network connectivity"
exit 1
fi
rmdir "${test_mount}" 2>/dev/null || true
}
create_k8s_resources() {
echo ""
echo "Creating Kubernetes NFS resources..."
echo "Applying NFS manifests..."
kubectl apply -k "${NFS_DIR}/"
echo "NFS PersistentVolume and StorageClass created"
echo "Verifying Kubernetes resources..."
if kubectl get storageclass nfs >/dev/null 2>&1; then
echo "StorageClass 'nfs' created"
else
echo "ERROR: StorageClass 'nfs' not found"
exit 1
fi
if kubectl get pv nfs-media-pv >/dev/null 2>&1; then
echo "PersistentVolume 'nfs-media-pv' created"
kubectl get pv nfs-media-pv
else
echo "ERROR: PersistentVolume 'nfs-media-pv' not found"
exit 1
fi
}
show_usage_instructions() {
echo ""
echo "=== NFS Kubernetes Setup Complete ==="
echo ""
echo "NFS server ${NFS_HOST} (${NFS_HOST_IP}) has been registered with the cluster"
echo ""
echo "Kubernetes resources created:"
echo " - StorageClass: nfs"
echo " - PersistentVolume: nfs-media-pv (${NFS_STORAGE_CAPACITY}, ReadWriteMany)"
echo ""
echo "To use NFS storage in your applications:"
echo " 1. Set storageClassName: nfs in your PVC"
echo " 2. Use accessMode: ReadWriteMany for shared access"
echo ""
echo "Example PVC:"
echo "---"
echo "apiVersion: v1"
echo "kind: PersistentVolumeClaim"
echo "metadata:"
echo " name: my-nfs-pvc"
echo "spec:"
echo " accessModes:"
echo " - ReadWriteMany"
echo " storageClassName: nfs"
echo " resources:"
echo " requests:"
echo " storage: 10Gi"
echo ""
}
main() {
resolve_nfs_host
test_nfs_accessibility
test_nfs_mount
create_k8s_resources
show_usage_instructions
}
echo "Starting NFS setup process..."
main "$@"

View File

@@ -1,12 +1,13 @@
name: nfs name: nfs
is: nfs is: nfs
description: NFS client provisioner for external NFS storage description: NFS client provisioner for external NFS storage
version: v4.0.18 version: v4.0.18-3
deploymentName: ""
storageClassName: "nfs"
category: infrastructure category: infrastructure
scripts:
- name: check-nfs
path: scripts/check-nfs.sh
description: Verify NFS server is reachable and the export path is available
defaultConfig: defaultConfig:
namespace: nfs
host: "192.168.1.100" host: "192.168.1.100"
mediaPath: "/mnt/storage/media" mediaPath: "/mnt/storage/media"
storageCapacity: "1Ti" storageCapacity: "1Ti"

79
nfs/scripts/check-nfs.sh Executable file
View File

@@ -0,0 +1,79 @@
#!/bin/bash
# Verify NFS server is reachable and the export path is available.
# Run before or after deployment to validate NFS connectivity.
set -e
set -o pipefail
if [ -z "${WILD_INSTANCE}" ] || [ -z "${WILD_API_DATA_DIR}" ]; then
echo "ERROR: WILD_INSTANCE and WILD_API_DATA_DIR must be set"
exit 1
fi
CONFIG_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/config.yaml"
NFS_HOST="$(yq '.apps.nfs.host' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
NFS_PATH="$(yq '.apps.nfs.mediaPath' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
if [ -z "${NFS_HOST}" ] || [ "${NFS_HOST}" = "null" ]; then
echo "ERROR: apps.nfs.host not set in config"
exit 1
fi
if [ -z "${NFS_PATH}" ] || [ "${NFS_PATH}" = "null" ]; then
echo "ERROR: apps.nfs.mediaPath not set in config"
exit 1
fi
echo "NFS host: ${NFS_HOST}"
echo "NFS path: ${NFS_PATH}"
# Resolve hostname to IP
if [[ "${NFS_HOST}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
NFS_IP="${NFS_HOST}"
else
NFS_IP=$(getent hosts "${NFS_HOST}" 2>/dev/null | awk '{print $1}' | head -n1 || true)
if [ -z "${NFS_IP}" ]; then
echo "ERROR: Cannot resolve hostname ${NFS_HOST}"
exit 1
fi
echo "Resolved to: ${NFS_IP}"
fi
# Check showmount
if ! command -v showmount >/dev/null 2>&1; then
echo "WARNING: showmount not available, skipping export check"
else
echo ""
echo "Checking NFS exports..."
if timeout 10 showmount -e "${NFS_IP}" >/dev/null 2>&1; then
if showmount -e "${NFS_IP}" | grep -q "${NFS_PATH}"; then
echo "OK: ${NFS_PATH} is exported"
else
echo "ERROR: ${NFS_PATH} not found in NFS exports:"
showmount -e "${NFS_IP}"
exit 1
fi
else
echo "ERROR: Cannot reach NFS server at ${NFS_IP}:2049"
exit 1
fi
fi
# Check k8s resources if KUBECONFIG is available
if [ -n "${KUBECONFIG}" ]; then
echo ""
echo "Checking Kubernetes resources..."
if kubectl get storageclass nfs >/dev/null 2>&1; then
echo "OK: StorageClass 'nfs' exists"
else
echo "WARNING: StorageClass 'nfs' not found (deploy NFS first)"
fi
if kubectl get pv nfs-media-pv >/dev/null 2>&1; then
echo "OK: PersistentVolume 'nfs-media-pv' exists"
kubectl get pv nfs-media-pv --no-headers
else
echo "WARNING: PersistentVolume 'nfs-media-pv' not found (deploy NFS first)"
fi
fi
echo ""
echo "NFS check complete."

View File

@@ -19,7 +19,7 @@ spec:
type: RuntimeDefault type: RuntimeDefault
containers: containers:
- name: open-webui - name: open-webui
image: {{ .image }} image: ghcr.io/open-webui/open-webui:v0.9.5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
@@ -29,12 +29,12 @@ spec:
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
ports: ports:
- name: http - name: http
containerPort: {{ .port }} containerPort: 8080
env: env:
- name: WEBUI_AUTH - name: WEBUI_AUTH
value: "{{ .enableAuth }}" value: "true"
- name: ENABLE_SIGNUP - name: ENABLE_SIGNUP
value: "{{ .enableSignup }}" value: "false"
- name: OPENAI_API_BASE_URL - name: OPENAI_API_BASE_URL
value: "{{ .vllmApiUrl }}" value: "{{ .vllmApiUrl }}"
- name: OPENAI_API_KEY - name: OPENAI_API_KEY

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