• fsylvain released this 2026-10-13 22:42:51 +02:00 | 12 commits to main since this release

    This release pages the media library, remembers your page size on the article, activity and media lists, and previews image variants on a real sample photo. The media list API changes shape, so read the upgrading notes if you call it.

    New

    Media library

    • The media library is now paged, with a page size of 5, 25, 50 or 100. The image picker in the editor uses the same pages.

    Image variants

    • Creating a variant shows a preview rendered from a real sample image, so the size estimate and the before and after comparison match what visitors will get.

    Improvements and fixes

    • The article, activity and media lists keep their page and page size in the address, so a reload or a shared link opens the same view.
    • The Integration page is now fully translated into French.

    API

    • Changed: GET /v1/media/ answers { items, total, page, size, counts } instead of { items, next_cursor, counts }. It accepts page (default 1) and size (5, 25, 50 or 100, default 25). An invalid value answers 400.
    • Changed: GET /v1/media/ no longer accepts cursor or limit.

    Under the hood

    • Replace the cursor and limit of the media list query with a page and a size in the image domain and the list use case.
    • Add specifications, design checks and Go and Vitest suites for the paged media screens.

    Upgrading

    • Replace the binary, or pull the new image. There is no schema change and no new environment variable.
    • Any script or site that reads GET /v1/media/ must now read total and loop over page instead of following next_cursor.
    Downloads
  • fsylvain released this 2026-10-13 03:28:24 +02:00 | 18 commits to main since this release

    This release adds paginated, searchable article lists, links in the editor and an export of the activity log, and removes the article length limit. The article list API changes shape, so read the upgrading notes if you call it.

    New

    Articles

    • The article list is now paged, with a search box, an Online and Draft filter, sortable columns and a page size of 5, 25, 50 or 100. Counts for All, Online and Draft stay fixed while you search.
    • Articles have no length limit any more, and a new article can be created without a body.

    Editor

    • Insert, edit and remove links directly in the editor.

    Activity log

    • The summary cards at the top are now filters: select one to narrow the list, select it again to clear it.
    • A new Export menu downloads the filtered events as a CSV file.
    • The date range also accepts "All time".

    Integration

    • The Integration page has a new Images section covering image variants, and its code snippets now share one format.

    Improvements and fixes

    • Each page now sets its own browser tab title, in the form "Page · RenewCMS", instead of keeping the title of the first page loaded.
    • Registering now waits for the session to be refreshed before showing the pending-approval screen, which fixes a redirect back to the sign in page.
    • The media and variant screens, the navigation contrast and the shell colors follow the design spec more closely.
    • The development workflow on Linux no longer deletes the visual check output.

    API

    • Changed: GET /v1/article/ always answers { items, total, page, size, counts } instead of a bare array. It accepts page, size (5, 25, 50, 100), q, status (online or draft) and sort (for example title:asc; default updated_at:desc). An invalid value answers 400. Anonymous callers still only see online articles.
    • Changed: POST /v1/article/ and PATCH /v1/article/{id} no longer reject a body over 10,000 characters, and POST accepts an empty body.
    • GET /v1/activity and GET /v1/activity/export accept range=all.

    Under the hood

    • Add the paged article query to the article repository contract, with accent-insensitive title search, and promote golang.org/x/text to a direct dependency.
    • Add specifications, design checks and Go and Vitest suites for the new screens.
    • Document the article list in docs/api/articles.md and link the API docs from the README.

    Upgrading

    • Replace the binary, or pull the new image. There is no schema change and no new environment variable.
    • Any script or site that reads GET /v1/article/ must now read the items property of the response. Without page and size you get the first 25 articles, so loop over page until you have total items.
    Downloads
  • fsylvain released this 2026-10-08 21:15:43 +02:00 | 33 commits to main since this release

    This release adds image variants: named sizes and formats of your images, generated on first use and cached, plus a size limit for the activity log.

    New

    Image variants

    • Owners and admins define named variants (width, height, fit, format and quality) in the new variants screens under Settings. Variants can be created, edited, enabled, disabled, purged and deleted.
    • Add ?v=<name> to any uploaded image URL to get the variant. It is generated on the first request, cached, and served from the cache afterwards. The original is never changed, and URLs without ?v= behave as before.
    • Each image in the media library shows its variants, and lets you generate or purge them one by one or all at once.
    • The new Cache screen shows how many files are generated, the disk space used, how many requests were rejected and what has been unused for 30 days. Purge everything, unused files, one variant or one image.
    • Output can keep the original format or be converted to JPEG, PNG or lossy WebP. Images are never enlarged. SVG and animated GIF are served as they are.
    • Oversized sources are refused with a clear error instead of exhausting memory, and identical concurrent requests share one generation.
    • The new screens are available in English and French.

    Activity log

    • Retention can now also be capped by size, in GB, in addition to its age. Whichever limit is reached first removes the oldest events. No cap is set by default.

    Improvements and fixes

    • Fix the missing routes of the integration pages.
    • Harmonize the design of the latest screens with the design spec.

    API

    • GET /v1/image-variants and POST /v1/image-variants list and create variants. PUT /v1/image-variants/{name} replaces a definition, PATCH enables or disables it and DELETE removes it with its files. POST /v1/image-variants/{name}/purge removes the generated files only.
    • GET /v1/image-variants/cache returns the cache statistics, and POST /v1/image-variants/cache/purge purges by scope (all, unused, variant or image).
    • GET /v1/media/{id}/variants lists the variants of an image. POST /v1/media/{id}/variants generates all missing ones and POST or DELETE /v1/media/{id}/variants/{name} generates or purges one.
    • All of these routes are for owners and admins and accept a session only: an API key answers 403.
    • GET /static/uploadedImages/<storedName>?v=<name> serves the variant. An unknown or disabled name, or a width, height, format or quality parameter, answers 400. A source over the size or pixel limit answers 413. The response carries an ETag and answers 304 to If-None-Match.
    • PATCH /v1/settings also accepts a maximum activity log size in GB, returned as activity_retention_max_gb.

    Under the hood

    • Add the image variant domain, repositories, renderer, in-memory usage tracker flushed every 30 seconds, and use cases, behind the existing storage port so local and S3 storage both work.
    • Document the feature in docs/image-variants.md, docs/api.md and docs/architecture.md, and add the specifications, design screens and Go and Vitest suites.

    Upgrading

    • Replace the binary, or pull the new image. The variant tables are created automatically at startup. No variant exists until you define one.
    • Three optional environment variables are new: IMAGE_VARIANT_MAX_SOURCE_BYTES (10485760), IMAGE_VARIANT_MAX_SOURCE_PIXELS (50000000) and IMAGE_VARIANT_MAX_CONCURRENT_RENDERS (4). An existing install needs no change.
    • Generated variants are stored next to your images, in the upload directory or the S3 bucket, and count toward the storage you use.
    • Generating a variant when an image is uploaded is not part of this release.
    Downloads
  • fsylvain released this 2026-10-07 16:04:23 +02:00 | 43 commits to main since this release

    This release adds an activity log that records who did what on your site, and lets RenewCMS run on MySQL, MariaDB or PostgreSQL and keep uploaded images in an S3-compatible bucket.

    New

    Activity log

    • The new Activity page lists every important action as a plain sentence, newest first, grouped by day: articles created, edited, published or deleted, images uploaded, renamed, merged or purged, accounts and invitations, and the system tasks (scheduled purge, update checks and downloads, restarts).
    • Filter by search text, person, category, outcome and date range, with Today, 7 days, 30 days and a custom range. Click a summary card to apply its filter. Open any row to see its details, including what changed.
    • Failed actions are recorded with a plain reason, and failed logins and similar events are flagged as security events.
    • Owners and admins see everyone's activity, a summary strip (total, active people, failed actions, security events) and can export the filtered list as a CSV file. Any other role would only see its own activity.
    • Article bodies are never recorded, only the change in size. Passwords, codes, file contents and storage paths are never recorded either.
    • The Home page has a new Recent activity card with the last five events.
    • Owners choose how long events are kept in the new Retention card in Settings: 30 days, 90 days (the default), 365 days or forever. Shortening the period asks for confirmation first. Older events are removed automatically once a day, and Settings shows the last cleanup and the next one.
    • The new screens are available in English and French.

    More ways to run RenewCMS

    • Use MySQL 8, MariaDB 11 or PostgreSQL 16 and 17 instead of the local SQLite file by setting DB_DRIVER to mysql or postgres and the DB_* connection variables. SQLite stays the default.
    • Keep uploaded images in an S3-compatible bucket (AWS S3, MinIO, Ceph, Cloudflare R2, Backblaze B2 and similar) by setting STORAGE_DRIVER=s3. Image URLs do not change and the bucket stays private.
    • The update backup taken before a download now works with every database: SQLite as before, and a logical dump for MySQL, MariaDB and PostgreSQL. The newest 3 backups are kept.
    • Docker Compose files for each setup are included: docker-compose.mysql.yml, docker-compose.postgres.yml and docker-compose.s3.yml.

    API

    • GET /v1/activity returns the filtered, paged events as { items, total, page, size }, newest first. It accepts q, actor, category, outcome, security, range, from, to, page and size, and any invalid value answers 400. Owners and admins see every event. Any other role only sees its own, and asking for another actor answers 403.
    • GET /v1/activity/summary returns the counts for the range, the previous period, the active people and a seven-bucket series. Owners and admins only.
    • GET /v1/activity/export returns the same filters as a CSV attachment (when,actor,action,target,outcome,source,ip). Owners and admins only. Cells that start with =, +, - or @ get a leading quote.
    • The three routes accept a session only: an API key answers 403 with "This action needs a real session."
    • There is no POST, PUT, PATCH or DELETE under /v1/activity. Events can only be removed by the retention period.
    • GET /v1/settings now also returns activity_retention_days, activity_last_cleanup_at, activity_last_cleanup_removed and activity_next_cleanup_at.
    • PATCH /v1/settings accepts activity_retention_days with 0 (forever), 30, 90 or 365. Session and owner only, with 403 "Only the owner can do this." for anyone else. Any other value answers 400.

    Under the hood

    • Decouple persistence and image storage behind driver registries: persistence/database for the SQL drivers and internal/infrastructure/storage for the image adapters, with the domain image.Storage port. Repositories no longer touch files.
    • Add the audit domain, an append-only repository, a recorder that never fails or blocks the caller, and the use cases that record events from inside each action.
    • Add a scheduler for the activity cleanup, which runs once a day at or after 03:00 server time, alongside the media purge scheduler.
    • Add the activity, Home card and Retention card specifications and QA procedures to features/, the new screens to docs/design/, and Go and Vitest suites for each part.
    • Document the new variables in docs/configuration.md, and the new setups in docs/deployment.md and docs/docker.md.

    Upgrading

    • Replace the binary, or pull the new image. The activity tables are created automatically at startup.
    • New environment variables are all optional and default to the previous behaviour: DB_DRIVER (sqlite), STORAGE_DRIVER (local), DATA_DIR (the directory of DB_FILE), and the DB_* and S3_* variables listed in docs/configuration.md. An existing install needs no change.
    • Activity starts recording after the upgrade, and nothing earlier is added. The first cleanup applies the default retention of 90 days, so owners who want a longer history should change it in Settings.
    • There is no automatic migration between databases or from the upload directory to a bucket. To move, follow the steps in docs/configuration.md and docs/deployment.md.
    • Backups taken before an update cover the database only. With S3, back the bucket up with your provider.
    Downloads
  • fsylvain released this 2026-10-05 23:58:54 +02:00 | 53 commits to main since this release

    This release adds a duplicate purge to the media library: owners and admins can merge identical images in one action, or let RenewCMS do it on a schedule.

    New

    Media duplicate purge

    • Purge duplicates from the Duplicates tab of the media library with the new toolbar button, or from the new Media cleanup card in Settings. A summary shows how many copies would be merged and how many articles would be updated before you confirm.
    • For each group of identical images used in at least one article, the most used copy is kept, with ties going to the oldest. Every other copy is merged into it and the articles that used them are rewritten. Groups where no copy is used are left alone.
    • If the library changes between the summary and the confirmation, nothing is purged and you are asked to check the new summary.
    • Turn on automatic purge in Settings and choose an interval from 1 to 365 days, 30 by default. The first run happens one interval after you turn it on, then once per interval after each run.
    • The Media cleanup card shows the last run, with the number of copies merged and articles updated, and the date of the next one.
    • The purge is reserved for owners and admins. Editors get a clear message instead of the controls.
    • The new screens are available in English and French.

    API

    • GET /v1/media/purge-settings returns the automatic purge settings: auto_purge_enabled, auto_purge_interval_days, last_purge_at, last_purge_merged, last_purge_articles_updated and next_purge_at, which is null while automatic purge is off.
    • PATCH /v1/media/purge-settings updates auto_purge_enabled and auto_purge_interval_days. An interval outside 1 to 365 answers 400.
    • GET /v1/media/purge-preview returns what a purge would do, with a fingerprint of the plan. It changes nothing.
    • POST /v1/media/purge runs the purge. Send the preview's fingerprint to refuse a run if the library changed: a stale fingerprint answers 409, and so does a purge that is already running.
    • All four routes answer 403 with "Only owners and admins can do this." for any other role, and accept a session only, like the rest of the media routes.

    Under the hood

    • Add the purge domain, a SQLite repository for the single-row purge settings, and the use cases that build the plan, preview it, run it and decide when a scheduled run is due.
    • Add an hourly scheduler, started with the server after a one-minute delay and stopped on shutdown. It reads the settings on every tick, so changes apply without a restart, and a failed run is retried at the next tick.
    • Add the RequireOwnerOrAdmin middleware.
    • Add the media purge, scheduler and Settings card specifications and QA procedures to features/, the new screens to docs/design/, and Go and Vitest suites for each part.

    Upgrading

    • Replace the binary, or pull the new image. The purge settings table is created automatically at startup. No new environment variable is required.
    • Automatic purge is off after the upgrade. Nothing is merged until an owner or admin runs a purge or turns it on.
    • A purge cannot be undone: merged copies are removed. Check the summary before confirming.
    Downloads
  • fsylvain released this 2026-10-05 16:33:31 +02:00 | 58 commits to main since this release

    This release fixes the security headers added in v2.8.2-beta, which blocked parts of the web app in the browser.

    Improvements and fixes

    • The web app's icons now display again. They are bundled into the app instead of being fetched from an external icon service, which the security policy blocked.
    • The light and dark mode script in the page is now allowed by the security policy, so the color mode no longer fails to apply on load.
    • The security policy now allows WebAssembly, which parts of the web app need.

    Under the hood

    • Add a Vite plugin that bundles only the Lucide icons the app uses.
    • Compute a hash for each inline script in the served page and add it to the script-src directive, instead of allowing inline scripts in general.
    • Update the security headers tests.

    Upgrading

    • Replace the binary, or pull the new image. No schema change and no new environment variable.
    • If you are on v2.8.2-beta, upgrade: that version's policy broke icons and color mode.
    Downloads
  • fsylvain released this 2026-10-05 16:24:12 +02:00 | 60 commits to main since this release

    This release hardens the server with browser security headers on every response and makes the log in form clearer when the username is wrong.

    Improvements and fixes

    • RenewCMS now sends security headers on every response, including the web app and uploaded images: a Content Security Policy that forbids framing, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Cross-Origin-Opener-Policy. With ENVIRONMENT=production it also sends Strict-Transport-Security.
    • The log in form now tells you when you typed an email address instead of your username, and when the username is longer than 20 characters, instead of only asking for at least 3 characters. The messages are available in English and French.

    Under the hood

    • Add a security headers middleware, mounted on the whole router, with tests.
    • Document the headers, the Google Fonts and https: image allowances in the policy, and the advice not to duplicate them in the reverse proxy in the deployment guide.

    Upgrading

    • Replace the binary, or pull the new image. No schema change and no new environment variable.
    • If your reverse proxy already adds the same security headers, remove them there, or browsers will receive them twice.
    • The policy allows scripts from the same origin only. A custom script injected by your proxy will be blocked.
    • Serve the site over HTTPS only when ENVIRONMENT=production, because the server then asks browsers to remember HTTPS for a year.
    Downloads
  • fsylvain released this 2026-10-05 04:56:12 +02:00 | 63 commits to main since this release

    This release adds an official Docker deployment, so RenewCMS can run in a container instead of as a binary under systemd, and teaches Settings to guide container installs through updates.

    New

    Docker deployment

    • A container image holds the single RenewCMS binary on Alpine, runs as a non-root user and keeps the database and uploads on one volume. A Compose file starts it with an env file and a health check.
    • Images are published for amd64 and arm64 to the project registry at git.floriansylvain.fr/fsylvain/renewcms. Prereleases carry the version tag and a moving beta tag, and latest is only used for stable releases.
    • Upgrading is docker compose pull && docker compose up -d. Your data stays on the volume.
    • A new Docker guide covers the quick start, backups of the data volume, the reverse proxy and TLS, reaching an SMTP server from the container, and building the image from source.

    Update guidance in containers

    • In a container, Settings no longer shows the browser update flow, which cannot work there. It shows the new version, the command to run on the server with a Copy button, and a link to the Docker guide.
    • Download and restart are not offered in a container, and the API refuses them.

    API

    • GET /v1/system/update has a new container boolean. It is true when the server runs in a container, and can_apply is then always false.
    • POST /v1/system/update/download and POST /v1/system/update/restart answer 409 in a container, as they already do when browser updates are not set up.

    Under the hood

    • Add a multi-stage Dockerfile, a Compose file with a separate build override, and tasks to build, run, pull, upgrade and publish the image. Publishing is a maintainer task that refuses to run unless the version is an exact tag on a clean tree.
    • Add the container update guidance and the image publishing specifications and QA procedures to features/.

    Upgrading

    • Replace the binary. No schema change and no required environment variable.
    • There is one new optional variable, RENEWCMS_CONTAINER, which defaults to false. The Docker image and the Compose file set it to true. Leave it unset on a binary install.
    • Docker users start from the Docker guide. To move an existing binary install to Docker, copy the database and uploads into the volume and keep the same environment variables.

    Known limitations

    • Browser updates are not available in a container. Update by pulling a new image.
    • The new container wording in Settings has no design frame yet.
    Downloads
  • fsylvain released this 2026-10-05 02:08:26 +02:00 | 65 commits to main since this release

    This release adds API keys, so a script or service can use the whole API without the web interface, and turns the Integration page into a complete API reference.

    New

    API keys

    • Create named API keys from the new API keys screen under Integration, with an optional expiry date. The full key is shown once, when you create it, and only its prefix is visible afterwards.
    • Send a key as Authorization: Bearer rcms_... to manage articles, media, users and invites, and to read settings. A key acts with exactly the rights of the user who created it, so an admin's key cannot do owner-only things.
    • Revoke a key at any time. Active, expired and revoked keys all stay listed with their status, creation date and last use.
    • The owner sees every key and who created it. Admins see only their own.
    • Each key is limited to 120 requests per minute.
    • Resetting a user's password revokes all of that user's keys.

    API reference

    • The Integration page now documents every route a developer can call, grouped by area: articles, media, users and invites, and settings. Each endpoint lists who can call it, its request body, ready-to-copy fetch and curl examples, a sample response and its error statuses.
    • A new Authentication section explains public access and API keys, and a section index lets you jump between groups.
    • Routes that need a signed-in browser session are listed separately, and the page shows the real error messages the API returns.
    • The Integration page and the API keys screen are available in English and French.

    API

    • POST /v1/api-key creates a key. It answers 201 with the key, shown once.
    • GET /v1/api-key lists keys, newest first, each with a status of active, expired or revoked. For the owner, each entry also carries created_by_name.
    • DELETE /v1/api-key/{id} revokes a key and answers 204, also when the key is already revoked.
    • An invalid, unknown, expired or revoked key answers 401 on every route, including public ones, and is never treated as anonymous.
    • A key calling a session-only route answers 403 with "This action needs a real session." These routes are /v1/api-key, POST /v1/auth/verify, DELETE /v1/auth/register and everything under /v1/system.
    • GET /v1/auth/me accepts an API key, so a client can check who a key belongs to.
    • A key beyond 120 requests per minute gets 429 with a Retry-After header.
    • Cross-origin requests now allow the PATCH method.

    Under the hood

    • Add the API key domain, use cases, SQLite repository and an authentication middleware that runs after each token verifier. Keys are generated by a new domain interface and stored as SHA-256 hashes, never in clear.
    • Add a typed API catalogue that drives the Integration page, with a test that every route has a description.
    • Add the API keys and Integration specifications and QA procedures to features/, and the API key reference to docs/api/.

    Upgrading

    • Replace the binary. The new API keys table is created automatically at startup. No new environment variable is required.
    • Browsers calling the API from another origin can now use PATCH. Check your CORS configuration only if you restricted methods in a reverse proxy.
    Downloads
  • fsylvain released this 2026-10-04 19:04:12 +02:00 | 69 commits to main since this release

    This release lets your website read article bodies as markdown, HTML or structured blocks, and fixes text alignment in the editor.

    New

    Article body formats

    • Read an article body as markdown, HTML or a typed block array with the format query parameter, so a headless frontend can render it with markdown-it, comark or its own components.
    • Keep text alignment in every format: markdown wraps centered, right-aligned and justified blocks in a div with an inline style, and the block array carries an align field.
    • Keep plain text plain: text that merely looks like markdown, such as # not a heading or - not a list, is escaped in the markdown output.
    • See the Integration page snippets and the article API reference for the full contract.

    Improvements and fixes

    • Fix the text alignment buttons in the article editor, which were disabled and showed a blocked cursor. Paragraphs and headings can be aligned left, center, right or justified again, and unaligned text stays free of inline styles.
    • The web interface requests HTML explicitly when it loads, saves, publishes, unpublishes or creates an article, so the editor and the image counts behave exactly as before.

    API

    • Changed: GET /v1/article/, GET /v1/article/{id}, POST /v1/article/, PATCH /v1/article/{id}, PATCH /v1/article/{id}/publish and PATCH /v1/article/{id}/unpublish now return body as markdown by default. Pass ?format=html to keep the previous HTML body.
    • Every article object now includes body_format (markdown, html or json), which names the format of body.
    • ?format=json on the same routes returns body as a JSON array of typed blocks instead of a string.
    • An unsupported format answers 400 before anything is read or written.
    • Storage and the request bodies of POST and PATCH stay HTML.

    Under the hood

    • Add the article body renderer port in the domain, with a markdown and JSON block adapter, and tests for every supported element.
    • Update golang.org/x/crypto to v0.57.0 and add golang.org/x/net as a direct dependency for HTML parsing.
    • Add @tiptap/extension-text-align to the web interface.

    Upgrading

    • Replace the binary. No database migration or new environment variable is required.
    • Clients that read an article body as HTML must add ?format=html to their article requests, because the default is now markdown.
    • A markdown renderer needs raw HTML enabled for text alignment to show, for example html: true in markdown-it.
    Downloads