fix: add parser Public Suffix List for naming domain file config and fix failed to remove old legacy file config

This commit is contained in:
KodekaTeam committed 2026-06-04 02:47:38 +07:00
1 parent af92445c6c
commit 17cbc2fb98
5 files changed
+148 -1

No files matched your search

+19
View File
@@ -45,6 +45,7 @@
"react-dom": "19.2.4",
"recharts": "^3.8.1",
"tailwind-merge": "^3.5.0",
"tldts": "^7.4.2",
"zod": "^4.4.3"
},
"devDependencies": {
@@ -11834,6 +11835,24 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tldts": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz",
"integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==",
"license": "MIT",
"dependencies": {
"tldts-core": "^7.4.2"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz",
"integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==",
"license": "MIT"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+1
View File
@@ -62,6 +62,7 @@
"react-dom": "19.2.4",
"recharts": "^3.8.1",
"tailwind-merge": "^3.5.0",
"tldts": "^7.4.2",
"zod": "^4.4.3"
},
"devDependencies": {
@@ -1,3 +1,5 @@
import { getDomain } from "tldts";
export function sanitizeDomainFileName(domainName: string): string {
return domainName.toLowerCase().replace(/[^a-z0-9.-]/g, "-");
}
@@ -18,7 +20,27 @@ function normalizeAnchorDomain(domainName: string): string {
return domainName.trim().toLowerCase().replace(/^\*\./, "");
}
export function getDomainConfigAnchor(domainNames: string[]): string {
function resolveRegistrableDomain(domainName: string): string {
const parsed = getDomain(domainName, {
allowIcannDomains: true,
allowPrivateDomains: true,
});
if (parsed) {
return parsed;
}
const labels = domainName.split(".").filter(Boolean);
if (labels.length >= 2) {
return labels.slice(-2).join(".");
}
return domainName;
}
export function getLegacyTwoLabelDomainConfigAnchor(
domainNames: string[],
): string {
const normalized = Array.from(
new Set(domainNames.map(normalizeAnchorDomain).filter(Boolean)),
);
@@ -48,6 +70,30 @@ export function getDomainConfigAnchor(domainNames: string[]): string {
return candidate;
}
export function getDomainConfigAnchor(domainNames: string[]): string {
const normalized = Array.from(
new Set(domainNames.map(normalizeAnchorDomain).filter(Boolean)),
);
if (normalized.length === 0) {
return "shared-domain";
}
normalized.sort((left, right) => {
const leftLabels = left.split(".").length;
const rightLabels = right.split(".").length;
if (leftLabels !== rightLabels) {
return leftLabels - rightLabels;
}
return left.localeCompare(right);
});
const candidate = normalized[0] ?? "shared-domain";
return resolveRegistrableDomain(candidate);
}
export function buildManagedNginxSharedFileBase(
containerName: string,
domainNames: string[],
@@ -10,6 +10,7 @@ import {
buildManagedNginxFileBase,
buildManagedNginxSharedFileBase,
getDomainConfigAnchor,
getLegacyTwoLabelDomainConfigAnchor,
sanitizeDomainFileName,
sanitizeContainerFileName,
} from "./names";
@@ -385,6 +386,14 @@ export async function provisionNginxConfig(
configMode === "SHARED"
? `${sanitizeContainerFileName(containerName)}--${sanitizeDomainFileName(getDomainConfigAnchor(domainNames))}`
: null;
const legacyBuggyAnchorSafeName =
configMode === "SHARED"
? sanitizeDomainFileName(getLegacyTwoLabelDomainConfigAnchor(domainNames))
: null;
const legacyBuggySharedSafeName =
configMode === "SHARED"
? `${sanitizeContainerFileName(containerName)}--${sanitizeDomainFileName(getLegacyTwoLabelDomainConfigAnchor(domainNames))}`
: null;
const legacyServiceSharedSafeName =
configMode === "SHARED"
? `doktainer-${sanitizeContainerFileName(containerName)}`
@@ -425,6 +434,20 @@ export async function provisionNginxConfig(
`rm -f ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-enabled/${legacySharedSafeName}.conf`)} ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-available/${legacySharedSafeName}.conf`)}`,
);
}
if (legacyBuggyAnchorSafeName && legacyBuggyAnchorSafeName !== safeName) {
cleanupCommands.push(
`rm -f ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-enabled/${legacyBuggyAnchorSafeName}.conf`)} ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-available/${legacyBuggyAnchorSafeName}.conf`)}`,
);
}
if (
legacyBuggySharedSafeName &&
legacyBuggySharedSafeName !== safeName &&
legacyBuggySharedSafeName !== legacySharedSafeName
) {
cleanupCommands.push(
`rm -f ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-enabled/${legacyBuggySharedSafeName}.conf`)} ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-available/${legacyBuggySharedSafeName}.conf`)}`,
);
}
if (
legacyServiceSharedSafeName &&
legacyServiceSharedSafeName !== safeName
@@ -498,6 +521,14 @@ export async function removeManagedNginxProxyConfig(options: {
options.containerName && configMode === "SHARED"
? `${sanitizeContainerFileName(options.containerName)}--${sanitizeDomainFileName(getDomainConfigAnchor(domainNames))}`
: null;
const legacyBuggyAnchorSafeName =
configMode === "SHARED"
? sanitizeDomainFileName(getLegacyTwoLabelDomainConfigAnchor(domainNames))
: null;
const legacyBuggySharedSafeName =
options.containerName && configMode === "SHARED"
? `${sanitizeContainerFileName(options.containerName)}--${sanitizeDomainFileName(getLegacyTwoLabelDomainConfigAnchor(domainNames))}`
: null;
const legacyServiceSharedSafeName =
options.containerName && configMode === "SHARED"
? `doktainer-${sanitizeContainerFileName(options.containerName)}`
@@ -520,6 +551,30 @@ export async function removeManagedNginxProxyConfig(options: {
`/etc/nginx/sites-available/${legacySharedSafeName}.conf`,
);
}
if (legacyBuggyAnchorSafeName && legacyBuggyAnchorSafeName !== managedSafeName) {
await removePrivilegedFile(
options.server,
`/etc/nginx/sites-enabled/${legacyBuggyAnchorSafeName}.conf`,
);
await removePrivilegedFile(
options.server,
`/etc/nginx/sites-available/${legacyBuggyAnchorSafeName}.conf`,
);
}
if (
legacyBuggySharedSafeName &&
legacyBuggySharedSafeName !== managedSafeName &&
legacyBuggySharedSafeName !== legacySharedSafeName
) {
await removePrivilegedFile(
options.server,
`/etc/nginx/sites-enabled/${legacyBuggySharedSafeName}.conf`,
);
await removePrivilegedFile(
options.server,
`/etc/nginx/sites-available/${legacyBuggySharedSafeName}.conf`,
);
}
if (
legacyServiceSharedSafeName &&
legacyServiceSharedSafeName !== managedSafeName
@@ -5,6 +5,7 @@ import {
buildManagedNginxFileBase,
buildManagedNginxSharedFileBase,
getDomainConfigAnchor,
getLegacyTwoLabelDomainConfigAnchor,
} from "../../src/server/services/domain-provisioning/names";
test("isolated nginx config uses a single domain file name", () => {
@@ -48,3 +49,28 @@ test("shared nginx config anchor keeps service files separate across roots", ()
"example.com",
);
});
test("shared nginx config anchor respects public suffix domains", () => {
assert.equal(
getDomainConfigAnchor(["stream.appku.web.id"]),
"appku.web.id",
);
assert.equal(getDomainConfigAnchor(["api.appku.co.id"]), "appku.co.id");
assert.equal(
getDomainConfigAnchor(["foo.example.co.uk"]),
"example.co.uk",
);
assert.equal(
buildManagedNginxSharedFileBase("livestream-app", [
"stream.appku.web.id",
]),
"appku.web.id",
);
assert.equal(
getLegacyTwoLabelDomainConfigAnchor(["stream.appku.web.id"]),
"web.id",
);
});