@jait/gateway 0.1.8 → 0.1.9
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/bin/jait.mjs +260 -2
- package/package.json +1 -1
package/bin/jait.mjs
CHANGED
|
@@ -9,18 +9,42 @@
|
|
|
9
9
|
* jait --host 127.0.0.1 Bind to specific host
|
|
10
10
|
* jait --help Show help
|
|
11
11
|
* jait --version Show version
|
|
12
|
+
* jait daemon install Install systemd user service
|
|
13
|
+
* jait daemon start Start the service
|
|
14
|
+
* jait daemon stop Stop the service
|
|
15
|
+
* jait daemon restart Restart the service
|
|
16
|
+
* jait daemon status Show service status
|
|
17
|
+
* jait daemon uninstall Remove the systemd service
|
|
18
|
+
* jait daemon logs Tail service logs
|
|
12
19
|
*/
|
|
13
20
|
|
|
14
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
21
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
|
|
15
22
|
import { resolve, dirname, join } from "node:path";
|
|
16
|
-
import { homedir } from "node:os";
|
|
23
|
+
import { homedir, platform } from "node:os";
|
|
17
24
|
import { fileURLToPath } from "node:url";
|
|
25
|
+
import { execSync } from "node:child_process";
|
|
18
26
|
|
|
19
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
28
|
const pkg = JSON.parse(
|
|
21
29
|
readFileSync(resolve(__dirname, "..", "package.json"), "utf8"),
|
|
22
30
|
);
|
|
23
31
|
|
|
32
|
+
// ── Constants ───────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const SERVICE_NAME = "jait-gateway";
|
|
35
|
+
const JAIT_DIR = join(homedir(), ".jait");
|
|
36
|
+
const ENV_PATH = join(JAIT_DIR, ".env");
|
|
37
|
+
const LOG_PATH = join(JAIT_DIR, "gateway.log");
|
|
38
|
+
const ERR_LOG_PATH = join(JAIT_DIR, "gateway.err.log");
|
|
39
|
+
|
|
40
|
+
function systemdUnitDir() {
|
|
41
|
+
return join(homedir(), ".config", "systemd", "user");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function systemdUnitPath() {
|
|
45
|
+
return join(systemdUnitDir(), `${SERVICE_NAME}.service`);
|
|
46
|
+
}
|
|
47
|
+
|
|
24
48
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
25
49
|
|
|
26
50
|
function printBanner() {
|
|
@@ -38,6 +62,7 @@ function printBanner() {
|
|
|
38
62
|
function printHelp() {
|
|
39
63
|
printBanner();
|
|
40
64
|
console.log(`Usage: jait [options]
|
|
65
|
+
jait daemon <command>
|
|
41
66
|
|
|
42
67
|
Options:
|
|
43
68
|
--port <number> Port to listen on (default: 8000, env: PORT)
|
|
@@ -46,6 +71,15 @@ Options:
|
|
|
46
71
|
--version, -v Show version number
|
|
47
72
|
--help, -h Show this help message
|
|
48
73
|
|
|
74
|
+
Daemon commands (Linux systemd):
|
|
75
|
+
daemon install Install systemd user service (auto-starts on boot)
|
|
76
|
+
daemon uninstall Remove systemd user service
|
|
77
|
+
daemon start Start the service
|
|
78
|
+
daemon stop Stop the service
|
|
79
|
+
daemon restart Restart the service
|
|
80
|
+
daemon status Show service status + health check
|
|
81
|
+
daemon logs Tail service logs (journalctl)
|
|
82
|
+
|
|
49
83
|
Environment files are loaded in order (first found wins):
|
|
50
84
|
1. --env flag path
|
|
51
85
|
2. ./.env (current directory)
|
|
@@ -56,11 +90,235 @@ See https://github.com/JakobWl/Jait for full documentation.
|
|
|
56
90
|
`);
|
|
57
91
|
}
|
|
58
92
|
|
|
93
|
+
function run(cmd, { silent = false } = {}) {
|
|
94
|
+
try {
|
|
95
|
+
return execSync(cmd, {
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
stdio: silent ? "pipe" : "inherit",
|
|
98
|
+
});
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (silent) return err.stdout || "";
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function runSilent(cmd) {
|
|
106
|
+
return run(cmd, { silent: true }).trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Daemon commands ─────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
function ensureLinux() {
|
|
112
|
+
if (platform() !== "linux") {
|
|
113
|
+
console.error("Error: daemon commands are only supported on Linux (systemd).");
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function resolveNodePath() {
|
|
119
|
+
try {
|
|
120
|
+
return runSilent("which node");
|
|
121
|
+
} catch {
|
|
122
|
+
return "/usr/bin/node";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolveJaitBin() {
|
|
127
|
+
return resolve(__dirname, "jait.mjs");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function buildUnit({ port, host, envPath } = {}) {
|
|
131
|
+
const nodePath = resolveNodePath();
|
|
132
|
+
const jaitBin = resolveJaitBin();
|
|
133
|
+
const envFlag = envPath && existsSync(envPath) ? envPath : ENV_PATH;
|
|
134
|
+
|
|
135
|
+
const execArgs = [nodePath, jaitBin];
|
|
136
|
+
if (existsSync(envFlag)) execArgs.push("--env", envFlag);
|
|
137
|
+
if (port) execArgs.push("--port", String(port));
|
|
138
|
+
if (host) execArgs.push("--host", host);
|
|
139
|
+
|
|
140
|
+
const execStart = execArgs.join(" ");
|
|
141
|
+
|
|
142
|
+
return `[Unit]
|
|
143
|
+
Description=Jait AI Gateway
|
|
144
|
+
After=network-online.target
|
|
145
|
+
Wants=network-online.target
|
|
146
|
+
|
|
147
|
+
[Service]
|
|
148
|
+
ExecStart=${execStart}
|
|
149
|
+
Restart=always
|
|
150
|
+
RestartSec=5
|
|
151
|
+
KillMode=process
|
|
152
|
+
StandardOutput=journal
|
|
153
|
+
StandardError=journal
|
|
154
|
+
|
|
155
|
+
[Install]
|
|
156
|
+
WantedBy=default.target
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function daemonInstall(flags) {
|
|
161
|
+
ensureLinux();
|
|
162
|
+
const unitDir = systemdUnitDir();
|
|
163
|
+
const unitPath = systemdUnitPath();
|
|
164
|
+
|
|
165
|
+
// Ensure directories exist
|
|
166
|
+
mkdirSync(unitDir, { recursive: true });
|
|
167
|
+
mkdirSync(JAIT_DIR, { recursive: true });
|
|
168
|
+
|
|
169
|
+
const unit = buildUnit(flags);
|
|
170
|
+
writeFileSync(unitPath, unit, "utf8");
|
|
171
|
+
console.log(` Wrote ${unitPath}`);
|
|
172
|
+
|
|
173
|
+
// Enable user lingering so service runs without active login session
|
|
174
|
+
try {
|
|
175
|
+
const user = runSilent("whoami");
|
|
176
|
+
run(`loginctl enable-linger ${user}`, { silent: true });
|
|
177
|
+
console.log(` Enabled linger for user ${user}`);
|
|
178
|
+
} catch {
|
|
179
|
+
console.warn(" Warning: could not enable lingering (service may stop on logout)");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Reload systemd and enable the service
|
|
183
|
+
run(`systemctl --user daemon-reload`, { silent: true });
|
|
184
|
+
run(`systemctl --user enable ${SERVICE_NAME}`, { silent: true });
|
|
185
|
+
console.log(` Service ${SERVICE_NAME} installed and enabled`);
|
|
186
|
+
console.log("");
|
|
187
|
+
console.log(" Run 'jait daemon start' to start the gateway.");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function daemonUninstall() {
|
|
191
|
+
ensureLinux();
|
|
192
|
+
const unitPath = systemdUnitPath();
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
run(`systemctl --user stop ${SERVICE_NAME}`, { silent: true });
|
|
196
|
+
run(`systemctl --user disable ${SERVICE_NAME}`, { silent: true });
|
|
197
|
+
} catch { /* may not be running */ }
|
|
198
|
+
|
|
199
|
+
if (existsSync(unitPath)) {
|
|
200
|
+
unlinkSync(unitPath);
|
|
201
|
+
run(`systemctl --user daemon-reload`, { silent: true });
|
|
202
|
+
console.log(` Removed ${unitPath}`);
|
|
203
|
+
} else {
|
|
204
|
+
console.log(" Service not installed.");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function daemonStart() {
|
|
209
|
+
ensureLinux();
|
|
210
|
+
run(`systemctl --user start ${SERVICE_NAME}`);
|
|
211
|
+
console.log(` ${SERVICE_NAME} started`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function daemonStop() {
|
|
215
|
+
ensureLinux();
|
|
216
|
+
run(`systemctl --user stop ${SERVICE_NAME}`);
|
|
217
|
+
console.log(` ${SERVICE_NAME} stopped`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function daemonRestart() {
|
|
221
|
+
ensureLinux();
|
|
222
|
+
run(`systemctl --user restart ${SERVICE_NAME}`);
|
|
223
|
+
console.log(` ${SERVICE_NAME} restarted`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function daemonStatus() {
|
|
227
|
+
ensureLinux();
|
|
228
|
+
|
|
229
|
+
// Show systemd status
|
|
230
|
+
const status = runSilent(
|
|
231
|
+
`systemctl --user show ${SERVICE_NAME} -p ActiveState,SubState,MainPID --no-pager`
|
|
232
|
+
);
|
|
233
|
+
const fields = {};
|
|
234
|
+
for (const line of status.split("\n")) {
|
|
235
|
+
const eq = line.indexOf("=");
|
|
236
|
+
if (eq > 0) fields[line.slice(0, eq)] = line.slice(eq + 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const active = fields.ActiveState || "unknown";
|
|
240
|
+
const sub = fields.SubState || "unknown";
|
|
241
|
+
const pid = fields.MainPID || "0";
|
|
242
|
+
|
|
243
|
+
console.log(` Service: ${SERVICE_NAME}`);
|
|
244
|
+
console.log(` State: ${active} (${sub})`);
|
|
245
|
+
console.log(` PID: ${pid === "0" ? "—" : pid}`);
|
|
246
|
+
|
|
247
|
+
// Health check
|
|
248
|
+
if (active === "active") {
|
|
249
|
+
try {
|
|
250
|
+
const port = process.env.PORT || "8000";
|
|
251
|
+
const health = runSilent(
|
|
252
|
+
`curl -sf --max-time 3 http://127.0.0.1:${port}/health`
|
|
253
|
+
);
|
|
254
|
+
const data = JSON.parse(health);
|
|
255
|
+
console.log(` Version: ${data.version}`);
|
|
256
|
+
console.log(` Healthy: ${data.healthy ? "yes" : "no"}`);
|
|
257
|
+
console.log(` Uptime: ${data.uptime}s`);
|
|
258
|
+
} catch {
|
|
259
|
+
console.log(" Health: unreachable (gateway may still be starting)");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function daemonLogs() {
|
|
265
|
+
ensureLinux();
|
|
266
|
+
try {
|
|
267
|
+
execSync(
|
|
268
|
+
`journalctl --user -u ${SERVICE_NAME} -f --no-pager -n 100`,
|
|
269
|
+
{ stdio: "inherit" },
|
|
270
|
+
);
|
|
271
|
+
} catch {
|
|
272
|
+
// user Ctrl-C
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
59
276
|
// ── Argument parsing ────────────────────────────────────────────────
|
|
60
277
|
|
|
61
278
|
const args = process.argv.slice(2);
|
|
62
279
|
const flags = {};
|
|
63
280
|
|
|
281
|
+
// Check for daemon subcommand first
|
|
282
|
+
if (args[0] === "daemon") {
|
|
283
|
+
const subCmd = args[1];
|
|
284
|
+
// Parse remaining flags for daemon install
|
|
285
|
+
for (let i = 2; i < args.length; i++) {
|
|
286
|
+
if (args[i] === "--port" && args[i + 1]) flags.port = args[++i];
|
|
287
|
+
else if (args[i] === "--host" && args[i + 1]) flags.host = args[++i];
|
|
288
|
+
else if (args[i] === "--env" && args[i + 1]) flags.envPath = args[++i];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
printBanner();
|
|
292
|
+
switch (subCmd) {
|
|
293
|
+
case "install":
|
|
294
|
+
daemonInstall(flags);
|
|
295
|
+
break;
|
|
296
|
+
case "uninstall":
|
|
297
|
+
daemonUninstall();
|
|
298
|
+
break;
|
|
299
|
+
case "start":
|
|
300
|
+
daemonStart();
|
|
301
|
+
break;
|
|
302
|
+
case "stop":
|
|
303
|
+
daemonStop();
|
|
304
|
+
break;
|
|
305
|
+
case "restart":
|
|
306
|
+
daemonRestart();
|
|
307
|
+
break;
|
|
308
|
+
case "status":
|
|
309
|
+
daemonStatus();
|
|
310
|
+
break;
|
|
311
|
+
case "logs":
|
|
312
|
+
daemonLogs();
|
|
313
|
+
break;
|
|
314
|
+
default:
|
|
315
|
+
console.log("Unknown daemon command:", subCmd || "(none)");
|
|
316
|
+
console.log("Available: install, uninstall, start, stop, restart, status, logs");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
process.exit(0);
|
|
320
|
+
}
|
|
321
|
+
|
|
64
322
|
for (let i = 0; i < args.length; i++) {
|
|
65
323
|
const arg = args[i];
|
|
66
324
|
if (arg === "--help" || arg === "-h") {
|