Add entries for pagination flags, draft error code, just lint/fmt, config.toml.example, published_posts memoization, Store mtime invalidation, Cache-Control, FeedHelpers extraction, CSP header, upload size limit, and MIME whitelist.
8.5 KiB
8.5 KiB
Changelog
All notable changes to volumen are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[development]
Added
- Fediverse attribution: a
fediverse_creatorfrontmatter field (e.g.@user@mastodon.social) plus a site-wide[site].fediverse_creatordefault inconfig.toml. The value is exposed asfediverse_creatoron post and site payloads, rendered as<dc:creator>in the RSS feed, asauthors[].namein the JSON feed, and can be consumed by a front-end to emit<meta name="fediverse:creator">on rendered pages. Per-post values override the site default. has_nextandhas_prevboolean flags in the paginated/api/volumen/postsresponse so clients can navigate pages without computing boundaries themselves.- Distinct
"draft"error code in/api/volumen/posts/:slugwhen the post exists but is a draft, replacing the generic"not_found". just lintrecipe for standalone RuboCop checks andjust fmtfor auto-fixing lint issues.config.toml.exampletemplate for local development;config.tomlis now gitignored.
Changed
- Memoize
published_postswithin a single request so repeated calls (posts list, tags, feeds, sitemap) reuse the cached result. Store#allcache now invalidates on individual file mtime changes, not just on the content directory mtime. Manual edits andgit pullare detected without a restart.Cache-Control: public, max-age=60header on all public API responses for browser and CDN caching.- Extract feed and sitemap generation into a dedicated
FeedHelpersmodule, reducingServerfrom ~500 to ~420 lines.
Security
- Content-Security-Policy header on all
/admin/*responses:default-src 'self',script-src 'self' 'unsafe-inline',style-src 'self' 'unsafe-inline',img-src 'self' data:,frame-ancestors 'none'. - File uploads are rejected (HTTP 413) when exceeding 10 MB
(
MAX_UPLOAD_BYTES). - File uploads are rejected (HTTP 415) unless the MIME type is one of
JPEG, PNG, GIF, WebP, AVIF, or SVG (
ALLOWED_UPLOAD_TYPES).
[0.2.0] — 2026-06-25
Added
<pubDate>in RSS feed items, derived from the post date.<lastmod>in sitemap entries for SEO.- In-memory rate limiting on the login endpoint: 10 attempts per 60 seconds per IP.
<language>element in the RSS feed channel.- Periodic cleanup of stale
login_attemptsentries to prevent unbounded memory growth. - Slug format validation (
[a-z0-9._-]) preventing path traversal and XSS in templates. - Username validation in
change_usernamematching the create-user regex. - Escape-on-output for slug and username in admin template attributes.
- Memoization of
Store#allwith 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/<lang>/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#allmemoization 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_fileviaFile.realpath.
Added
- Scheduled publishing: a
publish_atfrontmatter 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.jsonserves a jsonfeed.org v1.1 feed withcontent_htmlanddate_publishedper item. - Fulltext search:
GET /api/volumen/posts?q=…filters posts by case-insensitive match in title and body.
Changed
- Split
lib/volumen/server.rbintoapi_routes.rbandadmin_routes.rbmodules, keeping the server class focused on configuration and shared helpers.
Fixed
admin.session_ttlwas defined but never read; it now controlsRack::Session::Cookieexpiration viaexpire_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::DEFAULTSinner hashes are now frozen anddeep_mergebuilds fresh copies, preventing mutation from silently poisoning laterConfig.loadcalls.- A warning is emitted when
admin.session_keyis 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
.mdfile with a TOML frontmatter block delimited by+++. No database. - Post fields:
title,slug,date,lang,author,tags,draft,excerpt,cover,all_langs, andtranslations(alang → slugmap). - 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, plusall_langsto 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), andsitemap.xml. postssupports pagination (page,limit, returningtotalandpage_size) and filtering bylangandtag.- 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 correctContent-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) andauthor(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 to9090),content_dir,users_file, andsitemetadata. - CLI:
serve,hash-password,version,help, with--config,--content,--host, and--portoverrides.
Deployment
install.shinstalls 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.
justtask set (install,run,dev,build,test,uninstall) and Forgejo Actions CI (RuboCop + tests on Ruby 3.3/3.4, gem build on release).CONTRIBUTING.mdand 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
HttpOnlyandSameSite=Strict, and gain theSecureattribute automatically on HTTPS requests (detected viarequest.ssl?/X-Forwarded-Proto), so the cookie never travels over plain HTTP in production. - Configurable, stable
session_keyso sessions survive restarts. - Generic login error message to avoid username enumeration.