4b71b5751f
ci / changes (push) Successful in 4s
ci / tc_operator (push) Has been skipped
ci / tc_website (push) Has been skipped
ci / test_platform_api (push) Has been skipped
ci / tc_platform_api (push) Has been skipped
ci / build_portal (push) Has been skipped
ci / build_booking (push) Has been skipped
ci / build_operator (push) Has been skipped
ci / build_platform_api (push) Has been skipped
ci / tc_portal (push) Has been skipped
ci / tc_booking (push) Has been skipped
ci / build_zpush (push) Has been skipped
ci / deploy (push) Successful in 28s
A root-run z-push-admin (kubectl exec defaults to root) left a root-owned 'users' file on the state PVC; Apache runs as www-data, so every request 500'd with 'Not possible to write to the configured state directory'. An initContainer now normalizes ownership on every start (state is disposable, ownership isn't precious), and the docs say to exec z-push-admin as www-data.
164 lines
5.4 KiB
YAML
164 lines
5.4 KiB
YAML
# zpush — Exchange ActiveSync gateway (Z-Push, AGPLv3) in front of host-
|
|
# Stalwart. Gives "Exchange" accounts on iOS/Android native Mail/Calendar
|
|
# two-way mail+calendar+contacts sync. EAS 14.1: covers native mobile
|
|
# clients, NOT the Outlook mobile app (requires EAS 16.1) and not new
|
|
# Outlook for Windows (no EAS at all). Credentials pass through to Stalwart
|
|
# per-request (mailbox password or app password) — the pod stores only sync
|
|
# state, which is disposable (devices resync after a wipe).
|
|
#
|
|
# replicas MUST stay 1: Z-Push's FILE state machine is not multi-writer-safe
|
|
# (same constraint family as the portal/operator session pinning).
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: zpush-state
|
|
namespace: dezky-apps
|
|
labels:
|
|
app.kubernetes.io/name: zpush
|
|
app.kubernetes.io/part-of: dezky
|
|
spec:
|
|
accessModes: [ReadWriteOnce]
|
|
storageClassName: local-path
|
|
resources:
|
|
requests:
|
|
storage: 1Gi
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: zpush
|
|
namespace: dezky-apps
|
|
labels:
|
|
app.kubernetes.io/name: zpush
|
|
app.kubernetes.io/part-of: dezky
|
|
spec:
|
|
replicas: 1
|
|
# Recreate, not RollingUpdate: the state PVC is RWO and the state machine
|
|
# must never have two writers.
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: zpush
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: zpush
|
|
spec:
|
|
# Normalize state ownership on every start: Apache/Z-Push runs as
|
|
# www-data, but anything exec'd as root (kubectl exec z-push-admin,
|
|
# debugging) can leave root-owned files on the PVC — which 500s every
|
|
# request with "Not possible to write to the configured state
|
|
# directory" (bitten 2026-06-12). State is disposable, ownership isn't
|
|
# precious: just take it back at boot.
|
|
initContainers:
|
|
- name: fix-state-ownership
|
|
image: git.lastcloud.io/ronnibaslund/dezky/zpush:latest
|
|
command: ["sh", "-c", "chown -R www-data:www-data /var/lib/z-push"]
|
|
volumeMounts:
|
|
- name: state
|
|
mountPath: /var/lib/z-push
|
|
containers:
|
|
- name: zpush
|
|
# CI pins this to the commit SHA at deploy time; :latest is the fallback.
|
|
image: git.lastcloud.io/ronnibaslund/dezky/zpush:latest
|
|
imagePullPolicy: IfNotPresent
|
|
ports:
|
|
- name: http
|
|
containerPort: 80
|
|
env:
|
|
# Host-Stalwart reached via the selectorless Services pinning the
|
|
# cni0 gateway address (see mail-autodiscovery.yaml).
|
|
- name: CALDAV_SERVER
|
|
value: stalwart-http.dezky-apps.svc.cluster.local
|
|
- name: CALDAV_PORT
|
|
value: "8080"
|
|
# Host-Stalwart only exposes implicit-TLS IMAPS/SMTPS (no plain
|
|
# 143/587) — hence /ssl with novalidate-cert (the cert names
|
|
# mail.dezky.eu, not the cluster service) and the ssl:// prefix.
|
|
- name: IMAP_SERVER
|
|
value: stalwart-imaps.dezky-apps.svc.cluster.local
|
|
- name: IMAP_PORT
|
|
value: "993"
|
|
- name: IMAP_OPTIONS
|
|
value: /ssl/novalidate-cert
|
|
- name: SMTP_SERVER
|
|
value: ssl://stalwart-smtps.dezky-apps.svc.cluster.local
|
|
- name: SMTP_PORT
|
|
value: "465"
|
|
# Hostname EAS autodiscover responses point devices at.
|
|
- name: ZPUSH_HOST
|
|
value: mail.dezky.eu
|
|
# Outlook-schema (mail) autodiscover POSTs proxied to Stalwart.
|
|
- name: MAIL_AUTODISCOVER_UPSTREAM
|
|
value: http://stalwart-http.dezky-apps.svc.cluster.local:8080
|
|
volumeMounts:
|
|
- name: state
|
|
mountPath: /var/lib/z-push
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 192Mi
|
|
limits:
|
|
memory: 512Mi
|
|
readinessProbe:
|
|
tcpSocket:
|
|
port: http
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 15
|
|
livenessProbe:
|
|
tcpSocket:
|
|
port: http
|
|
initialDelaySeconds: 15
|
|
periodSeconds: 30
|
|
volumes:
|
|
- name: state
|
|
persistentVolumeClaim:
|
|
claimName: zpush-state
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: zpush
|
|
namespace: dezky-apps
|
|
labels:
|
|
app.kubernetes.io/name: zpush
|
|
spec:
|
|
selector:
|
|
app.kubernetes.io/name: zpush
|
|
ports:
|
|
- name: http
|
|
port: 80
|
|
targetPort: http
|
|
---
|
|
# EAS endpoint on mail.dezky.eu. Path-scoped: everything else on the host
|
|
# (CalDAV well-knowns, /dav) keeps routing to Stalwart via the mail-dav
|
|
# Ingress. NO redirect middleware — EAS is POST-heavy and devices don't
|
|
# follow 301s (same reasoning as the autodiscover Ingress). No cert-manager
|
|
# annotation either: the mail-dav Ingress already owns the Certificate that
|
|
# keeps mail-dezky-eu-traefik-tls fresh; we only reference the secret.
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: zpush-eas
|
|
namespace: dezky-apps
|
|
annotations:
|
|
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
|
spec:
|
|
ingressClassName: traefik
|
|
tls:
|
|
- hosts:
|
|
- mail.dezky.eu
|
|
secretName: mail-dezky-eu-traefik-tls
|
|
rules:
|
|
- host: mail.dezky.eu
|
|
http:
|
|
paths:
|
|
- path: /Microsoft-Server-ActiveSync
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: zpush
|
|
port:
|
|
number: 80
|