feat: relays for download media

This commit is contained in:
Sarto
2026-04-30 00:47:11 +03:30
parent b4e9cd8714
commit 989fec3cec
26 changed files with 2517 additions and 368 deletions
+89 -8
View File
@@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"syscall"
"time"
"golang.org/x/term"
@@ -35,12 +36,19 @@ func main() {
sessionPath := flag.String("session", "", "Path to Telegram session file (default: {data-dir}/session.json)")
maxPadding := flag.Int("padding", 32, "Max random padding bytes in DNS responses (anti-DPI, 0=disabled)")
msgLimit := flag.Int("msg-limit", 15, "Maximum messages to fetch per Telegram channel")
fetchIntervalMin := flag.Int("fetch-interval", 10, "Fetch cycle interval in minutes (min 3, default 10)")
allowManage := flag.Bool("allow-manage", false, "Allow remote channel management and sending via DNS")
debug := flag.Bool("debug", false, "Log every decoded DNS query")
noMedia := flag.Bool("no-media", false, "Disable downloading and serving image/file media (clients see [TAG] only)")
mediaMaxSizeKB := flag.Int("media-max-size", 100, "Per-file size cap for cached media in KB (0 = no cap)")
mediaCacheTTLMin := flag.Int("media-cache-ttl", 600, "How long a cached media entry stays available, in minutes")
mediaCompression := flag.String("media-compression", "gzip", "Compression for cached media: none|gzip|deflate")
dnsMediaEnabled := flag.Bool("dns-media-enabled", false, "Serve media via DNS (slow relay)")
dnsMediaMaxSizeKB := flag.Int("dns-media-max-size", 100, "Per-file cap for the DNS relay in KB (0 = no cap)")
dnsMediaCacheTTLMin := flag.Int("dns-media-cache-ttl", 600, "TTL for DNS-relay cached media, in minutes")
dnsMediaCompression := flag.String("dns-media-compression", "gzip", "Compression for DNS-relay media bytes: none|gzip|deflate")
ghEnabled := flag.Bool("github-relay-enabled", false, "Serve media via GitHub (fast relay)")
ghToken := flag.String("github-relay-token", "", "GitHub PAT with contents:write on the relay repo")
ghRepo := flag.String("github-relay-repo", "", "GitHub repo for the fast relay, e.g. owner/repo")
ghBranch := flag.String("github-relay-branch", "main", "Default branch to commit to (e.g. main, master)")
ghMaxSizeKB := flag.Int("github-relay-max-size", 25*1024, "Per-file cap for the GitHub relay in KB (0 = no cap)")
ghCacheTTLMin := flag.Int("github-relay-ttl", 600, "TTL for GitHub-relay objects in minutes")
showVersion := flag.Bool("version", false, "Show version and exit")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "thefeed-server %s\n\nServes Telegram/X feed content over encrypted DNS for censorship-resistant access.\n\nUsage:\n thefeed-server [flags]\n\nFlags:\n", version.Version)
@@ -53,6 +61,15 @@ func main() {
os.Exit(0)
}
// Catch the common --bool true mistake: Go's flag package stops parsing at
// the first positional, so any flags after it are silently dropped. Bool
// flags must use --foo or --foo=true (no space).
if flag.NArg() > 0 {
fmt.Fprintf(os.Stderr, "Error: unexpected positional argument(s): %v\n", flag.Args())
fmt.Fprintln(os.Stderr, "Hint: bool flags must be written as --flag or --flag=true (NOT --flag true).")
os.Exit(1)
}
// Create data directory
if err := os.MkdirAll(*dataDir, 0700); err != nil {
log.Fatalf("Create data dir: %v", err)
@@ -82,6 +99,17 @@ func main() {
if os.Getenv("THEFEED_ALLOW_MANAGE") == "0" {
*allowManage = false
}
if *fetchIntervalMin == 10 {
if v := os.Getenv("THEFEED_FETCH_INTERVAL"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
*fetchIntervalMin = n
}
}
}
if *fetchIntervalMin < 3 {
fmt.Fprintf(os.Stderr, "Error: --fetch-interval must be at least 3 minutes (got %d)\n", *fetchIntervalMin)
os.Exit(1)
}
if *msgLimit == 15 {
if v := os.Getenv("THEFEED_MSG_LIMIT"); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
@@ -144,6 +172,49 @@ func main() {
}
}
if env := os.Getenv("THEFEED_DNS_MEDIA_ENABLED"); env == "0" {
*dnsMediaEnabled = false
} else if env == "1" {
*dnsMediaEnabled = true
}
if env := os.Getenv("THEFEED_DNS_MEDIA_MAX_SIZE_KB"); env != "" {
if n, err := strconv.Atoi(env); err == nil {
*dnsMediaMaxSizeKB = n
}
}
if env := os.Getenv("THEFEED_DNS_MEDIA_CACHE_TTL_MIN"); env != "" {
if n, err := strconv.Atoi(env); err == nil {
*dnsMediaCacheTTLMin = n
}
}
if env := os.Getenv("THEFEED_DNS_MEDIA_COMPRESSION"); env != "" {
*dnsMediaCompression = env
}
if !*ghEnabled && os.Getenv("THEFEED_GITHUB_RELAY_ENABLED") == "1" {
*ghEnabled = true
}
if *ghToken == "" {
*ghToken = os.Getenv("THEFEED_GITHUB_RELAY_TOKEN")
}
if *ghRepo == "" {
*ghRepo = os.Getenv("THEFEED_GITHUB_RELAY_REPO")
}
if *ghBranch == "main" {
if v := os.Getenv("THEFEED_GITHUB_RELAY_BRANCH"); v != "" {
*ghBranch = v
}
}
if env := os.Getenv("THEFEED_GITHUB_RELAY_MAX_SIZE_KB"); env != "" {
if n, err := strconv.Atoi(env); err == nil {
*ghMaxSizeKB = n
}
}
if env := os.Getenv("THEFEED_GITHUB_RELAY_TTL_MIN"); env != "" {
if n, err := strconv.Atoi(env); err == nil {
*ghCacheTTLMin = n
}
}
cfg := server.Config{
ListenAddr: *listen,
Domain: *domain,
@@ -156,10 +227,20 @@ func main() {
NoTelegram: *noTelegram,
AllowManage: *allowManage,
Debug: *debug,
NoMedia: *noMedia,
MediaMaxSize: int64(*mediaMaxSizeKB) * 1024,
MediaCacheTTL: *mediaCacheTTLMin,
MediaCompression: *mediaCompression,
DNSMediaEnabled: *dnsMediaEnabled,
DNSMediaMaxSize: int64(*dnsMediaMaxSizeKB) * 1024,
DNSMediaCacheTTL: *dnsMediaCacheTTLMin,
DNSMediaCompression: *dnsMediaCompression,
FetchInterval: time.Duration(*fetchIntervalMin) * time.Minute,
GitHubRelay: server.GitHubRelayConfig{
Enabled: *ghEnabled,
Token: *ghToken,
Repo: *ghRepo,
Branch: *ghBranch,
StatePath: filepath.Join(*dataDir, "gh_relay_state.json"),
MaxBytes: int64(*ghMaxSizeKB) * 1024,
TTLMinutes: *ghCacheTTLMin,
},
Telegram: server.TelegramConfig{
APIID: id,
APIHash: *apiHash,