From b98ae50f30170dfafed0307df35fdb18f948fd4f Mon Sep 17 00:00:00 2001 From: Mischa Date: Thu, 18 Jun 2026 23:05:13 +0200 Subject: [PATCH] docs: add Grav 2.0 upgrade implementation plan Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-06-18-grav2-upgrade.md | 489 ++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-18-grav2-upgrade.md diff --git a/docs/superpowers/plans/2026-06-18-grav2-upgrade.md b/docs/superpowers/plans/2026-06-18-grav2-upgrade.md new file mode 100644 index 0000000..7266786 --- /dev/null +++ b/docs/superpowers/plans/2026-06-18-grav2-upgrade.md @@ -0,0 +1,489 @@ +# Grav 2.0 Upgrade Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Upgrade the local dev Docker environment from linuxserver/grav 1.7 to getgrav/grav 2.0 RC, validate the full Milestone 1 posting workflow, and update the production install script for a fresh Grav 2.0 deploy. + +**Architecture:** Two tracks in sequence — (1) swap the Docker image and update all dependent config/paths, boot the site with `make setup`, run the existing test suite; (2) update `server-install.sh` so `make remote-install` deploys Grav 2.0 fresh on the production PHP 8.4 server. The `user/` directory (content, config, theme, custom plugins) is already isolated as a git repo and requires only a small compatibility addition to `cache-on-save`. + +**Tech Stack:** Grav CMS 2.0.0-rc.9, PHP 8.4 (production) / Docker `getgrav/grav` with PHP 8.3 (dev), Apache, Twig 3, Symfony 7, Playwright (UI tests). + +## Global Constraints + +- All work on branch `update-to-2.0` (already created) +- Never read `.env` — contains sensitive credentials +- Only modify files in the project root or `user/` subfolders +- `user/config/`, `user/plugins/cache-on-save/`, `user/themes/` changes go through the `user/` git repo (tracked separately; push with `make content-push`) +- Container name stays `intotheeast_grav`; local port stays `8081` +- `make` commands are the only way to interact with the remote server +- Grav 2.0 requires PHP ≥ 8.3 (dev container uses 8.3 default; production uses 8.4 — both compliant) +- Production download URL format: `https://getgrav.org/download/core/grav-admin/${GRAV_VERSION}${GRAV_CHANNEL_SUFFIX}` + +--- + +## Files Changed + +| File | Action | Reason | +|---|---|---| +| `docker-compose.yml` | Modify | Switch image, update volume + PHP ini path, add env var | +| `Makefile` | Modify | Three `docker exec` targets hardcode linuxserver's `/app/www/public` path | +| `user/plugins/cache-on-save/blueprints.yaml` | Create | Grav 2.0 compat flag (required by GPM) | +| `user/config/system.yaml` | Modify | Switch GPM channel from `stable` to `testing` | +| `scripts/server-install.sh` | Modify | Support `GRAV_CHANNEL_SUFFIX` for `?testing` query param on 2.0 RC download | + +--- + +## Task 1: Swap Docker image and fix container paths + +**Files:** +- Modify: `docker-compose.yml` +- Modify: `Makefile` + +**Interfaces:** +- Produces: A running Grav 2.0 container reachable at `http://localhost:8081` with `user/` mounted at `/var/www/html/user` and PHP upload limits applied via `/usr/local/etc/php/conf.d/php-local.ini` + +- [ ] **Step 1: Stop and remove the current container** + +```bash +cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast +docker compose down +``` + +Expected: container `intotheeast_grav` stops and is removed. + +- [ ] **Step 2: Update `docker-compose.yml`** + +Replace the entire contents of `docker-compose.yml` with: + +```yaml +services: + grav: + image: getgrav/grav + container_name: intotheeast_grav + environment: + - GRAV_CHANNEL=beta + ports: + - "8081:80" + volumes: + - ./user:/var/www/html/user + - ./php/php-local.ini:/usr/local/etc/php/conf.d/php-local.ini + restart: unless-stopped +``` + +Key changes from old file: +- `image`: `lscr.io/linuxserver/grav:latest` → `getgrav/grav` +- `environment`: removed `PUID`/`PGID` (linuxserver-specific), added `GRAV_CHANNEL=beta` +- `volumes[0]`: `/config/www/user` → `/var/www/html/user` +- `volumes[1]`: `/config/php/php-local.ini` → `/usr/local/etc/php/conf.d/php-local.ini` + +- [ ] **Step 3: Update Makefile — three targets use the old container path** + +In `Makefile`, make these three targeted replacements: + +**`install-plugins` target** — change working directory flag: + +Old: +```makefile +install-plugins: + docker exec -w /app/www/public intotheeast_grav php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y +``` + +New: +```makefile +install-plugins: + docker exec -w /var/www/html intotheeast_grav php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y +``` + +**`demo-load` target** — change cache clear path: + +Old: +```makefile +demo-load: + cp -r user/docs/demo/tracker/. user/pages/01.tracker/ + docker exec intotheeast_grav bash -c "cd /app/www/public && php bin/grav clearcache" +``` + +New: +```makefile +demo-load: + cp -r user/docs/demo/tracker/. user/pages/01.tracker/ + docker exec intotheeast_grav bash -c "cd /var/www/html && php bin/grav clearcache" +``` + +**`demo-reset` target** — change cache clear path: + +Old: +```makefile +demo-reset: + @for dir in user/docs/demo/tracker/*/; do \ + folder=$$(basename "$$dir"); \ + rm -rf "user/pages/01.tracker/$$folder"; \ + done + docker exec intotheeast_grav bash -c "cd /app/www/public && php bin/grav clearcache" +``` + +New: +```makefile +demo-reset: + @for dir in user/docs/demo/tracker/*/; do \ + folder=$$(basename "$$dir"); \ + rm -rf "user/pages/01.tracker/$$folder"; \ + done + docker exec intotheeast_grav bash -c "cd /var/www/html && php bin/grav clearcache" +``` + +- [ ] **Step 4: Validate docker-compose syntax** + +```bash +docker compose config +``` + +Expected: prints merged compose config with no errors. If you see `Error`, re-check the YAML indentation in `docker-compose.yml`. + +- [ ] **Step 5: Commit** + +```bash +git add docker-compose.yml Makefile +git commit -m "feat: switch to getgrav/grav 2.0 RC docker image + +Co-Authored-By: Claude Sonnet 4.6 " +``` + +--- + +## Task 2: Add Grav 2.0 compat flag and switch GPM to testing channel + +**Files:** +- Create: `user/plugins/cache-on-save/blueprints.yaml` +- Modify: `user/config/system.yaml` (line ~200, `gpm:` section) + +**Interfaces:** +- Consumes: Running container from Task 1 +- Produces: GPM resolves 2.0-compatible plugin versions on install; `cache-on-save` is recognized as 2.0-compatible by Grav's plugin registry + +- [ ] **Step 1: Create `user/plugins/cache-on-save/blueprints.yaml`** + +Create the file with this exact content: + +```yaml +name: Cache On Save +version: 1.0.0 +description: Clears Grav cache on new-entry form submission +author: + name: Mischa + email: mischa@gorinskat.nl +license: MIT + +dependencies: + - { name: grav, version: '>=1.6.0' } + +grav: + version: ['1.7', '2.0'] +``` + +- [ ] **Step 2: Update GPM channel in `user/config/system.yaml`** + +Find the `gpm:` section (around line 200 in the file) and change `releases: stable` to `releases: testing`: + +Old: +```yaml +gpm: + releases: stable + official_gpm_only: true +``` + +New: +```yaml +gpm: + releases: testing + official_gpm_only: true +``` + +- [ ] **Step 3: Commit to user/ repo and main repo** + +```bash +cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast +git add user/plugins/cache-on-save/blueprints.yaml user/config/system.yaml +git commit -m "feat: add Grav 2.0 compat flag and switch GPM to testing channel + +Co-Authored-By: Claude Sonnet 4.6 " +``` + +--- + +## Task 3: Boot Grav 2.0 and install plugins + +**Files:** None (runtime only) + +**Interfaces:** +- Consumes: docker-compose.yml from Task 1, GPM config from Task 2 +- Produces: Running Grav 2.0 instance at `http://localhost:8081` with all plugins installed + +- [ ] **Step 1: Run setup** + +```bash +cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast +make setup +``` + +This starts the container and installs all plugins from `plugins.txt`. First run may take 1-2 minutes as `getgrav/grav` downloads and extracts Grav 2.0 RC. + +Expected output ends with something like: +``` +GPM Packages Installed: admin, email, error, form, login, problems, add-page-by-form, shortcode-gallery-plusplus +``` + +If `make setup` fails on plugin install with a permission error, fix with: +```bash +docker exec intotheeast_grav chown -R www-data:www-data /var/www/html/cache /var/www/html/logs /var/www/html/tmp +make install-plugins +``` + +- [ ] **Step 2: Verify PHP upload limits are applied** + +```bash +docker exec intotheeast_grav php -r "echo ini_get('upload_max_filesize') . ' / ' . ini_get('post_max_size');" +``` + +Expected: `100M / 500M` + +If you see `2M / 8M` (PHP defaults), the ini mount path is wrong. Verify with: +```bash +docker exec intotheeast_grav php -r "echo php_ini_scanned_files();" +``` +It should include `/usr/local/etc/php/conf.d/php-local.ini`. + +- [ ] **Step 3: Verify site loads** + +```bash +curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/ +``` + +Expected: `200` + +If you get `500`, check container logs: +```bash +docker logs intotheeast_grav --tail 50 +``` + +- [ ] **Step 4: Verify Admin2 loads** + +```bash +curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/admin +``` + +Expected: `200` (Admin2 SPA login page, not the old Twig admin) + +- [ ] **Step 5: Run config and HTTP tests** + +```bash +make test-config +make test-post +``` + +`test-config` validates the form YAML config. `test-post` submits the posting form via HTTP and checks an entry is created. + +Expected: both exit 0. + +If `test-post` fails, check the output of: +```bash +bash scripts/test-post.sh +``` +This is the critical `add-page-by-form` go/no-go test. If it fails with a 500 or the entry isn't created, see the **If add-page-by-form fails** section at the bottom of this plan. + +- [ ] **Step 6: Commit task completion note** + +No new files to commit. Move to Task 4. + +--- + +## Task 4: Run Playwright test suite and fix any Admin2 regressions + +**Files:** +- Modify: `tests/*.spec.js` (only if tests fail due to Admin2 DOM changes) + +**Interfaces:** +- Consumes: Running Grav 2.0 from Task 3 +- Produces: All Playwright tests passing (or updated for Admin2's new DOM) + +- [ ] **Step 1: Run the full UI test suite** + +```bash +cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast +make test-ui +``` + +Expected: 25 tests pass. + +- [ ] **Step 2: If any tests fail, classify the failure** + +For each failing test, determine whether it is: + +**A) A genuine regression** (e.g., posting form broken, tracker page missing entries, gallery not rendering) — these are blockers. Stop, investigate the root cause, and fix the underlying Grav/plugin issue before updating the test. + +**B) An Admin2 DOM change** (e.g., selectors targeting old admin HTML structure like `.admin-menu`, `.grav-nav`, admin-specific CSS classes) — these are acceptable test updates. Update the selector in the test file to match Admin2's new HTML. + +To inspect the current Admin2 DOM for a failing selector: +```bash +# Check what the admin page actually renders +curl -s http://localhost:8081/admin | grep -o '<[^>]*class="[^"]*admin[^"]*"[^>]*>' | head -20 +``` + +- [ ] **Step 3: Update any Admin2 selector regressions** + +For each type-(B) failure, open the relevant test file in `tests/` and update the selector. Example pattern for updating an admin navigation selector: + +Old (targeting classic admin): +```js +await page.click('.grav-nav-toggle') +``` + +New (targeting Admin2 SPA — find actual selector from step 2's output): +```js +await page.click('[data-testid="nav-toggle"]') // replace with actual Admin2 selector +``` + +After each fix, re-run just that test: +```bash +npx playwright test tests/.spec.js --headed +``` + +- [ ] **Step 4: Re-run full suite to confirm all pass** + +```bash +make test-ui +``` + +Expected: all tests pass. + +- [ ] **Step 5: Commit any test updates** + +If any test files were modified: +```bash +git add tests/ +git commit -m "test: update Playwright selectors for Admin2 DOM + +Co-Authored-By: Claude Sonnet 4.6 " +``` + +If no test files changed, no commit needed. + +--- + +## Task 5: Update production install script for Grav 2.0 + +**Files:** +- Modify: `scripts/server-install.sh` + +**Interfaces:** +- Consumes: Nothing from prior tasks (independent of Docker) +- Produces: `make remote-install` deploys a fresh Grav 2.0 on the production PHP 8.4 server when `GRAV_VERSION=2.0.0-rc.9` and `GRAV_CHANNEL_SUFFIX=?testing` are set in `.env` + +- [ ] **Step 1: Update the wget download line in `scripts/server-install.sh`** + +The script currently downloads Grav with: +```bash +wget --no-verbose "https://getgrav.org/download/core/grav-admin/$GRAV_VERSION" -O grav-admin.zip +``` + +Grav 2.0 RC requires `?testing` appended to the URL. Add `GRAV_CHANNEL_SUFFIX` support: + +Old (line ~15 in the file): +```bash +echo "==> Downloading Grav $GRAV_VERSION" +cd "$WEBROOT" +wget --no-verbose "https://getgrav.org/download/core/grav-admin/$GRAV_VERSION" -O grav-admin.zip +``` + +New: +```bash +echo "==> Downloading Grav $GRAV_VERSION" +cd "$WEBROOT" +wget --no-verbose "https://getgrav.org/download/core/grav-admin/${GRAV_VERSION}${GRAV_CHANNEL_SUFFIX:-}" -O grav-admin.zip +``` + +The `${GRAV_CHANNEL_SUFFIX:-}` expands to empty string if unset, keeping stable releases working without any changes to `.env`. + +- [ ] **Step 2: Add GRAV_CHANNEL_SUFFIX to the env var validation block** + +At the top of the script the required vars are validated. `GRAV_CHANNEL_SUFFIX` is optional, so do NOT add it to the `:?` required list. Instead, add a comment above the download step: + +After the `set -e` and required var block, add a comment before the download line: + +```bash +# GRAV_CHANNEL_SUFFIX: optional, set to '?testing' for RC/beta releases (e.g. 2.0.0-rc.9) +# Leave unset or empty for stable releases. +``` + +- [ ] **Step 3: Verify the script logic looks correct** + +```bash +# Dry-run: simulate what the URL would be with 2.0 RC vars +GRAV_VERSION=2.0.0-rc.9 GRAV_CHANNEL_SUFFIX='?testing' bash -c \ + 'echo "https://getgrav.org/download/core/grav-admin/${GRAV_VERSION}${GRAV_CHANNEL_SUFFIX:-}"' +``` + +Expected output: +``` +https://getgrav.org/download/core/grav-admin/2.0.0-rc.9?testing +``` + +```bash +# Dry-run: simulate stable release (no suffix) +GRAV_VERSION=1.7.53 bash -c \ + 'echo "https://getgrav.org/download/core/grav-admin/${GRAV_VERSION}${GRAV_CHANNEL_SUFFIX:-}"' +``` + +Expected output: +``` +https://getgrav.org/download/core/grav-admin/1.7.53 +``` + +- [ ] **Step 4: Commit** + +```bash +git add scripts/server-install.sh +git commit -m "feat: support GRAV_CHANNEL_SUFFIX for Grav 2.0 RC production install + +Co-Authored-By: Claude Sonnet 4.6 " +``` + +--- + +## If `add-page-by-form` fails (contingency) + +If `make test-post` in Task 3 step 5 returns a non-zero exit code or the entry is not created, `add-page-by-form` is incompatible with Grav 2.0. The fallback is to write a custom replacement plugin. + +**Do not proceed to Task 4 if the posting workflow is broken.** Instead: + +1. Check the container logs for the specific error: +```bash +docker logs intotheeast_grav --tail 100 | grep -i "error\|exception\|warning" +``` + +2. Note the error, stop work, and report back. The custom replacement plugin is a separate task requiring design input from the project owner before implementation. + +The custom plugin would: +- Hook `onFormProcessed` (same as `cache-on-save`) +- Read form field values (`title`, `content`, `photo`) +- Build the page path under `user/pages/01.tracker/` +- Write the page file to disk using `Grav\Common\Page\Page` +- Merge `cache-on-save` functionality (call `$this->grav['cache']->deleteAll()`) +- Replace both `add-page-by-form` and `cache-on-save` with a single plugin + +This is ~200 lines of PHP and ~1 day of work. It should be planned separately. + +--- + +## Final smoke test (after all tasks complete) + +Run the full test suite one last time: + +```bash +cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast +make test +``` + +Expected: all three suites (`test-config`, `test-post`, `test-ui`) exit 0. + +Then verify the go/no-go criteria from the spec are all met before merging to `main` or deploying to production.