162 lines
5.3 KiB
Markdown
162 lines
5.3 KiB
Markdown
# btmx
|
|
|
|
A self-contained hypermedia-driven blogging platform in a single portable binary. Written in Lua for the [RedBean](https://redbean.dev/) webserver, routed with [Fullmoon](https://github.com/pkulchenko/fullmoon), rendered as hypermedia with [HTMX](https://htmx.org/), 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](https://highlightjs.org/) 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](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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
./blog.com -i setup_admin.lua
|
|
```
|
|
|
|
### Development
|
|
|
|
```bash
|
|
./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:
|
|
|
|
```bash
|
|
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](https://justine.lol/ape.html) loader.
|
|
```
|
|
which ape
|
|
```
|
|
|
|
Create `/etc/systemd/system/blog.service`:
|
|
|
|
```ini
|
|
[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:
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```bash
|
|
#!/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_user="blog: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:
|
|
|
|
```bash
|
|
sudo chmod +x /etc/init.d/blog
|
|
sudo rc-update add blog default
|
|
```
|
|
|
|
Finally, start the service:
|
|
|
|
```bash
|
|
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.
|