Skip to content

Commit bc2090f

Browse files
committed
feat: configure PM2 on startup
1 parent 8d69a6a commit bc2090f

File tree

12 files changed

+123
-106
lines changed

12 files changed

+123
-106
lines changed

api/ecosystem.config.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
{
2+
"$schema": "https://json.schemastore.org/pm2-ecosystem",
23
"apps": [
34
{
45
"name": "unraid-api",
56
"script": "./dist/main.js",
67
"cwd": "/usr/local/unraid-api",
7-
"log": "/var/log/unraid-api/unraid-api.log",
88
"exec_mode": "fork",
99
"wait_ready": true,
10-
"listen_timeout": 30000,
10+
"listen_timeout": 15000,
1111
"max_restarts": 10,
1212
"min_uptime": 10000,
1313
"ignore_watch": [
1414
"node_modules",
1515
"src",
1616
".env.*",
1717
"myservers.cfg"
18-
]
18+
],
19+
"log": "/var/log/unraid-api/unraid-api.log"
1920
}
2021
]
2122
}

api/src/core/logrotate/setup-logrotate.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

api/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ try {
5959
// Must occur before config is loaded to ensure that the handler can fix broken configs
6060
await startStoreSync();
6161

62-
await setupLogRotation();
63-
6462
// Load my servers config file into store
6563
await store.dispatch(loadConfigFile());
6664

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Injectable } from '@nestjs/common';
22

3+
import { levels, LogLevel } from '@app/core/log';
4+
import { LOG_LEVEL } from '@app/environment';
5+
36
@Injectable()
47
export class LogService {
58
private logger = console;
@@ -8,23 +11,41 @@ export class LogService {
811
this.logger.clear();
912
}
1013

14+
shouldLog(level: LogLevel): boolean {
15+
const logLevelsLowToHigh = levels;
16+
return (
17+
logLevelsLowToHigh.indexOf(level) >=
18+
logLevelsLowToHigh.indexOf(LOG_LEVEL.toLowerCase() as LogLevel)
19+
);
20+
}
21+
1122
log(message: string): void {
12-
this.logger.log(message);
23+
if (this.shouldLog('info')) {
24+
this.logger.log(message);
25+
}
1326
}
1427

1528
info(message: string): void {
16-
this.logger.info(message);
29+
if (this.shouldLog('info')) {
30+
this.logger.info(message);
31+
}
1732
}
1833

1934
warn(message: string): void {
20-
this.logger.warn(message);
35+
if (this.shouldLog('warn')) {
36+
this.logger.warn(message);
37+
}
2138
}
2239

2340
error(message: string, trace: string = ''): void {
24-
this.logger.error(message, trace);
41+
if (this.shouldLog('error')) {
42+
this.logger.error(message, trace);
43+
}
2544
}
2645

2746
debug(message: any, ...optionalParams: any[]): void {
28-
this.logger.debug(message, ...optionalParams);
47+
if (this.shouldLog('debug')) {
48+
this.logger.debug(message, ...optionalParams);
49+
}
2950
}
3051
}

api/src/unraid-api/cli/start.command.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import { existsSync } from 'node:fs';
2+
import { writeFile } from 'node:fs/promises';
3+
14
import { execa } from 'execa';
25
import { Command, CommandRunner, Option } from 'nest-commander';
36

7+
import type { LogLevel } from '@app/core/log';
48
import { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts';
5-
import { levels, type LogLevel } from '@app/core/log';
9+
import { levels } from '@app/core/log';
610
import { LogService } from '@app/unraid-api/cli/log.service';
711

812
interface StartCommandOptions {
@@ -15,18 +19,47 @@ export class StartCommand extends CommandRunner {
1519
super();
1620
}
1721

22+
async configurePm2(): Promise<void> {
23+
if (existsSync('/tmp/pm2-configured')) {
24+
return;
25+
}
26+
// Write a temp file when first started to prevent needing to run this again
27+
// Install PM2 Logrotate
28+
await execa(PM2_PATH, ['install', 'pm2-logrotate'])
29+
.then(({ stdout }) => {
30+
this.logger.debug(stdout);
31+
})
32+
.catch(({ stderr }) => {
33+
this.logger.error('PM2 Logrotate Error: ' + stderr);
34+
});
35+
// Now set logrotate options
36+
await execa(PM2_PATH, ['set', 'pm2-logrotate:retain', '2'])
37+
.then(({ stdout }) => this.logger.debug(stdout))
38+
.catch(({ stderr }) => this.logger.error('PM2 Logrotate Set Error: ' + stderr));
39+
await execa(PM2_PATH, ['set', 'pm2-logrotate:compress', 'true'])
40+
.then(({ stdout }) => this.logger.debug(stdout))
41+
.catch(({ stderr }) => this.logger.error('PM2 Logrotate Compress Error: ' + stderr));
42+
await execa(PM2_PATH, ['set', 'pm2-logrotate:max_size', '1M'])
43+
.then(({ stdout }) => this.logger.debug(stdout))
44+
.catch(({ stderr }) => this.logger.error('PM2 Logrotate Max Size Error: ' + stderr));
45+
46+
// PM2 Save Settings
47+
await execa(PM2_PATH, ['set', 'pm2:autodump', 'true'])
48+
.then(({ stdout }) => this.logger.debug(stdout))
49+
.catch(({ stderr }) => this.logger.error('PM2 Autodump Error: ' + stderr));
50+
51+
// Update PM2
52+
await execa(PM2_PATH, ['update'])
53+
.then(({ stdout }) => this.logger.debug(stdout))
54+
.catch(({ stderr }) => this.logger.error('PM2 Update Error: ' + stderr));
55+
56+
await writeFile('/tmp/pm2-configured', 'true');
57+
}
58+
1859
async run(_: string[], options: StartCommandOptions): Promise<void> {
1960
this.logger.info('Starting the Unraid API');
2061

21-
// Update PM2 first if necessary
22-
const { stderr: updateErr, stdout: updateOut } = await execa(PM2_PATH, ['update']);
23-
if (updateOut) {
24-
this.logger.log(updateOut);
25-
}
26-
if (updateErr) {
27-
this.logger.error('PM2 Update Error: ' + updateErr);
28-
process.exit(1);
29-
}
62+
await this.configurePm2();
3063

3164
const envLog = options['log-level'] ? `LOG_LEVEL=${options['log-level']}` : '';
3265
const { stderr, stdout } = await execa(`${envLog} ${PM2_PATH}`.trim(), [
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { LogCleanupService } from '@app/unraid-api/cron/log-cleanup.service';
21
import { WriteFlashFileService } from '@app/unraid-api/cron/write-flash-file.service';
32
import { Module } from '@nestjs/common';
43
import { ScheduleModule } from '@nestjs/schedule';
54

65
@Module({
76
imports: [ScheduleModule.forRoot()],
8-
providers: [LogCleanupService, WriteFlashFileService],
7+
providers: [WriteFlashFileService],
98
})
109
export class CronModule {}

api/src/unraid-api/cron/log-cleanup.service.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

api/src/unraid-api/unraid-file-modifier/modifications/auth-request.modification.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { readFile, writeFile } from 'fs/promises';
44

55
import {
66
FileModification,
7-
FileModificationService,
87
ShouldApplyWithReason,
98
} from '@app/unraid-api/unraid-file-modifier/unraid-file-modifier.service';
9+
import { backupFile } from '@app/utils';
1010

1111
const AUTH_REQUEST_FILE = '/usr/local/emhttp/auth-request.php' as const;
1212
const WEB_COMPS_DIR = '/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/_nuxt/' as const;
@@ -34,7 +34,7 @@ export default class AuthRequestModification implements FileModification {
3434
const fileContent = await readFile(AUTH_REQUEST_FILE, 'utf8');
3535

3636
if (fileContent.includes('$arrWhitelist')) {
37-
FileModificationService.backupFile(AUTH_REQUEST_FILE, true);
37+
backupFile(AUTH_REQUEST_FILE, true);
3838
this.logger.debug(`Backup of ${AUTH_REQUEST_FILE} created.`);
3939

4040
const filesToAddString = FILES_TO_ADD.map((file) => ` '${file}',`).join('\n');

api/src/unraid-api/unraid-file-modifier/modifications/sso.modification.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { readFile, writeFile } from 'node:fs/promises';
33

44
import {
55
FileModification,
6-
FileModificationService,
76
ShouldApplyWithReason,
87
} from '@app/unraid-api/unraid-file-modifier/unraid-file-modifier.service';
8+
import { backupFile, restoreFile } from '@app/utils';
99

1010
export default class SSOFileModification implements FileModification {
1111
id: string = 'sso';
@@ -47,9 +47,9 @@ function verifyUsernamePasswordAndSSO(string $username, string $password): bool
4747
'<?php include "$docroot/plugins/dynamix.my.servers/include/sso-login.php"; ?>';
4848

4949
// Restore the original file if exists
50-
await FileModificationService.restoreFile(this.loginFilePath, false);
50+
await restoreFile(this.loginFilePath, false);
5151
// Backup the original content
52-
await FileModificationService.backupFile(this.loginFilePath, true);
52+
await backupFile(this.loginFilePath, true);
5353

5454
// Read the file content
5555
let fileContent = await readFile(this.loginFilePath, 'utf-8');
@@ -73,7 +73,7 @@ function verifyUsernamePasswordAndSSO(string $username, string $password): bool
7373
this.logger.log('Login Function replaced successfully.');
7474
}
7575
async rollback(): Promise<void> {
76-
const restored = await FileModificationService.restoreFile(this.loginFilePath, false);
76+
const restored = await restoreFile(this.loginFilePath, false);
7777
if (restored) {
7878
this.logger.debug('SSO login file restored.');
7979
} else {

api/src/unraid-api/unraid-file-modifier/unraid-file-modifier.service.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
2-
import { copyFile, unlink } from 'node:fs/promises';
32

43
import AuthRequestModification from '@app/unraid-api/unraid-file-modifier/modifications/auth-request.modification';
54
import SSOFileModification from '@app/unraid-api/unraid-file-modifier/modifications/sso.modification';
@@ -122,40 +121,5 @@ export class UnraidFileModificationService implements OnModuleInit, OnModuleDest
122121
}
123122
}
124123

125-
/**
126-
* Helper method to allow backing up a single file to a .bak file.
127-
* @param path the file to backup, creates a .bak file in the same directory
128-
* @throws Error if the file cannot be copied
129-
*/
130-
public static backupFile = async (path: string, throwOnMissing = true): Promise<void> => {
131-
try {
132-
const backupPath = path + '.bak';
133-
await copyFile(path, backupPath);
134-
} catch (err) {
135-
if (throwOnMissing) {
136-
throw new Error(`File does not exist: ${path}`);
137-
}
138-
}
139-
};
140124

141-
/**
142-
*
143-
* @param path Path to original (not .bak) file
144-
* @param throwOnMissing Whether to throw an error if the backup file does not exist
145-
* @throws Error if the backup file does not exist and throwOnMissing is true
146-
* @returns boolean indicating whether the restore was successful
147-
*/
148-
public static restoreFile = async (path: string, throwOnMissing = true): Promise<boolean> => {
149-
const backupPath = path + '.bak';
150-
try {
151-
await copyFile(backupPath, path);
152-
await unlink(backupPath);
153-
return true;
154-
} catch {
155-
if (throwOnMissing) {
156-
throw new Error(`Backup file does not exist: ${backupPath}`);
157-
}
158-
return false;
159-
}
160-
};
161125
}

0 commit comments

Comments
 (0)