259 lines
10 KiB
TypeScript
259 lines
10 KiB
TypeScript
/**
|
|
* Lets a deployment configure the server through the `GERBEUR_*` environment
|
|
* variables, which it reads once at import, and holds the fixed limits the
|
|
* backend applies. The deno tasks load `.env` with `--env-file`, so changing a
|
|
* setting needs a restart.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import { BASE_STYLE, isStyleName, type StyleName } from "../shared/theme.ts";
|
|
import { isLocale, type Locale, SOURCE_LOCALE } from "../shared/locale.ts";
|
|
|
|
/** The port the server listens on (`GERBEUR_PORT`, default 8000). */
|
|
export const PORT: number = Number(Deno.env.get("GERBEUR_PORT")) || 8000;
|
|
/**
|
|
* The `smtps://` URL of the server `email-service.ts` sends mail through
|
|
* (`GERBEUR_SMTPS_URL`). When it is empty, no email is sent: sign-up sends no
|
|
* welcome, a password reset request does nothing, and there are no digests.
|
|
*/
|
|
export const SMTPS_URL: string = Deno.env.get("GERBEUR_SMTPS_URL")?.trim() ||
|
|
"";
|
|
/**
|
|
* Whether the instance sends email, which is whether {@link SMTPS_URL} is
|
|
* set. `email-service.ts` opens its transport by it, and index.html tells the
|
|
* client, whose settings show the digest switches disabled without it.
|
|
*/
|
|
export const EMAIL_ENABLED: boolean = SMTPS_URL.length > 0;
|
|
/**
|
|
* The `From` address of every email (`GERBEUR_FROM_EMAIL`), as an address or
|
|
* as `Name <address>`. `email-service.ts` refuses to load without it when
|
|
* {@link SMTPS_URL} is set.
|
|
*/
|
|
export const FROM_EMAIL: string = Deno.env.get("GERBEUR_FROM_EMAIL")?.trim() ||
|
|
"";
|
|
/**
|
|
* The Markdown of the welcome email sent on registration
|
|
* (`GERBEUR_WELCOME_EMAIL_BODY`), or null for the built-in one in the
|
|
* {@link EMAIL_LOCALE}. `api/mail/welcome.ts` replaces `{{site_name}}` and
|
|
* `{{username}}` in it.
|
|
*/
|
|
export const WELCOME_EMAIL_BODY: string | null =
|
|
Deno.env.get("GERBEUR_WELCOME_EMAIL_BODY")?.trim() || null;
|
|
/**
|
|
* The language of every email (`GERBEUR_EMAIL_LOCALE`). The interface picks
|
|
* each visitor's language in their browser, which the server never learns, so
|
|
* the emails take the instance's. A value that isn't a locale means the
|
|
* source one, English.
|
|
*/
|
|
export const EMAIL_LOCALE: Locale = (() => {
|
|
const locale = Deno.env.get("GERBEUR_EMAIL_LOCALE")?.trim();
|
|
return isLocale(locale) ? locale : SOURCE_LOCALE;
|
|
})();
|
|
/**
|
|
* The instance's style (`GERBEUR_DEFAULT_STYLE`). The site shows it until a
|
|
* visitor or their account picks one, through index.html, and the emails that
|
|
* aren't a user's own (the welcome and the password reset) are drawn in it. A
|
|
* value that isn't a style means {@link BASE_STYLE}.
|
|
*/
|
|
export const DEFAULT_STYLE: StyleName = (() => {
|
|
const style = Deno.env.get("GERBEUR_DEFAULT_STYLE")?.trim();
|
|
return isStyleName(style) ? style : BASE_STYLE;
|
|
})();
|
|
/**
|
|
* The key lib/jwt.ts signs and verifies tokens with (`GERBEUR_JWT_SECRET`).
|
|
* The server refuses to start without it.
|
|
*/
|
|
export const JWT_SECRET: string = Deno.env.get("GERBEUR_JWT_SECRET")?.trim() ||
|
|
"";
|
|
/**
|
|
* The interface the server binds to (`GERBEUR_LISTEN_HOST`). The default,
|
|
* 0.0.0.0, lets Docker forward the port; 127.0.0.1 keeps it to loopback.
|
|
*/
|
|
export const LISTEN_HOST: string = Deno.env.get("GERBEUR_LISTEN_HOST") ||
|
|
"0.0.0.0";
|
|
/**
|
|
* The URL visitors reach the server at (`GERBEUR_PUBLIC_URL`), without a
|
|
* trailing slash. Behind a reverse proxy it must be the outside URL: it is an
|
|
* allowed origin, it tells links to this site's own pages from other links,
|
|
* and it prefixes the image URLs in their previews.
|
|
*/
|
|
export const PUBLIC_URL: string =
|
|
Deno.env.get("GERBEUR_PUBLIC_URL")?.trim().replace(/\/$/, "") ||
|
|
`http://localhost:${PORT}`;
|
|
/**
|
|
* Points password reset links and the Open Graph tags' page URLs at the
|
|
* frontend when it isn't served from {@link PUBLIC_URL}, as with a separate
|
|
* Vite server, and allows it as an origin. It is `GERBEUR_FRONTEND_URL`,
|
|
* without a trailing slash.
|
|
*/
|
|
export const FRONTEND_URL: string =
|
|
Deno.env.get("GERBEUR_FRONTEND_URL")?.trim().replace(/\/$/, "") ||
|
|
PUBLIC_URL;
|
|
/**
|
|
* The SQLite database file (`GERBEUR_DB_PATH`). api/test/setup.ts points it at
|
|
* a throwaway file before anything opens the database.
|
|
*/
|
|
export const DB_PATH: string = Deno.env.get("GERBEUR_DB_PATH") ||
|
|
"api/sql/gerbeur.db";
|
|
|
|
/**
|
|
* The directory uploads are stored under (`GERBEUR_UPLOADS_DIR`). Each kind has
|
|
* its subdirectory below, created by the first upload, and each file is named
|
|
* after the id of what it belongs to.
|
|
*/
|
|
export const UPLOADS_DIR: string = Deno.env.get("GERBEUR_UPLOADS_DIR") ||
|
|
"api/uploads";
|
|
/** Holds the file of each file dump. */
|
|
export const DUMPS_DIR = `${UPLOADS_DIR}/dumps`;
|
|
/** Holds each user's avatar. */
|
|
export const AVATARS_DIR = `${UPLOADS_DIR}/avatars`;
|
|
/** Holds each playlist's cover. */
|
|
export const PLAYLIST_IMAGES_DIR = `${UPLOADS_DIR}/playlist-images`;
|
|
/**
|
|
* Holds the images pasted into chat messages, comments, dump notes and
|
|
* descriptions. The startup tasks purge those nothing links to.
|
|
*/
|
|
export const ATTACHMENTS_DIR = `${UPLOADS_DIR}/attachments`;
|
|
/**
|
|
* Holds the thumbnails a poster uploads (`<id>-custom`) and those the
|
|
* thumbnails route generates from file dumps.
|
|
*/
|
|
export const THUMBNAILS_DIR = `${UPLOADS_DIR}/thumbnails`;
|
|
|
|
/**
|
|
* Caps the size of an image upload, in bytes, for avatars, covers, thumbnails
|
|
* and attachments. lib/upload.ts states it in megabytes in its error.
|
|
*/
|
|
export const MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
|
|
/**
|
|
* The MIME type prefixes a file dump may have. dump-service also accepts the
|
|
* exact types of {@link DUMP_ALLOWED_MIME_TYPES}.
|
|
*/
|
|
export const DUMP_ALLOWED_MIME_PREFIXES: string[] = [
|
|
"text/",
|
|
"image/",
|
|
"video/",
|
|
"audio/",
|
|
];
|
|
/** The documents and archives a file dump may be, beyond the prefixes. */
|
|
export const DUMP_ALLOWED_MIME_TYPES: Set<string> = new Set([
|
|
"application/pdf",
|
|
"application/json",
|
|
"application/zip",
|
|
"application/x-zip-compressed",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
"application/msword",
|
|
"application/vnd.ms-excel",
|
|
"application/vnd.ms-powerpoint",
|
|
]);
|
|
|
|
/** The page size when a request names none. */
|
|
export const PAGINATION_DEFAULT_LIMIT = 20;
|
|
/** The largest page size a request may ask for. */
|
|
export const PAGINATION_MAX_LIMIT = 100;
|
|
|
|
/** How long an attachment nothing links to is kept before startup purges it. */
|
|
export const ORPHANED_ATTACHMENTS_RETENTION_HOURS = 1;
|
|
/**
|
|
* How long an invite stays usable. invite-service refuses an older one, and
|
|
* the startup tasks purge it.
|
|
*/
|
|
export const UNUSED_INVITES_RETENTION_DAYS = 7;
|
|
|
|
/**
|
|
* How often, in minutes, the `/community` page's figures are recomputed
|
|
* (`GERBEUR_COMMUNITY_REFRESH_MINUTES`), 15 by default.
|
|
*/
|
|
export const COMMUNITY_REFRESH_MINUTES: number =
|
|
Number(Deno.env.get("GERBEUR_COMMUNITY_REFRESH_MINUTES")) || 15;
|
|
|
|
/**
|
|
* The site's name (`GERBEUR_SITE_NAME`). It is filled into index.html, the OG
|
|
* tags, the icons' manifest and the emails.
|
|
*/
|
|
export const OG_SITE_NAME: string = Deno.env.get("GERBEUR_SITE_NAME") ||
|
|
"gerbeur";
|
|
|
|
/**
|
|
* The emoji that serves as the site's logo (`GERBEUR_SITE_EMOJI`). The icons
|
|
* script draws the favicons from it, and index.html carries it to the app's
|
|
* header, so changing it needs a restart but no rebuild.
|
|
*/
|
|
export const SITE_EMOJI: string = Deno.env.get("GERBEUR_SITE_EMOJI") || "🚚";
|
|
|
|
/**
|
|
* How the frontend plays Bandcamp. `"embed"` uses Bandcamp's iframe, which
|
|
* can't autoplay. `"native"` resolves the page's streams through
|
|
* `GET /api/bandcamp/tracks` and plays them in the app's player, which
|
|
* autoplays and queues an album's tracks.
|
|
*/
|
|
export type BandcampPlayer = "embed" | "native";
|
|
/**
|
|
* How the frontend plays Bandcamp (`GERBEUR_BANDCAMP_PLAYER`), passed to it in
|
|
* index.html. Any value but `"native"` means `"embed"`, so a typo falls back to
|
|
* the player that always works.
|
|
*/
|
|
export const BANDCAMP_PLAYER: BandcampPlayer =
|
|
Deno.env.get("GERBEUR_BANDCAMP_PLAYER")?.trim() === "native"
|
|
? "native"
|
|
: "embed";
|
|
|
|
/**
|
|
* The background of the generated Apple touch icon, the manifest's colors, and
|
|
* the accent of previews of this site's own pages. index.html's `theme-color`
|
|
* tags don't follow it.
|
|
*/
|
|
export const THEME_COLOR = "#111827";
|
|
|
|
const rawOrigins = Deno.env.get("GERBEUR_ALLOWED_ORIGINS") ?? "";
|
|
/**
|
|
* The origins that CORS in main.ts and the WebSocket route accept. They are
|
|
* {@link PUBLIC_URL}, {@link FRONTEND_URL} and the comma-separated
|
|
* `GERBEUR_ALLOWED_ORIGINS`.
|
|
*/
|
|
export const ALLOWED_ORIGINS: string[] = Array.from(
|
|
new Set([
|
|
PUBLIC_URL,
|
|
FRONTEND_URL,
|
|
...rawOrigins.split(",").map((o) => o.trim()).filter(Boolean),
|
|
]),
|
|
);
|
|
|
|
/**
|
|
* Lets federated sign-in reach instances on private addresses, `localhost`
|
|
* among them, and over plain http (`GERBEUR_FEDERATION_ALLOW_PRIVATE=1`), so
|
|
* that two instances on one machine can trust each other in development.
|
|
* Off, `remote/gerbeur-instance.ts` reaches only public https instances, since
|
|
* an identity is only as safe as the connection it came over.
|
|
*/
|
|
export const FEDERATION_ALLOW_PRIVATE: boolean =
|
|
Deno.env.get("GERBEUR_FEDERATION_ALLOW_PRIVATE")?.trim() === "1";
|
|
/**
|
|
* Which ways federated sign-in goes, each on unless set to `0`.
|
|
*
|
|
* - `incoming` (`GERBEUR_FEDERATION_INCOMING`): users of other instances sign
|
|
* in here. Off, the login and register pages offer no other instance,
|
|
* `/manage` lists only the blocked ones, which this instance's users can't
|
|
* sign in to (and none with `outgoing` off too), `federation-service.ts`
|
|
* refuses their sign-ins, and the accounts that came from one are signed
|
|
* out (`getSessionState`) until it is on again. Their homes' events are
|
|
* still applied, so that none is lost meanwhile.
|
|
* - `outgoing` (`GERBEUR_FEDERATION_OUTGOING`): this instance's users sign in
|
|
* to others. Off, `identity-provider-service.ts` lets no instance sign them
|
|
* in, and its metadata says so, which the others check before sending
|
|
* anyone here to sign in. The settings page lists no instances, since at
|
|
* startup `main.ts` withdraws every grant they gave, which signs them out
|
|
* there.
|
|
*
|
|
* index.html tells the client both. An object, so that the tests can turn one
|
|
* off for a while.
|
|
*/
|
|
export const FEDERATION: { incoming: boolean; outgoing: boolean } = {
|
|
incoming: Deno.env.get("GERBEUR_FEDERATION_INCOMING")?.trim() !== "0",
|
|
outgoing: Deno.env.get("GERBEUR_FEDERATION_OUTGOING")?.trim() !== "0",
|
|
};
|