Merge remote-tracking branch 'origin/main' into feat/journal-post-form
# Conflicts: # user
This commit is contained in:
@@ -171,15 +171,55 @@ Active settings in `user/config/system.yaml`:
|
||||
|
||||
With these settings, Grav rebuilds templates on every request. This is intentionally slower but means you never need to flush cache after editing a `.html.twig` file.
|
||||
|
||||
### Production mode (not yet configured)
|
||||
### Production mode (per-environment override)
|
||||
|
||||
Before going live, change in `user/config/system.yaml`:
|
||||
Production needs different Twig settings than dev, but **never change the
|
||||
committed `user/config/system.yaml`** — `twig.cache: false` (and `debug`/
|
||||
`auto_reload: true`) are the *intended dev values*, and committing prod values
|
||||
there breaks local development for everyone.
|
||||
|
||||
| Setting | Prod value | Why |
|
||||
|---|---|---|
|
||||
| `twig.cache` | `true` | Templates compiled once and reused; safe because theme files don't change at runtime |
|
||||
Instead, prod values are a **per-environment override** deployed to the server
|
||||
only, via Grav's per-environment config (`environment://config`, keyed on the
|
||||
request hostname):
|
||||
|
||||
**Pre-launch smoke test required:** with `twig.cache: true`, submit one post via `/post` and confirm the entry appears in the trip page feed at `/trips/italy-2026-demo` immediately. This verifies the cache-on-save plugin (BUG-001 fix) works correctly with caching enabled.
|
||||
| Setting | Dev (committed) | Prod (override) | Why prod differs |
|
||||
|---|---|---|---|
|
||||
| `twig.cache` | `false` | `true` | Compile templates once and reuse |
|
||||
| `twig.debug` | `true` | `false` | No debug functions in prod |
|
||||
| `twig.auto_reload` | `true` | `false` | Don't stat templates every request |
|
||||
|
||||
- **Source of truth:** `deploy/env/prod/system.yaml` (version-controlled).
|
||||
- **Deploy:** `make remote-apply-env-prod` — writes it to
|
||||
`<webroot>/user/env/<hostname>/config/system.yaml` and clears cache. It
|
||||
deep-merges over the committed `system.yaml`.
|
||||
- **Not synced by content:** `user/env/` is outside the content repo's tracked
|
||||
folders, so `content-push` / git-sync / `remote-fetch-content` do **not**
|
||||
restore it. **Re-run `make remote-apply-env-prod` after any fresh install.**
|
||||
- The hostname segment defaults to `REMOTE_HOST`; override with `WEB_HOST` in
|
||||
`.env.<env>` if Grav sees a different host than the SSH host.
|
||||
|
||||
> **⚠️ Once `user/env/<hostname>/` exists, Grav's Admin saves ALL config there.**
|
||||
> Creating the env override dir has a site-wide side effect: Grav's Admin panel
|
||||
> writes **every** config change (system *and* plugin) into the active
|
||||
> environment's config tree — e.g. editing a plugin on prod saves to
|
||||
> `user/env/intotheeast.com/config/plugins/<name>.yaml`, **not**
|
||||
> `user/config/plugins/<name>.yaml`. Consequences you must remember:
|
||||
> - Config edited via **Admin on the server is server-only**: `user/env/` is
|
||||
> outside the content repo's tracked folders, so it is **not committed** and
|
||||
> **not synced by git-sync** (which syncs only `pages`/`config`/`themes`).
|
||||
> Good for secrets — `git-sync.yaml` (token) safely lives at the env path —
|
||||
> but it means prod Admin config edits silently do **not** reach Gitea/local.
|
||||
> - When reading/writing server config, check **both** `user/config/...` and
|
||||
> `user/env/<host>/config/...` (env wins). Server tooling must search the env
|
||||
> path first — see `scripts/git-sync-toggle.sh` and `make remote-diag`.
|
||||
> - Repo-authored config (`user/config/...` via `make content-push`) still
|
||||
> applies everywhere; the env tree only holds per-host overrides + Admin-on-
|
||||
> server edits. Full details: `docs/working/git-sync-notes.md`.
|
||||
|
||||
**Pre-launch smoke test required:** with the prod override applied, submit one
|
||||
post via `/post` and confirm the entry appears in the trip page feed
|
||||
immediately. This verifies the cache-on-save plugin (BUG-001 fix) works
|
||||
correctly with caching enabled.
|
||||
|
||||
### What the cache-on-save plugin handles
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ REMOTE_PORT ?= 22
|
||||
SSH := ssh -p $(REMOTE_PORT) $(REMOTE_USER)@$(REMOTE_HOST)
|
||||
WEBROOT ?= $(REMOTE_HOME)/public_html
|
||||
SITE_CONFIG_DIR ?= $(REMOTE_HOME)/site-config
|
||||
# Hostname Grav uses to pick its per-environment config (user/env/<host>/).
|
||||
# Defaults to the SSH host; override in .env.<ENV> only if the web hostname
|
||||
# Grav sees differs from the SSH host (e.g. an addon domain on a shared box).
|
||||
WEB_HOST ?= $(REMOTE_HOST)
|
||||
|
||||
# ── Environment guard + generated per-env remote targets ──────────────────────
|
||||
# Every remote-* target below gains `-test` / `-prod` variants, e.g.
|
||||
@@ -22,7 +26,8 @@ SITE_CONFIG_DIR ?= $(REMOTE_HOME)/site-config
|
||||
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
|
||||
remote-content-status remote-clean remote-diag remote-apply-env \
|
||||
remote-gpm-install remote-maintenance-on remote-maintenance-off
|
||||
ENVS := test prod
|
||||
|
||||
guard-env:
|
||||
@@ -153,7 +158,7 @@ remote-fetch-content: guard-env
|
||||
$(SSH) "git -C $(WEBROOT)/user fetch origin main && git -C $(WEBROOT)/user sparse-checkout disable && git -C $(WEBROOT)/user reset --hard origin/main"
|
||||
|
||||
remote-install-plugins: guard-env
|
||||
$(SSH) "cd $(WEBROOT) && php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y"
|
||||
$(SSH) "cd $(WEBROOT) && php bin/gpm index -f && 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"
|
||||
@@ -162,10 +167,10 @@ remote-upgrade-grav: guard-env
|
||||
$(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
|
||||
$(SSH) "bash -s -- '$(WEBROOT)' 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
|
||||
$(SSH) "bash -s -- '$(WEBROOT)' true" < scripts/git-sync-toggle.sh
|
||||
|
||||
remote-content-status: guard-env
|
||||
$(SSH) "cd $(WEBROOT)/user && git status --short && echo '--- config diff ---' && git diff -- config/"
|
||||
@@ -173,6 +178,37 @@ remote-content-status: guard-env
|
||||
remote-clean: guard-env
|
||||
$(SSH) "cd $(WEBROOT) && php bin/grav clearcache"
|
||||
|
||||
# Install a single GPM package on the server (e.g. git-sync, which is
|
||||
# intentionally NOT in plugins.txt — it is remote-only).
|
||||
# Usage: make remote-gpm-install-prod PKG=git-sync
|
||||
remote-gpm-install: guard-env
|
||||
@test -n "$(PKG)" || { echo "ERROR: set PKG=<plugin-slug>"; exit 1; }
|
||||
$(SSH) "cd $(WEBROOT) && php bin/gpm index -f && php bin/gpm install $(PKG) -y && php bin/grav clearcache"
|
||||
|
||||
# Deploy per-environment Grav config overrides to the server's
|
||||
# user/env/<WEB_HOST>/config/ tree (deep-merged over the committed config).
|
||||
# Source of truth: deploy/env/$(ENV)/system.yaml (version-controlled). This
|
||||
# tree is outside the content repo, so it is NOT restored by content sync —
|
||||
# re-run after any fresh install.
|
||||
remote-apply-env: guard-env
|
||||
@test -f deploy/env/$(ENV)/system.yaml || { echo "ERROR: missing deploy/env/$(ENV)/system.yaml"; exit 1; }
|
||||
@host="$${WEB_HOST:-$(REMOTE_HOST)}"; \
|
||||
test -n "$$host" || { echo "ERROR: WEB_HOST/REMOTE_HOST unresolved"; exit 1; }; \
|
||||
$(SSH) "mkdir -p $(WEBROOT)/user/env/$$host/config && cat > $(WEBROOT)/user/env/$$host/config/system.yaml && cd $(WEBROOT) && php bin/grav clearcache" < deploy/env/$(ENV)/system.yaml; \
|
||||
echo "Applied deploy/env/$(ENV)/system.yaml -> $(WEBROOT)/user/env/$$host/config/system.yaml"
|
||||
|
||||
# Read-only health check: plugin install state, versions, key config, log tail.
|
||||
remote-diag: guard-env
|
||||
$(SSH) "cd $(WEBROOT) && \
|
||||
echo '=== Grav version ==='; php bin/grav --version 2>/dev/null; \
|
||||
echo '=== installed plugin versions ==='; for p in login admin2 flex-objects form api; do printf '%s: ' \"\$$p\"; grep -m1 '^version:' user/plugins/\$$p/blueprints.yaml 2>/dev/null || echo '(NOT installed)'; done; \
|
||||
echo '=== what does GPM say about api? ==='; php bin/gpm info api 2>&1 | head -12; \
|
||||
echo '=== api override (enabled/route/session) ==='; grep -nE '^enabled:|^route:|session_enabled:' user/config/plugins/api.yaml 2>&1; \
|
||||
echo '=== per-env override present? ==='; for f in user/env/*/config/system.yaml; do echo \"\$$f:\"; cat \"\$$f\" 2>/dev/null | grep -E 'cache:|debug:|auto_reload:'; done; \
|
||||
echo '=== twig cache populating? (non-empty => cache on) ==='; ls cache/twig/ 2>/dev/null | head -1 || echo '(empty)'; \
|
||||
echo '=== git-sync config (secrets redacted) ==='; grep -vaiE 'password|token|secret' user/config/plugins/git-sync.yaml user/env/*/config/plugins/git-sync.yaml 2>/dev/null; \
|
||||
echo '=== grav.log tail ==='; tail -8 logs/grav.log 2>/dev/null"
|
||||
|
||||
remote-maintenance-on: guard-env
|
||||
$(SSH) "bash -s on $(WEBROOT)" < scripts/server-maintenance.sh
|
||||
|
||||
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
# Production-only Grav config overrides.
|
||||
#
|
||||
# Deep-merged OVER the committed user/config/system.yaml via Grav's
|
||||
# per-environment config mechanism: on the server this file is deployed to
|
||||
# <webroot>/user/env/<hostname>/config/system.yaml
|
||||
# and Grav's `environment://config` stream (keyed on the request hostname)
|
||||
# layers it on top of `user://config`.
|
||||
#
|
||||
# These values are deliberately NOT in the committed system.yaml because they
|
||||
# would break local development (see CLAUDE.md §1 — dev keeps twig.cache:false
|
||||
# so theme edits take effect immediately). Prod is the only place they apply.
|
||||
#
|
||||
# Deploy with: make remote-apply-env-prod
|
||||
# The user/env/ tree is outside the content repo's tracked folders, so it is
|
||||
# NOT restored by content-push / git-sync / remote-fetch-content — re-run the
|
||||
# target above after any fresh install.
|
||||
twig:
|
||||
cache: true
|
||||
debug: false
|
||||
auto_reload: false
|
||||
|
||||
# Compression / connection handling.
|
||||
#
|
||||
# This host is not FastCGI (no fastcgi_finish_request()), so Grav's shutdown
|
||||
# "early connection close" falls back to emitting `Content-Encoding: identity`
|
||||
# to ask the webserver not to compress. But Apache's mod_deflate compresses
|
||||
# anyway and adds `Content-Encoding: gzip`, giving TWO conflicting headers —
|
||||
# the browser can't decode the body and renders raw gzip bytes (a garbage
|
||||
# page). Note: allow_webserver_gzip:true takes the SAME identity branch, so it
|
||||
# does not help. The real fix is to disable the early-close path, so Grav never
|
||||
# emits the bogus header and mod_deflate compresses cleanly (single header).
|
||||
debugger:
|
||||
shutdown:
|
||||
close_connection: false
|
||||
# Let the webserver own gzip; Grav does not compress or double-label.
|
||||
cache:
|
||||
gzip: false
|
||||
allow_webserver_gzip: false
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "Grav plugin config must live in the tracked user/config/plugins/ override, not the plugin folder"
|
||||
date: 2026-07-04
|
||||
category: docs/solutions/conventions
|
||||
module: "grav / plugin configuration"
|
||||
problem_type: convention
|
||||
component: tooling
|
||||
severity: high
|
||||
applies_when:
|
||||
- "Editing functional config for any GPM-managed Grav plugin"
|
||||
- "user/plugins/ is gitignored and only pages/config/accounts/themes are tracked"
|
||||
- "Preparing a fresh install or production cutover"
|
||||
- "A plugin behaves correctly locally but ships with only default config on deploy"
|
||||
related_components:
|
||||
- "grav"
|
||||
- "gpm"
|
||||
- "api plugin"
|
||||
- "content repo"
|
||||
- "deployment"
|
||||
tags:
|
||||
- grav
|
||||
- plugin-config
|
||||
- gpm
|
||||
- config-override
|
||||
- gitignore
|
||||
- deployment
|
||||
- api-plugin
|
||||
---
|
||||
|
||||
# Grav plugin config must live in the tracked user/config/plugins/ override, not the plugin folder
|
||||
|
||||
## Context
|
||||
|
||||
Grav resolves a plugin's config by deep-merging two layers: the plugin's own shipped file `user/plugins/<name>/<name>.yaml` (installed by GPM, part of the package) and the tracked override `user/config/plugins/<name>.yaml` (which wins). In this project the content repo tracks only `pages/`, `config/`, `accounts/`, `themes/`; `user/plugins/` and `user/data/` are gitignored (GPM manages plugin *code*). So any functional config a developer edits into a plugin's own `user/plugins/<name>/<name>.yaml` is invisible to version control.
|
||||
|
||||
It was this gap that left the `api` plugin unconfigured on the fresh prod install. Its `enabled`/`route`/`session_enabled`/cors/rate_limit config existed only in the untracked plugin folder locally, while the committed `user/config/plugins/api.yaml` held only a runtime `popularity.salt`. The local machine worked because the plugin folder had been hand-edited; every fresh environment got only the plugin's shipped defaults.
|
||||
|
||||
## Guidance
|
||||
|
||||
Put **functional** plugin configuration in the TRACKED override `user/config/plugins/<name>.yaml`. Grav deep-merges it over the plugin's shipped defaults, so it need only carry the keys that must differ (or the full config, for clarity). Keep **secrets and per-install generated values** OUT of the tracked file — JWT secrets, salts, encrypted tokens belong in gitignored `*-private.php` companion files (e.g. `api-private.php`, `security-private.php`) or should be regenerated per-install.
|
||||
|
||||
Never rely on edits to the plugin's own `user/plugins/<name>/<name>.yaml`: it is gitignored (won't deploy) and is overwritten on the next `php bin/gpm update`.
|
||||
|
||||
Concrete before/after, using the `api` plugin:
|
||||
|
||||
```yaml
|
||||
# WRONG: user/plugins/api/api.yaml (gitignored, GPM-managed, wiped on update)
|
||||
enabled: true
|
||||
route: /api
|
||||
auth:
|
||||
session_enabled: true
|
||||
```
|
||||
|
||||
```yaml
|
||||
# RIGHT: user/config/plugins/api.yaml (tracked, deploys, survives gpm update)
|
||||
enabled: true
|
||||
route: /api
|
||||
version_prefix: v1
|
||||
auth:
|
||||
session_enabled: true
|
||||
# JWT secret intentionally NOT here — it lives in the gitignored api-private.php
|
||||
```
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Reproducible deploys: a fresh clone or `make remote-install-<env>` must produce a working site from the repo alone. Config stranded in the gitignored plugin folder silently yields a plugin with only its shipped defaults on every new environment — which, for a plugin whose behavior depends on non-default config, means it's misconfigured or effectively off. On prod the `api` plugin's route/auth simply didn't work.
|
||||
|
||||
The failure is silent and per-environment: it works on the developer's machine (where the plugin folder was hand-edited) and breaks everywhere else. `gpm update` compounds it by wiping the folder edit even locally, so the "working" state is not just unshared — it is also unstable on the one machine that had it.
|
||||
|
||||
## When to Apply
|
||||
|
||||
- Any time you configure a Grav plugin whose non-default settings must work on a server (prod/test) or survive a plugin update.
|
||||
- Especially for plugins whose function depends on config: `api` (route/auth/cors), `admin2`, `flex-objects`, form/media settings, etc.
|
||||
- When auditing a fresh-install failure: check whether the "working" local config actually lives in a tracked path (`git ls-files user/config/plugins/<name>.yaml`) or was stranded in `user/plugins/<name>/`.
|
||||
|
||||
## Examples
|
||||
|
||||
- **api plugin (this project):** the functional config was moved from the untracked `user/plugins/api/api.yaml` into the tracked `user/config/plugins/api.yaml`, then `make content-push` + `make remote-fetch-content-<env>` deployed it. The JWT secret stayed in the gitignored `api-private.php`.
|
||||
- **Quick audit command:** `git -C user ls-files config/plugins/` shows exactly which plugin configs are tracked/deployable; anything you rely on that isn't listed is a latent fresh-install failure.
|
||||
|
||||
## Related
|
||||
|
||||
- `docs/solutions/integration-issues/stale-grav-version-blocks-api-plugin-install.md` — the config gap documented here was the *other* latent problem surfaced in that same investigation: the `api` plugin also had to be *installed* first before any config could take effect. The install gap (GPM version floor) and this config-tracking gap compounded each other on the fresh prod environment.
|
||||
- `docs/working/git-sync-notes.md` — the related third config location: on prod, Grav Admin saves config into the per-environment tree `user/env/<host>/config/`, which is *also* untracked. Same "config that doesn't reach the repo" family.
|
||||
- **CLAUDE.md §0 (plugin-management model):** only `pages/`, `config/`, `accounts/`, `themes/` are tracked in the `user/` repo; `plugins/` and `data/` are gitignored and GPM-managed. That tracking boundary is exactly why functional config must live under `config/plugins/`, not in the plugin's own folder.
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: "Grav garbage page from two conflicting Content-Encoding headers (identity + gzip)"
|
||||
date: 2026-07-04
|
||||
category: docs/solutions/integration-issues
|
||||
module: "grav / production deploy"
|
||||
problem_type: integration_issue
|
||||
component: tooling
|
||||
severity: high
|
||||
symptoms:
|
||||
- "Homepage and every dynamic Grav page render as full-screen binary/mojibake garbage in the browser"
|
||||
- "curl without Accept-Encoding returns clean HTML, so the page looks fine from a naive curl"
|
||||
- "curl -H \"Accept-Encoding: gzip\" (a browser-style request) returns raw gzip bytes"
|
||||
- "Response carries two conflicting headers: content-encoding: identity AND content-encoding: gzip"
|
||||
- "Only appeared after switching prod to production Twig mode (twig.debug: false)"
|
||||
root_cause: config_error
|
||||
resolution_type: config_change
|
||||
related_components:
|
||||
- "Apache mod_deflate"
|
||||
- "DirectAdmin shared host"
|
||||
- "Grav shutdown handler (system/src/Grav/Common/Grav.php)"
|
||||
- "deploy/env/prod/system.yaml"
|
||||
- "make remote-apply-env-prod"
|
||||
tags:
|
||||
- grav
|
||||
- content-encoding
|
||||
- gzip
|
||||
- mod-deflate
|
||||
- fastcgi-finish-request
|
||||
- apache
|
||||
- production-deploy
|
||||
- twig-debug
|
||||
---
|
||||
|
||||
# Grav garbage page from two conflicting Content-Encoding headers (identity + gzip)
|
||||
|
||||
## Problem
|
||||
|
||||
Grav renders every dynamic page as binary garbage in the browser because it emits two conflicting `Content-Encoding` headers. The response body is valid gzip, but because the server advertises both `content-encoding: identity` and `content-encoding: gzip`, the browser cannot decide how (or whether) to inflate it, and paints the raw compressed bytes to screen. Static assets are unaffected — only Grav's own dynamically generated pages are broken. The problem surfaced only after switching the site to production Twig mode (`twig.debug: false`).
|
||||
|
||||
## Symptoms
|
||||
|
||||
- The homepage and all dynamic Grav pages show a full screen of binary/mojibake characters in the browser (completely unreadable). The surrounding HTML shell and static assets are fine.
|
||||
- A naive `curl https://site/` (with **no** `Accept-Encoding` header) returns clean, correct HTML — so a quick curl sanity check looks perfectly healthy and completely hides the bug.
|
||||
- A browser-style request exposes it. `curl -H "Accept-Encoding: gzip" -D - -o /dev/null https://site/` shows **two** `Content-Encoding` response headers:
|
||||
```
|
||||
content-encoding: identity
|
||||
content-encoding: gzip
|
||||
```
|
||||
The body is valid gzip and `gunzip`s to the correct HTML.
|
||||
- A static asset served by the webserver alone (e.g. a CSS/JS file) shows a **single** clean `content-encoding: gzip` under the same request — confirming the webserver's gzip is fine and the duplication is Grav-originated.
|
||||
- The bug only appeared after switching the site to production Twig mode (`twig.debug: false`), which activates Grav's full shutdown/output path.
|
||||
|
||||
## What Didn't Work
|
||||
|
||||
- **First fix attempt: `cache.gzip: false` + `allow_webserver_gzip: true`.** This was the key dead end. It had **no effect** — the duplicated headers were unchanged. Reading Grav's source explained why: the branch that emits the bogus header fires on `if ($config->get('system.cache.gzip') || $config->get('system.cache.allow_webserver_gzip'))`. Setting `allow_webserver_gzip: true` satisfies the **same** `||` condition, so Grav still takes the identical `header('Content-Encoding: identity')` code path. The two knobs that *look* like they control this are both on the wrong side of the problem.
|
||||
- **Verifying with a naive `curl` (no `Accept-Encoding: gzip`).** This hid the problem entirely, because the server only compresses when the client advertises gzip support. Any healthcheck that omits `Accept-Encoding: gzip` reports a false "all clear." Reproduce it the way a browser does — send `Accept-Encoding: gzip`, or take a real headless-browser (Playwright) screenshot.
|
||||
|
||||
## Solution
|
||||
|
||||
Root the fix in Grav's shutdown handler, `system/src/Grav/Common/Grav.php` (~lines 615–631):
|
||||
|
||||
```php
|
||||
if ($config->get('system.debugger.shutdown.close_connection', true)) {
|
||||
$success = function_exists('fastcgi_finish_request') ? @fastcgi_finish_request() : false;
|
||||
if (!$success) {
|
||||
if (!ini_get('zlib.output_compression')) {
|
||||
if ($config->get('system.cache.gzip') || $config->get('system.cache.allow_webserver_gzip')) {
|
||||
header('Content-Encoding: identity'); // <-- the bogus header
|
||||
} elseif (function_exists('apache_setenv')) {
|
||||
@apache_setenv('no-gzip', '1');
|
||||
} else {
|
||||
header('Content-Encoding: none');
|
||||
}
|
||||
header('Content-Length: ' . ob_get_length());
|
||||
}
|
||||
header('Connection: close');
|
||||
ob_end_flush();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The entire problematic block is gated by `system.debugger.shutdown.close_connection` (default `true`). Disable it so the whole branch is skipped and Grav never touches `Content-Encoding` at all.
|
||||
|
||||
In this project it is applied as a **per-environment (prod-only) override** so local dev is untouched — `deploy/env/prod/system.yaml`, deployed via `make remote-apply-env-prod`:
|
||||
|
||||
```yaml
|
||||
debugger:
|
||||
shutdown:
|
||||
close_connection: false
|
||||
```
|
||||
|
||||
Verify — the response must show **exactly one** `content-encoding`:
|
||||
|
||||
```bash
|
||||
curl -s -D - -o /dev/null -H "Accept-Encoding: gzip" https://site/ | grep -i content-encoding
|
||||
# content-encoding: gzip
|
||||
```
|
||||
|
||||
Then take a screenshot of the rendered page to confirm it displays correctly. A `curl`-without-gzip check is **not** sufficient proof — it would have passed even while the bug was live.
|
||||
|
||||
## Why This Works
|
||||
|
||||
`debugger.shutdown.close_connection` (default `true`) makes Grav flush the full response and close the connection to the browser **early**, so slow shutdown tasks (logging, debugger teardown) don't keep the visitor waiting. On a FastCGI/PHP-FPM host, Grav does this cleanly via `fastcgi_finish_request()` and never manipulates headers — which is why the bug is invisible on most stacks.
|
||||
|
||||
On a **non-FastCGI** host (LiteSpeed, suPHP, plain CGI), `fastcgi_finish_request()` does not exist, so `$success` is `false` and Grav falls back to closing the connection *manually*. To do that it must set an explicit `Content-Length`, and to keep that length honest it tries to tell the webserver "do not compress this body" — which, on the `cache.gzip`/`allow_webserver_gzip` branch, it expresses as `header('Content-Encoding: identity')`.
|
||||
|
||||
But `identity` is not a real content transformation and is **not** a recognized "suppress compression" signal to Apache `mod_deflate`. `mod_deflate` ignores it, compresses the body anyway, and appends its **own** `Content-Encoding: gzip`. The response now carries two contradictory `Content-Encoding` headers (`identity` and `gzip`). Browsers cannot reconcile the contradiction, fail to inflate the gzip stream, and render the raw compressed bytes — the "binary garbage" screen.
|
||||
|
||||
Setting `close_connection: false` means Grav never enters the manual connection-close path, never emits `Content-Encoding: identity`, and leaves the webserver as the **sole** authority on compression. The webserver then sends a single, correct `Content-Encoding: gzip`, and the browser inflates and renders normally.
|
||||
|
||||
## Prevention
|
||||
|
||||
- On **non-FastCGI PHP hosts with server-side gzip** (Apache `mod_deflate`, LiteSpeed), set `debugger.shutdown.close_connection: false` for that environment. Deliver it as a **per-environment override**, never by editing the committed `system.yaml` (which would silently change dev behavior too).
|
||||
- **Reproduce compression bugs the way a browser sees them.** Always test with `curl -H "Accept-Encoding: gzip" -D -` and/or a headless-browser screenshot. A plain `curl` negotiates no compression and silently masks encoding bugs.
|
||||
- **Health check:** dynamic pages must return **exactly one** `Content-Encoding` header. Two of them (`identity` + `gzip`) is the unambiguous signature of this bug. Add this assertion to any smoke test.
|
||||
- **Know the trigger.** This can stay completely hidden in development mode and only appear once a site is switched to production mode (`twig.debug: false`), which activates Grav's full shutdown/output path. Re-run the browser-style compression check as part of any production cutover.
|
||||
|
||||
## Related
|
||||
|
||||
- **Grav per-environment override mechanism** — `deploy/env/prod/system.yaml` applied via `make remote-apply-env-prod`, described in `CLAUDE.md` §1 ("Production mode — per-environment override"). The pattern that lets a prod-only setting like `debugger.shutdown.close_connection: false` ship without mutating the committed dev `system.yaml`.
|
||||
- **`docs/working/git-sync-notes.md`** — documents the `user/env/<hostname>/config/` override tree (where this fix physically lives on the server) and the caveat that config saved via Admin on the server stays server-only.
|
||||
- **`docs/solutions/integration-issues/stale-grav-version-blocks-api-plugin-install.md`** — sibling from the same cutover: Admin2 login failed because a stale `GRAV_VERSION` installed an rc core and GPM wouldn't serve the `api` plugin. Different root cause, same deploy.
|
||||
- **`docs/solutions/test-failures/new-user-grants-api-not-admin-on-admin2.md`** — a sibling gotcha from the same 2026-07-04 Grav 2.0.4 production cutover (account permission provisioning); different root cause, same deploy.
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: "Admin2 login fails silently because a stale GRAV_VERSION installs an rc core below the api plugin's version floor"
|
||||
date: 2026-07-04
|
||||
category: docs/solutions/integration-issues
|
||||
module: "grav / production deploy / plugin install"
|
||||
problem_type: integration_issue
|
||||
component: authentication
|
||||
severity: high
|
||||
symptoms:
|
||||
- "Admin2 login at /admin silently fails: button disables then re-enables, no visible error, nothing written to grav.log"
|
||||
- "admin2 SPA background login POST to /api/... returns 404"
|
||||
- "/api/v1/pages returns 404 on prod but 401 locally (api plugin route not registered)"
|
||||
- "user/plugins/api directory does not exist on prod (plugin never installed)"
|
||||
- "gpm install api reports 'These packages were not found on Grav: api' even after gpm index -f"
|
||||
root_cause: config_error
|
||||
resolution_type: environment_setup
|
||||
related_components:
|
||||
- "gpm"
|
||||
- "admin2 plugin"
|
||||
- "api plugin"
|
||||
- "scripts/server-install.sh"
|
||||
- "Makefile remote targets"
|
||||
- ".env.prod"
|
||||
tags:
|
||||
- grav
|
||||
- gpm
|
||||
- admin2
|
||||
- api-plugin
|
||||
- plugin-dependency
|
||||
- version-compatibility
|
||||
- production-deploy
|
||||
- env-config
|
||||
---
|
||||
|
||||
# Admin2 login fails silently because a stale GRAV_VERSION installs an rc core below the api plugin's version floor
|
||||
|
||||
## Problem
|
||||
|
||||
On a fresh Grav production install, Admin2 login fails silently because the `api` plugin — which Admin2 authenticates through — never installed. GPM refused to serve it: a stale `GRAV_VERSION` in `.env.prod` had installed Grav `2.0.0-rc.10`, and the `api` plugin requires Grav core `>=2.0.4`. GPM filters offered packages by the installed core version, so on an rc.10 core the `api` plugin was excluded from results entirely and reported as "not found." Admin2 was present (and depends on `api`), but its login POST hit an `/api/...` route that was never registered, so authentication silently 404'd before it ever reached Grav's auth layer.
|
||||
|
||||
## Symptoms
|
||||
|
||||
- Admin login at `/admin` silently fails: the login button disables briefly, re-enables, and shows no error. **Nothing appears in `logs/grav.log`** — a wrong password *would* log a failed-attempt warning, so its absence means auth was never reached.
|
||||
- The Admin2 SPA's background login request (to an `/api/...` endpoint) returns **HTTP 404** with `content-type: application/json`.
|
||||
- `GET /api/v1/pages` returns **404** on prod, but **401 Unauthorized** on the working local install — i.e. the api route isn't registered on prod at all.
|
||||
- `ls user/plugins/api` on the server: **No such file or directory** — the plugin was never installed, even though `admin2` (which depends on it) was.
|
||||
- `php bin/gpm install ... api -y` → `"These packages were not found on Grav: api"`, even after `php bin/gpm index -f`.
|
||||
|
||||
## What Didn't Work
|
||||
|
||||
- **Committing/deploying the api plugin config** (`enabled` / `route` / `session_enabled`, moved from the untracked `user/plugins/api/api.yaml` into the tracked `user/config/plugins/api.yaml`). This was a real, necessary fix for a *different* latent problem, but it did not fix login: you cannot configure a plugin that isn't installed. Still 404.
|
||||
- **Forcing a GPM index refresh** (`php bin/gpm index -f`). No effect. "Package not found" here is not a stale-index problem — GPM filters the packages it offers by the installed Grav **core** version, and rc.10 is below the api plugin's `>=2.0.4` requirement, so `api` is excluded from results entirely.
|
||||
- **Assuming "same channel = same availability."** Local (Grav 2.0.4, `stable` channel) found `api` via `gpm info api`; prod (also `stable`) reported it "not found." The channel was identical — the difference was the Grav **core** version, which silently filtered the plugin out.
|
||||
|
||||
## Solution
|
||||
|
||||
The real cause is that prod was running the wrong Grav core. `scripts/server-install.sh` downloads `grav-admin-v${GRAV_VERSION}.zip`, and `.env.prod` still carried the stale pre-upgrade `GRAV_VERSION=2.0.0-rc.10`.
|
||||
|
||||
1. Upgrade the Grav core in place to stable (rc.10 → 2.0.7):
|
||||
```bash
|
||||
make remote-upgrade-grav-prod # php bin/gpm self-upgrade -y && php bin/grav cache
|
||||
```
|
||||
2. Install the plugins now that a compatible core is present (the api plugin resolves):
|
||||
```bash
|
||||
make remote-install-plugins-prod # php bin/gpm index -f && php bin/gpm install <plugins.txt> -y
|
||||
# => "Preparing to install API [v1.0.8] ... Success!"
|
||||
```
|
||||
3. Clear cache, then verify the api route is live and login works:
|
||||
```bash
|
||||
make remote-clean-prod
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://site/api/v1/pages
|
||||
# 401 (was 404) => plugin installed + routed
|
||||
```
|
||||
4. **Prevent recurrence:** update `.env.prod` to `GRAV_VERSION=2.0.4` so a future *fresh* install doesn't reinstall rc.10 (self-upgrade fixed the running server, not the env file). Keep the api plugin's functional config in the tracked `user/config/plugins/api.yaml` so it deploys on a clean clone.
|
||||
|
||||
## Why This Works
|
||||
|
||||
GPM (Grav Package Manager) only offers a plugin version whose declared Grav requirement is satisfied by the **installed core**. The `api` plugin requires Grav `>=2.0.4`; on a `2.0.0-rc.10` core there is no compatible version, so GPM reports the package as "not found" rather than a version conflict. Admin2 declares `api` as a hard dependency and performs all authentication over the api plugin's `/api/v1` JWT endpoints, so with `api` absent the login POST hits a route that doesn't exist (404) and never reaches Grav's auth layer — hence the silent failure with no `grav.log` entry. Upgrading the core to a stable `>=2.0.4` build makes GPM offer `api` again; installing it registers `/api/v1`, and Admin2's login flow succeeds.
|
||||
|
||||
## Prevention
|
||||
|
||||
- **Keep `.env.<env>` `GRAV_VERSION` current.** It is the version a *fresh* `make remote-install-<env>` bakes in; a stale value silently installs an old core. After any core upgrade, bump the env file too — self-upgrade only moves the running server. Note this is a *third* version-authority surface alongside `user/config/system.yaml` `gpm.releases` (channel) and `plugins.txt` — they must stay in sync.
|
||||
- **When GPM says "package not found" for a package you know is on your channel, check the target's Grav core version first** (`php bin/grav --version` on the server, or `make remote-diag-<env>`). GPM filters by core compatibility; "not found" often means "no version compatible with your core," not "missing from the index." `gpm index -f` will not help.
|
||||
- **Don't trust a top-level install "Success" to mean dependencies installed.** A fresh install can leave a plugin's declared dependency unsatisfied (here `admin2` installed but its `api` dependency didn't). Verify with `ls user/plugins/<dependency>`.
|
||||
- **Know the Admin2 ⇄ api coupling.** Admin2 authenticates via the api plugin's `/api/v1` endpoints; a missing or unrouted api plugin makes admin login fail *silently* (login POST 404s, nothing logged). A quick `curl /api/v1/pages` expecting `401` (not `404`) is a good post-deploy smoke check.
|
||||
|
||||
## Related
|
||||
|
||||
This is one of three independent gotchas from the same **2026-07-04 Grav 2.0.4 production cutover**:
|
||||
|
||||
- `docs/solutions/integration-issues/grav-double-content-encoding-garbage-page.md` — sibling: garbage-rendered pages from a double `Content-Encoding` header on a non-FastCGI host. Different root cause (HTTP compression), same deploy.
|
||||
- `docs/solutions/test-failures/new-user-grants-api-not-admin-on-admin2.md` — sibling: an authenticated account is denied an admin-gated page because `login new-user` auto-detect granted `api.*` but not `admin.*`. Different root cause (permission provisioning), same admin2/api area.
|
||||
- `docs/working/plans/2026-07-04-grav-2.0.4-upgrade.md` — the upgrade plan whose Global Constraints spell out the GPM version floors (`grav >=2.0.4`, `api >=1.0.6`) that cause the "package not found" on an rc core.
|
||||
- `docs/solutions/conventions/grav-plugin-config-must-be-tracked-override.md` — the *other* latent problem from this same investigation: the api plugin's functional config (`enabled` / `route` / `session_enabled`) must live in the tracked `user/config/plugins/api.yaml` to deploy at all. Necessary but not sufficient here (the plugin must be installed first), but a durable convention in its own right.
|
||||
@@ -78,5 +78,6 @@ This site runs **Admin2 only** (the classic `admin` plugin is disabled), so auto
|
||||
|
||||
## 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.
|
||||
- Sibling gotchas from the same 2026-07-04 Grav 2.0.4 production cutover (all surface around admin2/api but with distinct root causes): `docs/solutions/integration-issues/stale-grav-version-blocks-api-plugin-install.md` (stale `GRAV_VERSION` → rc core → GPM won't serve the `api` plugin → login 404s) and `docs/solutions/integration-issues/grav-double-content-encoding-garbage-page.md` (double `Content-Encoding` header → garbage page).
|
||||
- `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.
|
||||
|
||||
@@ -6,11 +6,12 @@ Ideas and improvements not yet planned or scheduled.
|
||||
|
||||
## Production — remaining items
|
||||
|
||||
- [ ] Set `twig.cache: true` in `user/config/system.yaml` on the server (do not commit — breaks local dev)
|
||||
- [x] Prod Twig prod-mode (`cache: true`, `debug/auto_reload: false`) — applied as a per-environment override via `make remote-apply-env-prod` (source: `deploy/env/prod/system.yaml`); committed `system.yaml` stays dev
|
||||
- [ ] Smoke test: submit one post via `/post`, confirm entry appears in dailies immediately (verifies cache-on-save with twig cache on)
|
||||
- [ ] Confirm `/post` requires login — unauthenticated visitors must not be able to post
|
||||
- [x] Confirm `/post` requires login — verified on prod (returns the login gate to unauthenticated visitors)
|
||||
- [ ] Register at carto.com and review terms for production traffic
|
||||
- [ ] Japan & Korea 2026 trip page: set `date_start`, add `cover_image`, upload GPX route file(s)
|
||||
- [ ] Update `GRAV_VERSION` in `.env.prod` to `2.0.4` (was stale `2.0.0-rc.10`; fixed on the running server via self-upgrade, but a future fresh install would repeat the RC)
|
||||
- [ ] git-sync on prod: install, add encrypted token, apply `folders:` fix, enable after first content round-trip
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
# Git Sync Plugin — Setup Notes
|
||||
|
||||
## Folders YAML bug
|
||||
## ⚠️ Config lives in the ENVIRONMENT tree, not `user/config/` (IMPORTANT)
|
||||
|
||||
The plugin UI always saves the folders field as a single comma-string:
|
||||
Prod has a per-environment override directory `user/env/<hostname>/config/`
|
||||
(created for Twig prod-mode — see CLAUDE.md §1). **A crucial Grav side effect:
|
||||
once that env dir exists, the Admin panel saves ALL config changes — system and
|
||||
plugin — into the active environment's config tree**, not `user/config/`.
|
||||
|
||||
```yaml
|
||||
folders:
|
||||
- 'pages,config,themes'
|
||||
So on prod, `git-sync.yaml` (configured via Admin) lives at:
|
||||
|
||||
```
|
||||
user/env/intotheeast.com/config/plugins/git-sync.yaml ← here (env tree)
|
||||
user/config/plugins/git-sync.yaml ← NOT here
|
||||
```
|
||||
|
||||
But the plugin code iterates the array expecting separate items. This causes `git status pages,config,themes` to be passed as a single path, so git sees nothing to commit and sync silently does nothing.
|
||||
Why this matters:
|
||||
|
||||
**Fix:** Edit `user/config/plugins/git-sync.yaml` directly:
|
||||
- **Server-only, not synced, not committed.** `user/env/` is outside the
|
||||
content repo's tracked folders (`pages`/`config`/`accounts`/`themes`) and is
|
||||
not one of git-sync's synced folders (`pages`/`config`/`themes`). So config
|
||||
saved via Admin *on the server* never reaches Gitea or local. This is ideal
|
||||
for the git-sync token (it stays server-only) but means **prod Admin config
|
||||
edits silently diverge** — author durable config in the repo, not prod Admin.
|
||||
- **Look in both places.** When inspecting/toggling server config, check
|
||||
`user/config/plugins/<name>.yaml` **and**
|
||||
`user/env/<host>/config/plugins/<name>.yaml` (env wins).
|
||||
- **Tooling is env-path-aware.** `scripts/git-sync-toggle.sh` takes a `WEBROOT`
|
||||
and searches `user/env/*/config/plugins/git-sync.yaml` first, then
|
||||
`user/config/plugins/git-sync.yaml`. `make remote-git-sync-disable/enable-<env>`
|
||||
and `make remote-diag-<env>` use it.
|
||||
|
||||
## Folders format
|
||||
|
||||
Older plugin versions' UI saved the `folders` field as a single comma-string
|
||||
(`- 'pages,config,themes'`), which the plugin iterated as one path, so sync
|
||||
silently did nothing. **git-sync v3.4.4 (installed on prod 2026-07-04) saves it
|
||||
correctly** as separate list items:
|
||||
|
||||
```yaml
|
||||
folders:
|
||||
@@ -20,8 +44,33 @@ folders:
|
||||
- themes
|
||||
```
|
||||
|
||||
Never use the Admin UI to change folders — it will rewrite the broken format.
|
||||
If you see the comma-string form on an older version, fix it by editing
|
||||
`git-sync.yaml` directly (at whichever path it lives — see above); do not
|
||||
re-save folders via the Admin UI on the buggy version.
|
||||
|
||||
## Files to gitignore
|
||||
## Per-install / secret files — must be gitignored (never synced)
|
||||
|
||||
`user/config/plugins/git-sync.yaml` contains an encrypted token and is server-specific. `user/config/security.yaml` contains Grav nonces/salts, also server-specific. Both are in `.gitignore` and must never be committed.
|
||||
git-sync syncs the `config/` folder, so any per-install or secret file tracked
|
||||
there would get pushed to Gitea and pollute every environment. Keep these out
|
||||
of the content repo (all in `user/.gitignore`):
|
||||
|
||||
| File | Why |
|
||||
|---|---|
|
||||
| `config/plugins/git-sync.yaml` | encrypted token; server-specific (also lives at env path on prod) |
|
||||
| `config/plugins/api-private.php` | API JWT secret |
|
||||
| `config/security.yaml` | Grav nonces/salts (legacy location) |
|
||||
| `config/versions.yaml` | per-install Grav schema-migration state — differs per env (dev 2.0.4, prod 2.0.7); Grav regenerates it. Untracked 2026-07-04. |
|
||||
| `config/security-private.php` | **TODO:** committed salt secret; should be gitignored like `api-private.php` (deferred — untracking resets server sessions) |
|
||||
|
||||
## git-sync config summary (prod, 2026-07-04)
|
||||
|
||||
- `repository: https://git.gorinskat.nl/m038/intotheeast-com-content.git`,
|
||||
`branch: main`, HTTPS + token auth (SSH is Tailscale-only).
|
||||
- `sync.direction: both`, `on_save/on_delete/on_media: true` → prod Admin edits
|
||||
and `/post` push to Gitea; content-repo pushes pull to prod **via webhook**
|
||||
(`/_git-sync`). The webhook is configured in Gitea repo settings (same secret
|
||||
as the test instance).
|
||||
- **Before enabling on a fresh server**, reset the synced folders clean
|
||||
(`make remote-fetch-content-<env>`) so no install-time drift (e.g. a stale
|
||||
`versions.yaml`) gets pushed on the first sync. Toggle with
|
||||
`make remote-git-sync-disable/enable-<env>`.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **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.
|
||||
**Status:** ✅ Complete — Phase 1 (local), Phase 2 (test env), and **Phase 3 (production) all executed and shipped**. Phase 3 was run for real on 2026-07-05 (see the Phase 3 section for the execution outcome and the three `docs/solutions/` gotchas it produced). git-sync re-enabled on test and set up on prod. 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).
|
||||
|
||||
@@ -539,11 +539,32 @@ Content, config, and accounts are in git, so no data restore is required — but
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Production (fresh install, NOT executed)
|
||||
## Phase 3 — Production (fresh install) — EXECUTED 2026-07-05
|
||||
|
||||
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.
|
||||
> **Execution outcome (2026-07-05):** the fresh prod install was run for real
|
||||
> (`make remote-install-prod`) and the site is live at `https://intotheeast.com`.
|
||||
> The runbook below was followed, but three non-obvious gotchas surfaced — each
|
||||
> now has its own learning in `docs/solutions/`:
|
||||
> - **Stale `.env.prod GRAV_VERSION`** installed Grav rc.10, so GPM wouldn't
|
||||
> serve the `api` plugin (needs ≥2.0.4) → Admin2 login 404'd silently. Fixed
|
||||
> via `make remote-upgrade-grav-prod` (→ 2.0.7) + reinstall. See
|
||||
> `docs/solutions/integration-issues/stale-grav-version-blocks-api-plugin-install.md`.
|
||||
> **TODO: bump `.env.prod GRAV_VERSION` to `2.0.4`** so a future fresh install
|
||||
> doesn't repeat the RC.
|
||||
> - **Double `Content-Encoding` header** (non-FastCGI host + mod_deflate)
|
||||
> rendered a garbage page once prod switched to `twig.debug: false`. Fixed via
|
||||
> `debugger.shutdown.close_connection: false` in the prod env override. See
|
||||
> `docs/solutions/integration-issues/grav-double-content-encoding-garbage-page.md`.
|
||||
> - **Plugin config stranded in the untracked `user/plugins/`** doesn't deploy.
|
||||
> See `docs/solutions/conventions/grav-plugin-config-must-be-tracked-override.md`.
|
||||
>
|
||||
> Twig prod-mode is applied as a per-environment override (`deploy/env/prod/system.yaml`
|
||||
> via `make remote-apply-env-prod`); git-sync is installed, configured, and enabled
|
||||
> (see `docs/working/git-sync-notes.md`). Remaining minor follow-ups: gitignore
|
||||
> `config/security-private.php` (committed salt); optional `popularity.salt` strip.
|
||||
|
||||
When prod is provisioned:
|
||||
The original runbook (production was empty, so this was a **fresh install**, not
|
||||
an upgrade):
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
FILE="$1"
|
||||
# Enable/disable the git-sync plugin by flipping `enabled:` in its config.
|
||||
#
|
||||
# git-sync.yaml may live in the per-environment config tree
|
||||
# (user/env/<host>/config/plugins/) when an env override dir exists — Grav's
|
||||
# Admin saves config there when an environment is active — otherwise in the
|
||||
# standard user/config/plugins/. Search both, env path first.
|
||||
WEBROOT="$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>}"
|
||||
: "${WEBROOT:?usage: git-sync-toggle.sh <webroot> <true|false>}"
|
||||
: "${STATE:?usage: git-sync-toggle.sh <webroot> <true|false>}"
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "ERROR: $FILE not found — is git-sync installed on this server?" >&2
|
||||
FILE=$(ls "$WEBROOT"/user/env/*/config/plugins/git-sync.yaml \
|
||||
"$WEBROOT"/user/config/plugins/git-sync.yaml 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
|
||||
echo "ERROR: git-sync.yaml not found under $WEBROOT — is git-sync installed/configured?" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -17,4 +26,4 @@ else
|
||||
printf 'enabled: %s\n' "$STATE" | cat - "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
|
||||
fi
|
||||
|
||||
echo "git-sync now: $(grep -E '^enabled:' "$FILE")"
|
||||
echo "git-sync now: $(grep -E '^enabled:' "$FILE") ($FILE)"
|
||||
|
||||
+1
-1
Submodule user updated: fff5358ce2...16a570ca3b
Reference in New Issue
Block a user