- Astro 59.1%
- TypeScript 19.6%
- CSS 6.2%
- JavaScript 5.1%
- Shell 4.4%
- Other 5.5%
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. |
||
|---|---|---|
| .forgejo | ||
| .husky | ||
| docker | ||
| web | ||
| .dockerignore | ||
| .editorconfig | ||
| .gitignore | ||
| .nvmrc | ||
| bun.lock | ||
| compose.yaml | ||
| justfile | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
blogue
A personal Astro blog you can edit from a browser and deploy end to end from your own Forgejo.
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:
- You log in with your Forgejo account through OAuth (PKCE).
- Sveltia fetches collections defined in
admin/config.yml, whichscripts/prepare-admin.mjsrenders at build time from yourFORGEJO_URL,FORGEJO_REPO, andSVELTIA_OAUTH_CLIENT_ID. - 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:
changes: diffsHEAD~1..HEADand outputs two booleans,code(anything underweb/,docker/, or.forgejo/workflows/) andcompose(rootcompose.yaml).check: ESLint + Prettier +astro check. Runs whencode == true.build: Bun build + Docker buildx + push togit.libresoftware.cloud/morgan/blogue:latestand:<short-sha>. Runs whencode == true.notify-komodo: POSTs to Komodo's procedure webhook. Runs whenbuildsucceeded, or when the only change wascompose.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:
- Edit a post (through Sveltia or directly in Git).
- Push to
main. - CI rebuilds the image and pushes the new
:latest(and a:<short-sha>tag). - 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 byscripts/prepare-admin.mjs.web/src/config/— site config in TOML, home config in YAML.docker/—Dockerfileandnginx.conffor the container.compose.yaml— production deploy..forgejo/workflows/build.yml— CI pipeline..husky/— git hooks.LICENSE— MIT, covers the project structure anddocker/.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.