mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-18 19:06:38 +00:00
add enterprise license system
This commit is contained in:
@@ -3,10 +3,11 @@ import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
|
||||
import { db } from "@server/db";
|
||||
import { SupporterKey, supporterKey } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { license } from "@server/license/license";
|
||||
import { license } from "#dynamic/license/license";
|
||||
import { configSchema, readConfigFile } from "./readConfigFile";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { build } from "@server/build";
|
||||
import logger from "@server/logger";
|
||||
|
||||
export class Config {
|
||||
private rawConfig!: z.infer<typeof configSchema>;
|
||||
@@ -111,11 +112,11 @@ export class Config {
|
||||
}
|
||||
|
||||
private async checkKeyStatus() {
|
||||
const licenseStatus = await license.check();
|
||||
if (
|
||||
build != "oss" &&
|
||||
!licenseStatus.isHostLicensed
|
||||
) {
|
||||
if (build === "enterprise") {
|
||||
await license.check();
|
||||
}
|
||||
|
||||
if (build == "oss") {
|
||||
this.checkSupporterKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,39 +12,45 @@ const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => {
|
||||
|
||||
export const configSchema = z
|
||||
.object({
|
||||
app: z.object({
|
||||
dashboard_url: z
|
||||
.string()
|
||||
.url()
|
||||
.pipe(z.string().url())
|
||||
.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),
|
||||
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
|
||||
}
|
||||
}),
|
||||
app: z
|
||||
.object({
|
||||
dashboard_url: z
|
||||
.string()
|
||||
.url()
|
||||
.pipe(z.string().url())
|
||||
.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),
|
||||
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
|
||||
}
|
||||
}),
|
||||
managed: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
secret: z.string().optional(),
|
||||
endpoint: z.string().optional().default("https://pangolin.fossorial.io"),
|
||||
endpoint: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("https://pangolin.fossorial.io"),
|
||||
redirect_endpoint: z.string().optional()
|
||||
})
|
||||
.optional(),
|
||||
@@ -61,94 +67,95 @@ export const configSchema = z
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
server: z.object({
|
||||
integration_port: portSchema
|
||||
.optional()
|
||||
.default(3003)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema.optional()),
|
||||
external_port: portSchema
|
||||
.optional()
|
||||
.default(3000)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema),
|
||||
internal_port: portSchema
|
||||
.optional()
|
||||
.default(3001)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema),
|
||||
next_port: portSchema
|
||||
.optional()
|
||||
.default(3002)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema),
|
||||
internal_hostname: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("pangolin")
|
||||
.transform((url) => url.toLowerCase()),
|
||||
session_cookie_name: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("p_session_token"),
|
||||
resource_access_token_param: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("p_token"),
|
||||
resource_access_token_headers: z
|
||||
.object({
|
||||
id: z.string().optional().default("P-Access-Token-Id"),
|
||||
token: z.string().optional().default("P-Access-Token")
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
resource_session_request_param: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("resource_session_request_param"),
|
||||
dashboard_session_length_hours: z
|
||||
.number()
|
||||
.positive()
|
||||
.gt(0)
|
||||
.optional()
|
||||
.default(720),
|
||||
resource_session_length_hours: z
|
||||
.number()
|
||||
.positive()
|
||||
.gt(0)
|
||||
.optional()
|
||||
.default(720),
|
||||
cors: z
|
||||
.object({
|
||||
origins: z.array(z.string()).optional(),
|
||||
methods: z.array(z.string()).optional(),
|
||||
allowed_headers: z.array(z.string()).optional(),
|
||||
credentials: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
trust_proxy: z.number().int().gte(0).optional().default(1),
|
||||
secret: z
|
||||
.string()
|
||||
.pipe(z.string().min(8))
|
||||
.optional(),
|
||||
maxmind_db_path: z.string().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
|
||||
}),
|
||||
server: z
|
||||
.object({
|
||||
integration_port: portSchema
|
||||
.optional()
|
||||
.default(3003)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema.optional()),
|
||||
external_port: portSchema
|
||||
.optional()
|
||||
.default(3000)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema),
|
||||
internal_port: portSchema
|
||||
.optional()
|
||||
.default(3001)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema),
|
||||
next_port: portSchema
|
||||
.optional()
|
||||
.default(3002)
|
||||
.transform(stoi)
|
||||
.pipe(portSchema),
|
||||
internal_hostname: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("pangolin")
|
||||
.transform((url) => url.toLowerCase()),
|
||||
session_cookie_name: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("p_session_token"),
|
||||
resource_access_token_param: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("p_token"),
|
||||
resource_access_token_headers: z
|
||||
.object({
|
||||
id: z.string().optional().default("P-Access-Token-Id"),
|
||||
token: z.string().optional().default("P-Access-Token")
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
resource_session_request_param: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("resource_session_request_param"),
|
||||
dashboard_session_length_hours: z
|
||||
.number()
|
||||
.positive()
|
||||
.gt(0)
|
||||
.optional()
|
||||
.default(720),
|
||||
resource_session_length_hours: z
|
||||
.number()
|
||||
.positive()
|
||||
.gt(0)
|
||||
.optional()
|
||||
.default(720),
|
||||
cors: z
|
||||
.object({
|
||||
origins: z.array(z.string()).optional(),
|
||||
methods: z.array(z.string()).optional(),
|
||||
allowed_headers: z.array(z.string()).optional(),
|
||||
credentials: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
trust_proxy: z.number().int().gte(0).optional().default(1),
|
||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
||||
maxmind_db_path: z.string().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({
|
||||
connection_string: z.string().optional(),
|
||||
@@ -161,10 +168,26 @@ export const configSchema = z
|
||||
.optional(),
|
||||
pool: z
|
||||
.object({
|
||||
max_connections: z.number().positive().optional().default(20),
|
||||
max_replica_connections: z.number().positive().optional().default(10),
|
||||
idle_timeout_ms: z.number().positive().optional().default(30000),
|
||||
connection_timeout_ms: z.number().positive().optional().default(5000)
|
||||
max_connections: z
|
||||
.number()
|
||||
.positive()
|
||||
.optional()
|
||||
.default(20),
|
||||
max_replica_connections: z
|
||||
.number()
|
||||
.positive()
|
||||
.optional()
|
||||
.default(10),
|
||||
idle_timeout_ms: z
|
||||
.number()
|
||||
.positive()
|
||||
.optional()
|
||||
.default(30000),
|
||||
connection_timeout_ms: z
|
||||
.number()
|
||||
.positive()
|
||||
.optional()
|
||||
.default(5000)
|
||||
})
|
||||
.optional()
|
||||
.default({
|
||||
@@ -193,7 +216,10 @@ export const configSchema = z
|
||||
.optional()
|
||||
.default("/var/dynamic/router_config.yml"),
|
||||
static_domains: z.array(z.string()).optional().default([]),
|
||||
site_types: z.array(z.string()).optional().default(["newt", "wireguard", "local"]),
|
||||
site_types: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.default(["newt", "wireguard", "local"]),
|
||||
allow_raw_resources: z.boolean().optional().default(true),
|
||||
file_mode: z.boolean().optional().default(false)
|
||||
})
|
||||
@@ -343,7 +369,10 @@ export const configSchema = z
|
||||
if (data.server?.secret === undefined) {
|
||||
data.server.secret = process.env.SERVER_SECRET;
|
||||
}
|
||||
return data.server?.secret !== undefined && data.server.secret.length > 0;
|
||||
return (
|
||||
data.server?.secret !== undefined &&
|
||||
data.server.secret.length > 0
|
||||
);
|
||||
},
|
||||
{
|
||||
message: "Server secret must be defined"
|
||||
@@ -356,7 +385,10 @@ export const configSchema = z
|
||||
return true;
|
||||
}
|
||||
// If hybrid is not defined, dashboard_url must be defined
|
||||
return data.app.dashboard_url !== undefined && data.app.dashboard_url.length > 0;
|
||||
return (
|
||||
data.app.dashboard_url !== undefined &&
|
||||
data.app.dashboard_url.length > 0
|
||||
);
|
||||
},
|
||||
{
|
||||
message: "Dashboard URL must be defined"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { APP_VERSION } from "./consts";
|
||||
import crypto from "crypto";
|
||||
import { UserType } from "@server/types/UserTypes";
|
||||
import { build } from "@server/build";
|
||||
import license from "@server/license/license";
|
||||
|
||||
class TelemetryClient {
|
||||
private client: PostHog | null = null;
|
||||
@@ -176,17 +177,36 @@ class TelemetryClient {
|
||||
|
||||
const stats = await this.getSystemStats();
|
||||
|
||||
this.client.capture({
|
||||
distinctId: hostMeta.hostMetaId,
|
||||
event: "supporter_status",
|
||||
properties: {
|
||||
valid: stats.supporterStatus.valid,
|
||||
tier: stats.supporterStatus.tier,
|
||||
github_username: stats.supporterStatus.githubUsername
|
||||
? this.anon(stats.supporterStatus.githubUsername)
|
||||
: "None"
|
||||
}
|
||||
});
|
||||
if (build === "enterprise") {
|
||||
const licenseStatus = await license.check();
|
||||
const payload = {
|
||||
distinctId: hostMeta.hostMetaId,
|
||||
event: "enterprise_status",
|
||||
properties: {
|
||||
is_host_licensed: licenseStatus.isHostLicensed,
|
||||
is_license_valid: licenseStatus.isLicenseValid,
|
||||
license_tier: licenseStatus.tier || "unknown"
|
||||
}
|
||||
};
|
||||
logger.debug("Sending enterprise startup telemtry payload:", {
|
||||
payload
|
||||
});
|
||||
// this.client.capture(payload);
|
||||
}
|
||||
|
||||
if (build === "oss") {
|
||||
this.client.capture({
|
||||
distinctId: hostMeta.hostMetaId,
|
||||
event: "supporter_status",
|
||||
properties: {
|
||||
valid: stats.supporterStatus.valid,
|
||||
tier: stats.supporterStatus.tier,
|
||||
github_username: stats.supporterStatus.githubUsername
|
||||
? this.anon(stats.supporterStatus.githubUsername)
|
||||
: "None"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.client.capture({
|
||||
distinctId: hostMeta.hostMetaId,
|
||||
|
||||
Reference in New Issue
Block a user