@logboard/cli 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/.env.example +37 -0
- package/README.md +200 -0
- package/bin/logboard +536 -0
- package/client/logger.js +309 -0
- package/config/index.js +142 -0
- package/config.js +2 -0
- package/controllers/AnalyticsController.js +46 -0
- package/controllers/ApiAnalyticsController.js +129 -0
- package/controllers/ApiKeyController.js +58 -0
- package/controllers/AuthController.js +131 -0
- package/controllers/HealthController.js +56 -0
- package/controllers/LogController.js +197 -0
- package/controllers/OrgController.js +152 -0
- package/controllers/RoleConfigController.js +20 -0
- package/controllers/SettingsController.js +39 -0
- package/controllers/StreamController.js +55 -0
- package/controllers/UiController.js +789 -0
- package/controllers/UserController.js +79 -0
- package/lib/batchWriter.js +57 -0
- package/lib/cleanup.js +67 -0
- package/lib/ejs.js +103 -0
- package/lib/emitter.js +5 -0
- package/lib/healthMonitor.js +245 -0
- package/lib/logger.js +21 -0
- package/lib/streams.js +32 -0
- package/lib/theme.js +77 -0
- package/lib/userStore.js +13 -0
- package/lib/utils.js +44 -0
- package/middleware/apiKey.js +82 -0
- package/middleware/auth.js +55 -0
- package/middleware/ipWhitelist.js +59 -0
- package/middleware/org.js +85 -0
- package/middleware/pageAccess.js +20 -0
- package/middleware/rateLimit.js +29 -0
- package/middleware/roles.js +11 -0
- package/package.json +77 -0
- package/routes/alerts.js +18 -0
- package/routes/analytics.js +26 -0
- package/routes/api-analytics.js +30 -0
- package/routes/api-keys.js +12 -0
- package/routes/archive.js +91 -0
- package/routes/audit.js +50 -0
- package/routes/auth.js +22 -0
- package/routes/bookmarks.js +13 -0
- package/routes/health.js +11 -0
- package/routes/logs.js +88 -0
- package/routes/metrics.js +66 -0
- package/routes/notifications.js +14 -0
- package/routes/orgs.js +98 -0
- package/routes/registration.js +202 -0
- package/routes/role-config.js +97 -0
- package/routes/saved-searches.js +12 -0
- package/routes/server.js +151 -0
- package/routes/settings.js +28 -0
- package/routes/status.js +21 -0
- package/routes/stream.js +11 -0
- package/routes/super.js +129 -0
- package/routes/ui.js +120 -0
- package/routes/users.js +13 -0
- package/server.js +172 -0
- package/services/AlertRulesService.js +323 -0
- package/services/AnalyticsService.js +665 -0
- package/services/ApiAnalyticsService.js +471 -0
- package/services/ApiKeyService.js +166 -0
- package/services/AuditService.js +249 -0
- package/services/AuthService.js +234 -0
- package/services/BookmarkService.js +49 -0
- package/services/GlobalSettingsService.js +44 -0
- package/services/LogService.js +1066 -0
- package/services/MetricsService.js +116 -0
- package/services/NotificationService.js +70 -0
- package/services/OrgService.js +217 -0
- package/services/ReportService.js +247 -0
- package/services/RoleConfigService.js +201 -0
- package/services/SavedSearchService.js +63 -0
- package/services/SettingsService.js +220 -0
- package/services/UserService.js +121 -0
- package/setup.js +132 -0
- package/views/404.ejs +8 -0
- package/views/alerts.ejs +190 -0
- package/views/analytics.ejs +209 -0
- package/views/api-analytics.ejs +660 -0
- package/views/api-keys.ejs +150 -0
- package/views/archive.ejs +123 -0
- package/views/audit.ejs +314 -0
- package/views/bookmarks.ejs +54 -0
- package/views/custom-dashboard.ejs +162 -0
- package/views/dashboard.ejs +186 -0
- package/views/diff.ejs +98 -0
- package/views/health.ejs +269 -0
- package/views/heatmap.ejs +126 -0
- package/views/insights.ejs +334 -0
- package/views/invite.ejs +74 -0
- package/views/live.ejs +299 -0
- package/views/login.ejs +64 -0
- package/views/logo.png +0 -0
- package/views/logs.ejs +754 -0
- package/views/notifications.ejs +58 -0
- package/views/partials/head.ejs +282 -0
- package/views/partials/sidebar.ejs +168 -0
- package/views/register.ejs +100 -0
- package/views/roles.ejs +279 -0
- package/views/saved-searches.ejs +51 -0
- package/views/service-map.ejs +142 -0
- package/views/settings.ejs +1159 -0
- package/views/sidebar.ejs +129 -0
- package/views/status.ejs +100 -0
- package/views/super-admin-admins.ejs +58 -0
- package/views/super-admin-analytics.ejs +49 -0
- package/views/super-admin-orgs.ejs +310 -0
- package/views/super-admin-profile.ejs +77 -0
- package/views/super-admin-settings.ejs +108 -0
- package/views/super-admin-system.ejs +46 -0
- package/views/users.ejs +153 -0
package/bin/logboard
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LogBoard CLI
|
|
6
|
+
* Usage:
|
|
7
|
+
* logboard start Start server in foreground
|
|
8
|
+
* logboard start --port 9900 --data ./data
|
|
9
|
+
* logboard stop Stop background service
|
|
10
|
+
* logboard restart Restart service
|
|
11
|
+
* logboard status Show running status + version
|
|
12
|
+
* logboard setup First-run: create data/, .env, default users
|
|
13
|
+
* logboard service install Register as OS service (auto-start on boot)
|
|
14
|
+
* logboard service uninstall Remove OS service
|
|
15
|
+
* logboard service start Start OS service
|
|
16
|
+
* logboard service stop Stop OS service
|
|
17
|
+
* logboard service logs Tail service logs
|
|
18
|
+
* logboard open Open LogBoard in default browser
|
|
19
|
+
* logboard --version / -v
|
|
20
|
+
* logboard --help / -h
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const os = require('os');
|
|
26
|
+
const { execSync, spawn, spawnSync } = require('child_process');
|
|
27
|
+
|
|
28
|
+
// ── Paths ─────────────────────────────────────────────────────────────────
|
|
29
|
+
const BLORQ_DIR = path.resolve(__dirname, '..'); // root of logboard install
|
|
30
|
+
const PKG = require(path.join(BLORQ_DIR, 'package.json'));
|
|
31
|
+
const VERSION = PKG.version;
|
|
32
|
+
const PID_FILE = path.join(os.homedir(), '.logboard', 'logboard.pid');
|
|
33
|
+
const LOG_FILE = path.join(os.homedir(), '.logboard', 'logboard.log');
|
|
34
|
+
const SERVICE_ID = 'dev.logboard.server';
|
|
35
|
+
|
|
36
|
+
// Colours
|
|
37
|
+
const C = {
|
|
38
|
+
reset: '\x1b[0m',
|
|
39
|
+
bold: '\x1b[1m',
|
|
40
|
+
dim: '\x1b[2m',
|
|
41
|
+
red: '\x1b[31m',
|
|
42
|
+
green: '\x1b[32m',
|
|
43
|
+
yellow: '\x1b[33m',
|
|
44
|
+
blue: '\x1b[34m',
|
|
45
|
+
cyan: '\x1b[36m',
|
|
46
|
+
white: '\x1b[37m',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function c(color, text) { return C[color] + text + C.reset; }
|
|
50
|
+
function ok(msg) { console.log(c('green',' ✓ ') + msg); }
|
|
51
|
+
function info(msg) { console.log(c('blue', ' → ') + msg); }
|
|
52
|
+
function warn(msg) { console.log(c('yellow',' ⚠ ') + msg); }
|
|
53
|
+
function fail(msg) { console.error(c('red', ' ✗ ') + msg); }
|
|
54
|
+
function title(msg) { console.log('\n' + c('bold', c('cyan', msg)) + '\n'); }
|
|
55
|
+
|
|
56
|
+
function ensureDir(d) {
|
|
57
|
+
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Arg parsing ───────────────────────────────────────────────────────────
|
|
61
|
+
const args = process.argv.slice(2);
|
|
62
|
+
const command = args[0] || 'help';
|
|
63
|
+
const subCmd = args[1] || '';
|
|
64
|
+
|
|
65
|
+
function getFlag(flag, defaultVal) {
|
|
66
|
+
const i = args.indexOf(flag);
|
|
67
|
+
return i !== -1 && args[i + 1] ? args[i + 1] : defaultVal;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasFlag(flag) { return args.includes(flag); }
|
|
71
|
+
|
|
72
|
+
// ── Banner ────────────────────────────────────────────────────────────────
|
|
73
|
+
function banner() {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(c('bold', c('cyan', ' ██████╗ ██╗ ██████╗ ██████╗ ██████╗ ')));
|
|
76
|
+
console.log(c('bold', c('cyan', ' ██╔══██╗██║ ██╔═══██╗██╔══██╗██╔═══██╗')));
|
|
77
|
+
console.log(c('bold', c('cyan', ' ██████╔╝██║ ██║ ██║██████╔╝██║ ██║')));
|
|
78
|
+
console.log(c('bold', c('cyan', ' ██╔══██╗██║ ██║ ██║██╔══██╗██║▄▄ ██║')));
|
|
79
|
+
console.log(c('bold', c('cyan', ' ██████╔╝███████╗╚██████╔╝██║ ██║╚██████╔╝')));
|
|
80
|
+
console.log(c('bold', c('cyan', ' ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══▀▀═╝ ')));
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(c('dim', ' Production-grade log aggregator ') + c('cyan', 'v' + VERSION));
|
|
83
|
+
console.log('');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── PID helpers ───────────────────────────────────────────────────────────
|
|
87
|
+
function readPid() {
|
|
88
|
+
try { return parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10) || null; }
|
|
89
|
+
catch { return null; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isRunning(pid) {
|
|
93
|
+
if (!pid) return false;
|
|
94
|
+
try { process.kill(pid, 0); return true; }
|
|
95
|
+
catch { return false; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writePid(pid) {
|
|
99
|
+
ensureDir(path.dirname(PID_FILE));
|
|
100
|
+
fs.writeFileSync(PID_FILE, String(pid), 'utf8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function clearPid() {
|
|
104
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Platform detection ────────────────────────────────────────────────────
|
|
108
|
+
const PLATFORM = process.platform; // 'darwin' | 'linux' | 'win32'
|
|
109
|
+
|
|
110
|
+
// ── Commands ──────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
// ─── start ────────────────────────────────────────────────────────────────
|
|
113
|
+
function cmdStart() {
|
|
114
|
+
banner();
|
|
115
|
+
const port = getFlag('--port', process.env.PORT || '9900');
|
|
116
|
+
const dataDir = getFlag('--data', process.env.DATA_DIR || path.join(BLORQ_DIR, 'data'));
|
|
117
|
+
const bg = hasFlag('--background') || hasFlag('-b');
|
|
118
|
+
|
|
119
|
+
// Ensure data dir + setup
|
|
120
|
+
ensureDir(dataDir);
|
|
121
|
+
const usersFile = path.join(dataDir, 'users.json');
|
|
122
|
+
if (!fs.existsSync(usersFile)) {
|
|
123
|
+
info('No data found — running first-time setup…');
|
|
124
|
+
spawnSync(process.execPath, [path.join(BLORQ_DIR, 'setup.js')], { stdio: 'inherit', cwd: BLORQ_DIR });
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (bg) {
|
|
129
|
+
// Background mode — detach process
|
|
130
|
+
const pid = readPid();
|
|
131
|
+
if (isRunning(pid)) {
|
|
132
|
+
fail('LogBoard is already running (PID ' + pid + ')');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
ensureDir(path.dirname(LOG_FILE));
|
|
136
|
+
const out = fs.openSync(LOG_FILE, 'a');
|
|
137
|
+
const child = spawn(process.execPath, [path.join(BLORQ_DIR, 'server.js')], {
|
|
138
|
+
detached: true,
|
|
139
|
+
stdio: ['ignore', out, out],
|
|
140
|
+
cwd: BLORQ_DIR,
|
|
141
|
+
env: { ...process.env, PORT: port, DATA_DIR: dataDir },
|
|
142
|
+
});
|
|
143
|
+
child.unref();
|
|
144
|
+
writePid(child.pid);
|
|
145
|
+
ok('LogBoard started in background (PID ' + child.pid + ')');
|
|
146
|
+
ok('URL: ' + c('cyan', 'http://localhost:' + port));
|
|
147
|
+
info('Logs: ' + LOG_FILE);
|
|
148
|
+
info('Stop: logboard stop');
|
|
149
|
+
console.log('');
|
|
150
|
+
} else {
|
|
151
|
+
// Foreground
|
|
152
|
+
info('Starting LogBoard on ' + c('cyan', 'http://localhost:' + port));
|
|
153
|
+
info('Press Ctrl+C to stop\n');
|
|
154
|
+
const child = spawn(process.execPath, [path.join(BLORQ_DIR, 'server.js')], {
|
|
155
|
+
stdio: 'inherit',
|
|
156
|
+
cwd: BLORQ_DIR,
|
|
157
|
+
env: { ...process.env, PORT: port, DATA_DIR: dataDir },
|
|
158
|
+
});
|
|
159
|
+
child.on('exit', code => process.exit(code || 0));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ─── stop ─────────────────────────────────────────────────────────────────
|
|
164
|
+
function cmdStop() {
|
|
165
|
+
const pid = readPid();
|
|
166
|
+
if (!isRunning(pid)) {
|
|
167
|
+
warn('LogBoard is not running');
|
|
168
|
+
clearPid();
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
process.kill(pid, 'SIGTERM');
|
|
173
|
+
ok('Stopped LogBoard (PID ' + pid + ')');
|
|
174
|
+
clearPid();
|
|
175
|
+
} catch (e) {
|
|
176
|
+
fail('Could not stop LogBoard: ' + e.message);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── restart ──────────────────────────────────────────────────────────────
|
|
182
|
+
function cmdRestart() {
|
|
183
|
+
const pid = readPid();
|
|
184
|
+
if (isRunning(pid)) {
|
|
185
|
+
info('Stopping current instance…');
|
|
186
|
+
try { process.kill(pid, 'SIGTERM'); clearPid(); } catch {}
|
|
187
|
+
// Wait briefly for clean exit
|
|
188
|
+
const deadline = Date.now() + 3000;
|
|
189
|
+
while (isRunning(pid) && Date.now() < deadline) { /* spin */ }
|
|
190
|
+
}
|
|
191
|
+
// Start fresh
|
|
192
|
+
info('Restarting…\n');
|
|
193
|
+
args.push('--background');
|
|
194
|
+
cmdStart();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── status ───────────────────────────────────────────────────────────────
|
|
198
|
+
function cmdStatus() {
|
|
199
|
+
const pid = readPid();
|
|
200
|
+
const port = process.env.PORT || '9900';
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(c('bold', ' LogBoard') + ' ' + c('dim', 'v' + VERSION));
|
|
203
|
+
console.log('');
|
|
204
|
+
if (isRunning(pid)) {
|
|
205
|
+
console.log(' Status ' + c('green', '● running') + ' ' + c('dim', 'PID ' + pid));
|
|
206
|
+
console.log(' URL ' + c('cyan', 'http://localhost:' + port));
|
|
207
|
+
console.log(' Logs ' + c('dim', LOG_FILE));
|
|
208
|
+
} else {
|
|
209
|
+
console.log(' Status ' + c('red', '● stopped'));
|
|
210
|
+
console.log(' Run: ' + c('cyan', 'logboard start'));
|
|
211
|
+
}
|
|
212
|
+
console.log(' Data ' + c('dim', process.env.DATA_DIR || path.join(BLORQ_DIR, 'data')));
|
|
213
|
+
console.log(' Install ' + c('dim', BLORQ_DIR));
|
|
214
|
+
console.log(' Platform ' + c('dim', PLATFORM + ' ' + os.arch()));
|
|
215
|
+
console.log('');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ─── open ─────────────────────────────────────────────────────────────────
|
|
219
|
+
function cmdOpen() {
|
|
220
|
+
const port = process.env.PORT || '9900';
|
|
221
|
+
const url = 'http://localhost:' + port;
|
|
222
|
+
const pid = readPid();
|
|
223
|
+
if (!isRunning(pid)) {
|
|
224
|
+
warn('LogBoard may not be running. Open anyway: ' + c('cyan', url));
|
|
225
|
+
}
|
|
226
|
+
info('Opening ' + url);
|
|
227
|
+
const open = PLATFORM === 'win32' ? 'start' : PLATFORM === 'darwin' ? 'open' : 'xdg-open';
|
|
228
|
+
try { execSync(open + ' ' + url, { stdio: 'ignore' }); }
|
|
229
|
+
catch { info('Open manually: ' + c('cyan', url)); }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── service ──────────────────────────────────────────────────────────────
|
|
233
|
+
function cmdService() {
|
|
234
|
+
const action = subCmd || 'help';
|
|
235
|
+
switch (action) {
|
|
236
|
+
case 'install': return serviceInstall();
|
|
237
|
+
case 'uninstall': return serviceUninstall();
|
|
238
|
+
case 'start': return serviceStart();
|
|
239
|
+
case 'stop': return serviceStop();
|
|
240
|
+
case 'restart': return serviceRestart();
|
|
241
|
+
case 'logs': return serviceLogs();
|
|
242
|
+
default:
|
|
243
|
+
console.log('\nUsage: logboard service <install|uninstall|start|stop|restart|logs>\n');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── macOS launchd ─────────────────────────────────────────────────────────
|
|
248
|
+
function plistPath() {
|
|
249
|
+
return path.join(os.homedir(), 'Library', 'LaunchAgents', SERVICE_ID + '.plist');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function writePlist(port, dataDir) {
|
|
253
|
+
const nodePath = process.execPath;
|
|
254
|
+
const serverPath = path.join(BLORQ_DIR, 'server.js');
|
|
255
|
+
const logOut = path.join(os.homedir(), '.logboard', 'stdout.log');
|
|
256
|
+
const logErr = path.join(os.homedir(), '.logboard', 'stderr.log');
|
|
257
|
+
ensureDir(path.join(os.homedir(), '.logboard'));
|
|
258
|
+
|
|
259
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
260
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
261
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
262
|
+
<plist version="1.0">
|
|
263
|
+
<dict>
|
|
264
|
+
<key>Label</key> <string>${SERVICE_ID}</string>
|
|
265
|
+
<key>ProgramArguments</key>
|
|
266
|
+
<array>
|
|
267
|
+
<string>${nodePath}</string>
|
|
268
|
+
<string>${serverPath}</string>
|
|
269
|
+
</array>
|
|
270
|
+
<key>EnvironmentVariables</key>
|
|
271
|
+
<dict>
|
|
272
|
+
<key>PORT</key> <string>${port}</string>
|
|
273
|
+
<key>DATA_DIR</key> <string>${dataDir}</string>
|
|
274
|
+
<key>NODE_ENV</key> <string>production</string>
|
|
275
|
+
</dict>
|
|
276
|
+
<key>WorkingDirectory</key> <string>${BLORQ_DIR}</string>
|
|
277
|
+
<key>RunAtLoad</key> <true/>
|
|
278
|
+
<key>KeepAlive</key> <true/>
|
|
279
|
+
<key>StandardOutPath</key> <string>${logOut}</string>
|
|
280
|
+
<key>StandardErrorPath</key> <string>${logErr}</string>
|
|
281
|
+
<key>ProcessType</key> <string>Background</string>
|
|
282
|
+
</dict>
|
|
283
|
+
</plist>`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── Linux systemd ─────────────────────────────────────────────────────────
|
|
287
|
+
function systemdPath() { return path.join(os.homedir(), '.config', 'systemd', 'user', 'logboard.service'); }
|
|
288
|
+
function systemdPathSys() { return '/etc/systemd/system/logboard.service'; } // root install
|
|
289
|
+
|
|
290
|
+
function writeSystemd(port, dataDir) {
|
|
291
|
+
const nodePath = process.execPath;
|
|
292
|
+
const serverPath = path.join(BLORQ_DIR, 'server.js');
|
|
293
|
+
const logDir = path.join(os.homedir(), '.logboard');
|
|
294
|
+
ensureDir(logDir);
|
|
295
|
+
|
|
296
|
+
return `[Unit]
|
|
297
|
+
Description=LogBoard Log Aggregator v${VERSION}
|
|
298
|
+
After=network.target
|
|
299
|
+
|
|
300
|
+
[Service]
|
|
301
|
+
Type=simple
|
|
302
|
+
User=${os.userInfo().username}
|
|
303
|
+
WorkingDirectory=${BLORQ_DIR}
|
|
304
|
+
ExecStart=${nodePath} ${serverPath}
|
|
305
|
+
Restart=on-failure
|
|
306
|
+
RestartSec=5s
|
|
307
|
+
Environment=PORT=${port}
|
|
308
|
+
Environment=DATA_DIR=${dataDir}
|
|
309
|
+
Environment=NODE_ENV=production
|
|
310
|
+
StandardOutput=journal
|
|
311
|
+
StandardError=journal
|
|
312
|
+
SyslogIdentifier=logboard
|
|
313
|
+
|
|
314
|
+
[Install]
|
|
315
|
+
WantedBy=default.target
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Windows NSSM / sc ────────────────────────────────────────────────────
|
|
320
|
+
function writeWindowsScript(port, dataDir) {
|
|
321
|
+
const nodePath = process.execPath;
|
|
322
|
+
const serverPath = path.join(BLORQ_DIR, 'server.js');
|
|
323
|
+
// Returns a batch script that installs via sc.exe
|
|
324
|
+
return `@echo off
|
|
325
|
+
echo Installing LogBoard as Windows service...
|
|
326
|
+
sc create LogBoard binPath= "${nodePath} ${serverPath}" start= auto DisplayName= "LogBoard Log Aggregator"
|
|
327
|
+
sc description LogBoard "LogBoard production-grade log aggregator"
|
|
328
|
+
sc start LogBoard
|
|
329
|
+
echo Done. LogBoard will start automatically on login.
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function serviceInstall() {
|
|
334
|
+
const port = getFlag('--port', process.env.PORT || '9900');
|
|
335
|
+
const dataDir = getFlag('--data', path.join(BLORQ_DIR, 'data'));
|
|
336
|
+
ensureDir(dataDir);
|
|
337
|
+
|
|
338
|
+
title('Installing LogBoard as a system service');
|
|
339
|
+
|
|
340
|
+
if (PLATFORM === 'darwin') {
|
|
341
|
+
const plist = plistPath();
|
|
342
|
+
ensureDir(path.dirname(plist));
|
|
343
|
+
fs.writeFileSync(plist, writePlist(port, dataDir), 'utf8');
|
|
344
|
+
try {
|
|
345
|
+
// Unload first (in case it already exists)
|
|
346
|
+
try { execSync('launchctl unload "' + plist + '" 2>/dev/null', { stdio: 'ignore' }); } catch {}
|
|
347
|
+
execSync('launchctl load "' + plist + '"');
|
|
348
|
+
ok('Service registered with launchd');
|
|
349
|
+
ok('LogBoard will start automatically on login');
|
|
350
|
+
ok('URL: ' + c('cyan', 'http://localhost:' + port));
|
|
351
|
+
console.log('');
|
|
352
|
+
info('Control: logboard service start|stop|restart|logs');
|
|
353
|
+
console.log('');
|
|
354
|
+
} catch (e) {
|
|
355
|
+
warn('launchctl failed: ' + e.message);
|
|
356
|
+
info('Plist written to: ' + plist);
|
|
357
|
+
info('Load manually: launchctl load "' + plist + '"');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
} else if (PLATFORM === 'linux') {
|
|
361
|
+
const svcPath = systemdPath();
|
|
362
|
+
ensureDir(path.dirname(svcPath));
|
|
363
|
+
fs.writeFileSync(svcPath, writeSystemd(port, dataDir), 'utf8');
|
|
364
|
+
try {
|
|
365
|
+
execSync('systemctl --user daemon-reload');
|
|
366
|
+
execSync('systemctl --user enable logboard.service');
|
|
367
|
+
execSync('systemctl --user start logboard.service');
|
|
368
|
+
ok('Service enabled and started via systemd (user)');
|
|
369
|
+
ok('LogBoard starts automatically on login');
|
|
370
|
+
ok('URL: ' + c('cyan', 'http://localhost:' + port));
|
|
371
|
+
console.log('');
|
|
372
|
+
info('Logs: journalctl --user -u logboard -f');
|
|
373
|
+
info('Control: logboard service start|stop|restart|logs');
|
|
374
|
+
console.log('');
|
|
375
|
+
} catch (e) {
|
|
376
|
+
warn('systemctl failed: ' + e.message);
|
|
377
|
+
info('Unit file: ' + svcPath);
|
|
378
|
+
info('Try: systemctl --user daemon-reload && systemctl --user enable --now logboard');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
} else if (PLATFORM === 'win32') {
|
|
382
|
+
const batchPath = path.join(BLORQ_DIR, 'install-service.bat');
|
|
383
|
+
fs.writeFileSync(batchPath, writeWindowsScript(port, dataDir), 'utf8');
|
|
384
|
+
warn('Run the generated script as Administrator:');
|
|
385
|
+
info(batchPath);
|
|
386
|
+
info('');
|
|
387
|
+
info('Or use NSSM (recommended): https://nssm.cc');
|
|
388
|
+
info(' nssm install LogBoard "' + process.execPath + '" "' + path.join(BLORQ_DIR, 'server.js') + '"');
|
|
389
|
+
info(' nssm start LogBoard');
|
|
390
|
+
} else {
|
|
391
|
+
warn('Unsupported platform: ' + PLATFORM);
|
|
392
|
+
info('Start manually: logboard start --background');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function serviceUninstall() {
|
|
397
|
+
title('Uninstalling LogBoard service');
|
|
398
|
+
if (PLATFORM === 'darwin') {
|
|
399
|
+
const plist = plistPath();
|
|
400
|
+
try { execSync('launchctl unload "' + plist + '" 2>/dev/null', { stdio: 'ignore' }); } catch {}
|
|
401
|
+
try { fs.unlinkSync(plist); } catch {}
|
|
402
|
+
ok('Service removed');
|
|
403
|
+
} else if (PLATFORM === 'linux') {
|
|
404
|
+
try { execSync('systemctl --user stop logboard 2>/dev/null', { stdio: 'ignore' }); } catch {}
|
|
405
|
+
try { execSync('systemctl --user disable logboard 2>/dev/null', { stdio: 'ignore' }); } catch {}
|
|
406
|
+
try { fs.unlinkSync(systemdPath()); } catch {}
|
|
407
|
+
try { execSync('systemctl --user daemon-reload'); } catch {}
|
|
408
|
+
ok('Service removed');
|
|
409
|
+
} else if (PLATFORM === 'win32') {
|
|
410
|
+
try { execSync('sc stop LogBoard', { stdio: 'ignore' }); } catch {}
|
|
411
|
+
try { execSync('sc delete LogBoard', { stdio: 'ignore' }); } catch {}
|
|
412
|
+
ok('Windows service removed');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function serviceStart() {
|
|
417
|
+
if (PLATFORM === 'darwin') {
|
|
418
|
+
execSync('launchctl load "' + plistPath() + '"');
|
|
419
|
+
} else if (PLATFORM === 'linux') {
|
|
420
|
+
execSync('systemctl --user start logboard');
|
|
421
|
+
} else if (PLATFORM === 'win32') {
|
|
422
|
+
execSync('sc start LogBoard');
|
|
423
|
+
}
|
|
424
|
+
ok('LogBoard service started');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function serviceStop() {
|
|
428
|
+
if (PLATFORM === 'darwin') {
|
|
429
|
+
try { execSync('launchctl unload "' + plistPath() + '"'); } catch {}
|
|
430
|
+
} else if (PLATFORM === 'linux') {
|
|
431
|
+
execSync('systemctl --user stop logboard');
|
|
432
|
+
} else if (PLATFORM === 'win32') {
|
|
433
|
+
execSync('sc stop LogBoard');
|
|
434
|
+
}
|
|
435
|
+
ok('LogBoard service stopped');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function serviceRestart() {
|
|
439
|
+
if (PLATFORM === 'darwin') {
|
|
440
|
+
try { execSync('launchctl unload "' + plistPath() + '" 2>/dev/null', { stdio:'ignore' }); } catch {}
|
|
441
|
+
execSync('launchctl load "' + plistPath() + '"');
|
|
442
|
+
} else if (PLATFORM === 'linux') {
|
|
443
|
+
execSync('systemctl --user restart logboard');
|
|
444
|
+
} else if (PLATFORM === 'win32') {
|
|
445
|
+
execSync('sc stop LogBoard && sc start LogBoard');
|
|
446
|
+
}
|
|
447
|
+
ok('LogBoard service restarted');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function serviceLogs() {
|
|
451
|
+
if (PLATFORM === 'linux') {
|
|
452
|
+
// Use journalctl
|
|
453
|
+
const child = spawn('journalctl', ['--user', '-u', 'logboard', '-f', '--no-pager', '-n', '50'], { stdio: 'inherit' });
|
|
454
|
+
child.on('error', () => tailFile());
|
|
455
|
+
} else {
|
|
456
|
+
tailFile();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function tailFile() {
|
|
461
|
+
const f = path.join(os.homedir(), '.logboard', 'stdout.log');
|
|
462
|
+
if (!fs.existsSync(f)) {
|
|
463
|
+
warn('No log file yet: ' + f);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
info('Tailing ' + f + ' (Ctrl+C to stop)\n');
|
|
467
|
+
// Simple tail -f implementation
|
|
468
|
+
let pos = fs.statSync(f).size;
|
|
469
|
+
setInterval(() => {
|
|
470
|
+
const stat = fs.statSync(f);
|
|
471
|
+
if (stat.size > pos) {
|
|
472
|
+
const fd = fs.openSync(f, 'r');
|
|
473
|
+
const buf = Buffer.alloc(stat.size - pos);
|
|
474
|
+
fs.readSync(fd, buf, 0, buf.length, pos);
|
|
475
|
+
fs.closeSync(fd);
|
|
476
|
+
process.stdout.write(buf);
|
|
477
|
+
pos = stat.size;
|
|
478
|
+
}
|
|
479
|
+
}, 250);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ─── setup ────────────────────────────────────────────────────────────────
|
|
483
|
+
function cmdSetup() {
|
|
484
|
+
banner();
|
|
485
|
+
spawnSync(process.execPath, [path.join(BLORQ_DIR, 'setup.js')], { stdio: 'inherit', cwd: BLORQ_DIR });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ─── help ─────────────────────────────────────────────────────────────────
|
|
489
|
+
function cmdHelp() {
|
|
490
|
+
banner();
|
|
491
|
+
console.log(c('bold',' Usage') + ' logboard <command> [options]\n');
|
|
492
|
+
console.log(c('bold',' Commands:'));
|
|
493
|
+
const cmds = [
|
|
494
|
+
['start', 'Start LogBoard in foreground'],
|
|
495
|
+
['start --background','Start LogBoard in background'],
|
|
496
|
+
['start --port 9900', 'Start on custom port'],
|
|
497
|
+
['stop', 'Stop background instance'],
|
|
498
|
+
['restart', 'Restart background instance'],
|
|
499
|
+
['status', 'Show running status'],
|
|
500
|
+
['setup', 'First-run: create data/, .env, users'],
|
|
501
|
+
['open', 'Open dashboard in browser'],
|
|
502
|
+
['service install', 'Register as OS service (starts on boot)'],
|
|
503
|
+
['service uninstall', 'Remove OS service'],
|
|
504
|
+
['service start', 'Start the OS service'],
|
|
505
|
+
['service stop', 'Stop the OS service'],
|
|
506
|
+
['service logs', 'Tail service logs'],
|
|
507
|
+
['--version', 'Print version'],
|
|
508
|
+
['--help', 'Print this help'],
|
|
509
|
+
];
|
|
510
|
+
const pad = 26;
|
|
511
|
+
cmds.forEach(([cmd, desc]) => {
|
|
512
|
+
console.log(' ' + c('cyan', cmd.padEnd(pad)) + c('dim', desc));
|
|
513
|
+
});
|
|
514
|
+
console.log('');
|
|
515
|
+
console.log(c('bold',' Environment variables:'));
|
|
516
|
+
console.log(' ' + c('cyan','PORT'.padEnd(pad)) + c('dim','Server port (default: 9900)'));
|
|
517
|
+
console.log(' ' + c('cyan','DATA_DIR'.padEnd(pad))+ c('dim','Where to store data/ files'));
|
|
518
|
+
console.log(' ' + c('cyan','JWT_SECRET'.padEnd(pad))+c('dim','JWT signing secret (required in prod)'));
|
|
519
|
+
console.log('');
|
|
520
|
+
console.log(' Docs: ' + c('cyan','https://github.com/logboard/logboard') + '\n');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ── Dispatch ──────────────────────────────────────────────────────────────
|
|
524
|
+
switch (command) {
|
|
525
|
+
case 'start': cmdStart(); break;
|
|
526
|
+
case 'stop': cmdStop(); break;
|
|
527
|
+
case 'restart': cmdRestart(); break;
|
|
528
|
+
case 'status': cmdStatus(); break;
|
|
529
|
+
case 'setup': cmdSetup(); break;
|
|
530
|
+
case 'open': cmdOpen(); break;
|
|
531
|
+
case 'service': cmdService(); break;
|
|
532
|
+
case '--version': case '-v':
|
|
533
|
+
console.log('logboard v' + VERSION); break;
|
|
534
|
+
case '--help': case '-h': case 'help': default:
|
|
535
|
+
cmdHelp();
|
|
536
|
+
}
|