diff --git a/MAIL_STACK_README.md b/MAIL_STACK_README.md new file mode 100644 index 0000000..4e5956a --- /dev/null +++ b/MAIL_STACK_README.md @@ -0,0 +1,529 @@ +# Lightweight Mail Stack for k3s GitOps Lab + +Complete deployment of **Stalwart Mail Server** + **SnappyMail** webmail using GitOps with ArgoCD. + +## 📋 Overview + +This repository deploys a lightweight, modern mail stack designed for self-hosting and lab environments: + +- **Stalwart Mail Server**: All-in-one mail server (SMTP, IMAP, Admin UI) +- **SnappyMail**: Modern, lightweight webmail client +- **GitOps**: Managed by ArgoCD +- **Storage**: NFS-based persistent volumes +- **Ingress**: Traefik for web UI access + +## 🏗️ Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Traefik Ingress │ +│ mail.dvirlabs.com webmail.dvirlabs.com │ +└───────────┬──────────────────────┬──────────────┘ + │ │ + │ │ + ┌───────▼────────┐ ┌────────▼────────┐ + │ Stalwart │◄───│ SnappyMail │ + │ Mail Server │ │ Webmail │ + │ │ │ │ + │ • SMTP │ │ Connects via: │ + │ • IMAP │ │ • IMAP:993 │ + │ • Admin UI │ │ • SMTP:587 │ + └────────┬───────┘ └─────────────────┘ + │ + │ + ┌────────▼───────┐ + │ NFS Storage │ + │ Mail Data │ + └────────────────┘ +``` + +## 📁 Repository Structure + +``` +mail-services/ +├── argocd-apps/ +│ ├── stalwart.yaml # ArgoCD Application for Stalwart +│ └── snappymail.yaml # ArgoCD Application for SnappyMail +├── charts/ +│ ├── stalwart/ # Local Helm chart for Stalwart +│ │ ├── Chart.yaml +│ │ ├── values.yaml # Default values +│ │ └── templates/ +│ │ ├── namespace.yaml +│ │ ├── secret.yaml +│ │ ├── statefulset.yaml +│ │ ├── service.yaml +│ │ └── ingress.yaml +│ └── snappymail/ # Local Helm chart for SnappyMail +│ ├── Chart.yaml +│ ├── values.yaml # Default values +│ └── templates/ +│ ├── deployment.yaml +│ ├── pvc.yaml +│ ├── service.yaml +│ ├── ingress.yaml +│ └── configmap.yaml +└── manifests/ + ├── stalwart/ + │ └── values.yaml # Custom values for dvirlabs.com + └── snappymail/ + └── values.yaml # Custom values for dvirlabs.com +``` + +## 🚀 Quick Start + +### Prerequisites + +- k3s cluster running +- ArgoCD installed and configured +- Traefik ingress controller +- NFS storage class (`nfs-client`) +- DNS records pointing to your cluster + +### Step 1: Update Configuration + +1. **Update ArgoCD Application manifests** with your Git repository URL: + +```bash +# Edit both files and replace YOUR_USERNAME with your actual repo +vim argocd-apps/stalwart.yaml +vim argocd-apps/snappymail.yaml +``` + +2. **Change the Stalwart admin password**: + +```bash +# Edit and set a strong password +vim manifests/stalwart/values.yaml +``` + +Find this section and change `CHANGE_ME_PLEASE_USE_STRONG_PASSWORD`: + +```yaml +secret: + create: true + name: stalwart-credentials + adminPassword: "YOUR_STRONG_PASSWORD_HERE" +``` + +3. **Update domain names** (if not using dvirlabs.com): + +```bash +# Update in both files +vim manifests/stalwart/values.yaml +vim manifests/snappymail/values.yaml +``` + +### Step 2: Deploy with ArgoCD + +```bash +# Apply ArgoCD Applications +kubectl apply -f argocd-apps/stalwart.yaml +kubectl apply -f argocd-apps/snappymail.yaml + +# Check deployment status +kubectl get applications -n argocd + +# Watch pods come up +kubectl get pods -n mail -w +``` + +### Step 3: Verify Deployment + +```bash +# Check all resources in mail namespace +kubectl get all -n mail + +# Check PVCs +kubectl get pvc -n mail + +# Check ingresses +kubectl get ingress -n mail +``` + +Expected output: +``` +NAME READY STATUS RESTARTS AGE +pod/stalwart-0 1/1 Running 0 2m +pod/snappymail-xxx-xxx 1/1 Running 0 2m + +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) +service/stalwart ClusterIP 10.43.x.x 8080/TCP,25/TCP,587/TCP,993/TCP +service/snappymail ClusterIP 10.43.x.x 8888/TCP + +NAME CLASS HOSTS +ingress.networking.k8s.io/stalwart traefik mail.dvirlabs.com +ingress.networking.k8s.io/snappymail traefik webmail.dvirlabs.com +``` + +## 🌐 Access the Services + +### Stalwart Admin UI + +URL: `https://mail.dvirlabs.com` + +Default credentials: +- Username: `admin@dvirlabs.com` +- Password: (the one you set in manifests/stalwart/values.yaml) + +### SnappyMail Webmail + +URL: `https://webmail.dvirlabs.com` + +First-time setup: +1. Access the admin panel: `https://webmail.dvirlabs.com/?admin` +2. Default admin password: `12345` (change immediately!) +3. Configure mail server connection: + - **IMAP Server**: `stalwart.mail.svc.cluster.local` + - **IMAP Port**: `993` + - **IMAP Security**: SSL/TLS + - **SMTP Server**: `stalwart.mail.svc.cluster.local` + - **SMTP Port**: `587` + - **SMTP Security**: STARTTLS + +## 📧 Configuring Real Mail Service + +### Important: Cloudflare Tunnel Limitations + +⚠️ **WARNING**: While Cloudflare Tunnel works fine for web UIs (admin panel and webmail), it **CANNOT** be used for actual email protocols (SMTP/IMAP). + +**What works through Cloudflare Tunnel:** +- ✅ Stalwart admin UI (HTTPS) +- ✅ SnappyMail webmail (HTTPS) + +**What does NOT work through Cloudflare Tunnel:** +- ❌ Receiving mail from other servers (SMTP port 25) +- ❌ Sending mail to other servers (SMTP port 25) +- ❌ External email clients (IMAP/SMTP) + +### Required for Real Email + +To receive and send real email, you need: + +#### 1. DNS Records + +```dns +; MX Record (Mail Exchange) +@ IN MX 10 mail.dvirlabs.com. + +; A Record (pointing to your public IP - NOT Cloudflare Tunnel) +mail IN A YOUR_PUBLIC_IP + +; SPF Record (Sender Policy Framework) +@ IN TXT "v=spf1 mx ~all" + +; DMARC Record +_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:admin@dvirlabs.com" + +; DKIM Record (generated by Stalwart) +; Get this from Stalwart admin UI after setup +default._domainkey IN TXT "v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY_HERE" +``` + +#### 2. Port Forwarding + +You need to expose these ports directly (NOT through Cloudflare): + +``` +Port 25 (SMTP) - Required for receiving mail from other servers +Port 587 (SMTP) - Required for sending mail (submission) +Port 465 (SMTPS) - Optional, secure SMTP submission +Port 993 (IMAPS) - Required for IMAP access +Port 143 (IMAP) - Optional, plaintext IMAP +``` + +**Option A: NodePort Service** + +```bash +kubectl apply -f - < mail.dvirlabs.com +``` + +This is **critical** for email deliverability. Without it, many servers will reject your mail. + +## 🔧 Configuration Management + +### Using External Secrets (Recommended for Production) + +Instead of storing passwords in Git, use External Secrets Operator: + +1. Install External Secrets Operator +2. Create a secret in your secret backend (Vault, AWS Secrets Manager, etc.) +3. Update manifests/stalwart/values.yaml: + +```yaml +secret: + create: false # Don't create the secret + name: stalwart-credentials # Reference external secret +``` + +4. Create an ExternalSecret: + +```yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: stalwart-credentials + namespace: mail +spec: + refreshInterval: 1h + secretStoreRef: + name: your-secret-store + kind: SecretStore + target: + name: stalwart-credentials + data: + - secretKey: STALWART_ADMIN_PASSWORD + remoteRef: + key: mail/stalwart/admin-password +``` + +## 🛠️ Maintenance + +### View Stalwart Logs + +```bash +kubectl logs -n mail stalwart-0 -f +``` + +### View SnappyMail Logs + +```bash +kubectl logs -n mail -l app.kubernetes.io/name=snappymail -f +``` + +### Access Stalwart Shell + +```bash +kubectl exec -it -n mail stalwart-0 -- /bin/sh +``` + +### Backup Mail Data + +```bash +# Backup Stalwart data +kubectl exec -n mail stalwart-0 -- tar czf /tmp/mail-backup.tar.gz /opt/stalwart-mail +kubectl cp mail/stalwart-0:/tmp/mail-backup.tar.gz ./mail-backup-$(date +%Y%m%d).tar.gz + +# Backup SnappyMail config +kubectl exec -n mail -l app.kubernetes.io/name=snappymail -- tar czf /tmp/snappymail-backup.tar.gz /var/lib/snappymail +kubectl cp mail/snappymail-xxx:/tmp/snappymail-backup.tar.gz ./snappymail-backup-$(date +%Y%m%d).tar.gz +``` + +### Restore from Backup + +```bash +# Restore Stalwart +kubectl cp ./mail-backup.tar.gz mail/stalwart-0:/tmp/mail-backup.tar.gz +kubectl exec -n mail stalwart-0 -- tar xzf /tmp/mail-backup.tar.gz -C / +kubectl rollout restart statefulset -n mail stalwart +``` + +## 🔍 Troubleshooting + +### Pods Not Starting + +```bash +# Check pod events +kubectl describe pod -n mail stalwart-0 +kubectl describe pod -n mail -l app.kubernetes.io/name=snappymail + +# Check PVC status +kubectl get pvc -n mail +``` + +### Ingress Not Working + +```bash +# Check ingress status +kubectl describe ingress -n mail + +# Check Traefik logs +kubectl logs -n kube-system -l app.kubernetes.io/name=traefik + +# Test internal connectivity +kubectl run -it --rm debug --image=busybox -n mail -- wget -O- http://stalwart:8080 +``` + +### SnappyMail Can't Connect to Stalwart + +```bash +# Test IMAP connectivity from SnappyMail pod +kubectl exec -it -n mail -l app.kubernetes.io/name=snappymail -- nc -zv stalwart.mail.svc.cluster.local 993 + +# Check Stalwart service +kubectl get svc -n mail stalwart +``` + +### Email Not Being Delivered + +Common issues: +1. **No PTR record**: Check reverse DNS +2. **Port 25 blocked**: Many ISPs block outbound port 25 +3. **Missing SPF/DKIM/DMARC**: Check DNS records +4. **IP on blacklist**: Check https://mxtoolbox.com/blacklists.aspx + +## 📊 Monitoring + +### Check Mail Queue + +```bash +# Access Stalwart admin UI +# https://mail.dvirlabs.com +# Navigate to Queue section +``` + +### Resource Usage + +```bash +# Check pod resource usage +kubectl top pods -n mail + +# Check PVC usage +kubectl exec -n mail stalwart-0 -- df -h /opt/stalwart-mail +``` + +## 🔐 Security Hardening + +### Recommended Post-Deployment Steps + +1. **Change SnappyMail admin password** immediately +2. **Enable fail2ban** for brute force protection +3. **Set up TLS certificates** with cert-manager (if not using Cloudflare) +4. **Enable DKIM signing** in Stalwart +5. **Configure rate limiting** in Stalwart +6. **Regular backups** of mail data +7. **Monitor logs** for suspicious activity + +### TLS Certificates with cert-manager + +If you want Let's Encrypt certificates instead of Cloudflare: + +```yaml +# Add to ingress annotations +cert-manager.io/cluster-issuer: letsencrypt-prod +``` + +And configure TLS: + +```yaml +tls: + - hosts: + - mail.dvirlabs.com + secretName: stalwart-tls +``` + +## 📚 Additional Resources + +- [Stalwart Documentation](https://stalw.art/docs) +- [SnappyMail Documentation](https://snappymail.eu/) +- [Email Deliverability Best Practices](https://www.mail-tester.com/) +- [DKIM Setup Guide](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/) + +## ⚙️ Customization + +### Adjust Storage Size + +Edit `manifests/stalwart/values.yaml` or `manifests/snappymail/values.yaml`: + +```yaml +persistence: + size: 50Gi # Increase as needed +``` + +### Change Resource Limits + +Edit `manifests/*/values.yaml`: + +```yaml +resources: + requests: + memory: "2Gi" + cpu: "1000m" + limits: + memory: "8Gi" + cpu: "4000m" +``` + +### Add Multiple Domains + +Configure in Stalwart admin UI after deployment. + +## 🤝 Contributing + +This is a personal lab setup, but feel free to fork and adapt for your needs! + +## 📝 License + +MIT License - Use at your own risk + +## ⚠️ Disclaimer + +This setup is designed for lab/self-hosting environments. For production use: +- Use External Secrets for credentials +- Set up proper TLS certificates +- Configure backup automation +- Enable monitoring and alerting +- Review security best practices +- Test email deliverability thoroughly diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..880bd11 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,298 @@ +# Quick Start Guide - Stalwart + SnappyMail Mail Stack + +## 📋 What Was Created + +A complete GitOps-ready mail stack with: +- ✅ Stalwart Mail Server (all-in-one: SMTP, IMAP, Admin UI) +- ✅ SnappyMail webmail client +- ✅ Local Helm charts for both applications +- ✅ ArgoCD Application manifests +- ✅ Custom values files for dvirlabs.com +- ✅ All manifests validated successfully + +## 📁 File Structure Created + +``` +mail-services/ +├── argocd-apps/ +│ ├── stalwart.yaml # ⚠️ UPDATE: Change repo URL +│ └── snappymail.yaml # ⚠️ UPDATE: Change repo URL +│ +├── charts/ +│ ├── stalwart/ # Local Helm chart for Stalwart +│ │ ├── Chart.yaml +│ │ ├── values.yaml +│ │ └── templates/ +│ │ ├── _helpers.tpl +│ │ ├── namespace.yaml +│ │ ├── secret.yaml +│ │ ├── statefulset.yaml +│ │ ├── service.yaml +│ │ └── ingress.yaml +│ │ +│ └── snappymail/ # Local Helm chart for SnappyMail +│ ├── Chart.yaml +│ ├── values.yaml +│ └── templates/ +│ ├── _helpers.tpl +│ ├── deployment.yaml +│ ├── pvc.yaml +│ ├── service.yaml +│ ├── ingress.yaml +│ └── configmap.yaml +│ +├── manifests/ +│ ├── stalwart/ +│ │ └── values.yaml # ⚠️ UPDATE: Change admin password +│ └── snappymail/ +│ └── values.yaml +│ +├── MAIL_STACK_README.md # 📖 Full documentation +└── QUICKSTART.md # 👈 This file +``` + +## ⚠️ REQUIRED CHANGES Before Deployment + +### 1. Update Git Repository URL + +Edit these files and replace `YOUR_USERNAME` with your actual Git username/organization: + +**File: `argocd-apps/stalwart.yaml`** +```yaml +source: + repoURL: https://github.com/YOUR_USERNAME/mail-services.git # ← CHANGE THIS +``` + +**File: `argocd-apps/snappymail.yaml`** +```yaml +source: + repoURL: https://github.com/YOUR_USERNAME/mail-services.git # ← CHANGE THIS +``` + +### 2. Change Admin Password (CRITICAL!) + +Edit `manifests/stalwart/values.yaml`: + +Find this section: +```yaml +secret: + create: true + name: stalwart-credentials + adminPassword: "CHANGE_ME_PLEASE_USE_STRONG_PASSWORD" # ← CHANGE THIS! +``` + +Replace with a strong password: +```yaml + adminPassword: "MyStr0ng!P@ssw0rd#2024" +``` + +**⚠️ DO NOT commit this file with the default password!** + +### 3. (Optional) Update Domain Names + +If you're not using `dvirlabs.com`, update these files: + +**`manifests/stalwart/values.yaml`:** +```yaml +ingress: + hosts: + - host: mail.YOUR-DOMAIN.com # ← Update +``` + +**`manifests/snappymail/values.yaml`:** +```yaml +ingress: + hosts: + - host: webmail.YOUR-DOMAIN.com # ← Update +``` + +## 🚀 Deployment Steps + +### Step 1: Commit and Push to Git + +```bash +cd c:\Users\dvirl\OneDrive\Desktop\gitea\mail-services + +# Review changes +git status + +# Add new files +git add argocd-apps/stalwart.yaml +git add argocd-apps/snappymail.yaml +git add charts/stalwart/ +git add charts/snappymail/ +git add manifests/stalwart/ +git add manifests/snappymail/ +git add MAIL_STACK_README.md +git add QUICKSTART.md + +# Commit +git commit -m "Add Stalwart Mail Server + SnappyMail stack" + +# Push to your Git server +git push origin main +``` + +### Step 2: Deploy with ArgoCD + +```bash +# Apply ArgoCD Applications +kubectl apply -f argocd-apps/stalwart.yaml +kubectl apply -f argocd-apps/snappymail.yaml + +# Watch ArgoCD sync +kubectl get applications -n argocd -w + +# Watch pods come up +kubectl get pods -n mail -w +``` + +### Step 3: Verify Deployment + +```bash +# Check all resources +kubectl get all -n mail + +# Expected output: +# - statefulset.apps/stalwart (1/1) +# - deployment.apps/snappymail (1/1) +# - service/stalwart +# - service/snappymail +# - ingress.networking.k8s.io/stalwart +# - ingress.networking.k8s.io/snappymail + +# Check PVCs +kubectl get pvc -n mail + +# Check logs +kubectl logs -n mail stalwart-0 +kubectl logs -n mail -l app.kubernetes.io/name=snappymail +``` + +## 🌐 Access the Services + +### Stalwart Admin UI +- URL: `https://mail.dvirlabs.com` +- Username: `admin@dvirlabs.com` +- Password: (what you set in manifests/stalwart/values.yaml) + +### SnappyMail Webmail +- URL: `https://webmail.dvirlabs.com` +- First access: Admin panel at `https://webmail.dvirlabs.com/?admin` +- Default admin password: `12345` (CHANGE IMMEDIATELY!) + +## ⚙️ SnappyMail Configuration + +After deployment, configure SnappyMail to connect to Stalwart: + +1. Go to `https://webmail.dvirlabs.com/?admin` +2. Login with default password `12345` +3. Change admin password immediately +4. Go to **Domains** → **Add Domain** +5. Configure: + - **IMAP Server:** `stalwart.mail.svc.cluster.local` + - **IMAP Port:** `993` + - **IMAP Secure:** `SSL/TLS` + - **SMTP Server:** `stalwart.mail.svc.cluster.local` + - **SMTP Port:** `587` + - **SMTP Secure:** `STARTTLS` + +## 📧 Setting Up Real Email + +### DNS Records Needed + +```dns +; MX Record +@ IN MX 10 mail.dvirlabs.com. + +; A Record (use your public IP, NOT Cloudflare proxy) +mail IN A YOUR_PUBLIC_IP + +; SPF Record +@ IN TXT "v=spf1 mx ~all" + +; DMARC Record +_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:admin@dvirlabs.com" +``` + +### Port Forwarding Required + +For real email (not just webmail), you need to expose these ports directly: + +``` +Port 25 (SMTP) - Receiving mail +Port 587 (SMTP) - Sending mail +Port 993 (IMAPS) - IMAP access +``` + +**⚠️ Important:** These ports CANNOT go through Cloudflare Tunnel! + +## 🔍 Troubleshooting + +### Pods stuck in Pending +```bash +# Check PVC status +kubectl describe pvc -n mail + +# Check if nfs-client storage class exists +kubectl get storageclass +``` + +### Can't access web UIs +```bash +# Check ingress +kubectl describe ingress -n mail + +# Check if DNS resolves to your cluster +nslookup mail.dvirlabs.com +nslookup webmail.dvirlabs.com +``` + +### SnappyMail can't connect to Stalwart +```bash +# Test connectivity from SnappyMail pod +kubectl exec -it -n mail deploy/snappymail -- nc -zv stalwart.mail.svc.cluster.local 993 +``` + +## 📖 Full Documentation + +See [MAIL_STACK_README.md](MAIL_STACK_README.md) for: +- Complete architecture overview +- External mail setup instructions +- Security hardening guide +- Backup and restore procedures +- Advanced configuration options +- External Secrets integration + +## ✅ Validation Results + +All manifests have been validated: +- ✅ Stalwart Helm chart renders correctly +- ✅ SnappyMail Helm chart renders correctly +- ✅ ArgoCD Application manifests are valid +- ✅ All Kubernetes resources are syntactically correct + +## 🎯 Next Steps + +1. **Update repo URL** in ArgoCD manifests ← DO THIS FIRST! +2. **Change admin password** in manifests/stalwart/values.yaml +3. **Commit and push** to Git +4. **Apply ArgoCD applications** +5. **Wait for deployment** (2-3 minutes) +6. **Access Stalwart admin UI** and configure mail settings +7. **Configure SnappyMail** to connect to Stalwart +8. **Set up DNS records** for real email +9. **Configure port forwarding** for mail protocols + +## 💡 Pro Tips + +- Start with web UIs only, add real mail later +- Use External Secrets for production passwords +- Enable DKIM in Stalwart for better deliverability +- Monitor logs during first email tests +- Test with mail-tester.com for deliverability score +- Backup mail data regularly + +--- + +**Need help?** Check [MAIL_STACK_README.md](MAIL_STACK_README.md) for detailed documentation. diff --git a/argocd-apps/snappymail.yaml b/argocd-apps/snappymail.yaml new file mode 100644 index 0000000..e78165d --- /dev/null +++ b/argocd-apps/snappymail.yaml @@ -0,0 +1,36 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: snappymail + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + repoURL: https://github.com/YOUR_USERNAME/mail-services.git # TODO: Update with your repo URL + targetRevision: HEAD + path: charts/snappymail + helm: + valueFiles: + - ../../manifests/snappymail/values.yaml + + destination: + server: https://kubernetes.default.svc + namespace: mail + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m diff --git a/argocd-apps/stalwart.yaml b/argocd-apps/stalwart.yaml new file mode 100644 index 0000000..6e5ad96 --- /dev/null +++ b/argocd-apps/stalwart.yaml @@ -0,0 +1,36 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: stalwart + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + repoURL: https://github.com/YOUR_USERNAME/mail-services.git # TODO: Update with your repo URL + targetRevision: HEAD + path: charts/stalwart + helm: + valueFiles: + - ../../manifests/stalwart/values.yaml + + destination: + server: https://kubernetes.default.svc + namespace: mail + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + retry: + limit: 5 + backoff: + duration: 5s + factor: 2 + maxDuration: 3m diff --git a/charts/snappymail/Chart.yaml b/charts/snappymail/Chart.yaml new file mode 100644 index 0000000..5b53815 --- /dev/null +++ b/charts/snappymail/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: snappymail +description: SnappyMail - Simple, modern, lightweight webmail client +type: application +version: 1.0.0 +appVersion: "2.39.1" +keywords: + - webmail + - mail + - snappymail +home: https://snappymail.eu +sources: + - https://github.com/the-djmaze/snappymail +maintainers: + - name: dvirlabs diff --git a/charts/snappymail/templates/_helpers.tpl b/charts/snappymail/templates/_helpers.tpl new file mode 100644 index 0000000..d9a3dae --- /dev/null +++ b/charts/snappymail/templates/_helpers.tpl @@ -0,0 +1,49 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "snappymail.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "snappymail.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "snappymail.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "snappymail.labels" -}} +helm.sh/chart: {{ include "snappymail.chart" . }} +{{ include "snappymail.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "snappymail.selectorLabels" -}} +app.kubernetes.io/name: {{ include "snappymail.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/snappymail/templates/configmap.yaml b/charts/snappymail/templates/configmap.yaml new file mode 100644 index 0000000..ffc3fba --- /dev/null +++ b/charts/snappymail/templates/configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "snappymail.fullname" . }}-config + namespace: {{ .Values.namespace }} + labels: + {{- include "snappymail.labels" . | nindent 4 }} +data: + # Stalwart mail server connection info + # This can be used for documentation or manual configuration + # SnappyMail is configured through its web UI + IMAP_HOST: {{ .Values.stalwart.imap.host | quote }} + IMAP_PORT: {{ .Values.stalwart.imap.port | quote }} + SMTP_HOST: {{ .Values.stalwart.smtp.host | quote }} + SMTP_PORT: {{ .Values.stalwart.smtp.port | quote }} diff --git a/charts/snappymail/templates/deployment.yaml b/charts/snappymail/templates/deployment.yaml new file mode 100644 index 0000000..e486518 --- /dev/null +++ b/charts/snappymail/templates/deployment.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "snappymail.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "snappymail.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "snappymail.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "snappymail.selectorLabels" . | nindent 8 }} + spec: + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + containers: + - name: snappymail + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + volumeMounts: + - name: data + mountPath: {{ .Values.persistence.mountPath }} + resources: + {{- toYaml .Values.resources | nindent 10 }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + {{- with .Values.env }} + env: + {{- toYaml . | nindent 10 }} + {{- end }} + volumes: + {{- if .Values.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ include "snappymail.fullname" . }} + {{- else }} + - name: data + emptyDir: {} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/snappymail/templates/ingress.yaml b/charts/snappymail/templates/ingress.yaml new file mode 100644 index 0000000..b2e6271 --- /dev/null +++ b/charts/snappymail/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "snappymail.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "snappymail.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "snappymail.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/snappymail/templates/pvc.yaml b/charts/snappymail/templates/pvc.yaml new file mode 100644 index 0000000..1faa2d9 --- /dev/null +++ b/charts/snappymail/templates/pvc.yaml @@ -0,0 +1,16 @@ +{{- if .Values.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "snappymail.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "snappymail.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + storageClassName: {{ .Values.persistence.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.size }} +{{- end }} diff --git a/charts/snappymail/templates/service.yaml b/charts/snappymail/templates/service.yaml new file mode 100644 index 0000000..ed656f0 --- /dev/null +++ b/charts/snappymail/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "snappymail.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "snappymail.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "snappymail.selectorLabels" . | nindent 4 }} diff --git a/charts/snappymail/values.yaml b/charts/snappymail/values.yaml new file mode 100644 index 0000000..88a93eb --- /dev/null +++ b/charts/snappymail/values.yaml @@ -0,0 +1,86 @@ +## SnappyMail webmail configuration +## Default values for snappymail chart + +## Image configuration +image: + repository: djmaze/snappymail + tag: "2.39" + pullPolicy: IfNotPresent + +## Namespace +namespace: mail + +## Replica count +replicaCount: 1 + +## Service configuration +service: + type: ClusterIP + port: 8888 + targetPort: 8888 + +## Ingress configuration +ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: webmail.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: [] + +## Persistence configuration for SnappyMail data +persistence: + enabled: true + storageClass: nfs-client + accessMode: ReadWriteOnce + size: 1Gi + # Where SnappyMail stores its data + mountPath: /var/lib/snappymail + +## Resource limits +resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + +## Stalwart mail server configuration +## SnappyMail will connect to Stalwart internally +stalwart: + # Service name of Stalwart in the same namespace + serviceName: stalwart + # IMAP configuration + imap: + host: stalwart.mail.svc.cluster.local + port: 993 + secure: true + # SMTP configuration + smtp: + host: stalwart.mail.svc.cluster.local + port: 587 + secure: true + +## Environment variables +env: {} + +## Pod Security Context +securityContext: + fsGroup: 82 # www-data group in Alpine + runAsUser: 82 + runAsNonRoot: true + +## Node selector +nodeSelector: {} + +## Tolerations +tolerations: [] + +## Affinity +affinity: {} diff --git a/charts/stalwart/Chart.yaml b/charts/stalwart/Chart.yaml new file mode 100644 index 0000000..121654f --- /dev/null +++ b/charts/stalwart/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +name: stalwart +description: Stalwart Mail Server - Modern all-in-one mail server +type: application +version: 1.0.0 +appVersion: "0.10.8" +keywords: + - mail + - smtp + - imap + - stalwart +home: https://stalw.art +sources: + - https://github.com/stalwartlabs/mail-server +maintainers: + - name: dvirlabs diff --git a/charts/stalwart/templates/_helpers.tpl b/charts/stalwart/templates/_helpers.tpl new file mode 100644 index 0000000..590f7a2 --- /dev/null +++ b/charts/stalwart/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "stalwart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "stalwart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "stalwart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "stalwart.labels" -}} +helm.sh/chart: {{ include "stalwart.chart" . }} +{{ include "stalwart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "stalwart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "stalwart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "stalwart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "stalwart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/stalwart/templates/ingress.yaml b/charts/stalwart/templates/ingress.yaml new file mode 100644 index 0000000..56fc127 --- /dev/null +++ b/charts/stalwart/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "stalwart.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "stalwart.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "stalwart.fullname" $ }} + port: + number: {{ .servicePort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/stalwart/templates/namespace.yaml b/charts/stalwart/templates/namespace.yaml new file mode 100644 index 0000000..bb65df0 --- /dev/null +++ b/charts/stalwart/templates/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.namespace }} + labels: + {{- include "stalwart.labels" . | nindent 4 }} diff --git a/charts/stalwart/templates/secret.yaml b/charts/stalwart/templates/secret.yaml new file mode 100644 index 0000000..c829de1 --- /dev/null +++ b/charts/stalwart/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if .Values.secret.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.secret.name }} + namespace: {{ .Values.namespace }} + labels: + {{- include "stalwart.labels" . | nindent 4 }} +type: Opaque +stringData: + STALWART_ADMIN_PASSWORD: {{ .Values.secret.adminPassword | quote }} +{{- end }} diff --git a/charts/stalwart/templates/service.yaml b/charts/stalwart/templates/service.yaml new file mode 100644 index 0000000..38f1147 --- /dev/null +++ b/charts/stalwart/templates/service.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "stalwart.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "stalwart.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.web.port }} + targetPort: {{ .Values.service.web.targetPort }} + protocol: TCP + name: web + - port: {{ .Values.service.smtp.port }} + targetPort: {{ .Values.service.smtp.targetPort }} + protocol: TCP + name: smtp + - port: {{ .Values.service.smtps.port }} + targetPort: {{ .Values.service.smtps.targetPort }} + protocol: TCP + name: smtps + - port: {{ .Values.service.submission.port }} + targetPort: {{ .Values.service.submission.targetPort }} + protocol: TCP + name: submission + - port: {{ .Values.service.imap.port }} + targetPort: {{ .Values.service.imap.targetPort }} + protocol: TCP + name: imap + - port: {{ .Values.service.imaps.port }} + targetPort: {{ .Values.service.imaps.targetPort }} + protocol: TCP + name: imaps + selector: + {{- include "stalwart.selectorLabels" . | nindent 4 }} diff --git a/charts/stalwart/templates/statefulset.yaml b/charts/stalwart/templates/statefulset.yaml new file mode 100644 index 0000000..70318a4 --- /dev/null +++ b/charts/stalwart/templates/statefulset.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "stalwart.fullname" . }} + namespace: {{ .Values.namespace }} + labels: + {{- include "stalwart.labels" . | nindent 4 }} +spec: + serviceName: {{ include "stalwart.fullname" . }} + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "stalwart.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "stalwart.selectorLabels" . | nindent 8 }} + spec: + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + containers: + - name: stalwart + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: web + containerPort: {{ .Values.service.web.targetPort }} + protocol: TCP + - name: smtp + containerPort: {{ .Values.service.smtp.targetPort }} + protocol: TCP + - name: smtps + containerPort: {{ .Values.service.smtps.targetPort }} + protocol: TCP + - name: submission + containerPort: {{ .Values.service.submission.targetPort }} + protocol: TCP + - name: imap + containerPort: {{ .Values.service.imap.targetPort }} + protocol: TCP + - name: imaps + containerPort: {{ .Values.service.imaps.targetPort }} + protocol: TCP + env: + - name: STALWART_ADMIN_USER + value: {{ .Values.env.STALWART_ADMIN_USER | quote }} + - name: STALWART_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.secret.name }} + key: STALWART_ADMIN_PASSWORD + volumeMounts: + - name: data + mountPath: {{ .Values.persistence.mountPath }} + resources: + {{- toYaml .Values.resources | nindent 10 }} + livenessProbe: + httpGet: + path: / + port: web + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: web + initialDelaySeconds: 10 + periodSeconds: 5 + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + labels: + {{- include "stalwart.labels" . | nindent 8 }} + spec: + accessModes: + - {{ .Values.persistence.accessMode }} + storageClassName: {{ .Values.persistence.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- end }} diff --git a/charts/stalwart/values.yaml b/charts/stalwart/values.yaml new file mode 100644 index 0000000..6fca9dc --- /dev/null +++ b/charts/stalwart/values.yaml @@ -0,0 +1,104 @@ +## Stalwart Mail Server configuration +## Default values for stalwart chart + +## Image configuration +image: + repository: stalwartlabs/mail-server + tag: v0.10.8 + pullPolicy: IfNotPresent + +## Namespace +namespace: mail + +## Replica count (should be 1 for StatefulSet with persistent data) +replicaCount: 1 + +## Service configuration +service: + type: ClusterIP + # Admin/Web UI port + web: + port: 8080 + targetPort: 8080 + # SMTP ports + smtp: + port: 25 + targetPort: 25 + smtps: + port: 465 + targetPort: 465 + submission: + port: 587 + targetPort: 587 + # IMAP ports + imap: + port: 143 + targetPort: 143 + imaps: + port: 993 + targetPort: 993 + +## Ingress configuration for admin UI +ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: mail.dvirlabs.com + paths: + - path: / + pathType: Prefix + servicePort: 8080 + tls: [] + +## Persistence configuration +persistence: + enabled: true + storageClass: nfs-client + accessMode: ReadWriteOnce + size: 10Gi + # Where Stalwart stores mail data + mountPath: /opt/stalwart-mail + +## Resource limits +resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1000m" + +## Environment variables for Stalwart configuration +env: + # Admin credentials - CHANGE THESE! + # Or reference a secret using envFrom + STALWART_ADMIN_USER: "admin@dvirlabs.com" + # Password should come from a secret + # STALWART_ADMIN_PASSWORD: defined in secret + +## Secret configuration +secret: + # Set to true to create a secret + create: true + # Name of the secret (if create: false, this should reference an existing secret) + name: stalwart-credentials + # Admin password - CHANGE THIS or use external secrets later + adminPassword: "CHANGE_ME_PLEASE" + +## Pod Security Context +securityContext: + fsGroup: 1000 + runAsUser: 1000 + runAsNonRoot: true + +## Node selector +nodeSelector: {} + +## Tolerations +tolerations: [] + +## Affinity +affinity: {} diff --git a/manifests/snappymail/values.yaml b/manifests/snappymail/values.yaml new file mode 100644 index 0000000..de5783e --- /dev/null +++ b/manifests/snappymail/values.yaml @@ -0,0 +1,54 @@ +## SnappyMail - Custom values for dvirlabs.com +## Override default chart values here + +## Namespace +namespace: mail + +## Image configuration +image: + repository: djmaze/snappymail + tag: "2.39" + pullPolicy: IfNotPresent + +## Persistence - using NFS storage +persistence: + enabled: true + storageClass: nfs-client + accessMode: ReadWriteOnce + size: 2Gi + mountPath: /var/lib/snappymail + +## Ingress for webmail UI +ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: webmail.dvirlabs.com + paths: + - path: / + pathType: Prefix + +## Stalwart mail server connection +## SnappyMail will connect to Stalwart internally +stalwart: + serviceName: stalwart + imap: + host: stalwart.mail.svc.cluster.local + port: 993 + secure: true + smtp: + host: stalwart.mail.svc.cluster.local + port: 587 + secure: true + +## Resource allocation +resources: + requests: + memory: "256Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1000m" diff --git a/manifests/stalwart/values.yaml b/manifests/stalwart/values.yaml new file mode 100644 index 0000000..733afd2 --- /dev/null +++ b/manifests/stalwart/values.yaml @@ -0,0 +1,54 @@ +## Stalwart Mail Server - Custom values for dvirlabs.com +## Override default chart values here + +## Namespace +namespace: mail + +## Image configuration +image: + repository: stalwartlabs/mail-server + tag: v0.10.8 + pullPolicy: IfNotPresent + +## Persistence - using NFS storage +persistence: + enabled: true + storageClass: nfs-client + accessMode: ReadWriteOnce + size: 20Gi # Increased for production mail storage + mountPath: /opt/stalwart-mail + +## Ingress for admin UI only +ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + hosts: + - host: mail.dvirlabs.com + paths: + - path: / + pathType: Prefix + servicePort: 8080 + +## Admin credentials +env: + STALWART_ADMIN_USER: "admin@dvirlabs.com" + +## Secret configuration +## IMPORTANT: Change the adminPassword before deploying! +secret: + create: true + name: stalwart-credentials + # TODO: Replace with a strong password or use External Secrets + adminPassword: "CHANGE_ME_PLEASE_USE_STRONG_PASSWORD" + +## Resource allocation +resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "4Gi" + cpu: "2000m"