425 lines
12 KiB
Markdown
425 lines
12 KiB
Markdown
|
||
|
||
# Matrix ESS (Community) — Single‑Node Install on Debian 13 (K3s + Traefik + cert‑manager)
|
||
_Last updated: 2025-09-25 08:45 UTC_
|
||
|
||
This guide installs **Element Server Suite (ESS) Community** (Synapse + MAS + Element Web + Matrix RTC) on a **single Debian 13** node using **K3s**, **Traefik** (default in K3s), and **cert‑manager** with **Let’s Encrypt**. It is tailored to your domain choices:
|
||
|
||
- **serverName**: `chans.xyz`
|
||
- **Hosts**: `synapse.chans.xyz`, `account.chans.xyz`, `chat.chans.xyz`, `mrtc.chans.xyz`
|
||
|
||
> Tip: if you already have K3s and cert‑manager installed and working, you can jump to **5. Values files** and **6. Install ESS**.
|
||
|
||
---
|
||
|
||
## 0) Requirements & Ports
|
||
|
||
- Debian 13 (root/sudo), public IPv4 (and optional IPv6).
|
||
- DNS control for `chans.xyz`.
|
||
- Open/forward these ports to this node:
|
||
- **80/tcp**, **443/tcp** (ACME + HTTPS + federation)
|
||
- **30881/tcp**, **30882/udp** (Matrix RTC SFU)
|
||
- Time in sync (`systemd-timesyncd` or equivalent).
|
||
|
||
---
|
||
|
||
## 1) DNS Setup
|
||
|
||
Create A/AAAA records that point to your node’s public IP(s):
|
||
|
||
```
|
||
chans.xyz A / AAAA -> <YOUR_IP>
|
||
synapse.chans.xyz A / AAAA -> <YOUR_IP>
|
||
account.chans.xyz A / AAAA -> <YOUR_IP>
|
||
chat.chans.xyz A / AAAA -> <YOUR_IP>
|
||
mrtc.chans.xyz A / AAAA -> <YOUR_IP>
|
||
```
|
||
|
||
Notes:
|
||
|
||
- **Do not** use a `CNAME` at the **apex** (`chans.xyz`)—use `A/AAAA`. Subdomains can be `CNAME`s if you prefer.
|
||
- Federation relies on `https://chans.xyz/.well-known/matrix/server` which the chart serves for you.
|
||
|
||
---
|
||
|
||
## 2) (Optional) Cloud‑Init (without firewalld)
|
||
|
||
If you build the node via cloud‑init, this minimal config installs K3s & Helm and disables swap:
|
||
|
||
```yaml
|
||
#cloud-config
|
||
package_update: true
|
||
package_upgrade: true
|
||
packages: [curl, ca-certificates, gnupg, lsb-release]
|
||
|
||
runcmd:
|
||
- swapoff -a
|
||
- sed -ri 's/^[^#].*\sswap\s/## &/g' /etc/fstab
|
||
- curl -sfL https://get.k3s.io | sh -s - server
|
||
- mkdir -p /home/windy/.kube
|
||
- cp /etc/rancher/k3s/k3s.yaml /home/windy/.kube/config
|
||
- chown windy:windy /home/windy/.kube/config && chmod 600 /home/windy/.kube/config
|
||
- bash -lc 'echo export KUBECONFIG=$HOME/.kube/config >> /home/windy/.bashrc'
|
||
- su - windy -c "curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash"
|
||
```
|
||
|
||
You can manage ports at the cloud firewall or your router (no `firewalld` required).
|
||
|
||
---
|
||
|
||
## 3) Manual K3s + Helm (if not using cloud‑init)
|
||
|
||
```bash
|
||
# Install latest K3s
|
||
curl -sfL https://get.k3s.io | sh -s - server
|
||
|
||
# kubeconfig for your user (replace 'windy' if needed)
|
||
mkdir -p ~windy/.kube
|
||
sudo cp /etc/rancher/k3s/k3s.yaml ~windy/.kube/config
|
||
sudo chown windy:windy ~windy/.kube/config
|
||
chmod 600 ~windy/.kube/config
|
||
echo 'export KUBECONFIG=$HOME/.kube/config' | sudo tee -a ~windy/.bashrc
|
||
|
||
# Helm
|
||
sudo -iu windy bash -lc 'curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash'
|
||
```
|
||
|
||
Verify:
|
||
```bash
|
||
kubectl get nodes -o wide
|
||
kubectl get pods -A
|
||
```
|
||
|
||
You should see the node `Ready` and `traefik` running in `kube-system`.
|
||
|
||
---
|
||
|
||
## 4) cert‑manager + Let’s Encrypt (ClusterIssuer)
|
||
|
||
If you haven’t installed cert‑manager yet:
|
||
|
||
```bash
|
||
helm repo add jetstack https://charts.jetstack.io --force-update
|
||
kubectl create namespace cert-manager 2>/dev/null || true
|
||
helm install cert-manager jetstack/cert-manager -n cert-manager --set crds.enabled=true
|
||
```
|
||
|
||
Create a production ClusterIssuer (`letsencrypt-prod`):
|
||
|
||
```yaml
|
||
# clusterissuer.yaml
|
||
apiVersion: cert-manager.io/v1
|
||
kind: ClusterIssuer
|
||
metadata:
|
||
name: letsencrypt-prod
|
||
spec:
|
||
acme:
|
||
server: https://acme-v02.api.letsencrypt.org/directory
|
||
privateKeySecretRef:
|
||
name: letsencrypt-prod-private-key
|
||
solvers:
|
||
- http01:
|
||
ingress:
|
||
class: traefik
|
||
```
|
||
|
||
Apply:
|
||
```bash
|
||
kubectl apply -f clusterissuer.yaml
|
||
kubectl get clusterissuer
|
||
```
|
||
|
||
You should see `letsencrypt-prod READY=True`.
|
||
|
||
---
|
||
|
||
## 5) Values files (hosts + TLS)
|
||
|
||
Create the directory and values files:
|
||
|
||
```bash
|
||
mkdir -p ~/ess-config-values
|
||
```
|
||
|
||
**`~/ess-config-values/hostnames.yaml`**
|
||
```yaml
|
||
serverName: chans.xyz
|
||
|
||
elementWeb:
|
||
ingress:
|
||
host: chat.chans.xyz
|
||
|
||
synapse:
|
||
ingress:
|
||
host: synapse.chans.xyz
|
||
|
||
matrixAuthenticationService:
|
||
ingress:
|
||
host: account.chans.xyz
|
||
|
||
matrixRTC:
|
||
ingress:
|
||
host: mrtc.chans.xyz
|
||
```
|
||
|
||
**`~/ess-config-values/tls.yaml`**
|
||
```yaml
|
||
global:
|
||
ingress:
|
||
className: traefik
|
||
annotations:
|
||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||
tls:
|
||
enabled: true
|
||
issuer: letsencrypt-prod
|
||
```
|
||
|
||
> The TLS values ensure your Ingresses are annotated for cert‑manager and include TLS host entries so Certificates are created automatically.
|
||
|
||
---
|
||
|
||
## 6) Install ESS (matrix‑stack chart)
|
||
|
||
```bash
|
||
kubectl create namespace ess 2>/dev/null || true
|
||
|
||
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml --wait
|
||
```
|
||
|
||
Check status:
|
||
```bash
|
||
kubectl get pods -n ess
|
||
kubectl get ingress -n ess
|
||
```
|
||
|
||
You should see ingresses for `synapse`, `account`, `chat`, `mrtc`, and `well-known` with `CLASS=traefik`.
|
||
|
||
---
|
||
|
||
## 7) Certificates issuance
|
||
|
||
Confirm the ingresses have TLS + issuer:
|
||
```bash
|
||
kubectl -n ess get ingress -o jsonpath='{range .items[*]}{.metadata.name}{" issuer="}{.metadata.annotations.cert-manager\.io/cluster-issuer}{" tlsHosts="}{range .spec.tls[*].hosts}{.}{" "}{end}{"\n"}{end}'
|
||
```
|
||
|
||
Then watch certs:
|
||
```bash
|
||
kubectl get certificate -n ess
|
||
kubectl get order,challenge -n ess
|
||
```
|
||
|
||
When ready, confirm live certs:
|
||
```bash
|
||
for h in synapse.chans.xyz account.chans.xyz chat.chans.xyz mrtc.chans.xyz chans.xyz; do
|
||
echo "=== $h ==="
|
||
openssl s_client -connect "$h:443" -servername "$h" </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -dates
|
||
done
|
||
```
|
||
|
||
---
|
||
|
||
## 8) Well‑Known verification (federation & clients)
|
||
|
||
```bash
|
||
curl -s https://chans.xyz/.well-known/matrix/server | jq .
|
||
curl -s https://chans.xyz/.well-known/matrix/client | jq .
|
||
```
|
||
|
||
Expected:
|
||
- `server` → `{ "m.server": "synapse.chans.xyz:443" }`
|
||
- `client` → `{ "m.homeserver": { "base_url": "https://synapse.chans.xyz" }, ... }`
|
||
|
||
Optional federation tester: <https://federationtester.matrix.org/#chans.xyz>
|
||
|
||
---
|
||
|
||
## 9) Create the first admin account
|
||
|
||
Interactive:
|
||
```bash
|
||
kubectl exec -n ess -it deploy/ess-matrix-authentication-service -- mas-cli manage register-user --admin
|
||
```
|
||
|
||
Non‑interactive example:
|
||
```bash
|
||
kubectl exec -n ess deploy/ess-matrix-authentication-service -- mas-cli manage register-user --yes --admin --username admin --password 'CHANGE_ME_strong_password'
|
||
```
|
||
|
||
Login at **https://chat.chans.xyz**.
|
||
|
||
---
|
||
|
||
## 10) Enable self‑registration (optional)
|
||
|
||
```yaml
|
||
# ~/ess-config-values/mas-registration.yaml
|
||
matrixAuthenticationService:
|
||
additional:
|
||
registration.yaml:
|
||
config: |
|
||
account:
|
||
password_registration_enabled: true
|
||
password_recovery_enabled: true
|
||
login_with_email_allowed: true
|
||
```
|
||
|
||
Apply (include this file):
|
||
```bash
|
||
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml -f ~/ess-config-values/mas-registration.yaml --wait
|
||
```
|
||
|
||
---
|
||
|
||
## 11) Outbound email (MAS required, Synapse optional)
|
||
|
||
### 11.1 MAS SMTP (required for signup/reset)
|
||
|
||
**Option A — inline values (simple):**
|
||
```yaml
|
||
# ~/ess-config-values/mas-email.yaml
|
||
matrixAuthenticationService:
|
||
additional:
|
||
user-config.yaml:
|
||
config: |
|
||
email:
|
||
from: '"Matrix @ chans.xyz" <noreply@chans.xyz>'
|
||
reply_to: '"Support" <support@chans.xyz>'
|
||
transport: smtp
|
||
mode: starttls
|
||
hostname: smtp.windy.me
|
||
port: 587
|
||
username: noreply@chans.xyz # authenticate as the sender
|
||
password: "MAILBOX_PASSWORD"
|
||
account:
|
||
password_registration_enabled: true
|
||
password_recovery_enabled: true
|
||
login_with_email_allowed: true
|
||
```
|
||
|
||
**Option B — secret ref (keeps password out of Git):**
|
||
```bash
|
||
cat > /tmp/mas-user-config.yaml <<'YAML'
|
||
email:
|
||
from: '"Matrix @ chans.xyz" <noreply@chans.xyz>'
|
||
reply_to: '"Support" <support@chans.xyz>'
|
||
transport: smtp
|
||
mode: starttls
|
||
hostname: smtp.windy.me
|
||
port: 587
|
||
username: noreply@chans.xyz
|
||
password: "MAILBOX_PASSWORD"
|
||
account:
|
||
password_registration_enabled: true
|
||
password_recovery_enabled: true
|
||
login_with_email_allowed: true
|
||
YAML
|
||
|
||
kubectl -n ess create secret generic mas-extra-config --from-file=user-config.yaml=/tmp/mas-user-config.yaml
|
||
```
|
||
|
||
Then reference it:
|
||
```yaml
|
||
# ~/ess-config-values/mas-email-secretref.yaml
|
||
matrixAuthenticationService:
|
||
additional:
|
||
user-config.yaml:
|
||
configSecret: mas-extra-config
|
||
configSecretKey: user-config.yaml
|
||
```
|
||
|
||
Apply (include one of the two files above):
|
||
```bash
|
||
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml -f ~/ess-config-values/mas-email.yaml --wait
|
||
# or replace mas-email.yaml with mas-email-secretref.yaml if you used a Secret
|
||
```
|
||
|
||
> **Mailcow 553 fix**: If authenticating as `zhiqiang@windy.me` and sending as `noreply@chans.xyz`, Mailcow rejects with `553 5.7.1 Sender address rejected`. Either (a) **authenticate as** `noreply@chans.xyz` by creating that mailbox in Mailcow and publishing SPF/DKIM/DMARC for `chans.xyz`; or (b) allow “send as” in Mailcow’s **Sender ACL** for `zhiqiang@windy.me`. Hosting the `chans.xyz` mailbox gives best deliverability (DKIM/DMARC alignment).
|
||
|
||
Monitor while testing:
|
||
```bash
|
||
kubectl -n ess logs deploy/ess-matrix-authentication-service -f | grep -iE 'smtp|email|send'
|
||
```
|
||
|
||
### 11.2 Synapse email notifications (optional)
|
||
```yaml
|
||
# ~/ess-config-values/synapse-email.yaml
|
||
synapse:
|
||
additional:
|
||
email.yaml:
|
||
config: |
|
||
email:
|
||
smtp_host: "smtp.windy.me"
|
||
smtp_port: 587
|
||
smtp_user: "noreply@chans.xyz"
|
||
smtp_pass: "MAILBOX_PASSWORD"
|
||
require_transport_security: true
|
||
notif_from: "Matrix on chans.xyz <noreply@chans.xyz>"
|
||
enable_notifs: true
|
||
```
|
||
|
||
Include this file in your next Helm upgrade.
|
||
|
||
---
|
||
|
||
## 12) Health checks & troubleshooting
|
||
|
||
**Basic:**
|
||
```bash
|
||
kubectl get pods,svc,ingress,certificate -n ess -o wide
|
||
```
|
||
|
||
**Certs flow:**
|
||
```bash
|
||
kubectl get certificate,order,challenge -n ess
|
||
kubectl describe challenge -n ess <name>
|
||
kubectl logs -n kube-system deploy/traefik --tail=200
|
||
```
|
||
|
||
**Well‑known + federation:**
|
||
```bash
|
||
curl -s https://chans.xyz/.well-known/matrix/server | jq .
|
||
curl -s https://chans.xyz/.well-known/matrix/client | jq .
|
||
```
|
||
|
||
**Common pitfalls:**
|
||
- Ingresses lack TLS + `cert-manager.io/cluster-issuer` → fix `tls.yaml`.
|
||
- `553 Sender address rejected` from Mailcow → align SMTP auth user with sender or allow “send as”, and set SPF/DKIM/DMARC for `chans.xyz`.
|
||
- Port 80 blocked → Let’s Encrypt HTTP‑01 fails (check challenges).
|
||
- Apex `chans.xyz` not pointing at the node → `.well-known` fails → federation fails.
|
||
|
||
---
|
||
|
||
## 13) Upgrades / Uninstall
|
||
|
||
Upgrade to latest chart:
|
||
```bash
|
||
helm repo update # if using repos
|
||
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml --wait
|
||
```
|
||
|
||
Uninstall ESS (keeps PVCs unless you delete them):
|
||
```bash
|
||
helm uninstall ess -n ess
|
||
kubectl delete namespace ess
|
||
```
|
||
|
||
Reset K3s (if ever needed):
|
||
```bash
|
||
sudo /usr/local/bin/k3s-uninstall.sh
|
||
```
|
||
|
||
---
|
||
|
||
## 14) Quick copy‑paste checklist
|
||
|
||
1. DNS A/AAAA for: `chans.xyz`, `synapse.`, `account.`, `chat.`, `mrtc.` → your IP.
|
||
2. K3s running with Traefik; cert‑manager installed; `ClusterIssuer letsencrypt-prod` **Ready**.
|
||
3. `hostnames.yaml` with `*.ingress.host` set to your subdomains.
|
||
4. `tls.yaml` with `global.ingress.annotations.cert-manager.io/cluster-issuer=letsencrypt-prod` and TLS enabled.
|
||
5. `helm upgrade --install ess …` with both files.
|
||
6. `kubectl get certificate -n ess` → `READY=True`.
|
||
7. `/.well-known` returns correct JSON; federation tester OK.
|
||
8. Create admin via MAS CLI; log in at `https://chat.chans.xyz`.
|
||
9. Configure SMTP for MAS (and optionally Synapse), fix Mailcow sender policy if needed.
|