Skip to content
suhligPublic

About

Postfacto, but in Rust.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

504 Commits

Folders and files

Repository files navigation

Rostfacto

Postfacto (with a P) was a self-hosted retro tool aimed at helping teams run great retrospectives, remotely or in presence. This project aims to revive it, but in Rust.

Rostfacto Board

What's different to Postfacto

Rostfacto (with an R) keeps the retro flow that Postfacto (with a P) made popular — cards, highlighting, timers, action items — and rebuilds it from scratch:

  • It's actively maintained. Postfacto is archived; Rostfacto is developed in the open, released on a regular basis, and covered by an end-to-end test suite.
  • One binary, one dependency. Rostfacto is written in Rust and ships as a single static binary — all you need besides it is PostgreSQL. No Ruby on Rails, no Node.js build step, no Redis.
  • Sign in with GitHub, access via teams. Instead of per-retro passwords and an admin dashboard, sign-in uses GitHub (or GitHub Enterprise), admins are a GitHub team, and every retro belongs to a team whose members can see and edit it.
  • Real-time sync you can trust. Every change lands in a durable event log in Postgres and is pushed to all connected clients over server-sent events; a client that reconnects replays everything it missed.
  • See who's in the room. A live participants panel shows everyone currently on the board; presence lives in Postgres, so it stays correct even when Rostfacto runs as several processes behind a load balancer and never flickers on reconnects.
  • A timer everyone agrees on. The five-minute highlight countdown is server-authoritative: the deadline lives in the database, so every participant sees the same time, and it can be extended by two minutes.
  • No analytics, no tracking. Rostfacto ships without analytics or tracking scripts — your retrospectives stay on your server.

Run

brew install postgresql@18
brew services start postgresql@18

# Create a dedicated database role for the app
createuser --createdb rostfacto
createdb -O rostfacto rostfacto-dev

export DATABASE_URL=postgres://rostfacto@localhost/rostfacto-dev
cargo install sqlx-cli
sqlx migrate run
# Without the GitHub variables below the app refuses to start: run unsecured
# local development with DEMO_MODE=1 or configure GITHUB_ADMIN_ORG etc.
export DEMO_MODE=1
cargo watch -x run

Docker Compose

Quickest way to run the whole stack (database, migrations, app) — no local build required:

cp .env.example .env
# either fill in the GitHub auth values (see the Authentication section below),
# or set DEMO_MODE=1 and leave the GitHub variables blank for an unsecured demo
docker compose up -d

All secrets come from your .env (which is gitignored). The compose file requires only the database passwords: POSTGRES_PASSWORD for the migration user and POSTGRES_APP_PASSWORD for the least-privilege rostfacto_app role the app connects as. The GitHub auth variables are optional: set GITHUB_ADMIN_ORG, GITHUB_ADMIN_TEAM_SLUG, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET and PUBLIC_URL to run with GitHub authentication, or set DEMO_MODE=1 and leave them blank for an unsecured demo instance (never in production). The app fails closed when neither is configured. On first boot, docker/init-app-role.sh creates the rostfacto_app role with only SELECT/INSERT/UPDATE/DELETE privileges; for an existing database volume, run it manually once:

docker compose exec db /docker-entrypoint-initdb.d/init-app-role.sh

Images are pulled from the GitHub Container Registry and pinned by digest for reproducible deployments: ghcr.io/suhlig/rostfacto for the app and ghcr.io/suhlig/rostfacto-migrator for the one-shot migration step. Both are also tagged latest and sha-<commit> on every push to main that changes image-relevant files (the Container workflow path filter, which keeps the digest-bump PRs from triggering their own rebuild); every GitHub release additionally tags them with the release version, e.g. ghcr.io/suhlig/rostfacto:v2.0.

The app is published on port 3000. To run it on a different host port, set APP_PORT:

APP_PORT=8080 docker compose up -d

or put APP_PORT=8080 in the .env file; the default stays 3000. With GitHub authentication enabled, make sure PUBLIC_URL matches the chosen port (see the table below).

Authentication (GitHub)

Set DEMO_MODE=1 to run the app without authentication (every visitor is treated as an admin and a red banner warns that the instance is unsecured). Otherwise the app fails closed: it refuses to start until the GitHub authentication environment variables below are configured. To enable GitHub or GitHub Enterprise authentication, configure the following environment variables:

Variable Where to get it
DEMO_MODE Set to 1 to run without authentication; never set it in production.
PUBLIC_URL The public base URL of your deployment, e.g. https://rostfacto.example.com. Used to build the OAuth callback URL. Required whenever authentication is enabled.
GITHUB_CLIENT_ID Create an OAuth App at https://github.com/settings/developers (or on your GHE instance).
GITHUB_CLIENT_SECRET Same OAuth App page as above.
GITHUB_ADMIN_ORG The GitHub organization that contains your admin team. You can see your orgs at https://github.com/settings/organizations.
GITHUB_ADMIN_TEAM_SLUG Inside that org, open the team page and take the slug from the URL, e.g. https://github.com/orgs/ORG/teams/TEAM_SLUG.
GITHUB_USER_ORG Colon-separated list of organizations whose teams can be assigned to retros, e.g. org-a:org-b. Usually includes GITHUB_ADMIN_ORG. The retro creation form lists all teams GitHub exposes to the signed-in user across these orgs: you must be a member (or owner) of an org to see its teams, and secret teams you do not belong to are never exposed by GitHub. Teams are always shown and stored org-qualified, e.g. org-a/engineering.
GITHUB_APP_OWNER Optional name or email of the person to contact when an org's teams cannot be listed (e.g. because the org enforces SAML SSO and the app is not authorized for it). Shown on the retro creation form.
GITHUB_ENTERPRISE_URL Base URL of your GitHub Enterprise Server, e.g. https://github.example.com. Leave unset for github.com.

When you create the OAuth App, set the callback URL to:

<PUBLIC_URL>/auth/callback

Real-time sync

Multiple clients on the same retro stay in sync via server-sent events (SSE):

  • The board subscribes to GET /retro/{slug}/events; every mutation (card added, status changed, liked, edited, timer changed, retro archived) is pushed to all connected clients immediately.
  • Postgres is the hub: each mutation writes its event to an events table in the same transaction and NOTIFYs a channel that a background task fans out to the connected browsers. The event log is durable, so a client that reconnects catches up on everything it missed (Last-Event-ID replay).
  • A client's own mutations are deduplicated, so the HTMX response and the SSE event for the same change are applied exactly once.
  • The highlight timer is server-authoritative: highlighting a card starts a five-minute countdown in the database, the +2 min button extends it, and a background sweep marks it elapsed so every client sees 0:00 at the same time. The countdown ticks locally, but the deadline always comes from the server.
  • Archiving a retro (or completing the last card, via the all-done modal) clears the board and stops all timers on every connected client.

Test

The integration tests live in tests/integration_test.rs (with the test functions grouped by feature area under tests/integration_test/) and use thirtyfour to drive Firefox via geckodriver. They start their own instance of the app on a random port, so you can keep your dev server running on port 3000.

Prerequisites

On macOS:

brew install geckodriver
brew install --cask firefox

On other systems, install geckodriver and make sure it is on your PATH, and install a recent Firefox.

Running the tests

export DATABASE_URL=postgres://rostfacto@localhost/rostfacto-dev
cargo test --test integration_test

Set SHOW_BROWSER to make Firefox visible while the tests run:

SHOW_BROWSER=1 cargo test --test integration_test

TODO

Backlog

  • Limit growth of the events table
  • Clean archived retros after e.g. a year
  • Periodic cleanup of sessions

Maybe

  • Allow adding a card anonymously

License

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

See the LICENSE file for full license text.

The concept and some of the artwork was inspired by Postfacto (with a P), reproduced under the GNU Affero General Public License.

About

Postfacto, but in Rust.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages