Liten statisk blogg med mobilvennlig Markdown-editor, bildeopplasting og innhold i git. Én hovedbruker, valgfrie gjester.
  • Python 66.4%
  • HTML 15.7%
  • CSS 9%
  • JavaScript 5.4%
  • Shell 2.4%
  • Other 1.1%
Find a file
Ole-Morten Duesund 42b296ca06 Cache-busting: immutable bare på versjonerte URL-er, streng asset()
Fra code review av ea07f34:
- /theme/* fikk max-age=1 år + immutable også uten ?v=; url("favicon.svg")
  inne i CSS ville da sittet fast etter en temaendring. Nå bare med hash.
- asset() feiler ved bygging på ukjent temafil i stedet for å gi en
  uversjonert URL i stillhet.
- rel() defineres én gang og gjenbrukes av asset(); dødt is_dir-guard
  fjernet; docstring peker på asset() for temafiler.
- Tester: uversjonert forespørsel skal ikke være immutable; hash-format
  sjekkes på nestede sider; ukjent asset gir feil.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcEy43fNYpwg6K6oTKakWR
2026-08-26 15:59:53 +02:00
soapbox Cache-busting: immutable bare på versjonerte URL-er, streng asset() 2026-08-26 15:59:53 +02:00
tests Cache-busting: immutable bare på versjonerte URL-er, streng asset() 2026-08-26 15:59:53 +02:00
.env.example Lytt på alle adresser (IPv4 og IPv6) som standard 2026-08-26 15:13:32 +02:00
.gitignore run.sh: automatisk backup med rotasjon, og --restart=always 2026-08-26 14:41:16 +02:00
build.sh Legg til build.sh, run.sh og .env.example 2026-08-26 12:36:12 +02:00
Caddyfile.example Lytt på alle adresser (IPv4 og IPv6) som standard 2026-08-26 15:13:32 +02:00
compose.yaml Lytt på alle adresser (IPv4 og IPv6) som standard 2026-08-26 15:13:32 +02:00
Containerfile Sikkerhet: tett path traversal i post-id og herd innlogging/upload 2026-08-26 14:07:14 +02:00
LICENSE Lisensier under AGPL-3.0-or-later og lenk til kildekoden i footeren 2026-08-26 13:13:25 +02:00
pyproject.toml Lisensier under AGPL-3.0-or-later og lenk til kildekoden i footeren 2026-08-26 13:13:25 +02:00
README.md Cache-busting for temafiler 2026-08-26 15:49:43 +02:00
run.sh Lytt på alle adresser (IPv4 og IPv6) som standard 2026-08-26 15:13:32 +02:00
uv.lock Soapbox: statisk blogg med mobilvennlig markdown-editor 2026-08-26 12:12:33 +02:00

Soapbox

En liten blogg: statiske sider, mobilvennlig Markdown-editor med bildeopplasting og live forhåndsvisning, innhold versjonert i git. Én hovedbruker («soapbox») pluss valgfrie gjester.

Hvordan det henger sammen

data/
├── content/            git-repo  alt innhold, kan klones/flyttes fritt
│   ├── site.toml       tittel, beskrivelse, språk
│   ├── posts/<slug>/index.md + bilder        → /<slug>/
│   └── guests/<bruker>/<slug>/index.md       → /<bruker>/<slug>/
├── public/             generert statisk side (bygges automatisk ved lagring)
├── soapbox.sqlite      brukere og passord-hasher  det eneste som ikke ligger i git
└── secret_key          cookie-signering (genereres automatisk)
  • Slug lages av tittelen (Grønt er skjøntgrønt-er-skjønt). Den følger tittelen så lenge innlegget er utkast, og fryses ved publisering så lenker aldri brekker.
  • Alle interne lenker er relative. Malene regner ut ../ ut fra sidens dybde, og absolutte lenker du skriver i Markdown (/annet-innlegg/) skrives om. Derfor fungerer samme public/ både på blog.domene.no/ og annet-domene.com/blog/ uten rebuild.
  • Bilder ligger i samme mappe som innlegget og refereres som ![](bilde.jpg). Ved opplasting skaleres de ned (maks 2000 px) og EXIF fjernes (ingen GPS-posisjon publiseres).
  • Hver lagring er en git-commit med brukeren som forfatter. Hvert innlegg har en «Historikk»-seksjon i editoren med diff per endring; hele loggen ligger under Innstillinger (eller git -C data/content log).
  • Gjester ser og redigerer bare egne innlegg. Hovedbrukeren ser alt og administrerer gjester.
  • Tagger settes kommaseparert i editoren. /tag/<tagg>/ lister alle innlegg med taggen (også gjesters), /tag/ alle tagger. Tagger får slug på samme måte som titler.
  • Brukersider: /brukere/ lister hovedbruker øverst og gjester alfabetisk; /brukere/<navn>/ viser visningsnavn, beskrivelse (Markdown, settes under Profil), e-post og innlegg.
  • Signatur: innlegg avsluttes med «– Visningsnavn», evt. med e-post som mailto-lenke. Begge settes under Profil i admin og slås opp ved bygging, så en endring gjelder alle innlegg.

Kjøre lokalt

uv sync
SOAPBOX_DATA_DIR=./data uv run soapbox create-user olemd --role owner
SOAPBOX_DATA_DIR=./data uv run soapbox serve --debug
# → http://localhost:8080/admin/

Tester: uv run pytest. Lint: uv run ruff check . && uv run ruff format .

Konfigurasjon (miljøvariabler)

Se .env.example for en kommentert mal. Alt har standardverdier; kun SOAPBOX_SITE_URL og hovedbrukeren bør settes.

Variabel Standard Beskrivelse
SOAPBOX_DATA_DIR ./data (/data i container) Alt persistent
SOAPBOX_BASE_PATH stien i SOAPBOX_SITE_URL Overstyring, kun hvis proxyen stripper prefikset
SOAPBOX_SITE_URL http://localhost:8080 Absolutt URL til rot inkl. sub-path; brukes i Open Graph, feed, sitemap og til å utlede base-path
SOAPBOX_THEME green Mappe under soapbox/themes/
SOAPBOX_MAX_IMAGE_PX 2000 Lengste side på opplastede bilder
SOAPBOX_SECRET_KEY autogenerert Overstyr hvis du vil ha nøkkelen utenfor volumet
SOAPBOX_OWNER_USER / _PASSWORD Oppretter hovedbruker ved første start

Container og Caddy

cp .env.example .env        # sett SOAPBOX_SITE_URL og hovedbruker
./build.sh                  # bygger localhost/soapbox:latest med build-metadata
./run.sh                    # podman run --replace med .env, lytter på SOAPBOX_BIND:SOAPBOX_PORT
# eller: podman compose up -d

Ny versjon: git pull && ./build.sh && ./run.sh. Data ligger i podman-volumet soapbox-data (podman volume inspect soapbox-data viser stien).

run.sh tar automatisk backup av volumet før hver oppstart til SOAPBOX_BACKUP_DIR (standard ./backups/soapbox-data-<tidsstempel>.tar.gz) og beholder de SOAPBOX_BACKUP_KEEP nyeste (standard 5). Gjenopprett med podman volume import soapbox-data <fil>.

Caddy er ren reverse proxy, se Caddyfile.example. Appen lytter som standard på alle adresser (IPv4 og IPv6), så Caddy kan stå på en annen maskin og nå den over Tailscale/MagicDNS. Sett SOAPBOX_BIND=127.0.0.1 for å begrense når Caddy kjører lokalt. Ved sub-path skal Caddy ikke strippe prefikset appen monteres selv under stien fra SOAPBOX_SITE_URL.

Flytte bloggen (nytt domene og/eller sub-path)

Ingen innhold trenger endring alle lenker er relative. Velg én av to måter:

A. Kopier hele data/-volumet. Tar med innhold, brukere og cookie-nøkkel. Ingenting må opprettes på nytt.

podman volume export soapbox-data | ssh ny-server 'podman volume import soapbox-data -'

B. Klon bare innholdsrepoet. Brukere ligger i sqlite, ikke i git, så de må opprettes på nytt (hovedbruker via SOAPBOX_OWNER_USER/_PASSWORD eller soapbox create-user, gjester i admin med samme brukernavn som før brukernavnet er URL-prefikset /gjest/).

git clone gammel-server:/sti/til/data/content ./data/content

Deretter, uansett metode: sett SOAPBOX_SITE_URL til den nye adressen (sub-path utledes derfra) og start. Siden bygges på nytt ved oppstart.

Tema

Aktivt tema velges under Innstillinger i admin og lagres i site.toml (følger med i git). SOAPBOX_THEME er bare standardverdien før noe er valgt.

Lag et eget tema uten å bygge containeren på nytt: legg det i innholdsrepoet, så følger det med i git, backup og ved flytting.

mkdir -p data/content/themes/skog/static
cp soapbox/themes/green/static/style.css data/content/themes/skog/static/   # rediger fargene

Et tema kan være delvis: bare filene som finnes overstyrer det innebygde green-temaet. Vil du endre malene, legg templates/base.html, index.html eller post.html i temamappen. Malene får site, posts/post, html, source_url og rel(sti) (relativ lenke fra gjeldende side), samt filtrene date, excerpt(n) og tagslug. Bruk asset('theme/fil.css') for temafiler: den gir en relativ URL med innholdshash (?v=…) for cache-busting. static/ havner under /theme/.

Lisens

AGPL-3.0-or-later, se LICENSE. Bloggen lenker til kildekoden i footeren, slik lisensen krever for nettverkstjenester. Endrer du koden og hoster den, må lenken peke til din versjon (SOURCE_URL i soapbox/build.py).

Veien videre (ikke i denne omgangen)

  • Poste lenker til fediverset/Bluesky og vise svar som kommentarer.
  • Push av innholdsrepoet til en ekstern git-remote.