A fork of writefreely, in order to add features I'd like.
  • JavaScript 82%
  • Go 11.6%
  • Go Template 4%
  • Python 1.2%
  • Less 1%
  • Other 0.1%
Find a file
loweel 50efda6ad9
All checks were successful
continuous-integration/drone/push Build is passing
Allow owners to remove newsletter subscribers
2026-07-03 20:38:31 +02:00
.github Initial Blogfrei import 2026-06-12 23:34:46 +02:00
appstats Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
author Initial Blogfrei import 2026-06-12 23:34:46 +02:00
blogfrei Render Blogfrei horizontal rules 2026-06-20 13:44:29 +02:00
cmd/writefreely Add Bilberry theme and complete Blogfrei rebrand 2026-06-13 17:28:46 +02:00
config Add database-backed newsletter email settings 2026-07-03 11:30:40 +02:00
db Initial Blogfrei import 2026-06-12 23:34:46 +02:00
key Initial Blogfrei import 2026-06-12 23:34:46 +02:00
keys Initial Blogfrei import 2026-06-12 23:34:46 +02:00
less Add owner read count toggle 2026-06-28 21:24:03 +02:00
LICENSES Strengthen mixed-license documentation 2026-06-14 19:16:26 +02:00
mailer Send newsletter posts one email at a time 2026-07-03 16:19:26 +02:00
migrations Send newsletter posts one email at a time 2026-07-03 16:19:26 +02:00
oauth Initial Blogfrei import 2026-06-12 23:34:46 +02:00
page Add configurable public footer snippets 2026-07-03 14:14:21 +02:00
pages Add Bilberry theme and complete Blogfrei rebrand 2026-06-13 17:28:46 +02:00
parse Initial Blogfrei import 2026-06-12 23:34:46 +02:00
prose Update DOMPurify audit override 2026-06-19 16:41:01 +02:00
scripts Remove phone-home and maintain ActivityPub subscribers 2026-06-13 10:05:52 +02:00
smoketest Allow owners to remove newsletter subscribers 2026-07-03 20:38:31 +02:00
spam Initial Blogfrei import 2026-06-12 23:34:46 +02:00
static Add default theme pagination arrows 2026-06-29 08:41:46 +02:00
templates Allow owners to remove newsletter subscribers 2026-07-03 20:38:31 +02:00
testdata Initial Blogfrei import 2026-06-12 23:34:46 +02:00
themes/default Let custom footer replace default branding 2026-07-03 14:59:41 +02:00
.dockerignore Initial Blogfrei import 2026-06-12 23:34:46 +02:00
.drone.yml Update Drone registry for Forgejo 2026-06-19 16:23:05 +02:00
.editorconfig Initial Blogfrei import 2026-06-12 23:34:46 +02:00
.gitignore Add Blogfrei editor image storage and container pipeline 2026-06-13 00:14:12 +02:00
.golangci.yml Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
account.go Allow owners to remove newsletter subscribers 2026-07-03 20:38:31 +02:00
account_import.go Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
activitypub.go Add federation domain blocklist 2026-06-14 02:51:21 +02:00
activitypub_post_test.go Use post titles as federated summaries 2026-06-19 19:14:39 +02:00
activitypub_test.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
admin.go Add configurable public footer snippets 2026-07-03 14:14:21 +02:00
admin_config_test.go Add configurable public footer snippets 2026-07-03 14:14:21 +02:00
AGENTS.md Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
ap_subscriber_maintenance.go Remove phone-home and maintain ActivityPub subscribers 2026-06-13 10:05:52 +02:00
ap_subscriber_maintenance_test.go Remove phone-home and maintain ActivityPub subscribers 2026-06-13 10:05:52 +02:00
app.go Add configurable public footer snippets 2026-07-03 14:14:21 +02:00
auth.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
AUTHORS.md Define Blogfrei licensing and first milestone 2026-06-12 23:43:47 +02:00
bootstrap_test.go Repair missing ActivityPub schema objects 2026-06-19 16:34:32 +02:00
cache.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
collections.go Add database-backed newsletter email settings 2026-07-03 11:30:40 +02:00
CREDITS.md Strengthen mixed-license documentation 2026-06-14 19:16:26 +02:00
database-lib.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
database-no-sqlite.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
database-sqlite.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
database.go Allow owners to remove newsletter subscribers 2026-07-03 20:38:31 +02:00
database_activitypub.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
database_test.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
docker-compose.prod.yml Update Drone registry for Forgejo 2026-06-19 16:23:05 +02:00
docker-compose.yml Update Drone registry for Forgejo 2026-06-19 16:23:05 +02:00
docker-setup.sh Initial Blogfrei import 2026-06-12 23:34:46 +02:00
Dockerfile Strengthen mixed-license documentation 2026-06-14 19:16:26 +02:00
Dockerfile.prod Update Drone registry for Forgejo 2026-06-19 16:23:05 +02:00
email.go Send newsletter posts one email at a time 2026-07-03 16:19:26 +02:00
errors.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
export.go Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
federation_blocklist.go Add federation domain blocklist 2026-06-14 02:51:21 +02:00
federation_blocklist_test.go Add federation domain blocklist 2026-06-14 02:51:21 +02:00
feed.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
go.mod Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
go.sum Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
gopher.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
handle.go Serve CSS with the correct MIME type 2026-06-13 17:49:47 +02:00
hostmeta.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
hugo_archive.go Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
hugo_archive_test.go Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
images.go Use blog avatar as favicon 2026-07-03 13:38:55 +02:00
images_test.go Use blog avatar as favicon 2026-07-03 13:38:55 +02:00
instance.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
invites.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
jobs.go Fix newsletter subscription smoke coverage 2026-07-03 12:43:19 +02:00
keys.go Bootstrap keys and database automatically 2026-06-13 09:26:37 +02:00
LEGAL.md Strengthen mixed-license documentation 2026-06-14 19:16:26 +02:00
LICENSE Strengthen mixed-license documentation 2026-06-14 19:16:26 +02:00
main_test.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
Makefile Add Hugo archive export and import 2026-06-14 18:42:57 +02:00
monetization.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
nodeinfo.go Add Bilberry theme and complete Blogfrei rebrand 2026-06-13 17:28:46 +02:00
oauth.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
oauth_generic.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
oauth_gitea.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
oauth_gitlab.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
oauth_signup.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
oauth_slack.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
oauth_test.go Add Blogfrei editor image storage and container pipeline 2026-06-13 00:14:12 +02:00
oauth_writeas.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
ossl_legacy.cnf Initial Blogfrei import 2026-06-12 23:34:46 +02:00
outbound.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
outbound_test.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
pad.go Remove phone-home and maintain ActivityPub subscribers 2026-06-13 10:05:52 +02:00
pad_test.go Remove phone-home and maintain ActivityPub subscribers 2026-06-13 10:05:52 +02:00
pages.go Add Bilberry theme and complete Blogfrei rebrand 2026-06-13 17:28:46 +02:00
post_list_read_count_test.go Restore default theme post pin controls 2026-07-03 13:12:32 +02:00
post_title_test.go Set fallback titles for published posts 2026-06-19 19:18:07 +02:00
postrender.go Render Blogfrei horizontal rules 2026-06-20 13:44:29 +02:00
postrender_test.go Fix newsletter subscription smoke coverage 2026-07-03 12:43:19 +02:00
posts.go Fix newsletter subscription smoke coverage 2026-07-03 12:43:19 +02:00
posts_test.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
profile.go Add federated profile settings 2026-06-13 23:26:06 +02:00
read.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
README.md Fix newsletter subscription smoke coverage 2026-07-03 12:43:19 +02:00
request.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
routes.go Allow owners to remove newsletter subscribers 2026-07-03 20:38:31 +02:00
routes_test.go Serve CSS with the correct MIME type 2026-06-13 17:49:47 +02:00
schema.sql Fix newsletter subscription smoke coverage 2026-07-03 12:43:19 +02:00
SECURITY.md Initial Blogfrei import 2026-06-12 23:34:46 +02:00
semver.go Initial Blogfrei import 2026-06-12 23:34:46 +02:00
session.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
sitemap.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
smoketest.py Add Blogfrei editor image storage and container pipeline 2026-06-13 00:14:12 +02:00
sqlite.sql Initial Blogfrei import 2026-06-12 23:34:46 +02:00
subscriber_stats.go Add ActivityPub follower instance summary 2026-06-15 09:18:51 +02:00
subscriber_stats_test.go Add ActivityPub follower instance summary 2026-06-15 09:18:51 +02:00
templates.go Add database-backed newsletter email settings 2026-07-03 11:30:40 +02:00
unregisteredusers.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00
users.go Add Blogfrei editor image storage and container pipeline 2026-06-13 00:14:12 +02:00
view_stats.go Refine audience language and device stats 2026-06-15 08:58:58 +02:00
view_stats_test.go Show audience percentages with two decimals 2026-06-15 14:30:02 +02:00
webfinger.go Eliminate inherited technical debt 2026-06-13 00:48:50 +02:00

Blogfrei

Blogfrei is a self-hosted publishing platform focused on independent writing, simple operation, and a modern editing experience.

The project starts as a fork of WriteFreely, but follows its own direction from this point onward.

First Implementation Milestone

The first Blogfrei implementation milestone replaces the inherited default editor with TOAST UI Editor, an open-source Markdown WYSIWYG editor licensed under the MIT License.

The first milestone also includes image handling for posts:

  • Writers can upload and insert images directly from the editor.
  • Blogfrei saves uploaded image files as images/<md5>.<extension>.
  • The editor inserts the URL of the stored image into the canonical Markdown post instead of embedding base64 image data.
  • Blogfrei serves the stored images directly.

The container exposes /go/images as a volume. Uploaded images are available from the application at /images/<md5>.<extension>.

Select the editor under [app] in config.ini:

[app]
editor = wysywig

wysywig and wysiwyg select the Blogfrei TOAST UI editor and are the default when the option is absent. pad and bare keep the inherited plain Markdown editors available.

Public Theme

Blogfrei bundles an MIT-licensed default public theme derived from the Bilberry Hugo Theme. All static assets are vendored in the repository, so public pages do not need to contact an external CDN.

Themes follow Hugo's directory conventions under themes/<name>/layouts. Select the public theme under [app]:

[app]
blog_theme = default

default is used when the option is absent. Existing bilberry values are migrated transparently for compatibility. The inherited theme configuration option and per-blog Custom CSS editor are no longer used; public appearance belongs to the selected Hugo-style theme.

Network Policy

Blogfrei does not perform vendor update checks, telemetry, or load remote embedded content by default. In particular, it does not contact WriteFreely, write.as, or GitHub for updates or telemetry.

Outbound traffic is limited to features explicitly configured or requested: ActivityPub federation, OAuth providers, email delivery, Web Monetization, and links opened by a user. When federation is enabled, Blogfrei verifies remote ActivityPub subscribers in the background immediately after startup and every eight hours. Valid actors have their inboxes and public keys refreshed; unreachable or invalid actors and their local follow records are removed.

The ActivityPub followers page summarizes followers by originating instance before the complete follower list. Instances with one follower are grouped as Others; repeated instances receive their own pie-chart segment, and the summary table shows at most ten entries.

Email Newsletters

Admins configure newsletter delivery from Admin → Settings → Email Delivery. The SMTP relay, newsletter sender address, and sender display title are stored in the application database; the SMTP password is encrypted and stored as text safe for both SQLite and MySQL.

When email delivery is configured and a blog enables email subscriptions, the public subscription form is available at /subscribe for single-user blogs and /<blog>/subscribe for multi-user blogs. Newsletter emails contain the complete rendered post HTML, including Markdown images rewritten with absolute image URLs.

Audience Statistics

The Stats page retains the inherited per-post view and ActivityPub like counts and adds top-ten audience distributions with local CSS pie charts:

  • browser families derived from the visitor's user-agent header;
  • mobile or computer device classes derived from the user-agent header;
  • primary browser languages, with regional variants grouped together; and
  • referrer origins with links.

Audience percentage tables display two decimal places.

Only visits already classified as human are recorded. Blogfrei stores neither visitor IP addresses nor complete referrer URLs: referrers are reduced to their http:// or https:// origin before storage. Detailed statistics begin accumulating after the database reaches migration V19.

Language values are intentionally synthetic: for example, de, de-DE, and de-AT are all recorded as de.

Container Compatibility

The Blogfrei container is initially a drop-in replacement for the WriteFreely container. An existing deployment should be able to replace its image with git.keinpfusch.net/loweel/blogfrei:latest while keeping the same configuration file, database, keys, mounted paths, port, and startup command.

Until an explicit migration is provided, changes must preserve compatibility with existing WriteFreely installations and data.

The container is built with Go 1.26.4 and uses Debian 13 as its runtime base.

Goals

Develop new Blogfrei work under the EUPL

New original Blogfrei work is licensed under the European Union Public Licence v1.2 (EUPL-1.2). Code inherited from WriteFreely, and modifications derived from that code, remain covered by the GNU Affero General Public License v3.0 (AGPL-3.0).

Add a true WYSIWYG editor

Replace the current editing experience with TOAST UI Editor, providing a real JavaScript WYSIWYG editor whose canonical output remains Markdown.

The editor should provide direct visual formatting while preserving clean, portable content and a focused writing experience. Blogfrei will preserve the TOAST UI Editor attribution and MIT licence notice.

Manage uploaded images

Allow writers to insert and upload images directly from the WYSIWYG editor.

Blogfrei should save uploaded images in a dedicated, configurable folder and serve them directly as part of the published blog. Image files should remain easy to inspect, back up, restore, and migrate together with Markdown content.

Store posts as Markdown files

Store blog posts primarily as Markdown files on the filesystem instead of in the database.

Each post should have a stable path based on its author and identifier, for example:

data/<author>/<post-id>.md

The Markdown files are the source of truth and should be easy to inspect, version, copy, restore, and migrate. The database may still hold accounts, configuration, and rebuildable indexes, but not the canonical post content.

Export portable archives

Export creates a persistent Hugo-compatible tree under /go/export/<user>/content. Every post is a Markdown file with standard Hugo front matter plus Blogfrei restore metadata, including its user and blog. Referenced local images are copied to Hugo's static/images tree. Subsequent exports add missing posts and images and atomically refresh changed posts before creating the downloadable ZIP.

Import accepts this ZIP, safely extracts it under /go/import, and restores or updates posts in the database from the front matter. The declared user and blog must belong to the logged-in account.

An export should not require a database dump. Its contents should remain readable and usable without Blogfrei, making backup and migration simple.

Support Hugo themes

Allow blogs to load and use themes in the Hugo theme format.

This should make it possible to reuse the existing Hugo theme ecosystem while keeping theme installation and selection straightforward for Blogfrei administrators and writers.

Current Status

Blogfrei is at the beginning of its independent development. The current codebase provides the technical foundation inherited from WriteFreely; project identity, documentation, user experience, and deployment practices are being reworked for Blogfrei.

Development

Blogfrei is written primarily in Go, with JavaScript and LESS used for the web interface.

make
make test

Docker and Compose files are also available for local development and testing.

At normal startup Blogfrei automatically creates any missing encryption keys under /go/keys and initializes an empty configured database. Existing keys and initialized databases are left unchanged. Persist /go/keys, /go/images, /go/export, /go/import, and the database storage as shown in the included Compose files; do not use docker compose down -v unless those volumes should be deleted.

For NFS-backed volumes, /go/keys, /go/images, /go/export, and /go/import must be writable by the container runtime user daemon (numeric UID/GID 1:1). Database storage requires an NFS server and mount options that provide reliable POSIX locking; do not share one live database volume between multiple database containers.

Definition of Done

Every Blogfrei change is complete only when:

  • The requested implementation is finished.
  • Relevant automated tests have been added or updated.
  • The complete test suite passes.
  • The race detector passes.
  • Go source files pass gofmt.
  • golangci-lint passes.
  • gosec passes.
  • The completed change is committed and pushed to the canonical repository.

Run the local quality checks with:

make dod

The full gate includes the smoke-test suite in smoketest/. It verifies the editor, valid and invalid image storage cases, zero gosec findings, and the built container contract. Run it separately with ./smoketest.py; use ./smoketest.py --build to include an additional clean Docker build.

The security gate requires zero gosec findings, zero reachable Go vulnerabilities reported by govulncheck, and zero known npm vulnerabilities. golangci-lint enforces govet, errcheck, staticcheck, and ineffassign.

Repository

The canonical repository is:

https://git.keinpfusch.net/loweel/blogfrei

License

Blogfrei uses a mixed-licence model:

  • New original Blogfrei work: EUPL-1.2.
  • Code inherited from WriteFreely and derivative modifications: AGPL-3.0.
  • Independent third-party components: their respective licences.

See LICENSE, LEGAL.md, CREDITS.md, and the licence texts under LICENSES/. Copyright for inherited and third-party material remains with its respective copyright holders.