Skip to content

sartoopjj/thefeed

Repository files navigation

thefeed

DNS-based feed reader and lite messenger for networks where only DNS gets through. Read Telegram channels and public X accounts, and exchange end-to-end-encrypted messages with other users — all over plain DNS.

English · فارسی · 简体中文 · Русский

Contents: Install the app · Run a server · Messenger · How it works · Security · Build from source · Reference · Links

Screenshots

Main feed
Main feed
Messenger
Messenger
Reading a post
Reading a post
Telemirror
Telemirror
Resolver scanner
Resolver scanner
Resolver bank
Resolver bank
Settings
Settings

Install the app

For anyone who just wants to read feeds and chat — you don't need a server, just import a config into the client.

Download the client for your platform from the latest release — pick the mirror that's reachable for you: GitHub · GitLab.

Platform Notes
Android (7.0+) APK. Pick arm64-v8a (phones since ~2017) or armeabi-v7a (older 32-bit only). Installing the wrong ABI installs but won't run.
iOS (13+) Install via TestFlight. App Store build is planned; you can also build from source under ios/ — see Build from source.
Windows (10/11) The .exe is unsigned, so SmartScreen shows "Windows protected your PC" and Defender may quarantine it — a known false positive for DNS-tunneling tools, not malware. Click More info → Run anyway; restore from Defender → Protection history if removed; verify the SHA-256 from the release page if unsure.
macOS Universal .dmg (Intel + Apple Silicon), drag-installs Thefeed.app. Unsigned, so on first launch either right-click → Open, or run xattr -dr com.apple.quarantine /Applications/Thefeed.app.
Linux / Termux thefeed-client binary — run it and open http://127.0.0.1:8080.

Then open Settings → Configs and import a config (or paste your domain + passphrase). DNS resolvers are managed in the Resolver tab — a shared Bank used by all configs, plus a Scanner to find more.

Public configs to try: @thefeedconfig.


Run a server

For operators who host feeds for others. The server runs outside the censored network, pulls from Telegram / X, and answers encrypted DNS queries. Setup is two steps: (1) DNS records, then (2) install.

1. DNS records

You need one A record and one NS delegation. Example: your server IP is 203.0.113.10 and your domain is example.com.

# Type Name Value Purpose
1 A ns.example.com 203.0.113.10 Point a hostname at your server
2 NS t.example.com ns.example.com Delegate the feed subdomain to your server
3 (optional) NS c.example.com ns.example.com Delegate a messenger subdomain — only if you enable chat

Records 1–2 are required for the feed. Record 3 is only needed for the optional messenger, which must use a separate subdomain from the feed (e.g. c.example.com).

2. Install the server

With DNS in place, install with the script or with Docker.

Option A — install script (Linux + systemd)

The one-liner auto-detects a reachable mirror (GitHub first, then GitLab); pass --gitlab to force GitLab.

# GitHub mirror
sudo bash -c "$(curl -Ls https://raw.githubusercontent.com/sartoopjj/thefeed/main/scripts/install.sh)"

# GitLab mirror (use while the GitHub account is unavailable)
sudo bash -c "$(curl -Ls https://gitlab.com/sartoopjj/thefeed/-/raw/main/scripts/install.sh)" -- --gitlab

The script downloads the latest binary, asks for your domain / passphrase / channels / X accounts, asks whether to use Telegram login (recommended: No — public channels work without it), and sets up a systemd service. Re-run it any time to update.

Other actions (pipe the script to sudo bash -s -- <flag>):

Flag Action
--version v0.9.2 (or -v) Install a specific tag (rollback)
--pre Install the newest pre-release (beta / rc)
--list List recent releases
--login Re-run Telegram login
--config Print the import URI (domain, key, sk= server key, bootstrap resolvers)
--uninstall Remove the service

Option B — Docker

No Go toolchain needed. Base image alpine:3.21 (~23 MB), runs as non-root thefeed (UID 1000).

# 1. Configure — set THEFEED_DOMAIN and THEFEED_KEY (uncomment Telegram vars if you need private channels)
cp .env.example .env && nano .env

# 2. Add your channels
mkdir -p data
cp configs/channels.txt data/
cp configs/x_accounts.txt data/   # optional

# 3. Build and run (listens on :5300/udp inside the container)
docker compose up -d
docker compose logs -f

# 4. Print the client import config (the thefeed:// URI — domain, key, server key sk=,
#    resolvers) to hand to your users, just like the script prints at the end:
docker compose run --rm server --print-config --data-dir /data --domain YOUR_DOMAIN --key YOUR_KEY

Then set up the port 53 redirect below. For private channels, do a one-time interactive login first:

docker compose run -it --rm server --login-only --data-dir /data \
  --domain YOUR_DOMAIN --key YOUR_KEY \
  --api-id YOUR_API_ID --api-hash YOUR_HASH --phone YOUR_PHONE
# then remove --no-telegram from docker-compose.yml, add the Telegram flags, and `docker compose up -d`

Port 53

The server must receive external DNS on UDP 53, but binding :53 directly conflicts with systemd-resolved. So it listens on an unprivileged port (:5300) and you redirect external :53 to it with iptables. Local DNS on the host keeps working — only packets arriving on the external interface are redirected.

# Replace eth0 with your interface (check: ip a)
sudo iptables  -I INPUT -p udp --dport 5300 -j ACCEPT
sudo iptables  -t nat -I PREROUTING -i eth0 -p udp --dport 53 -j REDIRECT --to-ports 5300
sudo ip6tables -I INPUT -p udp --dport 5300 -j ACCEPT
sudo ip6tables -t nat -I PREROUTING -i eth0 -p udp --dport 53 -j REDIRECT --to-ports 5300

# Persist across reboots (Debian/Ubuntu)
sudo apt install -y iptables-persistent && sudo netfilter-persistent save

Undo instantly if anything breaks:

sudo iptables -t nat -D PREROUTING -i eth0 -p udp --dport 53 -j REDIRECT --to-ports 5300
sudo iptables -D INPUT -p udp --dport 5300 -j ACCEPT
sudo netfilter-persistent save

Quick sanity checks: ss -ulnp | grep ':53 ' (expect only systemd-resolved on 127.0.0.53), dig +short google.com @127.0.0.53 (local DNS still works), iptables -t nat -L PREROUTING -n | grep 5300 (redirect active).

Managing the service

systemctl status thefeed-server
systemctl restart thefeed-server
journalctl -u thefeed-server -f

sudo vi /opt/thefeed/data/channels.txt   # edit channels, then:
sudo systemctl restart thefeed-server

The server also renders a terminal dashboard from its hourly reports (<data-dir>/dns_hourly.jsonl) — serves nothing on the network, just reads the data dir:

thefeed-server --data-dir /srv/thefeed --report                 # snapshot
thefeed-server --data-dir /srv/thefeed --report --report-refresh 5s   # live

It shows total / channel-fetch / metadata / media / chat query counts, per-channel and per-domain aggregates, and chat stats.

Server flags

Key flags (also settable via env vars, e.g. THEFEED_DOMAIN, THEFEED_KEY, THEFEED_ALLOW_MANAGE):

Flag Default Description
--data-dir ./data Data directory (channels, session, cache, config)
--domain DNS feed domain (required)
--key Encryption passphrase (required)
--extra-domains Comma-separated extra feed sub-domains (load spread + resilience)
--chat-domains Enable the messenger on these sub-domains (separate from feed)
--no-telegram false Run without Telegram login (public channels only)
--api-id / --api-hash / --phone Telegram credentials (private channels)
--login-only false Authenticate to Telegram, save session, exit
--listen :5300 DNS listen address
--msg-limit 15 Max messages fetched per channel
--fetch-interval 10 Fetch cycle in minutes (min 3)
--allow-manage false Allow remote send / channel management (leave off unless trusted)
--padding 32 Max random padding bytes (anti-DPI; 0 = off)
--x-rss-instances nitter.net,… Comma-separated X RSS base URLs
--dns-media-enabled false Serve media over the slow DNS relay
--github-relay-enabled false Serve media over the fast GitHub relay (needs --github-relay-token / -repo)
--report Render the terminal dashboard and exit
--version Show version and exit

Full media-relay flags are in How it works → Media relays.


Messenger

An optional, standalone store-and-forward messenger between users of the same server — it never touches Telegram. Enable it by giving the server one or more dedicated sub-domains (with a matching NS record, separate from the feed domains):

thefeed-server ... --chat-domains c.example.com     # or THEFEED_CHAT_DOMAINS=c.example.com
  • End-to-end encrypted — only the two users can read a message; the server stores opaque blobs and verifies senders without reading anything. Contact names never leave the device.
  • Identity — the client generates a recovery code locally; your address is 20 characters derived from it. Share the address out-of-band; the same recovery code works on any server.
  • Fail-closed — chat activates only when the profile pins the server key (sk=) and the server's signed chat capability verifies. A signed bit in the feed metadata lets a keyless client tell you "this server has a messenger — re-import the config with its key" instead of failing silently.
  • Abuse limits (advertised to clients): --chat-send-per-hour (30), --chat-inbox-cap (50), --chat-per-pair-cap (10), --chat-max-msg-bytes (500); undelivered messages expire after --chat-ttl-hours (72). --chat-enabled=false keeps the domains but advertises chat as disabled.

In the client, open Chat from the bottom nav. ✓ = stored on the server, ✓✓ = picked up by the recipient; matching safety emojis on both devices confirm the conversation is secure.


How it works

                                  Encrypted DNS TXT
   ┌──────────────┐  feed meta + small media   ┌──────────────────┐    MTProto      ┌──────────┐
   │              │ ─────────────────────────▸ │      Server      │ ─────────────▸  │ Telegram │
   │    Client    │ ◂───────────────────────── │  (DNS auth +     │ ◂─────────────  │   API    │
   │  (Web UI)    │                            │   media relays)  │    RSS / HTTP   ┌──────────┐
   │              │  large media (fast relay)  │                  │ ─────────────▸  │  Nitter  │
   │              │ ◂───── api.github.com ◂──  │                  │ ◂─────────────  │ (X feed) │
   └──────────────┘     (uploaded by server)   └──────────────────┘                 └──────────┘

Server (outside the censored network): connects to Telegram and reads configured channels; fetches public X posts via RSS-compatible mirrors (no login); serves feed metadata and small media as encrypted DNS TXT responses with random padding (anti-DPI). Login once, run forever; --no-telegram reads public channels with no credentials. Optional multi-domain and messenger modes. All state in one data directory.

Client (inside the censored network): a browser-based web UI (RTL/Farsi, VazirMatn font) that sends encrypted DNS queries through a shared Resolver Bank — resolvers are found via the Scanner, imported, or entered manually, and scored by success-rate + latency so healthy ones are preferred. Scatter mode fans a query out to several resolvers and takes the fastest. Media downloads are relay-aware and hash/size-verified. Includes the messenger, per-channel auto-update, new-message indicators, and a live DNS query log.

Protocol

All communication is encrypted (AES-256) and rides standard DNS TXT queries/responses with variable padding and per-resolver scoring, so it blends with normal DNS. Message data is deflate-compressed before encryption; each query is independent (no session state on the wire).

Media relays

Messages with photos, files, GIFs, audio, and video can be cached on the server and downloaded over the same channel. The server dedupes each file (by upstream id + content hash), pushes bytes to every enabled relay, and adds a small header to the message text:

[IMAGE]<size>:<flags>:<dnsCh>:<dnsBlk>:<crc32>[:<filename>]
optional caption

<flags> is a comma-separated list of per-relay availability bits (1=available, 0=not): slot 0 = DNS, slot 1 = GitHub, future relays append; older clients ignore unknown slots. Each relay is independent — the same file can be served via several at once. Clients prefer the fastest available, retry on failure, and ask before falling back to a slower path. Block 0 of every DNS-cached file starts with a 16-byte header (CRC32 + version + compression byte + reserved) that the client verifies before delivering bytes. Downloads are cached client-side (IndexedDB, 7 days) and on the local client (<dataDir>/media-cache/, 7 days).

Two relays ship today:

  • DNS relay (slow, censorship-resistant, off by default) — bytes split across DNS blocks. Default cap 100 KB.
  • GitHub relay (fast, off by default) — bytes uploaded to a repo, pulled over plain HTTPS; needs a PAT with contents:write. Objects land at <repo>/<sanitised-domain>/<size>_<crc32>. Default cap 15 MB.
Flag Env Default Notes
--dns-media-enabled THEFEED_DNS_MEDIA_ENABLED false toggle DNS relay
--dns-media-max-size THEFEED_DNS_MEDIA_MAX_SIZE_KB 100 KB per-file cap
--dns-media-compression THEFEED_DNS_MEDIA_COMPRESSION gzip none / gzip / deflate
--github-relay-enabled THEFEED_GITHUB_RELAY_ENABLED false toggle GitHub relay
--github-relay-token THEFEED_GITHUB_RELAY_TOKEN PAT, contents:write
--github-relay-repo THEFEED_GITHUB_RELAY_REPO owner/repo
--github-relay-branch THEFEED_GITHUB_RELAY_BRANCH main branch for relay objects
--github-relay-max-size THEFEED_GITHUB_RELAY_MAX_SIZE_KB 15360 KB per-file cap

Security

Two-part access control:

  • Encryption passphrase (--key) — required on both server and client. Anyone with it can read all channel messages (including private channels); share it only with people you trust.
  • Remote management (--allow-manage) — when enabled, anyone with the passphrase can, from the client: send messages through the server's Telegram account (into channels / private chats — this is Telegram sending via the operator's logged-in account, and is completely separate from the end-to-end messenger), and change the feed's channel list (add or remove the channels shown in the feed). Off by default; enable only on trusted servers.
  • Client web password (--password) — HTTP Basic Auth on the web UI. Local protection only; it does not affect DNS-level access.

Properties: end-to-end AES-256 in both directions · random padding defeats size analysis · each query independent (no wire session state) · writes gated by --allow-manage · Telegram 2FA prompted interactively (never stored in args) · session file 0600.

⚠️ Never share your passphrase publicly. Anyone who has it can run their own client and read all your messages — there is no way to prevent that. The --password flag only guards the web UI on your own machine.

The optional messenger is separately end-to-end encrypted per conversation and independent of the feed passphrase.


Build from source

Prerequisites: Go 1.26+, and (for private channels) Telegram API credentials from https://my.telegram.org.

make build          # build server + client into ./build
make build-server   # server only
make build-client   # client only
make test           # tests with the race detector
make build-all      # cross-compile all platforms (incl. Android)
make vet            # go vet

Run the server (see flags):

./build/thefeed-server --data-dir ./data --domain t.example.com --key "passphrase" --no-telegram --listen ":5300"
# private channels: add --login-only once (with --api-id/--api-hash/--phone), then run without it

Run the client: ./build/thefeed-client (creates ./thefeeddata/, opens http://127.0.0.1:8080). Options: --data-dir, --port (8080), --password. Per-config options (resolvers, scatter, rate limit, timeout) are set in the web UI, not via flags.

macOS: make mac-dmgbuild/Thefeed.app + universal .dmg. Data lives under ~/Library/Application Support/Thefeed; a menu-bar item offers Open / Quit.

Android APK:

make build-android-arm64
cp build/thefeed-client-android-arm64 android/app/src/main/assets/thefeed-client
cd android && gradle wrapper --gradle-version 8.10.2 && ./gradlew assembleDebug
# → android/app/build/outputs/apk/debug/app-debug.apk

iOS: wraps the Go client as a gomobile-bound xcframework consumed by a SwiftUI app under ios/. The server runs in-process on 127.0.0.1:<random-port>, foreground only. Needs Xcode 15+, Go 1.26+, gomobile (go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init).

make ios-bind    # build Mobile.xcframework (device + Simulator)
make ios-build   # build the app for the Simulator

Then open ios/Thefeed.xcodeproj in Xcode to run on a device.

Releases: pushing a tag starting with v triggers a CI build + release. A tag containing - (e.g. v1.4.0-rc1) is marked pre-release automatically. Assets include server/client binaries for every platform plus the Android APKs (arm64-v8a, armeabi-v7a).


Reference

Config file formats

All optional, in the data directory; # starts a comment and blank lines are ignored.

  • channels.txt — public Telegram channels, one @username per line.
  • x_accounts.txt — public X (Twitter) usernames, one per line.
  • private_channels.txt — private-channel invite links, one per line. Requires Telegram login — the server joins each channel at startup, then fetches it like any other. Accepts https://t.me/+…, t.me/joinchat/…, tg://join?invite=…, or the bare invite hash.
# channels.txt      # x_accounts.txt    # private_channels.txt
@VahidOnline        Vahid               https://t.me/+aBcDeF123456

The web UI

A Telegram-style shell with a bottom navigation bar across five sections:

  • Feed — channel/X feed grouped by type (Public/X/Private), native RTL/Farsi rendering, a floating composer (send to channels/private chats when Telegram is connected), per-channel new-message badges, media-tag highlighting, in-channel search, and a live DNS query log. Saved Messages (encrypted local notes + bookmarks) lives here.
  • Mirror (Telemirror) — a read-only, Telegram-web-style mirror of channels with aspect-ratio-stable image/album rendering.
  • Chat — the end-to-end-encrypted messenger.
  • Resolver — the shared Bank, your named resolver lists, and the Scanner in one place. The Scanner probes IPs / CIDRs (e.g. 5.1.0.0/16) or domains to find DNS servers that can reach your server; results sort by latency, expand a working resolver's /24, and can be applied to a config directly. A one-click preset loads a curated CIDR list.
  • SettingsDisplay (theme, font, language, wallpaper), Connection (query mode, rate limit, scatter, timeout, password, debug), Storage (disk-cache budget), Backup (encrypted export/import), About, and Configs (import/manage, with ready-made starter configs). Theme follows the device by default. All fields auto-save.

X fetch safety

X posts are fetched via RSS/XML only. Instance URLs are validated (http/https, host-only), response size is capped and timeouts enforced, and on a 403/failure the server tries the next configured instance. Set your own trusted mirrors with --x-rss-instances.


Links & donate

Donate — any amount in USDT or USDC on Polygon or BNB Chain: 0xe73f022f668c57cce79feccd875ac7332311013a — thank you ❤️

License

MIT


For FREE IRAN Lion-and-Sun

Everyone deserves free access to information

About

DNS-based feed reader and lite messenger for environments where only DNS queries work. Read Telegram channels and public X accounts, and exchange end-to-end-encrypted messages with other users — all over DNS.

Topics

Resources

License

Stars

1.4k stars

Watchers

13 watching

Forks

Contributors