Compare commits
13 Commits
b52e76eeeb
...
945d2225a2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
945d2225a2 | ||
|
|
6e1c676c09 | ||
|
|
6b5325c6f3 | ||
|
|
e2e3f730a5 | ||
|
|
46002ff273 | ||
|
|
acec744df8 | ||
|
|
12e87635c6 | ||
|
|
351dff14d4 | ||
|
|
0645624ded | ||
|
|
afa21ef650 | ||
|
|
5733c20098 | ||
|
|
54abfdd469 | ||
|
|
e4c24d4a8c |
162
ADDING-APPS.md
162
ADDING-APPS.md
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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
|
||||||
|
|||||||
89
cert-manager/scripts/repair-certificates.sh
Executable file
89
cert-manager/scripts/repair-certificates.sh
Executable 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"
|
||||||
13286
cert-manager/upstream/cert-manager.yaml
Normal file
13286
cert-manager/upstream/cert-manager.yaml
Normal file
File diff suppressed because it is too large
Load Diff
30
cert-manager/upstream/kustomization.yaml
Normal file
30
cert-manager/upstream/kustomization.yaml
Normal 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"
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ""
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ spec:
|
|||||||
service:
|
service:
|
||||||
name: decidim
|
name: decidim
|
||||||
port:
|
port:
|
||||||
number: {{ .port }}
|
number: 3000
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
72
e2e-test-app/.versions/1.0.0-1/db-init-job.yaml
Normal file
72
e2e-test-app/.versions/1.0.0-1/db-init-job.yaml
Normal 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"
|
||||||
55
e2e-test-app/.versions/1.0.0-1/deployment.yaml
Normal file
55
e2e-test-app/.versions/1.0.0-1/deployment.yaml
Normal 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
|
||||||
15
e2e-test-app/.versions/1.0.0-1/kustomization.yaml
Normal file
15
e2e-test-app/.versions/1.0.0-1/kustomization.yaml
Normal 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
|
||||||
23
e2e-test-app/.versions/1.0.0-1/manifest.yaml
Normal file
23
e2e-test-app/.versions/1.0.0-1/manifest.yaml
Normal 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
|
||||||
4
e2e-test-app/.versions/1.0.0-1/namespace.yaml
Normal file
4
e2e-test-app/.versions/1.0.0-1/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
e2e-test-app/.versions/1.0.0-1/pvc.yaml
Normal file
11
e2e-test-app/.versions/1.0.0-1/pvc.yaml
Normal 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/.versions/1.0.0-1/service.yaml
Normal file
11
e2e-test-app/.versions/1.0.0-1/service.yaml
Normal 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
|
||||||
72
e2e-test-app/db-init-job.yaml
Normal file
72
e2e-test-app/db-init-job.yaml
Normal 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"
|
||||||
55
e2e-test-app/deployment.yaml
Normal file
55
e2e-test-app/deployment.yaml
Normal 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
|
||||||
15
e2e-test-app/kustomization.yaml
Normal file
15
e2e-test-app/kustomization.yaml
Normal 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
|
||||||
32
e2e-test-app/manifest.yaml
Normal file
32
e2e-test-app/manifest.yaml
Normal 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
|
||||||
4
e2e-test-app/namespace.yaml
Normal file
4
e2e-test-app/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .namespace }}
|
||||||
11
e2e-test-app/pvc.yaml
Normal file
11
e2e-test-app/pvc.yaml
Normal 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
11
e2e-test-app/service.yaml
Normal 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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}'
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}'
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ spec:
|
|||||||
component: web
|
component: web
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
targetPort: {{ .port }}
|
targetPort: 4000
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ spec:
|
|||||||
component: backend
|
component: backend
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: {{ .backendPort }}
|
port: 8536
|
||||||
targetPort: {{ .backendPort }}
|
targetPort: 8536
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ spec:
|
|||||||
component: pictrs
|
component: pictrs
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: {{ .pictrsPort }}
|
port: 8080
|
||||||
targetPort: {{ .pictrsPort }}
|
targetPort: 8080
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ spec:
|
|||||||
component: ui
|
component: ui
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
port: {{ .uiPort }}
|
port: 1234
|
||||||
targetPort: {{ .uiPort }}
|
targetPort: 1234
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
9
longhorn/backup-target.yaml
Normal file
9
longhorn/backup-target.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: longhorn.io/v1beta2
|
||||||
|
kind: BackupTarget
|
||||||
|
metadata:
|
||||||
|
name: default
|
||||||
|
namespace: longhorn-system
|
||||||
|
spec:
|
||||||
|
backupTargetURL: "{{ .backupTarget }}"
|
||||||
|
credentialSecret: ""
|
||||||
|
pollInterval: 5m0s
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }}'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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: []
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
229
nfs/install.sh
229
nfs/install.sh
@@ -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 "$@"
|
|
||||||
@@ -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
79
nfs/scripts/check-nfs.sh
Executable 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."
|
||||||
@@ -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
Reference in New Issue
Block a user