Compare commits
1 Commits
12e87635c6
...
mailu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b0c56f720 |
@@ -121,6 +121,15 @@ 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")
|
||||
|
||||
##### SMTP Configuration (SMTP Service):
|
||||
|
||||
- cloud.smtp.host - SMTP server hostname
|
||||
- cloud.smtp.port - SMTP port (typically "465" or "587")
|
||||
- cloud.smtp.user - SMTP username
|
||||
- cloud.smtp.from - Default 'from' email address
|
||||
- cloud.smtp.tls - Enable TLS (true/false)
|
||||
- cloud.smtp.startTls - Enable STARTTLS (true/false)
|
||||
|
||||
###### Backup Configuration:
|
||||
|
||||
- cloud.backup.root - Root path for backups
|
||||
@@ -205,7 +214,8 @@ Configuration Flow
|
||||
- ExternalDNS → cluster.externalDns.ownerId
|
||||
- NFS → cloud.nfs.*
|
||||
- Docker Registry → cloud.dockerRegistryHost, cluster.dockerRegistry.storage
|
||||
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest (including SMTP as an infrastructure app at apps.smtp.*)
|
||||
- SMTP → cloud.smtp.*
|
||||
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest
|
||||
|
||||
#### Manifest App Reference Resolution:
|
||||
|
||||
@@ -359,44 +369,6 @@ When apps need database URLs with embedded credentials, **always use a dedicated
|
||||
|
||||
Add `apps.myapp.dbUrl` to your manifest's `defaultSecrets`, and the system will generate the complete URL with embedded credentials automatically when the app is added.
|
||||
|
||||
### Backup/Restore Database Name Conventions
|
||||
|
||||
Wild Cloud's backup/restore system uses blue-green deployments. During restore, a standby copy of the app is created with a colored database name (e.g., `myapp_green`). The system automatically patches env vars in your Kubernetes resources to point to the standby database.
|
||||
|
||||
**How it works:** The restore system compiles your kustomize resources, finds env vars whose values match the original database name, and generates kustomize JSON patches to replace them with the standby database name. It uses env var naming conventions to distinguish database name fields from username fields (since both often have the same value).
|
||||
|
||||
**Env var naming guidelines for database-related fields:**
|
||||
|
||||
- **Database name env vars** should contain one of: `DATABASE`, `DB_NAME`, `DBNAME`, or `__DATABASE` in the env var name (e.g., `LISTMONK_db__database`, `DB_NAME`, `POSTGRES_DB`)
|
||||
- **Database URL env vars** are detected by containing `://` in the value (e.g., `postgresql://user:pass@host/dbname`)
|
||||
- **Username env vars** should contain `USER` in the name (e.g., `DB_USER`, `LISTMONK_db__user`) — these will NOT be patched even if the value matches the database name
|
||||
- Avoid env var names that are ambiguous about whether they hold a database name or username
|
||||
|
||||
**Example — correct naming:**
|
||||
```yaml
|
||||
env:
|
||||
- name: DB_NAME # Will be patched (contains "DB_NAME")
|
||||
value: myapp
|
||||
- name: DB_USER # Will NOT be patched (contains "USER")
|
||||
value: myapp
|
||||
- name: DATABASE_URL # Will be patched (contains "://")
|
||||
value: "postgresql://myapp:secret@postgres/myapp"
|
||||
```
|
||||
|
||||
## Deployment Strategy
|
||||
|
||||
Apps using `ReadWriteOnce` (RWO) persistent volumes **must** set `strategy: type: Recreate` on their Deployment. RWO volumes can only be attached to one pod at a time, so the default `RollingUpdate` strategy will cause Multi-Attach errors during updates (the new pod can't mount the volume while the old pod still holds it).
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
```
|
||||
|
||||
## Security Requirements
|
||||
|
||||
### Security Contexts
|
||||
@@ -554,7 +526,6 @@ Before submitting a new or modified app, verify:
|
||||
|
||||
- [ ] **Resources**
|
||||
- [ ] Security contexts on all pods (both pod-level and container-level)
|
||||
- [ ] `strategy: type: Recreate` on deployments with ReadWriteOnce PVCs
|
||||
- [ ] Simple component labels, no Helm-style labels
|
||||
- [ ] Ingresses include external-dns annotations
|
||||
- [ ] Database apps include init jobs (if applicable)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# 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,19 +0,0 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: wildcard-internal-wild-cloud
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
secretName: wildcard-internal-wild-cloud-tls
|
||||
dnsNames:
|
||||
- "*.{{ .internalDomain }}"
|
||||
- "{{ .internalDomain }}"
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 360h # 15 days
|
||||
privateKey:
|
||||
algorithm: RSA
|
||||
size: 2048
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- letsencrypt-staging-dns01.yaml
|
||||
- letsencrypt-prod-dns01.yaml
|
||||
- internal-wildcard-certificate.yaml
|
||||
- wildcard-certificate.yaml
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
email: {{ .email }}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
solvers:
|
||||
# DNS-01 solver for wildcard certificates
|
||||
- dns01:
|
||||
cloudflare:
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
selector:
|
||||
dnsZones:
|
||||
- "{{ .cloudflareDomain }}"
|
||||
# Keep the HTTP-01 solver for non-wildcard certificates
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
email: {{ .email }}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
solvers:
|
||||
# DNS-01 solver for wildcard certificates
|
||||
- dns01:
|
||||
cloudflare:
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
selector:
|
||||
dnsZones:
|
||||
- "{{ .cloudflareDomain }}"
|
||||
# Keep the HTTP-01 solver for non-wildcard certificates
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
@@ -1,30 +0,0 @@
|
||||
name: cert-manager
|
||||
is: cert-manager
|
||||
description: X.509 certificate management for Kubernetes
|
||||
version: v1.17.2
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: traefik
|
||||
defaultConfig:
|
||||
namespace: cert-manager
|
||||
cloudDomain: "{{ .cloud.domain }}"
|
||||
internalDomain: "{{ .cloud.internalDomain }}"
|
||||
email: "{{ .operator.email }}"
|
||||
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:
|
||||
- key: cloudflareToken
|
||||
deploy:
|
||||
phases:
|
||||
- path: upstream
|
||||
waitFor:
|
||||
name: cert-manager-webhook
|
||||
timeout: "120s"
|
||||
- path: .
|
||||
createSecrets:
|
||||
- name: cloudflare-api-token
|
||||
entries:
|
||||
api-token: cloudflareToken
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Repair stuck certificates, orphaned ACME orders, and Cloudflare DNS errors.
|
||||
# This is an operational maintenance script, not part of deployment.
|
||||
# Run manually when cert-manager has issues with certificate issuance.
|
||||
#
|
||||
# Usage: KUBECONFIG=/path/to/kubeconfig ./repair-certificates.sh
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
if [ -z "${KUBECONFIG}" ]; then
|
||||
echo "ERROR: KUBECONFIG is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
needs_restart=false
|
||||
|
||||
echo "=== cert-manager Certificate Repair ==="
|
||||
echo ""
|
||||
|
||||
echo "Checking for certificates with failed issuance attempts..."
|
||||
stuck_certs=$(kubectl get certificates --all-namespaces -o json 2>/dev/null | \
|
||||
jq -r '.items[] | select(.status.conditions[]? | select(.type=="Issuing" and .status=="False" and (.message | contains("404")))) | "\(.metadata.namespace) \(.metadata.name)"')
|
||||
|
||||
if [ -n "$stuck_certs" ]; then
|
||||
echo "WARNING: Found certificates stuck with non-existent orders, recreating them..."
|
||||
echo "$stuck_certs" | while read ns name; do
|
||||
echo "Recreating certificate $ns/$name..."
|
||||
cert_spec=$(kubectl get certificate "$name" -n "$ns" -o json | jq '.spec')
|
||||
kubectl delete certificate "$name" -n "$ns"
|
||||
echo "{\"apiVersion\":\"cert-manager.io/v1\",\"kind\":\"Certificate\",\"metadata\":{\"name\":\"$name\",\"namespace\":\"$ns\"},\"spec\":$cert_spec}" | kubectl apply -f -
|
||||
done
|
||||
needs_restart=true
|
||||
sleep 5
|
||||
else
|
||||
echo "No certificates stuck with failed orders"
|
||||
fi
|
||||
|
||||
echo "Checking for orphaned ACME orders..."
|
||||
orphaned_orders=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \
|
||||
grep -E "failed to retrieve the ACME order.*404" 2>/dev/null | \
|
||||
sed -n 's/.*resource_name="\([^"]*\)".*/\1/p' | \
|
||||
sort -u || true)
|
||||
|
||||
if [ -n "$orphaned_orders" ]; then
|
||||
echo "WARNING: Found orphaned ACME orders from logs"
|
||||
for order in $orphaned_orders; do
|
||||
echo "Deleting orphaned order: $order"
|
||||
orders_found=$(kubectl get orders --all-namespaces 2>/dev/null | grep "$order" 2>/dev/null || true)
|
||||
if [ -n "$orders_found" ]; then
|
||||
echo "$orders_found" | while read ns name rest; do
|
||||
kubectl delete order "$name" -n "$ns" 2>/dev/null || true
|
||||
done
|
||||
fi
|
||||
done
|
||||
needs_restart=true
|
||||
else
|
||||
echo "No orphaned orders found in logs"
|
||||
fi
|
||||
|
||||
echo "Checking for Cloudflare DNS cleanup errors..."
|
||||
cloudflare_errors=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \
|
||||
grep -c "Error: 7003.*Could not route" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$cloudflare_errors" -gt "0" ]; then
|
||||
echo "WARNING: Found $cloudflare_errors Cloudflare DNS cleanup errors (stale DNS record references)"
|
||||
echo "Deleting stuck challenges and orders to allow fresh start"
|
||||
|
||||
kubectl delete challenges --all -n cert-manager 2>/dev/null || true
|
||||
kubectl delete orders --all -n cert-manager 2>/dev/null || true
|
||||
|
||||
needs_restart=true
|
||||
else
|
||||
echo "No Cloudflare DNS cleanup errors"
|
||||
fi
|
||||
|
||||
if [ "$needs_restart" = true ]; then
|
||||
echo "Restarting cert-manager to clear internal state..."
|
||||
kubectl rollout restart deployment cert-manager -n cert-manager
|
||||
kubectl rollout status deployment/cert-manager -n cert-manager --timeout=120s
|
||||
echo "Waiting for cert-manager to recreate fresh challenges..."
|
||||
sleep 15
|
||||
else
|
||||
echo "No restart needed - cert-manager state is clean"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Repair complete. Check certificate status with:"
|
||||
echo " kubectl get certificates --all-namespaces"
|
||||
echo " kubectl get clusterissuers"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
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"
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: wildcard-wild-cloud
|
||||
namespace: cert-manager
|
||||
spec:
|
||||
secretName: wildcard-wild-cloud-tls
|
||||
dnsNames:
|
||||
- "*.{{ .cloudDomain }}"
|
||||
- "{{ .cloudDomain }}"
|
||||
issuerRef:
|
||||
name: letsencrypt-prod
|
||||
kind: ClusterIssuer
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 360h # 15 days
|
||||
privateKey:
|
||||
algorithm: RSA
|
||||
size: 2048
|
||||
@@ -1,45 +0,0 @@
|
||||
# CoreDNS
|
||||
|
||||
- https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
|
||||
- https://github.com/kubernetes/dns/blob/master/docs/specification.md
|
||||
- https://coredns.io/
|
||||
|
||||
CoreDNS has the `kubernetes` plugin, so it returns all k8s service endpoints in well-known format.
|
||||
|
||||
All services and pods are registered in CoreDNS.
|
||||
|
||||
- <service-name>.<namespace>.svc.cluster.local
|
||||
- <service-name>.<namespace>
|
||||
- <service-name> (if in the same namespace)
|
||||
|
||||
- <pod-ipv4-address>.<namespace>.pod.cluster.local
|
||||
- <pod-ipv4-address>.<service-name>.<namespace>.svc.cluster.local
|
||||
|
||||
Any query for a resource in the `internal.$DOMAIN` domain will be given the IP of the Traefik proxy. We expose the CoreDNS server in the LAN via MetalLB just for this capability.
|
||||
|
||||
## Default CoreDNS Configuration
|
||||
|
||||
This is the default CoreDNS configuration, for reference:
|
||||
|
||||
```txt
|
||||
.:53 {
|
||||
errors
|
||||
health { lameduck 5s }
|
||||
ready
|
||||
log . { class error }
|
||||
prometheus :9153
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
ttl 30
|
||||
}
|
||||
forward . /etc/resolv.conf { max_concurrent 1000 }
|
||||
cache 30 {
|
||||
disable success cluster.local
|
||||
disable denial cluster.local
|
||||
}
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
```
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: coredns-custom
|
||||
namespace: kube-system
|
||||
data:
|
||||
# Custom server block for internal domains. All internal domains should
|
||||
# resolve to the cluster proxy.
|
||||
internal.server: |
|
||||
{{ .internalDomain }} {
|
||||
errors
|
||||
cache 30
|
||||
reload
|
||||
template IN A {
|
||||
match (.*)\.{{ .internalDomain | strings.ReplaceAll "." "\\." }}\.
|
||||
answer "{{`{{ .Name }}`}} 60 IN A {{ .loadBalancerIp }}"
|
||||
}
|
||||
template IN AAAA {
|
||||
match (.*)\.{{ .internalDomain | strings.ReplaceAll "." "\\." }}\.
|
||||
rcode NXDOMAIN
|
||||
}
|
||||
}
|
||||
# Custom override to set external resolvers.
|
||||
external.override: |
|
||||
forward . {{ .externalResolver }} {
|
||||
max_concurrent 1000
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- coredns-custom-config.yaml
|
||||
@@ -1,17 +0,0 @@
|
||||
name: coredns
|
||||
is: coredns
|
||||
description: DNS server for internal cluster DNS resolution
|
||||
version: v1.12.0
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: metallb
|
||||
defaultConfig:
|
||||
namespace: kube-system
|
||||
internalDomain: "{{ .cloud.internalDomain }}"
|
||||
loadBalancerIp: "{{ .apps.metallb.loadBalancerIp }}"
|
||||
externalResolver: "8.8.8.8"
|
||||
deploy:
|
||||
restartDeployments:
|
||||
- coredns
|
||||
waitForRollout:
|
||||
name: coredns
|
||||
@@ -1,118 +0,0 @@
|
||||
# CrowdSec Security Service
|
||||
|
||||
CrowdSec is an open-source security engine that analyzes traffic patterns and blocks malicious actors. This service integrates CrowdSec with Traefik to provide automatic threat detection and rate limiting for all Wild Cloud ingresses.
|
||||
|
||||
## Components
|
||||
|
||||
- **CrowdSec Agent**: Analyzes traffic patterns, maintains decision lists, and connects to the CrowdSec threat intelligence network
|
||||
- **Traefik Bouncer**: Integrates with Traefik via ForwardAuth to enforce CrowdSec decisions
|
||||
- **Security Middlewares**: Traefik middleware for rate limiting and security headers
|
||||
|
||||
## Default Protection
|
||||
|
||||
After installation, **all ingresses are automatically protected** with:
|
||||
- Threat detection (blocks known malicious IPs and attack patterns)
|
||||
- Rate limiting (100 requests per minute per IP)
|
||||
- Security headers (HSTS, XSS protection, content-type sniffing prevention)
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is stored in `config.yaml` under `apps.crowdsec`:
|
||||
|
||||
```yaml
|
||||
apps:
|
||||
crowdsec:
|
||||
rateLimitAverage: "100"
|
||||
rateLimitBurst: "100"
|
||||
```
|
||||
|
||||
## Secrets
|
||||
|
||||
Secrets are stored in `secrets.yaml` under `apps.crowdsec`:
|
||||
|
||||
```yaml
|
||||
apps:
|
||||
crowdsec:
|
||||
agentPassword: <auto-generated>
|
||||
bouncerApiKey: <auto-generated>
|
||||
```
|
||||
|
||||
## Opting Out
|
||||
|
||||
To disable CrowdSec protection for a specific ingress (e.g., webhooks, health checks):
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.middlewares: ""
|
||||
```
|
||||
|
||||
## Using Only Rate Limiting
|
||||
|
||||
To use rate limiting without threat detection:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.middlewares: crowdsec-rate-limit@kubernetescrd
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
View active decisions (blocked IPs):
|
||||
```bash
|
||||
kubectl exec -n crowdsec deploy/crowdsec -- cscli decisions list
|
||||
```
|
||||
|
||||
View registered bouncers:
|
||||
```bash
|
||||
kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers list
|
||||
```
|
||||
|
||||
View alerts:
|
||||
```bash
|
||||
kubectl exec -n crowdsec deploy/crowdsec -- cscli alerts list
|
||||
```
|
||||
|
||||
View metrics (Prometheus format):
|
||||
```bash
|
||||
kubectl port-forward -n crowdsec svc/crowdsec-lapi 6060:6060
|
||||
curl http://localhost:6060/metrics
|
||||
```
|
||||
|
||||
## Threat Intelligence
|
||||
|
||||
CrowdSec includes these detection collections:
|
||||
- `crowdsecurity/traefik` - Traefik-specific detections
|
||||
- `crowdsecurity/http-cve` - Known HTTP CVE exploits
|
||||
- `crowdsecurity/whitelist-good-actors` - Whitelist for known good actors (search engines, etc.)
|
||||
|
||||
Enabled scenarios:
|
||||
- HTTP probing and path traversal detection
|
||||
- Bad user agent detection
|
||||
- Sensitive file access attempts
|
||||
- HTTP crawling detection
|
||||
- SSH brute force (if exposed)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bouncer not connecting to agent:**
|
||||
```bash
|
||||
kubectl logs -n crowdsec deploy/traefik-crowdsec-bouncer
|
||||
kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers list
|
||||
```
|
||||
|
||||
**Check if middleware is applied:**
|
||||
```bash
|
||||
kubectl get middleware -n crowdsec
|
||||
kubectl describe ingressroute -n <app-namespace> <route-name>
|
||||
```
|
||||
|
||||
**View CrowdSec logs:**
|
||||
```bash
|
||||
kubectl logs -n crowdsec deploy/crowdsec
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: crowdsec-config
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
data:
|
||||
acquis.yaml: |
|
||||
filenames:
|
||||
- /var/log/containers/traefik-*_traefik_*.log
|
||||
force_inotify: true
|
||||
poll_without_inotify: true
|
||||
labels:
|
||||
type: containerd
|
||||
program: traefik
|
||||
profiles.yaml: |
|
||||
name: default_ip_remediation
|
||||
debug: false
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Ip"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
on_success: break
|
||||
---
|
||||
name: default_range_remediation
|
||||
debug: false
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Range"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: 4h
|
||||
scope: Range
|
||||
on_success: break
|
||||
postoverflows.yaml: |
|
||||
# Post-overflow configuration for crowdsec
|
||||
name: "rdns"
|
||||
debug: false
|
||||
filter: "evt.Enriched.IsoCode != ''"
|
||||
# Add reverse DNS enrichment
|
||||
@@ -1,134 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: crowdsec
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
serviceAccountName: crowdsec
|
||||
affinity:
|
||||
podAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app: traefik
|
||||
topologyKey: kubernetes.io/hostname
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsNonRoot: false
|
||||
fsGroup: 0
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: crowdsec
|
||||
image: crowdsecurity/crowdsec:v1.7.8
|
||||
env:
|
||||
- name: COLLECTIONS
|
||||
value: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors crowdsecurity/iptables crowdsecurity/linux"
|
||||
- name: PARSERS
|
||||
value: "crowdsecurity/traefik-logs crowdsecurity/http-logs crowdsecurity/nginx-logs"
|
||||
- name: SCENARIOS
|
||||
value: "crowdsecurity/http-crawl-non_statics crowdsecurity/http-probing crowdsecurity/http-sensitive-files crowdsecurity/http-bad-user-agent crowdsecurity/http-path-traversal-probing crowdsecurity/ssh-bf crowdsecurity/ssh-slow-bf"
|
||||
- name: POSTOVERFLOWS
|
||||
value: "crowdsecurity/rdns crowdsecurity/cdn-whitelist"
|
||||
- name: GID
|
||||
value: "1000"
|
||||
- name: LEVEL_TRACE
|
||||
value: "false"
|
||||
- name: LEVEL_DEBUG
|
||||
value: "false"
|
||||
- name: LEVEL_INFO
|
||||
value: "true"
|
||||
- name: AGENT_USERNAME
|
||||
value: "kubernetes-cluster"
|
||||
- name: AGENT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crowdsec-agent-secret
|
||||
key: password
|
||||
- name: BOUNCER_KEY_traefik
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crowdsec-secrets
|
||||
key: bouncerApiKey
|
||||
optional: true
|
||||
ports:
|
||||
- name: lapi
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: prometheus
|
||||
containerPort: 6060
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 200Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: false
|
||||
volumeMounts:
|
||||
- name: crowdsec-config
|
||||
mountPath: /etc/crowdsec/acquis.yaml
|
||||
subPath: acquis.yaml
|
||||
readOnly: true
|
||||
- name: crowdsec-config
|
||||
mountPath: /etc/crowdsec/profiles.yaml
|
||||
subPath: profiles.yaml
|
||||
readOnly: true
|
||||
- name: crowdsec-data
|
||||
mountPath: /var/lib/crowdsec/data
|
||||
- name: crowdsec-config-dir
|
||||
mountPath: /etc/crowdsec/config
|
||||
- name: varlog
|
||||
mountPath: /var/log
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: crowdsec-config
|
||||
configMap:
|
||||
name: crowdsec-config
|
||||
- name: crowdsec-data
|
||||
persistentVolumeClaim:
|
||||
claimName: crowdsec-data
|
||||
- name: crowdsec-config-dir
|
||||
emptyDir: {}
|
||||
- name: varlog
|
||||
hostPath:
|
||||
path: /var/log
|
||||
@@ -1,24 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: crowdsec-lapi
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
ports:
|
||||
- name: lapi
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
protocol: TCP
|
||||
- name: prometheus
|
||||
port: 6060
|
||||
targetPort: 6060
|
||||
protocol: TCP
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: "{{ .namespace }}"
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- serviceaccount.yaml
|
||||
- configmap.yaml
|
||||
- pvc.yaml
|
||||
- crowdsec-deployment.yaml
|
||||
- crowdsec-service.yaml
|
||||
- middleware.yaml
|
||||
@@ -1,30 +0,0 @@
|
||||
name: crowdsec
|
||||
is: crowdsec
|
||||
description: CrowdSec security engine with Traefik bouncer for threat detection and rate limiting
|
||||
version: v1.7.8
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: longhorn
|
||||
- name: traefik
|
||||
defaultConfig:
|
||||
namespace: crowdsec
|
||||
rateLimitAverage: "100"
|
||||
rateLimitBurst: "100"
|
||||
defaultSecrets:
|
||||
- key: agentPassword
|
||||
- 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,89 +0,0 @@
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: crowdsec-bouncer
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
plugin:
|
||||
bouncer:
|
||||
crowdsecLapiScheme: http
|
||||
crowdsecLapiHost: crowdsec-lapi.crowdsec.svc.cluster.local:8080
|
||||
crowdsecLapiKeyFile: /etc/traefik/crowdsec/api-key
|
||||
crowdsecMode: stream
|
||||
updateIntervalSeconds: 15
|
||||
defaultDecisionSeconds: 60
|
||||
crowdsecAppsecEnabled: false
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: rate-limit
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
rateLimit:
|
||||
average: {{ .rateLimitAverage }}
|
||||
burst: {{ .rateLimitBurst }}
|
||||
period: 1m
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: security-headers
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
headers:
|
||||
browserXssFilter: true
|
||||
contentTypeNosniff: true
|
||||
forceSTSHeader: true
|
||||
frameDeny: true
|
||||
sslRedirect: true
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
stsSeconds: 31536000
|
||||
addVaryHeader: true
|
||||
accessControlAllowMethods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- OPTIONS
|
||||
accessControlAllowOriginList:
|
||||
- "*"
|
||||
accessControlMaxAge: 100
|
||||
customRequestHeaders:
|
||||
X-Forwarded-Proto: https
|
||||
customResponseHeaders:
|
||||
Server: ""
|
||||
X-Robots-Tag: noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: security-chain
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
spec:
|
||||
chain:
|
||||
middlewares:
|
||||
- name: security-headers
|
||||
namespace: crowdsec
|
||||
- name: rate-limit
|
||||
namespace: crowdsec
|
||||
- name: crowdsec-bouncer
|
||||
namespace: crowdsec
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: crowdsec-data
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
volumeMode: Filesystem
|
||||
resources:
|
||||
requests:
|
||||
storage: 512Mi
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: crowdsec
|
||||
namespace: crowdsec
|
||||
labels:
|
||||
app: crowdsec
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing participatory processes and user data
|
||||
- **Redis** - Used for Sidekiq background job processing
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Decidim will be accessible (default: `decidim.{your-cloud-domain}`)
|
||||
- **siteName** - Display name for your platform (default: `Decidim`)
|
||||
- **systemAdminEmail** - System admin email (defaults to your operator email)
|
||||
- **storage** - Persistent volume size (default: `20Gi`)
|
||||
- **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
|
||||
@@ -8,7 +8,6 @@ requires:
|
||||
installed_as: postgres
|
||||
- name: redis
|
||||
installed_as: redis
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: decidim
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
@@ -26,12 +25,12 @@ defaultConfig:
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
enabled: true
|
||||
host: "{{ .apps.smtp.host }}"
|
||||
port: "{{ .apps.smtp.port }}"
|
||||
user: "{{ .apps.smtp.user }}"
|
||||
from: "{{ .apps.smtp.from }}"
|
||||
tls: "{{ .apps.smtp.tls }}"
|
||||
startTls: "{{ .apps.smtp.startTls }}"
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: systemAdminPassword
|
||||
- key: secretKeyBase
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Discourse
|
||||
|
||||
Discourse is a modern, open-source discussion platform designed for online communities and forums.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing application data
|
||||
- **Redis** - Used for caching and background jobs
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Discourse will be accessible (default: `discourse.{your-cloud-domain}`)
|
||||
- **adminEmail** - Admin account email (defaults to your operator email)
|
||||
- **adminUsername** - Admin account username (default: `admin`)
|
||||
- **siteName** - Your community name (default: `Community`)
|
||||
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Discourse will be available at:
|
||||
- `https://discourse.{your-cloud-domain}`
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add discourse
|
||||
wild app deploy discourse
|
||||
```
|
||||
|
||||
2. Log in with the admin credentials configured during setup
|
||||
|
||||
3. Complete the setup wizard to configure your community
|
||||
@@ -6,7 +6,6 @@ icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: redis
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: discourse
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
@@ -25,12 +24,12 @@ defaultConfig:
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
enabled: false
|
||||
host: "{{ .apps.smtp.host }}"
|
||||
port: "{{ .apps.smtp.port }}"
|
||||
user: "{{ .apps.smtp.user }}"
|
||||
from: "{{ .apps.smtp.from }}"
|
||||
tls: "{{ .apps.smtp.tls }}"
|
||||
startTls: "{{ .apps.smtp.startTls }}"
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: adminPassword
|
||||
- key: secretKeyBase
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: docker-registry
|
||||
labels:
|
||||
app: docker-registry
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: docker-registry
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: docker-registry
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- image: registry:3.0.0
|
||||
name: docker-registry
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
protocol: TCP
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/registry
|
||||
name: docker-registry-storage
|
||||
readOnly: false
|
||||
volumes:
|
||||
- name: docker-registry-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: docker-registry-pvc
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: docker-registry
|
||||
spec:
|
||||
rules:
|
||||
- host: {{ .host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: docker-registry
|
||||
port:
|
||||
number: 5000
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .host }}
|
||||
secretName: wildcard-internal-wild-cloud-tls
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: "{{ .namespace }}"
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: docker-registry
|
||||
managedBy: wild-cloud
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- ingress.yaml
|
||||
- service.yaml
|
||||
- namespace.yaml
|
||||
- pvc.yaml
|
||||
@@ -1,12 +0,0 @@
|
||||
name: docker-registry
|
||||
is: docker-registry
|
||||
description: Private Docker image registry for cluster
|
||||
version: "3.0.0"
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: traefik
|
||||
- name: cert-manager
|
||||
defaultConfig:
|
||||
namespace: docker-registry
|
||||
host: "registry.{{ .cloud.internalDomain }}"
|
||||
storage: "100Gi"
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: docker-registry-pvc
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
volumeMode: Filesystem
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage }}
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: docker-registry
|
||||
labels:
|
||||
app: docker-registry
|
||||
spec:
|
||||
ports:
|
||||
- port: 5000
|
||||
targetPort: 5000
|
||||
selector:
|
||||
app: docker-registry
|
||||
@@ -1,72 +0,0 @@
|
||||
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: {{ .dbHost }}
|
||||
- name: PGUSER
|
||||
value: postgres
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: e2e-test-app-secrets
|
||||
key: postgres.password
|
||||
- name: DB_NAME
|
||||
value: {{ .dbName }}
|
||||
- name: DB_USER
|
||||
value: {{ .dbUser }}
|
||||
- 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"
|
||||
@@ -1,55 +0,0 @@
|
||||
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
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
requires:
|
||||
- name: postgres
|
||||
defaultConfig:
|
||||
namespace: e2e-test-app
|
||||
domain: e2e-test-app.{{ .cloud.domain }}
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
storage: 1Gi
|
||||
dbHost: "{{ .apps.postgres.host }}"
|
||||
dbPort: "{{ .apps.postgres.port }}"
|
||||
dbName: e2e_test_app
|
||||
dbUser: e2e_test_app
|
||||
timezone: UTC
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
- key: dbUrl
|
||||
default: "postgres://{{ .app.dbUser }}:{{ .secrets.dbPassword }}@{{ .app.dbHost }}:{{ .app.dbPort }}/{{ .app.dbName }}?sslmode=disable"
|
||||
requiredSecrets:
|
||||
- postgres.password
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: e2e-test-app
|
||||
spec:
|
||||
selector:
|
||||
component: web
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
name: http
|
||||
@@ -1,9 +0,0 @@
|
||||
# Example Admin App
|
||||
|
||||
An example application deployed with internal-only access. This app is useful for testing Wild Cloud's internal ingress and TLS configuration.
|
||||
|
||||
The app uses the internal wildcard TLS certificate and is only accessible within your local network.
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, the app will be available at an internal domain on your Wild Cloud instance.
|
||||
@@ -1,5 +1,6 @@
|
||||
name: example-admin
|
||||
is: example
|
||||
install: true
|
||||
description: An example application that is deployed with internal-only access.
|
||||
version: 1.0.0
|
||||
defaultConfig:
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Example App
|
||||
|
||||
An example application deployed with public access. This app is useful for testing Wild Cloud's public ingress, TLS, and external DNS configuration.
|
||||
|
||||
The app uses the public wildcard TLS certificate and is accessible from the internet.
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, the app will be available at:
|
||||
- `https://example-app.{your-cloud-domain}`
|
||||
@@ -1,5 +1,6 @@
|
||||
name: example-app
|
||||
is: example
|
||||
install: true
|
||||
description: An example application that is deployed with public access.
|
||||
version: 1.0.0
|
||||
defaultConfig:
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# External DNS
|
||||
|
||||
See: https://github.com/kubernetes-sigs/external-dns
|
||||
|
||||
ExternalDNS allows you to keep selected zones (via --domain-filter) synchronized with Ingresses and Services of type=LoadBalancer and nodes in various DNS providers.
|
||||
|
||||
Currently, we are only configured to use CloudFlare.
|
||||
|
||||
Docs: https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/cloudflare.md
|
||||
|
||||
Any Ingress that has metatdata.annotions with
|
||||
external-dns.alpha.kubernetes.io/hostname: `<something>.${DOMAIN}`
|
||||
|
||||
will have Cloudflare records created by External DNS.
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: externaldns
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.13.4
|
||||
args:
|
||||
- --source=service
|
||||
- --source=ingress
|
||||
- --txt-owner-id={{ .ownerId }}
|
||||
- --provider=cloudflare
|
||||
- --domain-filter=payne.io
|
||||
#- --exclude-domains=internal.${DOMAIN}
|
||||
- --cloudflare-dns-records-per-page=5000
|
||||
- --publish-internal-services
|
||||
- --no-cloudflare-proxied
|
||||
- --log-level=debug
|
||||
env:
|
||||
- name: CF_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: cloudflare-api-token
|
||||
key: api-token
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: externaldns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: external-dns
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services", "endpoints", "pods"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: ["extensions", "networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "watch", "list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: external-dns-viewer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: external-dns
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: external-dns
|
||||
namespace: externaldns
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- externaldns-rbac.yaml
|
||||
- externaldns-cloudflare.yaml
|
||||
@@ -1,23 +0,0 @@
|
||||
name: externaldns
|
||||
is: externaldns
|
||||
description: Automatically configures DNS records for services
|
||||
version: v0.13.4
|
||||
deploymentName: external-dns
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: cert-manager
|
||||
defaultConfig:
|
||||
namespace: externaldns
|
||||
ownerId: "wild-cloud-{{ .cluster.name }}"
|
||||
defaultSecrets:
|
||||
- key: cloudflareToken
|
||||
requiredSecrets:
|
||||
- cert-manager.cloudflareToken
|
||||
deploy:
|
||||
createSecrets:
|
||||
- name: cloudflare-api-token
|
||||
entries:
|
||||
api-token: cert-manager.cloudflareToken
|
||||
waitForRollout:
|
||||
name: external-dns
|
||||
timeout: "60s"
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
@@ -1,33 +0,0 @@
|
||||
# Ghost
|
||||
|
||||
Ghost is a powerful app for new-media creators to publish, share, and grow a business around their content. It provides a clean writing experience with built-in membership and subscription features.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **MySQL** - Database for storing content and configuration
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Ghost will be accessible (default: `ghost.{your-cloud-domain}`)
|
||||
- **blogTitle** - Your blog's title (default: `My Blog`)
|
||||
- **adminEmail** - Admin account email (defaults to your operator email)
|
||||
- **storage** - Persistent volume size for content (default: `10Gi`)
|
||||
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Ghost will be available at:
|
||||
- `https://ghost.{your-cloud-domain}` - Public blog
|
||||
- `https://ghost.{your-cloud-domain}/ghost` - Admin panel
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add ghost
|
||||
wild app deploy ghost
|
||||
```
|
||||
|
||||
2. Navigate to the admin panel and create your first post
|
||||
@@ -6,7 +6,6 @@ version: 5.118.1
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png
|
||||
requires:
|
||||
- name: mysql
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: ghost
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
@@ -24,10 +23,10 @@ defaultConfig:
|
||||
blogTitle: My Blog
|
||||
timezone: UTC
|
||||
smtp:
|
||||
host: '{{ .apps.smtp.host }}'
|
||||
port: '{{ .apps.smtp.port }}'
|
||||
from: '{{ .apps.smtp.from }}'
|
||||
user: '{{ .apps.smtp.user }}'
|
||||
host: '{{ .cloud.smtp.host }}'
|
||||
port: '{{ .cloud.smtp.port }}'
|
||||
from: '{{ .cloud.smtp.from }}'
|
||||
user: '{{ .cloud.smtp.user }}'
|
||||
defaultSecrets:
|
||||
- key: adminPassword
|
||||
- key: dbPassword
|
||||
|
||||
@@ -5,7 +5,6 @@ version: 1.24.3
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: gitea
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
@@ -25,10 +24,10 @@ defaultConfig:
|
||||
timezone: UTC
|
||||
runMode: prod
|
||||
smtp:
|
||||
host: '{{ .apps.smtp.host }}'
|
||||
port: '{{ .apps.smtp.port }}'
|
||||
user: '{{ .apps.smtp.user }}'
|
||||
from: '{{ .apps.smtp.from }}'
|
||||
host: '{{ .cloud.smtp.host }}'
|
||||
port: '{{ .cloud.smtp.port }}'
|
||||
user: '{{ .cloud.smtp.user }}'
|
||||
from: '{{ .cloud.smtp.from }}'
|
||||
defaultSecrets:
|
||||
- key: adminPassword
|
||||
- key: dbPassword
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: headlamp
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: headlamp
|
||||
spec:
|
||||
serviceAccountName: headlamp-admin
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 100
|
||||
runAsGroup: 101
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: headlamp
|
||||
image: ghcr.io/headlamp-k8s/headlamp:v0.42.0
|
||||
args:
|
||||
- "-in-cluster"
|
||||
- "-plugins-dir=/headlamp/plugins"
|
||||
- "-kubeconfig=/home/headlamp/.kube/config"
|
||||
ports:
|
||||
- containerPort: 4466
|
||||
name: http
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 4466
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 4466
|
||||
initialDelaySeconds: 15
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
volumeMounts:
|
||||
- name: kubeconfig
|
||||
mountPath: /home/headlamp/.kube
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: kubeconfig
|
||||
configMap:
|
||||
name: headlamp-kubeconfig
|
||||
items:
|
||||
- key: kubeconfig
|
||||
path: config
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: internal-only
|
||||
namespace: headlamp
|
||||
spec:
|
||||
ipWhiteList:
|
||||
sourceRange:
|
||||
- 127.0.0.1/32
|
||||
- 10.0.0.0/8
|
||||
- 172.16.0.0/12
|
||||
- 192.168.0.0/16
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: headlamp-redirect-scheme
|
||||
namespace: headlamp
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: headlamp-https
|
||||
namespace: headlamp
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`headlamp.{{ .internalDomain }}`)
|
||||
kind: Rule
|
||||
middlewares:
|
||||
- name: internal-only
|
||||
namespace: headlamp
|
||||
services:
|
||||
- name: headlamp
|
||||
port: 80
|
||||
tls:
|
||||
secretName: wildcard-internal-wild-cloud-tls
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: headlamp-http
|
||||
namespace: headlamp
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`headlamp.{{ .internalDomain }}`)
|
||||
kind: Rule
|
||||
middlewares:
|
||||
- name: headlamp-redirect-scheme
|
||||
namespace: headlamp
|
||||
services:
|
||||
- name: headlamp
|
||||
port: 80
|
||||
@@ -1,24 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: headlamp-kubeconfig
|
||||
namespace: headlamp
|
||||
data:
|
||||
kubeconfig: |
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
server: https://kubernetes.default.svc
|
||||
name: in-cluster
|
||||
contexts:
|
||||
- context:
|
||||
cluster: in-cluster
|
||||
user: headlamp-admin
|
||||
name: in-cluster
|
||||
current-context: in-cluster
|
||||
users:
|
||||
- name: headlamp-admin
|
||||
user:
|
||||
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: "{{ .namespace }}"
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: headlamp
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- service-account.yaml
|
||||
- kubeconfig-cm.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
@@ -1,15 +0,0 @@
|
||||
name: headlamp
|
||||
is: headlamp
|
||||
description: Modern Kubernetes web UI (SIG UI) with in-cluster authentication
|
||||
version: v0.42.0
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: traefik
|
||||
- name: cert-manager
|
||||
defaultConfig:
|
||||
namespace: headlamp
|
||||
internalDomain: "{{ .cloud.internalDomain }}"
|
||||
deploy:
|
||||
waitForRollout:
|
||||
name: headlamp
|
||||
timeout: "120s"
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: "{{ .namespace }}"
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: headlamp-admin
|
||||
namespace: headlamp
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: headlamp-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: headlamp-admin
|
||||
namespace: headlamp
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,11 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: headlamp
|
||||
namespace: headlamp
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 4466
|
||||
selector:
|
||||
app: headlamp
|
||||
@@ -1,41 +1,7 @@
|
||||
# Immich
|
||||
# Immich App
|
||||
|
||||
Immich is a self-hosted photo and video backup solution that allows you to store, manage, and share your media files securely. It provides a mobile-first experience similar to Google Photos.
|
||||
## To Do
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing metadata and search indexes
|
||||
- **Redis** - Used for caching and background job queuing
|
||||
|
||||
## Components
|
||||
|
||||
Immich runs two services:
|
||||
|
||||
- **Server** - Main API and web server
|
||||
- **Machine Learning** - Handles facial recognition and smart search
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Immich will be accessible (default: `immich.{your-cloud-domain}`)
|
||||
- **storage** - Persistent volume for photos and videos (default: `250Gi`)
|
||||
- **cacheStorage** - Persistent volume for ML cache (default: `10Gi`)
|
||||
- **timezone** - Server timezone (default: `UTC`)
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Immich will be available at:
|
||||
- `https://immich.{your-cloud-domain}`
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add immich
|
||||
wild app deploy immich
|
||||
```
|
||||
|
||||
2. Create your account through the web interface
|
||||
|
||||
3. Download the Immich mobile app and connect it to your server for automatic photo backup
|
||||
- We need a full uninstall script.
|
||||
- We need full backup and restore scripts.
|
||||
- When recreating the app (uninstall/reinstall), db-init needs to re-run (currently the previous one blocks).
|
||||
|
||||
@@ -5,8 +5,6 @@ metadata:
|
||||
name: immich-machine-learning
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: immich-machine-learning
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
name: immich
|
||||
is: immich
|
||||
install: true
|
||||
description: Immich is a self-hosted photo and video backup solution that allows you
|
||||
to store, manage, and share your media files securely.
|
||||
version: 1.135.3
|
||||
version: release
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg
|
||||
requires:
|
||||
- name: redis
|
||||
@@ -10,8 +11,8 @@ requires:
|
||||
defaultConfig:
|
||||
namespace: immich
|
||||
externalDnsDomain: '{{ .cloud.domain }}'
|
||||
serverImage: ghcr.io/immich-app/immich-server:v1.135.3
|
||||
mlImage: ghcr.io/immich-app/immich-machine-learning:v1.135.3
|
||||
serverImage: ghcr.io/immich-app/immich-server:release
|
||||
mlImage: ghcr.io/immich-app/immich-machine-learning:release
|
||||
timezone: UTC
|
||||
serverPort: 2283
|
||||
mlPort: 3003
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# Keila
|
||||
|
||||
Keila is an open-source email marketing platform that allows you to send newsletters and manage mailing lists with privacy and control.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing contacts and campaigns
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Keila will be accessible (default: `keila.{your-cloud-domain}`)
|
||||
- **adminUser** - Admin account email (default: `admin@{your-cloud-domain}`)
|
||||
- **disableRegistration** - Whether to allow new signups (default: `true`)
|
||||
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Keila will be available at:
|
||||
- `https://keila.{your-cloud-domain}`
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add keila
|
||||
wild app deploy keila
|
||||
```
|
||||
|
||||
2. Log in with the admin credentials configured during setup
|
||||
|
||||
3. Configure your SMTP sender and create your first campaign
|
||||
@@ -4,8 +4,6 @@ metadata:
|
||||
name: keila
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
component: web
|
||||
|
||||
@@ -5,7 +5,6 @@ version: 0.17.1
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: keila
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
@@ -21,12 +20,12 @@ defaultConfig:
|
||||
adminUser: admin@{{ .cloud.domain }}
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
smtp:
|
||||
host: "{{ .apps.smtp.host }}"
|
||||
port: "{{ .apps.smtp.port }}"
|
||||
from: "{{ .apps.smtp.from }}"
|
||||
user: "{{ .apps.smtp.user }}"
|
||||
tls: "{{ .apps.smtp.tls }}"
|
||||
startTls: "{{ .apps.smtp.startTls }}"
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
startTls: "{{ .cloud.smtp.startTls }}"
|
||||
defaultSecrets:
|
||||
- key: secretKeyBase
|
||||
default: "{{ random.AlphaNum 64 }}"
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# Lemmy
|
||||
|
||||
Lemmy is a selfhosted social link aggregation and discussion platform. It is an open-source alternative to Reddit, designed for the fediverse.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing communities, posts, and comments
|
||||
|
||||
## Components
|
||||
|
||||
Lemmy runs three separate services:
|
||||
|
||||
- **Backend** - Rust API server handling federation and data
|
||||
- **UI** - Web frontend for browsing and interacting
|
||||
- **pict-rs** - Image hosting and processing service
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Lemmy will be accessible (default: `lemmy.{your-cloud-domain}`)
|
||||
- **storage** - Persistent volume for application data (default: `10Gi`)
|
||||
- **pictrsStorage** - Persistent volume for uploaded images (default: `50Gi`)
|
||||
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Lemmy will be available at:
|
||||
- `https://lemmy.{your-cloud-domain}`
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add lemmy
|
||||
wild app deploy lemmy
|
||||
```
|
||||
|
||||
2. Create your admin account through the web interface
|
||||
|
||||
3. Set up your first community and customize your instance settings
|
||||
@@ -43,11 +43,9 @@ spec:
|
||||
port: {{ .uiPort }}
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .uiPort }}
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
|
||||
@@ -5,7 +5,6 @@ version: 0.19.15
|
||||
icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lemmy.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: lemmy
|
||||
backendImage: dessalines/lemmy:0.19.15
|
||||
@@ -28,11 +27,11 @@ defaultConfig:
|
||||
dbHost: postgres.postgres.svc.cluster.local
|
||||
dbPort: 5432
|
||||
smtp:
|
||||
host: "{{ .apps.smtp.host }}"
|
||||
port: "{{ .apps.smtp.port }}"
|
||||
user: "{{ .apps.smtp.user }}"
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
from: "noreply@{{ .cloud.baseDomain }}"
|
||||
tls: "{{ .apps.smtp.tls }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
- key: adminPassword
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# Listmonk
|
||||
|
||||
Listmonk is a standalone, self-hosted newsletter and mailing list manager. It is fast, feature-rich, and packed into a single binary.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing subscribers and campaigns
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Listmonk will be accessible (default: `listmonk.{your-cloud-domain}`)
|
||||
- **storage** - Persistent volume size (default: `1Gi`)
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Listmonk will be available at:
|
||||
- `https://listmonk.{your-cloud-domain}`
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add listmonk
|
||||
wild app deploy listmonk
|
||||
```
|
||||
|
||||
2. Log in to the admin interface and configure your SMTP settings for sending emails
|
||||
|
||||
3. Create your first mailing list and start adding subscribers
|
||||
@@ -1,20 +0,0 @@
|
||||
# Longhorn Storage
|
||||
|
||||
See: [Longhorn Docs v 1.8.1](https://longhorn.io/docs/1.8.1/deploy/install/install-with-kubectl/)
|
||||
|
||||
## Installation Notes
|
||||
|
||||
- Manifest copied from https://raw.githubusercontent.com/longhorn/longhorn/v1.8.1/deploy/longhorn.yaml
|
||||
- Using kustomize to apply custom configuration (see `kustomization.yaml`)
|
||||
|
||||
## Important Settings
|
||||
|
||||
- **Number of Replicas**: Set to 1 (default is 3) to accommodate smaller clusters
|
||||
- This avoids "degraded" volumes when fewer than 3 nodes are available
|
||||
- For production with 3+ nodes, consider changing back to 3 for better availability
|
||||
|
||||
## Common Operations
|
||||
|
||||
- View volumes: `kubectl get volumes.longhorn.io -n longhorn-system`
|
||||
- Check volume status: `kubectl describe volumes.longhorn.io <volume-name> -n longhorn-system`
|
||||
- Access Longhorn UI: Set up port-forwarding with `kubectl -n longhorn-system port-forward service/longhorn-frontend 8080:80`
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: longhorn.io/v1beta2
|
||||
kind: BackupTarget
|
||||
metadata:
|
||||
name: default
|
||||
namespace: longhorn-system
|
||||
spec:
|
||||
backupTargetURL: "{{ .backupTarget }}"
|
||||
credentialSecret: ""
|
||||
pollInterval: 5m0s
|
||||
@@ -1,21 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: longhorn-ingress
|
||||
namespace: longhorn-system
|
||||
spec:
|
||||
rules:
|
||||
- host: "longhorn.{{ .internalDomain }}"
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: longhorn-frontend
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- secretName: wildcard-internal-wild-cloud-tls
|
||||
hosts:
|
||||
- "longhorn.{{ .internalDomain }}"
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- longhorn.yaml
|
||||
- backup-target.yaml
|
||||
- ingress.yaml
|
||||
- volumesnapshotclass-longhorn.yaml
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
name: longhorn
|
||||
is: longhorn
|
||||
description: Cloud-native distributed block storage for Kubernetes
|
||||
version: v1.8.1
|
||||
deploymentName: longhorn-ui
|
||||
category: infrastructure
|
||||
requires:
|
||||
- name: traefik
|
||||
- name: nfs
|
||||
defaultConfig:
|
||||
namespace: longhorn-system
|
||||
internalDomain: "{{ .cloud.internalDomain }}"
|
||||
backupTarget: "nfs://{{ .apps.nfs.host }}:/data/{{ .cluster.name }}/backups"
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: snapshot.storage.k8s.io/v1
|
||||
kind: VolumeSnapshotClass
|
||||
metadata:
|
||||
name: longhorn-snapshot-class
|
||||
driver: driver.longhorn.io
|
||||
deletionPolicy: Delete
|
||||
parameters:
|
||||
type: snap
|
||||
@@ -1,34 +0,0 @@
|
||||
# Loomio
|
||||
|
||||
Loomio is a collaborative decision-making tool that makes it easy for groups to make decisions together. It supports proposals, polls, and structured discussions.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **PostgreSQL** - Database for storing groups, discussions, and decisions
|
||||
- **Redis** - Used for caching and background jobs
|
||||
|
||||
## Configuration
|
||||
|
||||
Key settings configured through your instance's `config.yaml`:
|
||||
|
||||
- **domain** - Where Loomio will be accessible (default: `loomio.{your-cloud-domain}`)
|
||||
- **appName** - Display name for your instance (default: `Loomio`)
|
||||
- **adminEmail** - Admin contact email (defaults to your operator email)
|
||||
- **SMTP** - Email delivery settings inherited from your Wild Cloud instance
|
||||
|
||||
## Access
|
||||
|
||||
After deployment, Loomio will be available at:
|
||||
- `https://loomio.{your-cloud-domain}`
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
1. Add and deploy the app:
|
||||
```bash
|
||||
wild app add loomio
|
||||
wild app deploy loomio
|
||||
```
|
||||
|
||||
2. Create your account and set up your first group
|
||||
|
||||
3. Invite members and start a discussion or poll
|
||||
@@ -7,7 +7,6 @@ requires:
|
||||
- name: postgres
|
||||
installed_as: postgres
|
||||
- name: redis
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: loomio
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
@@ -38,11 +37,11 @@ defaultConfig:
|
||||
smtp:
|
||||
auth: plain
|
||||
domain: "{{ .cloud.domain }}"
|
||||
host: "{{ .apps.smtp.host }}"
|
||||
port: "{{ .apps.smtp.port }}"
|
||||
user: "{{ .apps.smtp.user }}"
|
||||
tls: "{{ .apps.smtp.tls }}"
|
||||
from: "{{ .apps.smtp.from }}"
|
||||
host: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
from: "{{ .cloud.smtp.from }}"
|
||||
defaultSecrets:
|
||||
- key: dbPassword
|
||||
default: "{{ random.AlphaNum 32 }}"
|
||||
|
||||
30
mailu/configmap.yaml
Normal file
30
mailu/configmap.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: mailu-config
|
||||
namespace: {{ .namespace }}
|
||||
data:
|
||||
DOMAIN: "{{ .domain }}"
|
||||
HOSTNAMES: "{{ .hostname }}"
|
||||
POSTMASTER: "admin"
|
||||
TZ: "{{ .timezone }}"
|
||||
TLS_FLAVOR: "cert"
|
||||
MESSAGE_SIZE_LIMIT: "50000000"
|
||||
MESSAGE_RATELIMIT: "200/day"
|
||||
RELAYNETS: ""
|
||||
RELAYHOST: "{{ .relayHost }}"
|
||||
RELAYPORT: "{{ .relayPort }}"
|
||||
FETCHMAIL_ENABLED: "false"
|
||||
RECIPIENT_DELIMITER: "+"
|
||||
DMARC_RUA: "admin"
|
||||
DMARC_RUF: "admin"
|
||||
WELCOME: "false"
|
||||
WELCOME_SUBJECT: "Welcome to your new email account"
|
||||
WELCOME_BODY: "Welcome! You can now use your email account."
|
||||
ADMIN: "true"
|
||||
WEB_ADMIN: "/admin"
|
||||
WEB_WEBMAIL: "/webmail"
|
||||
WEBMAIL: "roundcube"
|
||||
SITENAME: "Mailu"
|
||||
WEBSITE: "https://{{ .hostname }}"
|
||||
LOG_LEVEL: "{{ .logLevel }}"
|
||||
103
mailu/deployment-admin.yaml
Normal file
103
mailu/deployment-admin.yaml
Normal file
@@ -0,0 +1,103 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: admin
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: admin
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: admin
|
||||
spec:
|
||||
dnsPolicy: "None"
|
||||
dnsConfig:
|
||||
nameservers:
|
||||
- {{ .unbound.ip }}
|
||||
searches:
|
||||
- {{ .namespace }}.svc.cluster.local
|
||||
- svc.cluster.local
|
||||
- cluster.local
|
||||
options:
|
||||
- name: ndots
|
||||
value: "5"
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /data /dkim']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: admin
|
||||
mountPath: /data
|
||||
- name: data
|
||||
subPath: dkim
|
||||
mountPath: /dkim
|
||||
containers:
|
||||
- name: admin
|
||||
image: {{ .images.admin }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: secretKey
|
||||
- name: REDIS_ADDRESS
|
||||
value: "{{ .redis.host }}"
|
||||
- name: I_KNOW_MY_SETUP_DOESNT_FIT_REQUIREMENTS_AND_WONT_FILE_ISSUES_WITHOUT_PATCHES
|
||||
value: "true"
|
||||
- name: INITIAL_ADMIN_ACCOUNT
|
||||
value: "{{ .initialAccount.username }}"
|
||||
- name: INITIAL_ADMIN_DOMAIN
|
||||
value: "{{ .initialAccount.domain }}"
|
||||
- name: INITIAL_ADMIN_PW
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: initialAccountPassword
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: admin
|
||||
mountPath: /data
|
||||
- name: data
|
||||
subPath: dkim
|
||||
mountPath: /dkim
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
70
mailu/deployment-dovecot.yaml
Normal file
70
mailu/deployment-dovecot.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dovecot
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: dovecot
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: dovecot
|
||||
spec:
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /data /mail']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mail
|
||||
mountPath: /mail
|
||||
- name: data
|
||||
subPath: dovecot
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: dovecot
|
||||
image: {{ .images.dovecot }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: imap
|
||||
containerPort: 143
|
||||
- name: imaps
|
||||
containerPort: 993
|
||||
- name: pop3
|
||||
containerPort: 110
|
||||
- name: pop3s
|
||||
containerPort: 995
|
||||
- name: sieve
|
||||
containerPort: 4190
|
||||
- name: auth
|
||||
containerPort: 2102
|
||||
- name: lmtp
|
||||
containerPort: 2525
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mail
|
||||
mountPath: /mail
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
70
mailu/deployment-front.yaml
Normal file
70
mailu/deployment-front.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: front
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: front
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: front
|
||||
spec:
|
||||
containers:
|
||||
- name: front
|
||||
image: {{ .images.front }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
- NET_BIND_SERVICE
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: secretKey
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
- name: https
|
||||
containerPort: 443
|
||||
- name: smtp
|
||||
containerPort: 25
|
||||
- name: smtps
|
||||
containerPort: 465
|
||||
- name: submission
|
||||
containerPort: 587
|
||||
- name: imap
|
||||
containerPort: 143
|
||||
- name: imaps
|
||||
containerPort: 993
|
||||
- name: pop3
|
||||
containerPort: 110
|
||||
- name: pop3s
|
||||
containerPort: 995
|
||||
volumeMounts:
|
||||
- name: certs
|
||||
mountPath: /certs
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
volumes:
|
||||
- name: certs
|
||||
secret:
|
||||
secretName: {{ .tlsSecretName }}
|
||||
optional: true
|
||||
60
mailu/deployment-postfix.yaml
Normal file
60
mailu/deployment-postfix.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: postfix
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: postfix
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: postfix
|
||||
spec:
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /queue']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mailqueue
|
||||
mountPath: /queue
|
||||
containers:
|
||||
- name: postfix
|
||||
image: {{ .images.postfix }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
- NET_BIND_SERVICE
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: smtp
|
||||
containerPort: 25
|
||||
- name: smtps
|
||||
containerPort: 465
|
||||
- name: submission
|
||||
containerPort: 587
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: mailqueue
|
||||
mountPath: /queue
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
56
mailu/deployment-redis.yaml
Normal file
56
mailu/deployment-redis.yaml
Normal file
@@ -0,0 +1,56 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: redis
|
||||
spec:
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 999
|
||||
runAsGroup: 999
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: redis
|
||||
image: {{ .images.redis }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: [ALL]
|
||||
readOnlyRootFilesystem: false
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 6379
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 6379
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
45
mailu/deployment-rspamd.yaml
Normal file
45
mailu/deployment-rspamd.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rspamd
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: rspamd
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: rspamd
|
||||
spec:
|
||||
containers:
|
||||
- name: rspamd
|
||||
image: {{ .images.rspamd }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: REDIS_ADDRESS
|
||||
value: "{{ .redis.host }}:{{ .redis.port }}"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: rspamd
|
||||
containerPort: 11332
|
||||
- name: http
|
||||
containerPort: 11334
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: rspamd
|
||||
mountPath: /var/lib/rspamd
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
49
mailu/deployment-unbound.yaml
Normal file
49
mailu/deployment-unbound.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: unbound
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: unbound
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: unbound
|
||||
spec:
|
||||
containers:
|
||||
- name: unbound
|
||||
image: {{ .unbound.image }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
env:
|
||||
- name: UNBOUND_TLS_NAME
|
||||
value: "dns"
|
||||
ports:
|
||||
- name: dns
|
||||
containerPort: 53
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
containerPort: 53
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 53
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 53
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
61
mailu/deployment-webmail.yaml
Normal file
61
mailu/deployment-webmail.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: webmail
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
component: webmail
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: webmail
|
||||
spec:
|
||||
initContainers:
|
||||
- name: fix-permissions
|
||||
image: busybox:latest
|
||||
command: ['sh', '-c', 'chown -R 999:999 /data']
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: webmail
|
||||
mountPath: /data
|
||||
containers:
|
||||
- name: webmail
|
||||
image: {{ .images.webmail }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- SYS_CHROOT
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: mailu-secrets
|
||||
key: secretKey
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: mailu-config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- name: data
|
||||
subPath: webmail
|
||||
mountPath: /data
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
42
mailu/ingress.yaml
Normal file
42
mailu/ingress.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: mailu
|
||||
namespace: {{ .namespace }}
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
external-dns.alpha.kubernetes.io/target: {{ .externalDnsDomain }}
|
||||
external-dns.alpha.kubernetes.io/cloudflare-proxied: "false"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .hostname }}
|
||||
secretName: {{ .tlsSecretName }}
|
||||
rules:
|
||||
- host: {{ .hostname }}
|
||||
http:
|
||||
paths:
|
||||
- path: /admin
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: admin
|
||||
port:
|
||||
number: 80
|
||||
- path: /webmail
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: webmail
|
||||
port:
|
||||
number: 80
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: front
|
||||
port:
|
||||
number: 80
|
||||
25
mailu/kustomization.yaml
Normal file
25
mailu/kustomization.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: mailu
|
||||
labels:
|
||||
- includeSelectors: true
|
||||
pairs:
|
||||
app: mailu
|
||||
managedBy: kustomize
|
||||
partOf: wild-cloud
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- pvc.yaml
|
||||
- configmap.yaml
|
||||
- deployment-redis.yaml
|
||||
- service-redis.yaml
|
||||
- deployment-unbound.yaml
|
||||
- service-unbound.yaml
|
||||
- deployment-admin.yaml
|
||||
- deployment-front.yaml
|
||||
- deployment-postfix.yaml
|
||||
- deployment-dovecot.yaml
|
||||
- deployment-rspamd.yaml
|
||||
- deployment-webmail.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
60
mailu/manifest.yaml
Normal file
60
mailu/manifest.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
name: mailu
|
||||
is: mailu
|
||||
description: Mailu is a simple yet full-featured mail server as a set of Docker images. It includes a mail transfer agent, mail delivery agent, webmail, antispam, antivirus, and admin interface.
|
||||
version: 2024.06
|
||||
icon: https://mailu.io/master/_static/mailu_logo.svg
|
||||
defaultConfig:
|
||||
namespace: mailu
|
||||
|
||||
# Domain configuration
|
||||
domain: "{{ .cloud.baseDomain }}"
|
||||
hostname: mail.{{ .cloud.domain }}
|
||||
|
||||
# Container images (from ghcr.io)
|
||||
images:
|
||||
admin: ghcr.io/mailu/admin:2024.06
|
||||
front: ghcr.io/mailu/nginx:2024.06
|
||||
postfix: ghcr.io/mailu/postfix:2024.06
|
||||
dovecot: ghcr.io/mailu/dovecot:2024.06
|
||||
rspamd: ghcr.io/mailu/rspamd:2024.06
|
||||
clamav: ghcr.io/mailu/clamav:2024.06
|
||||
webmail: ghcr.io/mailu/webmail:2024.06
|
||||
redis: redis:alpine
|
||||
|
||||
# Redis configuration (built-in Redis without authentication)
|
||||
redis:
|
||||
host: redis.mailu.svc.cluster.local
|
||||
port: 6379
|
||||
|
||||
# Unbound DNS resolver (for DNSSEC validation)
|
||||
unbound:
|
||||
image: ghcr.io/mailu/unbound:2024.06
|
||||
ip: 10.96.200.1
|
||||
|
||||
# Timezone
|
||||
timezone: UTC
|
||||
|
||||
# Storage
|
||||
storage: 100Gi
|
||||
|
||||
# Initial admin account
|
||||
initialAccount:
|
||||
enabled: true
|
||||
username: admin
|
||||
domain: "{{ .cloud.baseDomain }}"
|
||||
email: "{{ .operator.email }}"
|
||||
|
||||
# TLS configuration
|
||||
tlsSecretName: mailu-tls
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
|
||||
# Log level
|
||||
logLevel: WARNING
|
||||
|
||||
# SMTP relay (optional)
|
||||
relayHost: ""
|
||||
relayPort: 25
|
||||
|
||||
defaultSecrets:
|
||||
- key: secretKey
|
||||
- key: initialAccountPassword
|
||||
@@ -1,11 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: e2e-test-app-data
|
||||
name: mailu-storage
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .storage }}
|
||||
14
mailu/service-redis.yaml
Normal file
14
mailu/service-redis.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
selector:
|
||||
component: redis
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
19
mailu/service-unbound.yaml
Normal file
19
mailu/service-unbound.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: unbound
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
clusterIP: {{ .unbound.ip }}
|
||||
selector:
|
||||
component: unbound
|
||||
ports:
|
||||
- name: dns
|
||||
port: 53
|
||||
targetPort: 53
|
||||
protocol: UDP
|
||||
- name: dns-tcp
|
||||
port: 53
|
||||
targetPort: 53
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
123
mailu/service.yaml
Normal file
123
mailu/service.yaml
Normal file
@@ -0,0 +1,123 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: admin
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
selector:
|
||||
component: admin
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: front
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
component: front
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 443
|
||||
- name: smtp
|
||||
port: 25
|
||||
targetPort: 25
|
||||
- name: smtps
|
||||
port: 465
|
||||
targetPort: 465
|
||||
- name: submission
|
||||
port: 587
|
||||
targetPort: 587
|
||||
- name: imap
|
||||
port: 143
|
||||
targetPort: 143
|
||||
- name: imaps
|
||||
port: 993
|
||||
targetPort: 993
|
||||
- name: pop3
|
||||
port: 110
|
||||
targetPort: 110
|
||||
- name: pop3s
|
||||
port: 995
|
||||
targetPort: 995
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postfix
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
selector:
|
||||
component: postfix
|
||||
ports:
|
||||
- name: smtp
|
||||
port: 25
|
||||
targetPort: 25
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dovecot
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
selector:
|
||||
component: dovecot
|
||||
ports:
|
||||
- name: imap
|
||||
port: 143
|
||||
targetPort: 143
|
||||
- name: imaps
|
||||
port: 993
|
||||
targetPort: 993
|
||||
- name: pop3
|
||||
port: 110
|
||||
targetPort: 110
|
||||
- name: pop3s
|
||||
port: 995
|
||||
targetPort: 995
|
||||
- name: sieve
|
||||
port: 4190
|
||||
targetPort: 4190
|
||||
- name: auth
|
||||
port: 2102
|
||||
targetPort: 2102
|
||||
- name: lmtp
|
||||
port: 2525
|
||||
targetPort: 2525
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: rspamd
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
selector:
|
||||
component: rspamd
|
||||
ports:
|
||||
- name: rspamd
|
||||
port: 11332
|
||||
targetPort: 11332
|
||||
- name: http
|
||||
port: 11334
|
||||
targetPort: 11334
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: webmail
|
||||
namespace: {{ .namespace }}
|
||||
spec:
|
||||
selector:
|
||||
component: webmail
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
@@ -6,7 +6,6 @@ icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mastodon.svg
|
||||
requires:
|
||||
- name: postgres
|
||||
- name: redis
|
||||
- name: smtp
|
||||
defaultConfig:
|
||||
namespace: mastodon
|
||||
externalDnsDomain: "{{ .cloud.domain }}"
|
||||
@@ -32,14 +31,14 @@ defaultConfig:
|
||||
systemStorage: 100Gi
|
||||
# SMTP configuration
|
||||
smtp:
|
||||
enabled: "{{ .apps.smtp.host | ternary true false }}"
|
||||
server: "{{ .apps.smtp.host }}"
|
||||
port: "{{ .apps.smtp.port }}"
|
||||
enabled: "{{ .cloud.smtp.host | ternary true false }}"
|
||||
server: "{{ .cloud.smtp.host }}"
|
||||
port: "{{ .cloud.smtp.port }}"
|
||||
from: notifications@{{ .cloud.domain }}
|
||||
user: "{{ .apps.smtp.user }}"
|
||||
user: "{{ .cloud.smtp.user }}"
|
||||
authMethod: plain
|
||||
enableStarttls: auto
|
||||
tls: "{{ .apps.smtp.tls }}"
|
||||
tls: "{{ .cloud.smtp.tls }}"
|
||||
# TLS
|
||||
tlsSecretName: wildcard-wild-cloud-tls
|
||||
# Sidekiq configuration
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user