IMipPlugin.php core patch is not persisted — silently reverts on any image bump or container recreate #2

Open
opened 2026-07-13 13:31:22 -04:00 by hiveiq · 1 comment
Owner

Problem

Calendar invites on office.bcos.cloud are sent from the organizer's own SMTP account (rather than
the system invitations-noreply address) only because a core Nextcloud file is hand-patched inside
the running container
:

/var/www/html/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php

This patch exists nowhere except the live container filesystem — not in a Docker image, not in
hiveiq-openmetal/hiveops/instances/bcos-office/, not in this repo.

Why this is a problem

Any of the following silently reverts it:

  • bumping the Nextcloud image (nextcloud:29-apache -> anything newer)
  • docker compose up -d --force-recreate bcos-office
  • occ upgrade / an app store update that reinstalls dav
  • rebuilding the VM

The failure is silent and easy to misread: invites keep working, they just quietly start arriving
from the noreply address again. Nobody gets an error. We only notice when someone asks "why does the
invite come from noreply?" — or, worse, we re-apply a half-remembered version of the patch and
reintroduce a fatal (which is exactly what happened, see below).

History — why this is worth doing properly

  • 2026-06-23#1 filed: invites to external people never send.
  • 2026-06-29IMipPlugin.php patched in-container to send via the organizer's mail account.
    The patch called $result->fetchAssociative() — a Doctrine DBAL method that does not exist on
    Nextcloud's OC\DB\ResultAdapter. It raised \Error, which the surrounding catch (\Exception)
    does not catch (separate \Throwable branch), so the fatal escaped and killed the CalDAV PUT.
    Result: every event with any attendee failed to save, for every user, with the Calendar
    UI's generic "Failed to save event". Undiagnosed for two weeks.
  • 2026-07-13 — fixed in-container (fetch() + is_array() guard + catch (\Throwable)).
    Verified: CalDAV PUT with attendee -> HTTP 201; bcos-mail logs from=<organizer> status=sent;
    external delivery confirmed by a real recipient.

An unversioned patch to a core file, with no review and no test, cost us two weeks of broken calendar
invites. It needs to live somewhere that gets reviewed and survives a rebuild.

Proposed fix

Pick one:

  1. Entrypoint hook (lightest). Nextcloud's official image runs /docker-entrypoint-hooks.d/before-starting/*
    on every container start. Drop the patch in as a script there, mounted from
    hiveiq-openmetal/hiveops/instances/bcos-office/, and make it idempotent (detect already-patched,
    and hard-fail loudly if the upstream file's shape changed rather than patching blindly).
  2. Custom image layer. FROM nextcloud:29-apache + COPY/patch the file, build and pin the tag.
    Heavier, but the patch is then reviewable in git and versioned with the image.
  3. Drop the patch; do it the supported way. Investigate whether the same outcome is achievable via
    supported config (occ config:app:set dav ..., mail app settings, or a small proper Nextcloud app
    implementing the scheduling hook) so we stop editing core at all. Cleanest, most work.

Recommendation: 1 now (cheap, stops the silent revert), 3 as the real answer when there's room.

Whichever we pick, the patch content must be committed to this repo so it is reviewable and
re-appliable, and docs/ should note that core is patched.

Acceptance criteria

  • Patch content committed to bcos/bcos-office (not just living in the container)
  • docker compose up -d --force-recreate bcos-office leaves invites still sending from the organizer's address
  • Patch application is idempotent and fails loudly if upstream IMipPlugin.php changes shape
  • Upgrade runbook notes that core apps/dav is patched

Reference

  • In-container backup of the pre-fix file: IMipPlugin.php.bak-20260713-preclaude (same directory)
  • Symptom shape to recognise: events with no attendees save fine; adding any attendee 500s the PUT
  • Real error is never in the UI — it's in data/nextcloud.log (level>=3); "Failed to save event" is a frontend catch-all
## Problem Calendar invites on office.bcos.cloud are sent from the **organizer's own SMTP account** (rather than the system `invitations-noreply` address) only because a **core Nextcloud file is hand-patched inside the running container**: `/var/www/html/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php` This patch exists **nowhere except the live container filesystem** — not in a Docker image, not in `hiveiq-openmetal/hiveops/instances/bcos-office/`, not in this repo. ## Why this is a problem Any of the following silently reverts it: - bumping the Nextcloud image (`nextcloud:29-apache` -> anything newer) - `docker compose up -d --force-recreate bcos-office` - `occ upgrade` / an app store update that reinstalls `dav` - rebuilding the VM The failure is **silent and easy to misread**: invites keep working, they just quietly start arriving from the noreply address again. Nobody gets an error. We only notice when someone asks "why does the invite come from noreply?" — or, worse, we re-apply a half-remembered version of the patch and reintroduce a fatal (which is exactly what happened, see below). ## History — why this is worth doing properly - **2026-06-23** — #1 filed: invites to external people never send. - **2026-06-29** — `IMipPlugin.php` patched in-container to send via the organizer's mail account. The patch called `$result->fetchAssociative()` — a **Doctrine DBAL** method that does not exist on Nextcloud's `OC\DB\ResultAdapter`. It raised `\Error`, which the surrounding `catch (\Exception)` does **not** catch (separate `\Throwable` branch), so the fatal escaped and killed the CalDAV `PUT`. Result: **every** event with **any** attendee failed to save, for **every** user, with the Calendar UI's generic "Failed to save event". Undiagnosed for two weeks. - **2026-07-13** — fixed in-container (`fetch()` + `is_array()` guard + `catch (\Throwable)`). Verified: CalDAV PUT with attendee -> HTTP 201; bcos-mail logs `from=<organizer> status=sent`; external delivery confirmed by a real recipient. An unversioned patch to a core file, with no review and no test, cost us two weeks of broken calendar invites. It needs to live somewhere that gets reviewed and survives a rebuild. ## Proposed fix Pick one: 1. **Entrypoint hook (lightest).** Nextcloud's official image runs `/docker-entrypoint-hooks.d/before-starting/*` on every container start. Drop the patch in as a script there, mounted from `hiveiq-openmetal/hiveops/instances/bcos-office/`, and make it **idempotent** (detect already-patched, and hard-fail loudly if the upstream file's shape changed rather than patching blindly). 2. **Custom image layer.** `FROM nextcloud:29-apache` + `COPY`/`patch` the file, build and pin the tag. Heavier, but the patch is then reviewable in git and versioned with the image. 3. **Drop the patch; do it the supported way.** Investigate whether the same outcome is achievable via supported config (`occ config:app:set dav ...`, mail app settings, or a small proper Nextcloud app implementing the scheduling hook) so we stop editing core at all. Cleanest, most work. Recommendation: **1 now** (cheap, stops the silent revert), **3 as the real answer** when there's room. Whichever we pick, the patch content must be committed to this repo so it is reviewable and re-appliable, and `docs/` should note that core is patched. ## Acceptance criteria - [ ] Patch content committed to `bcos/bcos-office` (not just living in the container) - [ ] `docker compose up -d --force-recreate bcos-office` leaves invites still sending from the organizer's address - [ ] Patch application is idempotent and fails loudly if upstream `IMipPlugin.php` changes shape - [ ] Upgrade runbook notes that core `apps/dav` is patched ## Reference - In-container backup of the pre-fix file: `IMipPlugin.php.bak-20260713-preclaude` (same directory) - Symptom shape to recognise: events with **no** attendees save fine; adding **any** attendee 500s the PUT - Real error is never in the UI — it's in `data/nextcloud.log` (`level>=3`); "Failed to save event" is a frontend catch-all
Author
Owner

Superseded by hiveiq-ops/hiveiq-devops#38hiveiq-ops/hiveiq-devops#38

Filed here by mistake. This is deployment/infra work: bcos/bcos-office is an empty repo, while the office stack config actually lives in hiveiq-openmetal-prod/hiveops/instances/bcos-office/ and infra issues are tracked in hiveiq-devops.

@johannes — please close this one in the UI.

Superseded by **hiveiq-ops/hiveiq-devops#38** — https://hiveiq-gitea.directlx.dev/hiveiq-ops/hiveiq-devops/issues/38 Filed here by mistake. This is deployment/infra work: `bcos/bcos-office` is an empty repo, while the office stack config actually lives in `hiveiq-openmetal-prod/hiveops/instances/bcos-office/` and infra issues are tracked in `hiveiq-devops`. @johannes — please close this one in the UI.
Sign in to join this conversation.