feat: flyt sitet fra Astro til Zola

Samme indhold, URL'er og udseende, bygget af en enkelt zola-binær og Tailwind
standalone-CLI uden Node. Kanonisk domæne er asger.weirsoe.dk.

Blogindlæg ligger som page bundles med frontmatter Zola forstår, så de gamle
stempler er ugyldige, indtil scripts/omstempl.py er kørt med PUBLISH_KEY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Asger Geel Weirsøe
2026-11-01 16:47:08 +01:00
co-authored by Claude Opus 5.5
parent 7e13068dd2
commit e58a11c2b4
159 changed files with 8031 additions and 3574 deletions
+61
View File
@@ -0,0 +1,61 @@
"""Godkendelsesstemplet på blogindlæg: HMAC-SHA256 over sti, indhold og billeder.
canonical() skal matche godkendelsestjenesten (service/app.py i agw/pw-drafts) byte for byte.
"""
import hashlib
import hmac
import os
import re
BLOG = "content/blog"
def canonical(rel, text, assets=()):
"""assets: [(sti, sha256-hex)] for billederne i bundlen, sorteret efter sti."""
lines = text.replace("\r\n", "\n").split("\n")
if lines and lines[0] == "---" and "---" in lines[1:]:
end = lines.index("---", 1)
lines = [l for i, l in enumerate(lines) if not (0 < i < end and re.match(r"\s*godkendt:", l))]
return f"{rel}\n" + "\n".join(lines) + "".join(f"\n@asset {p} {h}" for p, h in assets)
def sign(key, rel, text, assets=()):
return "v1:" + hmac.new(key, canonical(rel, text, assets).encode(), hashlib.sha256).hexdigest()
def sha256_file(path):
with open(path, "rb") as f:
return hashlib.sha256(f.read()).hexdigest()
def assets_for(slug):
"""Alt i bundlen ud over index.md er billeder, og de ligger fladt."""
d = f"{BLOG}/{slug}"
out = []
for name in os.listdir(d):
if name == "index.md":
continue
p = f"{d}/{name}"
if not os.path.isfile(p):
raise ValueError(f"{p}: undermapper understøttes ikke")
if name.endswith(".md"):
raise ValueError(f"{p}: kun index.md må være markdown i en bundle")
out.append((p, sha256_file(p)))
return sorted(out)
def read_text(path):
with open(path, "rb") as f:
return f.read().decode()
def stamp_of(text):
"""(stempel eller None, om indlægget er markeret draft: true)"""
lines = text.replace("\r\n", "\n").split("\n")
end = lines.index("---", 1) if lines[0] == "---" and "---" in lines[1:] else -1
if end < 0:
return None, False
fm = lines[1:end]
g = next((l for l in fm if re.match(r"\s*godkendt:", l)), None)
draft = any(re.fullmatch(r"draft:\s*true\s*", l) for l in fm)
return (re.sub(r"^\s*godkendt:\s*", "", g).strip("'\"") if g else None), draft