ultimate-media-tracker/server/config.go
Tronax de353f0218
feat: add Android app and end-to-end encrypted library sync
Introduce the Kotlin/Compose Android companion app and extend the TMDB
proxy into a combo server that synchronizes the whole library as an
encrypted snapshot, with all three components (Go server, Android,
desktop) sharing one zero-knowledge crypto envelope and JSON schema.

- server: add PostgreSQL-backed /sync/library (GET/PUT) with optimistic
  concurrency (revision + 409 on conflict); sync stays disabled unless
  DATABASE_URL is set. Add docker-compose with Postgres.
- desktop: add libsodium CryptoEnvelope, LibrarySerializer and SyncClient;
  storage-mode and passphrase settings; a "Sync now" toolbar action with
  conflict resolution.
- android: Room-backed library with local/cloud storage modes, encrypted
  sync client, and settings UI. The proxy server (URL + token) can be
  configured as a TMDB proxy even in local-only mode.

Crypto is identical across platforms: Argon2id (libsodium crypto_pwhash,
INTERACTIVE) + XChaCha20-Poly1305 in a versioned "UMTS" envelope, so a
snapshot encrypted on one client decrypts on the other.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-21 00:35:54 +02:00

91 lines
2.8 KiB
Go

package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
// Config holds all runtime settings, sourced from environment variables so the
// server is easy to run under systemd, Docker or a PaaS without code changes.
type Config struct {
ListenAddr string // e.g. ":8080"
PublicURL string // externally reachable base URL, e.g. https://tmdb.example.com
TMDBKey string // the single shared TMDB v3 API key
TMDBBase string // upstream base, defaults to https://api.themoviedb.org/3
Issuer string // Authentik OIDC issuer URL
ClientID string // OAuth2 client id
ClientSecret string // OAuth2 client secret
RedirectURL string // OAuth2 redirect URL (PublicURL + /callback if empty)
SessionKey []byte // HMAC secret for signing access tokens / state cookies
AllowGroups []string // if set, the user must be in one of these Authentik groups
TokenTTL time.Duration // lifetime of issued desktop-app tokens
DatabaseURL string // PostgreSQL DSN for library sync; empty disables sync
}
func getenv(key, def string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return def
}
func loadConfig() (*Config, error) {
c := &Config{
ListenAddr: getenv("LISTEN_ADDR", ":8080"),
PublicURL: strings.TrimRight(getenv("PUBLIC_URL", "http://localhost:8080"), "/"),
TMDBKey: getenv("TMDB_API_KEY", ""),
TMDBBase: strings.TrimRight(getenv("TMDB_BASE_URL", "https://api.themoviedb.org/3"), "/"),
Issuer: getenv("OIDC_ISSUER", ""),
ClientID: getenv("OIDC_CLIENT_ID", ""),
ClientSecret: getenv("OIDC_CLIENT_SECRET", ""),
RedirectURL: getenv("OIDC_REDIRECT_URL", ""),
DatabaseURL: getenv("DATABASE_URL", ""),
}
if c.RedirectURL == "" {
c.RedirectURL = c.PublicURL + "/callback"
}
secret := getenv("SESSION_SECRET", "")
if secret == "" {
return nil, fmt.Errorf("SESSION_SECRET is required (a long random string)")
}
c.SessionKey = []byte(secret)
if groups := getenv("ALLOWED_GROUPS", ""); groups != "" {
for _, g := range strings.Split(groups, ",") {
if g = strings.TrimSpace(g); g != "" {
c.AllowGroups = append(c.AllowGroups, g)
}
}
}
days, err := strconv.Atoi(getenv("TOKEN_TTL_DAYS", "90"))
if err != nil || days <= 0 {
days = 90
}
c.TokenTTL = time.Duration(days) * 24 * time.Hour
// Required fields.
var missing []string
if c.TMDBKey == "" {
missing = append(missing, "TMDB_API_KEY")
}
if c.Issuer == "" {
missing = append(missing, "OIDC_ISSUER")
}
if c.ClientID == "" {
missing = append(missing, "OIDC_CLIENT_ID")
}
if c.ClientSecret == "" {
missing = append(missing, "OIDC_CLIENT_SECRET")
}
if len(missing) > 0 {
return nil, fmt.Errorf("missing required environment variables: %s", strings.Join(missing, ", "))
}
return c, nil
}