615 lines
21 KiB
TypeScript
615 lines
21 KiB
TypeScript
import { Server } from "@prisma/client";
|
|
import { reloadNginx, resolveSslCertificate } from "../ssh.service";
|
|
import {
|
|
pathExistsPrivileged,
|
|
readPrivilegedFile,
|
|
removePrivilegedFile,
|
|
writePrivilegedFile,
|
|
} from "./filesystem";
|
|
import {
|
|
buildManagedNginxFileBase,
|
|
buildManagedNginxSharedFileBase,
|
|
getDomainConfigAnchor,
|
|
getLegacyTwoLabelDomainConfigAnchor,
|
|
sanitizeDomainFileName,
|
|
sanitizeContainerFileName,
|
|
} from "./names";
|
|
import {
|
|
escapeDoubleQuotedShellArg,
|
|
execPrivileged,
|
|
execPrivilegedLoose,
|
|
} from "./shell";
|
|
import { DomainProvisioningError, DomainProvisioningResult } from "./types";
|
|
|
|
function findHttpBlockEnd(configContent: string): number | null {
|
|
const httpMatch = /\bhttp\s*\{/.exec(configContent);
|
|
if (!httpMatch) {
|
|
return null;
|
|
}
|
|
|
|
const braceStart = configContent.indexOf("{", httpMatch.index);
|
|
if (braceStart < 0) {
|
|
return null;
|
|
}
|
|
|
|
let depth = 0;
|
|
for (let index = braceStart; index < configContent.length; index += 1) {
|
|
const char = configContent[index];
|
|
if (char === "{") {
|
|
depth += 1;
|
|
} else if (char === "}") {
|
|
depth -= 1;
|
|
if (depth === 0) {
|
|
return index;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function ensureNginxSitesEnabledInclude(configContent: string): string {
|
|
if (
|
|
/include\s+\/etc\/nginx\/sites-enabled\/\*\.conf\s*;/.test(configContent)
|
|
) {
|
|
return configContent;
|
|
}
|
|
|
|
const httpBlockEnd = findHttpBlockEnd(configContent);
|
|
if (httpBlockEnd === null) {
|
|
throw new DomainProvisioningError(
|
|
"Could not find the http block in nginx.conf to register /etc/nginx/sites-enabled/*.conf.",
|
|
);
|
|
}
|
|
|
|
const includeLine = "\n include /etc/nginx/sites-enabled/*.conf;\n";
|
|
return `${configContent.slice(0, httpBlockEnd)}${includeLine}${configContent.slice(httpBlockEnd)}`;
|
|
}
|
|
|
|
function normalizeNginxFailureMessage(message: string): string {
|
|
if (
|
|
/socket\(\) \[::\]:80 failed \(97: Address family not supported by protocol\)/i.test(
|
|
message,
|
|
)
|
|
) {
|
|
return [
|
|
"Nginx validation failed on the target server because an IPv6 listener like `listen [::]:80` is enabled, but the host does not support IPv6.",
|
|
"Adjust the existing Nginx configuration on that server to remove or conditionalize the IPv6 listener, then retry Add Domain.",
|
|
].join(" ");
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
function buildNginxProxyLocation(upstream: string): string[] {
|
|
return [
|
|
" location / {",
|
|
` proxy_pass ${upstream};`,
|
|
" proxy_http_version 1.1;",
|
|
" proxy_set_header Host $host;",
|
|
" proxy_set_header X-Real-IP $remote_addr;",
|
|
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
|
" proxy_set_header X-Forwarded-Proto $scheme;",
|
|
" proxy_set_header Upgrade $http_upgrade;",
|
|
' proxy_set_header Connection "upgrade";',
|
|
" }",
|
|
];
|
|
}
|
|
|
|
function normalizeDomainSet(domainNames: string[]): string[] {
|
|
return Array.from(
|
|
new Set(
|
|
domainNames.map((name) => name.trim().toLowerCase()).filter(Boolean),
|
|
),
|
|
);
|
|
}
|
|
|
|
function buildServerNameValue(domainNames: string[]): string {
|
|
return normalizeDomainSet(domainNames).join(" ");
|
|
}
|
|
|
|
type NginxServerEntry = {
|
|
domainName: string;
|
|
upstream: string;
|
|
};
|
|
|
|
function normalizeNginxServerEntries(
|
|
entries: NginxServerEntry[] | undefined,
|
|
fallbackDomainNames: string[],
|
|
fallbackUpstream: string,
|
|
): NginxServerEntry[] {
|
|
const normalized = new Map<string, NginxServerEntry>();
|
|
|
|
for (const entry of entries ?? []) {
|
|
const domainName = entry.domainName.trim().toLowerCase();
|
|
const upstream = entry.upstream.trim();
|
|
if (domainName && upstream) {
|
|
normalized.set(domainName, { domainName, upstream });
|
|
}
|
|
}
|
|
|
|
if (normalized.size === 0) {
|
|
for (const domainName of fallbackDomainNames) {
|
|
normalized.set(domainName, { domainName, upstream: fallbackUpstream });
|
|
}
|
|
}
|
|
|
|
return Array.from(normalized.values()).sort((left, right) =>
|
|
left.domainName.localeCompare(right.domainName),
|
|
);
|
|
}
|
|
|
|
async function resolveSharedSslCertificateName(options: {
|
|
server: Server;
|
|
domainNames: string[];
|
|
primaryDomainName: string;
|
|
sslEnabled: boolean;
|
|
}): Promise<string | null> {
|
|
if (!options.sslEnabled) {
|
|
return null;
|
|
}
|
|
|
|
const exactDomains = normalizeDomainSet(options.domainNames).filter(
|
|
(domainName) => !domainName.startsWith("*."),
|
|
);
|
|
if (exactDomains.length !== options.domainNames.length) {
|
|
return null;
|
|
}
|
|
|
|
const certificate = await resolveSslCertificate(
|
|
options.server,
|
|
options.primaryDomainName,
|
|
).catch(() => null);
|
|
if (!certificate) {
|
|
return null;
|
|
}
|
|
|
|
const certificateDomains = new Set(
|
|
certificate.domainNames.map((domainName) => domainName.toLowerCase()),
|
|
);
|
|
|
|
return exactDomains.every((domainName) => certificateDomains.has(domainName))
|
|
? certificate.certName
|
|
: null;
|
|
}
|
|
|
|
function buildNginxConfig(
|
|
domainNames: string[],
|
|
upstream: string,
|
|
configMode: "SHARED" | "ISOLATED",
|
|
primaryDomainName: string,
|
|
sslCertName?: string | null,
|
|
serverEntries?: NginxServerEntry[],
|
|
): string {
|
|
const serverNameValue = buildServerNameValue(domainNames);
|
|
const entries = normalizeNginxServerEntries(
|
|
serverEntries,
|
|
domainNames,
|
|
upstream,
|
|
);
|
|
|
|
if (!sslCertName) {
|
|
const blocks =
|
|
configMode === "SHARED"
|
|
? entries.flatMap((entry) => [
|
|
"server {",
|
|
" listen 80;",
|
|
` server_name ${entry.domainName};`,
|
|
"",
|
|
...buildNginxProxyLocation(entry.upstream),
|
|
"}",
|
|
"",
|
|
])
|
|
: [
|
|
"server {",
|
|
" listen 80;",
|
|
` server_name ${serverNameValue};`,
|
|
"",
|
|
...buildNginxProxyLocation(upstream),
|
|
"}",
|
|
"",
|
|
];
|
|
|
|
return [
|
|
"# doktainer-managed: true",
|
|
`# doktainer-config-mode: ${configMode}`,
|
|
`# doktainer-primary-domain: ${primaryDomainName}`,
|
|
...blocks,
|
|
].join("\n");
|
|
}
|
|
|
|
const certPath = `/etc/letsencrypt/live/${sslCertName}/fullchain.pem`;
|
|
const keyPath = `/etc/letsencrypt/live/${sslCertName}/privkey.pem`;
|
|
|
|
const httpsBlocks =
|
|
configMode === "SHARED"
|
|
? entries.flatMap((entry) => [
|
|
"server {",
|
|
" listen 443 ssl http2;",
|
|
` server_name ${entry.domainName};`,
|
|
` ssl_certificate ${certPath};`,
|
|
` ssl_certificate_key ${keyPath};`,
|
|
" ssl_session_cache shared:SSL:10m;",
|
|
" ssl_session_timeout 10m;",
|
|
" ssl_protocols TLSv1.2 TLSv1.3;",
|
|
" ssl_prefer_server_ciphers off;",
|
|
"",
|
|
...buildNginxProxyLocation(entry.upstream),
|
|
"}",
|
|
"",
|
|
])
|
|
: [
|
|
"server {",
|
|
" listen 443 ssl http2;",
|
|
` server_name ${serverNameValue};`,
|
|
` ssl_certificate ${certPath};`,
|
|
` ssl_certificate_key ${keyPath};`,
|
|
" ssl_session_cache shared:SSL:10m;",
|
|
" ssl_session_timeout 10m;",
|
|
" ssl_protocols TLSv1.2 TLSv1.3;",
|
|
" ssl_prefer_server_ciphers off;",
|
|
"",
|
|
...buildNginxProxyLocation(upstream),
|
|
"}",
|
|
"",
|
|
];
|
|
|
|
return [
|
|
"# doktainer-managed: true",
|
|
`# doktainer-config-mode: ${configMode}`,
|
|
`# doktainer-primary-domain: ${primaryDomainName}`,
|
|
"server {",
|
|
" listen 80;",
|
|
` server_name ${serverNameValue};`,
|
|
" return 301 https://$host$request_uri;",
|
|
"}",
|
|
"",
|
|
...httpsBlocks,
|
|
].join("\n");
|
|
}
|
|
|
|
async function detectNginxMainConfigPath(server: Server): Promise<string> {
|
|
const candidates = new Set([
|
|
"/etc/nginx/nginx.conf",
|
|
"/etc/openresty/nginx.conf",
|
|
"/usr/local/nginx/conf/nginx.conf",
|
|
"/usr/local/openresty/nginx/conf/nginx.conf",
|
|
"/usr/local/etc/nginx/nginx.conf",
|
|
"/opt/nginx/conf/nginx.conf",
|
|
"/www/server/nginx/conf/nginx.conf",
|
|
]);
|
|
|
|
const versionOutput = await execPrivilegedLoose(
|
|
server,
|
|
"nginx -V 2>&1 || true",
|
|
);
|
|
const combinedVersionOutput = `${versionOutput.stdout}\n${versionOutput.stderr}`;
|
|
const confPathMatch = combinedVersionOutput.match(/--conf-path=([^\s]+)/);
|
|
if (confPathMatch?.[1]) {
|
|
candidates.add(confPathMatch[1].trim());
|
|
}
|
|
|
|
for (const candidate of candidates) {
|
|
if (await pathExistsPrivileged(server, candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
const discoveryResult = await execPrivilegedLoose(
|
|
server,
|
|
[
|
|
"for root in /etc /usr/local /opt /www /home /srv; do",
|
|
' if [ -d "$root" ]; then',
|
|
' found=$(find -L "$root" -maxdepth 6 -type f -name nginx.conf 2>/dev/null | head -n 1);',
|
|
' if [ -n "$found" ]; then printf "%s\\n" "$found"; exit 0; fi',
|
|
" fi",
|
|
"done",
|
|
].join(" "),
|
|
);
|
|
|
|
const discoveredPath = `${discoveryResult.stdout || ""}`
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.find(Boolean);
|
|
|
|
if (discoveredPath && (await pathExistsPrivileged(server, discoveredPath))) {
|
|
return discoveredPath;
|
|
}
|
|
|
|
throw new DomainProvisioningError(
|
|
`nginx.conf was not found on ${server.name}. Checked Nginx --conf-path and common locations: ${Array.from(candidates).join(", ")}`,
|
|
);
|
|
}
|
|
|
|
async function ensureNginxSitesEnabledRegistration(
|
|
server: Server,
|
|
): Promise<void> {
|
|
const mainConfigPath = await detectNginxMainConfigPath(server);
|
|
const currentContent = await readPrivilegedFile(server, mainConfigPath);
|
|
|
|
if (currentContent === null) {
|
|
throw new DomainProvisioningError(
|
|
`Unable to read Nginx main config at ${mainConfigPath}.`,
|
|
);
|
|
}
|
|
|
|
const nextContent = ensureNginxSitesEnabledInclude(currentContent);
|
|
if (nextContent !== currentContent) {
|
|
await writePrivilegedFile(server, mainConfigPath, nextContent);
|
|
}
|
|
}
|
|
|
|
async function validateAndReloadNginx(
|
|
server: Server,
|
|
rollbackPaths: string[],
|
|
): Promise<void> {
|
|
try {
|
|
await execPrivileged(server, "nginx -t");
|
|
await reloadNginx(server);
|
|
} catch (error) {
|
|
for (const rollbackPath of rollbackPaths) {
|
|
await removePrivilegedFile(server, rollbackPath).catch(() => undefined);
|
|
}
|
|
|
|
throw new DomainProvisioningError(
|
|
normalizeNginxFailureMessage(
|
|
error instanceof Error ? error.message : String(error),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function provisionNginxConfig(
|
|
server: Server,
|
|
domainName: string,
|
|
upstream: string,
|
|
containerName: string,
|
|
sslEnabled: boolean,
|
|
options?: {
|
|
domainNames?: string[];
|
|
serverEntries?: NginxServerEntry[];
|
|
configMode?: "SHARED" | "ISOLATED";
|
|
primaryDomainName?: string | null;
|
|
},
|
|
): Promise<DomainProvisioningResult> {
|
|
const configMode = options?.configMode ?? "ISOLATED";
|
|
const domainNames = normalizeDomainSet(options?.domainNames ?? [domainName]);
|
|
const primaryDomainName =
|
|
options?.primaryDomainName?.trim().toLowerCase() ||
|
|
domainNames[0] ||
|
|
domainName;
|
|
const safeName =
|
|
configMode === "SHARED"
|
|
? buildManagedNginxSharedFileBase(containerName, domainNames)
|
|
: buildManagedNginxFileBase(containerName, domainName);
|
|
const legacySharedSafeName =
|
|
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)}`
|
|
: null;
|
|
const configPath = `/etc/nginx/sites-available/${safeName}.conf`;
|
|
const enabledPath = `/etc/nginx/sites-enabled/${safeName}.conf`;
|
|
const cleanupCommands = domainNames.flatMap((managedDomainName) => {
|
|
const managedDomainSafeName = sanitizeDomainFileName(managedDomainName);
|
|
const legacyConfigPath = `/etc/nginx/conf.d/portainer-domains/${managedDomainSafeName}.conf`;
|
|
const previousConfigPath = `/etc/nginx/sites-available/${managedDomainSafeName}.conf`;
|
|
const previousEnabledPath = `/etc/nginx/sites-enabled/${managedDomainSafeName}.conf`;
|
|
const isolatedBase = buildManagedNginxFileBase(
|
|
containerName,
|
|
managedDomainName,
|
|
);
|
|
const isolatedAvailablePath = `/etc/nginx/sites-available/${isolatedBase}.conf`;
|
|
const isolatedEnabledPath = `/etc/nginx/sites-enabled/${isolatedBase}.conf`;
|
|
|
|
const commands = [`rm -f ${escapeDoubleQuotedShellArg(legacyConfigPath)}`];
|
|
|
|
if (managedDomainSafeName !== safeName) {
|
|
commands.push(
|
|
`rm -f ${escapeDoubleQuotedShellArg(previousEnabledPath)} ${escapeDoubleQuotedShellArg(previousConfigPath)}`,
|
|
);
|
|
}
|
|
|
|
if (isolatedBase !== safeName) {
|
|
commands.push(
|
|
`rm -f ${escapeDoubleQuotedShellArg(isolatedEnabledPath)} ${escapeDoubleQuotedShellArg(isolatedAvailablePath)}`,
|
|
);
|
|
}
|
|
|
|
return commands;
|
|
});
|
|
|
|
if (legacySharedSafeName && legacySharedSafeName !== safeName) {
|
|
cleanupCommands.push(
|
|
`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
|
|
) {
|
|
cleanupCommands.push(
|
|
`rm -f ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-enabled/${legacyServiceSharedSafeName}.conf`)} ${escapeDoubleQuotedShellArg(`/etc/nginx/sites-available/${legacyServiceSharedSafeName}.conf`)}`,
|
|
);
|
|
}
|
|
|
|
await ensureNginxSitesEnabledRegistration(server);
|
|
const sslCertificateName =
|
|
configMode === "SHARED"
|
|
? await resolveSharedSslCertificateName({
|
|
server,
|
|
domainNames,
|
|
primaryDomainName,
|
|
sslEnabled,
|
|
})
|
|
: sslEnabled
|
|
? ((await resolveSslCertificate(server, domainName).catch(() => null))
|
|
?.certName ?? null)
|
|
: null;
|
|
await writePrivilegedFile(
|
|
server,
|
|
configPath,
|
|
buildNginxConfig(
|
|
domainNames,
|
|
upstream,
|
|
configMode,
|
|
primaryDomainName,
|
|
sslCertificateName,
|
|
options?.serverEntries,
|
|
),
|
|
);
|
|
await execPrivileged(
|
|
server,
|
|
[
|
|
"mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled",
|
|
`ln -sfn ${escapeDoubleQuotedShellArg(configPath)} ${escapeDoubleQuotedShellArg(enabledPath)}`,
|
|
...cleanupCommands,
|
|
].join(" && "),
|
|
);
|
|
await validateAndReloadNginx(server, [enabledPath, configPath]);
|
|
|
|
return {
|
|
upstream,
|
|
configPath,
|
|
reloadTarget: "nginx",
|
|
};
|
|
}
|
|
|
|
export async function removeManagedNginxProxyConfig(options: {
|
|
server: Server;
|
|
domainName: string;
|
|
containerName?: string | null;
|
|
domainNames?: string[];
|
|
configMode?: "SHARED" | "ISOLATED";
|
|
}): Promise<void> {
|
|
const configMode = options.configMode ?? "ISOLATED";
|
|
const domainNames = normalizeDomainSet(
|
|
options.domainNames ?? [options.domainName],
|
|
);
|
|
const domainSafeName = sanitizeDomainFileName(options.domainName);
|
|
const managedSafeName =
|
|
options.containerName && configMode === "SHARED"
|
|
? buildManagedNginxSharedFileBase(options.containerName, domainNames)
|
|
: options.containerName
|
|
? buildManagedNginxFileBase(options.containerName, options.domainName)
|
|
: domainSafeName;
|
|
const legacySharedSafeName =
|
|
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)}`
|
|
: null;
|
|
const enabledPath = `/etc/nginx/sites-enabled/${managedSafeName}.conf`;
|
|
const availablePath = `/etc/nginx/sites-available/${managedSafeName}.conf`;
|
|
const previousEnabledPath = `/etc/nginx/sites-enabled/${domainSafeName}.conf`;
|
|
const previousAvailablePath = `/etc/nginx/sites-available/${domainSafeName}.conf`;
|
|
const legacyConfigPath = `/etc/nginx/conf.d/portainer-domains/${domainSafeName}.conf`;
|
|
|
|
await removePrivilegedFile(options.server, enabledPath);
|
|
await removePrivilegedFile(options.server, availablePath);
|
|
if (legacySharedSafeName && legacySharedSafeName !== managedSafeName) {
|
|
await removePrivilegedFile(
|
|
options.server,
|
|
`/etc/nginx/sites-enabled/${legacySharedSafeName}.conf`,
|
|
);
|
|
await removePrivilegedFile(
|
|
options.server,
|
|
`/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
|
|
) {
|
|
await removePrivilegedFile(
|
|
options.server,
|
|
`/etc/nginx/sites-enabled/${legacyServiceSharedSafeName}.conf`,
|
|
);
|
|
await removePrivilegedFile(
|
|
options.server,
|
|
`/etc/nginx/sites-available/${legacyServiceSharedSafeName}.conf`,
|
|
);
|
|
}
|
|
if (managedSafeName !== domainSafeName) {
|
|
await removePrivilegedFile(options.server, previousEnabledPath);
|
|
await removePrivilegedFile(options.server, previousAvailablePath);
|
|
}
|
|
await removePrivilegedFile(options.server, legacyConfigPath);
|
|
|
|
await execPrivileged(
|
|
options.server,
|
|
[
|
|
`[ -d /etc/nginx/sites-enabled ] && find /etc/nginx/sites-enabled -maxdepth 1 -type f -name ${escapeDoubleQuotedShellArg(`*--${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-enabled ] && find /etc/nginx/sites-enabled -maxdepth 1 -type l -name ${escapeDoubleQuotedShellArg(`*--${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-enabled ] && find /etc/nginx/sites-enabled -maxdepth 1 -type f -name ${escapeDoubleQuotedShellArg(`${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-enabled ] && find /etc/nginx/sites-enabled -maxdepth 1 -type l -name ${escapeDoubleQuotedShellArg(`${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-available ] && find /etc/nginx/sites-available -maxdepth 1 -type f -name ${escapeDoubleQuotedShellArg(`*--${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-available ] && find /etc/nginx/sites-available -maxdepth 1 -type l -name ${escapeDoubleQuotedShellArg(`*--${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-available ] && find /etc/nginx/sites-available -maxdepth 1 -type f -name ${escapeDoubleQuotedShellArg(`${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/sites-available ] && find /etc/nginx/sites-available -maxdepth 1 -type l -name ${escapeDoubleQuotedShellArg(`${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/conf.d/portainer-domains ] && find /etc/nginx/conf.d/portainer-domains -maxdepth 1 -type f -name ${escapeDoubleQuotedShellArg(`${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
`[ -d /etc/nginx/conf.d/portainer-domains ] && find /etc/nginx/conf.d/portainer-domains -maxdepth 1 -type l -name ${escapeDoubleQuotedShellArg(`${domainSafeName}.conf`)} -delete 2>/dev/null || true`,
|
|
].join(" && "),
|
|
);
|
|
|
|
await validateAndReloadNginx(options.server, []);
|
|
}
|