Join Nostr
2026-07-01 15:58:03 UTC

Stefano on littleFedi on Nostr: ## LittleFedi: light, yet complete I'm writing this post from a Raspberry Pi Zero W - ...

## LittleFedi: light, yet complete

I'm writing this post from a Raspberry Pi Zero W - 512 MB RAM, single-core ARMv6 - running NetBSD, powered by littleFedi. The process sits at 33 MB RSS, CPU basically asleep:

```
load averages: 0.05, 0.08, 0.09
CPU states: 0.0% user, 0.0% nice, 0.0% system, 1.0% interrupt, 99.0% idle
Memory: 301M Free
PID COMMAND RES STATE
2082 littlefedi-armv6 33M kqueue
```

No Redis. No PostgreSQL (but optional). No Sidekiq. No Node.js build pipeline. One statically-linked binary, one SQLite file, and the full fediverse experience. And this is the part people tend to miss: the same binary that runs happily on a Pi Zero scales, on the right hardware, to numbers that have nothing to do with "lightweight". It's not a toy that stays a toy. It's built to grow when you need it to.

### What LittleFedi actually ships

**Federation** - Full ActivityPub S2S: WebFinger, NodeInfo 2.1, host-meta, HTTP Signatures with anti-impersonation checks. Per-user and shared inboxes, outbox, followers, following, featured collections. Thread completion with bounded on-demand fetching of missing ancestors/replies (separate sync and background budgets, all hard-capped). Quote posts via FEP-044f with the full approval handshake (`QuoteRequest` -> `QuoteAuthorization`), matching Mastodon 4.4 semantics, plus `_misskey_quote` and Fedibird `quoteUri` aliases. Account migration (`Move`), both outgoing and incoming, with `alsoKnownAs` linking, automatic follower migration, follow import from AP collections, and CSV export/import. Remote interaction discovery - async like/boost resolution from origin servers with REST fallback.

**Mastodon API** - Broad coverage: timelines (home, public, local, hashtag, bubble, direct), status CRUD with edits, scheduled posts, polls, bookmarks, lists (with `replies_policy` and `exclusive`), filters v2 (keyword CRUD), featured tags, followed hashtags (posts appear in home), markers, conversations, notifications with type exclusion, follow requests, blocks (federated `Block`/`Undo`), mutes with duration/expiry and `hide_notifications`, per-user domain blocks. OAuth2 with app registration, authorization code, client credentials and refresh_token flows, PKCE, consent screen, and scope enforcement (`read`/`write`/`admin:read`/`admin:write`). It talks fine to Elk, Tusky, Ivory, Phanpy, Semaphore and MastoBlaster.

**Streaming** - WebSocket and SSE. In-process pub/sub hub with per-connection send buffers, zero cost when no client is connected. Broadcast streams for public, local, remote, hashtags and lists. Per-account streams for user timeline, notifications and direct messages. Optional PostgreSQL `LISTEN`/`NOTIFY` backend for cross-process fan-out. Mastodon-compatible event serialization.

**Push notifications** - Full Web Push / VAPID (RFC 8030/8291) with `aes128gcm` encryption. Per-type alert toggles (mention, follow, reblog, favourite, poll, follow_request, status). Notify-bell support on followed accounts. Subscription expiry detection, rate-limit handling, 5-retry delivery.

**Media pipeline** - Upload processing: thumbnail generation (600x600), blurhash computation, EXIF stripping, magic-byte validation, SVG rejection, MIME mismatch detection, UUID-based file renaming. Size limits (40 MB default), pixel caps (16 MP default, tunable down to 4 MP for SBCs).

**Media privacy proxy** - This is the part I actually care about most. All remote media streams through the instance via HMAC-signed URLs (`/proxy/media?url=...&sig=...`), so local users never expose their IP address to remote servers. SSRF-guarded: DNS resolution check, private/CGNAT IP rejection, redirect re-validation. Pure `io.Copy` pass-through, no disk, no decode, ~32 KB buffer. Forwards HTTP `Range` requests for audio/video seeking. Configure a `proxy_secret` for stable URLs across restarts. On low-RAM devices, set `cache_remote = "off"` and you still see every image on the fediverse, the instance just doesn't store or process them.

**Remote media caching** - Three modes: `off`, `eager` (background sweep caches all remote attachments, avatars, headers and emoji, backfills existing on mode switch), `lazy` (cache on first access). Content-addressed, deduplicated by origin URL. Age-based pruning with file GC. Negative-cache for permanently dead URLs. Transparent origin fallback on cache miss. Open Graph preview cards stored durably with posts.

**S3-compatible storage** - A separate build tag (`-tags s3`), deliberately excluded from the default binary to keep it small. Supports AWS S3, MinIO, SeaweedFS, Ceph, Backblaze B2, Wasabi, DigitalOcean Spaces. Native media migration CLI: `littlefedi admin media storage-migrate` between local and S3 (DB-queue-backed, resumable, bounded batches). `storage-status`, `storage-cancel`, `storage-resume` commands. `storage-manifest` for rclone JSONL integration.

**Markdown posts** - Powered by goldmark with GFM extensions: tables, strikethrough, bare URL autolinking, hard wraps. Raw HTML deliberately not rendered. Output sanitized through bluemonday (defense-in-depth). Composer toggle in the web UI. Federates `source.mediaType: text/markdown` (Pleroma/GTS convention). Inbound Markdown source is rendered to HTML.

**Visibility modes** - The standard four (public, unlisted, private, direct) plus local-only (`local`, instance timeline only, never federates) and local unlisted (`local_unlisted`, followers only, no federation). Useful for notes to your own instance community.

**Bubble timeline** - Curated set of instances whose posts appear alongside local posts in a special timeline. Akkoma-compatible extension. Admin panel for adding/removing bubble instances. API endpoint at `/api/v1/timelines/bubble`.

**Moderation** - Account states: suspended (tombstone, federates `Delete(Person)`), silenced (visible to followers only, dropped from public timelines), quiet (like silenced plus it downgrades federation to followers-only, a middle ground I haven't seen anywhere else), disabled (cannot log in, content stays visible). Self-suspend prevention, last-admin-demotion guard. Blocks (bidirectional, federated), mutes (local-only, with duration and `hide_notifications`), per-user domain blocks (distinct from admin instance-wide blocks). Reports pipeline: user submissions plus inbound/forwarded `Flag` into an admin triage UI with resolution actions. Admin notification on new reports. Domain blocks with severity (`noop`/`silence`/`suspend`) plus Mastodon-parity options (`reject_media` enforced in the proxy, `reject_reports`, `obfuscate`, `public`). A moderation audit log records every admin action.

**Web UI** - Server-rendered HTML with `html/template`, templates embedded via `//go:embed`. Inline CSS (dark mode, Inter font, gradients). htmx 2.x and Alpine.js for progressive enhancement. No build step, no Webpack, no Tailwind, no npm. Every action works as a plain form POST without JavaScript. Works in Lynx, eLinks, text-only browsers, and on mobile.

Full feature set: home/public/local/bubble timelines with infinite scroll, profile pages with follow/unfollow/bell toggle, status threads with reply composer and background thread completion, post creation with CW, visibility selector (6 modes), media upload with alt text, Markdown toggle, quote posts (pre-loads composer with the quoted post as an inline card), post editing, composer autocomplete for `@mentions` and `#hashtags`, settings (display name, bio, password, sessions, moderation, pruning, account move, timeline preferences), report form, search page, tag management.

**Admin panel** - Dashboard (user counts, pending approvals, unreachable instances, open reports), accounts (with suspend/silence/quiet/disable/approve/reject actions), invites (CRUD), domain blocks (with severity and options), reports (triage and resolution), audit log, instance health (per-instance status with follower/following counts, reachability tracking, purge with typed-domain confirmation), bubble instances, settings, housekeeping (on-demand pruning), queue console (ready/scheduled/running/failed by job kind), media storage (S3 migration controls in S3 builds).

**Background jobs** - DB-backed queue that survives restarts, 8 job kinds: `inbox`, `delivery` (16 attempts over roughly 26h with capped exponential backoff and equal jitter), `push_notification`, `actor_refresh`, `poll_close`, `scheduled_status`, `media_cache`, `media_migration`. Per-instance circuit breaker suspends delivery at `backoff_count >= 10`. Actor refresh dispatcher with stale-while-revalidate, crash-safe leases, and per-actor exponential backoff.

**Backups** - Periodic or on-demand, server-side, no external tooling required. Each run produces a timestamped directory with `config.toml`, a portable database dump (`VACUUM INTO` for SQLite, `pg_dump` for PostgreSQL), an optional copy of owned media, and a manifest. Toggle it on, set an interval (`24h`, `7d`, whatever fits), decide whether to include owned media (the remote cache is always excluded, no point backing up other people's content), and set a retention count so old backups get pruned automatically. Off by default, one line to turn on.

**Housekeeping** - Automated pruning: remote statuses by age, own low-interaction statuses (per-user or server thresholds, min likes/boosts caps), tombstones, expired mutes, stale media (>24h unattached), orphaned media (deleted posts), unreferenced media files (disk files with no DB record), cached media by age, cache file GC. On-demand controls in the admin UI.

**Security** - Token-bucket rate limiting per IP. Security headers: `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Strict-Transport-Security`. Content-Security-Policy with nonce-based script/style. CORS. CSRF on all cookie-authenticated POSTs. Session fixation protection. Password reset token in a cookie, not the URL. OAuth consent screen (not auto-issuing). SSRF protection (DNS, IP and redirect re-validation) on all outbound HTTP. HTTP Signature algorithm enforcement (`rsa-sha256` only). Inbox body size limit (1 MB). Backfill goroutine cap. Thread-fetch amplification limits. File upload validation (magic bytes, SVG rejection, MIME mismatch). Username enumeration hardening.

**Operational** - Prometheus metrics at `/metrics` (counters for API/inbox/fed/web requests, statuses, deliveries, thread fetches, queue depth; gauges for workers, pending follows, uptime). Health checks (`/health`, `/readyz`). CLI: `admin create-user`, `admin set-admin`, `admin list-users`, `admin suspend/unsuspend`, `admin invite generate/list/revoke`, `admin media prune/prune-orphans/prune-files`, `admin media storage-migrate/status/cancel/resume/manifest`, `post` (publish from stdin/file with Markdown, visibility, CW, media, reply, quote), `migrate` (run migrations only). SMTP for password reset and notifications (falls back to stdout). Config via TOML file plus environment variables (`LITTLEFEDI_{SECTION}_{KEY}`).

**Platform support** - CGO-free, compiles with `CGO_ENABLED=0`. 23+ `GOOS/GOARCH` combos via the modernc SQLite driver: macOS (amd64, arm64), Linux (386, amd64, arm, arm64, loong64, ppc64le, riscv64, s390x), FreeBSD (386, amd64, arm, arm64), Windows (386, amd64, arm64), OpenBSD (amd64, arm64). NetBSD (amd64, arm, arm64) via a WASM-based fallback SQLite driver, I don't think anything else in the fediverse space explicitly targets NetBSD. PostgreSQL is a separate build tag (`-tags postgres`), S3 is another (`-tags s3`). The default binary carries neither, keeping it small. ARMv6 (GOARM=6) gets special treatment in the release naming, that's the Pi Zero target.

### Why it matters

The fediverse shouldn't demand a beefy VPS. It shouldn't require Docker, 2 GB of RAM, Redis, Sidekiq, or a JS toolchain that pulls in 800 packages. A 10 euro Raspberry Pi Zero W running NetBSD, sitting on a shelf, drawing less than 2 watts, can be a fully functional fediverse instance with a web UI, mobile app compatibility, streaming, push notifications, quote posts, account migration, and a moderation toolkit. That's not hypothetical, that's what this post is running on.

But don't mistake "runs on a Pi Zero" for "only runs on a Pi Zero". Point the same binary at real hardware and it scales to numbers that have nothing to do with hobby-instance territory. Low power is the floor, not the ceiling.

One binary, one config file, one SQLite database. Light, yet complete.

I've been involved in this project for a while now, though I can't say much more about it at the moment, there are other people involved besides me and it's not entirely my call to talk about it publicly yet.

#Fediverse #littleFedi #OwnYourData #NetBSD