badmfck-api-server 4.1.36 → 4.1.38
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 +2 -1
- package/dist/apiServer/deployment/ConfigService.d.ts +15 -10
- package/dist/apiServer/deployment/ConfigService.js +6 -4
- package/dist/apiServer/deployment/DeployerService.d.ts +6 -0
- package/dist/apiServer/deployment/DeployerService.js +131 -49
- package/dist/apiServer/deployment/NginxHelper.d.ts +7 -6
- package/dist/apiServer/deployment/NginxHelper.js +54 -26
- package/package.json +1 -1
|
@@ -249,10 +249,11 @@ class APIService extends BaseService_1.BaseService {
|
|
|
249
249
|
origin: (origin, callback) => {
|
|
250
250
|
if (!origin)
|
|
251
251
|
return callback(null, true);
|
|
252
|
+
if (this.noCors)
|
|
253
|
+
return callback(null, true);
|
|
252
254
|
try {
|
|
253
255
|
const o = new URL(String(origin));
|
|
254
256
|
const originNorm = o.origin.replace(/\/$/, "");
|
|
255
|
-
const hostNorm = o.host.replace(/\/$/, "");
|
|
256
257
|
const ok = corsSet.has(originNorm);
|
|
257
258
|
return callback(null, ok);
|
|
258
259
|
}
|
|
@@ -49,10 +49,12 @@ declare const _TConfig: {
|
|
|
49
49
|
};
|
|
50
50
|
readonly $__bluegreen_optional: true;
|
|
51
51
|
readonly nginx: {
|
|
52
|
-
readonly
|
|
53
|
-
readonly $
|
|
54
|
-
readonly
|
|
55
|
-
readonly $
|
|
52
|
+
readonly config_dir: "";
|
|
53
|
+
readonly $__config_dir_optional: true;
|
|
54
|
+
readonly domain: "";
|
|
55
|
+
readonly $__domain_optional: true;
|
|
56
|
+
readonly upstream_name: "";
|
|
57
|
+
readonly $__upstream_name_optional: true;
|
|
56
58
|
};
|
|
57
59
|
readonly $__nginx_optional: true;
|
|
58
60
|
};
|
|
@@ -69,8 +71,9 @@ export declare const REQ_DEPLOYMENT_CONFIG: Req<void, Map<string, {
|
|
|
69
71
|
green_config: {};
|
|
70
72
|
} | undefined;
|
|
71
73
|
nginx?: {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
config_dir?: string | undefined;
|
|
75
|
+
domain?: string | undefined;
|
|
76
|
+
upstream_name?: string | undefined;
|
|
74
77
|
} | undefined;
|
|
75
78
|
name: string;
|
|
76
79
|
destination: string;
|
|
@@ -98,8 +101,9 @@ export declare const REQ_DEPLOYMENT_CONFIG_ADD: Req<{
|
|
|
98
101
|
green_config: {};
|
|
99
102
|
} | undefined;
|
|
100
103
|
nginx?: {
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
config_dir?: string | undefined;
|
|
105
|
+
domain?: string | undefined;
|
|
106
|
+
upstream_name?: string | undefined;
|
|
103
107
|
} | undefined;
|
|
104
108
|
name: string;
|
|
105
109
|
destination: string;
|
|
@@ -126,8 +130,9 @@ export declare const REQ_DEPLOYMENT_CONFIG_ADD: Req<{
|
|
|
126
130
|
green_config: {};
|
|
127
131
|
} | undefined;
|
|
128
132
|
nginx?: {
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
config_dir?: string | undefined;
|
|
134
|
+
domain?: string | undefined;
|
|
135
|
+
upstream_name?: string | undefined;
|
|
131
136
|
} | undefined;
|
|
132
137
|
name: string;
|
|
133
138
|
destination: string;
|
|
@@ -80,10 +80,12 @@ const _TConfig = {
|
|
|
80
80
|
},
|
|
81
81
|
$__bluegreen_optional: true,
|
|
82
82
|
nginx: {
|
|
83
|
-
|
|
84
|
-
$
|
|
85
|
-
|
|
86
|
-
$
|
|
83
|
+
config_dir: "",
|
|
84
|
+
$__config_dir_optional: true,
|
|
85
|
+
domain: "",
|
|
86
|
+
$__domain_optional: true,
|
|
87
|
+
upstream_name: "",
|
|
88
|
+
$__upstream_name_optional: true,
|
|
87
89
|
},
|
|
88
90
|
$__nginx_optional: true,
|
|
89
91
|
};
|
|
@@ -46,8 +46,13 @@ export declare class DeployerService extends BaseService {
|
|
|
46
46
|
setup: TDeployerSetup | null;
|
|
47
47
|
notifier: IDeploymentNotifier | null;
|
|
48
48
|
busy: Map<string, boolean>;
|
|
49
|
+
private _setupWatchDebounceTimer;
|
|
50
|
+
private _lastSetupHash;
|
|
49
51
|
constructor(options: IDeployerServiceOptions);
|
|
50
52
|
init(): Promise<void>;
|
|
53
|
+
private _loadSetup;
|
|
54
|
+
private _reloadSetup;
|
|
55
|
+
private _watchSetup;
|
|
51
56
|
proceed(data: {
|
|
52
57
|
auth?: string;
|
|
53
58
|
name: string;
|
|
@@ -107,6 +112,7 @@ export declare class DeployerService extends BaseService {
|
|
|
107
112
|
}>;
|
|
108
113
|
tokensMatch(a: string | undefined, b: string | undefined): boolean;
|
|
109
114
|
private _checkUserAuth;
|
|
115
|
+
private _renderNginx;
|
|
110
116
|
runPM2(found: IConfig, projectState?: IProjectState | null): Promise<IError | null>;
|
|
111
117
|
private static readonly SKIP_DIRS;
|
|
112
118
|
private static readonly TEMPLATE_EXTENSIONS;
|
|
@@ -37,6 +37,8 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
37
37
|
setup = null;
|
|
38
38
|
notifier = null;
|
|
39
39
|
busy = new Map();
|
|
40
|
+
_setupWatchDebounceTimer = null;
|
|
41
|
+
_lastSetupHash = null;
|
|
40
42
|
constructor(options) {
|
|
41
43
|
super("DeployerService");
|
|
42
44
|
this.options = options;
|
|
@@ -45,41 +47,84 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
45
47
|
super.init();
|
|
46
48
|
const cs = new ConfigService_1.ConfigService(this.options.projects_path);
|
|
47
49
|
await cs.init();
|
|
50
|
+
const initial = await this._loadSetup();
|
|
51
|
+
if (!initial) {
|
|
52
|
+
throw new Error("Setup file missing/invalid at start — cannot start deployer");
|
|
53
|
+
}
|
|
54
|
+
this.setup = initial;
|
|
55
|
+
this._lastSetupHash = crypto_1.default.createHash("md5").update(JSON.stringify(initial)).digest("hex");
|
|
56
|
+
if (this.setup.notifier?.URL && this.setup.notifier?.KEY) {
|
|
57
|
+
this.notifier = new Notifier_1.Notifier({ URL: this.setup.notifier.URL, KEY: this.setup.notifier.KEY });
|
|
58
|
+
await this.notifier.init();
|
|
59
|
+
}
|
|
60
|
+
if (this.setup.watchdog) {
|
|
61
|
+
const wd = new Watchdog_1.Watchdog(this.notifier);
|
|
62
|
+
await wd.init();
|
|
63
|
+
}
|
|
64
|
+
this._watchSetup();
|
|
65
|
+
exports.REQ_DEPLOYMENT_PROCEED.listener = async (data) => this.proceed(data);
|
|
66
|
+
exports.REQ_DEPLOYMENT_SWITCH.listener = async (data) => this.switchSlot(data);
|
|
67
|
+
exports.REQ_DEPLOYMENT_STATUS.listener = async (data) => this.status(data);
|
|
68
|
+
}
|
|
69
|
+
async _loadSetup() {
|
|
48
70
|
const setupFile = path_1.default.resolve(this.options.setup_path, "setup.json");
|
|
49
71
|
if (!fs_1.default.existsSync(setupFile)) {
|
|
50
72
|
(0, LogService_1.logError)("Setup file not found: " + setupFile);
|
|
51
|
-
|
|
73
|
+
return null;
|
|
52
74
|
}
|
|
75
|
+
let raw;
|
|
53
76
|
try {
|
|
54
|
-
|
|
77
|
+
raw = fs_1.default.readFileSync(setupFile, "utf-8");
|
|
55
78
|
}
|
|
56
79
|
catch (e) {
|
|
57
80
|
(0, LogService_1.logError)("Failed to read setup file: " + e.message);
|
|
58
|
-
|
|
81
|
+
return null;
|
|
59
82
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw new Error("Setup file validation failed: " + JSON.stringify(validation));
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(raw);
|
|
64
86
|
}
|
|
65
|
-
|
|
66
|
-
(0, LogService_1.logError)("
|
|
67
|
-
|
|
87
|
+
catch (e) {
|
|
88
|
+
(0, LogService_1.logError)("Failed to parse setup.json: " + e.message);
|
|
89
|
+
return null;
|
|
68
90
|
}
|
|
69
|
-
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
await notifier.init();
|
|
74
|
-
}
|
|
91
|
+
const errors = await __1.Validator.validateStructure(_TSetup, parsed);
|
|
92
|
+
if (errors && errors.length > 0) {
|
|
93
|
+
(0, LogService_1.logError)("Setup validation failed: " + JSON.stringify(errors));
|
|
94
|
+
return null;
|
|
75
95
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
async _reloadSetup() {
|
|
99
|
+
const fresh = await this._loadSetup();
|
|
100
|
+
if (!fresh) {
|
|
101
|
+
(0, LogService_1.logWarn)("Setup reload failed — keeping previous good setup in memory");
|
|
102
|
+
return;
|
|
79
103
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
const hash = crypto_1.default.createHash("md5").update(JSON.stringify(fresh)).digest("hex");
|
|
105
|
+
if (hash === this._lastSetupHash)
|
|
106
|
+
return;
|
|
107
|
+
this._lastSetupHash = hash;
|
|
108
|
+
this.setup = fresh;
|
|
109
|
+
(0, LogService_1.logWarn)("Setup reloaded from disk (users list refreshed live; notifier/watchdog need service restart to change)");
|
|
110
|
+
}
|
|
111
|
+
_watchSetup() {
|
|
112
|
+
try {
|
|
113
|
+
fs_1.default.watch(this.options.setup_path, (_eventType, filename) => {
|
|
114
|
+
if (filename !== "setup.json")
|
|
115
|
+
return;
|
|
116
|
+
if (this._setupWatchDebounceTimer)
|
|
117
|
+
clearTimeout(this._setupWatchDebounceTimer);
|
|
118
|
+
this._setupWatchDebounceTimer = setTimeout(() => {
|
|
119
|
+
this._setupWatchDebounceTimer = null;
|
|
120
|
+
this._reloadSetup();
|
|
121
|
+
}, 200);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
(0, LogService_1.logError)("Failed to fs.watch setup file: " + e.message);
|
|
126
|
+
}
|
|
127
|
+
setInterval(() => { this._reloadSetup(); }, 60_000).unref();
|
|
83
128
|
}
|
|
84
129
|
async proceed(data) {
|
|
85
130
|
const user = this._checkUserAuth(data.auth);
|
|
@@ -109,6 +154,7 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
109
154
|
const safeName = (path_1.default.basename(file.name || "").replace(/[^a-zA-Z0-9._-]/g, "_")) || "upload";
|
|
110
155
|
let projectState = null;
|
|
111
156
|
let projectStatePath = null;
|
|
157
|
+
let projectStateWasCreated = false;
|
|
112
158
|
if (found.bluegreen && found.bluegreen.blue_config && found.bluegreen.green_config) {
|
|
113
159
|
projectStatePath = path_1.default.resolve(this.options.projects_path, data.name + "_state_.json");
|
|
114
160
|
if (fs_1.default.existsSync(projectStatePath)) {
|
|
@@ -125,6 +171,7 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
125
171
|
lastDeployed: "blue",
|
|
126
172
|
history: []
|
|
127
173
|
};
|
|
174
|
+
projectStateWasCreated = true;
|
|
128
175
|
}
|
|
129
176
|
else {
|
|
130
177
|
projectState.lastDeployed = projectState.active === "blue" ? "green" : "blue";
|
|
@@ -186,37 +233,31 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
186
233
|
if (found.config) {
|
|
187
234
|
await this.applyConfig(found.destination, found.config);
|
|
188
235
|
}
|
|
189
|
-
if (found.
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
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) {
|
|
236
|
+
if (found.pm2) {
|
|
237
|
+
const pm2Result = await this.runPM2(found, projectState);
|
|
238
|
+
if (__1.ErrorUtils.isError(pm2Result)) {
|
|
203
239
|
if (found.email && found.email.length > 0) {
|
|
204
240
|
for (let email of found.email) {
|
|
205
|
-
this.notifier?.notifyEmailStatus(email, "error-
|
|
241
|
+
this.notifier?.notifyEmailStatus(email, "error-pm2", found.name, user.login);
|
|
206
242
|
}
|
|
207
243
|
}
|
|
208
|
-
throw
|
|
244
|
+
throw pm2Result;
|
|
209
245
|
}
|
|
210
246
|
}
|
|
211
|
-
if (found.
|
|
212
|
-
const
|
|
213
|
-
|
|
247
|
+
if (found.nginx?.config_dir && (!projectState || projectStateWasCreated)) {
|
|
248
|
+
const activeCfg = (found.config ?? {});
|
|
249
|
+
const nginxErr = await this._renderNginx(found, data.name, activeCfg.PORT ?? "");
|
|
250
|
+
if (nginxErr) {
|
|
214
251
|
if (found.email && found.email.length > 0) {
|
|
215
252
|
for (let email of found.email) {
|
|
216
|
-
this.notifier?.notifyEmailStatus(email, "error-
|
|
253
|
+
this.notifier?.notifyEmailStatus(email, "error-nginx", found.name, user.login);
|
|
217
254
|
}
|
|
218
255
|
}
|
|
219
|
-
throw
|
|
256
|
+
throw nginxErr;
|
|
257
|
+
}
|
|
258
|
+
const hookErr = await NginxHelper_1.NginxHelper.runHook(found.bluegreen?.on_switch);
|
|
259
|
+
if (hookErr) {
|
|
260
|
+
(0, LogService_1.logWarn)("nginx reload after initial deploy failed: " + hookErr);
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
if (projectState && projectStatePath) {
|
|
@@ -313,14 +354,18 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
313
354
|
fs_1.default.writeFileSync(tmp, JSON.stringify(projectState, null, 4), "utf-8");
|
|
314
355
|
fs_1.default.renameSync(tmp, projectStatePath);
|
|
315
356
|
let hookError = null;
|
|
316
|
-
if (found.nginx?.
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
357
|
+
if (found.nginx?.config_dir) {
|
|
358
|
+
const slotCfg = target === "blue" ? found.bluegreen.blue_config : found.bluegreen.green_config;
|
|
359
|
+
const merged = { ...(found.config ?? {}), ...(slotCfg ?? {}) };
|
|
360
|
+
const nginxErr = await this._renderNginx(found, data.name, merged.PORT ?? "");
|
|
361
|
+
if (nginxErr) {
|
|
362
|
+
(0, LogService_1.logError)("nginx render during switch failed (state already flipped): " + nginxErr.message);
|
|
363
|
+
hookError = nginxErr.message;
|
|
321
364
|
}
|
|
322
365
|
}
|
|
323
|
-
|
|
366
|
+
if (!hookError) {
|
|
367
|
+
hookError = await NginxHelper_1.NginxHelper.runHook(found.bluegreen.on_switch);
|
|
368
|
+
}
|
|
324
369
|
const status = hookError ? "switch-hook-failed" : action === "rollback" ? `rolled-back-to-${target}` : `switched-to-${target}`;
|
|
325
370
|
if (found.email && found.email.length > 0) {
|
|
326
371
|
for (const email of found.email) {
|
|
@@ -387,6 +432,43 @@ class DeployerService extends BaseService_1.BaseService {
|
|
|
387
432
|
}
|
|
388
433
|
return matched;
|
|
389
434
|
}
|
|
435
|
+
async _renderNginx(found, projectName, activePort) {
|
|
436
|
+
if (!found.nginx?.config_dir)
|
|
437
|
+
return null;
|
|
438
|
+
if (!activePort) {
|
|
439
|
+
return { code: 52, message: "Cannot render nginx: no PORT in merged config for project " + projectName, httpStatus: 500 };
|
|
440
|
+
}
|
|
441
|
+
const dir = found.nginx.config_dir;
|
|
442
|
+
try {
|
|
443
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
return { code: 50, message: "Cannot create nginx config_dir: " + String(e?.message ?? e), httpStatus: 500 };
|
|
447
|
+
}
|
|
448
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
449
|
+
const upstreamName = found.nginx.upstream_name || (safeName + "_backend");
|
|
450
|
+
const upstreamPath = path_1.default.resolve(dir, projectName + ".upstream.conf");
|
|
451
|
+
const serverPath = path_1.default.resolve(dir, projectName + ".server.conf");
|
|
452
|
+
const domain = found.nginx.domain || projectName;
|
|
453
|
+
if (!found.nginx.domain) {
|
|
454
|
+
(0, LogService_1.logWarn)("nginx.domain not set for project " + projectName +
|
|
455
|
+
" — seeding server.conf with server_name=" + projectName + " (edit or delete .server.conf to fix)");
|
|
456
|
+
}
|
|
457
|
+
const seedResult = NginxHelper_1.NginxHelper.seedServerConfig(serverPath, NginxHelper_1.DEFAULT_SERVER_TEMPLATE, {
|
|
458
|
+
DOMAIN: domain,
|
|
459
|
+
UPSTREAM: upstreamName,
|
|
460
|
+
});
|
|
461
|
+
if (seedResult.error)
|
|
462
|
+
return seedResult.error;
|
|
463
|
+
if (seedResult.seeded) {
|
|
464
|
+
(0, LogService_1.logWarn)("Seeded nginx server config for " + projectName + " at " + serverPath +
|
|
465
|
+
" — run `certbot --nginx -d " + domain + "` to add SSL");
|
|
466
|
+
}
|
|
467
|
+
const upstreamErr = NginxHelper_1.NginxHelper.writeUpstream(upstreamPath, upstreamName, activePort);
|
|
468
|
+
if (upstreamErr)
|
|
469
|
+
return upstreamErr;
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
390
472
|
async runPM2(found, projectState) {
|
|
391
473
|
let pm2attrs = projectState?.lastDeployed === "blue" ? found.bluegreen?.blue_pm2_attributes : found.bluegreen?.green_pm2_attributes;
|
|
392
474
|
const pm2Attrs = Array.isArray(pm2attrs) ? pm2attrs.filter(s => typeof s === "string" && s.length > 0) : found.pm2_attributes;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { IError } from "../structures/Interfaces";
|
|
2
|
+
export declare const DEFAULT_SERVER_TEMPLATE = "# Seeded by deployer for _{{DOMAIN}}_. SAFE TO EDIT.\n# Deployer NEVER overwrites this file after the initial seed.\n# Add SSL by running: certbot --nginx -d _{{DOMAIN}}_\n\nserver {\n server_name _{{DOMAIN}}_;\n listen 80;\n\n location / {\n proxy_pass http://_{{UPSTREAM}}_;\n proxy_http_version 1.1;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 60s;\n }\n}\n";
|
|
1
3
|
export declare class NginxHelper {
|
|
2
|
-
static
|
|
3
|
-
static
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} | null;
|
|
4
|
+
static writeUpstream(filePath: string, upstreamName: string, port: string): IError | null;
|
|
5
|
+
static seedServerConfig(filePath: string, template: string, values: Record<string, string>): {
|
|
6
|
+
seeded: boolean;
|
|
7
|
+
error?: IError;
|
|
8
|
+
};
|
|
8
9
|
static runHook(cmd: string[] | undefined | null): Promise<string | null>;
|
|
9
10
|
}
|
|
@@ -3,50 +3,78 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.NginxHelper = void 0;
|
|
6
|
+
exports.NginxHelper = exports.DEFAULT_SERVER_TEMPLATE = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const util_1 = require("util");
|
|
11
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
12
11
|
const LogService_1 = require("../LogService");
|
|
13
12
|
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
13
|
+
exports.DEFAULT_SERVER_TEMPLATE = `# Seeded by deployer for _{{DOMAIN}}_. SAFE TO EDIT.
|
|
14
|
+
# Deployer NEVER overwrites this file after the initial seed.
|
|
15
|
+
# Add SSL by running: certbot --nginx -d _{{DOMAIN}}_
|
|
16
|
+
|
|
17
|
+
server {
|
|
18
|
+
server_name _{{DOMAIN}}_;
|
|
19
|
+
listen 80;
|
|
20
|
+
|
|
21
|
+
location / {
|
|
22
|
+
proxy_pass http://_{{UPSTREAM}}_;
|
|
23
|
+
proxy_http_version 1.1;
|
|
24
|
+
proxy_set_header Host $host;
|
|
25
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
26
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
27
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
28
|
+
proxy_read_timeout 60s;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
14
32
|
class NginxHelper {
|
|
15
|
-
static
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
static writeUpstream(filePath, upstreamName, port) {
|
|
34
|
+
const content = `# Auto-generated by deployer on each activate — do not edit.
|
|
35
|
+
upstream ${upstreamName} {
|
|
36
|
+
server 127.0.0.1:${port};
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
try {
|
|
40
|
+
const tmp = filePath + ".tmp";
|
|
41
|
+
try {
|
|
42
|
+
fs_1.default.unlinkSync(tmp);
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
fs_1.default.writeFileSync(tmp, content, "utf-8");
|
|
46
|
+
fs_1.default.renameSync(tmp, filePath);
|
|
47
|
+
return null;
|
|
27
48
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const unique = [...new Set(leftover)];
|
|
31
|
-
(0, LogService_1.logWarn)("Unresolved template placeholders in nginx config " + filePath + ": " + unique.join(", "));
|
|
49
|
+
catch (e) {
|
|
50
|
+
return { code: 48, message: "Failed to write nginx upstream file: " + String(e?.message ?? e), httpStatus: 500 };
|
|
32
51
|
}
|
|
33
52
|
}
|
|
34
|
-
static
|
|
35
|
-
if (
|
|
36
|
-
return {
|
|
53
|
+
static seedServerConfig(filePath, template, values) {
|
|
54
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
55
|
+
return { seeded: false };
|
|
37
56
|
}
|
|
38
57
|
try {
|
|
39
|
-
|
|
58
|
+
let content = template;
|
|
59
|
+
for (const [key, value] of Object.entries(values)) {
|
|
60
|
+
content = content.replaceAll(`_{{${key}}}_`, value);
|
|
61
|
+
}
|
|
62
|
+
const leftover = content.match(/_\{\{[A-Za-z0-9_]+\}\}_/g);
|
|
63
|
+
if (leftover && leftover.length > 0) {
|
|
64
|
+
const unique = [...new Set(leftover)];
|
|
65
|
+
(0, LogService_1.logWarn)("Unresolved placeholders in seeded nginx server config " + filePath + ": " + unique.join(", "));
|
|
66
|
+
}
|
|
67
|
+
const tmp = filePath + ".tmp";
|
|
40
68
|
try {
|
|
41
69
|
fs_1.default.unlinkSync(tmp);
|
|
42
70
|
}
|
|
43
71
|
catch { }
|
|
44
|
-
fs_1.default.
|
|
45
|
-
fs_1.default.renameSync(tmp,
|
|
46
|
-
return
|
|
72
|
+
fs_1.default.writeFileSync(tmp, content, "utf-8");
|
|
73
|
+
fs_1.default.renameSync(tmp, filePath);
|
|
74
|
+
return { seeded: true };
|
|
47
75
|
}
|
|
48
76
|
catch (e) {
|
|
49
|
-
return { code: 49, message: "nginx
|
|
77
|
+
return { seeded: false, error: { code: 49, message: "Failed to seed nginx server config: " + String(e?.message ?? e), httpStatus: 500 } };
|
|
50
78
|
}
|
|
51
79
|
}
|
|
52
80
|
static async runHook(cmd) {
|