Files
intotheeast-com/docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md
T
m038andClaude Opus 4.8 6ad62360c6 docs: apply doc-review fixes to Grav 2.0.4 upgrade plan
- Task 2 Step 8: version-assert Expected output now includes the
  'version:' prefix the grep command actually prints (coherence).
- Rollback: state core self-upgrade is forward-only (no revert) and warn
  that server-install.sh must never be used as a live-server rollback —
  it rm -rf's the untracked git-sync.yaml encrypted token (feasibility +
  adversarial).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
2026-07-04 16:35:20 +02:00

523 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Grav 2.0.4 Upgrade + GPM-Manage Plugins — 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.
**Status:** 📋 Not started
**Goal:** Upgrade Grav core `2.0.0-rc.10``2.0.4` stable and promote `admin2`/`api`/`flex-objects` to GPM management, validated on local then the remote test env (prod is documented-only).
**Architecture:** One dependency-forced atomic upgrade. Local core is baked into the Docker image (rebuild); the server upgrades in place via `bin/gpm self-upgrade` + `bin/gpm update`. The GPM release channel is switched from `testing` to `stable` in `user/config/system.yaml`. `git-sync` is disabled for the duration of the remote upgrade and left off pending user validation.
**Tech Stack:** Grav 2.0 (PHP 8.3), GPM CLI, Docker Compose, Make (env-suffixed remote targets), Gitea content sync.
**Spec:** `docs/working/specs/2026-07-04-grav-2.0.4-upgrade-design.md`
## Global Constraints
- Version floors (GPM enforces): `grav >= 2.0.4`, `api >= 1.0.6`, `admin2 >= 2.0.9`, `flex-objects >= 1.4.3`, `login >= 3.8.11`, `form >= 6.0.0`. Assert with `>=`, not `==` — GPM may serve a newer stable patch at execution time.
- Only write inside `travel-blog-intotheeast/` or its subfolders.
- **Dual git repos.** The project root is one repo; `user/` is a *separate* repo where only `pages/ config/ accounts/ themes/` are tracked (`plugins/` is gitignored except `cache-on-save/` and `story-blocks/`). Changes to `user/config/system.yaml` commit to the **user repo** and reach the server via `make content-push` → server pull; everything else commits to the **root repo**.
- GPM channel authority is `user/config/system.yaml``gpm.releases` (must be `stable` on the server *before* any GPM op). `GRAV_CHANNEL` in docker-compose is cosmetic/consistency only.
- Never read `.env*`. Use `make remote-*` targets for all server ops.
- Do not touch `twig.cache` (stays `false` in dev per CLAUDE.md).
- `git-sync` is remote-only: never add it to `plugins.txt`; disable it during the remote upgrade and leave it disabled until the user re-enables.
- Prod is empty → **Phase 3 is documentation only, never executed.**
- Verified CLI names (against the rc.10 container): `php bin/gpm self-upgrade -y` (core), `php bin/gpm update -y` (all plugins), `php bin/grav cache` (clear cache). `bin/grav upgrade` does **not** exist.
---
## File structure
| File | Repo | Responsibility |
|---|---|---|
| `Dockerfile` | root | Baked local core version (grav-admin zip URL) |
| `plugins.txt` | root | GPM plugin manifest — gains admin2/api/flex-objects |
| `docker-compose.yml` | root | `GRAV_CHANNEL` cosmetic bump |
| `user/config/system.yaml` | **user** | Authoritative GPM channel (`gpm.releases`) |
| `scripts/server-install.sh` | root | Fresh-install script — drop admin2/api special-casing |
| `scripts/git-sync-toggle.sh` | root | New: idempotently set git-sync `enabled:` on the server |
| `Makefile` | root | Fix `remote-upgrade-grav`; add 3 remote targets |
| `docs/working/plans/...` `CLAUDE.md` `docs/reference/architecture.md` | root | Runbook + stack docs |
---
## Task 1: Phase 0 — core, plugin-list, and channel edits
**Files:**
- Modify: `Dockerfile` (the grav-admin zip URL line)
- Modify: `plugins.txt`
- Modify: `docker-compose.yml` (`GRAV_CHANNEL`)
- Modify: `user/config/system.yaml` (`gpm.releases`)
**Interfaces:**
- Produces: local image that installs Grav 2.0.4; `plugins.txt` containing `api`, `admin2`, `flex-objects`; `stable` GPM channel consumed by Task 2 (local) and Task 5 (server).
- [ ] **Step 1: Bump the core version in the Dockerfile**
In `Dockerfile`, change the download URL:
```dockerfile
RUN curl -sL 'https://github.com/getgrav/grav/releases/download/2.0.4/grav-admin-v2.0.4.zip' \
-o /tmp/grav-admin.zip \
```
(Only the URL changes — the zip still extracts to `/tmp/grav-admin/`, so every `cp` line below it is unchanged.)
- [ ] **Step 2: Add the three plugins to `plugins.txt`**
Append these lines to `plugins.txt` (order is not significant; GPM resolves deps):
```
api
admin2
flex-objects
```
- [ ] **Step 3: Switch the GPM channel to stable**
In `user/config/system.yaml`, under the `gpm:` block (currently line ~212):
```yaml
gpm:
releases: stable
official_gpm_only: true
```
(Change `testing``stable`. Leave `official_gpm_only` as-is.)
- [ ] **Step 4: Bump the cosmetic channel env**
In `docker-compose.yml`, under the `grav` service environment:
```yaml
- GRAV_CHANNEL=production
```
- [ ] **Step 5: Commit the user-repo change**
```bash
cd user
git add config/system.yaml
git commit -m "config: switch GPM release channel testing -> stable"
cd ..
```
Expected: commit succeeds in the `user` repo.
- [ ] **Step 6: Commit the root-repo changes**
```bash
git add Dockerfile plugins.txt docker-compose.yml
git commit -m "build: pin Grav core 2.0.4 and add admin2/api/flex-objects to plugins.txt"
```
Expected: commit succeeds on branch `grav-2.0.4-upgrade`.
---
## Task 2: Phase 1 — local build, clean install, validation
**Files:**
- No file edits. Executes the Task 1 changes locally.
**Interfaces:**
- Consumes: Task 1 (Dockerfile 2.0.4, plugins.txt, stable channel).
- Produces: a proven-working local 2.0.4 stack — the go/no-go gate for the remote phases.
- [ ] **Step 1: Remove the stale manually-extracted plugin folders**
These were hand-extracted from the rc.10 bundle; GPM must install them fresh.
```bash
rm -rf user/plugins/admin2 user/plugins/api user/plugins/flex-objects
```
Expected: the three folders are gone (`ls user/plugins/` no longer lists them). They are gitignored, so `git status` in `user/` is unaffected.
- [ ] **Step 2: Rebuild the image with core 2.0.4**
```bash
make build
```
Expected: build completes; the RUN layer downloads `grav-admin-v2.0.4.zip`.
- [ ] **Step 3: Recreate the container**
```bash
make start
```
Expected: `intotheeast_grav` is up on http://localhost:8081.
- [ ] **Step 4: Confirm the core version is 2.0.4**
```bash
docker exec intotheeast_grav php bin/grav --version
```
Expected output contains: `Grav CLI Application 2.0.4` (or a newer 2.0.x).
- [ ] **Step 5: Update already-installed GPM plugins to stable**
This bumps `login` (3.8.9 → ≥3.8.11, required by `api`) and `form` before the new plugins install. `gpm install` alone would skip them because they already exist.
```bash
docker exec -w /var/www/html intotheeast_grav php bin/gpm update -y
```
Expected: `login`, `form`, `shortcode-core`, etc. report as updated (or already up to date).
- [ ] **Step 6: Install the newly-listed plugins**
```bash
make install-plugins
```
Expected: `admin2`, `api`, `flex-objects` install; their dependencies resolve against the now-current `login`/`form`; no "requires grav >= 2.0.4" errors.
- [ ] **Step 7: Clear the cache**
```bash
docker exec intotheeast_grav php bin/grav cache
```
Expected: "Cache cleared" output.
- [ ] **Step 8: Assert plugin versions meet the floors**
```bash
docker exec intotheeast_grav sh -c 'cd /var/www/html && for p in admin2 api flex-objects login form; do printf "%s: " "$p"; grep -m1 "^version:" user/plugins/$p/blueprints.yaml; done'
```
Expected (at least): `admin2: version: 2.0.9`, `api: version: 1.0.6`, `flex-objects: version: 1.4.3`, `login: version: 3.8.11`, `form: version: 9.1.8` — equal or higher.
- [ ] **Step 9: Run the automated smoke suite**
This exercises the posting pipeline (`test-post` submits via the real form → add-page-by-form → cache-on-save) and renders pages via Playwright — the exact admin2/api-critical path.
```bash
make test
```
Expected: `test-config`, `test-post`, and `test-ui` all pass.
- [ ] **Step 10: Manual browser spot-check**
Visit and confirm each renders without error:
- http://localhost:8081/ (home)
- the active trip page (`/trips/japan-korea-2026`) — filter bar + map load
- one story page — hero + shortcodes render
- http://localhost:8081/admin2 — login page loads; log in
- http://localhost:8081/gpx-manager — list loads; upload a small `.gpx`, then delete it
Expected: all load; no PHP errors in `docker logs intotheeast_grav`.
- [ ] **Step 11: Checkpoint (no commit needed)**
No files changed in this task. If any step failed, stop and diagnose before proceeding — this is the go/no-go gate for remote work.
---
## Task 3: Remote Makefile targets (fix + additions)
**Files:**
- Create: `scripts/git-sync-toggle.sh`
- Modify: `Makefile` (fix `remote-upgrade-grav`; add `remote-update-plugins`, `remote-git-sync-disable`, `remote-git-sync-enable`; register the new targets in `REMOTE_TARGETS`)
**Interfaces:**
- Produces: `make remote-upgrade-grav-<env>`, `make remote-update-plugins-<env>`, `make remote-git-sync-disable-<env>`, `make remote-git-sync-enable-<env>` — consumed by Task 5.
- [ ] **Step 1: Create the git-sync toggle script**
Create `scripts/git-sync-toggle.sh` (piped to the server via `bash -s`, matching the `server-install.sh` pattern). It only ever rewrites the top-level `enabled:` key — never `folders` or the encrypted token.
```bash
#!/bin/bash
set -e
FILE="$1"
STATE="$2"
: "${FILE:?usage: git-sync-toggle.sh <git-sync.yaml path> <true|false>}"
: "${STATE:?usage: git-sync-toggle.sh <git-sync.yaml path> <true|false>}"
if [ ! -f "$FILE" ]; then
echo "ERROR: $FILE not found — is git-sync installed on this server?" >&2
exit 1
fi
if grep -qE '^enabled:' "$FILE"; then
sed -i -E "s/^enabled:.*/enabled: ${STATE}/" "$FILE"
else
printf 'enabled: %s\n' "$STATE" | cat - "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
fi
echo "git-sync now: $(grep -E '^enabled:' "$FILE")"
```
- [ ] **Step 2: Make it executable**
```bash
chmod +x scripts/git-sync-toggle.sh
```
- [ ] **Step 3: Fix the broken `remote-upgrade-grav` target**
In `Makefile`, replace the body of `remote-upgrade-grav` (currently `php bin/grav upgrade`, which is not a real command):
```make
remote-upgrade-grav: guard-env
$(SSH) "cd $(WEBROOT) && php bin/gpm self-upgrade -y && php bin/grav cache"
```
- [ ] **Step 4: Add the plugin-update target**
Add below `remote-install-plugins`:
```make
remote-update-plugins: guard-env
$(SSH) "cd $(WEBROOT) && php bin/gpm update -y && php bin/grav cache"
```
- [ ] **Step 5: Add the git-sync toggle targets**
```make
remote-git-sync-disable: guard-env
$(SSH) "bash -s -- '$(WEBROOT)/user/config/plugins/git-sync.yaml' false" < scripts/git-sync-toggle.sh
remote-git-sync-enable: guard-env
$(SSH) "bash -s -- '$(WEBROOT)/user/config/plugins/git-sync.yaml' true" < scripts/git-sync-toggle.sh
```
- [ ] **Step 6: Add a server content-status target**
For reviewing config drift after the plugin upgrade without raw SSH:
```make
remote-content-status: guard-env
$(SSH) "cd $(WEBROOT)/user && git status --short && echo '--- config diff ---' && git diff -- config/"
```
- [ ] **Step 7: Register the new targets for env-suffix generation**
In `Makefile`, extend the `REMOTE_TARGETS` list so the `-test`/`-prod` variants get generated:
```make
REMOTE_TARGETS := remote-env-setup remote-env-remove remote-wipe remote-install \
remote-fetch remote-fetch-content remote-install-plugins remote-update-plugins \
remote-upgrade-grav remote-git-sync-disable remote-git-sync-enable \
remote-content-status remote-clean remote-maintenance-on remote-maintenance-off
```
- [ ] **Step 8: Verify the targets exist and expand correctly (dry run)**
```bash
make -n remote-update-plugins-test
make -n remote-git-sync-disable-test
make -n remote-upgrade-grav-test
make -n remote-content-status-test
```
Expected: each prints the intended `ssh ...` command with `ENV=test` resolved, and no "No rule to make target" error. (No server is contacted by `-n`.)
- [ ] **Step 9: Commit**
```bash
git add scripts/git-sync-toggle.sh Makefile
git commit -m "build: fix remote-upgrade-grav; add remote plugin-update, git-sync toggle, content-status targets"
```
---
## Task 4: Fresh-install script cleanup (option B server-side)
**Files:**
- Modify: `scripts/server-install.sh` (remove admin2/api stash+restore)
**Interfaces:**
- Consumes: `plugins.txt` now containing admin2/api/flex-objects (Task 1).
- Produces: a fresh-install path where admin2/api/flex install purely via `gpm install $PLUGINS`.
- [ ] **Step 1: Remove the zip-stash lines**
In `scripts/server-install.sh`, delete lines 2526 (the admin2/api stash into `/tmp`):
```bash
cp -rf grav-admin/user/plugins/admin2 /tmp/admin2-plugin
cp -rf grav-admin/user/plugins/api /tmp/api-plugin
```
- [ ] **Step 2: Remove the restore lines**
Delete lines 4345 (the restore after the user re-clone):
```bash
cp -rf /tmp/admin2-plugin user/plugins/admin2
cp -rf /tmp/api-plugin user/plugins/api
rm -rf /tmp/admin2-plugin /tmp/api-plugin
```
Leave `mkdir -p user/plugins user/accounts user/data` in place. admin2/api/flex now come from `php bin/gpm install $PLUGINS -y` (unchanged line ~48).
- [ ] **Step 3: Syntax-check the script**
```bash
bash -n scripts/server-install.sh
```
Expected: no output (valid syntax).
- [ ] **Step 4: Commit**
```bash
git add scripts/server-install.sh
git commit -m "build: drop admin2/api zip-stash from server-install; install via GPM (option B)"
```
---
## Task 5: Phase 2 — test env upgrade
**Files:**
- No file edits. Executes against the test environment using Task 3 targets.
**Interfaces:**
- Consumes: Tasks 14 (pushed to Gitea), the `-test` make targets.
- Produces: test env on 2.0.4 with GPM-managed plugins, validated; git-sync left disabled.
- [ ] **Step 1: Push all changes to Gitea**
The server pulls `user/` content (incl. the stable-channel `system.yaml`) from Gitea; the root repo pushes normally.
```bash
git push origin grav-2.0.4-upgrade # or merge to the branch the server tracks, per your deploy convention
make content-push # pushes the user repo commit (system.yaml) to Gitea
```
Expected: both remotes updated. (Confirm with the user which branch the test server tracks before pushing.)
- [ ] **Step 2: Disable git-sync on test**
```bash
make remote-git-sync-disable-test
```
Expected: prints `git-sync now: enabled: false`.
- [ ] **Step 3: Pull latest content to the test server**
Brings the `gpm.releases: stable` change onto the server *before* any GPM op.
```bash
make remote-fetch-content-test
```
Expected: server `user/` fast-forwards; `user/config/system.yaml` shows `releases: stable`.
- [ ] **Step 4: Upgrade the core on test**
```bash
make remote-upgrade-grav-test
```
Expected: `bin/gpm self-upgrade` moves core rc.10 → 2.0.4 (stable channel); cache cleared. If it fails on a shared-folder error (see spec Risks), retry is safe — `self-upgrade` supports `-o/--overwrite`; add it to the target temporarily if a retry is needed.
- [ ] **Step 5: Update all plugins on test**
```bash
make remote-update-plugins-test
```
Expected: admin2 → ≥2.0.9, api → ≥1.0.6, flex-objects → ≥1.4.3, login → ≥3.8.11, form, git-sync all update to their stable versions; cache cleared.
- [ ] **Step 6: Review server config drift**
Inspect the server `user/` working tree for unexpected rewrites from the plugin upgrades (do NOT blind-commit):
```bash
make remote-content-status-test
```
Expected: review any `config/` diffs deliberately. Discard server-specific/reformatting churn; keep only intended changes. (git-sync is disabled, so nothing auto-commits while you review.)
- [ ] **Step 7: Smoke-test the test URL**
Against the test site (URL per your test env), confirm:
- home, a trip page, a story render
- admin2 login works
- submit one `/post` → the entry appears in the active trip's dailies
- `/gpx-manager` lists, uploads, and deletes a file
Expected: all pass. (git-sync stays disabled, so the new post will not auto-sync yet — that's expected and verified in Step 9.)
- [ ] **Step 8: Notify the user — validation checkpoint**
Report results and explicitly state that **git-sync remains disabled** on test pending their validation. Do not re-enable automatically.
- [ ] **Step 9: (User-gated) Re-enable git-sync and verify sync**
After the user confirms validation:
```bash
make remote-git-sync-enable-test
make content-push # or trigger a content change; confirm it syncs through
```
Expected: `git-sync now: enabled: true`; a content round-trip syncs between the test server and Gitea.
---
## Task 6: Docs, prod runbook, and memory
**Files:**
- Modify: `CLAUDE.md` (stack versions + plugin-management model)
- Modify: `docs/reference/architecture.md` (versions/channel)
- Modify: `docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md` (this file — Phase 3 runbook + Status)
- Modify: memory files under the auto-memory dir (project-grav2-upgrade, project-plugin-architecture)
**Interfaces:**
- Consumes: the completed local + test upgrade.
- Produces: current docs; an executable-but-unexecuted prod runbook.
- [ ] **Step 1: Update the stack facts in `CLAUDE.md`**
Change the "Current stack" block: Grav `2.0.4` (not rc.10); Admin2 to the installed stable version; note that admin2/api/flex-objects are now **GPM-managed via `plugins.txt`** (no longer hand-extracted); note `gpm.releases: stable`.
- [ ] **Step 2: Update `docs/reference/architecture.md`**
Reflect core 2.0.4, stable channel, and the three-category plugin model (GPM-managed / former-manual-now-GPM / remote-only git-sync).
- [ ] **Step 3: Write the Phase 3 prod runbook**
Append a "Phase 3 — Production (fresh install, NOT executed)" section to this plan documenting: run `make remote-install-prod` with `GRAV_VERSION=2.0.4`; admin2/api/flex install via GPM from `plugins.txt`; then set up git-sync manually (install, add encrypted token, apply the `folders:` array fix per `docs/working/git-sync-notes.md`), and leave it disabled until first validation.
- [ ] **Step 4: Update memory**
Update `project-grav2-upgrade.md` (now on 2.0.4 stable; GPM serves stable so direct-download-only no longer applies) and `project-plugin-architecture.md` (admin2/api/flex now GPM-managed; git-sync remote-only category). Refresh the `MEMORY.md` pointers if the hooks change.
- [ ] **Step 5: Set the plan Status to complete**
Change the `**Status:**` line at the top of this file to `✅ Complete (YYYY-MM-DD)` (today's date at execution).
- [ ] **Step 6: Commit**
```bash
git add CLAUDE.md docs/reference/architecture.md docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md
git commit -m "docs: record Grav 2.0.4 upgrade; GPM-managed plugins; prod runbook"
```
(Memory files live outside the repo; they are written directly, not committed here.)
---
## Rollback
If any phase fails and cannot be fixed forward:
1. `git revert` the relevant commits on `grav-2.0.4-upgrade` (root repo) and the `user` repo `system.yaml` commit.
2. Local: `make build && make start && make install-plugins`.
3. Test server: config, content, and plugin reverts are delivered via `make content-push` + `make remote-fetch-content-test`. **The core is the exception — once `bin/gpm self-upgrade` has completed it cannot downgrade, so treat a completed core upgrade as forward-only and fix forward; there is no revert for it.**
> ⚠️ Do **not** run the fresh-install path (`scripts/server-install.sh`) against a live server as a rollback. It does `rm -rf user; git clone`, which destroys the server-only, gitignored `user/config/plugins/git-sync.yaml` (the encrypted git-sync token a clone never restores). The fresh-install path is for empty/new servers only.
Content, config, and accounts are in git, so no data restore is required — but note the core caveat above: "rollback = git" covers config/content/plugins, **not** a completed server core self-upgrade.