- 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>
72 lines
3.1 KiB
TypeScript
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);
|