NNTP server powered by IPFS + IPNS. Zero installation time.
  • Go 69.9%
  • Python 28.2%
  • Makefile 1.3%
  • Shell 0.6%
Find a file
2026-07-12 18:13:06 +02:00
cmd/tribes Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
internal Release v1.0.0-rc3 2026-07-12 15:04:28 +02:00
ipfs Release v1.0.0-rc4 2026-07-12 15:12:28 +02:00
nntp Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
packaging/debian Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
release Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
smoketest Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
.gitignore Document installation and add cross-platform releases 2026-07-12 09:42:50 +02:00
go.mod Release v1.0.0-rc2 2026-07-12 14:47:08 +02:00
go.sum Release v1.0.0-rc2 2026-07-12 14:47:08 +02:00
Makefile Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
README.md Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
README_it.md Release v1.0.0-rc7 2026-07-12 18:13:06 +02:00
TODO.md Add standard IPFS bootstrap peers 2026-07-12 11:01:34 +02:00
tribes.yaml.example Add standard IPFS bootstrap peers 2026-07-12 11:01:34 +02:00

Tribes

Tribes is a federated NNTP server. It exposes local newsgroups to standard Usenet clients and exchanges articles and group catalogs with other instances through IPFS, IPNS, libp2p, and GossipSub. It does not require Kubo or an external IPFS daemon.

Remote groups do not need to be listed in the configuration. Instances discover them through PubSub, remember them in remote_groups_root/registry.json, and expose them to clients through LIST, GROUP, and NEWGROUPS.

Italian documentation is available in README_it.md.

Installing a prebuilt executable

Prebuilt executables are published as downloadable assets on the Forgejo Releases page. Copies and checksums are also kept in the release directory:

  • tribes-linux-amd64.zip
  • tribes-freebsd-amd64.zip
  • tribes-windows-amd64.zip
  • tribes_1.0.0~rc7_amd64.deb
  • SHA256SUMS

Always verify checksums before use:

cd release
sha256sum -c SHA256SUMS

Each ZIP contains the platform executable and its own SHA256SUMS. On Linux or FreeBSD:

unzip tribes-linux-amd64.zip
sha256sum -c SHA256SUMS
chmod +x tribes-linux-amd64
sudo install -m 0755 tribes-linux-amd64 /usr/local/bin/tribes

On Windows, extract tribes-windows-amd64.zip, verify the included checksum, and copy tribes-windows-amd64.exe to a directory in %PATH% or run it directly from PowerShell.

On Debian or Ubuntu, install the package directly:

sudo apt install ./tribes_1.0.0~rc7_amd64.deb
systemctl status tribes

The package creates an unprivileged tribes system user, installs /etc/tribes.yml, enables and starts the service on 127.0.0.1:119, stores all state below /var/local/tribes, and rotates /var/log/tribes.txt daily. No local groups are configured by the package; uncomment local_groups in /etc/tribes.yml to publish your own groups, then run sudo systemctl restart tribes.

Building from source

Go 1.25 or later and GNU Make are required:

git clone https://git.keinpfusch.net/loweel/tribes.git
cd tribes
make tribes

The local binary is created as ./tribes. To regenerate all amd64 release artifacts:

make release

Initial configuration

Copy tribes.yaml.example and adjust paths, ports, and local groups:

cp tribes.yaml.example tribes.yaml
tribes -c ./tribes.yaml

Minimal example for a node on a local network:

nntp_addr: "127.0.0.1:119"
nntp_port: 119
max_article_bytes: 10485760

logging:
  level: "info"
  format: "json"
  add_source: false

spool_root: "/var/lib/tribes"
local_groups_root: "/var/lib/tribes/local"
remote_groups_root: "/var/cache/tribes/remote"

ipfs:
  mode: "embedded"
  datastore_path: "/var/lib/tribes/ipfs"
  listen_addrs:
    - "/ip4/0.0.0.0/tcp/4001"
  bootstrap_peers:
    - "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN"
    - "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa"
    - "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"
    - "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt"
    - "/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8"
  enable_mdns: true
  enable_autonat: false
  enable_relay: false

discovery:
  enabled: true
  topic: "tribes/groups/v1"
  request_interval: 30s
  response_window: 5s
  max_responses: 32
  max_groups_per_peer: 256
  max_total_groups: 4096

cleanup:
  local_interval: 24h
  remote_interval: 6h

local_groups:
  - name: "example.general"
    title: "Example General"

NNTP port

nntp_addr defines the listen address and port. When greater than zero, nntp_port overrides only the port contained in nntp_addr:

nntp_addr: "0.0.0.0:119"
nntp_port: 8119

The node will listen on 0.0.0.0:8119. The --nntp-addr command-line flag takes precedence over the configuration file:

tribes -c tribes.yaml --nntp-addr 127.0.0.1:1119

Ports below 1024 may require administrative privileges. A non-privileged port such as 8119 is usually easier to operate.

Networking and discovery

ipfs.listen_addrs configures libp2p, not NNTP. With enable_mdns: true, nodes on the same LAN can find each other automatically. To connect separate networks, add at least one known address to bootstrap_peers:

bootstrap_peers:
  - "/dns4/peer.example.net/tcp/4001/p2p/12D3KooW..."

After connecting, GossipSub exchanges requests and announcements for locally known groups. Discovered IPNS sources are saved in the registry; Tribes creates/updates the matching local group folder and imports missing article files by IPFS CID. There is no manual remote_groups list to maintain.

IPNS naming and node keys are managed automatically. The persistent libp2p identity is created on first startup inside ipfs.datastore_path; local group names are used as stable IPNS identifiers. Operators never need to generate or configure an IPNS key.

Storage

  • local_groups_root: articles and metadata for groups published by this node.
  • remote_groups_root: registry and reconstructable metadata for discovered IPNS sources.
  • ipfs.datastore_path: persistent blockstore and IPNS records.
  • spool_root/objects: local article-object cache.

The cleanup.local_interval and cleanup.remote_interval jobs remove reconstructable article files while retaining indexes and IPFS blocks. A value of 0 disables the corresponding job. If a client requests a removed article again, Tribes retrieves it by CID.

Known IPNS sources are refreshed in the background according to sync.interval (five minutes by default). The job resolves each source's IPNS head, imports missing article files into the local group folder by IPFS CID, and updates metadata.

Cleanup requires ipfs.mode: embedded. Offline mode is intended for local development and tests.

Article size limit

max_article_bytes limits every article received through POST, IHAVE, or TAKETHIS. The default is 10 MiB. Oversized articles are rejected without terminating the NNTP session.

Logging

Logging is structured and can be emitted as JSON or text:

logging:
  level: "debug"    # debug, info, warn, error
  format: "json"    # json, text
  add_source: false
  • debug: NNTP commands and responses, sessions, peer connections, announcements, and PubSub messages sent or received.
  • info: node startup, connected peers, learned groups, stored articles, and cleanup jobs.
  • warn: conflicts, invalid PubSub input, and rejected articles.
  • error: operational failures and fatal shutdowns.

Article bodies and AUTHINFO secrets are never written to logs.

Using an NNTP client

Configure the client with the host and port from nntp_addr/nntp_port. A quick Python check:

import nntplib

with nntplib.NNTP("127.0.0.1", 8119) as client:
    print(client.list()[1])
    print(client.group("example.general"))

Posting a test article:

import io
import nntplib

article = b"""From: user@example.net
Newsgroups: example.general
Subject: First message

Hello from Tribes.
"""

with nntplib.NNTP("127.0.0.1", 8119) as client:
    client.post(io.BytesIO(article))

On the first post, Tribes updates the group index, publishes the new head through IPNS, and announces it to other nodes.

Running tests

Quick Go checks:

go test ./...
go test -race ./...

Complete pipeline, including security checks, vulnerability scanning, Docker, and federation smoke tests:

make smoke

Operational notes

  • Do not expose NNTP to an untrusted network without an external access-control layer.
  • Make the configured libp2p port reachable when connecting nodes across separate networks.
  • Preserve the IPFS blockstore: cleaned spool files depend on their CIDs for reconstruction.
  • Back up the keys and IPNS storage for locally hosted groups.