Something went wrong. Try again.
A decentralized music tracking and discovery platform built on AT Protocol 🎵 rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112import dotenv from "dotenv";import { bool, cleanEnv, host, num, port, str } from "envalid";
dotenv.config();
export const env = cleanEnv(process.env, { NODE_ENV: str({ default: "development", choices: ["development", "production", "test"], }), HOST: host({ default: "localhost" }), PORT: port({ default: 8000 }), JWKS_JSON: str({ devDefault: '{"keys":[]}' }), PUBLIC_URL: str({ default: "http://localhost:8000" }), DB_PATH: str({ devDefault: ":memory:" }), KV_DB_PATH: str({ devDefault: ":memory:" }), COOKIE_SECRET: str({ devDefault: "00000000000000000000000000000000" }), FRONTEND_URL: str({ devDefault: "http://localhost:5174" }), JWT_SECRET: str({ devDefault: "00000000000000000000000000000000" }), SPOTIFY_REDIRECT_URI: str({ devDefault: "http://localhost:8000/spotify/callback", }), SPOTIFY_CLIENT_ID: str({}), SPOTIFY_CLIENT_SECRET: str({}), SPOTIFY_API_URL: str({ default: "https://api.spotify.com/v1" }), SPOTIFY_ENCRYPTION_KEY: str({}), SPOTIFY_ENCRYPTION_IV: str(), ROCKSKY_BETA_TOKEN: str({}), // Read+write endpoint. Still the only required one: the split URLs below // both fall back to it. XATA_POSTGRES_URL: str({}), // Read-only replica. Cheap to read from but lags the primary, so it must not // serve anything that has to reflect a write that just happened. XATA_READ_POSTGRES_URL: str({ default: "" }), // Primary. Every write, and every read that has to be fresh. XATA_WRITE_POSTGRES_URL: str({ default: "" }), NATS_URL: str({ default: "nats://localhost:4222" }), ANALYTICS: str({ default: "http://localhost:7879" }), TRACKLIST: str({ default: "http://localhost:7884" }), REDIS_URL: str({ default: "redis://localhost:6379" }), MATCHING_URL: str({ default: "http://127.0.0.1:8097" }), SUBMISSIONS_URL: str({ default: "http://127.0.0.1:8098" }), // No insecure development fallback: both the queue and callback require it. SUBMISSIONS_SECRET: str({ default: "" }), MUSICBRAINZ_URL: str({ default: "http://localhost:8088" }), DEEZER_URL: str({ default: "http://localhost:8090" }), DISCOGS_URL: str({ default: "http://localhost:8095" }), DISCOGS_ENRICHMENT_ENABLED: bool({ default: false }), PRIVATE_KEY_1: str({}), PRIVATE_KEY_2: str({}), PRIVATE_KEY_3: str({}), DISABLED_TEALFM: str({ default: "" }), STORAGE_ENCRYPTION_KEY: str({ devDefault: "0000000000000000000000000000000000000000000000000000000000000000", }), S3_ENDPOINT: str({}), S3_REGION: str({ default: "auto" }), S3_ACCESS_KEY_ID: str({}), S3_SECRET_ACCESS_KEY: str({}), S3_BUCKET_NAME: str({ default: "rocksky-library" }), S3_COVERS_BUCKET_NAME: str({ default: "rocksky" }), CDN_URL: str({ default: "https://cdn.rocksky.app" }), MEDIA_CDN_URL: str({ default: "https://files.rocksky.app" }), TYPESENSE_HOST: str({ default: "localhost" }), TYPESENSE_PORT: port({ default: 8108 }), TYPESENSE_PROTOCOL: str({ default: "http", choices: ["http", "https"] }), TYPESENSE_API_KEY: str({}), // Base URL of the internal navidrome (Subsonic-compat) service that the // app.rocksky.library.* XRPC methods proxy to. Server-to-server only. NAVIDROME_INTERNAL_URL: str({ default: "http://127.0.0.1:4533" }), // Shared secret sent as the X-Rocksky-Internal header so navidrome trusts the // JWT-authenticated user this proxy resolved (skips Subsonic credential auth). NAVIDROME_INTERNAL_SECRET: str({ devDefault: "" }), // The did:web every Rocksky-native feed (the "all" feed plus every genre // algo in apps/feeds) is registered under — see getFeed.ts. Used to // recognize feeds we host ourselves, as opposed to a third party's. ROCKSKY_FEEDGEN_DID: str({ default: "did:web:discover.rocksky.app" }), // Port apps/feeds listens on, on this same host. getFeed calls it directly // there instead of resolving our own feeds over public DNS/TLS. // // No devDefault on purpose: prod runs with NODE_ENV=development, so a // devDefault silently becomes the production value. Getting this wrong // points every native feed at whatever else is listening — 8002 is // apps/ws, which answers every plain HTTP request with 426. ROCKSKY_FEEDGEN_PORT: port({ default: 8003 }), // PDS hosts that are unreachable from this server, mapped to a reverse proxy // that is (see lib/pdsProxy.ts). Comma separated "unreachable=proxy" pairs; // set to "" to disable. PDS_HOST_OVERRIDES: str({ default: "caramelo.social.br=caramelo.tsiry.workers.dev", }), // PDS that GET /login?prompt=create sends new users to. Passed to the OAuth // resolver as a service URL, so no login_hint is attached and the PDS shows // its own sign-up screen. ROCKSKY_SIGNUP_PDS: str({ default: "https://rocksky.social" }), // Bot-scrobble guard (see lib/scrobbleGuard.ts). Set SCROBBLE_ABUSE_MAX=0 to disable. SCROBBLE_ABUSE_WINDOW: num({ default: 1800 }), // rolling window, seconds (30m) SCROBBLE_ABUSE_MAX: num({ default: 25 }), // max accepted scrobbles per window SCROBBLE_ABUSE_BLOCK: num({ default: 3600 }), // temp block duration, seconds (1h) // Postgres pool (see drizzle.ts). Tunable without a redeploy. PG_MAX_CONNECTIONS: num({ default: 20 }), // Coarse server-side backstop for a single statement. Handlers that use // lib/dbQuery cancel at their own (shorter) budget; this only catches the // ones that do not. Kept above the longest handler timeout (120s charts is // the outlier) so it never truncates a legitimately slow query. PG_STATEMENT_TIMEOUT: num({ default: 60_000 }), // Shows up in pg_stat_activity.application_name, so it is possible to tell // which process is holding connections. PG_APP_NAME: str({ default: "" }),});