mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-07 14:26:47 +00:00
* feat: add Caddy reverse proxy with auto HTTPS for LAN access and auto-derive WebSocket URL Add a Caddy service to docker-compose.standalone.yml that provides automatic HTTPS with local certificates, enabling secure access to both the frontend and API from the local network through a single entrypoint. Backend changes: - Add ROOT_PATH setting to FastAPI so the API can be served under /api prefix - Route frontend and API (/server-api) through Caddy reverse proxy Frontend changes: - Support WEBSOCKET_URL=auto to derive the WebSocket URL from API_URL automatically, using the page protocol (http→ws, https→wss) and host - Make WEBSOCKET_URL env var optional instead of required * style: pre-commit * fix: make standalone compose self-contained (drop !reset dependency) docker-compose.standalone.yml used !reset YAML tags to clear network_mode and volumes from the base compose. !reset requires Compose v2.24+ and breaks on Colima + brew-installed compose. Rewrite as a fully self-contained file with all services defined directly (server, worker, beat, redis, postgres, web, garage, cpu, gpu-nvidia, ollama, ollama-cpu). No longer overlays docker-compose.yml. Update setup-standalone.sh compose_cmd() to use only the standalone file instead of both files. * fix: update standalone docs to match self-contained compose usage --------- Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
93 lines
2.6 KiB
TypeScript
93 lines
2.6 KiB
TypeScript
import {
|
|
assertExists,
|
|
assertExistsAndNonEmptyString,
|
|
NonEmptyString,
|
|
parseMaybeNonEmptyString,
|
|
parseNonEmptyString,
|
|
} from "./utils";
|
|
import { isBuildPhase } from "./next";
|
|
import { getNextEnvVar } from "./nextBuild";
|
|
|
|
export const FEATURE_REQUIRE_LOGIN_ENV_NAME = "FEATURE_REQUIRE_LOGIN" as const;
|
|
export const FEATURE_PRIVACY_ENV_NAME = "FEATURE_PRIVACY" as const;
|
|
export const FEATURE_BROWSE_ENV_NAME = "FEATURE_BROWSE" as const;
|
|
export const FEATURE_SEND_TO_ZULIP_ENV_NAME = "FEATURE_SEND_TO_ZULIP" as const;
|
|
export const FEATURE_ROOMS_ENV_NAME = "FEATURE_ROOMS" as const;
|
|
|
|
const FEATURE_ENV_NAMES = [
|
|
FEATURE_REQUIRE_LOGIN_ENV_NAME,
|
|
FEATURE_PRIVACY_ENV_NAME,
|
|
FEATURE_BROWSE_ENV_NAME,
|
|
FEATURE_SEND_TO_ZULIP_ENV_NAME,
|
|
FEATURE_ROOMS_ENV_NAME,
|
|
] as const;
|
|
|
|
export type FeatureEnvName = (typeof FEATURE_ENV_NAMES)[number];
|
|
|
|
export type EnvFeaturePartial = {
|
|
[key in FeatureEnvName]: boolean | null;
|
|
};
|
|
|
|
// CONTRACT: isomorphic with JSON.stringify
|
|
export type ClientEnvCommon = EnvFeaturePartial & {
|
|
API_URL: NonEmptyString;
|
|
WEBSOCKET_URL: NonEmptyString | null;
|
|
};
|
|
|
|
let clientEnv: ClientEnvCommon | null = null;
|
|
export const getClientEnvClient = (): ClientEnvCommon => {
|
|
if (typeof window === "undefined") {
|
|
throw new Error(
|
|
"getClientEnv() called during SSR - this should only be called in browser environment",
|
|
);
|
|
}
|
|
if (clientEnv) return clientEnv;
|
|
clientEnv = assertExists(
|
|
JSON.parse(
|
|
assertExistsAndNonEmptyString(
|
|
document.body.dataset.env,
|
|
"document.body.dataset.env is missing",
|
|
),
|
|
),
|
|
"document.body.dataset.env is parsed to nullish",
|
|
);
|
|
return clientEnv!;
|
|
};
|
|
|
|
const parseBooleanString = (str: string | undefined): boolean | null => {
|
|
if (str === undefined) return null;
|
|
return str === "true";
|
|
};
|
|
|
|
export const getClientEnvServer = (): ClientEnvCommon => {
|
|
if (typeof window !== "undefined") {
|
|
throw new Error(
|
|
"getClientEnv() not called during SSR - this should only be called in server environment",
|
|
);
|
|
}
|
|
if (clientEnv) return clientEnv;
|
|
|
|
const features = FEATURE_ENV_NAMES.reduce((acc, x) => {
|
|
acc[x] = parseBooleanString(process.env[x]);
|
|
return acc;
|
|
}, {} as EnvFeaturePartial);
|
|
|
|
if (isBuildPhase) {
|
|
return {
|
|
API_URL: getNextEnvVar("API_URL"),
|
|
WEBSOCKET_URL: parseMaybeNonEmptyString(process.env.WEBSOCKET_URL ?? ""),
|
|
...features,
|
|
};
|
|
}
|
|
|
|
clientEnv = {
|
|
API_URL: getNextEnvVar("API_URL"),
|
|
WEBSOCKET_URL: parseMaybeNonEmptyString(process.env.WEBSOCKET_URL ?? ""),
|
|
...features,
|
|
};
|
|
return clientEnv;
|
|
};
|
|
|
|
export const getClientEnv =
|
|
typeof window === "undefined" ? getClientEnvServer : getClientEnvClient;
|