Root-caused with systemd-run repros: under the unit's hardening flags the
backgrounded grandchild ('cmd &' / setsid) died silently, so a CMS publish
committed+pushed but never rebuilt the site (stale content, no deploy.log,
no trace). A direct (non-detached) exec child provably survives the same
flags; the response is sent first, output goes to deploy.log, and the
callback's audit entry now reports real deploy completion (ok/error).
Closes MITHOME-72
A CMS publish's background deploy silently never ran under systemd
(naive 'cmd &' child died with the spawning shell; no log, no trace —
the site kept serving stale content). The spawn now uses
setsid+nohup+stdin-null so it survives any parent exit, and writes
deploy_spawned / deploy_exec_exit audit entries so a failed spawn can
never be silent again.
Closes MITHOME-72
The version tag is the git SHA read at CMS startup, but the deploy script
only rebuilt the website container, leaving the tag stale (v8838304 shown
after aa8d926 was live). The local deploy script now restarts the CMS
service after the server-side deploy (the CMS publish flow is unaffected —
it calls deploy.sh directly and never restarts itself). The editor main
page also gains Cache-Control: no-store so browsers stop caching it.
Closes MITHOME-71
MITHOME-68: the optimistic-lock fingerprint was frozen at page load, so the
user's own second save 409'd. /save now returns the hash of the written
content and the client refreshes CONTENT_HASH on success — 409 only fires
for genuine external changes (deploy, other tab, restore). Also: successful
logins no longer consume the auth failure budget (only failed attempts do).
MITHOME-69: bottom bar items no longer shift while saving/publishing — the
status message occupies a constant flex slot (visibility instead of
display), the publish button locks its width while running and restores
its env-specific label, auto margins removed. Layout guard test added.
Test markers are now run-unique so a crashed run can never poison the
next one's expectations.
- 🎨 Logó page in the CMS bottom bar: replace the website header logo and
the CMS login icon with a PNG upload (magic-byte validation, 1 MiB cap)
- the replaced logo gets a timestamped backup in .content-backups; every
upload is audited (logo_updated)
- /logo.png?variant=header serves the header variant for the preview
- publish stages proto/public too, so logo changes ride the same
commit+deploy pipeline as content
- route handling extracted to scripts/cms-logo.js to stay under the
400-line limit
- integration test: upload+replace+backup, variant preview, 415/413/400,
CSRF, auth
Closes MITHOME-65
- GET /versions lists the automatic backups of the selected file (timestamp,
size); ?show=<backup> renders a line diff against the current content
- POST /restore validates the backup against the content schema and restores
it atomically; the pre-restore state gets a fresh backup first, so a
restore itself is reversible; audited as version_restored
- dependency-free LCS line diff (scripts/cms-diff.js) with add/del
highlighting and context trimming; backup names validated against a strict
pattern (path traversal impossible)
- new 🕘 Verziók entry in the CMS bottom bar
- refactor: security/infra helpers extracted to scripts/cms-core.js to keep
content-editor.js under the 400-line hard limit
- integration test: list, diff, restore + reversibility backup, traversal
rejection, CSRF enforcement, auth
Closes MITHOME-64
'Is the fix live?' becomes a single check instead of an SSH session:
- CMS: git short SHA read at startup, shown in the bottom bar (v<sha>),
served by the public GET /version endpoint, recorded in a startup audit
entry
- Website: deploy.sh exports DEPLOY_VERSION (git SHA), Dockerfile bakes it
via build ARG into the runtime env, /api/health reports it as
deployVersion, smoke test asserts a non-'unversioned' stamp
Closes MITHOME-63
Safari-specific deviations fixed:
1. Safari shows its native auth dialog on fetch() calls answered with a
401 + WWW-Authenticate challenge (e.g. save with an expired session).
All CMS 401 responses now omit WWW-Authenticate; browsers use the styled
/login page instead.
2. Safari caches Basic credentials and resends them automatically, which
made logout ineffective (a navigation after logout went straight back
into the editor). Browser navigations (GET + text/html) now authenticate
ONLY via the session cookie; Basic Auth remains valid for non-browser
clients (curl, API).
3. /login and redirects send Cache-Control: no-store so Safari does not
cache the login page or the 302.
Closes MITHOME-62
A Content Editor tab left open across a deploy (or a save from another tab)
held the pre-deploy content; one Save would silently overwrite the newer
file. The editor page now embeds a SHA-256 fingerprint of the file content
at load time, /save requires it back in X-Content-Hash and compares against
the current file: mismatch (or a missing header) answers 409 with an
explanatory message and writes nothing. The client offers a reload on 409.
Integration test covers: matching hash saves, stale hash rejected with the
file untouched, missing hash rejected, retry with the fresh hash succeeds.
Closes MITHOME-61
Review follow-up on MITHOME-59:
- no-op publish no longer triggers a background deploy (deploy moved behind
a deterministic hadChanges flag)
- replace output-regex classification ('Already up to date.' also appears on
real publishes when the remote did not move, which misclassified them as
no_changes) with an explicit __NO_CONTENT_CHANGES__ marker echoed by the
shell skip-branch
- failed git pull --rebase is aborted immediately so the repo is never left
mid-rebase; the error is reported and nothing is pushed or deployed
- command + interpretation extracted to scripts/cms-publish.js
- CONTENT_EDITOR_CONTENT_DIR / CONTENT_EDITOR_DEPLOY_CMD env overrides enable
an integration test against throwaway git repos covering: no-change skip,
real publish + deploy, rebase conflict abort
- .gitignore: drop patterns already covered by .env.*
- user guide: new no-changes message
Closes MITHOME-60
- logout asks for confirmation, then invalidates the server-side session
and navigates to a public /login page (logo, form, error messages)
- POST /login validates credentials (timing-safe) and issues an HttpOnly
SameSite=Strict session cookie (8h, Secure behind HTTPS); Basic Auth
stays valid in parallel for curl/API use
- unauthenticated browser navigations redirect to /login; non-browser
requests keep the 401 challenge
- failed form logins share the auth rate-limit budget with Basic attempts
- save/publish redirect to /login when the session expired
- refactor: templates and browser script extracted to scripts/cms-pages.js
and scripts/cms-editor-client.js, session logic to scripts/cms-session.js
(content-editor.js back under the 400-line limit)
- user guide updated (login page, confirmation, 8h session)
Closes MITHOME-58
- docs/felhasznaloi-utmutato.md: user guide for the website and the CMS
(login, editing, arrays, save/validation, publish, security)
- /guide endpoint renders the markdown auth-protected via a dependency-free
renderer (scripts/markdown-render.js) in the CMS dark theme
- new ❓ Súgó entry in the CMS bottom bar
- steering rule: the guide must be updated in the same commit as any CMS or
website feature change
Closes MITHOME-57
Basic Auth credentials are cached by the browser until it closes, so the
editor had no real logout. Add a /logout endpoint (always answers 401 with
a challenge; deliberately exempt from the auth rate limiter so logging out
never locks the user out) and a Kilépés button that overwrites the cached
credentials with an invalid pair via fetch, then reloads into the login
prompt.
Closes MITHOME-56
reindexItems replaced the FIRST [n] index in a data-path, which for nested
arrays (e.g. services[1].specs.items) rewrote the OUTER array index instead
of the item's own index. Deleting or adding an item scattered paths across
services[0..n], produced sparse arrays (null items) and schema errors like
'$.details.services[1].specs.items[0]: string érték szükséges'.
Rewrite only the index directly following the reindexed array's path prefix;
drop the now-redundant str-item special case.
Closes MITHOME-30