From ab149c1ae4fe38048fc2d32edac6061e7f3b4638 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Tue, 11 Aug 2026 20:34:02 -0500 Subject: Finish the move off weblorg Remove what the migration left behind, then cut build.py down to the work orgo does not do: 443 lines to 168. - Delete publish.el and theme/templates/, superseded by content/orgo.toml and content/templates/. theme/static/ stays; orgo publishes it as assets. - Drop the tag builder, the org-source copy, and the helpers that only fed them. orgo generates /tags/ and a page per tag from a collection. - Rewrite the README and the migration-era comments that explained the config by pointing at what weblorg used to do. - Serve styles.css as authored. Minifying 1KB of CSS saved 52 bytes over the wire once compressed, which did not pay for a build step and a toolchain dependency. - Stop wiping the output directory. The .orgo-cache.json inside it is what makes a build incremental, and orgo already prunes outputs whose source is gone and re-emits any that are missing. Production keeps .build/ and development gets .build-dev/, because production rewrites image URLs in the output and a shared directory would let one environment reuse pages rendered for the other. An incremental build is byte-identical to a clean one, and the site output is unchanged apart from the /uses/ stack row. --- .build.yml.bkp | 44 ----- .gitignore | 2 +- README.md | 100 ++++++----- build.py | 385 ++++++------------------------------------ content/orgo.toml | 16 +- content/templates/base.html | 12 +- content/templates/blog.html | 4 +- content/templates/feed.xml | 9 +- content/templates/garden.html | 2 +- content/templates/home.html | 5 +- content/templates/tags.html | 9 +- content/uses/index.org | 2 +- publish.el | 128 -------------- theme/templates/base.html | 46 ----- theme/templates/blog.html | 13 -- theme/templates/feed.xml | 37 ---- theme/templates/garden.html | 27 --- theme/templates/index.html | 33 ---- theme/templates/page.html | 8 - theme/templates/post.html | 19 --- theme/templates/tags.html | 6 - 21 files changed, 135 insertions(+), 772 deletions(-) delete mode 100644 .build.yml.bkp delete mode 100644 publish.el delete mode 100644 theme/templates/base.html delete mode 100644 theme/templates/blog.html delete mode 100644 theme/templates/feed.xml delete mode 100644 theme/templates/garden.html delete mode 100644 theme/templates/index.html delete mode 100644 theme/templates/page.html delete mode 100644 theme/templates/post.html delete mode 100644 theme/templates/tags.html diff --git a/.build.yml.bkp b/.build.yml.bkp deleted file mode 100644 index 26193db..0000000 --- a/.build.yml.bkp +++ /dev/null @@ -1,44 +0,0 @@ -image: ubuntu/lts - -packages: - - emacs-nox - - rsync - - openssh-client - - git - - curl - - tar - -sources: - - https://git.sr.ht/~ccleberg/cleberg.net - -secrets: - - 303fbd30-2970-4d18-b88b-0f6abf822ea5 - - e8b41b71-c4b2-4bd6-8159-25ae1eb7d282 - -environment: - ENV: prod - BUILD: "true" - -tasks: - - setup: | - curl -LsSf https://astral.sh/uv/install.sh | sh - mkdir -p ~/.local/bin - curl -sSfL https://github.com/tdewolff/minify/releases/latest/download/minify_linux_amd64.tar.gz \ - | tar -xz -C ~/.local/bin minify - mkdir -p ~/.config/emacs/.local/straight/repos - git clone --depth 1 https://github.com/emacsorphanage/htmlize \ - ~/.config/emacs/.local/straight/repos/htmlize - git clone --depth 1 https://github.com/emacs-love/weblorg \ - ~/.config/emacs/.local/straight/repos/weblorg - git clone --depth 1 https://github.com/emacs-love/templatel \ - ~/.config/emacs/.local/straight/repos/templatel - - build: | - source ~/.local/bin/env - cd cleberg.net - uv run python3 -u build.py - - deploy: | - set -euo pipefail - cd cleberg.net - chmod 600 ~/.ssh/id_rsa - chmod 600 ~/.ssh/config - rsync -r --delete-before .build/ homelab:/var/www/cleberg.net/ diff --git a/.gitignore b/.gitignore index 3272079..5e1ce88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .build/ +.build-dev/ .DS_Store .gitlab-ci-local/ .ruff_cache/ @@ -6,5 +7,4 @@ public/ readme.html venv/ -theme/static/styles.min.css __pycache__/ diff --git a/README.md b/README.md index abd8fa4..4b1df21 100644 --- a/README.md +++ b/README.md @@ -3,23 +3,26 @@ This repository holds the files for [cleberg.net](https://cleberg.net), a static-site with blog posts, personal links, and more. -This site uses [weblorg](https://github.com/emacs-love/weblorg) to build -the static site, which relies on -[emacs](https://www.gnu.org/software/emacs/). +This site uses [orgo](https://github.com/krazywarez/orgo) to build the +static site. ## Site Structure I write content pages (e.g., blog posts) in org-mode and templates in -HTML. I use Weblorg to build these files into a static site, and then -deploy them to a web server. +HTML. orgo builds these files into a static site, and then I deploy +them to a web server. The main site components are: -- Org source files containing content, including blog posts and pages. -- A configuration file (`publish.el`) that specifies publishing - parameters like base URL, output directories, and export options. +- Org source files in `content/`, containing blog posts and pages. + That directory is the site's URL root: `content/blog/post.org` + publishes at `/blog/post.html`. +- A configuration file (`content/orgo.toml`) that specifies the base + URL, navigation, templates, and the generated pages (home, blog and + garden indexes, tags, and the RSS feed). +- HTML templates in `content/templates/`. - Assets such as images and style sheets, located in designated - subdirectories. + subdirectories. `theme/static/` is published at `/`. - Utility scripts (e.g., `build.py`) to facilitate building and deployment. @@ -27,24 +30,25 @@ The main site components are: The publishing system depends on: -- Emacs text editor with Org-Mode. -- The weblorg package, available at - [emacs-love/weblorg](https://github.com/emacs-love/weblorg), which - provides advanced Org publishing functionality and theming support. +- [orgo](https://github.com/krazywarez/orgo), the static site + generator, installed with `cargo install --git + https://github.com/krazywarez/orgo`. +- `rsync` for deployment. +- [uv](https://github.com/astral-sh/uv) to run `build.py`. ## Configuration -You can customize site settings can within the `publish.el` file. This -file establishes key variables such as: +You can customize site settings within the `content/orgo.toml` file. +This file establishes key variables such as: - The base URL for links. -- Output directories. -- Publishing rules to define which files to convert and how. -- Theme settings managed by weblorg. +- Which pages appear in the navigation, and in what order. +- Per-directory template rules. +- The collections that generate the indexes, tag pages, and feed. Users intending to modify site parameters should review and edit this -file accordingly. The weblorg documentation contains extensive details -on configuration options and expected formats. +file accordingly. The orgo documentation contains extensive details on +configuration options and expected formats. ## Setup Instructions @@ -62,48 +66,54 @@ For users employing Doom Emacs, open any repository Org file using ## Building and Publishing the Site -The publishing process involves invoking Emacs with the `publish.el` -script, which performs the export of Org documents to HTML output. +The `build.py` script wraps the build: it runs orgo, and then either +deploys the result or serves it locally. -Configure the environment variable `ENV` as follows: +Two environment variables control what it does, and both default to +off: -- If you set `ENV` to `prod`, the script uses production base URL - settings as defined in `publish.el`. -- If you do not set `ENV` or set it differently, the script defaults to - development settings, typically using `localhost:8000` as the base - URL. +- `BUILD=true` performs the build. +- `DEPLOY=true` deploys in production, or starts a development server + on port 8000 otherwise. -Example commands to build the site: +A third, `ENV`, selects the mode. Setting `ENV=prod` rewrites image +URLs to be root-relative (see Deployment below) and skips the `ruff` +pass; anything else builds for development. ``` shell # Production build: -ENV=prod emacs --script publish.el +ENV=prod BUILD=true uv run build.py # Development build: -emacs --script publish.el +BUILD=true uv run build.py + +# Development build, then serve it on localhost:8000: +BUILD=true DEPLOY=true uv run build.py ``` -Generated site files reside in the designated output directory, ready -for deployment. You can deploy the resulting static site files via -standard file transfer protocols such as `scp` or SFTP. +Generated site files reside in `.build/` for production and +`.build-dev/` for development, ready for deployment. You can deploy the +resulting static site files via standard file transfer protocols such as +`scp` or SFTP. -The `build.py` script automates the build process. You can execute this -script with or without the `ENV` variable to perform production or -development builds respectively. +Builds are incremental: orgo keeps an `.orgo-cache.json` inside the +output directory and re-renders only what changed, so the directory is +left in place between runs. Delete it for a clean build. The two +environments use separate directories because production rewrites image +URLs in the output and development does not. -``` shell -# Production build script: -ENV=prod uv run build.py +orgo can also build and preview on its own, without `build.py`: -# Development build script: -uv run build.py +``` shell +orgo serve content -o /tmp/preview ``` ## Deployment -Production builds emit root-relative URLs (`/styles.min.css`, -`/img/blog/...`) instead of absolute `https://` ones so the site renders -standalone on the onion service without fetching assets off-onion. +Production builds rewrite image URLs to root-relative ones +(`/img/blog/...`) instead of absolute `https://img.cleberg.net/` ones so +the site renders standalone on the onion service without fetching assets +off-onion. The stylesheet is already same-origin. This means the web server must serve the image store at `/img/` on the `cleberg.net` vhost. The images live at `/var/www/img/` (their own host, diff --git a/build.py b/build.py index 0d90e2b..61f6110 100755 --- a/build.py +++ b/build.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 """ -This script automates the process of building, testing, and deploying the website. +This script automates the process of building and deploying the website. It handles tasks such as: -- Removing and recreating the build directory. -- Minifying CSS assets. -- Running the Emacs publishing script to generate site content. -- Updating the index.html file with the latest blog posts. +- Running orgo to generate site content. +- Rewriting image URLs for the onion service (production only). - Optionally deploying the built site to a remote server. - Starting a local development server for previewing changes. Usage: - Set the environment variable ENV to 'prod' for production builds. - Run the script to perform the build process accordingly. + Set BUILD=true to build, DEPLOY=true to deploy or serve. + Set ENV=prod for production builds; anything else builds for development. + + Builds are incremental. Production writes .build/ and development .build-dev/; + delete either one for a clean build. Dependencies: - Python 3 - - Emacs with the publish.el script - - minify tool for CSS minification + - orgo, the static site generator (reads content/orgo.toml) - rsync for deployment Author: @@ -26,202 +26,24 @@ Author: import os import re -import shutil import subprocess import sys -from datetime import datetime -from html import escape from pathlib import Path -SITE_TEMPLATE_VARS = { - "site_name": "cleberg.net", - "site_owner": "Christian Cleberg ", - "site_description": "Stillness amidst the chaos.", -} + +def run(cmd, error): + """Run cmd quietly, exiting with its stderr if it fails.""" + result = subprocess.run(cmd, capture_output=True, text=True, check=False) + if result.returncode != 0: + print(error, file=sys.stderr) + print(result.stderr, file=sys.stderr) + sys.exit(1) def run_ruff(): print("Running ruff...") for cmd in [["ruff", "check", "--fix"], ["ruff", "format"]]: - result = subprocess.run(cmd, capture_output=True, text=True, check=False) - if result.returncode != 0: - print(f"ruff error ({' '.join(cmd)}):") - print(result.stderr, file=sys.stderr) - sys.exit(1) - - -def render_base_template(main_html, subtitle="", title=None): - """ - Render a small subset of the site's shared templates for Python-generated - pages that still need to follow the common chrome. - """ - base_template = Path("theme/templates/base.html").read_text(encoding="utf-8") - - if title is None: - title = SITE_TEMPLATE_VARS["site_name"] - - rendered = base_template - rendered = rendered.replace( - "{% block subtitle %}{% endblock %}", - escape(subtitle), - ) - rendered = rendered.replace( - '{% block title %}{{ site_name | default("cleberg.net") }}{% endblock %}', - escape(title), - ) - rendered = rendered.replace( - '{% if site_owner is defined %}{% endif %}', - f'', - ) - rendered = rendered.replace( - '{% if site_description is defined %}{% endif %}', - f'', - ) - rendered = rendered.replace( - '{% if site_keywords is defined %}{% endif %}', - "", - ) - rendered = rendered.replace("{% block meta %}{% endblock %}", "") - rendered = rendered.replace("{% block head %}", "") - rendered = rendered.replace("{% endblock %}", "", 1) - rendered = rendered.replace("{% block main %}{% endblock %}", main_html) - - return rendered - - -def render_tags_page_html(tags_html): - """ - Render the tags page by using the shared tags/base templates instead of a - hand-authored standalone HTML document. - """ - tags_template = Path("theme/templates/tags.html").read_text(encoding="utf-8") - - main_html = tags_template - main_html = main_html.replace('{% extends "base.html" %}', "") - main_html = main_html.replace( - "{% block subtitle %}tags - {% endblock %}", - "", - ) - main_html = main_html.replace("{% block main %}", "") - main_html = main_html.replace("{% endblock %}", "") - main_html = main_html.replace("\n", tags_html) - - return render_base_template(main_html.strip(), subtitle="tags - ") - - -def get_blog_posts(content_dir="./content/blog"): - """ - Scan blog posts and return normalized metadata for non-draft entries. - """ - posts = [] - - header_patterns = { - "title": re.compile(r"^#\+title:\s*(.+)$", re.IGNORECASE), - "date": re.compile(r"^#\+date:\s*[\[<](\d{4}-\d{2}-\d{2})"), - "slug": re.compile(r"^#\+slug:\s*(.+)$", re.IGNORECASE), - "tags": re.compile(r"^#\+filetags:\s*(.+)$", re.IGNORECASE), - "draft": re.compile(r"^#\+draft:\s*(.+)$", re.IGNORECASE), - } - - for org_path in Path(content_dir).glob("*.org"): - title = None - date_str = None - slug = None - tags = [] - is_draft = False - - with org_path.open("r", encoding="utf-8") as f: - for line in f: - if title is None: - m = header_patterns["title"].match(line) - if m: - title = m.group(1).strip() - continue - - if date_str is None: - m = header_patterns["date"].match(line) - if m: - # date_str is just YYYY-MM-DD - date_str = m.group(1) - continue - - if slug is None: - m = header_patterns["slug"].match(line) - if m: - slug = m.group(1).strip() - continue - - if not tags: - m = header_patterns["tags"].match(line) - if m: - raw = m.group(1).strip().strip(":") - tags = [t.strip() for t in raw.split(":") if t.strip()] - continue - - m = header_patterns["draft"].match(line) - if m: - draft_value = m.group(1).strip().lower() - if draft_value != "nil": - is_draft = True - break - continue - - # Stop scanning once we have all required fields - if title and date_str and slug: - break - - if is_draft: - continue - - if title and date_str and slug: - try: - date_obj = datetime.strptime(date_str, "%Y-%m-%d") - date_full = date_obj.strftime("%Y-%m-%d") - except ValueError: - # Skip files with invalid date format - continue - - posts.append( - { - "title": title, - "date_str": date_str, - "date_obj": date_obj, - "date_full": date_full, - "slug": slug, - "tags": tags, - } - ) - - posts.sort(key=lambda x: x["date_obj"], reverse=True) - return posts - - -def prompt(prompt_text): - try: - return input(prompt_text).strip() - except EOFError: - return "" - - -def remove_build_directory(build_dir): - if build_dir.exists(): - print(f"Removing previous build directory: {build_dir}/") - shutil.rmtree(build_dir) - build_dir.mkdir(parents=True, exist_ok=True) - - -def minify_css(src_css, dest_css): - print(f"Minifying CSS: {src_css} → {dest_css}") - result = subprocess.run( - ["minify", "-o", str(dest_css), str(src_css)], - capture_output=True, - text=True, - check=False, - ) - if result.returncode != 0: - print("Error during CSS minification:") - print(result.stderr, file=sys.stderr) - sys.exit(1) + run(cmd, f"ruff error ({' '.join(cmd)}):") def rewrite_img_urls(build_dir=".build"): @@ -249,22 +71,26 @@ def rewrite_img_urls(build_dir=".build"): print(f"Rewrote {count} img.cleberg.net references to /img/") -def run_orgo_build(dev_mode=True): +def run_orgo_build(build_dir): """ Build the site with orgo. - Replaces the weblorg/Emacs publish. orgo reads content/orgo.toml, so the routes, - templates and collections that used to live in publish.el live there now. Four of the - steps this script used to perform afterwards are gone with it: the blog index groups - itself by year, the tags page is a collection, the recent-posts list is generated from - the blog collection, and the sitemap is written by orgo once base_url is set. + orgo reads content/orgo.toml, which holds the routes, templates and collections: the + blog index groups itself by year, the tags page is a collection, the recent-posts + list comes from the blog collection, and the sitemap is written once base_url is set. + + The output directory is left in place between builds rather than wiped, because the + .orgo-cache.json inside it is what makes a build incremental: orgo re-renders only + the pages whose content, config or templates changed, re-emits any page whose output + is missing, and deletes the outputs of pages that have since been removed. Wiping the + directory would throw that away and force a full render every time. For a clean build + from scratch, delete the output directory by hand. - What is left around it is what orgo does not do: minifying CSS, copying the org - sources for readers who want them, and rewriting image URLs for the onion. + What is left around it is what orgo does not do: rewriting image URLs for the onion. """ print("Building with orgo...") result = subprocess.run( - ["orgo", "build", "content", "-o", ".build", "--strict"], + ["orgo", "build", "content", "-o", str(build_dir), "--strict"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, @@ -276,99 +102,10 @@ def run_orgo_build(dev_mode=True): sys.exit(1) -def copy_org_sources(content_dir="./content", build_dir="./.build/org"): - print(f"Copying org sources: {content_dir} → {build_dir}") - if os.path.exists(build_dir): - shutil.rmtree(build_dir) - - slug_pattern = re.compile(r"^#\+slug:\s*(.+)$", re.IGNORECASE) - - for src_path in Path(content_dir).rglob("*.org"): - rel_dir = src_path.parent.relative_to(content_dir) - dest_dir = Path(build_dir) / rel_dir - dest_dir.mkdir(parents=True, exist_ok=True) - - # Try to extract slug from file headers - slug = None - with src_path.open("r", encoding="utf-8") as f: - for line in f: - m = slug_pattern.match(line) - if m: - slug = m.group(1).strip() - break - - dest_name = f"{slug}.org" if slug else src_path.name - shutil.copy2(src_path, dest_dir / dest_name) - if slug: - print(f" {src_path.name} → {dest_name}") - - -def get_tags_html(content_dir="./content/blog"): - """ - Build the tag index HTML snippet for the rendered tags template. - """ - preferred_tag_order = [ - "audit", - "emacs", - "development", - "ios", - "linux", - "personal", - "privacy", - "security", - "self-hosting", - "web", - ] - - tag_map = {} - - for post in get_blog_posts(content_dir): - for tag in post["tags"]: - tag_map.setdefault(tag, []).append( - { - "title": post["title"], - "slug": post["slug"], - "date_obj": post["date_obj"], - "date_str": post["date_str"], - } - ) - - ordered_tags = [tag for tag in preferred_tag_order if tag in tag_map] - ordered_tags.extend( - sorted(tag for tag in tag_map if tag not in preferred_tag_order) - ) - - for tag in ordered_tags: - tag_map[tag].sort(key=lambda x: x["date_obj"], reverse=True) - - toc_items = "".join( - f'
  • {tag} ({len(tag_map[tag])})
  • ' - for tag in ordered_tags - ) - - sections = [] - for tag in ordered_tags: - posts = tag_map[tag] - items = "\n".join( - f'
  • ' - f'' - f'{p["title"]}' - f"
  • " - for p in posts - ) - sections.append( - f'

    {tag}

    \n' - ) - - return f'\n' + "".join( - f"
    {section}
    " for section in sections - ) - - def deploy_to_server(build_dir, server): remote_path = f"{server}:/var/www/cleberg.net/" - print(f"Deploying .build/ → {remote_path}") - result = subprocess.run( + print(f"Deploying {build_dir}/ → {remote_path}") + run( # The build cache lives in the output directory because it describes it, but it # is not part of the site. Excluding it also stops --delete removing it locally. [ @@ -380,14 +117,8 @@ def deploy_to_server(build_dir, server): f"{build_dir}/", remote_path, ], - capture_output=True, - text=True, - check=False, + "Error during rsync deployment:", ) - if result.returncode != 0: - print("Error during rsync deployment:") - print(result.stderr, file=sys.stderr) - sys.exit(1) def start_dev_server(build_dir): @@ -404,38 +135,32 @@ def start_dev_server(build_dir): def main(): - env = os.environ.get("ENV", "").casefold() - if env != "prod": + prod = os.environ.get("ENV", "").casefold() == "prod" + if not prod: run_ruff() - build_dir = Path(".build") - theme_dir = Path("theme/static") - css_src = theme_dir / "styles.css" - css_min = theme_dir / "styles.min.css" - - build = os.environ.get("BUILD", "").casefold() == "true" - deploy = os.environ.get("DEPLOY", "").casefold() == "true" - - if env == "prod": - print("Environment: Production") - if build: - remove_build_directory(build_dir) - minify_css(css_src, css_min) - run_orgo_build(dev_mode=False) - copy_org_sources() + # One output directory per environment, because the two differ after orgo has run: + # production rewrites image URLs in place and development does not. Sharing a + # directory would let an incremental build reuse a page rendered for the other one — + # a dev preview showing /img/ paths that only resolve on the server. Production keeps + # .build/ because that is the directory the deploy and the CI manifest name. + build_dir = Path(".build" if prod else ".build-dev") + + print(f"Environment: {'Production' if prod else 'Development'}") + + if os.environ.get("BUILD", "").casefold() == "true": + run_orgo_build(build_dir) + # The onion needs same-origin images; dev previews keep the absolute URLs. + # Runs over every page, not just the re-rendered ones, so a page carried over + # from an earlier build is rewritten too. + if prod: rewrite_img_urls(build_dir) - if deploy: + + if os.environ.get("DEPLOY", "").casefold() == "true": + if prod: print("Deploying to production...") deploy_to_server(build_dir, "homelab") - return - else: - print("Environment: Development") - if build: - remove_build_directory(build_dir) - minify_css(css_src, css_min) - run_orgo_build(dev_mode=True) - copy_org_sources() - if deploy: + else: start_dev_server(build_dir) diff --git a/content/orgo.toml b/content/orgo.toml index 307df57..c78b365 100644 --- a/content/orgo.toml +++ b/content/orgo.toml @@ -5,9 +5,6 @@ # # orgo serve content -o /tmp/preview # write and preview # orgo build content -o _site --strict # build for real, failing on broken links -# -# This sits alongside the existing weblorg setup (publish.el, theme/) and changes nothing -# about it. Delete this file and content/templates/ to remove orgo entirely. [site] title = "cleberg.net" @@ -21,8 +18,7 @@ base_url = "https://cleberg.net" [nav] # Explicit, because the pages that belong in the nav are not all top-level: /blog/ and # /garden/ are generated below, and salary/ is a section index one level down. index.org -# is deliberately absent — the layout writes the Home link itself, exactly as weblorg's -# base.html does. +# is deliberately absent — the layout writes the Home link itself. # # Generated pages are named by their output path, authored ones by their source. Listing # them together is what puts Salary last: a collection with `nav = true` that is not @@ -32,15 +28,13 @@ mode = "explicit" pages = ["blog/index.html", "garden/index.html", "salary/index.org"] [build] -# theme/static/ is weblorg's static root, published at /. orgo reads it from here +# theme/static/ is the site's static root, published at /. orgo reads it from here # rather than needing its files copied next to the writing — one copy, not two. assets = ["../theme/static"] [templates] dir = "templates" -# Blog posts render through post.html — the shared layout plus a reply footer — the way -# weblorg routes them through theme/templates/post.html. Everything else uses base.html. # Blog posts render through post.html, which adds the reply snippet. Nothing else does: # the live site also invites replies on garden notes and /now/, and that is a choice # rather than something a directory should decide. Any page opts in for itself with @@ -63,13 +57,11 @@ toc = true section_numbers = true # --- Generated pages ------------------------------------------------------------- -# These have no source .org file. weblorg produces the equivalents today through its -# blog-index, garden-index and tags routes. +# These have no source .org file. # The home page. Generated rather than authored, because it is a hand-written # introduction *plus* the most recent posts — and the post list is not something to -# maintain by hand. The prose lives in templates/home.html, which is where weblorg keeps -# it today too. +# maintain by hand. The prose lives in templates/home.html. # # content/index.org was deleted to make room: it held only #+title and #+description, # both of which are now [site] settings above. Restore it any time with diff --git a/content/templates/base.html b/content/templates/base.html index e35098c..237c398 100644 --- a/content/templates/base.html +++ b/content/templates/base.html @@ -1,9 +1,9 @@ -{# Page layout for orgo, mirroring theme/templates/base.html. +{# Page layout for the site. Styling comes from theme/static/styles.css, published by `[build] assets` in - orgo.toml — the same directory weblorg serves at /, read in place rather than - copied. weblorg links the minified styles.min.css instead; that file is gitignored - build output and the rules are the same, so this links the tracked one. + orgo.toml and read in place rather than copied. It is served as authored: at ~1KB, + minifying it saved 52 bytes over the wire once compressed, which did not pay for a + build step and a toolchain dependency. Available here: page (.title .url .source .date .date_iso .tags .excerpt .toc .keywords), site, nav, root, stylesheet. See the orgo guide on Templates. #} @@ -45,8 +45,8 @@ -{#- rel="me" identity links. weblorg's base.html writes the last one as , which a - browser hoists out of ; is the spelling that stays where it is put. #} +{#- rel="me" identity links, spelled as rather than : a browser hoists a + stray out of , while stays where it is put. #} diff --git a/content/templates/blog.html b/content/templates/blog.html index 17f8870..ecb5c5d 100644 --- a/content/templates/blog.html +++ b/content/templates/blog.html @@ -1,5 +1,5 @@ -{# The blog index at /blog/, mirroring theme/templates/blog.html. - Separate from list.html because the live page carries its own prose. #} +{# The blog index at /blog/. + Separate from list.html because the page carries its own prose. #} {% extends "base.html" %} {#- Group the list under year headings, as the live site does. Set to false for one flat diff --git a/content/templates/feed.xml b/content/templates/feed.xml index fa761b8..7596b02 100644 --- a/content/templates/feed.xml +++ b/content/templates/feed.xml @@ -1,10 +1,9 @@ -{#- The RSS feed at /feed.xml, mirroring theme/templates/feed.xml. A feed is read away - from the site that served it, so every URL here is absolute — which is what - `| absolute` needs [site] base_url for. +{#- The RSS feed at /feed.xml. A feed is read away from the site that served it, so + every URL here is absolute — which is what `| absolute` needs [site] base_url for. - carries each post's full rendered HTML, as weblorg's feed does — the - collection sets `include_content` to ask for it. #} + carries each post's full rendered HTML — the collection sets + `include_content` to ask for it. #} : the home page is - "cleberg.net" and not "cleberg.net - cleberg.net". #} + entries, newest first, and the prose lives here. The is bare on purpose: the + home page is "cleberg.net" and not "cleberg.net - cleberg.net". #} {% extends "base.html" %} {% block subtitle %}{% endblock %} {% block main %} diff --git a/content/templates/tags.html b/content/templates/tags.html index 5240b27..9b3a1e2 100644 --- a/content/templates/tags.html +++ b/content/templates/tags.html @@ -1,11 +1,10 @@ {# The tag index at /tags/. Receives `groups` — every tag with its count — rather than `pages`, because it lists tags and not posts. - This deliberately differs in shape from the live weblorg page, which is one page with - every post inlined under an `#anchor` per tag. Here each tag is its own page, so a tag - has a URL worth linking to and the index stays short. The `id` on each entry keeps the - old `/tags/#audit` links landing in the right place — they scroll to the tag and its - link, rather than 404ing or dumping the reader at the top of a long page. #} + Each tag is its own page, so a tag has a URL worth linking to and the index stays + short. The `id` on each entry keeps older `/tags/#audit` links landing in the right + place — they scroll to the tag and its link, rather than dumping the reader at the + top of the index. #} {% extends "base.html" %} {% block main %} <h1>{{ page.title }}</h1> diff --git a/content/uses/index.org b/content/uses/index.org index d7f5db9..051a6eb 100644 --- a/content/uses/index.org +++ b/content/uses/index.org @@ -24,7 +24,7 @@ threat model justification. | Operating System | [[https://ubuntu.com][Ubuntu]] + [[https://www.apple.com/os/macos/][macOS]] | Ubuntu: telemetry off, LTS, stable. macOS: workstation only. | | Web Server | [[https://nginx.org][Nginx]] + [[https://community.torproject.org/onion-services/setup/][Tor]] | Static file serving. Tor layer for censored-network access. | | SSL | [[https://certbot.eff.org][Certbot]] | Free automated TLS. No commercial CA dependency. Ensures Cloudflare can't view unencrypted traffic. | -| Static Site Generator | [[https://github.com/emacs-love/weblorg][Weblorg]] + [[https://git.sr.ht/~ccleberg/cleberg.net/tree/main/item/build.py][build.py]] | Org-mode source compiles to plaintext HTML via native Lisp. | +| Static Site Generator | [[https://github.com/krazywarez/orgo][orgo]] + [[https://git.sr.ht/~ccleberg/cleberg.net/tree/main/item/build.py][build.py]] | Org-mode source compiles to plaintext HTML. Single binary, no runtime. | | Terminal | [[https://iterm2.com/][iTerm2]] | Functional. Inherited from macOS. | | Shell | [[https://www.zsh.org/][Zsh]] | Portable, POSIX-adjacent, available on every target OS. | | Editor | [[https://github.com/doomemacs/doomemacs][Doom Emacs]] | Editor and markup are the same tool. No proprietary format. | diff --git a/publish.el b/publish.el deleted file mode 100644 index aecdb0f..0000000 --- a/publish.el +++ /dev/null @@ -1,128 +0,0 @@ -;;; -*- lexical-binding: t -*- -(setq debug-on-error t) - -;; Allow for macOS (dev machine) & Linux (GitHub Actions) execution -(defvar site-lisp-base - (expand-file-name "~/.config/emacs/.local/straight/repos")) - -;; Explicitly load packages -(add-to-list 'load-path (expand-file-name "htmlize" site-lisp-base)) -(add-to-list 'load-path (expand-file-name "weblorg" site-lisp-base)) -(add-to-list 'load-path (expand-file-name "templatel" site-lisp-base)) - -(require 'htmlize) -(require 'templatel) -(require 'weblorg) - -;; Set default URL for Weblorg -;; Only works if environment variable ENV=prod -(if (string-equal-ignore-case (or (getenv "ENV") "") "prod") - (setq weblorg-default-url "https://cleberg.net")) - -;; Define site metadata -(weblorg-site - :theme nil - :template-vars '(("site_name" . "cleberg.net") - ("site_owner" . "Christian Cleberg <hello@cleberg.net>") - ("site_description" . "Stillness amidst the chaos."))) - -;; Define routes for rendering content - -;; Index page route -(weblorg-route - :name "index" - :input-pattern "content/*.org" - :template "index.html" - :output ".build/index.html" - :url "/") - -;; Blog post route -(weblorg-route - :name "blog" - :input-pattern "content/blog/*.org" - :template "post.html" - :output ".build/blog/{{ slug }}.html" - :url "/blog/{{ slug }}.html") - -;; Blog index page route -(weblorg-route - :name "blog-index" - :input-pattern "content/blog/*.org" - :input-aggregate #'weblorg-input-aggregate-all-desc - :template "blog.html" - :output ".build/blog/index.html" - :url "/blog/") - -;; Garden post route -(weblorg-route - :name "garden" - :input-pattern "content/garden/*.org" - :template "post.html" - :output ".build/garden/{{ slug }}.html" - :url "/garden/{{ slug }}.html") - -;; Garden index page route -(weblorg-route - :name "garden-index" - :input-pattern "content/garden/*.org" - :input-aggregate #'weblorg-input-aggregate-all-desc - :template "garden.html" - :output ".build/garden/index.html" - :url "/garden/") - -;; Page post route -(weblorg-route - :name "pages" - :input-pattern "content/*.org" - :template "page.html" - :output ".build/{{ slug }}.html" - :url "/{{ slug }}.html") - -;; Salary page route -(weblorg-route - :name "salary" - :input-pattern "content/salary/*.org" - :template "page.html" - :output ".build/salary/{{ slug }}.html" - :url "/salary/{{ slug }}.html") - -;; Now page route -(weblorg-route - :name "now" - :input-pattern "content/now/*.org" - :template "post.html" - :output ".build/now/{{ slug }}.html" - :url "/now/{{ slug }}.html") - -;; Uses page route -(weblorg-route - :name "uses" - :input-pattern "content/uses/*.org" - :template "page.html" - :output ".build/uses/{{ slug }}.html" - :url "/uses/{{ slug }}.html") - -;; Tips page route -(weblorg-route - :name "tips" - :input-pattern "content/tips/*.org" - :template "page.html" - :output ".build/tips/{{ slug }}.html" - :url "/tips/{{ slug }}.html") - -;; RSS feed route -(weblorg-route - :name "rss" - :input-pattern "content/blog/*.org" - :input-aggregate #'weblorg-input-aggregate-all-desc - :template "feed.xml" - :output ".build/feed.xml" - :url "/feed.xml") - -;; Copy static assets and output to .build directory -(weblorg-copy-static - :output ".build/{{ file }}" - :url "/{{ file }}") - -;; Export all content using Weblorg engine -(weblorg-export) diff --git a/theme/templates/base.html b/theme/templates/base.html deleted file mode 100644 index 68ff487..0000000 --- a/theme/templates/base.html +++ /dev/null @@ -1,46 +0,0 @@ -<!doctype html> -<html lang="en-us"> -<head> - {% block head %} - <meta charset="utf-8"> - <title>{% block subtitle %}{% endblock %}{% block title %}{{ site_name | default("cleberg.net") }}{% endblock %} - - {% if site_owner is defined %}{% endif %} - {% if site_description is defined %}{% endif %} - {% if site_keywords is defined %}{% endif %} - - - - {% block meta %}{% endblock %} - {% endblock %} - - - - - - - - - - -
    - {% block main %}{% endblock %} -
    - - - - diff --git a/theme/templates/blog.html b/theme/templates/blog.html deleted file mode 100644 index b2e2b72..0000000 --- a/theme/templates/blog.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base.html" %} {% block subtitle %}blog - {% endblock %} {% block main %} -

    Blog

    -

    Use ⌘ F / Ctrl F to search · RSS Feed

    -

    Want to browse by topic instead? Head over to the tags page.

    - -{% endblock %} diff --git a/theme/templates/feed.xml b/theme/templates/feed.xml deleted file mode 100644 index 7b23994..0000000 --- a/theme/templates/feed.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - {{ site_name | default("cleberg.net") }} - - {{ url_for("index") }} - - en - {{ posts | first | getattr("date") | strftime("%a, %d %b %Y %H:%M:%S %z") }} - {{ now() | strftime("%a, %d %b %Y %H:%M:%S %z") }} - {{ meta.generator }} - {{ site_owner | default("hello@cleberg.net") }} - - {% for post in posts %} - - {{ post.title }} - {{ url_for("blog", slug=post.slug) }} - {{ post.author|default("hello@cleberg.net") }} - {{ url_for("blog", slug=post.slug) }} - {{ post.date|strftime("%a, %d %b %Y %H:%M:%S %z") }} - - - {% endfor %} - - - diff --git a/theme/templates/garden.html b/theme/templates/garden.html deleted file mode 100644 index a14d50c..0000000 --- a/theme/templates/garden.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "base.html" %} {% block subtitle %}garden - {% endblock %} {% block main %} -

    Garden

    -

    Notes in various states of growth. Some are stubs, some are developed. Browse -around.

    -
    -

    🪴 The Growth Stages

    -

    Instead of dates, these metadata labels show how mature a page is:

    - 🌱 Seedlings: Rough notes, raw ideas, or "half-baked" - thoughts.
    - 🌿 Budding: Notes that have some structure and a few - internal links.
    - 🌳 Evergreen: High-conviction ideas that are - well-researched and stable.
    - 🍂 Compost: Ideas you no longer agree with but want to - keep for historical context.

    -
    - -{% endblock %} diff --git a/theme/templates/index.html b/theme/templates/index.html deleted file mode 100644 index 75fc83d..0000000 --- a/theme/templates/index.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "base.html" %} -{% block meta %}{% endblock %} -{% block main %} -
    -

    Christian Cleberg

    -

    [ krz · audit labs ]

    -

    Privacy · Self-Hosting · iOS

    -

    I build tools, write about systems, and run my own infrastructure. Focused on privacy, user control, and long-term utility.

    -

    Uses · Now

    -
    - -
    -

    Recent Posts

    -

    Things I've written recently.

    -
      - -
    -

    All Posts →

    -
    - -
    -

    Elsewhere

    -

    Places to find me online.

    - -
    -{% endblock %} diff --git a/theme/templates/page.html b/theme/templates/page.html deleted file mode 100644 index 6c22589..0000000 --- a/theme/templates/page.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% block subtitle %}{{ post.title }} - {% endblock %} -{% block main %} -
    -

    {{ post.title }}

    -
    {{ post.html | safe }}
    -
    -{% endblock %} diff --git a/theme/templates/post.html b/theme/templates/post.html deleted file mode 100644 index a2951b0..0000000 --- a/theme/templates/post.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} {% block subtitle %} {{ post.title }} - {% endblock %} -{% block meta %} - - -{% if post.description is defined %} - -{% elif project_description is defined %} - -{% endif %} -{% endblock %} -{% block main %} -
    -

    {{ post.title }}

    -

    {% if post.description is defined %} · {{ post.description }}{% endif %}

    -
    {{ post.html | safe }}
    -
    Reply via email →
    -
    ...or, comment on this post on Bubbles!
    -
    -{% endblock %} diff --git a/theme/templates/tags.html b/theme/templates/tags.html deleted file mode 100644 index 83619d4..0000000 --- a/theme/templates/tags.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base.html" %} {% block subtitle %}tags - {% endblock %} {% block main %} -

    Tags

    -

    Browse posts by topic.

    - - -{% endblock %} -- cgit v1.2.3