No description
  • Go 91.7%
  • HTML 4%
  • Shell 3%
  • CSS 1.2%
Find a file
Jimmy Berglund 555dbe5413
All checks were successful
CI / build (push) Successful in 11m20s
Serve a robots.txt that keeps per-user pages out of search indexes
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>
2026-10-01 12:57:06 +02:00
.forgejo/workflows GoThread 1.0.0 2026-08-23 21:39:37 +02:00
cmd/server Refresh query planner statistics at every UTC midnight 2026-09-29 14:14:07 +02:00
internal Serve a robots.txt that keeps per-user pages out of search indexes 2026-10-01 12:57:06 +02:00
scripts GoThread 1.0.0 2026-08-23 21:39:37 +02:00
.dockerignore GoThread 1.0.0 2026-08-23 21:39:37 +02:00
.gitignore GoThread 1.0.0 2026-08-23 21:39:37 +02:00
CHANGELOG.md GoThread 1.0.3 2026-09-29 14:21:28 +02:00
config.example.conf GoThread 1.0.0 2026-08-23 21:39:37 +02:00
docker-compose.yml GoThread 1.0.0 2026-08-23 21:39:37 +02:00
Dockerfile GoThread 1.0.3 2026-09-29 14:21:28 +02:00
go.mod GoThread 1.0.0 2026-08-23 21:39:37 +02:00
go.sum GoThread 1.0.0 2026-08-23 21:39:37 +02:00
LICENSE GoThread 1.0.0 2026-08-23 21:39:37 +02:00
Makefile GoThread 1.0.3 2026-09-29 14:21:28 +02:00
README.md Refresh query planner statistics at every UTC midnight 2026-09-29 14:14:07 +02:00

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 Range support.
  • 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 ModBanFromCommunity events 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 --help for every flag, or hit /healthz for 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 localhost must 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

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 — /forgot mails 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/reset lets 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_email in 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 replay
  • moderation_smoke.sh — blocks, thread mods, reports, audit log, announced bans
  • media_smoke.sh — multipart uploads, media serve/Range/rejections
  • registration_smoke.sh — open/closed toggle, invites, optional email
  • interop_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 shutdown
  • internal/config/ — flags/env/file config
  • internal/mail/ — SMTP sending (net/smtp only, no new dependencies)
  • internal/storage/ — SQLite via database/sql, embedded migrations
  • internal/ap/ — AS2/AP vocab with tolerant decode
  • internal/httpsig/ — HTTP signatures
  • internal/federation/ — delivery queue, dedup, fetch-by-ID, SSRF guard
  • internal/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).