chore: prepare release v0.4.0
This commit is contained in:
+139
-99
@@ -2,95 +2,113 @@
|
||||
|
||||
> Status: production deployment on Linux with systemd + nginx.
|
||||
|
||||
volumen runs as a single Ruby process (Puma) bound to localhost, behind nginx
|
||||
which terminates TLS and reverse-proxies the API and admin. The public website
|
||||
(e.g. a Vue SPA) is served separately and consumes the JSON API.
|
||||
volumen runs as a single Python process (Uvicorn via `fastapi[standard]`) bound
|
||||
to localhost, behind nginx which terminates TLS and reverse-proxies the API and
|
||||
admin. The public website (e.g. a Vue SPA) is served separately and consumes
|
||||
the JSON API.
|
||||
|
||||
The CLI binary is installed system-wide via `uv tool install` (or `pipx
|
||||
install`); the *operational* bootstrap — config, data dirs, admin password,
|
||||
optional systemd unit — is performed by the `volumen init` Python subcommand
|
||||
that replaces the old `install.sh` bash script.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Ruby ≥ 3.3** with a C toolchain (Puma compiles a native extension).
|
||||
- Fedora: `sudo dnf install ruby ruby-devel @development-tools openssl-devel`
|
||||
- Debian/Ubuntu: `sudo apt install ruby-full build-essential libssl-dev`
|
||||
- **nginx** and a TLS certificate (e.g. via certbot).
|
||||
- A dedicated system user, e.g. `volumen`.
|
||||
- **Python ≥ 3.14** (the bundled wheel ships its own interpreter when you use
|
||||
`uv tool install` / `pipx install`; the system Python is only required if
|
||||
you're building from source).
|
||||
- Fedora: `sudo dnf install python3.14`
|
||||
- Debian/Ubuntu: `sudo apt install python3.14 python3-venv python3-pip`
|
||||
- **[`uv`](https://docs.astral.sh/uv/)** (recommended) — install with
|
||||
`curl -LsSf https://astral.sh/uv/install.sh | sh`. Alternatively,
|
||||
[`pipx`](https://pipx.pypa.io/) ships in most distro repos.
|
||||
- **nginx** and a TLS certificate (e.g. via certbot) for the public site.
|
||||
- A dedicated system user, `volumen`, created automatically by
|
||||
`volumen init` when run as root.
|
||||
|
||||
## Layout
|
||||
## Install the CLI
|
||||
|
||||
```bash
|
||||
# Pick one:
|
||||
uv tool install volumen
|
||||
# or
|
||||
pipx install volumen
|
||||
|
||||
# Both put a self-contained `volumen` binary on PATH. Verify:
|
||||
volumen version
|
||||
```
|
||||
|
||||
For platforms where the wheel is not available (or for development), build it
|
||||
from a source checkout:
|
||||
|
||||
```bash
|
||||
git clone https://sourcedock.dev/petrbalvin/volumen.git
|
||||
cd volumen
|
||||
uv tool install .
|
||||
```
|
||||
|
||||
## Bootstrap the deployment
|
||||
|
||||
### System install (root, systemd)
|
||||
|
||||
```bash
|
||||
sudo volumen init
|
||||
sudo systemctl status volumen
|
||||
```
|
||||
|
||||
`volumen init` walks through:
|
||||
|
||||
1. Whether `config_path` already exists — if so, leaves it untouched unless
|
||||
`--force` is passed (and even then it makes a timestamped `.bak.<ts>.toml`
|
||||
first).
|
||||
2. Creates `/etc/volumen/`, `/var/lib/volumen/posts` (and a `media/`
|
||||
subdirectory), `/var/lib/volumen/users.toml`.
|
||||
3. Creates the system user `volumen` if missing (`useradd --system …`).
|
||||
4. Prompts for an admin password (`getpass`), hashes it via scrypt, writes the
|
||||
`password_hash` into the rendered config.
|
||||
5. Generates a fresh 64-byte session key (`secrets.token_hex(64)`) and writes
|
||||
it into `[admin].session_key`.
|
||||
6. Forces production-style settings: `host = "::1"`, `env = "production"`,
|
||||
`trust_proxy = true`, `cookie_secure = true`.
|
||||
7. With `--systemd` (default under root), writes
|
||||
`/etc/systemd/system/volumen.service` and runs
|
||||
`systemctl daemon-reload && systemctl enable --now volumen`.
|
||||
|
||||
Pass `--no-enable` to install the unit without starting it; pass `--force` to
|
||||
back up and overwrite an existing config. The full flag list lives in
|
||||
`volumen init --help`.
|
||||
|
||||
### Per-user install (no root)
|
||||
|
||||
```bash
|
||||
volumen init --local
|
||||
```
|
||||
|
||||
Per-user paths: `~/.config/volumen/config.toml`,
|
||||
`~/.local/share/volumen/posts`, `~/.local/share/volumen/users.toml`. No
|
||||
system user is created and no systemd unit is installed. `--systemd` is
|
||||
rejected in this mode.
|
||||
|
||||
## Configuration locations
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `/opt/volumen` | source checkout (or installed gem) |
|
||||
| `/etc/volumen/config.toml` | configuration (auto-generated on first run) |
|
||||
| `/etc/volumen/config.toml` | configuration (rendered by `volumen init` on first run) |
|
||||
| `/var/lib/volumen/posts` | Markdown posts |
|
||||
| `/var/lib/volumen/users.toml` | admin users (created from Settings) |
|
||||
| `/var/lib/volumen/posts/media` | uploaded images |
|
||||
| `/var/lib/volumen/users.toml` | admin users (created from Settings) |
|
||||
| `/etc/systemd/system/volumen.service` | systemd unit (only when `--systemd`) |
|
||||
| `~/.config/volumen/config.toml` | per-user config (`--local`) |
|
||||
| `~/.local/share/volumen/posts` | per-user data dir (`--local`) |
|
||||
| `~/.local/share/volumen/users.toml` | per-user users file (`--local`) |
|
||||
|
||||
```bash
|
||||
sudo useradd --system --home /var/lib/volumen --shell /usr/sbin/nologin volumen
|
||||
sudo mkdir -p /etc/volumen /var/lib/volumen/posts
|
||||
sudo chown -R volumen:volumen /var/lib/volumen
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
### Quick install (Linux + systemd)
|
||||
|
||||
From a cloned repository, run the installer as root. It installs the Ruby
|
||||
toolchain from the distro repositories on **Fedora** and **openEuler** (prints
|
||||
guidance on other distros), creates the `volumen` user and directories, runs
|
||||
`bundle install`, generates `config.toml`, and installs and enables the systemd
|
||||
unit:
|
||||
|
||||
```bash
|
||||
sudo git clone https://codeberg.org/petrbalvin/volumen.git /opt/volumen
|
||||
cd /opt/volumen
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
The service is enabled but not started — finish by setting the admin password
|
||||
(printed steps), then `sudo systemctl start volumen`. The manual steps below
|
||||
describe what the script does.
|
||||
|
||||
### Manual install
|
||||
|
||||
From a source checkout (reproducible, no publishing needed):
|
||||
|
||||
```bash
|
||||
sudo git clone https://codeberg.org/petrbalvin/volumen.git /opt/volumen
|
||||
cd /opt/volumen
|
||||
sudo bundle install --deployment
|
||||
```
|
||||
|
||||
Alternatively build and install the gem (`just build` produces `pkg/volumen.gem`,
|
||||
then `gem install pkg/volumen.gem` puts `volumen` on the PATH).
|
||||
|
||||
## First run and the admin password
|
||||
|
||||
The first `serve` writes a commented `config.toml` if it is missing:
|
||||
|
||||
```bash
|
||||
sudo -u volumen bundle exec exe/volumen serve \
|
||||
--config /etc/volumen/config.toml \
|
||||
--content /var/lib/volumen/posts
|
||||
# → "volumen: wrote a default config to /etc/volumen/config.toml; review it and restart."
|
||||
```
|
||||
|
||||
Generate a password hash and a stable session secret, then edit the config:
|
||||
|
||||
```bash
|
||||
bundle exec exe/volumen hash-password # → scrypt$...
|
||||
openssl rand -hex 64 # → session_key
|
||||
sudo -u volumen $EDITOR /etc/volumen/config.toml
|
||||
```
|
||||
|
||||
Set `host = "127.0.0.1"`, your `site.base_url`, the `admin.password_hash`, and a
|
||||
`admin.session_key` (so sessions survive restarts). You can now sign in as user
|
||||
`admin`; from the **Settings** page you can add more users (roles `admin` /
|
||||
`author`) and change passwords. From then on `users.toml` is the source of
|
||||
truth.
|
||||
The config and users files are owned by `volumen:volumen` and `chmod 600` after
|
||||
a system install — they may carry password hashes, so never make them
|
||||
world-readable.
|
||||
|
||||
## systemd
|
||||
|
||||
`/etc/systemd/system/volumen.service`:
|
||||
`/etc/systemd/system/volumen.service` (rendered by `volumen init --systemd`):
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
@@ -98,10 +116,12 @@ Description=volumen blog engine
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=volumen
|
||||
Group=volumen
|
||||
WorkingDirectory=/opt/volumen
|
||||
ExecStart=/usr/bin/bundle exec exe/volumen serve --config /etc/volumen/config.toml --content /var/lib/volumen/posts
|
||||
WorkingDirectory=/etc/volumen
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
ExecStart=/usr/local/bin/volumen serve --config /etc/volumen/config.toml --content /var/lib/volumen/posts
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
NoNewPrivileges=true
|
||||
@@ -114,26 +134,34 @@ PrivateTmp=true
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Hardening notes — `NoNewPrivileges`, `ProtectSystem=strict` (the entire
|
||||
filesystem is read-only except for `ReadWritePaths`), `ProtectHome`
|
||||
(`/home`, `/root`, `/run/user` are inaccessible), `PrivateTmp` (private
|
||||
`/tmp`), and a two-second restart delay on failure.
|
||||
|
||||
Inspect health with:
|
||||
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now volumen
|
||||
sudo systemctl status volumen
|
||||
journalctl -u volumen -f
|
||||
volumen status # config / data dir / unit / service state
|
||||
volumen status --json # machine-readable
|
||||
```
|
||||
|
||||
## FreeBSD (rc.d)
|
||||
|
||||
volumen runs on FreeBSD too — it is plain CRuby plus gems; only Puma (and its
|
||||
`nio4r` dependency) build a native extension, which compiles with the base
|
||||
clang toolchain.
|
||||
volumen runs on FreeBSD too — it is plain CPython with no compiled extensions.
|
||||
|
||||
```sh
|
||||
pkg install ruby
|
||||
# then install volumen as above (git clone + bundle install)
|
||||
pkg install python3
|
||||
# install uv (https://docs.astral.sh/uv/getting-started/installation/)
|
||||
uv tool install volumen
|
||||
sudo volumen init --local # no systemd on FreeBSD
|
||||
```
|
||||
|
||||
Conventional FreeBSD paths: config in `/usr/local/etc/volumen/`, data in
|
||||
`/var/db/volumen/`. Service script `/usr/local/etc/rc.d/volumen`:
|
||||
`/var/db/volumen/`. Drop a tiny `rc.d` script in `/usr/local/etc/rc.d/volumen`
|
||||
that wraps the `volumen` console script:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
@@ -150,14 +178,13 @@ load_rc_config $name
|
||||
|
||||
: ${volumen_enable:="NO"}
|
||||
: ${volumen_user:="volumen"}
|
||||
: ${volumen_dir:="/usr/local/volumen"}
|
||||
: ${volumen_config:="/usr/local/etc/volumen/config.toml"}
|
||||
: ${volumen_content:="/var/db/volumen/posts"}
|
||||
|
||||
pidfile="/var/run/${name}.pid"
|
||||
command="/usr/sbin/daemon"
|
||||
command_args="-f -r -P ${pidfile} -u ${volumen_user} \
|
||||
/bin/sh -c 'cd ${volumen_dir} && exec bundle exec exe/volumen serve --config ${volumen_config} --content ${volumen_content}'"
|
||||
/bin/sh -c 'exec volumen serve --config ${volumen_config} --content ${volumen_content}'"
|
||||
|
||||
run_rc_command "$1"
|
||||
```
|
||||
@@ -165,6 +192,7 @@ run_rc_command "$1"
|
||||
Enable and start:
|
||||
|
||||
```sh
|
||||
chmod +x /usr/local/etc/rc.d/volumen
|
||||
sysrc volumen_enable=YES
|
||||
service volumen start
|
||||
```
|
||||
@@ -266,22 +294,34 @@ right block. Apply with `sudo nginx -t && sudo systemctl reload nginx`.
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd /opt/volumen
|
||||
sudo git pull
|
||||
sudo bundle install --deployment
|
||||
uv tool upgrade volumen
|
||||
sudo systemctl restart volumen
|
||||
volumen status # confirm: config_exists, password_hash_set, session_key_ok, …
|
||||
```
|
||||
|
||||
The CLI binary is replaced in place by `uv tool upgrade`; the systemd unit's
|
||||
`ExecStart` already points at the on-PATH `volumen` console script, so a
|
||||
restart is all that's needed. The persistent state (`config.toml`,
|
||||
`users.toml`, posts) is untouched.
|
||||
|
||||
If you used the legacy `install.sh` workflow (no longer documented, the script
|
||||
was removed), you can migrate forward by deleting `/etc/systemd/system/volumen.service`
|
||||
+ `/opt/volumen` and then running `uv tool install volumen && sudo volumen init`.
|
||||
|
||||
## Security notes
|
||||
|
||||
- Bind to `127.0.0.1`; only nginx is public. TLS is terminated by nginx.
|
||||
- Bind to `::1` (or `127.0.0.1`); only nginx is public. TLS is terminated by
|
||||
nginx.
|
||||
- Session cookies are `HttpOnly` and `SameSite=Strict`, and gain the `Secure`
|
||||
attribute automatically on HTTPS requests (detected via `X-Forwarded-Proto`,
|
||||
which the nginx snippet above sets) — so the cookie never travels over plain
|
||||
HTTP in production, while local HTTP testing still works. Combined with
|
||||
per-form CSRF tokens this protects the admin. Set a stable `admin.session_key`
|
||||
so sessions survive restarts.
|
||||
which the nginx snippet above sets, and enabled by
|
||||
`[server].cookie_secure = true`, set automatically by `volumen init
|
||||
--systemd`) — so the cookie never travels over plain HTTP in production,
|
||||
while local HTTP testing still works. Combined with per-form CSRF tokens
|
||||
this protects the admin. A stable `[admin].session_key` (also written by
|
||||
`volumen init`) lets sessions survive restarts.
|
||||
- Keep `config.toml` and `users.toml` readable only by the `volumen` user
|
||||
(`chmod 600`). Both may contain password hashes.
|
||||
- `volumen hash-password` reads the password without echo; never pass passwords
|
||||
on the command line.
|
||||
(`chmod 600`). Both may contain password hashes. The installer enforces
|
||||
this automatically.
|
||||
- Use `volumen hash-password` to rotate; it reads the password without echo.
|
||||
Never pass passwords on the command line.
|
||||
|
||||
Reference in New Issue
Block a user