server

package
v0.25.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 43 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrCacheFull = errors.New("no free media channel slot")

ErrCacheFull is returned by Store when no media channel slot is available. In practice this requires either MediaChannelEnd-Start+1 simultaneously pinned files or a TTL too generous for the workload.

View Source
var ErrChatAccountsFull = fmt.Errorf("chat: account store full")

ErrChatAccountsFull is returned by Register when the store is at its configured maxAccounts cap.

View Source
var ErrTooLarge = errors.New("media file exceeds configured max-size")

ErrTooLarge is returned by Store when content exceeds MaxFileBytes.

Functions

func ConfigURI added in v0.25.9

func ConfigURI(domain string, extraDomains []string, passphrase string, priv ed25519.PrivateKey) string

ConfigURI builds the thefeed:// import URI advertising this server's main domain (path), passphrase, pinned signing public key (sk=), any extra sub-domains (d=, comma-separated), and two bootstrap resolvers so a freshly-imported client can reach DNS immediately.

func DecompressMediaBytes added in v0.13.0

func DecompressMediaBytes(r io.Reader, compression protocol.MediaCompression) (io.ReadCloser, error)

DecompressMediaBytes is the inverse of compressMediaBytes; exposed for the HTTP layer (which receives a stream of compressed bytes after the header is stripped) and tests.

func LoadOrCreateServerEncKey added in v0.25.9

func LoadOrCreateServerEncKey(dataDir string) (*ecdh.PrivateKey, error)

LoadOrCreateServerEncKey returns the server's x25519 encryption key, reading it from <dataDir>/server_x25519.key or generating and persisting a new one if the file is absent.

func LoadOrCreateServerKey added in v0.25.9

func LoadOrCreateServerKey(dataDir string) (ed25519.PrivateKey, error)

LoadOrCreateServerKey returns the server's ed25519 private key, reading it from <dataDir>/server_ed25519.key or generating and persisting a new one if the file is absent. The accompanying public key (base64url) is what the operator pins in configs — print it with ServerPublicKeyString.

The file format is base64-std of the 32-byte seed. A raw 32-byte seed is also accepted, so an operator can drop in a key produced elsewhere.

func LoadPrivateInvites added in v0.20.0

func LoadPrivateInvites(path string) ([]string, error)

LoadPrivateInvites reads invite links from path (one per line, `#` comments allowed) and returns the parsed hashes. Missing file is not an error — the feature is opt-in.

func ParseInviteHash added in v0.20.0

func ParseInviteHash(link string) (string, error)

ParseInviteHash extracts the invite hash from any supported invite-link shape: t.me/+HASH, t.me/joinchat/HASH, tg://join?invite=HASH, or bare HASH (with or without leading +). Public-username URLs (t.me/foo without + or joinchat/) are rejected.

func SaveServerEncKey added in v0.25.9

func SaveServerEncKey(dataDir string, key *ecdh.PrivateKey) error

SaveServerEncKey overwrites the persisted x25519 chat key. Used on ek rotation so a restart loads the rotated key, not the pre-rotation one. The previous key lives on only in RAM (its grace window), which is what gives the session layer forward secrecy once it expires.

func ServerPublicKeyString added in v0.25.9

func ServerPublicKeyString(priv ed25519.PrivateKey) string

ServerPublicKeyString returns the base64url (no padding) encoding of the server public key — the value pinned in configs and bundled defaults.

func SetMediaDebugLogs added in v0.13.0

func SetMediaDebugLogs(enabled bool)

SetMediaDebugLogs enables or disables the media debug log channel.

Types

type ChatInboxEntry added in v0.25.9

type ChatInboxEntry struct {
	Src    [protocol.AddressSize]byte
	Seq    uint32
	Len    uint16
	Blocks uint8
}

ChatInboxEntry describes one waiting message for INBOX_STATUS.

type ChatService added in v0.25.9

type ChatService struct {
	// contains filtered or unexported fields
}

ChatService handles chat requests on the dedicated chat domain(s). Every query is one uniform sealed cell. A per-connection eph↔ek handshake yields a session (Ksession + a server-assigned selector); all later ops are sealed under Ksession, so the op, the destination and the read metadata are all encrypted. Sessions, uploads and handshake reassemblies are RAM-only and bounded; accounts/inboxes live in the ChatStore.

func NewChatService added in v0.25.9

func NewChatService(store *ChatStore, ek *ecdh.PrivateKey, queryKey [protocol.KeySize]byte, limits protocol.ChatLimits, domains []string) *ChatService

NewChatService creates the chat handler. domains are the dedicated chat sub-domains (already validated against feed domains by the DNS layer).

func (*ChatService) Domains added in v0.25.9

func (c *ChatService) Domains() []string

Domains returns the chat sub-domains.

func (*ChatService) HandleCell added in v0.25.9

func (c *ChatService) HandleCell(selector [protocol.ChatSelectorSize]byte, counter uint32, payload []byte, domain string, now time.Time) []byte

HandleCell processes one decoded chat cell and returns the response bytes (to be wrapped by EncodeResponse). An empty slice means "incomplete handshake, keep going"; ChatSessionLostResp means "re-handshake".

func (*ChatService) Info added in v0.25.9

func (c *ChatService) Info() protocol.ChatInfo

Info returns the ChatInfo payload advertised (signed) on the feed metadata path. EkPub is the current key; on rotation the caller re-publishes Info().

func (*ChatService) RotateEk added in v0.25.9

func (c *ChatService) RotateEk(now time.Time, grace time.Duration) ([]byte, error)

RotateEk swaps in a fresh current key, retires the old one into the grace window, persists the new key (if a saver is set), and returns the new public key so the caller can re-publish (re-sign) ChatInfo. Live sessions keep working; clients pick up the new key on their next ChatInfo refresh.

func (*ChatService) RunEkRotation added in v0.25.9

func (c *ChatService) RunEkRotation(ctx context.Context, republish func())

RunEkRotation rotates the server encryption key on chatEkRotatePeriod, then re-publishes (re-signs) ChatInfo so clients learn the new key. Runs until ctx is done.

func (*ChatService) RunSweeper added in v0.25.9

func (c *ChatService) RunSweeper(ctx context.Context)

RunSweeper periodically expires sessions, handshakes, old messages, and idle accounts until ctx is done.

func (*ChatService) SetEkPersist added in v0.25.9

func (c *ChatService) SetEkPersist(save func(*ecdh.PrivateKey) error)

SetEkPersist registers how a freshly rotated current key is saved to disk, so a restart loads the new key (not the pre-rotation one). Optional — tests leave it nil and rotate in RAM only.

func (*ChatService) StatsSnapshot added in v0.25.9

func (c *ChatService) StatsSnapshot() map[string]int64

StatsSnapshot returns the per-window counters for the hourly report and resets them. `accounts` and `sessions` are point-in-time gauges.

type ChatStore added in v0.25.9

type ChatStore struct {
	// contains filtered or unexported fields
}

ChatStore persists chat accounts: a hot RAM cache in front of a bbolt file (pure Go, single mmap'd file). Two durability modes:

  • write-through (default): every mutation is committed to disk before the op returns, so a crash never loses committed state. Concurrent writes go through bbolt's Batch (coalesced fsyncs).
  • periodic (EnablePeriodicSync): RAM is authoritative; mutations only mark the account dirty and RunSync writes all dirty accounts + fsyncs every interval. The hot path never touches disk, so ops stop convoying on the batch commit — a crash can lose up to ~interval of state.

The RAM cache + its lock are SHARDED by address so commits to different accounts proceed in parallel. Per account, operations still serialize on that account's shard, so ordering and correctness are preserved.

func OpenChatStore added in v0.25.9

func OpenChatStore(path string, limits protocol.ChatLimits) (*ChatStore, error)

OpenChatStore opens (or creates) the chat account store at path.

func (*ChatStore) AccountCount added in v0.25.9

func (s *ChatStore) AccountCount() int

AccountCount returns the number of stored accounts.

func (*ChatStore) Ack added in v0.25.9

func (s *ChatStore) Ack(addr, peer [protocol.AddressSize]byte, upToSeq uint32, receipt []byte, now time.Time) (status byte, err error)

Ack frees delivered messages: removes peer→addr messages with seq ≤ upToSeq and bumps last_delivered. receipt is the recipient's opaque E2E proof for upToSeq (stored verbatim, relayed to the sender via PairState). Idempotent (the cell seal authenticated the caller).

func (*ChatStore) Close added in v0.25.9

func (s *ChatStore) Close() error

Close writes any dirty accounts (periodic mode) and closes the file.

func (*ChatStore) CommitMessage added in v0.25.9

func (s *ChatStore) CommitMessage(src, dst [protocol.AddressSize]byte, seq uint32, envelope []byte, now time.Time) (status byte, lastAccepted uint32, remaining uint16, resetUnix uint32, err error)

CommitMessage stores a fully-verified envelope into dst's inbox, enforcing the authoritative quota/replay checks, and writes both accounts in one batch.

func (*ChatStore) EnablePeriodicSync added in v0.25.9

func (s *ChatStore) EnablePeriodicSync(interval time.Duration)

EnablePeriodicSync trades per-commit durability for a periodic flush every interval: ops mutate only the RAM cache (high throughput, CPU/RAM-bound rather than disk-bound) and RunSync writes the dirty accounts + fsyncs on the interval. A crash can lose up to ~interval of just-received messages. Call once after OpenChatStore, before serving, then start RunSync.

func (*ChatStore) FetchBlock added in v0.25.9

func (s *ChatStore) FetchBlock(addr, src [protocol.AddressSize]byte, seq uint32, block uint8, now time.Time) ([]byte, bool, error)

FetchBlock returns one slice of a stored envelope by (src, seq, block index). src disambiguates the sender — seq is per-pair, so the same seq can exist from two different senders in one inbox; matching seq alone would return the wrong envelope.

func (*ChatStore) InboxStatus added in v0.25.9

func (s *ChatStore) InboxStatus(addr [protocol.AddressSize]byte, now time.Time) ([]ChatInboxEntry, error)

InboxStatus lists the waiting messages for addr.

func (*ChatStore) Keys added in v0.25.9

func (s *ChatStore) Keys(addr [protocol.AddressSize]byte, now time.Time) (identityPub, encPub []byte, ok bool, err error)

Keys returns the registered identity and encryption keys for addr.

func (*ChatStore) Limits added in v0.25.9

func (s *ChatStore) Limits() protocol.ChatLimits

Limits returns the store's configured limits.

func (*ChatStore) OpenSession added in v0.25.9

func (s *ChatStore) OpenSession(addr [protocol.AddressSize]byte, ts uint32, now time.Time, commit bool) (identityPub []byte, lastAck uint32, status byte, err error)

OpenSession validates a strictly-increasing OPEN timestamp for addr and persists it. Retained for completeness (the v2 handshake checks skew inline).

func (*ChatStore) PairState added in v0.25.9

func (s *ChatStore) PairState(owner, peer [protocol.AddressSize]byte, now time.Time) (accepted, delivered uint32, receipt []byte, ok bool, err error)

PairState returns (last_accepted, last_delivered, receipt) for messages owner←peer. receipt is the recipient's E2E proof for last_delivered (nil if none yet), relayed to the sender for offline verification.

func (*ChatStore) PrecheckMessage added in v0.25.9

func (s *ChatStore) PrecheckMessage(src, dst [protocol.AddressSize]byte, now time.Time) (status byte, remaining uint16, resetUnix uint32, err error)

PrecheckMessage runs the advisory checks for a src→dst message.

func (*ChatStore) Register added in v0.25.9

func (s *ChatStore) Register(rec *protocol.RegisterEnvelope, raw []byte, now time.Time) error

Register creates or refreshes the account for a verified registration record.

func (*ChatStore) RegisterRecord added in v0.25.9

func (s *ChatStore) RegisterRecord(addr [protocol.AddressSize]byte, now time.Time) ([]byte, bool, error)

RegisterRecord returns the raw signed registration record for addr.

func (*ChatStore) ResolvePeerHandle added in v0.25.9

func (s *ChatStore) ResolvePeerHandle(owner [protocol.AddressSize]byte, handle [protocol.ChatPeerHandleSize]byte, now time.Time) (peer [protocol.AddressSize]byte, ok bool, err error)

ResolvePeerHandle maps a 4-byte peer handle to a full peer address within owner's known pairs. Returns ok=false if there is no unique match.

func (*ChatStore) RunSync added in v0.25.9

func (s *ChatStore) RunSync(ctx context.Context)

RunSync writes dirty accounts and fsyncs every syncEvery until ctx is done, with a final flush on shutdown. No-op unless EnablePeriodicSync was called.

func (*ChatStore) SendQuota added in v0.25.9

func (s *ChatStore) SendQuota(addr [protocol.AddressSize]byte, now time.Time) (remaining uint16, resetUnix uint32, ok bool, err error)

SendQuota returns how many messages addr may still send this hour.

func (*ChatStore) SetAccountTTL added in v0.25.9

func (s *ChatStore) SetAccountTTL(d time.Duration)

SetAccountTTL sets how long an idle account is kept before deletion. 0 (the default) never deletes accounts. Call once after OpenChatStore.

func (*ChatStore) SetMaxAccounts added in v0.25.9

func (s *ChatStore) SetMaxAccounts(n int)

SetMaxAccounts caps total stored accounts (0 = unlimited). Call once after OpenChatStore.

func (*ChatStore) Sweep added in v0.25.9

func (s *ChatStore) Sweep(now time.Time) (expiredMsgs, deletedAccounts int, err error)

Sweep expires idle RAM entries, inbox messages past TTL, and (if an account TTL is set) long-inactive accounts.

type Config

type Config struct {
	ListenAddr          string
	Domain              string   // main domain; canonical for relay path derivation
	ExtraDomains        []string // additional sub-domains the server also answers feed queries on
	Passphrase          string
	DataDir             string // server state dir; holds the signing key
	ChannelsFile        string
	PrivateChannelsFile string // optional: invite links for private channels
	XAccountsFile       string
	XRSSInstances       string
	MaxPadding          int
	MsgLimit            int  // max messages per channel (0 = default 15)
	NoTelegram          bool // if true, fetch public channels without Telegram login
	AllowManage         bool // if true, remote channel management and sending via DNS is allowed
	Debug               bool // if true, log every decoded DNS query
	// DNSMediaEnabled toggles the slow DNS-relay path. When false the
	// server still ingests media bytes (so other relays can serve them)
	// but the wire-format DNS flag is unset for clients.
	DNSMediaEnabled     bool
	DNSMediaMaxSize     int64         // per-file cap for the DNS relay (0 = no cap)
	DNSMediaCacheTTL    int           // DNS-relay TTL in minutes
	DNSMediaCompression string        // DNS-relay compression: none|gzip|deflate
	FetchInterval       time.Duration // 0 = default 10m; floor enforced by main
	GitHubRelay         GitHubRelayConfig
	Telegram            TelegramConfig

	// Chat (standalone messenger). Configured when ChatDomains is non-empty;
	// the domains are dedicated chat sub-domains, distinct from the feed
	// domains. Zero limits fall back to protocol defaults.
	ChatDomains        []string
	ChatEnabled        bool // serve chat (default true when domains set); false advertises chat-but-disabled
	ChatSendPerHour    int
	ChatInboxCap       int
	ChatPerPairCap     int
	ChatMaxMsgBytes    int
	ChatTTLHours       int // inbox-message TTL (hours)
	ChatAccountTTLDays int // delete idle accounts after N days (0 = never)
	ChatMaxAccounts    int // cap on stored accounts (0 = unlimited)
	// ChatSyncSeconds controls durability vs throughput for the chat store.
	// 0 = fsync every committed message (max durability, lowest throughput).
	// N>0 = flush to disk every N seconds instead (much higher throughput; a
	// crash can lose up to ~N seconds of just-received messages — acceptable
	// since chat is E2E and senders can resend).
	ChatSyncSeconds int
}

Config holds server configuration.

type DNSServer

type DNSServer struct {
	// contains filtered or unexported fields
}

DNSServer serves feed data over DNS TXT queries.

func NewDNSServer

func NewDNSServer(listenAddr, domain string, feed *Feed, queryKey, responseKey [protocol.KeySize]byte, maxPadding int, reader *TelegramReader, allowManage bool, channelsFile string, xAccounts []string, debug bool) *DNSServer

NewDNSServer creates a DNS server for the given domain.

func (*DNSServer) AddRefresher added in v0.7.0

func (s *DNSServer) AddRefresher(channelCtl channelRefresher)

AddRefresher adds an additional source refresher (e.g., X reader) for admin refresh.

func (*DNSServer) ListenAndServe

func (s *DNSServer) ListenAndServe(ctx context.Context) error

ListenAndServe starts the DNS server on UDP, shutting down when ctx is cancelled.

func (*DNSServer) SetChannelRefresher added in v0.7.0

func (s *DNSServer) SetChannelRefresher(channelCtl channelRefresher)

SetChannelRefresher allows wiring a non-Telegram channel source (e.g. public reader) for admin update/refresh operations.

func (*DNSServer) SetChatService added in v0.25.9

func (s *DNSServer) SetChatService(chat *ChatService) error

SetChatService attaches the chat handler on its dedicated sub-domains. Chat domains must not equal any feed domain (nesting under one is fine — longest-suffix matching routes them correctly). Call before ListenAndServe.

func (*DNSServer) SetExtraDomains added in v0.25.9

func (s *DNSServer) SetExtraDomains(extra []string)

SetExtraDomains adds sub-domains the server also answers feed queries on. The main domain (from NewDNSServer) stays canonical for relay path derivation. Blanks, the main domain, and duplicates are ignored. Call before ListenAndServe.

func (*DNSServer) SetReportFile added in v0.25.9

func (s *DNSServer) SetReportFile(path string) error

SetReportFile enables appending each hourly report as a JSON line to a rotating file (the canonical source for the `--report` dashboard). Empty path disables it. Call before ListenAndServe.

func (*DNSServer) SetXReader added in v0.9.0

func (s *DNSServer) SetXReader(xr *XPublicReader)

SetXReader stores the XPublicReader so baseCh can be updated on channel changes.

type Feed

type Feed struct {
	// contains filtered or unexported fields
}

Feed manages the block data for all channels.

func NewFeed

func NewFeed(channels []string) *Feed

NewFeed creates a new Feed with the given channel names.

func (*Feed) AfterFetchCycle added in v0.13.0

func (f *Feed) AfterFetchCycle(ctx context.Context)

AfterFetchCycle: touch live media → flush pending → prune stale. Touch must come first so files referenced by skipped fetches don't age out.

func (*Feed) ChannelNames

func (f *Feed) ChannelNames() []string

ChannelNames returns the configured channel names.

func (*Feed) GetBlock

func (f *Feed) GetBlock(channel, block int) ([]byte, error)

GetBlock returns the block data for a given channel and block number.

func (*Feed) GitHubRelay added in v0.13.0

func (f *Feed) GitHubRelay() *GitHubRelay

GitHubRelay returns the configured relay, or nil.

func (*Feed) IsPrivateChannel

func (f *Feed) IsPrivateChannel(channelNum int) bool

IsPrivateChannel returns true if the channel has chatType == ChatTypePrivate.

func (*Feed) MediaCache added in v0.13.0

func (f *Feed) MediaCache() *MediaCache

MediaCache returns the configured MediaCache or nil.

func (*Feed) MergeProfilePics added in v0.16.0

func (f *Feed) MergeProfilePics(pics map[string][]byte) int

MergeProfilePics is SetProfilePics that retains the existing bundle's entries (re-extracted and re-verified) and overlays pics on top. Used by readers that only know a subset of channels (Telegram-only, X-only) so each one contributes without wiping the others.

Serialised so two readers merging from the same prior state can't lose each other's writes.

func (*Feed) ProfilePicsBundle added in v0.16.0

func (f *Feed) ProfilePicsBundle() protocol.ProfilePicsBundle

ProfilePicsBundle returns a copy of the current directory.

func (*Feed) SetChannelDisplayName added in v0.11.0

func (f *Feed) SetChannelDisplayName(channelNum int, displayName string)

SetChannelDisplayName stores a human-readable title for a channel (1-indexed). It never mutates the handle in f.channels, which remains the stable identifier.

func (*Feed) SetChannels

func (f *Feed) SetChannels(channels []string)

SetChannels replaces the channel list and rebuilds metadata.

func (*Feed) SetChatAvailable added in v0.25.9

func (f *Feed) SetChatAvailable(v bool)

SetChatAvailable sets the metadata flag advertising that this server has the messenger configured. Call before SetChatInfoPayload.

func (*Feed) SetChatInfo

func (f *Feed) SetChatInfo(channelNum int, chatType protocol.ChatType, canSend bool)

SetChatInfo stores the chat type and send capability for a channel.

func (*Feed) SetChatInfoPayload added in v0.25.9

func (f *Feed) SetChatInfoPayload(payload []byte)

SetChatInfoPayload publishes (or clears, with empty payload) the chat capability data on ChatInfoChannel. Block 0 is prefixed with a uint16 total-block count (same convention as titles); the channel is signed like any other when a signing key is set. Call after SetSigningKey.

func (*Feed) SetGitHubRelay added in v0.13.0

func (f *Feed) SetGitHubRelay(r *GitHubRelay)

SetGitHubRelay attaches the GitHub fast relay. Safe to call once at startup. nil disables.

func (*Feed) SetLatestVersion added in v0.7.0

func (f *Feed) SetLatestVersion(v string)

SetLatestVersion stores latest known release version for the dedicated version channel.

func (*Feed) SetMediaCache added in v0.13.0

func (f *Feed) SetMediaCache(c *MediaCache)

SetMediaCache attaches a MediaCache to this Feed. Pass nil to disable media serving (the default for backward compat). Safe to call once at startup before any DNS query is served.

func (*Feed) SetNextFetch

func (f *Feed) SetNextFetch(ts uint32)

SetNextFetch sets the unix timestamp of the next server-side fetch.

func (*Feed) SetProfilePics added in v0.16.0

func (f *Feed) SetProfilePics(pics map[string][]byte) int

SetProfilePics replaces the profile-pic bundle with the given username → image-bytes map. Other usernames currently in the bundle are dropped; use MergeProfilePics for additive behaviour. Empty values are skipped. Requires SetMediaCache. Returns the number of avatars in the resulting bundle.

func (*Feed) SetSigningKey added in v0.25.9

func (f *Feed) SetSigningKey(priv ed25519.PrivateKey)

SetSigningKey enables ExtraBlock signing with the server key and builds the signed blocks for every channel already populated. Call once at startup, after NewFeed. nil leaves signing disabled.

func (*Feed) SetTelegramLoggedIn

func (f *Feed) SetTelegramLoggedIn(loggedIn bool)

SetTelegramLoggedIn sets the flag indicating whether the server has a Telegram session.

func (*Feed) UpdateChannel

func (f *Feed) UpdateChannel(channelNum int, msgs []protocol.Message)

UpdateChannel replaces the messages for a channel, re-serializing into blocks.

type GitHubRelay added in v0.13.0

type GitHubRelay struct {
	// contains filtered or unexported fields
}

GitHubRelay uploads encrypted media to a GitHub repo. Domain and object names are HMAC'd; blobs are AES-256-GCM. Uploads are batched into one Git Data API commit per flush.

func NewGitHubRelay added in v0.13.0

func NewGitHubRelay(cfg GitHubRelayConfig, domain, passphrase string) *GitHubRelay

NewGitHubRelay returns nil when the config is incomplete.

func (*GitHubRelay) Domain added in v0.13.0

func (g *GitHubRelay) Domain() string

Domain is the HMAC'd path segment used inside the relay repo.

func (*GitHubRelay) Flush added in v0.13.0

func (g *GitHubRelay) Flush(ctx context.Context) error

Flush forces an immediate commit of any pending uploads. Safe to call from tests or graceful shutdown; does nothing if the queue is empty.

func (*GitHubRelay) Has added in v0.13.0

func (g *GitHubRelay) Has(size int64, crc uint32) bool

Has reports whether the file is committed or queued for the next commit.

func (*GitHubRelay) MaxBytes added in v0.13.0

func (g *GitHubRelay) MaxBytes() int64

MaxBytes is the per-file cap. 0 means no cap.

func (*GitHubRelay) PruneStale added in v0.13.0

func (g *GitHubRelay) PruneStale(ctx context.Context, cutoff time.Time) (int, error)

PruneStale removes every file in `known` whose lastSeen is older than cutoff. Selection happens INSIDE commitMu so concurrent prunes from different readers can't pick the same files and race the resulting commits (which used to produce 422 BadObjectState).

func (*GitHubRelay) Repo added in v0.13.0

func (g *GitHubRelay) Repo() string

Repo returns the configured "owner/repo" so the discovery channel can expose it to clients without leaking the token.

func (*GitHubRelay) Run added in v0.13.0

func (g *GitHubRelay) Run(ctx context.Context)

Run waits for shutdown and flushes any remaining pending uploads on the way out. Flush + prune during normal operation are driven by Feed.AfterFetchCycle so they line up with the natural cadence of upstream fetches. A best-effort backstop tick handles the case where nothing has fetched in a long time (e.g. all channels were skipped from cache).

func (*GitHubRelay) TTL added in v0.13.0

func (g *GitHubRelay) TTL() time.Duration

TTL returns the configured object lifetime.

func (*GitHubRelay) Touch added in v0.13.0

func (g *GitHubRelay) Touch(size int64, crc uint32)

Touch refreshes the lastSeen timestamp without re-uploading. Used when upstream re-delivers a file that's already in the relay.

func (*GitHubRelay) Upload added in v0.13.0

func (g *GitHubRelay) Upload(ctx context.Context, body []byte) error

Upload encrypts body and queues it for the next batched commit. ErrTooLarge if body exceeds the configured cap.

type GitHubRelayConfig added in v0.13.0

type GitHubRelayConfig struct {
	Enabled    bool
	Token      string
	Repo       string
	Branch     string // default branch to commit to; "" → "main"
	StatePath  string // file used to persist lastSeen across restarts
	MaxBytes   int64
	TTLMinutes int
}

GitHubRelayConfig configures the GitHub fast relay. Active() requires Enabled + Token + Repo.

func (GitHubRelayConfig) Active added in v0.13.0

func (g GitHubRelayConfig) Active() bool

type MediaCache added in v0.13.0

type MediaCache struct {
	// contains filtered or unexported fields
}

MediaCache stores binary media blobs (images, files, ...) keyed by an upstream-stable identifier (Telegram file_id, image URL, ...). Each entry occupies one channel number drawn from the [MediaChannelStart, MediaChannelEnd] range, plus a precomputed list of fixed-size raw blocks served via the regular DNS TXT path.

The cache is safe for concurrent use. Hot-path operations (Store, GetBlock) are O(log n) at worst and typically O(1) with the help of two side maps.

func NewMediaCache added in v0.13.0

func NewMediaCache(cfg MediaCacheConfig) *MediaCache

NewMediaCache constructs a cache with the given configuration. A zero MaxFileBytes disables the size cap; a zero TTL means entries never expire (not recommended in production).

func (*MediaCache) GetBlock added in v0.13.0

func (c *MediaCache) GetBlock(channel, block uint16) ([]byte, error)

GetBlock returns one block of cached media for serving over DNS. Returns an error if the channel isn't a media channel, the entry has expired, or the block index is out of range. Increments the served-query counter.

func (*MediaCache) Lookup added in v0.13.0

func (c *MediaCache) Lookup(cacheKey string) (protocol.MediaMeta, bool)

Lookup returns the metadata for an entry by cache key, refreshing TTL on hit. Returns ok=false if not present.

func (*MediaCache) LookupByChannel added in v0.13.0

func (c *MediaCache) LookupByChannel(channel uint16) (mime, filename string, ok bool)

LookupByChannel returns the cached entry's transport metadata (mime, filename) for a serving channel. Returns ok=false if no entry is mapped. Used by the HTTP layer to pick a sensible Content-Type/Content-Disposition for clients that didn't provide one in the query string.

func (*MediaCache) MaxAcceptableBytes added in v0.13.0

func (c *MediaCache) MaxAcceptableBytes() int64

MaxAcceptableBytes returns the largest file size any enabled relay would accept. Callers use it as the "should we even fetch this?" gate so that files which fit GitHub but not DNS still get pulled. 0 means "no cap".

func (*MediaCache) SetGitHubRelay added in v0.13.0

func (c *MediaCache) SetGitHubRelay(g *GitHubRelay)

SetGitHubRelay attaches the GitHub fast relay. Store calls (and Lookup hits) will then surface RelayGitHub when the relay has the bytes.

func (*MediaCache) Stats added in v0.13.0

func (c *MediaCache) Stats() MediaCacheStats

Stats returns a snapshot of cache counters. Lock-free for the per-counter fields; Entries and Bytes are also atomic.

func (*MediaCache) Store added in v0.13.0

func (c *MediaCache) Store(cacheKey, tag string, content []byte, mimeType, filename string) (protocol.MediaMeta, error)

Store inserts (or refreshes) a media blob into the cache and returns metadata that the caller can embed in a feed message.

cacheKey is an upstream-stable identifier (e.g. Telegram file_id, image URL). When the same key is stored again, the existing entry's TTL is refreshed and the same channel/blocks are returned without copying the contents — callers should rely on this for the "fetch every 10 min" duplicate-handling case described in the design.

tag is the protocol media tag (MediaImage, MediaFile, ...); mimeType and filename are optional and stored for the HTTP layer to surface to the client. content is the raw file bytes; the caller may pass a slice it continues to use after the call (Store copies into block-sized chunks).

func (*MediaCache) StoreWithOptions added in v0.16.0

func (c *MediaCache) StoreWithOptions(cacheKey, tag string, content []byte, mimeType, filename string, opts MediaCacheStoreOptions) (protocol.MediaMeta, error)

StoreWithOptions is Store with selective relay control.

func (*MediaCache) Sweep added in v0.13.0

func (c *MediaCache) Sweep() int

Sweep evicts entries whose TTL has elapsed. Returns the number evicted. Safe to call from a periodic goroutine.

func (*MediaCache) TouchRelayEntries added in v0.13.0

func (c *MediaCache) TouchRelayEntries()

TouchRelayEntries refreshes relay lastSeen for every cached file so files referenced by skipped-fetch cycles aren't pruned.

type MediaCacheConfig added in v0.13.0

type MediaCacheConfig struct {
	MaxFileBytes    int64
	TTL             time.Duration
	Compression     protocol.MediaCompression
	Logf            func(format string, args ...interface{})
	DNSRelayEnabled bool // controls Relays[RelayDNS] on the wire
}

MediaCacheConfig configures a new MediaCache.

type MediaCacheStats added in v0.13.0

type MediaCacheStats struct {
	Entries       int64  `json:"entries"`
	Bytes         int64  `json:"bytes"`
	Queries       uint64 `json:"queries"`
	StoreHits     uint64 `json:"storeHits"`
	StoreMisses   uint64 `json:"storeMisses"`
	StoreRejected uint64 `json:"storeRejected"`
	Evictions     uint64 `json:"evictions"`
	MaxFileBytes  int64  `json:"maxFileBytes"`
	TTLSeconds    int64  `json:"ttlSeconds"`
}

MediaCacheStats is a snapshot of cache counters.

type MediaCacheStoreOptions added in v0.16.0

type MediaCacheStoreOptions struct {
	SkipGitHub bool
}

MediaCacheStoreOptions toggles relay paths for a single Store call. Zero value = both DNS channel and (if a relay is configured) GitHub upload. SkipGitHub keeps the DNS allocation but skips the upload — used when many small siblings share one bundled GitHub upload.

type PublicReader

type PublicReader struct {
	// contains filtered or unexported fields
}

PublicReader fetches recent posts from public Telegram channels via the web view.

func NewPublicReader

func NewPublicReader(channelUsernames []string, feed *Feed, msgLimit int, baseCh int) *PublicReader

NewPublicReader creates a reader for public channels without Telegram login.

func (*PublicReader) RequestRefresh added in v0.7.0

func (pr *PublicReader) RequestRefresh()

RequestRefresh signals the fetch loop to re-fetch immediately.

func (*PublicReader) Run

func (pr *PublicReader) Run(ctx context.Context) error

Run starts the periodic public-channel fetch loop.

func (*PublicReader) SetFetchInterval added in v0.13.0

func (pr *PublicReader) SetFetchInterval(d time.Duration)

SetFetchInterval overrides the default 10m fetch cadence. Caller must invoke before Run starts.

func (*PublicReader) UpdateChannels added in v0.7.0

func (pr *PublicReader) UpdateChannels(channels []string)

UpdateChannels replaces the channel list and updates Feed metadata.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server orchestrates the DNS server and Telegram reader.

func New

func New(cfg Config) (*Server, error)

New creates a new Server.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run starts both the DNS server and the Telegram reader.

type TelegramConfig

type TelegramConfig struct {
	APIID       int
	APIHash     string
	Phone       string
	Password    string // 2FA password, empty if not used
	SessionPath string
	LoginOnly   bool // if true, authenticate and exit
	CodePrompt  func(ctx context.Context) (string, error)
}

TelegramConfig holds Telegram API credentials.

type TelegramReader

type TelegramReader struct {
	// contains filtered or unexported fields
}

TelegramReader fetches messages from Telegram channels.

func NewTelegramReader

func NewTelegramReader(cfg TelegramConfig, channelUsernames []string, privateInviteHashes []string, feed *Feed, msgLimit int, baseCh int) *TelegramReader

NewTelegramReader creates a reader for the given channel usernames and private-channel invite hashes. privateInviteHashes is the list of base64url-ish invite codes (see ParseInviteHash) — empty when no private channels are configured.

func (*TelegramReader) RequestRefresh

func (tr *TelegramReader) RequestRefresh()

RequestRefresh signals the fetch loop to re-fetch immediately.

func (*TelegramReader) Run

func (tr *TelegramReader) Run(ctx context.Context) error

Run starts the Telegram client, authenticates, and periodically fetches messages.

func (*TelegramReader) SendMessage

func (tr *TelegramReader) SendMessage(ctx context.Context, channelNum int, text string) error

SendMessage sends a text message to the given channel/chat (1-indexed).

func (*TelegramReader) SetFetchInterval added in v0.13.0

func (tr *TelegramReader) SetFetchInterval(d time.Duration)

SetFetchInterval overrides the default 10m fetch cadence.

func (*TelegramReader) UpdateChannels

func (tr *TelegramReader) UpdateChannels(channels []string)

UpdateChannels replaces the channel list and updates the Feed accordingly.

type XPublicReader added in v0.7.0

type XPublicReader struct {
	// contains filtered or unexported fields
}

XPublicReader fetches public posts for X usernames via Nitter RSS endpoints.

func NewXPublicReader added in v0.7.0

func NewXPublicReader(accounts []string, feed *Feed, msgLimit int, baseCh int, instancesCSV string) *XPublicReader

func (*XPublicReader) RequestRefresh added in v0.7.0

func (xr *XPublicReader) RequestRefresh()

func (*XPublicReader) Run added in v0.7.0

func (xr *XPublicReader) Run(ctx context.Context) error

func (*XPublicReader) SetBaseCh added in v0.9.0

func (xr *XPublicReader) SetBaseCh(baseCh int)

SetBaseCh updates the base channel number when Telegram channels are added/removed.

func (*XPublicReader) SetFetchInterval added in v0.13.0

func (xr *XPublicReader) SetFetchInterval(d time.Duration)

SetFetchInterval overrides the default 10m fetch cadence.

func (*XPublicReader) UpdateChannels added in v0.7.0

func (xr *XPublicReader) UpdateChannels(_ []string)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL