makiolaj 8a1eb54a45 Render the app privacy policies from the app repos
Each policy now has exactly one copy: docs/PRIVACY.md in the app's own
repository. The pages keep their URLs and chrome and render that file
through a content collection, so the published page and the app's own
documentation cannot drift.

scripts/sync-external.mjs shallow-clones both repos into external/ from
prebuild and predev — not from CI: Coolify builds the site from the repo,
so a checkout that only ran in a Gitea job would never reach the deploy.
It falls back to the raw file if git is unavailable, and takes <APP>_REF
or <APP>_LOCAL for work against a branch or an unpushed working copy.

A missing, empty or malformed policy fails the build, verified against the
real image: the deploy stops rather than publishing an empty privacy page.
2026-09-09 16:33:11 +02:00

jeanlucmakiola.de

Personal website + blog. Built with Astro, deployed on self-hosted Coolify, analytics via self-hosted Umami.

See PLANNING.md for the decisions and roadmap.

Develop

npm install
npm run dev      # http://localhost:4321
npm run build    # output -> dist/
npm run preview  # serve the built site locally

App privacy policies (rendered from the app repos)

/calendula/privacy and /agendula/privacy hold no copy of their prose. Each policy lives in its own app repository, as docs/PRIVACY.md with title / description / updated frontmatter, and is the single copy of that policy anywhere:

Page Source
/calendula/privacy jlmakiola/calenduladocs/PRIVACY.md
/agendula/privacy jlmakiola/agenduladocs/PRIVACY.md

scripts/sync-external.mjs clones them (shallow, single-branch, no credentials) into external/ before every build and dev server — it is wired to prebuild and predev, not to CI, because Coolify builds the site from the repo and a checkout that only ran in a Gitea job would never reach the deployed page. src/content.config.ts exposes each file as a content collection; the pages render it and supply the <h1> from title.

To change a policy, edit it in the app repo, in a PR. It reaches the live page on the site's next build.

npm run sync:external                       # refresh the checkouts by hand
CALENDULA_REF=some/branch npm run build     # build against a branch
AGENDULA_LOCAL=../agendula npm run dev      # render a working copy, no network

The build fails if either policy is missing, empty, or malformed. That is deliberate: a privacy page silently rendering nothing is the one failure this arrangement exists to prevent, and it is worth a red deploy.

Getting a policy edit onto the site

A build is what publishes it, so:

  • Floor: the daily scheduled-deploy cron rebuilds every morning, so any edit is live within a day without anyone doing anything.
  • Immediate: add a webhook in the app repo (Codeberg → Settings → Webhooks) pointing at the same Coolify deploy URL the cron uses, so a merge to the app's main triggers a site rebuild at once. One-time setup per app repo; the secret lives in Coolify, not here.

Writing a post

Create a Markdown file in src/content/blog/, e.g. my-post.md:

---
title: My post
description: One-line summary (used for SEO + RSS).
pubDate: 2026-06-28
tags: [notes]
draft: false        # set true to hide from listings/feeds
---

Body in Markdown.

The filename (minus extension) becomes the URL slug: /blog/my-post/. Commit + push → Coolify rebuilds → live.

Analytics (Umami)

Analytics is off unless these env vars are set (so it stays off in dev). Set them as build-time variables in Coolify:

Variable Value
PUBLIC_UMAMI_SRC https://<your-umami>/script.js
PUBLIC_UMAMI_WEBSITE the website id (UUID) from Umami

Deploy on Coolify

This repo ships a multi-stage Dockerfile (build with Node → serve dist/ with Caddy on port 80).

  1. New Resource → Dockerfile (or auto-detect), point at this repo.
  2. Set the two PUBLIC_UMAMI_* build variables.
  3. Set the domain to jeanlucmakiola.de; Coolify provisions HTTPS.
  4. Deploy Umami separately (Coolify has a one-click Umami template), then copy its script URL + website id into the build variables above.

SEO

  • Sitemap at /sitemap-index.xml (via @astrojs/sitemap)
  • RSS at /rss.xml
  • Canonical URLs + Open Graph/Twitter tags in src/components/BaseHead.astro
  • robots.txt in public/
  • Add an og-default.png (1200×630) to public/ for link previews.
S
Description
Personal website & blog — Astro + Material 3 Expressive
Readme
333 KiB
Languages
Astro 43.4%
CSS 38.9%
JavaScript 8.6%
TypeScript 7.5%
Dockerfile 1.6%