puppyperpetual 0.0.1-security → 1.0.0
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/README.md +57 -1
- package/package.json +17 -4
- package/perpetual.js +22 -0
- package/src/ConfigManager.js +61 -0
- package/src/Logger.js +77 -0
- package/src/ProcessManager.js +193 -0
- package/src/defaultconfig.yaml +23 -0
- package/src/index.js +47 -0
- package/src/test/test.js +7 -0
- package/src/test/test_process.js +10 -0
- package/.gitattributes +0 -2
package/README.md
CHANGED
|
@@ -1 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
# puppyperpetual
|
|
2
|
+
|
|
3
|
+
node script to run things... perpetually. adapted from [legacyshell](https://github.com/onlypuppy7/LegacyShell)
|
|
4
|
+
|
|
5
|
+
[On npm @ https://www.npmjs.com/package/puppyperpetual](https://www.npmjs.com/package/puppyperpetual)
|
|
6
|
+
|
|
7
|
+
[And GitHub @ https://github.com/onlypuppy7/perpetual](https://github.com/onlypuppy7/perpetual)
|
|
8
|
+
|
|
9
|
+
## usage (as an npm package)
|
|
10
|
+
|
|
11
|
+
you can use it to run your node stuff perpetually:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import Perpetual from '../index.js';
|
|
15
|
+
|
|
16
|
+
const perpetual = new Perpetual('test_process', {
|
|
17
|
+
process_cmd: 'node ./src/test/test_process.js',
|
|
18
|
+
});
|
|
19
|
+
await perpetual.run();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
or anything really:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import Perpetual from '../index.js';
|
|
26
|
+
|
|
27
|
+
const perpetual = new Perpetual('test_process', {
|
|
28
|
+
process_cmd: 'cd . && git pull && date',
|
|
29
|
+
});
|
|
30
|
+
await perpetual.run();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## usage (as a node app thing)
|
|
34
|
+
|
|
35
|
+
as per tradition for my projects, simple installation!
|
|
36
|
+
|
|
37
|
+
1. `npm i`
|
|
38
|
+
|
|
39
|
+
then run once to create config
|
|
40
|
+
|
|
41
|
+
- `npm run start`
|
|
42
|
+
|
|
43
|
+
then customise it in `store/config.yaml`, making a new entry for every thing
|
|
44
|
+
|
|
45
|
+
then run like this:
|
|
46
|
+
|
|
47
|
+
- `node .\perpetual.js --default`
|
|
48
|
+
|
|
49
|
+
alternatively, just pass in your command:
|
|
50
|
+
|
|
51
|
+
- `node .\perpetual.js "cd . && echo date"`
|
|
52
|
+
|
|
53
|
+
then you dont need to mess around with the horrible yaml. dont worry, i hate doing it too.
|
|
54
|
+
|
|
55
|
+
## it doesnt work?
|
|
56
|
+
|
|
57
|
+
i dont care. i use this for my own stuff. i know that it works on linux and thats all i need.
|
package/package.json
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "puppyperpetual",
|
|
3
|
-
"
|
|
4
|
-
"version": "0.0
|
|
5
|
-
|
|
2
|
+
"name": "puppyperpetual",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "v1.0.0",
|
|
5
|
+
"description": "Run stuff FOREVER! PERPETUALLY!",
|
|
6
|
+
"main": "perpetual.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node perpetual.js",
|
|
9
|
+
"test": "node src/test/test.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"chalk": "^5.3.0",
|
|
13
|
+
"js-yaml": "^4.1.0",
|
|
14
|
+
"puppylog": "^1.0.5",
|
|
15
|
+
"puppymisc": "^1.0.4",
|
|
16
|
+
"puppywebhook": "^1.0.1"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/perpetual.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import Perpetual from './src/index.js';
|
|
4
|
+
|
|
5
|
+
let rootDir = import.meta.dirname;
|
|
6
|
+
|
|
7
|
+
if (!rootDir) {
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
rootDir = dirname(__filename);
|
|
10
|
+
console.log("(Using fallback mechanism for rootDir)");
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
console.log(process.argv, rootDir);
|
|
14
|
+
|
|
15
|
+
const serverName = process.argv[2].replace("--","");
|
|
16
|
+
const isDirect = !process.argv[2].startsWith("--");
|
|
17
|
+
|
|
18
|
+
const perpetual = new Perpetual(serverName, {
|
|
19
|
+
process_cmd: isDirect ? process.argv.slice(2).join(" ") : null,
|
|
20
|
+
rootDir,
|
|
21
|
+
});
|
|
22
|
+
await perpetual.run();
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
|
|
5
|
+
export class ConfigManager {
|
|
6
|
+
constructor(rootDir, serverName) {
|
|
7
|
+
this.rootDir = rootDir;
|
|
8
|
+
this.serverName = serverName;
|
|
9
|
+
|
|
10
|
+
this.defaultConfigPath = path.join(rootDir, 'src', 'defaultconfig.yaml');
|
|
11
|
+
this.configPath = path.join(rootDir, 'store', 'config.yaml');
|
|
12
|
+
|
|
13
|
+
this.ensureConfigExists();
|
|
14
|
+
this.config = yaml.load(fs.readFileSync(this.configPath, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
ensureConfigExists() {
|
|
18
|
+
if (!fs.existsSync(this.configPath)) {
|
|
19
|
+
fs.mkdirSync(path.dirname(this.configPath), { recursive: true });
|
|
20
|
+
fs.copyFileSync(this.defaultConfigPath, this.configPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getServerOptions(options) {
|
|
25
|
+
let passed = options || {};
|
|
26
|
+
Object.apply(passed, this.config.servers?.[this.serverName] || {});
|
|
27
|
+
|
|
28
|
+
if (!passed.dir) {
|
|
29
|
+
if (passed.process_cmd.includes("cd ")) {
|
|
30
|
+
console.log("Using custom process command:", passed.process_cmd);
|
|
31
|
+
//detect via regex if cd is used
|
|
32
|
+
const cdMatch = passed.process_cmd.match(/cd\s+([^\s]+)\s*&&\s*(.*)/);
|
|
33
|
+
if (cdMatch) {
|
|
34
|
+
console.log("Detected 'cd' command in process_cmd:", cdMatch[1]);
|
|
35
|
+
passed.dir = cdMatch[1];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
//process
|
|
42
|
+
process_cmd: passed.process_cmd || "idk lol",
|
|
43
|
+
dir: passed.dir || "",
|
|
44
|
+
//daily restart
|
|
45
|
+
dailyrestart_enable: passed.dailyrestart_enable || false,
|
|
46
|
+
dailyrestart_time: passed.dailyrestart_time || "4:00",
|
|
47
|
+
dailyrestart_quickpull: passed.dailyrestart_quickpull,
|
|
48
|
+
//file logging
|
|
49
|
+
logfile_enable: passed.logfile_enable,
|
|
50
|
+
logfile_location: passed.logfile_location || path.join(this.rootDir, "store", "logs", this.serverName), //no editing kek
|
|
51
|
+
//webhook logging
|
|
52
|
+
webhook_url: passed.webhook_url || "", //false or empty is disabled
|
|
53
|
+
webhook_username: passed.webhook_username || "Webhook", //eg "LegacyShell: Client Server"
|
|
54
|
+
webhook_avatar: passed.webhook_avatar || "https://cdn.onlypuppy7.online/legacyshell/client.png", //eg "https://cdn.onlypuppy7.online/legacyshell/client.png"
|
|
55
|
+
webhook_ping_user: passed.webhook_ping_user || false, //this might flood your shit
|
|
56
|
+
webhook_ping_role: passed.webhook_ping_role || false, //this might flood EVERYONE'S shit
|
|
57
|
+
//pulling
|
|
58
|
+
is_puller: this.config.pullers?.includes(this.serverName) || false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/Logger.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
import { getTimestamp, stripAnsi, divideString, ensureDirExists } from 'puppymisc';
|
|
4
|
+
import log from 'puppylog';
|
|
5
|
+
|
|
6
|
+
export class Logger {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.logQueue = [];
|
|
10
|
+
this.queuedChunks = [];
|
|
11
|
+
this.maxMessageLength = 1900;
|
|
12
|
+
this.messagesSent = 0;
|
|
13
|
+
|
|
14
|
+
ensureDirExists(options.logfile_location);
|
|
15
|
+
this.logFilePath = `${options.logfile_location}/${getTimestamp(true)}.log`;
|
|
16
|
+
ensureDirExists(this.logFilePath);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
appendLog(msg, noSend = false) {
|
|
20
|
+
msg = stripAnsi(msg);
|
|
21
|
+
if (!noSend) this.logQueue.push(msg);
|
|
22
|
+
fs.appendFile(this.logFilePath, `${msg}\n`, () => {});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
logSend(msg) {
|
|
26
|
+
const line = `#${getTimestamp()} ${msg}`;
|
|
27
|
+
log.muted(line);
|
|
28
|
+
this.appendLog(line);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
logNoSend(msg) {
|
|
32
|
+
const line = `#${getTimestamp()} ${msg}`;
|
|
33
|
+
log.muted(line);
|
|
34
|
+
this.appendLog(line, true);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async sendToWebhook() {
|
|
38
|
+
while (this.logQueue.length) {
|
|
39
|
+
let msg = this.logQueue.shift();
|
|
40
|
+
if (msg.length > this.maxMessageLength) {
|
|
41
|
+
this.logQueue.unshift(...divideString(msg, this.maxMessageLength));
|
|
42
|
+
} else {
|
|
43
|
+
const lastChunk = this.queuedChunks[0] || "";
|
|
44
|
+
const newChunk = lastChunk + `\n${msg}`;
|
|
45
|
+
if (newChunk.length > this.maxMessageLength) {
|
|
46
|
+
this.queuedChunks.unshift(`\n${msg}`);
|
|
47
|
+
} else {
|
|
48
|
+
this.queuedChunks[0] = newChunk;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (this.queuedChunks.length && this.options.webhook_url) {
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(this.options.webhook_url, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
username: this.options.webhook_username,
|
|
60
|
+
avatar_url: this.options.webhook_avatar,
|
|
61
|
+
content: this.queuedChunks[this.queuedChunks.length - 1].slice(0, 2000),
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) throw new Error(response.statusText);
|
|
65
|
+
this.logNoSend(`Logs sent to webhook (${this.messagesSent})`);
|
|
66
|
+
this.queuedChunks.pop();
|
|
67
|
+
this.messagesSent++;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
this.logNoSend(`Error sending webhook: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
startWebhookInterval(interval = 15000) {
|
|
75
|
+
this.webhookInterval = setInterval(() => this.sendToWebhook(), interval);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { Worker } from 'node:worker_threads';
|
|
5
|
+
import { getTimestamp } from 'puppymisc';
|
|
6
|
+
|
|
7
|
+
export class ProcessManager {
|
|
8
|
+
constructor(options, logger, rootDir) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.rootDir = rootDir;
|
|
12
|
+
|
|
13
|
+
this.runningProcess = null;
|
|
14
|
+
this.restartScheduled = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async startProcess(purposefulStop = false) {
|
|
18
|
+
if (this.runningProcess) {
|
|
19
|
+
this.logger.logSend(`Stopping previous process...`);
|
|
20
|
+
this.runningProcess.purposefulStop = purposefulStop;
|
|
21
|
+
|
|
22
|
+
if (this.runningProcess instanceof Worker) {
|
|
23
|
+
try {
|
|
24
|
+
await this.runningProcess.terminate();
|
|
25
|
+
} catch (err) {
|
|
26
|
+
this.logger.logSend(`Failed to stop previous process via .terminate: ${err.message} ${this.runningProcess}`);
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
try {
|
|
30
|
+
this.runningProcess.kill('SIGINT');
|
|
31
|
+
this.logger.logSend(`Stopped previous process via .kill: ${this.runningProcess.pid}`);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
this.logger.logSend(`Failed to stop previous process via .kill: ${err.message} ${this.runningProcess}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
process.kill(-this.runningProcess.pid, 'SIGINT');
|
|
38
|
+
this.logger.logSend(`Stopped previous process via process.kill: ${this.runningProcess.pid}`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
this.logger.logSend(`Failed to stop previous process via process.kill: ${err.message} ${this.runningProcess}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
this.logger.logSend(`Starting process: ${this.options.process_cmd}`);
|
|
45
|
+
|
|
46
|
+
const useWorkerThreads = true;
|
|
47
|
+
const isNodeScript = this.options.process_cmd.startsWith('node ');
|
|
48
|
+
let scriptPath = this.options.process_cmd;
|
|
49
|
+
|
|
50
|
+
if (isNodeScript && useWorkerThreads) {
|
|
51
|
+
// Use Worker Threads for Node.js scripts
|
|
52
|
+
scriptPath = scriptPath.slice(5).trim();
|
|
53
|
+
scriptPath = path.isAbsolute(scriptPath) ? scriptPath : path.join(this.rootDir, scriptPath);
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(scriptPath)) {
|
|
56
|
+
this.logger.logSend(`Script file does not exist: ${scriptPath}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.logger.logSend(`Using Worker Threads for script: ${scriptPath}`);
|
|
61
|
+
this.runningProcess = new Worker(scriptPath, { workerData: {}, stdout: true, stderr: true });
|
|
62
|
+
|
|
63
|
+
this.runningProcess.stdout.on('data', (data) => {
|
|
64
|
+
const lines = data.toString().split('\n').filter(Boolean);
|
|
65
|
+
lines.forEach(line => {
|
|
66
|
+
const logLine = `${getTimestamp()} ${line}`;
|
|
67
|
+
process.stdout.write(`${logLine}\n`);
|
|
68
|
+
this.logger.appendLog(logLine);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.runningProcess.stderr.on('data', (data) => {
|
|
73
|
+
const lines = data.toString().split('\n').filter(Boolean);
|
|
74
|
+
lines.forEach(line => {
|
|
75
|
+
const logLine = `${getTimestamp()} ERROR: ${line}`;
|
|
76
|
+
process.stderr.write(`${logLine}\n`);
|
|
77
|
+
this.logger.appendLog(logLine);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.runningProcess.on('error', (err) => {
|
|
82
|
+
const logLine = `${getTimestamp()} ERROR: ${err.message}`;
|
|
83
|
+
process.stderr.write(`${logLine}\n`);
|
|
84
|
+
this.logger.appendLog(logLine);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.runningProcess.on('exit', (code, signal) => {
|
|
88
|
+
code = code === 57 ? 1337 : code;
|
|
89
|
+
if (signal === 'SIGINT') {
|
|
90
|
+
this.logger.logSend(`Process terminated manually.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const pingUser = this.options.webhook_ping_user ? ` <@${this.options.webhook_ping_user}>` : "";
|
|
95
|
+
const pingRole = this.options.webhook_ping_role ? ` <@&${this.options.webhook_ping_role}>` : "";
|
|
96
|
+
|
|
97
|
+
this.logger.logSend(`Process exited with code ${code}, signal ${signal}. ${(code === 1337 || this.runningProcess.purposefulStop) ? "No ping, intended restart" : `Restarting...${pingUser}${pingRole}`}`);
|
|
98
|
+
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
this.runningProcess = null;
|
|
101
|
+
this.startProcess();
|
|
102
|
+
}, (code === 1337 || this.runningProcess.purposefulStop) ? 1000 : 5000);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
} else {
|
|
106
|
+
this.runningProcess = spawn('bash', ['-c', scriptPath], {
|
|
107
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
108
|
+
env: { ...process.env, FORCE_COLOR: 'true' },
|
|
109
|
+
detached: process.platform !== 'win32',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const handleOutput = (data, isError = false) => {
|
|
113
|
+
const lines = data.toString().split('\n').filter(Boolean);
|
|
114
|
+
lines.forEach(line => {
|
|
115
|
+
const logLine = `${getTimestamp()}${isError ? " ERROR:" : ""} ${line}`;
|
|
116
|
+
if (isError) process.stderr.write(`${logLine}\n`);
|
|
117
|
+
else process.stdout.write(`${logLine}\n`);
|
|
118
|
+
this.logger.appendLog(logLine);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
this.runningProcess.stdout.on('data', d => handleOutput(d));
|
|
123
|
+
this.runningProcess.stderr.on('data', d => handleOutput(d, true));
|
|
124
|
+
|
|
125
|
+
const onExit = (code, signal) => {
|
|
126
|
+
code = code === 57 ? 1337 : code;
|
|
127
|
+
const pingUser = this.options.webhook_ping_user ? ` <@${this.options.webhook_ping_user}>` : "";
|
|
128
|
+
const pingRole = this.options.webhook_ping_role ? ` <@&${this.options.webhook_ping_role}>` : "";
|
|
129
|
+
this.logger.logSend(`Process exited with code ${code}. ${code === 1337 ? "No ping, intended restart" : `Restarting...${pingUser}${pingRole}`}`);
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.runningProcess = null;
|
|
132
|
+
this.startProcess();
|
|
133
|
+
}, (code === 1337 || signal === 'SIGINT') ? 1000 : 5000);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
this.runningProcess.on('exit', (code, signal) => {
|
|
137
|
+
this.logger.logSend(`Process exited with code ${code}, signal ${signal}.`);
|
|
138
|
+
onExit(code, signal);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
autoRestart() {
|
|
145
|
+
if (!this.options.dailyrestart_enable) return;
|
|
146
|
+
if (this.restartScheduled) {
|
|
147
|
+
this.logger.logSend("Restart already scheduled.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const now = new Date();
|
|
152
|
+
const [hour, minute] = this.options.dailyrestart_time.split(':').map(Number);
|
|
153
|
+
const nextRestart = new Date();
|
|
154
|
+
nextRestart.setHours(hour, minute, 0, 0);
|
|
155
|
+
if (nextRestart < now) nextRestart.setDate(nextRestart.getDate() + 1);
|
|
156
|
+
|
|
157
|
+
let timeUntilRestart = nextRestart - now;
|
|
158
|
+
if (timeUntilRestart <= 0) timeUntilRestart += 24*60*60*1000;
|
|
159
|
+
|
|
160
|
+
this.logger.logSend(`Scheduled restart in ${Math.floor(timeUntilRestart / 1000 / 60)} minutes.`);
|
|
161
|
+
this.restartScheduled = true;
|
|
162
|
+
|
|
163
|
+
setTimeout(async () => {
|
|
164
|
+
if (this.options.dailyrestart_quickpull) {
|
|
165
|
+
this.logger.logSend("Quick-pulling before restart.");
|
|
166
|
+
this.executeCommand('git', ['pull'], 'ignore');
|
|
167
|
+
}
|
|
168
|
+
this.logger.logSend("Auto-restarting process.");
|
|
169
|
+
await this.startProcess();
|
|
170
|
+
this.restartScheduled = false;
|
|
171
|
+
this.autoRestart();
|
|
172
|
+
}, timeUntilRestart);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
executeCommand(command, args, stdio = "inherit") {
|
|
176
|
+
let dir = this.options.dir || "";
|
|
177
|
+
|
|
178
|
+
console.log(dir, this.options.dir);
|
|
179
|
+
|
|
180
|
+
const cmdProcess = spawn(command, args, {
|
|
181
|
+
stdio,
|
|
182
|
+
cwd: this.options.dir && this.options.dir !== "" && dir,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
cmdProcess.on('exit', (code) => {
|
|
186
|
+
if (stdio !== 'ignore') console.log(`${command} exited with code: ${code}`);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
cmdProcess.on('error', (err) => {
|
|
190
|
+
console.error(`Failed to start ${command}:`, err);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#which should be the ones to do pulls?
|
|
2
|
+
pullers: ["default"] #default: ["default"]
|
|
3
|
+
|
|
4
|
+
# config for how you want the files to continually run. setup for error notifications on discord!
|
|
5
|
+
# the options are the same for all, but you can configure each differently.
|
|
6
|
+
default: #example
|
|
7
|
+
process_cmd: "node src/test.js" #dont touch unless u changed the path
|
|
8
|
+
dir: ""
|
|
9
|
+
|
|
10
|
+
#daily restarts, at specified time every day
|
|
11
|
+
dailyrestart_enable: true
|
|
12
|
+
dailyrestart_time: "4:00" # HH:MM (24 hours)
|
|
13
|
+
dailyrestart_quickpull: false #if true, will pull before restarting
|
|
14
|
+
|
|
15
|
+
#logfiles
|
|
16
|
+
logfile_enable: true
|
|
17
|
+
|
|
18
|
+
#webhook logging
|
|
19
|
+
webhook_url: "" #empty disables it
|
|
20
|
+
webhook_username: "Test!"
|
|
21
|
+
webhook_avatar: "https://cdn.onlypuppy7.online/legacyshell/client.png"
|
|
22
|
+
webhook_ping_user: "514778439018872842" #ENTER THE USER ID. for when there is an error. empty = no ping.
|
|
23
|
+
webhook_ping_role: "1221557010651418705" #ENTER THE ROLE ID. for when there is an error. empty = no ping.
|
package/src/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ConfigManager } from './ConfigManager.js';
|
|
3
|
+
import { Logger } from './Logger.js';
|
|
4
|
+
import { ProcessManager } from './ProcessManager.js';
|
|
5
|
+
import readline from 'node:readline';
|
|
6
|
+
|
|
7
|
+
export class Perpetual {
|
|
8
|
+
constructor(serverName, options) {
|
|
9
|
+
const rootDir = options.rootDir || path.resolve('./');
|
|
10
|
+
this.configManager = new ConfigManager(rootDir, serverName);
|
|
11
|
+
this.options = this.configManager.getServerOptions(options);
|
|
12
|
+
this.logger = new Logger(this.options);
|
|
13
|
+
this.processManager = new ProcessManager(this.options, this.logger, rootDir);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async run() {
|
|
17
|
+
if (this.options.webhook_url) this.logger.startWebhookInterval();
|
|
18
|
+
await this.processManager.startProcess();
|
|
19
|
+
|
|
20
|
+
const rl = readline.createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
prompt: '> '
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
rl.prompt();
|
|
27
|
+
rl.on('line', async (line) => {
|
|
28
|
+
let cmd = line.trim();
|
|
29
|
+
if (cmd === "r" || cmd === "restart") {
|
|
30
|
+
await this.processManager.startProcess(true);
|
|
31
|
+
} else if (cmd === "p" || cmd === "pull") {
|
|
32
|
+
this.processManager.executeCommand('git', ['pull']);
|
|
33
|
+
} else if (cmd === "pr") { // pull and restart
|
|
34
|
+
this.processManager.executeCommand('git', ['pull']);
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
this.processManager.startProcess(true);
|
|
37
|
+
}, 5e3);
|
|
38
|
+
};
|
|
39
|
+
rl.prompt();
|
|
40
|
+
}).on('close', () => {
|
|
41
|
+
console.log('Exiting interactive mode!');
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default Perpetual;
|
package/src/test/test.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import log from 'puppylog';
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
let count = 0;
|
|
5
|
+
while (true) {
|
|
6
|
+
log.success("Hello, world!", count++);
|
|
7
|
+
if (count == 10) throw new Error("Test error to check error handling");
|
|
8
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
9
|
+
};
|
|
10
|
+
})();
|
package/.gitattributes
DELETED