Files
dezky/infrastructure/production/fleet/apps/zpush.yaml
T
Ronni Baslund 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
fix(mail): chown zpush state on pod start — root-owned files break sync
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.
2026-06-12 15:46:31 +02:00

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