Files
musicshare/apps/api/src/env.ts
T
chrisandClaude Opus 5.5 5cbde5e7f9 The Android app updates itself from our Gitea releases; feed cards fade in instead of bouncing
- API: GET /app/android/latest reads the latest Gitea release (ANDROID_RELEASES_URL, optional token for a
  private repo) with the APK's SHA-256 from SHA256SUMS; GET /app/android/apk/:name streams that APK.
- App (modules/updater): downloads in the background, checks the SHA-256, and installs through
  PackageInstaller: quietly once when leaving the app (Android 12+), otherwise from the feed banner or
  Settings > App version. F-Droid builds leave it out and drop the install permissions.
- CI: the release notes come from the fastlane changelog; the app shows the part above ---.
- Settings: Create-account and Link Spotify buttons for Last.fm and ListenBrainz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:34:04 -04:00

72 lines
3.1 KiB
TypeScript

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 <app@musicshare.binarygnome.com>'),
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);