Add license tiers

This commit is contained in:
Owen
2026-09-15 16:25:43 -04:00
parent 64bb6d9f9c
commit 324f3e50ff
14 changed files with 116 additions and 81 deletions
+2 -2
View File
@@ -45,9 +45,9 @@ export class JobScheduler {
label: string
): () => Promise<void> {
return async () => {
if (!(await license.isUnlocked())) {
if (!(await license.hasTier(["personal", "tier2", "enterprise"]))) {
logger.debug(
`Skipping ${label} tick - license is not subscribed`
`Skipping ${label} tick - requires a tier2 license`
);
return;
}
+11 -6
View File
@@ -63,10 +63,11 @@ export class AuthoritativeDNSServer {
private allDomains: Set<string> = new Set();
private domainRefreshInterval: NodeJS.Timeout | null = null;
// Cached license/subscription status. license.isUnlocked() does a DB
// round-trip on every call, so it can't be checked per-query on a UDP
// server that may see very high query volume - instead it's polled on
// the same cadence as the domain set refresh and read from memory here.
// Cached license/plan status - only a tier2 license unlocks the DNS
// server. license.hasPlan() does a DB round-trip on every call, so it
// can't be checked per-query on a UDP server that may see very high
// query volume - instead it's polled on the same cadence as the domain
// set refresh and read from memory here.
private isLicensed: boolean = false;
private licenseRefreshInterval: NodeJS.Timeout | null = null;
@@ -128,7 +129,7 @@ export class AuthoritativeDNSServer {
}
if (!this.isLicensed) {
logger.debug("Refusing DNS query - license is not subscribed");
logger.debug("Refusing DNS query - requires a tier2 license");
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
this.sendResponse(packet, [], rinfo, false, 5, []);
return;
@@ -1042,7 +1043,11 @@ export class AuthoritativeDNSServer {
private async refreshLicenseStatus(): Promise<void> {
try {
this.isLicensed = await license.isUnlocked();
this.isLicensed = await license.hasTier([
"personal",
"tier2",
"enterprise"
]);
} catch (error) {
logger.error("Failed to refresh license status:", error);
this.isLicensed = false;
+16 -26
View File
@@ -54,6 +54,7 @@ import {
getValidCertificatesForDomains
} from "@server/lib/certificates";
import { build } from "@server/build";
import license from "#private/license/license";
import regionalCache from "#private/lib/cache";
import { TargetWithSite } from "@server/lib/traefik/types";
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
@@ -395,8 +396,15 @@ export async function getTraefikConfig(
)
);
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
// license (self-hosted) or a saas build - otherwise fall back to
// Traefik's own cert resolvers (buildWildcardTls) throughout.
const pangolinCertModeEnabled =
privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin" &&
(await license.hasTier(["personal", "tier2", "enterprise"]));
let validCerts: CertificateResult[] = [];
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
if (pangolinCertModeEnabled) {
// create a list of all domains to get certs for
const domains = new Set<string>();
for (const resource of resourcesMap.values()) {
@@ -522,10 +530,7 @@ export async function getTraefikConfig(
);
let tls = {};
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
if (!pangolinCertModeEnabled) {
tls = buildWildcardTls({
fullDomain,
hasSubdomain: !!resource.subdomain,
@@ -790,10 +795,7 @@ export async function getTraefikConfig(
domainCertResolver,
preferWildcardCert
}) => {
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
if (!pangolinCertModeEnabled) {
return buildWildcardTls({
fullDomain,
hasSubdomain,
@@ -834,10 +836,7 @@ export async function getTraefikConfig(
maintenancePageUiUrl,
redirectHttpsMiddlewareName,
resolveTls: (fullDomain) => {
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
if (!pangolinCertModeEnabled) {
// siteResource aliases don't have a per-domain cert
// resolver stored, so always fall back to the global
// defaults.
@@ -928,10 +927,7 @@ export async function getTraefikConfig(
const rule = buildHostRule(fullDomain, ir.wildcard);
let tls: any = {};
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
if (!pangolinCertModeEnabled) {
tls = buildWildcardTls({
fullDomain,
hasSubdomain: !!ir.subdomain,
@@ -1011,10 +1007,7 @@ export async function getTraefikConfig(
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
let tls: any = {};
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
if (!pangolinCertModeEnabled) {
// siteResource aliases don't have a per-domain cert
// resolver stored, so always fall back to the global
// defaults.
@@ -1088,7 +1081,7 @@ export async function getTraefikConfig(
.where(eq(exitNodes.exitNodeId, exitNodeId));
let validCertsLoginPages: CertificateResult[] = [];
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
if (pangolinCertModeEnabled) {
// create a list of all domains to get certs for
const domains = new Set<string>();
for (const lp of exitNodeLoginPages) {
@@ -1133,10 +1126,7 @@ export async function getTraefikConfig(
}
const tls = {};
if (
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
"pangolin"
) {
if (!pangolinCertModeEnabled) {
// TODO: we need to add the wildcard logic here too
} else {
// find a cert that matches the full domain, if not continue