- Go 91.7%
- HTML 4%
- Shell 3%
- CSS 1.2%
|
All checks were successful
CI / build (push) Successful in 11m20s
Crawlers had no guidance, so they were invited into /settings, /notifications, /lists, /saved and /admin: pages that are either per-account or admin-only, and that index into nothing useful since the site already exposes its public content through RSS and ActivityPub. The file is embedded next to the stylesheet and LICENSE and served the same way /license is, so it ships in the binary and the container image with no extra deployment step, and registering it ahead of the catch-all keeps it off the custom 404. Only the five private prefixes are blocked. Community, profile, post and RSS paths stay crawlable, which is the point of having the file at all. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| cmd/server | ||
| internal | ||
| scripts | ||
| .dockerignore | ||
| .gitignore | ||
| CHANGELOG.md | ||
| config.example.conf | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
GoThread
A lightweight, spec-compliant threadiverse (Reddit-like) server that federates over ActivityPub. One static Go binary, one SQLite file, a built-in server- rendered UI — self-host in minutes.
GoThread speaks standard ActivityStreams 2.0 types and interops with the threadiverse: it is smoke-tested round-trip against Lemmy (follow, post, comment, vote, and moderation both directions).
Features
- Full local forum — communities, posts, comments with threaded sorting (active/hot/new/top/controversial + time windows), votes, markdown-lite, search, RSS, pagination, profiles, notifications (mentions/replies/participation), private multireddit lists, NSFW communities.
- Media hosting — images and video uploads stored in the same SQLite file,
served with
Rangesupport. - Hand-rolled ActivityPub — AS2 vocab with tolerant decode, HTTP signatures (sign RSA, verify RSA + Ed25519), WebFinger, NodeInfo, inbox/outbox, fetch-by-ID, and a delivery queue with retries, backoff and idempotent dedup.
- Moderation — removes/restores, deletes, pins, locks, reports and a mod
log, personal blocks, community bans and instance allow/blocklists. Cross-
instance moderation works: bans propagate to remote instances and remote
Lemmy
ModBanFromCommunityevents are enforced locally. - Admin — rename the instance, toggle registration open/closed with invite links, a federation-health dashboard (queue depth, failures, replay), and single-file backups.
- Email (optional) — SMTP-verified accounts and password resets. With a mail server configured, new accounts must confirm their email before signing in, email changes are staged until verified, unconfirmed registrations are purged after 7 days, and forgotten passwords get a one-time reset link.
Requirements
Go 1.26+ to build. Runtime is a single static binary (no cgo, no external
services); modernc.org/sqlite is the only dependency.
Quick start
go build -o gothread ./cmd/server
./gothread
The defaults are https on 127.0.0.1:8040 with a forum_like.db next to the
binary. For local development over plain HTTP:
./gothread -scheme http -domain 127.0.0.1:8040
For a real instance, copy the example config and edit it:
cp config.example.conf my.conf
# edit my.conf (set DOMAIN, INSTANCE_NAME, SMTP_*, etc.)
./gothread -config my.conf
The config file is KEY = value lines with comments; see
config.example.conf for every key and its default.
- The first registered account becomes the instance admin.
- Register a community, then post — no further setup required.
- Check
./gothread --helpfor every flag, or hit/healthzfor a health check.
Docker
docker build -t gothread .
docker run -p 8040:8040 -e FORUM_LIKE_DOMAIN=forum.example -v gothread-data:/app/data gothread
Configuration
Precedence: defaults < config file < environment < flags. Env vars are
FORUM_LIKE_*; the config file is KEY = value lines (see --help for keys).
Notable settings:
| Flag / env | Default | Purpose |
|---|---|---|
-scheme / SCHEME |
https |
Public URL scheme (http for dev) |
-domain / DOMAIN |
host:port | Public domain, used for federation IDs |
-instance-name / INSTANCE_NAME |
GoThread |
Display name shown in the navbar and titles (seed; change later in /admin) |
-registration-mode / REGISTRATION_MODE |
open |
open or closed (invite links) — a one-time seed; later toggled in /admin/registration |
-federation-mode / FEDERATION_MODE |
blocklist |
blocklist admits all non-blocked hosts; allowlist admits only allowed hosts |
-allow-private-federation |
off | Allow federation to/from private, loopback and link-local addresses (dev/LAN) |
-require-email / REQUIRE_EMAIL |
true |
Make an email mandatory at registration. Without an SMTP server configured, registration is blocked until one is set |
-smtp-host / SMTP_HOST |
— | SMTP server for email verification and password resets (empty disables all outbound mail) |
-smtp-port / SMTP_PORT |
587 |
SMTP port |
-smtp-username / SMTP_USERNAME |
— | SMTP username (PLAIN auth, only over TLS or localhost) |
-smtp-password / SMTP_PASSWORD |
— | SMTP password |
-smtp-from / SMTP_FROM |
noreply@<domain> |
Sender address on outbound mail |
-smtp-security / SMTP_SECURITY |
starttls |
starttls (587), ssl (implicit TLS, 465), or none (plaintext, local dev only) |
-smtp-insecure / SMTP_INSECURE |
off | Skip TLS certificate verification (test servers) |
-rate-limit-enabled / RATE_LIMIT_ENABLED |
off | Per-client-IP rate limiting (set -trusted-proxies behind a proxy) |
-backup-dir / BACKUP_DIR |
backups |
Where /admin/backup writes snapshots |
-backup |
— | One-shot: write a snapshot and exit (cron-friendly) |
Federation notes
- SSRF guard: outbound federation to private/loopback/link-local targets is
refused by default. Two instances on
localhostmust both pass-allow-private-federation. - Delivery is asynchronous with retries and backoff. Watch failures and
replay dropped deliveries from the admin federation dashboard (
/admin/federation).
Email is opt-in via -smtp-host; without it, addresses are collected but never
verified. Admins can still mint one-time password reset links by hand (see below).
With a mail server configured:
- Registration — a supplied address must be confirmed from a link before the account can sign in. The username stays reserved until the link is used (or the account is purged after 7 days). If the confirmation email cannot be sent, the registration is rolled back and the username is freed.
- Login — unverified accounts get a resend page instead of a session.
- Password reset —
/forgotmails a one-time, 1-hour link; responses are deliberately identical whether or not the address exists. Resetting revokes every live session. - Admin reset links — with or without SMTP,
/admin/resetlets an admin mint a one-time, 1-hour reset link for any local account (by username or email). Issuing one supersedes any earlier reset link for that account. - Email changes — the current address stays active until the new one is
confirmed; a staged change sits as
pending_emailin settings. - A janitor runs hourly: unverified registrations older than 7 days, spent or expired tokens, and expired sessions are removed.
- Query planner statistics are refreshed on startup and again at every UTC midnight. This only changes how fast queries run, never their results.
Backup & restore
Everything — posts, media, keys — lives in the one SQLite file.
- Backup:
/admin/backup(admin), or./gothread -backup /path/snapshot.db. - Restore: stop the server, replace the DB file with the snapshot, start again.
Development
go build ./... # build
go vet ./... # vet
go test ./... # full test suite (457+ tests, no external services)
gofmt -l . # format check, must print nothing
Smoke suites
Real servers on fixed ports (scripts/), run with go and curl:
federation_smoke.sh— two-instance federation and delivery replaymoderation_smoke.sh— blocks, thread mods, reports, audit log, announced bansmedia_smoke.sh— multipart uploads, media serve/Range/rejectionsregistration_smoke.sh— open/closed toggle, invites, optional emailinterop_smoke.sh— Lemmy round-trip (requires podman + podman-compose)
internal/httpsig/testdata/*.http and internal/ap/testdata/*.json are frozen
requests captured from the interop harness; they pin the signer/verifier to
on-wire shapes.
Layout
cmd/server/— entrypoint, wiring, graceful shutdowninternal/config/— flags/env/file configinternal/mail/— SMTP sending (net/smtponly, no new dependencies)internal/storage/— SQLite viadatabase/sql, embedded migrationsinternal/ap/— AS2/AP vocab with tolerant decodeinternal/httpsig/— HTTP signaturesinternal/federation/— delivery queue, dedup, fetch-by-ID, SSRF guardinternal/web/— routes, sessions, server-rendered templates, admin UI
License
AGPL-3.0. If you modify GoThread and serve it over a network, you must offer users the modified source (section 13).