# dezky EAS gateway — Z-Push (AGPLv3) wrapping Stalwart in Exchange # ActiveSync so "Exchange" accounts on iOS/Android native Mail/Calendar get # two-way mail + calendar + contacts sync. Z-Push talks to Stalwart with the # client's own Basic credentials (mailbox password or app password) over # IMAP/CalDAV/CardDAV — this container stores no secrets, only sync state. # # Z-Push 2.6.x speaks EAS up to 14.1. That covers native mobile clients but # NOT the Outlook mobile app (Microsoft enforces EAS >= 16.1 there since # March 2026) and not new Outlook for Windows (no EAS at all). See # LICENSE-NOTES.md for the AGPL source-offer obligation. ARG ZPUSH_VERSION=2.6.4 FROM alpine/git AS source ARG ZPUSH_VERSION RUN git clone --depth 1 --branch ${ZPUSH_VERSION} \ https://github.com/EGroupware/z-push.git /z-push # php:8.2 — the imap extension lives in PHP core through 8.3 and moved to # PECL in 8.4; stay on a version where docker-php-ext-install still works. # Pinned to the bookworm base: Debian trixie dropped the (upstream-dead) # uw-imap libc-client packages the imap extension compiles against. FROM php:8.2-apache-bookworm ARG ZPUSH_VERSION RUN apt-get update \ && apt-get install -y --no-install-recommends \ libc-client2007e-dev libkrb5-dev libicu-dev \ && docker-php-ext-configure imap --with-kerberos --with-imap-ssl \ && docker-php-ext-install -j"$(nproc)" imap intl sysvshm sysvsem \ && rm -rf /var/lib/apt/lists/* COPY --from=source /z-push/src/ /usr/share/z-push/ # Main config: keep the 50+ upstream defaults, patch only what we change. # The greps make the build fail loudly if an upstream config rename ever # makes a sed miss instead of shipping a silently unconfigured gateway. # (USE_FULLEMAIL_FOR_LOGIN is already true in the EGroupware fork.) RUN sed -i \ -e "s|define('TIMEZONE', '');|define('TIMEZONE', 'Europe/Copenhagen');|" \ -e "s|define('BACKEND_PROVIDER', '');|define('BACKEND_PROVIDER', 'BackendCombined');|" \ /usr/share/z-push/config.php \ && grep -q "define('BACKEND_PROVIDER', 'BackendCombined')" /usr/share/z-push/config.php \ && grep -q "define('USE_FULLEMAIL_FOR_LOGIN', true)" /usr/share/z-push/config.php # PHP 8 fix (upstream Z-Push 2.6.4 bug): sem_get()/shm_attach() return # objects instead of resources since PHP 8.0 and this debug log line # sprintf's them — a fatal TypeError on every request. The rest of the # provider only compares the handles against false, which is PHP 8-safe. RUN sed -i \ 's|sprintf("%s(): Initialized mutexid %s and memid %s.", $class, $this->mutexid, $this->memid)|sprintf("%s(): Initialized shared memory mutex and segment.", $class)|' \ /usr/share/z-push/backend/ipcsharedmemory/ipcsharedmemoryprovider.php \ && grep -q "Initialized shared memory mutex and segment" \ /usr/share/z-push/backend/ipcsharedmemory/ipcsharedmemoryprovider.php # Backend + autodiscover configs are small files — full replacements. COPY config/caldav.config.php /usr/share/z-push/backend/caldav/config.php COPY config/carddav.config.php /usr/share/z-push/backend/carddav/config.php COPY config/imap.config.php /usr/share/z-push/backend/imap/config.php COPY config/combined.config.php /usr/share/z-push/backend/combined/config.php COPY config/autodiscover.config.php /usr/share/z-push/autodiscover/config.php # Schema-sniffing autodiscover dispatcher (mobilesync → Z-Push, outlook # schema → proxied to Stalwart). Lives inside autodiscover/ because # autodiscover.php resolves its requires relative to that directory. COPY autodiscover-router.php /usr/share/z-push/autodiscover/router.php COPY apache/zpush.conf /etc/apache2/conf-available/zpush.conf COPY php/zpush.ini /usr/local/etc/php/conf.d/zpush.ini RUN a2enconf zpush \ && mkdir -p /var/lib/z-push/state /var/log/z-push \ && chown -R www-data:www-data /var/lib/z-push /var/log/z-push \ && echo "${ZPUSH_VERSION}" > /usr/share/z-push/DEZKY_PINNED_VERSION VOLUME /var/lib/z-push EXPOSE 80