badmfck-api-server 4.1.29 → 4.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apiServer/APIService.js +9 -5
- package/dist/apiServer/deployment/ConfigService.d.ts +18 -4
- package/dist/apiServer/deployment/ConfigService.js +6 -1
- package/dist/apiServer/deployment/Deployer.d.ts +1 -0
- package/dist/apiServer/deployment/Deployer.js +7 -2
- package/dist/apiServer/deployment/DeployerService.d.ts +18 -8
- package/dist/apiServer/deployment/DeployerService.js +76 -40
- package/dist/apiServer/deployment/NginxHelper.d.ts +7 -0
- package/dist/apiServer/deployment/NginxHelper.js +74 -31
- package/package.json +1 -1
|
@@ -146,14 +146,18 @@ class APIService extends BaseService_1.BaseService {
|
|
|
146
146
|
this.options.endpoints.push(new Documentation_1.Documentation(this.options));
|
|
147
147
|
this.options.endpoints.push(new ExternalServiceEndpoint_1.ExternalServiceEndpoint());
|
|
148
148
|
this.options.endpoints.push(new MicroserviceHostController_1.MicroserviceHostController());
|
|
149
|
-
|
|
150
|
-
this.monitor = new MonitorService_1.MonitorService();
|
|
151
|
-
this.monitor.init();
|
|
152
|
-
if (this.options.deployer && this.options.deployer.users.length > 0) {
|
|
153
|
-
this.options.endpoints.push(new Deployer_1.Deployer());
|
|
149
|
+
if (this.options.deployer && this.options.deployer.projects_path && this.options.deployer.setup_path) {
|
|
154
150
|
this.deployer = new DeployerService_1.DeployerService(this.options.deployer);
|
|
155
151
|
this.deployer.init();
|
|
152
|
+
this.options.endpoints = [
|
|
153
|
+
new Deployer_1.Deployer(),
|
|
154
|
+
new Documentation_1.Documentation(this.options),
|
|
155
|
+
new Monitor_1.Monitor(this.options),
|
|
156
|
+
];
|
|
156
157
|
}
|
|
158
|
+
this.monitor = new MonitorService_1.MonitorService();
|
|
159
|
+
this.monitor.init();
|
|
160
|
+
new DocumentService_1.DocumentGenerator(this.options.endpoints);
|
|
157
161
|
this.options.endpoints.push(new Liveness_1.Liveness(this.started), new Readiness_1.Readiness(this.started));
|
|
158
162
|
exports.REQ_HTTP_REQUESTS_COUNT.listener = async () => this.requestsCount;
|
|
159
163
|
}
|
|
@@ -48,7 +48,12 @@ declare const _TConfig: {
|
|
|
48
48
|
readonly $__on_switch_optional: true;
|
|
49
49
|
};
|
|
50
50
|
readonly $__bluegreen_optional: true;
|
|
51
|
-
readonly nginx: {
|
|
51
|
+
readonly nginx: {
|
|
52
|
+
readonly config: "";
|
|
53
|
+
readonly $__config_optional: true;
|
|
54
|
+
readonly symlink: "";
|
|
55
|
+
readonly $__symlink_optional: true;
|
|
56
|
+
};
|
|
52
57
|
readonly $__nginx_optional: true;
|
|
53
58
|
};
|
|
54
59
|
export type IConfig = ValidationModel<typeof _TConfig>;
|
|
@@ -63,7 +68,10 @@ export declare const REQ_DEPLOYMENT_CONFIG: Req<void, Map<string, {
|
|
|
63
68
|
blue_config: {};
|
|
64
69
|
green_config: {};
|
|
65
70
|
} | undefined;
|
|
66
|
-
nginx?: {
|
|
71
|
+
nginx?: {
|
|
72
|
+
config?: string | undefined;
|
|
73
|
+
symlink?: string | undefined;
|
|
74
|
+
} | undefined;
|
|
67
75
|
name: string;
|
|
68
76
|
destination: string;
|
|
69
77
|
description: string;
|
|
@@ -89,7 +97,10 @@ export declare const REQ_DEPLOYMENT_CONFIG_ADD: Req<{
|
|
|
89
97
|
blue_config: {};
|
|
90
98
|
green_config: {};
|
|
91
99
|
} | undefined;
|
|
92
|
-
nginx?: {
|
|
100
|
+
nginx?: {
|
|
101
|
+
config?: string | undefined;
|
|
102
|
+
symlink?: string | undefined;
|
|
103
|
+
} | undefined;
|
|
93
104
|
name: string;
|
|
94
105
|
destination: string;
|
|
95
106
|
description: string;
|
|
@@ -114,7 +125,10 @@ export declare const REQ_DEPLOYMENT_CONFIG_ADD: Req<{
|
|
|
114
125
|
blue_config: {};
|
|
115
126
|
green_config: {};
|
|
116
127
|
} | undefined;
|
|
117
|
-
nginx?: {
|
|
128
|
+
nginx?: {
|
|
129
|
+
config?: string | undefined;
|
|
130
|
+
symlink?: string | undefined;
|
|
131
|
+
} | undefined;
|
|
118
132
|
name: string;
|
|
119
133
|
destination: string;
|
|
120
134
|
description: string;
|
|
@@ -79,7 +79,12 @@ const _TConfig = {
|
|
|
79
79
|
$__on_switch_optional: true,
|
|
80
80
|
},
|
|
81
81
|
$__bluegreen_optional: true,
|
|
82
|
-
nginx: {
|
|
82
|
+
nginx: {
|
|
83
|
+
config: "",
|
|
84
|
+
$__config_optional: true,
|
|
85
|
+
symlink: "",
|
|
86
|
+
$__symlink_optional: true,
|
|
87
|
+
},
|
|
83
88
|
$__nginx_optional: true,
|
|
84
89
|
};
|
|
85
90
|
exports.REQ_DEPLOYMENT_CONFIG = new badmfck_signal_1.Req();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseEndpoint } from "../BaseEndpoint";
|
|
2
2
|
import { HTTPRequestVO, TransferPacketVO } from "../structures/Interfaces";
|
|
3
3
|
export declare class Deployer extends BaseEndpoint {
|
|
4
|
+
ignoreInDocumentation: boolean;
|
|
4
5
|
constructor();
|
|
5
6
|
file(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
|
|
6
7
|
switchSlot(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.Deployer = void 0;
|
|
4
7
|
const BaseEndpoint_1 = require("../BaseEndpoint");
|
|
8
|
+
const DefaultErrors_1 = __importDefault(require("../structures/DefaultErrors"));
|
|
5
9
|
const DeployerService_1 = require("./DeployerService");
|
|
6
10
|
class Deployer extends BaseEndpoint_1.BaseEndpoint {
|
|
11
|
+
ignoreInDocumentation = true;
|
|
7
12
|
constructor() {
|
|
8
|
-
super("
|
|
13
|
+
super("deployer");
|
|
9
14
|
this.registerEndpoints([
|
|
10
15
|
{
|
|
11
16
|
ignoreInterceptor: true,
|
|
@@ -41,7 +46,7 @@ class Deployer extends BaseEndpoint_1.BaseEndpoint {
|
|
|
41
46
|
}
|
|
42
47
|
async file(req) {
|
|
43
48
|
if (!req.files)
|
|
44
|
-
|
|
49
|
+
throw { ...DefaultErrors_1.default.FILE_NOT_EXISTS, details: "No files were uploaded" };
|
|
45
50
|
const result = await DeployerService_1.REQ_DEPLOYMENT_PROCEED.request({ ...req.data, files: req.files, auth: req.headers.authorization });
|
|
46
51
|
return { data: result };
|
|
47
52
|
}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { Req } from "badmfck-signal";
|
|
2
2
|
import { BaseService } from "../BaseService";
|
|
3
|
-
import { IDeployerUser } from "../APIService";
|
|
4
3
|
import { IConfig, IProjectState } from "./ConfigService";
|
|
5
4
|
import { UploadedFile } from "express-fileupload";
|
|
5
|
+
import { ValidationModel } from "../..";
|
|
6
6
|
import { IDeploymentNotifier } from "./Notifier";
|
|
7
7
|
import { IError } from "../structures/Interfaces";
|
|
8
|
+
declare const _TSetup: {
|
|
9
|
+
readonly users: readonly [{
|
|
10
|
+
readonly login: "";
|
|
11
|
+
readonly password: "";
|
|
12
|
+
}];
|
|
13
|
+
readonly watchdog: true;
|
|
14
|
+
readonly $__watchdog_optional: true;
|
|
15
|
+
readonly notifier: {
|
|
16
|
+
readonly URL: "";
|
|
17
|
+
readonly KEY: "";
|
|
18
|
+
};
|
|
19
|
+
readonly $__notifier_optional: true;
|
|
20
|
+
};
|
|
21
|
+
export type TDeployerSetup = ValidationModel<typeof _TSetup>;
|
|
8
22
|
export interface IDeployerServiceOptions {
|
|
9
23
|
projects_path: string;
|
|
10
|
-
|
|
11
|
-
watchdog?: boolean;
|
|
12
|
-
notifier?: {
|
|
13
|
-
URL?: string;
|
|
14
|
-
KEY?: string;
|
|
15
|
-
controller?: BaseService;
|
|
16
|
-
} | null;
|
|
24
|
+
setup_path: string;
|
|
17
25
|
}
|
|
18
26
|
export declare const REQ_DEPLOYMENT_PROCEED: Req<{
|
|
19
27
|
auth: string;
|
|
@@ -35,6 +43,7 @@ export declare const REQ_DEPLOYMENT_STATUS: Req<{
|
|
|
35
43
|
}, any>;
|
|
36
44
|
export declare class DeployerService extends BaseService {
|
|
37
45
|
options: IDeployerServiceOptions;
|
|
46
|
+
setup: TDeployerSetup | null;
|
|
38
47
|
notifier: IDeploymentNotifier | null;
|
|
39
48
|
busy: Map<string, boolean>;
|
|
40
49
|
constructor(options: IDeployerServiceOptions);
|
|
@@ -107,3 +116,4 @@ export declare class DeployerService extends BaseService {
|
|
|
107
116
|
unpack(destination: string, uploadedFile: string, install?: boolean, ignoreDirectoryClean?: boolean): Promise<IError | null>;
|
|
108
117
|
upload(file: UploadedFile, uploadPath: string): Promise<IError | null>;
|
|
109
118
|
}
|
|
119
|
+
export {};
|
|
@@ -12,16 +12,29 @@ const LogService_1 = require("../LogService");
|
|
|
12
12
|
const __1 = require("../..");
|
|
13
13
|
const Notifier_1 = require("./Notifier");
|
|
14
14
|
const child_process_1 = require("child_process");
|
|
15
|
-
const util_1 = require("util");
|
|
16
15
|
const fs_1 = __importDefault(require("fs"));
|
|
17
16
|
const crypto_1 = __importDefault(require("crypto"));
|
|
18
17
|
const Watchdog_1 = require("./Watchdog");
|
|
19
|
-
const
|
|
18
|
+
const NginxHelper_1 = require("./NginxHelper");
|
|
19
|
+
const _TSetup = {
|
|
20
|
+
users: [{
|
|
21
|
+
login: "",
|
|
22
|
+
password: ""
|
|
23
|
+
}],
|
|
24
|
+
watchdog: true,
|
|
25
|
+
$__watchdog_optional: true,
|
|
26
|
+
notifier: {
|
|
27
|
+
URL: "",
|
|
28
|
+
KEY: "",
|
|
29
|
+
},
|
|
30
|
+
$__notifier_optional: true
|
|
31
|
+
};
|
|
20
32
|
exports.REQ_DEPLOYMENT_PROCEED = new badmfck_signal_1.Req(undefined, "REQ_DEPLOYER_SERVICE");
|
|
21
33
|
exports.REQ_DEPLOYMENT_SWITCH = new badmfck_signal_1.Req(undefined, "REQ_DEPLOYER_SWITCH");
|
|
22
34
|
exports.REQ_DEPLOYMENT_STATUS = new badmfck_signal_1.Req(undefined, "REQ_DEPLOYER_STATUS");
|
|
23
35
|
class DeployerService extends BaseService_1.BaseService {
|
|
24
36
|
options;
|
|
37
|
+
setup = null;
|
|
25
38
|
notifier = null;
|
|
26
39
|
busy = new Map();
|
|
27
40
|
constructor(options) {
|
|
@@ -32,20 +45,35 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
32
45
|
super.init();
|
|
33
46
|
const cs = new ConfigService_1.ConfigService(this.options.projects_path);
|
|
34
47
|
await cs.init();
|
|
48
|
+
const setupFile = path_1.default.resolve(this.options.setup_path, "setup.json");
|
|
49
|
+
if (!fs_1.default.existsSync(setupFile)) {
|
|
50
|
+
(0, LogService_1.logError)("Setup file not found: " + setupFile);
|
|
51
|
+
throw new Error("Setup file not found: " + setupFile);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
this.setup = JSON.parse(fs_1.default.readFileSync(setupFile, "utf-8"));
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
(0, LogService_1.logError)("Failed to read setup file: " + e.message);
|
|
58
|
+
throw new Error("Failed to read setup file: " + e.message);
|
|
59
|
+
}
|
|
60
|
+
const validation = await __1.Validator.validateStructure(_TSetup, this.setup);
|
|
61
|
+
if (validation && validation.length > 0) {
|
|
62
|
+
(0, LogService_1.logError)("Setup file validation failed: " + JSON.stringify(validation));
|
|
63
|
+
throw new Error("Setup file validation failed: " + JSON.stringify(validation));
|
|
64
|
+
}
|
|
65
|
+
if (!this.setup) {
|
|
66
|
+
(0, LogService_1.logError)("Setup file is empty or invalid: " + setupFile);
|
|
67
|
+
throw new Error("Setup file is empty or invalid: " + setupFile);
|
|
68
|
+
}
|
|
35
69
|
let notifier = null;
|
|
36
|
-
if (this.
|
|
37
|
-
if (this.
|
|
38
|
-
notifier = this.
|
|
39
|
-
notifier.init();
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
if (this.options.notifier.URL && this.options.notifier.KEY) {
|
|
43
|
-
notifier = new Notifier_1.Notifier({ URL: this.options.notifier.URL, KEY: this.options.notifier.KEY });
|
|
44
|
-
await notifier.init();
|
|
45
|
-
}
|
|
70
|
+
if (this.setup && this.setup.notifier) {
|
|
71
|
+
if (this.setup.notifier.URL && this.setup.notifier.KEY) {
|
|
72
|
+
notifier = new Notifier_1.Notifier({ URL: this.setup.notifier.URL, KEY: this.setup.notifier.KEY });
|
|
73
|
+
await notifier.init();
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
|
-
if (this.
|
|
76
|
+
if (this.setup && this.setup.watchdog) {
|
|
49
77
|
const wd = new Watchdog_1.Watchdog(notifier);
|
|
50
78
|
await wd.init();
|
|
51
79
|
}
|
|
@@ -112,11 +140,12 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
112
140
|
fs_1.default.mkdirSync(deployPath, { recursive: true });
|
|
113
141
|
found.destination = deployPath;
|
|
114
142
|
if (projectState && found.bluegreen) {
|
|
143
|
+
const baseConfig = found.config ?? {};
|
|
115
144
|
if (projectState.lastDeployed === "blue" && found.bluegreen.blue_config) {
|
|
116
|
-
found.config = found.bluegreen.blue_config;
|
|
145
|
+
found.config = { ...baseConfig, ...found.bluegreen.blue_config };
|
|
117
146
|
}
|
|
118
147
|
else if (projectState.lastDeployed === "green" && found.bluegreen.green_config) {
|
|
119
|
-
found.config = found.bluegreen.green_config;
|
|
148
|
+
found.config = { ...baseConfig, ...found.bluegreen.green_config };
|
|
120
149
|
}
|
|
121
150
|
}
|
|
122
151
|
found.name = data.name + "-" + projectState.lastDeployed;
|
|
@@ -157,6 +186,28 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
157
186
|
if (found.config) {
|
|
158
187
|
await this.applyConfig(found.destination, found.config);
|
|
159
188
|
}
|
|
189
|
+
if (found.nginx?.config) {
|
|
190
|
+
const nginxPath = path_1.default.resolve(found.destination, found.nginx.config);
|
|
191
|
+
if (!fs_1.default.existsSync(nginxPath)) {
|
|
192
|
+
if (found.email && found.email.length > 0) {
|
|
193
|
+
for (let email of found.email) {
|
|
194
|
+
this.notifier?.notifyEmailStatus(email, "error-nginx-missing", found.name, user.login);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
throw { code: 22, message: "nginx config declared but file not found: " + nginxPath, httpStatus: 500 };
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
NginxHelper_1.NginxHelper.applyTemplate(nginxPath, found.config ?? {});
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (found.email && found.email.length > 0) {
|
|
204
|
+
for (let email of found.email) {
|
|
205
|
+
this.notifier?.notifyEmailStatus(email, "error-nginx-render", found.name, user.login);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw { code: 23, message: "nginx template render failed: " + String(e?.message ?? e), httpStatus: 500 };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
160
211
|
if (found.pm2) {
|
|
161
212
|
const pm2Result = await this.runPM2(found, projectState);
|
|
162
213
|
if (__1.ErrorUtils.isError(pm2Result)) {
|
|
@@ -262,31 +313,14 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
262
313
|
fs_1.default.writeFileSync(tmp, JSON.stringify(projectState, null, 4), "utf-8");
|
|
263
314
|
fs_1.default.renameSync(tmp, projectStatePath);
|
|
264
315
|
let hookError = null;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
hookError = "on_switch command must be an absolute path, got: " + cmd[0];
|
|
271
|
-
(0, LogService_1.logError)(hookError);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
try {
|
|
275
|
-
const result = await execFileAsync(cmd[0], cmd.slice(1), {
|
|
276
|
-
timeout: 30_000,
|
|
277
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
278
|
-
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
|
|
279
|
-
});
|
|
280
|
-
if (result.stderr)
|
|
281
|
-
(0, LogService_1.logWarn)("on_switch stderr:", result.stderr);
|
|
282
|
-
}
|
|
283
|
-
catch (e) {
|
|
284
|
-
hookError = String(e?.stderr || e?.message || e);
|
|
285
|
-
(0, LogService_1.logError)("on_switch hook failed:", hookError);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
316
|
+
if (found.nginx?.config && found.nginx?.symlink) {
|
|
317
|
+
const symlinkTarget = path_1.default.resolve(found.destination, target, found.nginx.config);
|
|
318
|
+
const swapErr = NginxHelper_1.NginxHelper.swapSymlink(found.nginx.symlink, symlinkTarget);
|
|
319
|
+
if (swapErr) {
|
|
320
|
+
return { error: swapErr };
|
|
288
321
|
}
|
|
289
322
|
}
|
|
323
|
+
hookError = await NginxHelper_1.NginxHelper.runHook(found.bluegreen.on_switch);
|
|
290
324
|
const status = hookError ? "switch-hook-failed" : action === "rollback" ? `rolled-back-to-${target}` : `switched-to-${target}`;
|
|
291
325
|
if (found.email && found.email.length > 0) {
|
|
292
326
|
for (const email of found.email) {
|
|
@@ -338,13 +372,15 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
338
372
|
_checkUserAuth(authHeader) {
|
|
339
373
|
if (!authHeader)
|
|
340
374
|
return null;
|
|
375
|
+
if (!this.setup)
|
|
376
|
+
return null;
|
|
341
377
|
const token = authHeader.split(" ").pop() ?? "";
|
|
342
378
|
if (!token)
|
|
343
379
|
return null;
|
|
344
|
-
if (!this.
|
|
380
|
+
if (!this.setup.users || this.setup.users.length === 0)
|
|
345
381
|
return null;
|
|
346
382
|
let matched = null;
|
|
347
|
-
for (const user of this.
|
|
383
|
+
for (const user of this.setup.users) {
|
|
348
384
|
const expected = crypto_1.default.createHash("sha256").update(user.login + ":" + user.password).digest("hex");
|
|
349
385
|
if (this.tokensMatch(token, expected))
|
|
350
386
|
matched = user;
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export declare class NginxHelper {
|
|
2
|
+
static applyTemplate(filePath: string, values: Record<string, string>): void;
|
|
3
|
+
static swapSymlink(symlinkPath: string, target: string): {
|
|
4
|
+
code: number;
|
|
5
|
+
message: string;
|
|
6
|
+
httpStatus: number;
|
|
7
|
+
} | null;
|
|
8
|
+
static runHook(cmd: string[] | undefined | null): Promise<string | null>;
|
|
2
9
|
}
|
|
@@ -1,37 +1,80 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.NginxHelper = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
include /etc/nginx/proxy_common.conf;
|
|
12
|
-
proxy_set_header X-Branch paypartner;
|
|
13
|
-
#paysolo api, 8096 to paypartner api
|
|
14
|
-
# 8065 -new paysolo
|
|
15
|
-
# 8095 - old paysolo
|
|
16
|
-
proxy_pass http://127.0.0.1:8065;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
location / {
|
|
21
|
-
try_files $uri $uri/ /index.html;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
listen 443 ssl; # managed by Certbot
|
|
25
|
-
#ssl_certificate /etc/letsencrypt/live/apgp.paypartner.eu/fullchain.pem; # managed by Certbot
|
|
26
|
-
#ssl_certificate_key /etc/letsencrypt/live/apgp.paypartner.eu/privkey.pem; # managed by Certbot
|
|
27
|
-
|
|
28
|
-
ssl_certificate /etc/letsencrypt/live/paypartner.eu/fullchain.pem;
|
|
29
|
-
ssl_certificate_key /etc/letsencrypt/live/paypartner.eu/privkey.pem;
|
|
30
|
-
|
|
31
|
-
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
|
32
|
-
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
|
33
|
-
}
|
|
34
|
-
`;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const util_1 = require("util");
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const LogService_1 = require("../LogService");
|
|
13
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
35
14
|
class NginxHelper {
|
|
15
|
+
static applyTemplate(filePath, values) {
|
|
16
|
+
if (!fs_1.default.existsSync(filePath))
|
|
17
|
+
throw new Error("nginx config file not found: " + filePath);
|
|
18
|
+
const original = fs_1.default.readFileSync(filePath, "utf-8");
|
|
19
|
+
const hashBefore = crypto_1.default.createHash("md5").update(original).digest("hex");
|
|
20
|
+
let content = original;
|
|
21
|
+
for (const [key, value] of Object.entries(values)) {
|
|
22
|
+
content = content.replaceAll(`_{{${key}}}_`, value);
|
|
23
|
+
}
|
|
24
|
+
const hashAfter = crypto_1.default.createHash("md5").update(content).digest("hex");
|
|
25
|
+
if (hashBefore !== hashAfter) {
|
|
26
|
+
fs_1.default.writeFileSync(filePath, content, "utf-8");
|
|
27
|
+
}
|
|
28
|
+
const leftover = content.match(/_\{\{[A-Za-z0-9_]+\}\}_/g);
|
|
29
|
+
if (leftover && leftover.length > 0) {
|
|
30
|
+
const unique = [...new Set(leftover)];
|
|
31
|
+
(0, LogService_1.logWarn)("Unresolved template placeholders in nginx config " + filePath + ": " + unique.join(", "));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
static swapSymlink(symlinkPath, target) {
|
|
35
|
+
if (!fs_1.default.existsSync(target)) {
|
|
36
|
+
return { code: 48, message: "nginx symlink target missing: " + target, httpStatus: 500 };
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const tmp = symlinkPath + ".tmp";
|
|
40
|
+
try {
|
|
41
|
+
fs_1.default.unlinkSync(tmp);
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
fs_1.default.symlinkSync(target, tmp);
|
|
45
|
+
fs_1.default.renameSync(tmp, symlinkPath);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return { code: 49, message: "nginx symlink swap failed: " + String(e?.message ?? e), httpStatus: 500 };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
static async runHook(cmd) {
|
|
53
|
+
if (!Array.isArray(cmd))
|
|
54
|
+
return null;
|
|
55
|
+
const filtered = cmd.filter(s => typeof s === "string" && s.length > 0);
|
|
56
|
+
if (filtered.length === 0)
|
|
57
|
+
return null;
|
|
58
|
+
if (!path_1.default.isAbsolute(filtered[0])) {
|
|
59
|
+
const err = "nginx hook command must be an absolute path, got: " + filtered[0];
|
|
60
|
+
(0, LogService_1.logError)(err);
|
|
61
|
+
return err;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const result = await execFileAsync(filtered[0], filtered.slice(1), {
|
|
65
|
+
timeout: 30_000,
|
|
66
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
67
|
+
env: { PATH: process.env.PATH ?? "/usr/bin:/bin" },
|
|
68
|
+
});
|
|
69
|
+
if (result.stderr)
|
|
70
|
+
(0, LogService_1.logWarn)("nginx hook stderr:", result.stderr);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
const err = String(e?.stderr || e?.message || e);
|
|
75
|
+
(0, LogService_1.logError)("nginx hook failed:", err);
|
|
76
|
+
return err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
36
79
|
}
|
|
37
80
|
exports.NginxHelper = NginxHelper;
|