A personal Astro blog you can edit from a browser and deploy end to end from your own Forgejo. https://blog.libresoftware.cloud
  • Astro 59.1%
  • TypeScript 19.6%
  • CSS 6.2%
  • JavaScript 5.1%
  • Shell 4.4%
  • Other 5.5%
Find a file
morgan f2a2d6f8fc
All checks were successful
Build and push / changes (push) Successful in 15s
Build and push / check (push) Successful in 1m11s
Build and push / build (push) Successful in 1m27s
Build and push / notify-komodo (push) Successful in 2s
Tighten Erugo article and rework the home intro
Split the Erugo compose into a minimal demo (host port 8080) and the
hardened version I actually run, so readers can take whichever they
need. Drop the "What I tried before" beat and replace it with an
"Alternatives worth a look" section at the end naming Picoshare, Plik,
PsiTransfer, Palmr and Papra, each with a one-line characterization.

Rework the home intro to lead with concrete shutdowns (Google Reader,
Stadia, Inbox, Bandcamp Daily) instead of a fabricated personal
anecdote about an online service I never actually lost files to. The
list does the same work for the reader and is honest.
2026-06-11 22:35:10 +02:00
.forgejo Filter W3C nu CSS false positives and dump failing audit elements 2026-06-11 14:51:58 +02:00
.husky Scaffold Astro publication site from astro-navfolio template 2026-06-09 16:13:36 +02:00
docker Enrich RSS feed and add a JSON Feed alongside it 2026-06-10 23:42:35 +02:00
web Tighten Erugo article and rework the home intro 2026-06-11 22:35:10 +02:00
.dockerignore Scaffold Astro publication site from astro-navfolio template 2026-06-09 16:13:36 +02:00
.editorconfig Scaffold Astro publication site from astro-navfolio template 2026-06-09 16:13:36 +02:00
.gitignore Use Sveltia's IIFE bundle so auto-init actually runs 2026-06-10 19:45:31 +02:00
.nvmrc Scaffold Astro publication site from astro-navfolio template 2026-06-09 16:13:36 +02:00
bun.lock Scaffold Astro publication site from astro-navfolio template 2026-06-09 16:13:36 +02:00
compose.yaml feat: add compose file 2026-06-10 17:20:34 +02:00
justfile Self-host Sveltia's Google Fonts under /admin/ 2026-06-10 21:13:02 +02:00
LICENSE Initial commit 2026-06-09 15:15:10 +02:00
package.json Scaffold Astro publication site from astro-navfolio template 2026-06-09 16:13:36 +02:00
README.md Make metrics self-sufficient and clear the 167 HTML errors 2026-06-11 14:09:54 +02:00

blogue

A personal Astro blog you can edit from a browser and deploy end to end from your own Forgejo.

License: MIT Build Metrics refresh

Astro Sveltia CMS Docker Self-hosted

Lighthouse W3C HTML W3C Feed JSON Feed Mozilla Observatory Last verified


Sveltia handles the CMS over Git, Komodo handles the redeploy, and there is no third party in the loop. The site is a static build served by an unprivileged nginx container. Content lives in Git, edited through a Sveltia admin panel that talks to Forgejo directly over OAuth (PKCE). Pushes to main are picked up by CI, which builds the image, pushes it to a Forgejo container registry, then POSTs a webhook to a Komodo deploy controller. The reverse proxy and the deploy target are yours.

What it assumes

A few moving parts are taken as given:

  • A Forgejo instance you administer. It hosts the repo, runs the container registry, and acts as the OAuth provider for Sveltia.
  • A reverse proxy in front of the container (the image expects HTTPS termination upstream).
  • A Komodo (or equivalent gitops controller) that exposes a procedure webhook your CI can call.
  • An optional auth wall in front of Komodo. The CI step supports HTTP Basic auth out of the box for Pangolin.

Requirements

  • bun: runtime and package manager.
  • just: task runner (brew install just).
  • docker: only for the docker-* recipes.
  • Node ≥ 22.12.0 (see .nvmrc).

Install

just install

Installs husky at the root (activates the git hooks) and the Astro dependencies in web/.

Local environment

web/.env (gitignored) supplies four build-time variables. Copy the template and fill in real values when you plan to run local builds:

cp web/.env.example web/.env
Variable Used by Example
SITE_URL astro.config.mjs for canonical URLs, sitemap, RSS, JSON Feed https://blog.libresoftware.cloud
FORGEJO_URL scripts/prepare-admin.mjs (templates Sveltia's admin/config.yml) https://git.libresoftware.cloud
FORGEJO_REPO same morgan/blogue
SVELTIA_OAUTH_CLIENT_ID same; paste the Client ID from your Forgejo OAuth2 app <uuid>

just dev works without web/.env (Astro falls back to a placeholder site URL). just build, just docker-build, and just docker-run need the four variables; prepare-admin.mjs aborts with a clear message naming any missing one.

None of these are secret. The OAuth Client ID is intentionally public under PKCE: the real credential never leaves the user's browser.

Commands

Command Description
just list all recipes
just install install dependencies (root + web/)
just dev start the Astro dev server
just build build the static site for production
just preview preview the production build locally
just lint run ESLint
just format run Prettier (write)
just check ESLint + Prettier check (no writes)
just clean remove dist/, .astro/, node_modules/
just new-post "Title" scaffold a new blog post
just fonts-refresh re-vendor Sveltia's Google Fonts (only when prepare-admin reports drift)
just astro <args> passthrough to the Astro CLI
just docker-build build the Docker image
just docker-run run the container on localhost:8080
just help show the underlying bun commands

The git pre-commit hook runs lint-staged against staged files in web/.

Editing through Sveltia

Sveltia is the reason this site has a CMS without a database. The admin panel is a static page (/admin/) shipped inside the build. When you open it, the page talks to Forgejo directly over HTTPS:

  1. You log in with your Forgejo account through OAuth (PKCE).
  2. Sveltia fetches collections defined in admin/config.yml, which scripts/prepare-admin.mjs renders at build time from your FORGEJO_URL, FORGEJO_REPO, and SVELTIA_OAUTH_CLIENT_ID.
  3. When you save an entry, Sveltia commits the change directly through the Forgejo API. The site rebuilds on the next push, like any other commit on main.

No backend service to host. No database to back up. The repo is the database. If you ever decide to stop running this stack, the content is just files in Git.

CI: build, push, redeploy

The pipeline lives in .forgejo/workflows/build.yml and runs in four jobs:

  1. changes: diffs HEAD~1..HEAD and outputs two booleans, code (anything under web/, docker/, or .forgejo/workflows/) and compose (root compose.yaml).
  2. check: ESLint + Prettier + astro check. Runs when code == true.
  3. build: Bun build + Docker buildx + push to git.libresoftware.cloud/morgan/blogue:latest and :<short-sha>. Runs when code == true.
  4. notify-komodo: POSTs to Komodo's procedure webhook. Runs when build succeeded, or when the only change was compose.yaml.

CI fires the webhook, not Forgejo. The reason: Forgejo's own push webhook fires the moment the commit lands, which means it would reach Komodo before the new image exists, and Komodo would pull a stale :latest. Letting CI own the webhook means the redeploy signal aligns with "the new image is on the registry."

The compose-only path is the small but useful escape hatch. Editing compose.yaml (a resource limit, a healthcheck setting) reaches Komodo without rebuilding the image at all.

Variables and secrets

CI reads the same four build-time variables as the local web/.env, plus the deploy-time set. None of the variables are confidential; the secrets are.

Variables (Forgejo repo Variables)

Name Used by Sensitive
SITE_URL Astro build, canonical URLs No
FORGEJO_URL prepare-admin.mjs to template admin/config.yml No
FORGEJO_REPO same No
SVELTIA_OAUTH_CLIENT_ID PKCE OAuth in the browser No (PKCE)
KOMODO_WEBHOOK_URL notify-komodo POST target No

Secrets (Forgejo repo Secrets)

Name Used by Why it is a secret
REGISTRY_TOKEN docker/login-action to push the image Forgejo token with write:package
KOMODO_WEBHOOK_SECRET HMAC-SHA256 of the webhook body, sent as X-Hub-Signature-256 Komodo verifies the signature before accepting the redeploy
KOMODO_BASIC_AUTH curl -u user:pass for the Pangolin auth wall in front of Komodo Only needed if Komodo sits behind HTTP Basic auth

Deploy and update

Production runs the top-level compose.yaml on a host managed by Komodo. The container is unprivileged nginx serving the static site on :8080, with a read-only filesystem, dropped capabilities, and small tmpfs mounts for nginx's runtime needs. The reverse proxy in front maps :80/:443 to the container's :8080.

Update path:

  1. Edit a post (through Sveltia or directly in Git).
  2. Push to main.
  3. CI rebuilds the image and pushes the new :latest (and a :<short-sha> tag).
  4. CI calls Komodo, which pulls and redeploys.

To roll back, point the compose file at a previous :<short-sha> tag and let Komodo redeploy. Every CI build keeps the short-SHA tag alongside :latest.

Metrics and checks

Most quality checks live outside the build. CI catches code regressions; the validators below are queried by .forgejo/workflows/metrics.yml, which writes badge data to the metrics branch. The badges in the README header refresh from that branch on every workflow run (weekly, after every deploy, or on manual trigger).

In CI

ESLint, Prettier, astro check, and the full Astro + Docker build run on every push to main. Failures block the deploy.

External validators

The badges at the top of this README each pull from a small JSON file on the metrics branch. The sources behind them:

Check Source What it covers
Lighthouse / PageSpeed pagespeed.web.dev Performance, accessibility, SEO, best practices
W3C HTML validator validator.w3.org/nu/ Markup correctness (orphan tags, duplicate IDs)
RSS feed (local) xmllint + RSS 2.0 spec Well-formed XML, required channel elements, atom self-link
JSON Feed validator spec check on /feed.json required keys and version: jsonfeed.org/...
Mozilla Observatory observatory.mozilla.org/ TLS config and response headers (CSP, HSTS)

Discovery

Where the blog gets indexed and surfaced. None of these are required to publish; they tell you whether anyone outside an RSS reader is finding the posts.

Tool Source What it tracks
Google Search Console search.google.com/search-console Indexation status, search performance, sitemap submission for Google
Bing Webmaster Tools www.bing.com/webmasters/ Same, for Bing
RSS aggregators /rss.xml pasted into Feedly/Inoreader That the feed renders correctly in a real reader

Structure

  • web/ — Astro frontend (sources, build, configs).
  • web/admin-template/config.yml — Sveltia config template, rendered at build time by scripts/prepare-admin.mjs.
  • web/src/config/ — site config in TOML, home config in YAML.
  • docker/Dockerfile and nginx.conf for the container.
  • compose.yaml — production deploy.
  • .forgejo/workflows/build.yml — CI pipeline.
  • .husky/ — git hooks.
  • LICENSE — MIT, covers the project structure and docker/.
  • web/LICENSE — MIT, covers the upstream template code.

Fork and adjust

What this repo describes is my setup. Forgejo, Komodo, Pangolin, the registry, the nginx container, the path filters in CI: all of it is the stack that happened to fit how I like to run things. None of it is load-bearing. The frontend is just Astro and the content is just Markdown, so swap pieces freely. Use GitHub instead of Forgejo, swap Komodo for whatever gitops controller you prefer, drop the auth wall if Komodo is reachable directly, ship through a different proxy. The only piece I would keep is Sveltia, because losing the Git-backed CMS is what makes this a different project.

Credits

The repository structure, deploy pipeline, and docker/ setup are mine, licensed under MIT (see LICENSE).

The frontend is based on astro-navfolio by dodolalorc, also distributed under the MIT license. See web/LICENSE for the original copyright notice.