import 'dotenv/config'; import { z } from 'zod'; const optional = z .string() .optional() .transform((v) => (v ? v : undefined)); const schema = z.object({ NODE_ENV: z.string().default('development'), DATABASE_URL: z.string().default('postgres://musicshare:musicshare@localhost:5447/musicshare'), REDIS_URL: z.string().default('redis://localhost:6380'), PORT: z.coerce.number().default(4000), WEB_URL: z.string().default('http://localhost:3737'), API_PUBLIC_URL: z.string().default('http://127.0.0.1:4000'), COOKIE_SECURE: z .string() .default('false') .transform((v) => v === 'true'), TOKEN_ENCRYPTION_KEY: z .string() .refine((v) => Buffer.from(v, 'base64').length === 32, 'TOKEN_ENCRYPTION_KEY must be 32 bytes, base64-encoded'), SPOTIFY_CLIENT_ID: optional, SPOTIFY_CLIENT_SECRET: optional, LASTFM_API_KEY: optional, /** Enables "Sign in with Last.fm" (web auth). Without it, users type their username. */ LASTFM_SHARED_SECRET: optional, ODESLI_API_KEY: optional, /** Web Push keys (`npx web-push generate-vapid-keys`). Optional: without them the server makes a pair and keeps it in the database. */ VAPID_PUBLIC_KEY: optional, VAPID_PRIVATE_KEY: optional, /** Lyrics come from LRCLIB (open source, self-hostable). Point this at your own instance, or set it empty to turn lyrics off. */ LRCLIB_URL: z .string() .default('https://lrclib.net') .transform((v) => v.replace(/\/+$/, '')), /** * App updates: the Gitea/Forgejo API URL of the latest Android release, e.g. * https://gitea.example.com/api/v1/repos/owner/musicshare/releases/latest. Empty turns update checks off. * The token (read access to the repo) is only needed when the repo is private; downloads go through us. */ ANDROID_RELEASES_URL: z.string().default(''), ANDROID_RELEASES_TOKEN: optional, /** Apple Music (MusicKit): Team ID, MusicKit key ID, and the key's .p8 contents (newlines may be written as \n). */ APPLE_TEAM_ID: optional, APPLE_MUSICKIT_KEY_ID: optional, APPLE_MUSICKIT_PRIVATE_KEY: optional, /** Identifies this app to Plex. Keep it stable: changing it invalidates users' Plex authorizations. */ PLEX_CLIENT_ID: z.string().default('musicshare-dev'), /** * Allow Jellyfin/Plex server URLs on private networks. Only enable for local * dev or self-hosting: in a hosted deployment it lets users make the server * request internal addresses (SSRF). */ /** Outgoing email (password resets). Without SMTP_HOST, emails are only logged. */ SMTP_HOST: optional, SMTP_PORT: z.coerce.number().default(465), SMTP_USER: optional, SMTP_PASSWORD: optional, MAIL_FROM: z.string().default('musicshare '), ALLOW_PRIVATE_SERVER_URLS: z .string() .default('false') .transform((v) => v === 'true'), }); export const env = schema.parse(process.env); export const spotifyConfigured = Boolean(env.SPOTIFY_CLIENT_ID && env.SPOTIFY_CLIENT_SECRET); export const appleMusicConfigured = Boolean(env.APPLE_TEAM_ID && env.APPLE_MUSICKIT_KEY_ID && env.APPLE_MUSICKIT_PRIVATE_KEY); export const mailConfigured = Boolean(env.SMTP_HOST);