Rich-text fields (hero lead, paragraphs, FAQ-style copy, datenschutz
sections, etc.) now use a proper toolbar editor - bold/italic/underline
buttons plus a per-selection font picker drawn from the same curated
font list as the Design tab - instead of typed **markdown** syntax.
Content is stored as a small allowlisted HTML subset (strong/em/u/span
with a validated data-font/br) and sanitized server-side with
sanitize-html on every save (admin/lib/rich-sanitize.js), independent
of what the client sends - verified this rejects script tags, event
handlers, and made-up font names while preserving legitimate markup,
and that re-saving already-sanitized content is a no-op.
The Astro site mirrors the same allowlist at render time for
defense-in-depth (src/lib/rich-sanitize.ts) and only requests Google
Fonts for fonts actually referenced somewhere in content, in addition
to the two Design-tab fonts - verified a font used via a data-font span
gets its own Google Fonts request and [data-font] CSS rule in the
built page, and that unused fonts don't get requested.
Migrated the three existing fields that used the old markdown-lite
syntax (**bold**/*italic*) to real HTML so they keep rendering
correctly under the new system.
Also replaces the single long scrolling admin page with a tabbed
sidebar (grouped "Inhalte"/"Design") to cut down on clutter, per
feedback that the all-in-one-page layout was hard to navigate.
Docker Compose interpolates $VAR references within env_file values (not
just docker-compose.yml), so a raw bcrypt hash like $2a$12$... gets
partially swallowed as undefined variables - exactly the truncation
Pascal hit. Pipe hash-password.js output through `sed 's/\$/$$/g'` and
store the escaped hash; document the 63-char (60 + 3 escaped $) length
check to verify it went in correctly.
ADMIN_USERNAME/ADMIN_PASSWORD_HASH now optionally extends to _2, _3, ...
for additional accounts (e.g. a second login for Lara), all with equal
access - no roles, still no self-service password reset. Falls back to
a dummy bcrypt compare on unknown usernames to keep login timing
consistent. Verified locally: both users log in, wrong password and
unknown usernames are rejected, and single-user setups still work
unchanged (backward compatible).
Also documents the optional second-user setup in DEPLOYMENT_ADMIN.md (B6/B7).
Dockerfile.admin sets WORKDIR /app and copies the whole repo there, so
hash-password.js lives at /app/admin/hash-password.js, not /app/hash-password.js
(same pattern as the existing admin/server.js CMD). The docker compose run
command in B6 was missing the admin/ prefix, causing MODULE_NOT_FOUND.
The ad-hoc `npm install bcryptjs` in a throwaway container hit npm's known
"Tracker 'idealTree' already exists" bug. Reorder Teil B so the admin
service is built first (a proper `npm ci`, the same path already used for
the real deployment), then generate the password hash and session secret
via `docker compose run --rm admin ...` against that image instead.
The deploy host only has Docker installed, not Node/npm directly, so the
original `npm install` / `node hash-password.js` steps failed with
"command not found". Both the bcrypt hash and session secret are now
generated with a throwaway `node:22-alpine` container instead.
Adds a self-service editor at admin.larpaso.de so texts, images, links,
Impressum/Datenschutz, fonts, font sizes and colors can be changed without
a developer. All homepage/legal content moves into content/site.json and
a new content/theme.json drives every CSS color/font/size token; the
Astro pages read from these at build time.
The admin/ service (Node/Express) provides bcrypt+session login with
rate limiting, a schema-driven form UI (including a hex/RGB/alpha color
picker), image upload, and a publish action that runs the Astro build
and republishes into a volume shared with the nginx website container -
so edits go live without redeploying that container.
See DEPLOYMENT_ADMIN.md for the server-side setup steps.
Replaces the previous multi-page structure (Startseite, Leistungen,
Über uns, Kontakt) with one long-form page covering pricing, work
samples, process and FAQ, matching the new visual identity (dark
theme, serif display type, sage-green logo mark). Old routes now
redirect to the corresponding anchors. Adds Impressum and Datenschutz
stub pages with clearly marked placeholders for the still-missing
real content (contact details, founder bios, portfolio, pricing
specifics).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
larpaso.de is now confirmed live (verified 200 OK + valid TLS cert).
Records what was done for Teil B and clarifies the ongoing workflow:
Claude Code can edit/build/push code changes directly, but the
server-side rebuild step still needs to run on the server manually
unless an auto-deploy hook is set up later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8eccP4XnXFJvpSYMm26as
Documents that Teil A is verified (build tested locally) and provides
exact commands for deploy key, docker-compose service, Caddyfile
block, and DNS steps so the server-side rollout needs no improvisation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8eccP4XnXFJvpSYMm26as
Multi-stage Dockerfile (node build -> nginx serve), nginx config with
_astro asset caching, .dockerignore, and .gitignore hardening for
TLS key files. Astro already builds static output with site set to
https://larpaso.de.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmTVcwbGJBYP7gu4P7b1yG