diff --git a/.env.example b/.env.example index 9cb20e4..66ab222 100644 --- a/.env.example +++ b/.env.example @@ -26,11 +26,15 @@ UID=1000 GID=1000 -# Local Grav dev server + test login, used by `make test-post` / `make test` -# (scripts/test-post.sh). Must be a valid Grav site login on the local instance. +# Local Grav dev server. GRAV_BASE_URL is used by the Playwright suite and +# scripts/test-post.sh. GRAV_BASE_URL=http://localhost:8081 -GRAV_TEST_USER=your-local-grav-user -GRAV_TEST_PASS=your-local-grav-password +# Test login for `make test` — OPTIONAL. If unset, the suite auto-creates and +# uses a dedicated local-only account (testrunner / Testpass1234), gitignored so +# it is never pushed to prod (see `make test-account`). Override only to test as +# a different account; keep the password free of shell/Make/URL-special chars. +# GRAV_TEST_USER=testrunner +# GRAV_TEST_PASS=Testpass1234 GRAV_USER_DIR=/absolute/path/to/travel-blog-intotheeast/user # travel-memories service (docker-compose `env_file: .env`). Fill in whatever @@ -55,7 +59,7 @@ WEBROOT=/home/example.com/public_html SITE_CONFIG_DIR=/home/example.com/site-config # Grav version installed by scripts/server-install.sh (remote-install). -GRAV_VERSION=2.0.0-rc.10 +GRAV_VERSION=2.0.4 # Repos cloned/pulled on the server. USER_REPO=https://gitea.example.com/org/intotheeast-user.git diff --git a/CLAUDE.md b/CLAUDE.md index 17a3322..bd59064 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,11 +15,15 @@ ### Current stack -- **Grav:** 2.0.0-rc.10 (baked into the custom Docker image via `Dockerfile`) -- **Admin:** Admin2 v2.0.0-rc.15 (plugin slug: `admin2`, NOT `admin`) -- **Docker image:** `getgrav/grav` with `GRAV_CHANNEL=beta` +- **Grav:** 2.0.4 stable (baked into the custom Docker image via `Dockerfile`; server upgrades in place via `bin/gpm self-upgrade`) +- **Admin:** Admin2 v2.0.10 (plugin slug: `admin2`, NOT `admin`) +- **GPM channel:** `stable` — set in `user/config/system.yaml` → `gpm.releases` (authoritative). `GRAV_CHANNEL=production` in `docker-compose.yml` is cosmetic/consistency only +- **Plugin management:** `admin2`, `api`, and `flex-objects` are now **GPM-managed via `plugins.txt`** (installed by `make install-plugins`), no longer hand-extracted from the core bundle. `git-sync` stays **remote-only** — never in `plugins.txt` +- **Docker image:** `getgrav/grav` with `GRAV_CHANNEL=production` - **PHP session:** `session.save_path = /tmp` set in `php/php-local.ini` +> Known issue (2026-07-04): Form 9.1.10 regressed the `filepond` upload field — on the post-submit re-render, `filepond.html.twig` runs `merge` on a string and 500s. The journal entry still saves correctly; only the browser re-render errors. This breaks the 6 `post.spec.js` UI specs. Being fixed separately in the form-to-page/image-upload rework — **do not** work around it here. + ### Dev server The Docker dev server runs at **http://localhost:8081** (mapped from container port 80 in `docker-compose.yml`). diff --git a/Dockerfile b/Dockerfile index 0ceb046..68084a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM getgrav/grav -RUN curl -sL 'https://github.com/getgrav/grav/releases/download/2.0.0-rc.10/grav-admin-v2.0.0-rc.10.zip' \ +RUN curl -sL 'https://github.com/getgrav/grav/releases/download/2.0.4/grav-admin-v2.0.4.zip' \ -o /tmp/grav-admin.zip \ && unzip -q /tmp/grav-admin.zip -d /tmp \ && cp -rf /tmp/grav-admin/assets /var/www/html/ \ diff --git a/Makefile b/Makefile index d97c29a..745c8c8 100644 --- a/Makefile +++ b/Makefile @@ -20,8 +20,9 @@ SITE_CONFIG_DIR ?= $(REMOTE_HOME)/site-config # make remote-install-prod → runs remote-install with ENV=prod # Calling a bare remote target (no ENV) fails via guard-env. REMOTE_TARGETS := remote-env-setup remote-env-remove remote-wipe remote-install \ - remote-fetch remote-fetch-content remote-install-plugins remote-upgrade-grav \ - remote-clean remote-maintenance-on remote-maintenance-off + 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 ENVS := test prod guard-env: @@ -35,13 +36,23 @@ $(foreach t,$(REMOTE_TARGETS),$(foreach e,$(ENVS),$(eval $(call make-env-target, # ── Tests ───────────────────────────────────────────────────────────────────── +# Local test account — auto-created, never committed (see user/.gitignore). +# Keep the password free of shell/Make/URL-special chars so every consumer agrees. +GRAV_TEST_USER ?= testrunner +GRAV_TEST_PASS ?= Testpass1234 + +test-account: + @docker exec intotheeast_grav sh -c 'test -f /var/www/html/user/accounts/$(GRAV_TEST_USER).yaml \ + || php bin/plugin login new-user -u $(GRAV_TEST_USER) -p "$(GRAV_TEST_PASS)" \ + -e $(GRAV_TEST_USER)@example.test -N "Test Runner" -P b --admin-type both -s enabled -n' + test-config: @bash scripts/test-form-config.sh -test-post: +test-post: test-account @bash scripts/test-post.sh -test-ui: +test-ui: test-account @npx playwright test test: test-config test-post test-ui @@ -144,9 +155,20 @@ remote-fetch-content: guard-env remote-install-plugins: guard-env $(SSH) "cd $(WEBROOT) && php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y" +remote-update-plugins: guard-env + $(SSH) "cd $(WEBROOT) && php bin/gpm update -y && php bin/grav cache" remote-upgrade-grav: guard-env - $(SSH) "cd $(WEBROOT) && php bin/grav upgrade" + $(SSH) "cd $(WEBROOT) && php bin/gpm self-upgrade -y && php bin/grav cache" + +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 + +remote-content-status: guard-env + $(SSH) "cd $(WEBROOT)/user && git status --short && echo '--- config diff ---' && git diff -- config/" remote-clean: guard-env $(SSH) "cd $(WEBROOT) && php bin/grav clearcache" diff --git a/docker-compose.yml b/docker-compose.yml index 50194c9..4e06171 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: build: . container_name: intotheeast_grav environment: - - GRAV_CHANNEL=beta + - GRAV_CHANNEL=production - APACHE_RUN_USER=#1000 - APACHE_RUN_GROUP=#1000 ports: diff --git a/docs/reference/architecture.md b/docs/reference/architecture.md index 688db1e..049b161 100644 --- a/docs/reference/architecture.md +++ b/docs/reference/architecture.md @@ -8,8 +8,9 @@ How the intotheeast site hangs together. | Layer | Technology | Notes | |---|---|---| -| CMS | Grav 2.0.0-rc.10 | Flat-file PHP CMS; no database | -| Admin | Admin2 v2.0.0-rc.15 | Plugin slug: `admin2` (not `admin`) | +| CMS | Grav 2.0.4 stable | Flat-file PHP CMS; no database. Server upgrades in place via `bin/gpm self-upgrade` | +| Admin | Admin2 v2.0.10 | Plugin slug: `admin2` (not `admin`) | +| GPM channel | `stable` | Authoritative in `user/config/system.yaml` → `gpm.releases`; `GRAV_CHANNEL=production` in compose is cosmetic | | Container | Docker (`getgrav/grav` base + custom `Dockerfile`) | Grav 2.0 baked in at build time | | PHP session | `session.save_path = /tmp` | Set in `php/php-local.ini` | | Dev URL | http://localhost:8081 | Mapped from container port 80 | @@ -49,6 +50,14 @@ Other notable plugins: | `api` (Grav API v1) | Used by /gpx-manager to list/upload/delete GPX files | | `admin2` | Admin panel at /admin | +### Plugin management model + +Three categories, by how each plugin is installed and maintained: + +1. **GPM-managed** (`plugins.txt` → `make install-plugins`): the marketplace plugins, including `login`, `form`, `admin2`, `api`, `flex-objects`, shortcodes, etc. As of the 2.0.4 upgrade, `admin2`/`api`/`flex-objects` moved into this category — they were previously hand-extracted from the core bundle. Update with `bin/gpm update` (`make remote-update-plugins-` on servers). +2. **Custom, in-repo** (`user/plugins/` allowlisted in `user/.gitignore`): `cache-on-save`, `story-blocks`. Versioned in the user repo. +3. **Remote-only**: `git-sync` — installed and configured only on servers, **never** in `plugins.txt`, and disabled during upgrades. + --- ## Template hierarchy diff --git a/docs/solutions/test-failures/new-user-grants-api-not-admin-on-admin2.md b/docs/solutions/test-failures/new-user-grants-api-not-admin-on-admin2.md new file mode 100644 index 0000000..890a6e0 --- /dev/null +++ b/docs/solutions/test-failures/new-user-grants-api-not-admin-on-admin2.md @@ -0,0 +1,82 @@ +--- +title: "Grav login new-user grants api.* but not admin.* on Admin2-only installs" +date: 2026-07-04 +category: docs/solutions/test-failures +module: testing / account provisioning +problem_type: test_failure +component: authentication +symptoms: + - "gpx-manager Playwright specs fail (401 / login form shown) after switching the suite onto a dedicated test account" + - "an authenticated account still sees the login form at /gpx-manager instead of the manager UI" + - "the generated accounts/*.yaml has an access.api block but no access.admin block" +root_cause: missing_permission +resolution_type: tooling_addition +severity: medium +related_components: + - testing_framework + - tooling +tags: + - grav + - login-plugin + - admin2 + - permissions + - playwright + - test-account + - gpx-manager +--- + +# Grav login new-user grants api.* but not admin.* on Admin2-only installs + +## Problem +When the Playwright suite was moved onto a dedicated local `testrunner` account, every `/gpx-manager` spec started failing — the account could authenticate but was treated as unauthorized for the manager page. The account had been created with `bin/plugin login new-user ... -P b` (Admin + Site access) but **without** `--admin-type`, and on this Admin2-only install that grants `api.*` permissions and no `admin.*` permissions. + +## Symptoms +- The `/gpx-manager` Playwright specs fail after switching from the real user to the `testrunner` account (they passed as the real user). +- An authenticated `testrunner` still gets the Login plugin's login form at `/gpx-manager` instead of the manager UI. +- The generated `user/accounts/testrunner.yaml` contains an `access.api` block (`login: true`, `super: true`) but **no** `access.admin` block. + +## What Didn't Work +- **Assuming `-P b` was enough.** `-P/--permissions b` selects the *category* of access (Admin + Site), but the *type* of admin permission — classic `admin.*` vs Admin2 `api.*` — is a separate axis controlled by `--admin-type`, which defaults to auto-detect. `-P b` alone does not guarantee `admin.login`. +- **Blaming the wrong specs.** In the same push, the home `H1`/map specs were also red, which looked like it might be the same auth problem. It was not — those were gated by `site.yaml` `travelling: false` hiding the active-trip view, a completely separate cause. Conflating the two delayed pinning the permission root cause. + +## Solution +Create the account with an explicit `--admin-type both`, and bake it into the idempotent `make test-account` target so every recreation is faithful: + +```make +test-account: + @docker exec intotheeast_grav sh -c 'test -f /var/www/html/user/accounts/$(GRAV_TEST_USER).yaml \ + || php bin/plugin login new-user -u $(GRAV_TEST_USER) -p "$(GRAV_TEST_PASS)" \ + -e $(GRAV_TEST_USER)@example.test -N "Test Runner" -P b --admin-type both -s enabled -n' +``` + +Verify the resulting permissions actually include `admin.login`: + +```bash +docker exec intotheeast_grav rm -f /var/www/html/user/accounts/testrunner.yaml +make test-account +docker exec intotheeast_grav sh -c 'grep -A6 "^access:" /var/www/html/user/accounts/testrunner.yaml' +# access: +# admin: +# login: true +# super: true +# api: +# login: true +# super: true +``` + +## Why This Works +The `login new-user` help text spells out the axis: + +> `--admin-type` — Which admin permission type to grant when permissions include Admin: `admin` (classic Admin plugin, `admin.*`), `api` (Admin2, `api.*`), or `both`. **If omitted, auto-detects from which admin plugin is installed.** + +This site runs **Admin2 only** (the classic `admin` plugin is disabled), so auto-detect resolves to `api` and emits `api.*` alone. `/gpx-manager` is gated by `access.admin.login: true` in its page frontmatter (enforced by the Login plugin), and that check looks specifically for the `admin.login` permission — `api.login` does not satisfy it. Passing `--admin-type both` forces both namespaces into the account, so the admin-gated page accepts the session. + +## Prevention +- **On Admin2-only Grav installs, always pass `--admin-type both` (or `admin`) to `login new-user`** when the account must reach any page gated by `access.admin.login` (e.g. `/gpx-manager`). Auto-detect will otherwise silently give you api-only. +- **Assert the permission, not the exit code.** After provisioning an account for admin-gated pages, check that `access.admin.login` exists in the generated YAML rather than trusting that account creation "succeeded." +- **Keep provisioning in one idempotent place.** The `make test-account` target is the single source of truth; `tests/global-setup.js` calls it, so `make test` and a bare `npx playwright test` both get identical permissions. Don't hand-create the account out-of-band with different flags — that reintroduces the drift this fix removed. + +## Related Issues +- `docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md` — the self-contained test-account infrastructure shipped alongside the Grav 2.0.4 upgrade. +- `docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md` — accounts live in the `user/` repo; the `testrunner` account is gitignored so it never reaches production. +- GPX manager auth model (`access.admin.login: true` frontmatter + Login plugin) — see the project's GPX manager notes. diff --git a/docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md b/docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md new file mode 100644 index 0000000..7ff3e3a --- /dev/null +++ b/docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md @@ -0,0 +1,552 @@ +# 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:** ✅ Complete (2026-07-04) — Phase 1 (local) and Phase 2 (remote test env) both validated and shipped; git-sync re-enabled on test. Phase 3 (prod) is documentation-only per design. See "Known issue" below re: Form 9.1.10 filepond. + +**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-`, `make remote-update-plugins-`, `make remote-git-sync-disable-`, `make remote-git-sync-enable-` — 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 }" +: "${STATE:?usage: git-sync-toggle.sh }" + +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 25–26 (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 43–45 (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 1–4 (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. + +--- + +## Execution outcome (2026-07-04) + +**Installed local versions (all at/above floors):** Grav `2.0.4`, admin2 `2.0.10`, api `1.0.7`, flex-objects `1.4.4`, login `3.8.11`, form `9.1.10`, shortcode-core `6.2.2`. + +**Phase 1 (local): validated + shipped.** Image rebuilt on 2.0.4, plugins installed via GPM, cache clears, rendering clean (home, trips, story, `/admin2` login, `/gpx-manager` list/upload/delete all 200). Test suite: **75 passing**. Test-config was re-pointed off the retired `japan-korea-2026` onto the vetted `italy-2026-demo` data. A self-contained, gitignored `testrunner` account (created via `make test-account` with `--admin-type both`) makes `make test` runnable without the real account in `.env`. + +**Known issue — Form 9.1.10 filepond regression (blocked elsewhere, not a go/no-go blocker).** The post form's `filepond` photo field 500s on the post-submit re-render (`filepond.html.twig` runs `merge` on a string). The journal entry still saves correctly (curl/on-disk `test-post.sh` passes); only the browser re-render errors, failing 6 `post.spec.js` UI specs. This is a stock-plugin upgrade regression, being fixed independently in the form-to-page/image-upload rework. **Do not** add a theme-override workaround in this upgrade — let that rework own the fix. + +**Tasks 3 & 4 (remote Makefile targets + server-install cleanup): shipped** (committed on this branch). + +**Task 5 (Phase 2, remote test-env): executed and validated (2026-07-04).** Sequence run: `remote-git-sync-disable-test` → `content-push` → `remote-fetch-content-test` → `remote-upgrade-grav-test` (core rc.10 → **2.0.7**; stable served a newer patch than the 2.0.4 floor) → `remote-update-plugins-test` (all plugins to stable) → `remote-content-status-test`. Smoke test on `https://test.intotheeast.com`: `/` (renders), `/admin` (admin2 panel; note the server routes admin at `/admin`, not `/admin2`), and `/gpx-manager` all return 200 with no Twig/PHP errors. git-sync was **re-enabled** afterward at the user's request. + +**Config-drift gotcha (reconciled).** The server's `bin/gpm self-upgrade` ran Grav's schema migration, which rewrote `system.yaml` `strict_mode` from the 1.7-era `twig_compat: false` to `twig2_compat: false` + `twig3_compat: true`. The **local** upgrade never triggered this because it was a fresh Docker-image build, not a `self-upgrade` — so the repo's `system.yaml` was stale and a future `remote-fetch-content` (`reset --hard`) would have reverted the server. Fix: folded the Twig 3 flags into the repo's `user/config/system.yaml` (committed `2e32a85`, content-pushed), verified the local Grav 2.0.x container renders 200 under them, then reset the server to the new `origin/main` before re-enabling git-sync so its working tree was clean. `versions.yaml` and `accounts/.htaccess` drift is install-local and left to Grav to manage. + +--- + +## Phase 3 — Production (fresh install, NOT executed) + +Production is empty, so this is a **fresh install**, not an upgrade — and it is **documentation only**. Do not run it as part of this plan. + +When prod is provisioned: + +1. **Provision creds:** copy the REMOTE section of `.env.example` into `.env.prod` with production values (never commit it). Run `make remote-env-setup-prod`. +2. **Fresh install at 2.0.4:** `make remote-install-prod` with `GRAV_VERSION=2.0.4` in `.env.prod`. `scripts/server-install.sh` installs core, then all of `plugins.txt` — `admin2`/`api`/`flex-objects` now install purely via `php bin/gpm install` (no zip-stash; that special-casing was removed in Task 4). The `gpm.releases: stable` channel arrives with the `user/` content clone. +3. **git-sync (remote-only, manual):** it is deliberately absent from `plugins.txt`. Install it on the server, add the encrypted token to `user/config/plugins/git-sync.yaml` (server-only, gitignored — a fresh clone never restores it), and apply the `folders:` array fix per `docs/working/git-sync-notes.md`. Leave it **disabled** (`make remote-git-sync-disable-prod`) until the first content round-trip is validated, then `make remote-git-sync-enable-prod`. +4. **Smoke test** the prod URL as in Task 5 Step 7 (home / trip / story / admin2 login / one `/post` / `/gpx-manager`). Note the Form filepond known-issue above will surface on `/post` until the separate rework lands — the entry still saves. +5. **Never** run `scripts/server-install.sh` against a populated server (it `rm -rf user; git clone`, destroying the server-only git-sync token). Fresh/empty servers only. diff --git a/docs/working/specs/2026-07-04-grav-2.0.4-upgrade-design.md b/docs/working/specs/2026-07-04-grav-2.0.4-upgrade-design.md new file mode 100644 index 0000000..065f464 --- /dev/null +++ b/docs/working/specs/2026-07-04-grav-2.0.4-upgrade-design.md @@ -0,0 +1,209 @@ +# Grav 2.0.4 Upgrade + GPM-Manage admin2/api/flex-objects — Design + +**Date:** 2026-07-04 +**Status:** Design approved — pending implementation plan + +## Goal + +Perform one coordinated upgrade of the intotheeast stack: + +1. Bump Grav core from `2.0.0-rc.10` → `2.0.4` (stable). +2. Promote `admin2`, `api`, and `flex-objects` from bundle-extracted plugins to + **GPM-managed** plugins (this is "option B"). + +These two changes are **not separable**. The stable plugins hard-require the +stable core, so GPM enforces an atomic upgrade of the whole chain. + +## Background / findings + +### Version gaps + +The project has been frozen on the final release candidate since the original +Grav 2.0 upgrade. Grav went stable on 2026-06-21; latest patch is `2.0.4` +(2026-06-29). No breaking changes exist within the 2.0.x line — the +2.0.1–2.0.4 releases are security hardening (XSS re-checks on editor Twig, +ZIP-bomb limits, `.htaccess` case-insensitive bypass fix) plus bugfixes. + +Installed vs. bundled-in-2.0.4 versions: + +| Plugin | Installed | 2.0.4 stable | +|---|---|---| +| core (grav) | 2.0.0-rc.10 | 2.0.4 | +| admin2 | 2.0.0-rc.15 | 2.0.9 | +| api | 1.0.0-rc.15 | 1.0.6 | +| flex-objects | 1.4.0-rc.7 | 1.4.3 | +| login | 3.8.9 | 3.8.11 | +| form | 9.1.6 | 9.1.8 | +| shortcode-core | 6.0.0 | 6.2.1 | + +### Dependency chain (why it's atomic) + +From the stable plugin blueprints: + +- `api` 1.0.6 requires `grav >= 2.0.4` **and** `login >= 3.8.11` +- `admin2` 2.0.9 requires `api >= 1.0.6` +- `flex-objects` 1.4.3 requires `form >= 6.0.0`, `api >= 1.0.0` + +So stable admin2/api cannot run on the rc.10 core — GPM would refuse. This is +the core reason option B is the right approach: `gpm` resolves and enforces the +entire chain automatically, which the previous manual-extract approach never +did. + +### Three plugin management categories + +The upgrade must account for the fact that plugins reached the servers three +different ways: + +| Category | Plugins | In `plugins.txt`? | How installed | Upgrade mechanism | +|---|---|---|---|---| +| GPM-managed | email, error, form, login, problems, add-page-by-form, shortcode-gallery-plusplus | yes | `gpm install` | `gpm update` | +| Manually-placed → GPM (option B) | admin2, api, flex-objects | **will add** | hand-extracted from grav-admin zip | `plugins.txt` for fresh installs; `gpm update` on existing test env | +| Remote-only | git-sync | **no** (config gitignored, holds encrypted token) | installed directly on the server | documented separately; carried by `gpm update`; **disabled during upgrade** | + +### git-sync compatibility + +git-sync is version 3.4.4 with an explicit `compatibility: 2.0` flag and is one +of Grav's own reference plugins for the Admin Next / API. It is safe to carry +through the upgrade. Note the documented folders-YAML quirk +(`docs/working/git-sync-notes.md`): its config must list `folders` as an array, +never the UI-written comma-string. + +### Local vs. server upgrade mechanisms differ + +- **Local** bakes the core into the Docker image (`Dockerfile`) → upgrade by + rebuilding the image. +- **Server** is a native webroot install → core upgrades via + `bin/grav upgrade`, plugins via `bin/gpm update`. + +The plan therefore has distinct local and remote steps. + +## Decisions + +- **Option B (GPM management)** for admin2/api/flex-objects. Add them to + `plugins.txt` so future fresh installs pull them via GPM. +- **Rollout order:** local → test → prod. +- **Prod is currently empty** → the prod phase is written as a *documented + fresh-install runbook only* and is **not executed** in this effort. Fresh prod + install uses the option-B-modified `server-install.sh` with + `GRAV_VERSION=2.0.4`. +- **Rollback = git.** All relevant data (`pages/`, `config/`, `accounts/`, + `themes/`) is committed. No separate backup step. Rollback is `git revert` of + this branch plus a rebuild/redeploy. +- **Upgrade verb on existing installs is `gpm update` (update-all)**, not + `gpm install `. `install` skips already-installed plugins and + never touches git-sync (which is not in the list); `update` upgrades every + installed plugin regardless of how it was placed, catching the manual and + remote-only categories in one shot. +- **git-sync stays out of `plugins.txt`** (that list is shared with local; git-sync + is remote-only with a manual encrypted token). Documented as separately + managed. +- **git-sync is disabled before the test upgrade and left disabled**, so the + upgrade cannot auto-commit reformatted/server-specific config back into the + shared Gitea `user` repo. The user validates first, then re-enables it as a + separate deliberate step. + +## File changes (Phase 0) + +| File | Change | +|---|---| +| `Dockerfile` | grav-admin zip URL `2.0.0-rc.10/grav-admin-v2.0.0-rc.10.zip` → `2.0.4/grav-admin-v2.0.4.zip`. Verified: the 2.0.4 zip still extracts to a `grav-admin/` folder, so the existing `cp` block is unchanged. | +| `plugins.txt` | add `api`, `admin2`, `flex-objects` (`form`, `login` already present as their deps) | +| `user/config/system.yaml` | **`gpm.releases: testing → stable`** — this is the authoritative GPM channel. `testing` is what has been serving RC/pre-release versions. Tracked in the `user` repo, so it applies to both local and server once pushed. | +| `docker-compose.yml` | `GRAV_CHANNEL=beta` → `production` for consistency only. This env drives the base image's `docker-entrypoint.sh`, **not** `bin/gpm`'s channel — `gpm.releases` above is what governs updates. | +| `scripts/server-install.sh` | remove the admin2/api stash+restore special-casing (lines 25–26 and 43–45); they now install via `gpm install` from `PLUGINS` | +| `Makefile` | **fix broken `remote-upgrade-grav`:** `php bin/grav upgrade` is not a real command — change to `php bin/gpm self-upgrade -y`. Add the new remote targets (below) to `REMOTE_TARGETS` so each gets `-test`/`-prod` variants. | +| `CLAUDE.md`, `docs/reference/architecture.md`, memory | update stack versions; document the three-category plugin model and the channel change | + +### New Makefile targets + +Added to the `REMOTE_TARGETS` list (Makefile:22–24) so the env-suffix macro +(Makefile:31–34) auto-generates `-test` / `-prod` variants: + +- `remote-update-plugins` → `cd $(WEBROOT) && php bin/gpm update -y` +- `remote-git-sync-disable` → set `enabled: false` in + `$(WEBROOT)/user/config/plugins/git-sync.yaml` (touch only the `enabled` key; + never rewrite `folders`) +- `remote-git-sync-enable` → set `enabled: true` in the same file + +`remote-upgrade-grav` exists but its command is **broken** (`php bin/grav +upgrade` is not a Grav CLI command) — it is fixed to `php bin/gpm self-upgrade +-y` as part of Phase 0. Core self-upgrade respects the `gpm.releases` channel. + +**Verified CLI command names** (against the running rc.10 container): +`php bin/gpm self-upgrade -y` (core), `php bin/gpm update -y` (all plugins), +`php bin/grav cache` (clear cache; aliases `clearcache`/`cache-clear`). + +> The exact idempotent shell used to toggle the `enabled` key is finalized in the +> implementation plan; it must not disturb the `folders` array or the encrypted +> token in `git-sync.yaml`. + +## Phases + +### Phase 0 — branch + edits +New branch off `main`. Apply all file changes above. + +### Phase 1 — local +1. Remove the stale manually-extracted `admin2`, `api`, `flex-objects` folders + from `user/plugins/` so GPM does a clean install. +2. `make build` (core → 2.0.4) +3. `make start` +4. Install the newly-listed plugins **and** update the already-installed ones + to their 2.0.4-compatible versions via GPM. Note: `gpm install` skips plugins + that are already present, so `login` (3.8.9 → ≥3.8.11, required by `api`) and + `form` need `gpm update`, not `install`. The exact `gpm update` + `gpm install` + sequencing (run inside the container via `docker exec`) is pinned in the plan. +5. Assert versions: admin2 2.0.9, api 1.0.6, flex-objects 1.4.3, login ≥ 3.8.11. +6. **Smoke test:** admin2 login; submit `/post` → entry appears in the active + trip's dailies; `/gpx-manager` list + upload + delete; a trip page and a + story render; maps load. + +Prerequisite: the Phase 0 config changes (esp. `system.yaml` +`gpm.releases: stable`) are committed and pushed to Gitea, or GPM on the server +will still resolve the `testing` channel and pull RCs. + +1. `make remote-git-sync-disable-test` +2. `make remote-fetch-content-test` — pull latest `user/` content to the test + server so `system.yaml` `gpm.releases: stable` is in place before any GPM + operation. +3. `make remote-upgrade-grav-test` (core self-upgrade → 2.0.4) +4. `make remote-update-plugins-test` (`gpm update -y` — all plugins incl. + admin2/api/flex/git-sync/login/form) +5. Clear cache on the server (`php bin/grav cache`). +6. Review `git status` in the server's `user/` for unexpected config diffs; + handle any deliberately (do not blind-commit). +7. Smoke test on the test URL (same checklist as Phase 1). +8. **Leave git-sync disabled and notify the user.** After the user validates, + re-enable as a separate deliberate step: `make remote-git-sync-enable-test`, + then a `content-push` round-trip to confirm sync still works. + +### Phase 3 — prod (DOCUMENTED, NOT EXECUTED) +Prod is empty, so this is a fresh install, not an upgrade. Documented as a +runbook: + +- Run the option-B-modified `server-install.sh` with `GRAV_VERSION=2.0.4` + (`make remote-install-prod`). +- admin2/api/flex-objects now install via GPM from `plugins.txt` — no manual + extraction. +- Set up git-sync manually afterward: install, add the encrypted token, apply + the folders-YAML array fix (`docs/working/git-sync-notes.md`). + +## Rollback + +`git revert` the branch (Dockerfile + plugins.txt + docker-compose + +server-install.sh + Makefile) and rebuild/redeploy. Content, config, and +accounts are already in git, so no data restore is needed. + +## Risks + +- **git-sync auto-commit during upgrade** — mitigated by disabling git-sync + before the test upgrade and reviewing `git status` before re-enabling. +- **admin2/api behavioral changes across RC→stable** — these back the `/post` + form and `/gpx-manager`; covered by the smoke tests, which are the + highest-weight validation in this effort. +- **GPM channel** — `gpm.releases` must be `stable` on the server *before* any + `gpm update`/`self-upgrade`, or GPM pulls RCs. Enforced by pushing the + `system.yaml` change and running `remote-fetch-content` first (Phase 2 step 2). +- **`bin/gpm self-upgrade` on shared hosting** — Grav 2.0.3 fixed self-upgrade + failures on shared-folder setups. On the native server this can still be + fragile; run `php bin/gpm preflight` first and use `-o/--overwrite` if a retry + is needed. diff --git a/plugins.txt b/plugins.txt index 127924c..842ddec 100644 --- a/plugins.txt +++ b/plugins.txt @@ -5,3 +5,6 @@ login problems add-page-by-form shortcode-gallery-plusplus +api +admin2 +flex-objects diff --git a/scripts/git-sync-toggle.sh b/scripts/git-sync-toggle.sh new file mode 100755 index 0000000..3f0268c --- /dev/null +++ b/scripts/git-sync-toggle.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +FILE="$1" +STATE="$2" +: "${FILE:?usage: git-sync-toggle.sh }" +: "${STATE:?usage: git-sync-toggle.sh }" + +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")" diff --git a/scripts/server-install.sh b/scripts/server-install.sh index a37a513..f4a15e2 100755 --- a/scripts/server-install.sh +++ b/scripts/server-install.sh @@ -22,8 +22,6 @@ cd "$WEBROOT" wget --no-verbose "https://github.com/getgrav/grav/releases/download/${GRAV_VERSION}/grav-admin-v${GRAV_VERSION}.zip" -O grav-admin.zip unzip -oq grav-admin.zip cp -rf grav-admin/. . -cp -rf grav-admin/user/plugins/admin2 /tmp/admin2-plugin -cp -rf grav-admin/user/plugins/api /tmp/api-plugin rm -rf grav-admin grav-admin.zip echo "==> Cloning user repo" @@ -40,9 +38,6 @@ fi echo "==> Creating required directories" mkdir -p user/plugins user/accounts user/data -cp -rf /tmp/admin2-plugin user/plugins/admin2 -cp -rf /tmp/api-plugin user/plugins/api -rm -rf /tmp/admin2-plugin /tmp/api-plugin echo "==> Installing plugins" php bin/gpm install $PLUGINS -y diff --git a/scripts/test-post.sh b/scripts/test-post.sh index cca1e58..ec5f67f 100755 --- a/scripts/test-post.sh +++ b/scripts/test-post.sh @@ -53,7 +53,10 @@ LOGIN_NONCE=$(echo "$LOGIN_HTML" | grep -o 'name="login-form-nonce" value="[^"]* LOGIN_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \ -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ -L \ - -d "username=${USER}&password=${PASS}&login-form-nonce=${LOGIN_NONCE}&task=login.login" \ + --data-urlencode "username=${USER}" \ + --data-urlencode "password=${PASS}" \ + --data-urlencode "login-form-nonce=${LOGIN_NONCE}" \ + --data-urlencode "task=login.login" \ "$BASE_URL/login") # After login, fetch /post and verify we see the post form (not the login form) diff --git a/tests/global-setup.js b/tests/global-setup.js index 0dfee8d..ec598c4 100644 --- a/tests/global-setup.js +++ b/tests/global-setup.js @@ -13,6 +13,14 @@ module.exports = async function globalSetup() { }); } + // Local test-account defaults (mirror the Makefile) so direct `npx playwright + // test` runs are self-contained without needing GRAV_TEST_* in .env. + if (!process.env.GRAV_TEST_USER) process.env.GRAV_TEST_USER = 'testrunner'; + if (!process.env.GRAV_TEST_PASS) process.env.GRAV_TEST_PASS = 'Testpass1234'; + + // Ensure the local test account exists (idempotent; never committed). + execSync('make test-account', { cwd: path.join(__dirname, '..'), stdio: 'inherit' }); + // Ensure demo content is loaded (italy-2026-demo trip + stories + GPX files) execSync('make demo-load', { cwd: path.join(__dirname, '..'), stdio: 'inherit' }); }; diff --git a/user b/user index 924cfc1..fff5358 160000 --- a/user +++ b/user @@ -1 +1 @@ -Subproject commit 924cfc18e21d050151e211a1dff877921150d1df +Subproject commit fff5358ce2ba29ac9f0619e9136acc8acfbc055c