Files
btmx/README.md
2026-07-16 23:18:39 -05:00

5.3 KiB

btmx

A self-contained hypermedia-driven blogging platform in a single portable binary. Written in Lua for the RedBean webserver, routed with Fullmoon, rendered as hypermedia with HTMX, and stored in SQLite. The build produces one executable (blog.com) that runs on Linux, BSD, macOS, and Windows; content lives in blog.db next to it.

Features

  • Single binary: no dependencies, no installation, no configuration files. Just run ./blog.com and it just works™.
  • Markdown support: posts are Markdown, rendered into HTML. highlight.js is included for code blocks.
  • Radio Station: web radio included as an optional feature for a given .m3u playlist URL.
  • RSS/Atom: Atom feed is generated automatically at /atom.xml.
  • Themes: attempts to respect user preferences for light or dark mode. Also includes a retro theme.
  • Admin dashboard: /edit does almost everything needed to manage the blog.

License

This project is licensed under the GPL-3.0 License - see the LICENSE file for details.

Requirements

All requirements are pulled during maketime with curl using GNU make (if Justine has not updated her TLS certs, you may need to grab redbean by hand.) Because it's awesome, the fatbin should work robustly on most OSes; daemons may need to use the ape loader, rather than running the binary directly.

Building

git clone https://git.asperger.pro/hpcdisrespecter/btmx.git
cd btmx
make        # fetch redbean/fullmoon/htmx/highlight.js/fonts and build blog.com
make setup  # set the admin password (creates blog.db)

There is nothing to edit before building: all site identity (title, author, base URL, …) is configured after first login at /edit/settings.

Running

Configuration

Resetting the password and setting up the database:

./blog.com -i setup_admin.lua

Development

./blog.com

Runs on localhost:8080 and attempts to open your default browser to the blog (set BLOG_NO_BROWSER=1 to suppress). Note that the Lua application is zipped inside blog.com; after editing anything under .lua/ or static/, run make again.

HTTPS

./blog.com -p 443 \
  -K privkey.pem \
  -C fullchain.pem \
  -J

Runs on localhost:443 with TLS and mandatory HTTPS using the provided certificates.

Daemon

Systemd Service

First, produce a daemon user and copy the application files to its home directory:

sudo useradd -r -s /usr/sbin/nologin -d /var/lib/blog blog
cp blog.com /var/lib/blog/blog.com
cp blog.db /var/lib/blog/blog.db
sudo chown -R blog:blog /var/lib/blog

Then, deploy your TLS certificates to /var/lib/blog/privkey.pem and /var/lib/blog/fullchain.pem with similar permissions. Consider using a hook in your certificate management tool to automate this process.

Finally, make sure your system has a working ape loader.

which ape

Create /etc/systemd/system/blog.service:

[Unit]
Description=Hypermedia Blog (Redbean)
After=network.target

[Service]
Type=simple
User=blog
WorkingDirectory=/var/lib/blog
Environment=BLOG_NO_BROWSER=1
ExecStart=ape \
  /var/lib/blog/blog.com -p 443 -p 80 \
  -K /path/to/privkey.pem \
  -C /path/to/fullchain.pem \
  -J
Restart=on-failure
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Then enable and start the service:

sudo systemctl enable blog.service
sudo systemctl start blog.service

OpenRC Service

As above, you produce a daemon user, a copy of the application files, and deploy your TLS certificates.

Then, create /etc/init.d/blog:

#!/sbin/openrc-run
name="blog"
directory="/var/lib/blog"
command="/usr/bin/ape"
command_args="/var/lib/blog/blog.com -p 443 -p 80 \
  -K /path/to/privkey.pem \
  -C /path/to/fullchain.pem \
  -J"
command_group="blog"
command_background="yes"
pidfile="/run/${RC_SVCNAME}.pid"
output_log="/var/log/blog.log"
error_log="/var/log/blog.err"
export BLOG_NO_BROWSER=1
depend() {
  need net
}

Make the script executable and add it to the default runlevel:

sudo chmod +x /etc/init.d/blog
sudo rc-update add blog default

Finally, start the service:

sudo rc-service blog start

Notes

  • Database: blog.db is created automatically at first request in the working directory. Back it up and you have backed up the whole site (posts, uploads, settings, password hash).
  • Admin access: Visit /login, enter your password. A session cookie (HttpOnly, SameSite=Strict, 7-day TTL) is set on success. Sessions are stored in blog.db and survive server restarts. /edit is the admin dashboard; /edit/settings configures the site.
  • Atom feed: set the Base URL in Settings to your public origin (e.g. https://example.com) so feed IDs and links are absolute.
  • Rate limiting: Login is limited to 5 attempts per IP per 15 minutes (in-memory; resets on restart).
  • CSRF tokens: Every mutating form includes a csrf_token hidden field validated against an HttpOnly SameSite=Strict cookie.
  • Password changes: Re-run ./blog.com -i setup_admin.lua at any time. The new hash overwrites the old one immediately.