# Changelog All notable changes to **volumen** are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [development] ### Added - **Fediverse attribution:** a `fediverse_creator` frontmatter field (e.g. `@user@mastodon.social`) plus a site-wide `[site].fediverse_creator` default in `config.toml`. The value is exposed as `fediverse_creator` on post and site payloads, rendered as `` in the RSS feed, as `authors[].name` in the JSON feed, and can be consumed by a front-end to emit `` on rendered pages. Per-post values override the site default. ## [0.2.0] — 2026-06-25 ### Added - `` in RSS feed items, derived from the post date. - `` in sitemap entries for SEO. - In-memory rate limiting on the login endpoint: 10 attempts per 60 seconds per IP. - `` element in the RSS feed channel. - Periodic cleanup of stale `login_attempts` entries to prevent unbounded memory growth. - Slug format validation (`[a-z0-9._-]`) preventing path traversal and XSS in templates. - Username validation in `change_username` matching the create-user regex. - Escape-on-output for slug and username in admin template attributes. - Memoization of `Store#all` with mtime-based invalidation, eliminating N×read+parse per request. - Atomic file writes (tmp + rename) for posts and `users.toml`. - Post language directory routing: non-default-language posts now land in `content_dir//` instead of overwriting root-level files. - CLI test coverage (version, help, unknown command, option parsing, config bootstrap). - Test coverage for RSS feed and sitemap endpoints. - CORS preflight (`OPTIONS /api/volumen/*`) so browsers can reach the JSON API. - `Users#all` memoization with mtime-based invalidation, matching Store#all. - Session invalidation: deleted users can no longer use existing session cookies. - CSRF protection on `POST /admin/preview`. - Symlink-safe containment in `Store#media_file` via `File.realpath`. ### Added - **Scheduled publishing:** a `publish_at` frontmatter field hides posts from the public API and feeds until their publication date arrives. The admin form includes a Publish at date picker. - **JSON Feed:** `GET /api/volumen/feed.json` serves a jsonfeed.org v1.1 feed with `content_html` and `date_published` per item. - **Fulltext search:** `GET /api/volumen/posts?q=…` filters posts by case-insensitive match in title and body. ### Changed - Split `lib/volumen/server.rb` into `api_routes.rb` and `admin_routes.rb` modules, keeping the server class focused on configuration and shared helpers. ### Fixed - `admin.session_ttl` was defined but never read; it now controls `Rack::Session::Cookie` expiration via `expire_after`. - Derived post excerpts now strip HTML tags before stripping markdown markup, avoiding raw HTML leaks in auto-generated excerpts. - Draft posts no longer leak through the public detail endpoint (`/api/volumen/posts/:slug`). - `Config::DEFAULTS` inner hashes are now frozen and `deep_merge` builds fresh copies, preventing mutation from silently poisoning later `Config.load` calls. - A warning is emitted when `admin.session_key` is configured but shorter than 64 bytes, instead of silently falling back to a random secret. ## [0.1.0] — 2026-06-20 First public release: a small, file-based Markdown blog engine in CRuby. Posts are plain files, the engine exposes them as a JSON API and ships a self-hosted admin — no database, no external services. ### Added **Content & storage** - File-based store: each post is a `.md` file with a TOML frontmatter block delimited by `+++`. No database. - Post fields: `title`, `slug`, `date`, `lang`, `author`, `tags`, `draft`, `excerpt`, `cover`, `all_langs`, and `translations` (a `lang → slug` map). - Drafts (`draft = true`) are hidden from the public API. - Excerpt falls back to the first paragraph (~200 chars) when none is set. - Estimated reading time (~200 words/min) computed per post. - Multilingual posts: per-language slugs via `translations`, plus `all_langs` to surface a single post under every language. **Markdown rendering** - GFM rendering via kramdown: tables, fenced code blocks, task lists, and strikethrough. - Automatic heading IDs for in-page anchors. - Raw HTML in post bodies is passed through (authors are the trusted admin). **Public JSON API (`/api/volumen`)** - Read-only endpoints: `site`, `posts`, `posts/{slug}`, `tags`, `feed.xml` (RSS), and `sitemap.xml`. - `posts` supports pagination (`page`, `limit`, returning `total` and `page_size`) and filtering by `lang` and `tag`. - Permissive CORS so a separate front end can consume the API. **Admin (`/admin`)** - Server-rendered admin with a sidebar layout and full post CRUD. - Dual-mode editor: Markdown source (primary) and a visual WYSIWYG, switchable per post, with a toggleable live preview persisted across sessions. - Image upload from the editor toolbar into `content_dir/media`; any image format is accepted (WebP and AVIF included) and served with the correct `Content-Type`. - Cover image field with upload, URL, clear, and preview. **Multi-user & roles** - Multiple admin users stored in `users.toml`, managed from a settings page. - Two roles: `admin` (full access incl. user management) and `author` (posts only). - Change password and username; last-admin and self-deletion safeguards. **Configuration & CLI** - TOML configuration, auto-generated with a commented template on first run. - Configurable `host`/`port` (defaults to `9090`), `content_dir`, `users_file`, and `site` metadata. - CLI: `serve`, `hash-password`, `version`, `help`, with `--config`, `--content`, `--host`, and `--port` overrides. **Deployment** - `install.sh` installs the Ruby toolchain from distro packages on Fedora and openEuler (and prints guidance on other distros), creates the service user and directories, and installs a systemd unit. - Deployment docs for systemd and FreeBSD (`rc.d`), plus an nginx reverse-proxy setup that runs the admin same-origin, with brute-force rate-limiting and an optional IP allowlist on the login endpoint. **Project & tooling** - Documentation set: architecture, configuration, API reference, deployment, and security. - `just` task set (`install`, `run`, `dev`, `build`, `test`, `uninstall`) and Forgejo Actions CI (RuboCop + tests on Ruby 3.3/3.4, gem build on release). - `CONTRIBUTING.md` and a minitest suite; clean RuboCop. ### Security - Passwords hashed with scrypt and verified in constant time. - Per-form CSRF tokens on every state-changing admin request. - Session cookies are `HttpOnly` and `SameSite=Strict`, and gain the `Secure` attribute automatically on HTTPS requests (detected via `request.ssl?` / `X-Forwarded-Proto`), so the cookie never travels over plain HTTP in production. - Configurable, stable `session_key` so sessions survive restarts. - Generic login error message to avoid username enumeration. [0.1.0]: https://codeberg.org/petrbalvin/volumen/releases/tag/v0.1.0