📝 docs: add session context and implementation plan

Captures architecture, fixes, caveats and commit plan for the
EuroOffice default editor work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: James Manuel <moodyjmz@users.noreply.github.com>
This commit is contained in:
James Manuel
2026-06-08 17:14:01 +02:00
parent 84818f764e
commit c88687255d
2 changed files with 340 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
# Euro-Office AIO — Work Context
## Goal
Ship Nextcloud AIO with **EuroOffice as the default document editor** (replacing Collabora).
This is the euro-office-public fork of `nextcloud/all-in-one`. Current phase: **testing**.
---
## What We've Done
### `php/src/Data/ConfigurationManager.php`
- `isEuroofficeEnabled` default: `false``true` (+ added missing `(bool)` cast)
- `isCollaboraEnabled` default: `true``false`
- `getNextcloudStartupApps()`: added `eurooffice` to the default app list
- Added `performMigrations()`: one-time migration (flag `eurooffice_default_migration_v1`) that forces existing installs to switch to EuroOffice on next mastercontainer start
### `php/public/index.php`
- Calls `$configurationManager->performMigrations()` at bootstrap (before route handling)
### `Containers/apache/Caddyfile` — EuroOffice SDK 404 fix
**Root cause:** EuroOffice nginx maps `$http_x_forwarded_prefix``$the_prefix` and uses it to construct SDK asset URLs (e.g. `/eurooffice/sdkjs/...`). Without the header, `$the_prefix` is empty so the browser requests `/sdkjs/...` which Caddy routes to Nextcloud → 404.
**Fix:** Send `X-Forwarded-Prefix` as a separate header instead of appending the path to `X-Forwarded-Host`:
```
route /eurooffice/* {
uri strip_prefix /eurooffice
reverse_proxy {$EUROOFFICE_HOST}:80 {
header_up X-Forwarded-Host {http.request.hostport}
header_up X-Forwarded-Prefix /eurooffice
}
}
```
Note: OnlyOffice block uses the old pattern (`X-Forwarded-Host host/onlyoffice`) and was left unchanged — OO worked with it. This fix is also needed upstream in `nextcloud/all-in-one`.
### `Containers/nextcloud/entrypoint.sh` — EuroOffice internal URL and preview fix
**Root cause summary:** Containers resolve `nextcloud.test``127.0.0.1` (Docker reads host `/etc/hosts` which has `127.0.0.1 nextcloud.test`). So any container→container call using the public domain fails. This affects:
1. NC → EuroOffice converter calls (preview generation, document editing callbacks)
2. EuroOffice → NC file download calls (EuroOffice needs to fetch the document to convert it)
Three fixes baked into the entrypoint EuroOffice block:
**Fix 1 — DocumentServerInternalUrl** (NC → EuroOffice, bypasses public domain):
```bash
php /var/www/html/occ config:app:set eurooffice DocumentServerInternalUrl --value="http://$EUROOFFICE_HOST:80/"
```
- Must have trailing slash — `getDocumentServerInternalUrl()` returns raw value, concatenated directly with `"converter"` in `DocumentService::getConvertedUri()` (line 124)
- `$EUROOFFICE_HOST` is the Docker container name (e.g. `nextcloud-aio-eurooffice`) before it gets rewritten to `$NC_DOMAIN/eurooffice`
**Fix 2 — StorageUrl** (EuroOffice → NC, bypasses public domain):
```bash
APACHE_CONTAINER_HOST=$(echo "$EUROOFFICE_HOST" | sed 's/-eurooffice$/-apache/')
php /var/www/html/occ config:app:set eurooffice StorageUrl --value="http://$APACHE_CONTAINER_HOST.nextcloud-aio:23973/"
```
- Port 23973 matches Caddy's `http://{$APACHE_HOST}.nextcloud-aio:23973` server block (the WOPI/callback port), which routes to Nextcloud at `127.0.0.1:8000`
- Port 11000 does NOT work even though it's open — Caddy rejects requests where `Host: nextcloud-aio-apache:11000` doesn't match the `nextcloud.test:11000` binding
- Must have trailing slash — `getStorageUrl()` returns raw value; `str_replace("https://nextcloud.test/", storageUrl, $fileUrl)` strips the `/` from the origin, producing `...23973apps/` (broken URL) if StorageUrl has no trailing slash
**Fix 3 — enabledPreviewProviders** (EuroOffice class missing from explicit allowlist):
```bash
if ! php /var/www/html/occ config:system:get enabledPreviewProviders | grep -q "Eurooffice"; then
php /var/www/html/occ config:system:set enabledPreviewProviders 50 --value="OCA\Eurooffice\Preview"
fi
```
- NC has `enabledPreviewProviders` explicitly set; `registerPreviewProvider()` in `Application.php` is insufficient — the class must also be in this allowlist or the provider is silently skipped
- FQCN: `OCA\Eurooffice\Preview` (namespace `OCA\Eurooffice`, class `Preview`)
- occ stores with `\\` in config.php (PHP string literal for single backslash) — this is correct
- Index 50 chosen to avoid collision with AIO's seeded range (indices 17 on fresh install, 23 for Imaginary). Previous `wc -l` approach would have produced index 7 on a standard install, silently overwriting the Krita preview provider.
### Local `aio-apache:beta` image
Built from repo to test the Caddyfile fix:
```bash
docker buildx build --file Containers/apache/Dockerfile --tag ghcr.io/nextcloud-releases/aio-apache:beta --load Containers/apache
```
AIO uses the `:beta` tag in this test environment; local image takes precedence over remote pull.
After the build, `/Caddyfile` inside the image still had the old content (build context was captured before the edit was saved). The runtime `/tmp/Caddyfile` was manually correct. Fixed with:
```bash
docker exec nextcloud-aio-apache caddy reload --config /tmp/Caddyfile
```
---
## Status as of 2026-06-08
-**EuroOffice documents open** — Caddyfile X-Forwarded-Prefix fix working, SDK assets load correctly
-**Preview generation works** — preview PNG files stored at `/mnt/ncdata/appdata_*/preview/.../107/` (confirmed valid)
- ⚠️ **Preview display** — browser/service worker caches stale 404; hard reload (`Cmd+Shift+R`) clears it
-**Commit all changes** — entrypoint.sh, Caddyfile, ConfigurationManager.php, index.php
-**Upstream PR** to `nextcloud/all-in-one` for the Caddyfile `X-Forwarded-Prefix` fix
-**Docker Desktop restart** — still needed for `daemon.json` DNS change; currently working around it with internal URLs
---
## Caveats & Local Environment Notes
### Docker DNS — containers resolving `nextcloud.test`
**Why internal URLs are needed:** `/etc/hosts` on the macOS host has `127.0.0.1 nextcloud.test`. Docker embeds these in container DNS, so containers resolve `nextcloud.test``127.0.0.1` (the container's own loopback) rather than the host Caddy. This breaks any container→host→container call.
**Workaround (in place):** Both `DocumentServerInternalUrl` and `StorageUrl` bypass `nextcloud.test` entirely by using Docker-internal container names.
**Permanent fix (not yet applied):** Restart Docker Desktop to load `daemon.json` (`"dns": ["192.168.97.1"]`). Then dnsmasq at `192.168.97.1` will resolve `.test``0.250.250.254` (macOS `host.docker.internal`) and `nextcloud.test` will resolve correctly from containers.
Setup:
- **dnsmasq** (Homebrew): `/opt/homebrew/etc/dnsmasq.conf` has:
```
address=/.test/0.250.250.254
listen-address=127.0.0.1,192.168.97.1
```
(`192.168.97.1` is the Docker bridge gateway — containers use it as DNS)
- **`~/.docker/daemon.json`**: `"dns": ["192.168.97.1"]` — containers get dnsmasq as DNS resolver
- **`/etc/hosts`**: `127.0.0.1 nextcloud.test` — browser resolves directly (Caddy only listens on loopback, not the Docker bridge)
- Docker Desktop restart required for daemon.json to take effect — **still pending**
### Apache container — patching Caddyfile at runtime
- `/Caddyfile` is baked into the image; cannot be edited in a running container (image rootfs is read-only for that path)
- `/tmp/Caddyfile` is the runtime copy generated by `start.sh`; it is writable
- Caddy loads from `/tmp/Caddyfile` at startup (`caddy run --config /tmp/Caddyfile`)
- To apply a Caddyfile fix without a full container restart:
```bash
# edit /tmp/Caddyfile, then:
docker exec nextcloud-aio-apache caddy reload --config /tmp/Caddyfile
```
### EuroOffice NC connector app
- App ID: `eurooffice`, FQCN for preview provider: `OCA\Eurooffice\Preview`
- Available on the Nextcloud appstore: https://apps.nextcloud.com/apps/eurooffice (v11.0.0, NC 33/34)
- Added to `STARTUP_APPS` so it installs automatically on first Nextcloud start
- Sibling repo: `/Users/jamesmanuel/PhpstormProjects/euro-office-public/eurooffice-nextcloud`
- Uses OnlyOffice `/converter` API (not WOPI) for preview generation and document editing
- JWT: shared secret configured via `jwt_secret` in both `config:system:set eurooffice jwt_secret` and `config:app:set eurooffice jwt_secret`; header is `AuthorizationJwt`
### AIO UI & access
- Mastercontainer: `https://localhost:9090`
- Nextcloud: `https://nextcloud.test`
- Stop/start containers via the AIO UI — not `docker stop` directly
- Upstream beta tag being tested: `v13.2.0` (first version with EuroOffice support merged via PR #8052)
### Local images pulled
- `ghcr.io/euro-office/documentserver:v9.3.1-beta.1` — 7.14 GB
- `ghcr.io/nextcloud-releases/all-in-one:latest` — 386 MB
- `ghcr.io/nextcloud-releases/aio-apache:beta` — 289 MB (locally built)
### Building locally
```bash
# apache image (Caddyfile changes):
docker buildx build --file Containers/apache/Dockerfile --tag ghcr.io/nextcloud-releases/aio-apache:beta --load Containers/apache
# mastercontainer (PHP/config changes):
docker buildx build --file Containers/mastercontainer/Dockerfile --tag ghcr.io/nextcloud-releases/all-in-one:develop --load .
# then add ?bypass_mastercontainer_update to the URL to skip self-update prompt
```
+187
View File
@@ -0,0 +1,187 @@
# EuroOffice Default Editor — Implementation Plan
**Status:** Changes implemented, tested locally. Ready to commit.
**Phase:** Testing → Commit → Build → (upstream Caddyfile PR optional)
---
## Security Review (Completed)
**Verdict: no regressions.** The four changed files introduce no new attack surface.
- **Caddyfile `X-Forwarded-Prefix /eurooffice`** — literal string set by Caddy `header_up`, not
user-controlled. Overwrites any client-supplied value before it reaches EuroOffice nginx.
- **`X-Forwarded-Host {http.request.hostport}`** — appears in the EuroOffice route and was already
present in existing OnlyOffice and Nextcloud server blocks. Caddy only routes requests matching its
server-block bindings, and Nextcloud enforces `trusted_domains` downstream. Considered and
dismissed; not a reflective-Host injection issue in this deployment model.
- **Internal HTTP (DocumentServerInternalUrl / StorageUrl)** — identical threat model to the existing
Collabora WOPI callback path. `http://nextcloud-aio-apache.nextcloud-aio:23973` is the documented
internal callback address (used by Collabora in containers.json lines 394/405). Same Docker
network isolation, same JWT signing (`AuthorizationJwt`). No new port opened.
- **`performMigrations()` in index.php** — runs an idempotent config read on every HTTP request until
the migration flag is set for the first time. Not a security concern; minor performance overhead
on first-boot only.
---
## Wider Implications
### 1. Migration blast-radius (decision required)
`performMigrations()` runs on every mastercontainer boot. When `eurooffice_default_migration_v1` is
unset it force-sets:
- `isCollaboraEnabled = false`
- `isOnlyofficeEnabled = false`
- `isEuroofficeEnabled = true`
**This overrides an admin's explicit prior choice**, not just an unset default. An existing install
that deliberately runs Collabora will have its editor swapped silently on the next image pull.
The flag (`eurooffice_default_migration_v1`) prevents re-fighting an admin who switches back after
migration. But there is no rollback: once migrated, Collabora/OnlyOffice must be re-enabled manually.
**Open question for James:** Is the silent force-switch the intended behaviour for all existing forks
of this repo, or should it only apply when no editor has been explicitly set?
If the answer is "force-switch is correct" → current code is right.
If "preserve explicit Collabora installs" → change the migration to check whether an explicit choice
was already made (e.g., only migrate if `isCollaboraEnabled` is still at its factory default).
### 2. Preview provider index fix (bug fixed)
`occ config:system:set enabledPreviewProviders` in AIO seeds indices 17 on fresh install, and also
uses index 23 for Imaginary. The previous approach of computing the next index via `wc -l` would
produce 7 on a standard install, which collides with the Krita entry at index 7 — silently removing
Krita previews.
**Fixed:** replaced dynamic `wc -l` with fixed index 50. AIO never exceeds index 23 in its own
seeding; 50 is safe for the foreseeable set of providers.
### 3. Local-only validation gap
All testing was done in a local environment where `nextcloud.test` resolves to `127.0.0.1` inside
containers (host `/etc/hosts` is embedded into Docker DNS). The internal URLs work around this. In a
production install where DNS resolves correctly, the public-domain code path would be used instead
of the internal Docker URLs — but the code we added runs only when `$EUROOFFICE_HOST` matches the
`nextcloud-.*-eurooffice` pattern, which is the AIO default naming. That pattern is stable.
**What is reasoned but not yet tested on a clean prod-like install:** that port 23973 routes
EuroOffice→NC callbacks correctly end-to-end without the local DNS workaround in play. The port is
the documented Collabora WOPI ingress (confirmed in containers.json:394) so the reasoning is sound.
---
## Commits
Three atomic commits in logical order:
### Commit 1 — Caddyfile X-Forwarded-Prefix fix
```
fix(apache): send X-Forwarded-Prefix for EuroOffice SDK assets
EuroOffice nginx uses $http_x_forwarded_prefix to construct SDK
asset URLs (e.g. /eurooffice/sdkjs/...). Without this header the
prefix is empty and the browser requests /sdkjs/... which Caddy
routes to Nextcloud → 404.
Send X-Forwarded-Prefix as a separate header (not appended to
X-Forwarded-Host as OnlyOffice does) to match EuroOffice nginx
expectations. X-Forwarded-Host continues to carry host only.
```
**Files:** `Containers/apache/Caddyfile`
Note: this fix is also applicable upstream to `nextcloud/all-in-one` as a standalone PR (no other
changes needed).
### Commit 2 — Make EuroOffice the default editor
```
feat: make EuroOffice the default editor for new and existing installs
- isEuroofficeEnabled default: false → true
- isCollaboraEnabled default: true → false
- Add eurooffice to STARTUP_APPS
- performMigrations(): one-time force-switch for existing installs
(guarded by eurooffice_default_migration_v1 flag to prevent
re-fighting an admin who switches back)
- Call performMigrations() at index.php bootstrap
```
**Files:** `php/src/Data/ConfigurationManager.php`, `php/public/index.php`
### Commit 3 — Configure EuroOffice internal URLs and preview provider
```
fix(nextcloud): configure EuroOffice internal URLs and preview provider
Three fixes applied in entrypoint.sh when $EUROOFFICE_HOST matches
the default AIO container naming pattern:
1. DocumentServerInternalUrl → http://$EUROOFFICE_HOST:80/
Bypasses the public domain (which containers cannot resolve in
many setups) for NC→EuroOffice converter calls. Trailing slash
required; DocumentService.php concatenates the raw value with
"converter" (no separator).
2. StorageUrl → http://$APACHE_CONTAINER_HOST.nextcloud-aio:23973/
Bypasses the public domain for EuroOffice→NC file fetch calls.
Port 23973 is the Collabora WOPI ingress (server block matches
the container FQDN; port 11000 rejects the wrong Host header).
Trailing slash required; str_replace strips the / from the origin.
3. enabledPreviewProviders index 50 → OCA\Eurooffice\Preview
NC's allowlist is explicit; registerPreviewProvider() alone is
insufficient. Index 50 avoids collision with AIO's seeded range
(1-7, 23).
```
**Files:** `Containers/nextcloud/entrypoint.sh`
---
## Build and Deploy
After committing, the apache image must be rebuilt for the Caddyfile change to take effect:
```bash
# Rebuild apache image
docker buildx build \
--file Containers/apache/Dockerfile \
--tag ghcr.io/nextcloud-releases/aio-apache:beta \
--load \
Containers/apache
# Rebuild mastercontainer (for ConfigurationManager + index.php changes)
docker buildx build \
--file Containers/mastercontainer/Dockerfile \
--tag ghcr.io/nextcloud-releases/all-in-one:develop \
--load .
```
Then restart the AIO stack via the AIO UI at `https://localhost:9090`.
Use `?bypass_mastercontainer_update` in the URL to skip self-update prompt.
---
## Upstream PR (Optional)
The Caddyfile fix (commit 1) is a clean, standalone bugfix that applies to
`nextcloud/all-in-one` regardless of EuroOffice. The other two commits are
fork-specific and should not go upstream.
Before opening the upstream PR, confirm that OnlyOffice's `X-Forwarded-Host
host/onlyoffice` pattern is intentional for OO (it works differently) so the
PR can be scoped to EuroOffice only.
---
## Remaining Items
- [ ] Docker Desktop restart — apply `daemon.json` `"dns": ["192.168.97.1"]` for
permanent DNS fix. Non-urgent: internal URL workaround is in place.
- [ ] Decision on migration behaviour (see §1 above) — confirm or adjust
`performMigrations()` before tagging a release.
- [ ] Validate on a clean install without the local DNS workaround.