docs(16-04): complete staging deploy plan

This commit is contained in:
2026-10-09 19:56:16 +02:00
parent 4ef6776382
commit b7649581da
4 changed files with 340 additions and 10 deletions

View File

@@ -959,7 +959,7 @@ Plans:
3. Public pages that PHP Journal + the current theme rendered have a named successor on Go; sitemap is in if the site still needs it.
4. The new code has unit tests, delivered in the phase's last plan.
**Plans:** 3/5 plans executed
**Plans:** 4/5 plans executed
Plans:
**Wave 1**
@@ -972,7 +972,7 @@ Plans:
- [x] 16-03-PLAN.md — sm-grzybyfunkcjonalne-app: /gfadmin, app plugin with featured-image and category-content commands, migration runbook, two-process deploy files, urls.txt and local rehearsal
**Wave 4** *(blocked on Wave 3 completion)*
- [ ] 16-04-PLAN.md — Staging deploy on the PHP site's server (human checkpoint) and smoke over every old URL
- [x] 16-04-PLAN.md — Staging deploy on the PHP site's server (human checkpoint) and smoke over every old URL
**Wave 5** *(blocked on Wave 4 completion)*
- [ ] 16-05-PLAN.md — Unit tests: layer and app node --test (incl. SSR), Go command and boot tests, phase gate
@@ -1125,7 +1125,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 14.2. Local Fonoteka frontend on local SummerCMS backend (INSERTED) | 0/TBD | Not started | - |
| 14.3. One-command app scaffolding (INSERTED) | 4/4 | In Progress| |
| 15. Journal plugin | 4/4 | Complete | 2026-10-06 |
| 16. grzybyfunkcjonalne.pl on reusable blog views | 3/5 | In Progress| |
| 16. grzybyfunkcjonalne.pl on reusable blog views | 4/5 | In Progress| |
| 17. Blog companions | 0/TBD | Not started | - |
| 18. Payment Gateway | 0/TBD | Not started | - |
| 18.1. Payment Gateway Subscriptions | 0/TBD | Not started | - |

View File

@@ -5,15 +5,15 @@ current_phase: 16
current_phase_name: grzybyfunkcjonalne.pl on reusable blog views
status: executing
stopped_at: Phase 19 context gathered
last_updated: "2026-10-09T17:53:17.248Z"
last_updated: "2026-10-09T17:55:52.322Z"
last_activity: 2026-10-09
last_activity_desc: Phase 16 execution started
state_head: bf297d5244260b200941136c58aad6eb8a33a227
state_head: 4ef677638258a4877eedafbb6ce19ffab4992d3e
progress:
total_phases: 32
completed_phases: 13
total_plans: 148
completed_plans: 142
completed_plans: 143
gsd_state_version: "1.0"
milestone_name: milestone
---
@@ -30,9 +30,9 @@ See: .planning/PROJECT.md (updated 2026-09-16)
## Current Position
Phase: 16 (grzybyfunkcjonalne.pl on reusable blog views) — EXECUTING
Plan: 4 of 5
Plan: 5 of 5
Status: Ready to execute
Last activity: 2026-10-09 - Completed quick task 261009-mys: subtitle, avatar and My account page for backend users
Last activity: 2026-10-09 - Completed 16-04: staging deploy at uat.grzybyfunkcjonalne.pl, smoke ok (98 urls)
Progress: [██████████] 100%
@@ -197,6 +197,7 @@ Progress: [██████████] 100%
| Phase 16 P01 | 1h 34m | 4 tasks | 55 files |
| Phase 16 P02 | 16 min | 3 tasks | 44 files |
| Phase 16 P03 | 22 min | 3 tasks | 32 files |
| Phase 16 P04 | 40min | 3 tasks | 10 files |
## Accumulated Context
@@ -634,6 +635,9 @@ Recent decisions affecting current work:
- [Phase 16]: migrate.sh imports from a copy of the PHP export with empty translations [] arrays normalized to {}; the importer fix is deferred to sm-journal-plugin
- [Phase 16]: The URL rehearsal gives each URL its own X-Forwarded-For visitor (203.0.113.N) so ~90 SSR pages stay under Go's 120/min per-visitor journal limit
- [Phase 16]: The staging nginx image location declares its own types map and matches extensions case-insensitively so images keep an image type under nosniff
- [Phase 16]: 16-04: staging keeps the UAT basic-auth gate (off for loopback via nginx geo); smoke and curl checks run on the server
- [Phase 16]: 16-04: staging host is uat.grzybyfunkcjonalne.pl; first-journal-post skipped in the import so Go serves the 62 live posts
- [Phase 16]: 16-04: fileblob uploads bucket uses no_tmp_dir=true because /tmp on the server is tmpfs
### Pending Todos
@@ -703,6 +707,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-10-09T17:53:16.583Z
Last session: 2026-10-09T17:55:51.735Z
Stopped at: Phase 19 context gathered
Resume file: .planning/phases/19-chat-and-chatchannels/19-CONTEXT.md

View File

@@ -0,0 +1,319 @@
---
phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views
plan: 04
subsystem: infra (staging deploy on the PHP site's server)
tags: [summercms, go, nuxt-ssr, nginx, supervisor, postgres, migration, staging, basic-auth]
status: complete
requires:
- phase: 16-03
provides: "host repo with migrate.sh, smoke.sh --url, urls.txt, deploy files, PHP export queries"
provides:
- "staging at https://uat.grzybyfunkcjonalne.pl on the PHP server: grzybyfunkcjonalne-go (127.0.0.1:8096) and grzybyfunkcjonalne-nuxt (127.0.0.1:3016) behind one vhost, UAT basic auth with a loopback bypass"
- "Postgres database grzybyfunkcjonalne migrated and imported once: 104 posts (62 published), 7 categories, 35 media files, 95 featured images, author /autor/admin"
- "DEPLOY.md in the host repo (server facts, prerequisites, build and upload, first install, migration runbook, smoke, release, rollback, production cutover)"
- "migrate.sh --skip; media copied with readable modes; fileblob bucket with no_tmp_dir=true"
affects: [16-05, production cutover of grzybyfunkcjonalne.pl]
actuals:
tokens: 4541 # chars/4 over the added lines of the host diff 8f7f2de..c797ab9 (9 files)
tasks: 3
commits: 5 # MEASURED in sm-grzybyfunkcjonalne-app: git rev-list --count 8f7f2de..HEAD
commits: 5
plan_head_before: 8f7f2de7650d9946970bc50e84cc9cc632057059 # sm-grzybyfunkcjonalne-app
plan_head_after: c797ab9d23e0a9e5d6862811560ab4c832a3c57c # sm-grzybyfunkcjonalne-app, pushed to origin/master
tech-stack:
added: []
patterns:
- "UAT gate as an nginx geo realm: basic auth for everyone, off for 127.0.0.1/::1, so on-server checks need no credentials"
- "Remote checks run on the server with CURL_HOME/.curlrc 'resolve = host:443:127.0.0.1' so smoke.sh --url goes through the real vhost and TLS"
- "Server-side files installed into /etc by the operator from uploaded copies in /srv/<app>/deploy/, verified by sha256 against the repo"
key-files:
created:
- ../sm-grzybyfunkcjonalne-app/DEPLOY.md
modified:
- ../sm-grzybyfunkcjonalne-app/README.md
- ../sm-grzybyfunkcjonalne-app/config/storage.yaml
- ../sm-grzybyfunkcjonalne-app/scripts/migrate.sh
- ../sm-grzybyfunkcjonalne-app/scripts/check-deploy.sh
- ../sm-grzybyfunkcjonalne-app/deploy/nginx/grzybyfunkcjonalne-staging.conf
- ../sm-grzybyfunkcjonalne-app/deploy/supervisor/grzybyfunkcjonalne-nuxt.conf
- ../sm-grzybyfunkcjonalne-app/deploy/env.example
- ../sm-grzybyfunkcjonalne-app/deploy/nuxt.env.example
- .planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md
key-decisions:
- "Staging keeps the server's UAT basic-auth gate (/etc/nginx/.g15), off only for loopback through a geo realm; it is not public. Smoke and curl checks run on the server"
- "Staging host is uat.grzybyfunkcjonalne.pl (the server's existing UAT naming), not staging.grzybyfunkcjonalne.pl"
- "first-journal-post is left out of the staging import (migrate.sh --skip), so Go serves the 62 posts the live sitemap lists"
- "The fileblob uploads bucket uses no_tmp_dir=true, because /tmp on the server is tmpfs and the rename across filesystems fails"
- "Install root and .env files belong to the single deploy/service user grzybyfunkcjonalne (0600), because the executor has no root; the operator installed the vhost and supervisor programs into /etc"
patterns-established:
- "DEPLOY.md per host app, with the on-server smoke recipe for basic-auth-gated staging"
requirements-completed: [SC-1, SC-3]
coverage:
- id: D1
description: "Both supervisor programs RUNNING; the imported post /blog/blogbadania-nad-cordycepsem answers over HTTPS with its title from Nuxt, and the Journal API returns featured_image_url (D-18, SC-1)"
requirement: SC-1
verification:
- kind: integration
ref: "Task 2 verify on the server via loopback: check-deploy ok, 2 RUNNING, title grep, featured_image_url grep -> staging-tracer-ok"
status: pass
human_judgment: false
- id: D2
description: "Every urls.txt line passes on staging: all 71 live sitemap URLs (re-pulled 2026-10-09, unchanged), pagination, static pages, negative cases and the remote storage lines (D-15, SC-3)"
requirement: SC-3
verification:
- kind: e2e
ref: "CURL_HOME=<resolve to 127.0.0.1> scripts/smoke.sh --url https://uat.grzybyfunkcjonalne.pl (on the server) -> smoke: ok (98 urls)"
status: pass
human_judgment: false
- id: D3
description: "A featured image and a media file referenced by a post body answer 200 with an image content type and nosniff; /gfadmin 200, /backend 404; no Google Tag Manager; robots.txt Disallow: / (D-09, D-17, D-22, D-23)"
requirement: SC-1
verification:
- kind: integration
ref: "on-server checks: 661e483fa0e06765454515.webp 200 image/webp nosniff; Artur%20Wolak.png 200 image/png nosniff; las.webp 200 image/webp nosniff; /gfadmin 200; /backend 404; homepage without googletagmanager; robots disallow-all"
status: pass
human_judgment: false
- id: D4
description: "The live Winter vhost is unchanged and only new files were installed (D-10)"
requirement: SC-1
verification:
- kind: manual_procedural
ref: "sha256 of /etc/nginx/sites-available/grzybyfunkcjonalne.pl = 37f9858c...7d77 before and after, mtime 2026-08-16; installed vhost and supervisor files equal the uploaded repo copies"
status: pass
human_judgment: false
- id: D5
description: "Staging reads like the live site: refreshed design, complete Polish copy, featured images and inline media on /, /blog, /blog?page=2, a post, /cordyceps, /amanita-muscaria, /kontakt and /autor/admin"
verification: []
human_judgment: true
rationale: "Visual and copy comparison against the live site needs a human with the UAT credentials; the Task 3 human-check has not been answered yet"
duration: 40min
completed: 2026-10-09
---
# Phase 16 Plan 04: Staging deploy on the PHP server Summary
**grzybyfunkcjonalne.pl runs on staging at https://uat.grzybyfunkcjonalne.pl: the Go Journal host and the Nuxt SSR server sit behind a basic-auth vhost on the PHP site's server, serving 62 imported posts with their featured images and media. Every one of the 98 URL checks passes on the server, and DEPLOY.md documents the deploy and the rollback.**
## Performance
- **Duration:** about 40 min. That includes the operator checkpoint between Task 2a/2b and the resume; the resumed part ran from 17:31Z to 17:55Z.
- **Started:** 2026-10-09T17:14:27Z
- **Completed:** 2026-10-09T17:55:00Z
- **Tasks:** 3 (Task 1 resolved by the orchestrator and operator; Tasks 2 and 3 executed)
- **Files modified:** 9 in the host repo, plus deferred-items.md
## Accomplishments
- The migration runbook ran once into the fresh staging database (D-25 order):
- 104 posts imported, 62 of them published.
- pl is the only default locale.
- 7 categories filled.
- The PHP media library copied (35 files).
- 95 featured images attached under their Winter disk names.
- Every post is assigned to `info@golem15.com` with the author slug `admin`.
- nginx -t passed before the reload. Both supervisor programs are RUNNING, and the tracer post renders over HTTPS.
- `smoke.sh --url` passes all 98 urls.txt lines. The live sitemap, re-pulled today, is unchanged (71 URLs, all covered).
- Images:
- The featured image and the media files answer 200 with an image type and nosniff.
- Admin:
- `/gfadmin` answers 200 (the SummerCMS admin shell).
- `/backend` answers 404.
- Search and analytics:
- Staging loads no analytics.
- robots.txt is `Disallow: /`.
- From outside, without credentials, the public address answers 401.
- DEPLOY.md is written and README points to it. The host repo is pushed to origin/master (c797ab9).
## Server facts
| What | Value |
|------|-------|
| SSH alias | `grzybyfunkcjonalne-uat` (user `grzybyfunkcjonalne`, host `alexandria`, 51.77.52.21) |
| Staging URL | `https://uat.grzybyfunkcjonalne.pl` (Cloudflare A record, not proxied; LE ECDSA cert `/etc/letsencrypt/live/uat.grzybyfunkcjonalne.pl/`, expires 2027-01-07) |
| nginx | 1.26.3 (`http2 on;`), user www-data. No options-ssl-nginx.conf or ssl-dhparams.pem on the server, so `ssl_protocols` is set inline. ACME webroot `/srv/http/uat.grzybyfunkcjonalne.pl` |
| Vhost | `/etc/nginx/sites-available/uat.grzybyfunkcjonalne.pl` = repo `deploy/nginx/grzybyfunkcjonalne-staging.conf` (sha256 150f1ef6…2f0c). The placeholder it replaced is backed up at `/root/uat.grzybyfunkcjonalne.pl.placeholder-20261009` |
| Supervisor | `/etc/supervisor/conf.d/grzybyfunkcjonalne-go.conf` (d1b674d2…94d6) and `grzybyfunkcjonalne-nuxt.conf` (31f5ed46…22f0c), equal to the repo |
| Ports | Go 127.0.0.1:8096, Nuxt 127.0.0.1:3016 |
| Node | `/var/www/.nvm/versions/node/v22.22.0/bin/node` (not on the non-interactive PATH) |
| PostgreSQL | 17.11, 127.0.0.1:5432, role/db `grzybyfunkcjonalne`, ICU pl-PL |
| DSN | `postgres://grzybyfunkcjonalne:<password>@127.0.0.1:5432/grzybyfunkcjonalne?sslmode=disable` |
| Install root | `/srv/grzybyfunkcjonalne/{go,nuxt,import,deploy}`, owner grzybyfunkcjonalne. Binary sha256 94116f4a…68ea, built from summercms.go 296b9cd |
| .env files | `/srv/grzybyfunkcjonalne/go/.env` and `nuxt/.env`, owner grzybyfunkcjonalne, mode 0600. Secrets were generated on the server and never printed. `NUXT_SITE_INDEXABLE=false`, `NUXT_PUBLIC_GA_ID` empty |
| sudo | `/usr/sbin/nginx -t`, `nginx -s reload`, `systemctl reload nginx`, `supervisorctl *` |
| PHP storage | uploads `/srv/http/grzybyfunkcjonalne.pl/storage/app/uploads/public` (1082 files), media `/srv/http/grzybyfunkcjonalne.pl/storage/app/media` (35) |
| PHP database | MySQL `grzyby`. Morph types `Golem15\Journal\Models\Post` and `Golem15\Journal\Models\Category`. Translate tables `winter_translate_*`. system_files: 95 post featured_images, 7 category featured_image |
| Import maps | `/srv/grzybyfunkcjonalne/import/featured-images.jsonl` (95), `categories.jsonl` (7); journal export `import/journal-export/` (105 files) |
| Admin | `info@golem15.com`, superuser, created by the operator with the password on stdin from a file outside the deploy (never read or stored by the executor) |
| /tmp | tmpfs, a separate filesystem from `/` |
| Live Winter vhost | `/etc/nginx/sites-available/grzybyfunkcjonalne.pl` sha256 37f9858c…7d77 and mtime 2026-08-16, unchanged after the deploy |
**Files touched on the server.** The executor wrote only under `/srv/grzybyfunkcjonalne`. The operator, as root, installed the vhost, its `sites-enabled` symlink and the two supervisor programs as new files, and backed up the placeholder vhost. The executor's sudo use was `nginx -t`, `systemctl reload nginx` and `supervisorctl reread/update/status`.
## Runbook output (Task 2)
The first run of `scripts/migrate.sh`, with the flags from DEPLOY.md including `--skip first-journal-post`:
```
migrate: migrate
✓ migrations applied
migrate: make pl the default locale
migrate: export: 4 files had translations [] (imported as {})
migrate: export: 1 files skipped (--skip first-journal-post)
migrate: journal:import-posts --dry-run (grzyby:import-posts)
ℹ Results: 104 created, 0 updated, 0 skipped, 0 failed
migrate: journal:import-posts (grzyby:import-posts)
ℹ Results: 104 created, 0 updated, 0 skipped, 0 failed
migrate: grzyby:import-categories --map /srv/grzybyfunkcjonalne/import/categories.jsonl
ℹ Results: 7 updated, 0 unchanged, 0 failed (cordyceps, chaga, reishi, amanita-muscaria, turkey-tail, shiitake, lions-mane)
migrate: rsync media -> /srv/grzybyfunkcjonalne/go/storage/app/media/
migrate: grzyby:import-featured-images ...
grzyby: write 660/cfe/44a/660cfe44a36c0821793311.webp: ... rename /tmp/....tmp /srv/grzybyfunkcjonalne/go/storage/app/uploads/public/...: invalid cross-device link
EXIT=1
```
After the fix (6ef012e), steps 7 to 9 ran with the same commands and the same normalized export copy:
```
ℹ Results: 95 created, 0 skipped, 0 failed
migrate: assign unassigned posts to info@golem15.com (/autor/admin)
migrate: gap: 1 export files reference https://gf.uat.golem15.com/storage/ (broken images, not patched)
wprowadzenie-do-grzyba-cordyceps
migrate: gap: 0 imported post bodies still reference https://gf.uat.golem15.com/storage/
migrate: gap: 0 published posts have no featured image
migrate: published posts: 62
migrate: ok (resumed at step 7)
```
**Gap report:**
- 1 export file has old UAT-host image references, only in its base text.
- 0 imported bodies are affected.
- 0 published posts lack a featured image.
## Smoke and checks (Task 3)
- The live sitemap was re-pulled on 2026-10-09: 71 `<loc>`, all present as `200` lines in urls.txt. urls.txt is unchanged, so there is no test commit.
- `scripts/smoke.sh --url https://uat.grzybyfunkcjonalne.pl`, run on the server through the loopback bypass: `smoke: ok (98 urls)`. The first run failed one line, `remote 200 /storage/app/media/las.webp` → 403; see deviation 3.
- Featured image `/storage/app/uploads/public/661/e48/3fa/661e483fa0e06765454515.webp`: 200, image/webp, nosniff.
- Media referenced by the body of `ekstrakt-z-turkey-tail`, `/storage/app/media/Artur%20Wolak.png`: 200, image/png, nosniff. `las.webp`: 200, image/webp, nosniff.
- `/gfadmin`: 200 text/html (`<title>SummerCMS</title>`). Not logged in, as instructed. `/backend`: 404.
- Homepage: no googletagmanager. robots.txt: `User-agent: *` / `Disallow: /` (nuxt-robots, indexing disabled).
- API `meta.total`: 62.
- Without the bypass, the public address answers 401.
**Human-check: pending.** Nobody has answered "staging approved" or reported issues yet. The visual comparison with the live site, made with the UAT credentials, is left for `/msd-verify-work` (coverage D5). The admin-password part of the original human-check no longer applies (operator note).
## Task Commits
All of them are in `sm-grzybyfunkcjonalne-app` and pushed to origin/master:
1. **Task 1: Server prerequisites**: no commit (resolved by the orchestrator and the operator).
2. **Task 2: deploy and runbook (tracer)**:
- `b488fdd` (chore): match staging deploy files to the server.
- `97cfd31` (feat): migrate.sh --skip for export posts absent from the live site.
- `6ef012e` (fix): fileblob temp files next to the upload, not in /tmp.
3. **Task 3: smoke, images and DEPLOY.md**:
- `faae3c0` (fix): media copy gets readable modes, not the PHP dir's 0776.
- `c797ab9` (docs): staging deploy guide.
**Plan metadata:** see the docs(16-04) commit in summercms.go.
## Files Created/Modified
- `../sm-grzybyfunkcjonalne-app/DEPLOY.md`: server facts, prerequisites, build and upload, first install, migration runbook, start, smoke (on-server recipe), release, rollback, the admin re-save warning, production cutover.
- `../sm-grzybyfunkcjonalne-app/README.md`: points to DEPLOY.md; the `--url` example now uses the uat host.
- `../sm-grzybyfunkcjonalne-app/config/storage.yaml`: `?no_tmp_dir=true` on the uploads bucket.
- `../sm-grzybyfunkcjonalne-app/scripts/migrate.sh`: `--skip NAME`; media rsync with `--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r`.
- `../sm-grzybyfunkcjonalne-app/deploy/nginx/grzybyfunkcjonalne-staging.conf`: uat host, certificate and webroot, `http2 on`, UAT basic auth with the loopback geo bypass, auth off on the ACME path, dotfiles 404.
- `../sm-grzybyfunkcjonalne-app/deploy/supervisor/grzybyfunkcjonalne-nuxt.conf`: absolute nvm Node path.
- `../sm-grzybyfunkcjonalne-app/deploy/env.example` and `deploy/nuxt.env.example`: uat origin; owner is the service user, mode 0600.
- `../sm-grzybyfunkcjonalne-app/scripts/check-deploy.sh`: follows the new host, webroot and log paths.
- `.planning/phases/16-grzybyfunkcjonalne-pl-on-reusable-blog-views/deferred-items.md`: 16-03 items resolved; new 16-04 items.
## Decisions Made
- **Basic-auth gate kept** (operator decision with the "installed" reply). Staging is not public. The geo realm turns auth off for 127.0.0.1 and ::1 only, so smoke and curl checks run on the server. The ACME webroot location has `auth_basic off`, so renewals keep working. Production drops the gate.
- **Host name `uat.grzybyfunkcjonalne.pl`.** It follows the server's existing UAT convention and its existing certificate.
- **`--skip first-journal-post`.** This resolves the 16-03 deferred item for staging: Go serves the same 62 posts the live sitemap lists.
- **No database drop after the featured-image failure.** Steps 1 to 6 had succeeded, the featured-image import is idempotent, and the failed write left no row behind (the resumed run reported 95 created, 0 skipped). Running steps 7 to 9 with the identical commands gives the same end state as a fresh full run, without root access to drop and recreate the database.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Featured-image import failed with "invalid cross-device link"**
- **Found during:** Task 2 (runbook, step 7).
- **Issue:** gocloud's fileblob writes temp files to `os.TempDir()` and renames them into the bucket. `/tmp` on the server is tmpfs, so the rename fails. Admin uploads would fail the same way.
- **Fix:** set `bucket_url: "file://./storage/app/uploads/public?no_tmp_dir=true"` in the host config, upload it, and run steps 7 to 9 with the same commands.
- **Files modified:** `../sm-grzybyfunkcjonalne-app/config/storage.yaml`
- **Verification:** 95 created, 0 failed; the featured images answer 200 on staging.
- **Committed in:** 6ef012e
- **Follow-up:** a framework default in `modules/lagoon/attach`, logged in deferred-items.md and not touched here (another session owns `modules/`).
**2. [Rule 3 - Blocking] Runbook resumed at step 7 instead of drop-and-rerun**
- **Found during:** Task 2.
- **Issue:** The plan says a failed run means dropping and recreating the database. The executor has no root or CREATEDB rights for that.
- **Fix:** a one-off script ran migrate.sh steps 7 to 9 verbatim (same normalized and skipped export copy, same SQL, same gap report). The script was removed from the server afterwards.
- **Verification:** 95 featured images, 0 published posts without an image, 62 published.
- **Committed in:** none (operational step); documented in DEPLOY.md.
**3. [Rule 1 - Bug] Media images answered 403**
- **Found during:** Task 3 (smoke: `remote 200 /storage/app/media/las.webp` got 403).
- **Issue:** `rsync -a` kept the PHP media directory's mode 0776, which www-data cannot traverse.
- **Fix:** the migrate.sh media step now sets modes with `--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r`. The uploaded script was used to re-sync the media on the server (idempotent; the PHP directory was only read).
- **Files modified:** `../sm-grzybyfunkcjonalne-app/scripts/migrate.sh`
- **Verification:** smoke `ok (98 urls)`; las.webp and Artur Wolak.png answer 200 image/* with nosniff.
- **Committed in:** faae3c0
**4. [Operator decision] Verify commands run on the server**
- **Issue:** The plan's verify commands curl `$STAGING_URL` from the executor's machine. Because of the kept UAT gate, they answer 401 there.
- **Fix:** run on the server with curl resolving the host to 127.0.0.1, which exercises the real vhost and TLS. The recipe is in DEPLOY.md, "Smoke".
**5. [Server constraint] .env owner and mode**
- **Issue:** The plan asks for `root:grzybyfunkcjonalne 0640`. The executor has no root.
- **Fix:** the files belong to the single deploy and service user, mode 0600, as recorded in b488fdd and in the env templates.
---
**Total deviations:**
- 3 auto-fixed: 2 blocking, 1 bug.
- 2 adaptations to server constraints and operator decisions.
**Impact on plan:** The fixes were needed to serve images on this server. No scope creep, and the live site was not modified.
## Issues Encountered
- `scripts/smoke.sh --go-only` could not run locally to pre-check the storage change: there is no Docker socket access and `SMOKE_ADMIN_DSN` is not set. The server run was the verification.
## User Setup Required
None beyond what the operator already did. The pending human visual check is tracked under coverage D5.
## Next Phase Readiness
- Staging serves the site end to end, so SC-1 is met on staging (D-18) and SC-3 is proven against every live URL. Plan 16-05 (unit tests) can proceed.
- Before the production cutover:
- the human visual approval;
- the deferred items (`journal-imported-html-admin-resave`, the cta.png size);
- the steps in DEPLOY.md, "Production cutover".
## Self-Check: PASSED
- `../sm-grzybyfunkcjonalne-app/DEPLOY.md`: FOUND.
- `../sm-grzybyfunkcjonalne-app/config/storage.yaml`: FOUND.
- `../sm-grzybyfunkcjonalne-app/scripts/migrate.sh`: FOUND.
- Commits b488fdd, 97cfd31, 6ef012e, faae3c0 and c797ab9: FOUND in sm-grzybyfunkcjonalne-app and on origin/master.
---
*Phase: 16-grzybyfunkcjonalne-pl-on-reusable-blog-views*
*Completed: 2026-10-09*

View File

@@ -12,4 +12,11 @@
- **sm-journal-plugin: `journal:import-posts` rejects `"translations": []`.** The PHP exporter writes a post without translations as `[]` (json_encode of an empty PHP array), and the Go `PostFile.Translations map[string]map[string]any` fails to unmarshal it ("Invalid JSON"). This affects 5 of the 105 grzybyfunkcjonalne export files; 2 of them are published, including the live post `ekstrakt-z-turkey-tail`. `scripts/migrate.sh` imports from a copy in which only that empty array becomes `{}`. The importer should accept an empty array as an empty map (PHP parity).
- **`first-journal-post` is published in the export but not on the live site.** "First journal post" was published 2026-02-23. It is absent from the live sitemap, so Go serves 63 published posts where the live site lists 62. Unpublish or delete it after the staging import (16-04), or confirm it should stay.
- **The UAT-host image references are not in the imported bodies.** The 2 `https://gf.uat.golem15.com/storage/...` references sit only in the base (older) content of `wprowadzenie-do-grzyba-cordyceps`. With pl as the default, the pl body replaces the base text, so the gap report shows 1 export file and 0 imported bodies. Nothing is broken on the new stack. The report keeps listing the export file.
- **nginx `listen ... http2` is deprecated on nginx 1.25.1 and newer** (a warning, not an error). The staging vhost keeps the summercms.io form because the PHP server's nginx version is unknown. Switch to `http2 on;` once 16-04 confirms the version.
- **nginx `listen ... http2` is deprecated on nginx 1.25.1 and newer** (a warning, not an error). The staging vhost keeps the summercms.io form because the PHP server's nginx version is unknown. Switch to `http2 on;` once 16-04 confirms the version. *Resolved in 16-04 (b488fdd): the server runs nginx 1.26.3 and the vhost uses `http2 on;`.*
- *`first-journal-post`: resolved in 16-04 for staging. `migrate.sh --skip first-journal-post` leaves it out of the import, so staging serves the 62 live posts. The export file itself is unchanged.*
## From 16-04 (staging deploy)
- **summercms.go `modules/lagoon/attach`: file buckets write temp files to `os.TempDir()`.** gocloud's fileblob creates the temp file in `/tmp` and renames it into the bucket. On a server whose `/tmp` is tmpfs (the grzybyfunkcjonalne staging server), every write fails with "invalid cross-device link". The host sets `?no_tmp_dir=true` in `config/storage.yaml` (sm-grzybyfunkcjonalne-app 6ef012e). `normalizeBucketURL` could default `no_tmp_dir` for `file://` the same way it defaults `create_dir`, so no host meets this on its first upload. Not changed here: another session owns `modules/`.
- **Visual review of staging (16-04 Task 3 human-check) is pending.** The automated checks all pass. A human still opens the staging pages with the UAT credentials and compares them with the live site (design, Polish copy, featured images, inline media). The admin password step of the original human-check no longer applies: the operator created the admin with a password piped from a file outside the deploy.
- **The plan's verify commands curl `$STAGING_URL` from the executor's machine,** but staging keeps the server's UAT basic-auth gate (operator decision), so they answer 401 from outside. They ran on the server through the loopback bypass instead (DEPLOY.md, "Smoke"). A later plan that checks staging from outside needs the credentials, or should run on the server the same way.