Skip to content

phalt/paulblish

Repository files navigation

Paulblish 📖

Python 3.14 License: MIT

A CLI tool that converts an Obsidian vault (or any directory of markdown files) into a static HTML site with a cyberpunk aesthetic, ready for deployment to GitHub Pages.

What is this?

Paulblish (pb) takes a directory of markdown files — such as an Obsidian vault — and generates a complete static HTML site from it. The generated output is committed directly to the repo and deployed via GitHub Pages. Your source vault lives on your machine; only the HTML output is in version control.

The project follows the file over app philosophy. Your content stays in plain markdown, and the site generator is just a tool you run locally.

Quick Start

git clone https://github.com/phalt/paulblish.git
cd paulblish
make install
uv run pb build --source ~/obsidian/blog --output ./_site
git add _site/
git commit -m "Rebuild site"
git push

Installation

Clone the repo and install dependencies using uv:

git clone https://github.com/phalt/paulblish.git
cd paulblish
make install

Usage

pb build

Build the static site from a source directory.

uv run pb build --source ~/obsidian/blog --output ./_site
Flag Default Description
--source, -s . (cwd) Path to the markdown source directory. Must contain a site.toml.
--output, -o ./_site Path to write generated HTML.
--base-url (from site.toml) Base URL for absolute links (overrides site.toml).
--templates (bundled defaults) Path to a custom Jinja2 templates directory.
--drafts false Include articles without publish: true.
--incremental false Only rebuild articles whose source file has changed since the last build. See Incremental Builds.

pb clean

Remove the output directory.

uv run pb clean --output ./_site
Flag Default Description
--output, -o ./_site Path to the built site directory to remove.

pb serve

Serve the built site locally for preview.

uv run pb serve --output ./_site
uv run pb serve --output ./_site --port 9000
Flag Default Description
--output, -o ./_site Path to the built site directory to serve.
--port, -p 8000 Port to listen on.

Site Configuration

Site configuration is loaded from one of two sources, tried in order:

  1. site.toml — a TOML file in the root of your source directory (preferred).
  2. Home.md frontmatter — YAML frontmatter fields in Home.md at the source root (useful for Obsidian users where .toml files are inconvenient).

If site.toml is present it takes priority. If neither source is found, or the required fields are missing, pb build exits with an error:

Error: No site configuration found in <source directory>
       Either create a site.toml file or add site config fields to your Home.md frontmatter:

         title, base_url, description, author

Method 1: site.toml

Create site.toml in the root of your source directory:

[site]
title       = "My Blog"
base_url    = "https://yourusername.github.io/yourrepo"
description = "A blog about things."
author      = "Your Name"
cname       = ""   # optional — your custom domain, e.g. "blog.example.com"
avatar      = ""   # optional — relative path to a square image for the home page

Method 2: Home.md frontmatter

Add the config fields to the YAML frontmatter of your Home.md:

---
publish: true
title: "My Blog"
base_url: "https://yourusername.github.io/yourrepo"
description: "A blog about things."
author: "Your Name"
cname: ""    # optional
avatar: ""   # optional
---

Fields

Field Required Description
title yes Site title shown in <title> and the nav bar
base_url yes Absolute base URL (e.g. https://user.github.io/repo)
description yes Short site description for <meta> tags
author yes Author name shown in footer and meta
cname no Custom domain — writes a CNAME file to the output root
avatar no Path to a square image shown on the home page

Frontmatter Schema

Only files with publish: true in their frontmatter are included in the build.

---
publish: true                    # required — must be true to be included
title: "Article Title"           # optional — derived from first H1 or filename if absent
slug: "article-title"            # required — used as the URL segment; also accepts `permalink`
date: 2026-03-15                 # optional — used for sorting; falls back to file mtime
tags: [python, tooling]          # optional — list of strings; generates /tags/{tag}/ pages
description: "A short summary."  # optional — shown in article header and listings
---

Files missing a slug (or permalink) are skipped with a clear reason in the build output.

Directory Structure

The source directory path of each file is preserved in the output URL:

Source file Output path URL
foo.md _site/foo/index.html /foo/
articles/foo.md _site/articles/foo/index.html /articles/foo/
articles/deep/bar.md _site/articles/deep/bar/index.html /articles/deep/bar/
Home.md _site/index.html /

The Home File

A file named Home.md (case-insensitive) at the root of your source directory becomes the site index page at /.

The home page renders with:

  • An ASCII art "Hello" banner (<pre class="ascii-banner" aria-hidden="true">)
  • An optional avatar image (configured via site.avatar in site.toml)
  • The body content of Home.md

If no Home.md is present or it is not published, the index page falls back to a generated article listing.

Deployment

The deployment workflow is: build locally → commit _site/ → push → GitHub Actions deploys.

There is no build step in CI. You build the site on your machine and commit the generated HTML.

Steps

  1. Build the site locally:

    uv run pb build --source ~/obsidian/blog --output ./_site
  2. Commit the output:

    git add _site/
    git commit -m "Rebuild site"
    git push
  3. In your GitHub repo settings, go to Settings → Pages and set the source to GitHub Actions.

The included deploy.yml workflow triggers on any push to main that touches _site/** and deploys the directory to GitHub Pages.

Base URL patterns

The base_url in your site.toml controls how all internal links and asset paths are generated. The correct value depends on how your site is hosted.

Pattern 1: GitHub Pages without a custom domain

Your site lives at https://username.github.io/reponame/. Set base_url to the full path including the repo name:

base_url = "https://username.github.io/reponame"

Pattern 2: GitHub Pages with a custom domain (CNAME)

When you use a CNAME, your site is served from the root of your domain. Set base_url to just the domain — no trailing slash, no path suffix:

base_url    = "https://blog.example.com"
cname       = "blog.example.com"

Testing locally

pb serve handles this automatically. On every pb build, a small metadata file (.pb-meta.json) is written to the output directory recording the base_url that was used. When you then run pb serve, the server reads that file and rewrites all occurrences of base_url in HTML and XML responses to an empty string before sending them to the browser. Internal paths like /articles/foo/ are not affected.

uv run pb build --source ~/obsidian/blog --output ./_site  # build once with production base_url
uv run pb serve                                            # works locally without any rebuild

The production _site/ files themselves are never modified; rewriting happens only in-flight during serving.

Custom Domain

Set cname in your site.toml:

cname = "blog.example.com"

This writes a CNAME file to _site/CNAME on every build. GitHub Pages reads the CNAME from the published directory root — no manual setup needed beyond pointing your DNS.

Incremental Builds

For large vaults, re-rendering every article on each build can be slow. Pass --incremental to skip articles whose source file has not been modified since the last build:

uv run pb build --source ~/obsidian/blog --output ./_site --incremental

How it works:

  • At the end of every build (full or incremental) a manifest file .pb-manifest.json is written to the output directory. It records each article's source path and its modification time.
  • On the next --incremental build, articles whose source mtime matches the manifest are skipped — their existing HTML files are left untouched.
  • Articles that have been modified (or are new) are fully re-rendered.
  • Source files that have been deleted since the last build have their output HTML removed automatically.
  • Shared pages (all-articles listing, tag pages, RSS feed, sitemap.xml, robots.txt, 404.html) are always regenerated — they reflect the full article set.

--incremental is compatible with --drafts. Draft articles are tracked in the manifest when --drafts is active.

Development

make install    # uv sync — install all dependencies
make test       # uv run pytest
make lint       # uv run ruff check .
make format     # uv run ruff format .
make clean      # remove _site/, __pycache__, and egg-info

Fork Your Own Copy

Paulblish is designed so anyone can fork it and run their own blog. To set up your own:

  1. Fork this repository (or use "Use this template" on GitHub).

  2. Clone it locally and run make install.

  3. Configure your site — pick whichever suits your workflow:

    Option A — site.toml (create in the root of your Obsidian directory):

    [site]
    title = "My Blog"
    base_url = "https://yourusername.github.io/yourrepo"
    description = "A blog about things."
    author = "Your Name"
    cname = ""   # set to your custom domain, or leave empty
    avatar = ""  # path to a square image, or leave empty

    Option B — Home.md frontmatter (add fields to your existing Home.md):

    ---
    publish: true
    title: "My Blog"
    base_url: "https://yourusername.github.io/yourrepo"
    description: "A blog about things."
    author: "Your Name"
    ---
  4. Ensure your markdown files have publish: true in their frontmatter.

  5. Create a Home.md in the root of your content directory for your index page.

  6. Build the site:

    uv run pb build --source /path/to/your/obsidian/dir --output ./_site
  7. Commit the _site/ directory and push to main.

  8. In your GitHub repo settings, enable Pages and set it to deploy from GitHub Actions.

The pb tool, templates, and styles are all included in the repo. Customise the templates in templates/ and the CSS in templates/static/style.css to make it your own.

About

Turn your markdown files into a static website

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors