Move docker podman question and add hybird question

Allow empty config

Continue to adjust config for hybrid
This commit is contained in:
Owen
2025-08-20 10:26:32 -07:00
parent 2907f22200
commit 907dab7d05
9 changed files with 207 additions and 116 deletions

View File

@@ -24,8 +24,8 @@ export const SESSION_COOKIE_EXPIRES =
60 *
60 *
config.getRawConfig().server.dashboard_session_length_hours;
export const COOKIE_DOMAIN =
"." + new URL(config.getRawConfig().app.dashboard_url).hostname;
export const COOKIE_DOMAIN = config.getRawConfig().app.dashboard_url ?
"." + new URL(config.getRawConfig().app.dashboard_url!).hostname : undefined;
export function generateSessionToken(): string {
const bytes = new Uint8Array(20);

View File

@@ -6,6 +6,11 @@ import logger from "@server/logger";
import SMTPTransport from "nodemailer/lib/smtp-transport";
function createEmailClient() {
if (config.isHybridMode()) {
// LETS NOT WORRY ABOUT EMAILS IN HYBRID
return;
}
const emailConfig = config.getRawConfig().email;
if (!emailConfig) {
logger.warn(

View File

@@ -96,7 +96,11 @@ export class Config {
if (!this.rawConfig) {
throw new Error("Config not loaded. Call load() first.");
}
license.setServerSecret(this.rawConfig.server.secret);
if (this.rawConfig.hybrid) {
// LETS NOT WORRY ABOUT THE SERVER SECRET WHEN HYBRID
return;
}
license.setServerSecret(this.rawConfig.server.secret!);
await this.checkKeyStatus();
}

View File

@@ -16,21 +16,28 @@ export const configSchema = z
dashboard_url: z
.string()
.url()
.optional()
.pipe(z.string().url())
.transform((url) => url.toLowerCase()),
.transform((url) => url.toLowerCase())
.optional(),
log_level: z
.enum(["debug", "info", "warn", "error"])
.optional()
.default("info"),
save_logs: z.boolean().optional().default(false),
log_failed_attempts: z.boolean().optional().default(false),
telmetry: z
telemetry: z
.object({
anonymous_usage: z.boolean().optional().default(true)
})
.optional()
.default({})
}).optional().default({
log_level: "info",
save_logs: false,
log_failed_attempts: false,
telemetry: {
anonymous_usage: true
}
}),
hybrid: z
.object({
@@ -122,9 +129,25 @@ export const configSchema = z
trust_proxy: z.number().int().gte(0).optional().default(1),
secret: z
.string()
.optional()
.transform(getEnvOrYaml("SERVER_SECRET"))
.pipe(z.string().min(8))
.optional()
}).optional().default({
integration_port: 3003,
external_port: 3000,
internal_port: 3001,
next_port: 3002,
internal_hostname: "pangolin",
session_cookie_name: "p_session_token",
resource_access_token_param: "p_token",
resource_access_token_headers: {
id: "P-Access-Token-Id",
token: "P-Access-Token"
},
resource_session_request_param: "resource_session_request_param",
dashboard_session_length_hours: 720,
resource_session_length_hours: 720,
trust_proxy: 1
}),
postgres: z
.object({
@@ -282,6 +305,10 @@ export const configSchema = z
if (data.flags?.disable_config_managed_domains) {
return true;
}
// If hybrid is defined, domains are not required
if (data.hybrid) {
return true;
}
if (keys.length === 0) {
return false;
}
@@ -290,6 +317,32 @@ export const configSchema = z
{
message: "At least one domain must be defined"
}
)
.refine(
(data) => {
// If hybrid is defined, server secret is not required
if (data.hybrid) {
return true;
}
// If hybrid is not defined, server secret must be defined
return data.server?.secret !== undefined && data.server.secret.length > 0;
},
{
message: "Server secret must be defined"
}
)
.refine(
(data) => {
// If hybrid is defined, dashboard_url is not required
if (data.hybrid) {
return true;
}
// If hybrid is not defined, dashboard_url must be defined
return data.app.dashboard_url !== undefined && data.app.dashboard_url.length > 0;
},
{
message: "Dashboard URL must be defined"
}
);
export function readConfigFile() {

View File

@@ -16,7 +16,7 @@ class TelemetryClient {
private intervalId: NodeJS.Timeout | null = null;
constructor() {
const enabled = config.getRawConfig().app.telmetry.anonymous_usage;
const enabled = config.getRawConfig().app.telemetry.anonymous_usage;
this.enabled = enabled;
const dev = process.env.ENVIRONMENT !== "prod";

View File

@@ -36,16 +36,16 @@ import { verifyTotpCode } from "@server/auth/totp";
// The RP ID is the domain name of your application
const rpID = (() => {
const url = new URL(config.getRawConfig().app.dashboard_url);
const url = config.getRawConfig().app.dashboard_url ? new URL(config.getRawConfig().app.dashboard_url!) : undefined;
// For localhost, we must use 'localhost' without port
if (url.hostname === 'localhost') {
if (url?.hostname === 'localhost' || !url) {
return 'localhost';
}
return url.hostname;
})();
const rpName = "Pangolin";
const origin = config.getRawConfig().app.dashboard_url;
const origin = config.getRawConfig().app.dashboard_url || "localhost";
// Database-based challenge storage (replaces in-memory storage)
// Challenges are stored in the webauthnChallenge table with automatic expiration

View File

@@ -8,7 +8,7 @@ export async function copyInConfig() {
const endpoint = config.getRawConfig().gerbil.base_endpoint;
const listenPort = config.getRawConfig().gerbil.start_port;
if (!config.getRawConfig().flags?.disable_config_managed_domains) {
if (!config.getRawConfig().flags?.disable_config_managed_domains && config.getRawConfig().domains) {
await copyInDomains();
}

View File

@@ -3,6 +3,7 @@ import { eq } from "drizzle-orm";
import { generateRandomString, RandomReader } from "@oslojs/crypto/random";
import moment from "moment";
import logger from "@server/logger";
import config from "@server/lib/config";
const random: RandomReader = {
read(bytes: Uint8Array): void {
@@ -22,6 +23,11 @@ function generateId(length: number): string {
}
export async function ensureSetupToken() {
if (config.isHybridMode()) {
// LETS NOT WORRY ABOUT THE SERVER SECRET WHEN HYBRID
return;
}
try {
// Check if a server admin already exists
const [existingAdmin] = await db