botforje 0.0.1 → 0.0.2
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 +1 -1
- package/dist/cli.js +0 -8
- package/dist/commands/guide.js +0 -1
- package/dist/config/validation.js +1 -1
- package/dist/config/yaml.js +7 -1
- package/dist/helpers/logger.js +2 -5
- package/dist/whatsapp/client.js +13 -0
- package/package.json +3 -3
- package/scripts/postinstall.js +131 -34
- package/scripts/preuninstall.js +37 -34
- package/service/botforje.service.template +2 -9
- package/scripts/setup-systemd.js +0 -91
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/botforje)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org)
|
|
6
6
|
[](https://github.com/felixzsh/botforje/pulls)
|
|
7
7
|
|
|
8
8
|
**Create multiple WhatsApp bots without writing code** - Just configure in YAML!
|
package/dist/cli.js
CHANGED
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
const commander_1 = require("commander");
|
|
38
|
-
const child_process_1 = require("child_process");
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const fs = __importStar(require("fs"));
|
|
41
40
|
const fleet_1 = require("./fleet");
|
|
@@ -59,13 +58,6 @@ program
|
|
|
59
58
|
.description('CLI tool for creating and managing WhatsApp bots')
|
|
60
59
|
.version(packageJson.version)
|
|
61
60
|
.option('-c, --config <path>', `Path to config file (default: ${(0, yaml_1.getDefaultConfigPath)()})`);
|
|
62
|
-
program
|
|
63
|
-
.command('setup')
|
|
64
|
-
.description('Setup/repair systemd service')
|
|
65
|
-
.action(() => {
|
|
66
|
-
const setupScript = path.join(__dirname, '..', 'scripts', 'setup-systemd.js');
|
|
67
|
-
(0, child_process_1.execSync)(`node ${setupScript}`, { stdio: 'inherit' });
|
|
68
|
-
});
|
|
69
61
|
program
|
|
70
62
|
.command('guide')
|
|
71
63
|
.description('Show AI agent configuration guide')
|
package/dist/commands/guide.js
CHANGED
|
@@ -555,7 +555,6 @@ curl http://localhost:3000/api/config/status
|
|
|
555
555
|
botforje daemon # Start the bot daemon
|
|
556
556
|
botforje status # Show bot session status
|
|
557
557
|
botforje auth <botId> # Authenticate a bot
|
|
558
|
-
botforje setup # Setup systemd service
|
|
559
558
|
botforje guide # Show this guide
|
|
560
559
|
\`\`\`
|
|
561
560
|
`;
|
|
@@ -467,7 +467,7 @@ function validateIdWithContext(id, kind, ctx) {
|
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
469
|
async function validateConfig(configPath) {
|
|
470
|
-
const targetPath = configPath || (0, yaml_1.
|
|
470
|
+
const targetPath = configPath || (0, yaml_1.getConfigPath)();
|
|
471
471
|
const configDir = path.dirname(targetPath);
|
|
472
472
|
const errors = [];
|
|
473
473
|
const actionsDir = path.join(configDir, 'actions');
|
package/dist/config/yaml.js
CHANGED
|
@@ -48,7 +48,13 @@ const logger_1 = require("../helpers/logger");
|
|
|
48
48
|
let configPath;
|
|
49
49
|
function getDefaultConfigPath() {
|
|
50
50
|
const home = process.env.HOME || os.homedir();
|
|
51
|
-
|
|
51
|
+
const userConfig = path.join(home, '.config', 'botforje', 'config.yml');
|
|
52
|
+
const systemConfig = '/etc/botforje/config.yml';
|
|
53
|
+
if (fsSync.existsSync(userConfig))
|
|
54
|
+
return userConfig;
|
|
55
|
+
if (fsSync.existsSync(systemConfig))
|
|
56
|
+
return systemConfig;
|
|
57
|
+
return userConfig;
|
|
52
58
|
}
|
|
53
59
|
function getConfigPath() {
|
|
54
60
|
return configPath || getDefaultConfigPath();
|
package/dist/helpers/logger.js
CHANGED
|
@@ -9,9 +9,6 @@ exports.getLogger = getLogger;
|
|
|
9
9
|
exports.setGlobalLogger = setGlobalLogger;
|
|
10
10
|
const pino_1 = __importDefault(require("pino"));
|
|
11
11
|
function getCallerInfo() {
|
|
12
|
-
const isDevelopment = process.env.NODE_ENV !== 'production';
|
|
13
|
-
if (!isDevelopment)
|
|
14
|
-
return undefined;
|
|
15
12
|
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
16
13
|
try {
|
|
17
14
|
const err = new Error();
|
|
@@ -38,10 +35,10 @@ function getCallerInfo() {
|
|
|
38
35
|
return undefined;
|
|
39
36
|
}
|
|
40
37
|
function createLogger(logLevel = 'info') {
|
|
41
|
-
const
|
|
38
|
+
const usePretty = process.stdout.isTTY;
|
|
42
39
|
const logger = (0, pino_1.default)({
|
|
43
40
|
level: logLevel,
|
|
44
|
-
transport:
|
|
41
|
+
transport: usePretty
|
|
45
42
|
? {
|
|
46
43
|
target: 'pino-pretty',
|
|
47
44
|
options: {
|
package/dist/whatsapp/client.js
CHANGED
|
@@ -97,9 +97,13 @@ class WhatsAppChannel {
|
|
|
97
97
|
this.stateChangeHandlers = [];
|
|
98
98
|
this.authRequiredHandlers = [];
|
|
99
99
|
this.isConnected = false;
|
|
100
|
+
this.dedupCache = new Set();
|
|
100
101
|
this.channelId = clientId;
|
|
101
102
|
this.client = new whatsapp_web_js_1.Client(getClientOptions(clientId));
|
|
102
103
|
this.setupEventListeners();
|
|
104
|
+
this.dedupInterval = setInterval(() => {
|
|
105
|
+
this.dedupCache.clear();
|
|
106
|
+
}, 30000);
|
|
103
107
|
}
|
|
104
108
|
get logger() {
|
|
105
109
|
return (0, logger_1.getLogger)();
|
|
@@ -113,6 +117,12 @@ class WhatsAppChannel {
|
|
|
113
117
|
this.readyHandlers.forEach(handler => handler());
|
|
114
118
|
});
|
|
115
119
|
this.client.on('message', async (msg) => {
|
|
120
|
+
const rawId = msg.id._serialized;
|
|
121
|
+
if (this.dedupCache.has(rawId)) {
|
|
122
|
+
this.logger.debug(`Skipping duplicate message ${rawId}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.dedupCache.add(rawId);
|
|
116
126
|
const domainMessage = (0, whatsapp_1.toDomainMessage)(msg);
|
|
117
127
|
if (msg.from.includes('@lid')) {
|
|
118
128
|
try {
|
|
@@ -184,6 +194,9 @@ class WhatsAppChannel {
|
|
|
184
194
|
await this.client.initialize();
|
|
185
195
|
}
|
|
186
196
|
async disconnect() {
|
|
197
|
+
if (this.dedupInterval) {
|
|
198
|
+
clearInterval(this.dedupInterval);
|
|
199
|
+
}
|
|
187
200
|
if (this.client) {
|
|
188
201
|
await this.client.destroy();
|
|
189
202
|
this.isConnected = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botforje",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Create powerful WhatsApp bots with YAML configuration",
|
|
5
5
|
"jest": {
|
|
6
6
|
"preset": "ts-jest",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@types/express": "^5.0.3",
|
|
61
|
+
"botforje": "link:",
|
|
61
62
|
"commander": "^12.1.0",
|
|
62
63
|
"express": "^5.1.0",
|
|
63
64
|
"fuse.js": "^7.4.2",
|
|
@@ -65,8 +66,7 @@
|
|
|
65
66
|
"pino": "^10.0.0",
|
|
66
67
|
"pino-pretty": "^13.1.2",
|
|
67
68
|
"qrcode-terminal": "^0.12.0",
|
|
68
|
-
"whatsapp-web.js": "^1.34.7"
|
|
69
|
-
"botforje": "link:"
|
|
69
|
+
"whatsapp-web.js": "^1.34.7"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
72
|
"node": ">=22.13.0"
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,45 +1,142 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const BANNER = `
|
|
8
|
+
██████ ██████ ████████ ███████ ██████ ██████ ██ ███████
|
|
9
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██
|
|
10
|
+
██████ ██ ██ ██ █████ ██ ██ ██████ ██ █████
|
|
11
|
+
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
12
|
+
██████ ██████ ██ ██ ██████ ██ ██ ██ ███████
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
console.log(BANNER);
|
|
16
|
+
console.log(` Botforje v${require('../package.json').version} installed!\n`);
|
|
17
|
+
|
|
18
|
+
const scriptDir = __dirname;
|
|
19
|
+
const templatePath = path.join(scriptDir, '..', 'service', 'botforje.service.template');
|
|
20
|
+
const exampleConfigPath = path.join(scriptDir, '..', 'config.example.yml');
|
|
21
|
+
|
|
22
|
+
function renderServiceFile(template, vars) {
|
|
23
|
+
return Object.entries(vars).reduce(
|
|
24
|
+
(content, [key, value]) => content.replace(new RegExp(`{{${key}}}`, 'g'), value),
|
|
25
|
+
template
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function setupSystemd() {
|
|
30
|
+
if (os.platform() !== 'linux') return 'no-systemd';
|
|
8
31
|
|
|
9
|
-
function hasSystemd() {
|
|
10
|
-
if (os.platform() !== 'linux') return false;
|
|
11
32
|
try {
|
|
12
|
-
execSync('systemctl --version', { stdio: 'ignore' });
|
|
13
|
-
return true;
|
|
33
|
+
require('child_process').execSync('systemctl --version', { stdio: 'ignore' });
|
|
14
34
|
} catch {
|
|
15
|
-
return
|
|
35
|
+
return 'no-systemd';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isRoot = process.getuid && process.getuid() === 0;
|
|
39
|
+
const homeDir = os.homedir();
|
|
40
|
+
const nodePath = process.execPath;
|
|
41
|
+
|
|
42
|
+
const nodeModulesDir = path.dirname(path.dirname(scriptDir));
|
|
43
|
+
const pkgDir = path.resolve(scriptDir, '..');
|
|
44
|
+
const scriptPath = path.join(pkgDir, 'dist', 'cli.js');
|
|
45
|
+
|
|
46
|
+
if (!fs.existsSync(scriptPath)) {
|
|
47
|
+
console.log(' (dist/cli.js not found — run "pnpm build" first for dev mode)\n');
|
|
48
|
+
return 'dev-mode';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let serviceDir, configDir, scopeLabel, wantedBy;
|
|
52
|
+
|
|
53
|
+
if (isRoot) {
|
|
54
|
+
serviceDir = '/etc/systemd/system';
|
|
55
|
+
configDir = '/etc/botforje';
|
|
56
|
+
scopeLabel = 'system';
|
|
57
|
+
wantedBy = 'multi-user.target';
|
|
58
|
+
} else {
|
|
59
|
+
serviceDir = path.join(homeDir, '.config', 'systemd', 'user');
|
|
60
|
+
configDir = path.join(homeDir, '.config', 'botforje');
|
|
61
|
+
scopeLabel = 'user';
|
|
62
|
+
wantedBy = 'default.target';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Create config directory
|
|
66
|
+
if (!fs.existsSync(configDir)) {
|
|
67
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Copy example config (commented) if config.yml doesn't exist
|
|
71
|
+
const targetConfigPath = path.join(configDir, 'config.yml');
|
|
72
|
+
if (fs.existsSync(exampleConfigPath) && !fs.existsSync(targetConfigPath)) {
|
|
73
|
+
const exampleContent = fs.readFileSync(exampleConfigPath, 'utf8');
|
|
74
|
+
const commentedContent = exampleContent
|
|
75
|
+
.split('\n')
|
|
76
|
+
.map(line => line.trim() ? `# ${line}` : line)
|
|
77
|
+
.join('\n');
|
|
78
|
+
fs.writeFileSync(targetConfigPath, commentedContent);
|
|
16
79
|
}
|
|
80
|
+
|
|
81
|
+
// Create service file
|
|
82
|
+
if (!fs.existsSync(serviceDir)) {
|
|
83
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
87
|
+
const serviceContent = renderServiceFile(template, {
|
|
88
|
+
NODE: nodePath,
|
|
89
|
+
SCRIPT: scriptPath,
|
|
90
|
+
WANTED_BY: wantedBy,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const servicePath = path.join(serviceDir, 'botforje.service');
|
|
94
|
+
fs.writeFileSync(servicePath, serviceContent);
|
|
95
|
+
|
|
96
|
+
return { scope: scopeLabel, configDir, servicePath, isRoot, homeDir };
|
|
17
97
|
}
|
|
18
98
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.log('
|
|
32
|
-
console.log('
|
|
33
|
-
console.log('
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
console.log(
|
|
42
|
-
console.log(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
99
|
+
async function main() {
|
|
100
|
+
const result = await setupSystemd();
|
|
101
|
+
|
|
102
|
+
if (result === 'no-systemd') {
|
|
103
|
+
console.log(' (systemd not detected — run manually)\n');
|
|
104
|
+
console.log(' ── Next steps ──\n');
|
|
105
|
+
console.log(' Start daemon: botforje daemon');
|
|
106
|
+
console.log(' Authenticate: botforje auth <botId>\n');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (result === 'dev-mode') {
|
|
111
|
+
console.log(' (dev mode — systemd service skipped)\n');
|
|
112
|
+
console.log(' ── Next steps ──\n');
|
|
113
|
+
console.log(' Build: pnpm build');
|
|
114
|
+
console.log(' Test daemon: pnpm start daemon\n');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { scope, configDir, servicePath, isRoot } = result;
|
|
119
|
+
const scopeFlag = scope === 'user' ? '--user ' : '';
|
|
120
|
+
|
|
121
|
+
console.log(` Service file: ${servicePath}`);
|
|
122
|
+
console.log(` Config dir: ${configDir}\n`);
|
|
123
|
+
|
|
124
|
+
if (scope === 'system') {
|
|
125
|
+
console.log(` ── Next steps ──\n`);
|
|
126
|
+
console.log(` Edit config: ${configDir}/config.yml`);
|
|
127
|
+
console.log(` Start service: systemctl enable --now botforje`);
|
|
128
|
+
console.log(` View logs: journalctl -u botforje -f`);
|
|
129
|
+
console.log(` Authenticate: sudo botforje auth <botId>\n`);
|
|
130
|
+
} else {
|
|
131
|
+
console.log(` ── Next steps ──\n`);
|
|
132
|
+
console.log(` Edit config: ${configDir}/config.yml`);
|
|
133
|
+
console.log(` Start service: systemctl --user enable --now botforje`);
|
|
134
|
+
console.log(` View logs: journalctl --user -u botforje -f`);
|
|
135
|
+
console.log(` Authenticate: botforje auth <botId>\n`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main().catch(err => {
|
|
140
|
+
console.error('postinstall error:', err.message);
|
|
141
|
+
process.exit(0);
|
|
142
|
+
});
|
package/scripts/preuninstall.js
CHANGED
|
@@ -8,41 +8,44 @@ const os = require('os');
|
|
|
8
8
|
function uninstallService() {
|
|
9
9
|
if (os.platform() !== 'linux') return;
|
|
10
10
|
|
|
11
|
+
const isRoot = process.getuid && process.getuid() === 0;
|
|
12
|
+
const homeDir = os.homedir();
|
|
13
|
+
|
|
14
|
+
const userServicePath = path.join(homeDir, '.config', 'systemd', 'user', 'botforje.service');
|
|
15
|
+
const userConfigDir = path.join(homeDir, '.config', 'botforje');
|
|
16
|
+
const systemServicePath = '/etc/systemd/system/botforje.service';
|
|
17
|
+
const systemConfigDir = '/etc/botforje';
|
|
18
|
+
|
|
19
|
+
const isUserService = fs.existsSync(userServicePath);
|
|
20
|
+
const isSystemService = fs.existsSync(systemServicePath);
|
|
21
|
+
|
|
22
|
+
if (!isUserService && !isSystemService) return;
|
|
23
|
+
|
|
24
|
+
const scopeFlag = isUserService ? '--user ' : '';
|
|
25
|
+
const scope = isUserService ? 'user' : 'system';
|
|
26
|
+
const servicePath = isUserService ? userServicePath : systemServicePath;
|
|
27
|
+
const configDir = isUserService ? userConfigDir : systemConfigDir;
|
|
28
|
+
|
|
29
|
+
console.log('\nUninstalling Botforje service...\n');
|
|
30
|
+
|
|
11
31
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!fs.existsSync(servicePath)) return;
|
|
17
|
-
|
|
18
|
-
console.log('\nUninstalling Botforje service...\n');
|
|
19
|
-
|
|
20
|
-
// Stop service first to prevent hanging
|
|
21
|
-
console.log('Stopping service...');
|
|
22
|
-
try {
|
|
23
|
-
execSync('systemctl --user stop botforje 2>/dev/null', { stdio: 'inherit', timeout: 10000 });
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.log('Service may not be running or failed to stop gracefully');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Disable service
|
|
29
|
-
try {
|
|
30
|
-
execSync('systemctl --user disable botforje 2>/dev/null', { stdio: 'ignore' });
|
|
31
|
-
} catch {}
|
|
32
|
-
|
|
33
|
-
// Eliminar archivo .service
|
|
34
|
-
fs.unlinkSync(servicePath);
|
|
35
|
-
console.log('Service removed');
|
|
36
|
-
|
|
37
|
-
// Recargar systemd
|
|
38
|
-
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
39
|
-
|
|
40
|
-
console.log(`\nConfiguration kept at: ${configDir}`);
|
|
41
|
-
console.log(` To remove: rm -rf ${configDir}\n`);
|
|
42
|
-
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error('Cleanup warning:', error.message);
|
|
32
|
+
console.log(' Stopping service...');
|
|
33
|
+
execSync(`systemctl ${scopeFlag}stop botforje 2>/dev/null`, { stdio: 'inherit', timeout: 10000 });
|
|
34
|
+
} catch {
|
|
35
|
+
console.log(' (service was not running)');
|
|
45
36
|
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
execSync(`systemctl ${scopeFlag}disable botforje 2>/dev/null`, { stdio: 'ignore' });
|
|
40
|
+
} catch {}
|
|
41
|
+
|
|
42
|
+
fs.unlinkSync(servicePath);
|
|
43
|
+
console.log(' Service file removed');
|
|
44
|
+
|
|
45
|
+
execSync(`systemctl ${scopeFlag}daemon-reload`, { stdio: 'ignore' });
|
|
46
|
+
|
|
47
|
+
console.log(`\n Config kept at: ${configDir}`);
|
|
48
|
+
console.log(` To remove: rm -rf ${configDir}\n`);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
uninstallService();
|
|
51
|
+
uninstallService();
|
|
@@ -5,21 +5,14 @@ Documentation=https://github.com/felixzsh/botforje
|
|
|
5
5
|
|
|
6
6
|
[Service]
|
|
7
7
|
Type=simple
|
|
8
|
-
|
|
9
|
-
ExecStart=xvfb-run -a {{NODE_PATH}} {{INSTALL_DIR}}/dist/cli.js daemon
|
|
8
|
+
ExecStart={{NODE}} {{SCRIPT}} daemon
|
|
10
9
|
Restart=on-failure
|
|
11
10
|
RestartSec=10
|
|
12
11
|
StandardOutput=journal
|
|
13
12
|
StandardError=journal
|
|
14
13
|
SyslogIdentifier=botforje
|
|
15
|
-
|
|
16
|
-
# Variables de entorno
|
|
17
|
-
Environment="NODE_ENV=production"
|
|
18
|
-
Environment="CONFIG_DIR={{CONFIG_DIR}}"
|
|
19
|
-
|
|
20
|
-
# Límites de seguridad
|
|
21
14
|
LimitNOFILE=65536
|
|
22
15
|
PrivateTmp=true
|
|
23
16
|
|
|
24
17
|
[Install]
|
|
25
|
-
WantedBy=
|
|
18
|
+
WantedBy={{WANTED_BY}}
|
package/scripts/setup-systemd.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { execSync } = require('child_process');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
function setupSystemd() {
|
|
9
|
-
try {
|
|
10
|
-
if (os.platform() !== 'linux') {
|
|
11
|
-
console.log('Systemd setup is only available on Linux');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const user = os.userInfo().username;
|
|
16
|
-
const homeDir = os.homedir();
|
|
17
|
-
const configDir = path.join(homeDir, '.config', 'botforje');
|
|
18
|
-
const systemdUserDir = path.join(homeDir, '.config', 'systemd', 'user');
|
|
19
|
-
|
|
20
|
-
const globalNpmRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
21
|
-
const scriptDir = path.dirname(__filename);
|
|
22
|
-
const projectRoot = path.resolve(scriptDir, '..');
|
|
23
|
-
|
|
24
|
-
// Detect mode: If project root is within global npm root, it's production
|
|
25
|
-
const isProduction = projectRoot.startsWith(globalNpmRoot);
|
|
26
|
-
const installDir = isProduction
|
|
27
|
-
? path.join(globalNpmRoot, 'botforje')
|
|
28
|
-
: projectRoot;
|
|
29
|
-
|
|
30
|
-
// For Node path, use 'which node' in both cases, but log a warning for dev
|
|
31
|
-
const nodePath = execSync('which node', { encoding: 'utf8' }).trim();
|
|
32
|
-
if (!isProduction) {
|
|
33
|
-
console.log('Development mode detected: Using local paths for testing');
|
|
34
|
-
console.log(` Install dir: ${installDir}`);
|
|
35
|
-
console.log(` Node path: ${nodePath}`);
|
|
36
|
-
console.log(' Note: Ensure dist/cli.js exists (run build first)');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.log('Setting up Botforje systemd service...');
|
|
40
|
-
|
|
41
|
-
if (!fs.existsSync(configDir)) {
|
|
42
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
43
|
-
console.log(`Created config directory: ${configDir}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Create example config file only if config.yml doesn't exist
|
|
47
|
-
const exampleConfigPath = path.join(__dirname, '..', 'config.example.yml');
|
|
48
|
-
const targetConfigPath = path.join(configDir, 'config.yml');
|
|
49
|
-
|
|
50
|
-
if (fs.existsSync(exampleConfigPath) && !fs.existsSync(targetConfigPath)) {
|
|
51
|
-
const exampleContent = fs.readFileSync(exampleConfigPath, 'utf8');
|
|
52
|
-
// Comment out all lines for the default config
|
|
53
|
-
const commentedContent = exampleContent
|
|
54
|
-
.split('\n')
|
|
55
|
-
.map(line => line.trim() ? `# ${line}` : line)
|
|
56
|
-
.join('\n');
|
|
57
|
-
fs.writeFileSync(targetConfigPath, commentedContent);
|
|
58
|
-
console.log(`Created example config file: ${targetConfigPath}`);
|
|
59
|
-
console.log(` Uncomment and edit this file to configure your bots`);
|
|
60
|
-
} else if (fs.existsSync(targetConfigPath)) {
|
|
61
|
-
console.log(`Config file already exists: ${targetConfigPath}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!fs.existsSync(systemdUserDir)) {
|
|
65
|
-
fs.mkdirSync(systemdUserDir, { recursive: true });
|
|
66
|
-
console.log(`Created systemd user directory: ${systemdUserDir}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const templatePath = path.join(__dirname, '..', 'service', 'botforje.service.template');
|
|
70
|
-
let serviceContent = fs.readFileSync(templatePath, 'utf8');
|
|
71
|
-
|
|
72
|
-
serviceContent = serviceContent
|
|
73
|
-
.replace(/{{USER}}/g, user)
|
|
74
|
-
.replace(/{{INSTALL_DIR}}/g, installDir)
|
|
75
|
-
.replace(/{{NODE_PATH}}/g, nodePath)
|
|
76
|
-
.replace(/{{CONFIG_DIR}}/g, configDir);
|
|
77
|
-
|
|
78
|
-
const servicePath = path.join(systemdUserDir, 'botforje.service');
|
|
79
|
-
fs.writeFileSync(servicePath, serviceContent);
|
|
80
|
-
console.log(`Created service file: ${servicePath}`);
|
|
81
|
-
|
|
82
|
-
execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
|
|
83
|
-
console.log('Reloaded systemd daemon\n');
|
|
84
|
-
|
|
85
|
-
} catch (error) {
|
|
86
|
-
console.error('Error setting up systemd service:', error.message);
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setupSystemd();
|