Releases: lxhwes/bramble
Releases · lxhwes/bramble
Release list
v0.1.0
First tagged release and first published container image.
Added
- Self-hosting as the primary deployment path. A single container backed by
one SQLite file on a named volume. No Cloudflare account and no external
services. - Multi-architecture images on GHCR for
linux/amd64andlinux/arm64, so
a self-hoster no longer compilesbetter-sqlite3from source. Pull
ghcr.io/lxhwes/bramble:0.1.0. /healthz, a storage-backed health probe used by the image's own
HEALTHCHECK. It runs a real query, so a corrupt database, an unwritable
/datavolume, or a failed migration marks the container unhealthy instead of
letting it report ready while every write fails.- Data retention via
BRAMBLE_RETENTION_DAYS(default 90), applied by
node build/prune.json a cron schedule you control. - Reverse-proxy support through
ADDRESS_HEADERandXFF_DEPTH, so the
in-process rate limiter keys on the real client IP. - Documentation for operators: prerequisites, a reverse-proxy example, an
upgrade path, backup and restore, and troubleshooting organised by symptom.
See the README's Self-host section. docs/DATA.mdcovering where the name dataset comes from, the filters
that decide what ships, and how to rebuild it.LICENSE-DATA.mdandstatic/names.LICENSE.txt, which document the
CC BY-SA 4.0 terms on the bundled dataset and travel inside the image.- Code of conduct (Contributor Covenant 2.1).
Changed
- SQL is the sole store for votes. Votes were previously written both as rows
and as a re-serialised blob in the key-value store, where nothing read them
and retention never deleted them — so a self-hosted SQLite file grew without
bound despite a bounded retention window. The key-value store now holds only
session metadata, and retention clears it. - A failed vote write now returns 500 instead of being swallowed. The client
keeps the batch on any non-2xx and retries on its 5-second flush interval, and
the insert is idempotent, so this converts silent loss into a retry.
Fixed
- Sessions with no votes yet are no longer deleted the moment the prune job
runs. Creating a session and sharing the link before anyone votes is the
normal flow; a nightly cron landing in that gap used to delete a session
minutes old, despite every document promising a window measured in days. - The documented SQLite backup command wrote an empty database. It let the host
shell expand$BRAMBLE_DB_PATH, which is only set inside the container, so in
cron's environmentsqlite3opened a temporary database and produced a
valid-looking backup file containing no tables. ADDRESS_HEADERandXFF_DEPTHset in.envnever reached the container, so
behind a reverse proxy every visitor shared one rate-limit bucket — five
session creates per minute for the entire site.- The About page told every self-hosted instance that its data lived in
Cloudflare KV and that Cloudflare Web Analytics was running. Both are true
only of the maintainer's hosted demo, so a self-host operator was publishing a
false privacy disclosure. These statements are now specific to the build
target. pnpm prunesilently ran pnpm's built-in dependency pruner rather than the
retention script. The script is nowpnpm prune:node.- Every match could vanish from a session.
getMatchesintersected the
liked-name sets of every slug in the session's partner list, and a slug with
no votes contributed an empty set — so a single visit to/s/{id}?p={typo}
emptied the match list, and nothing removed the slug again. Partners with no
votes are now ignored when intersecting. - A session's partner list was unbounded and grew from a
GET. Any previously
unseen?p=value was appended, so the metadata blob grew without limit and
every page load fanned out one vote lookup per slug. Capped at 8; people
already in a session still rejoin freely. - A vote batch the server could never accept was retried for the life of the
tab. The route mapped every failure to a 500 and the client only dropped a
batch on success, so a tab left open on a pruned session replayed forever.
Unknown sessions now answer 404 and full sessions 409, and the client drops a
batch on any 4xx except 429. GET /healthzdisclosed the database path. On failure it echoed the
underlying error, and SQLite embeds the file path in its messages, so an
unauthenticated probe against a misconfigured instance revealed the
filesystem layout. The body is now{"status":"error"}and the reason goes
to the container log.
Upgrade notes
Nothing to upgrade from — this is the first release. For releases after this
one, back up /data before pulling:
docker compose exec -T app sh -c 'sqlite3 "$BRAMBLE_DB_PATH" ".backup /data/pre-upgrade.sqlite"'
docker compose pull && docker compose up -dMigrations are forward-only and apply on the first request after a restart.
docker compose down -v deletes the volume and every session in it.