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>
91 lines
2.8 KiB
Go
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
|
|
}
|