@it-club/provisor 0.2.2 → 0.3.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 +71 -175
- package/dist/cli.js +2072 -281
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { render } from "ink";
|
|
|
6
6
|
|
|
7
7
|
// src/commands/init.tsx
|
|
8
8
|
import { useState as useState2, useEffect } from "react";
|
|
9
|
-
import { Box as
|
|
9
|
+
import { Box as Box5, Text as Text5, useApp } from "ink";
|
|
10
10
|
|
|
11
11
|
// src/components/Task.tsx
|
|
12
12
|
import { Box, Text } from "ink";
|
|
@@ -17,7 +17,8 @@ var statusIcons = {
|
|
|
17
17
|
running: /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
18
18
|
success: /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
|
|
19
19
|
error: /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717" }),
|
|
20
|
-
skipped: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "-" })
|
|
20
|
+
skipped: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "-" }),
|
|
21
|
+
warning: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u26A0" })
|
|
21
22
|
};
|
|
22
23
|
function Task({ label, status, message }) {
|
|
23
24
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
@@ -68,6 +69,33 @@ function Confirm({ message, onConfirm }) {
|
|
|
68
69
|
] });
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
// src/components/Layout.tsx
|
|
73
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
74
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
75
|
+
function Layout({ title, subtitle, children, footerStatus, footerTips }) {
|
|
76
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: "100%", children: [
|
|
77
|
+
/* @__PURE__ */ jsx4(Header, { title: title || "", subtitle: subtitle || "" }),
|
|
78
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children }),
|
|
79
|
+
/* @__PURE__ */ jsxs4(
|
|
80
|
+
Box4,
|
|
81
|
+
{
|
|
82
|
+
borderStyle: "single",
|
|
83
|
+
borderColor: "gray",
|
|
84
|
+
flexDirection: "column",
|
|
85
|
+
paddingX: 1,
|
|
86
|
+
marginTop: 1,
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
|
|
89
|
+
/* @__PURE__ */ jsx4(Text4, { color: "blue", children: footerStatus || "Provisor CLI" }),
|
|
90
|
+
/* @__PURE__ */ jsx4(Text4, { color: "gray", children: (/* @__PURE__ */ new Date()).toLocaleTimeString() })
|
|
91
|
+
] }),
|
|
92
|
+
footerTips && footerTips.length > 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "gray", children: footerTips.join(" | ") }) })
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
] });
|
|
97
|
+
}
|
|
98
|
+
|
|
71
99
|
// src/utils/ssh.ts
|
|
72
100
|
import { Client } from "ssh2";
|
|
73
101
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -98,7 +126,10 @@ function createSSHConfig(options) {
|
|
|
98
126
|
host: options.host,
|
|
99
127
|
port: parseInt(String(options.port || 22), 10),
|
|
100
128
|
username: options.user || "root",
|
|
101
|
-
privateKey: readFileSync(keyPath)
|
|
129
|
+
privateKey: readFileSync(keyPath),
|
|
130
|
+
readyTimeout: 3e4,
|
|
131
|
+
keepaliveInterval: 1e4,
|
|
132
|
+
tryKeyboard: true
|
|
102
133
|
};
|
|
103
134
|
}
|
|
104
135
|
function connect(options) {
|
|
@@ -136,12 +167,31 @@ async function execScript(client, script, useSudo = false) {
|
|
|
136
167
|
const command = useSudo ? `sudo bash -c '${escapedScript}'` : `bash -c '${escapedScript}'`;
|
|
137
168
|
return exec(client, command);
|
|
138
169
|
}
|
|
170
|
+
function execStream(client, command, onStdout, onStderr) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
client.exec(command, (err, stream) => {
|
|
173
|
+
if (err) {
|
|
174
|
+
reject(err);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
stream.on("data", (data) => {
|
|
178
|
+
onStdout(data.toString());
|
|
179
|
+
});
|
|
180
|
+
stream.stderr.on("data", (data) => {
|
|
181
|
+
onStderr(data.toString());
|
|
182
|
+
});
|
|
183
|
+
stream.on("close", (code) => {
|
|
184
|
+
resolve(code ?? 0);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
139
189
|
function disconnect(client) {
|
|
140
190
|
client.end();
|
|
141
191
|
}
|
|
142
192
|
|
|
143
193
|
// src/commands/init.tsx
|
|
144
|
-
import { jsx as
|
|
194
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
145
195
|
var SCRIPTS = {
|
|
146
196
|
checkUser: (user) => `id ${user} &>/dev/null && echo "exists" || echo "not_found"`,
|
|
147
197
|
createUser: (user) => `
|
|
@@ -303,28 +353,28 @@ function InitCommand(props) {
|
|
|
303
353
|
}
|
|
304
354
|
}, [error]);
|
|
305
355
|
const allDone = tasks.hardenSsh === "success" || tasks.hardenSsh === "skipped";
|
|
306
|
-
return /* @__PURE__ */
|
|
307
|
-
/* @__PURE__ */
|
|
308
|
-
/* @__PURE__ */
|
|
309
|
-
/* @__PURE__ */
|
|
310
|
-
/* @__PURE__ */
|
|
311
|
-
/* @__PURE__ */
|
|
312
|
-
/* @__PURE__ */
|
|
313
|
-
/* @__PURE__ */
|
|
314
|
-
waitingConfirm && tasks.hardenSsh === "pending" && /* @__PURE__ */
|
|
315
|
-
/* @__PURE__ */
|
|
356
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
357
|
+
/* @__PURE__ */ jsx5(Header, { title: "Server Initialization", subtitle: `Host: ${props.host}` }),
|
|
358
|
+
/* @__PURE__ */ jsx5(Task, { label: "Connect to server", status: tasks.connect }),
|
|
359
|
+
/* @__PURE__ */ jsx5(Task, { label: "Update system packages", status: tasks.update }),
|
|
360
|
+
/* @__PURE__ */ jsx5(Task, { label: `Create user '${props.user}'`, status: tasks.createUser }),
|
|
361
|
+
/* @__PURE__ */ jsx5(Task, { label: "Setup SSH keys", status: tasks.setupSsh }),
|
|
362
|
+
/* @__PURE__ */ jsx5(Task, { label: "Configure firewall", status: tasks.firewall }),
|
|
363
|
+
/* @__PURE__ */ jsx5(Task, { label: "Harden SSH", status: tasks.hardenSsh }),
|
|
364
|
+
waitingConfirm && tasks.hardenSsh === "pending" && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
365
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "yellow", bold: true, children: [
|
|
316
366
|
"\u26A0 Before proceeding, verify SSH access as '",
|
|
317
367
|
props.user,
|
|
318
368
|
"':"
|
|
319
369
|
] }),
|
|
320
|
-
/* @__PURE__ */
|
|
370
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
|
|
321
371
|
" ssh ",
|
|
322
372
|
props.port !== "22" ? `-p ${props.port} ` : "",
|
|
323
373
|
props.user,
|
|
324
374
|
"@",
|
|
325
375
|
props.host
|
|
326
376
|
] }),
|
|
327
|
-
/* @__PURE__ */
|
|
377
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
|
|
328
378
|
Confirm,
|
|
329
379
|
{
|
|
330
380
|
message: "Have you verified SSH access works?",
|
|
@@ -332,19 +382,19 @@ function InitCommand(props) {
|
|
|
332
382
|
}
|
|
333
383
|
) })
|
|
334
384
|
] }),
|
|
335
|
-
error && /* @__PURE__ */
|
|
385
|
+
error && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
|
|
336
386
|
"Error: ",
|
|
337
387
|
error
|
|
338
388
|
] }) }),
|
|
339
|
-
allDone && /* @__PURE__ */
|
|
340
|
-
/* @__PURE__ */
|
|
341
|
-
summary.map((msg, i) => /* @__PURE__ */
|
|
389
|
+
allDone && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
390
|
+
/* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2713 Initialization complete" }),
|
|
391
|
+
summary.map((msg, i) => /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
|
|
342
392
|
" \u2022 ",
|
|
343
393
|
msg
|
|
344
394
|
] }, i)),
|
|
345
|
-
/* @__PURE__ */
|
|
395
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
346
396
|
"Next: ",
|
|
347
|
-
/* @__PURE__ */
|
|
397
|
+
/* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
|
|
348
398
|
"provisor app -h ",
|
|
349
399
|
props.host
|
|
350
400
|
] })
|
|
@@ -355,12 +405,12 @@ function InitCommand(props) {
|
|
|
355
405
|
|
|
356
406
|
// src/commands/app.tsx
|
|
357
407
|
import { useState as useState3, useEffect as useEffect2 } from "react";
|
|
358
|
-
import { Box as
|
|
408
|
+
import { Box as Box6, Text as Text6, useApp as useApp2, useInput as useInput2 } from "ink";
|
|
359
409
|
import SelectInput from "ink-select-input";
|
|
360
410
|
import Spinner2 from "ink-spinner";
|
|
361
411
|
import TextInput from "ink-text-input";
|
|
362
412
|
import crypto from "crypto";
|
|
363
|
-
import { Fragment, jsx as
|
|
413
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
364
414
|
var SCRIPTS2 = {
|
|
365
415
|
installCaddy: () => `
|
|
366
416
|
if ! command -v caddy &>/dev/null; then
|
|
@@ -435,7 +485,7 @@ EOF
|
|
|
435
485
|
ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes -T git@${host} 2>&1 || true
|
|
436
486
|
`,
|
|
437
487
|
// Push-to-deploy: setup bare repo with hook
|
|
438
|
-
setupPushDeploy: (name, branch, user) => `
|
|
488
|
+
setupPushDeploy: (name, branch, user, buildCmd, startCmd) => `
|
|
439
489
|
APP_DIR="/var/www/${name}"
|
|
440
490
|
REPO_DIR="/var/repo/${name}.git"
|
|
441
491
|
|
|
@@ -490,14 +540,17 @@ if [ -f "package.json" ]; then
|
|
|
490
540
|
npm ci --production 2>/dev/null || npm install --production
|
|
491
541
|
fi
|
|
492
542
|
|
|
493
|
-
if
|
|
543
|
+
if [ -n "${buildCmd}" ]; then
|
|
494
544
|
echo "Building application..."
|
|
495
|
-
|
|
545
|
+
${buildCmd}
|
|
496
546
|
fi
|
|
497
547
|
|
|
498
548
|
PM2_NAME="${name}"
|
|
499
549
|
if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
|
|
500
|
-
pm2 restart "$PM2_NAME"
|
|
550
|
+
pm2 restart "$PM2_NAME" --update-env
|
|
551
|
+
else
|
|
552
|
+
echo "Starting application..."
|
|
553
|
+
pm2 start "${startCmd}" --name "$PM2_NAME"
|
|
501
554
|
fi
|
|
502
555
|
else
|
|
503
556
|
echo "Static site detected."
|
|
@@ -512,7 +565,7 @@ HOOK_EOF
|
|
|
512
565
|
echo "push-deploy-ready"
|
|
513
566
|
`,
|
|
514
567
|
// Clone from repository - fresh clone (replace)
|
|
515
|
-
cloneRepoFresh: (name, repoUrl, branch, user) => `
|
|
568
|
+
cloneRepoFresh: (name, repoUrl, branch, user, buildCmd, startCmd) => `
|
|
516
569
|
APP_DIR="/var/www/${name}"
|
|
517
570
|
|
|
518
571
|
# Remove existing if present
|
|
@@ -530,10 +583,19 @@ HOOK_EOF
|
|
|
530
583
|
echo "Node.js project detected."
|
|
531
584
|
sudo -u ${user} npm ci --production 2>/dev/null || sudo -u ${user} npm install --production
|
|
532
585
|
|
|
533
|
-
if
|
|
586
|
+
if [ -n "${buildCmd}" ]; then
|
|
534
587
|
echo "Building application..."
|
|
535
|
-
sudo -u ${user}
|
|
588
|
+
sudo -u ${user} ${buildCmd}
|
|
589
|
+
fi
|
|
590
|
+
|
|
591
|
+
PM2_NAME="${name}"
|
|
592
|
+
if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
|
|
593
|
+
pm2 restart "$PM2_NAME" --update-env
|
|
594
|
+
else
|
|
595
|
+
echo "Starting application..."
|
|
596
|
+
pm2 start "${startCmd}" --name "$PM2_NAME"
|
|
536
597
|
fi
|
|
598
|
+
pm2 save
|
|
537
599
|
else
|
|
538
600
|
echo "Static site detected."
|
|
539
601
|
fi
|
|
@@ -542,7 +604,7 @@ HOOK_EOF
|
|
|
542
604
|
echo "clone-complete"
|
|
543
605
|
`,
|
|
544
606
|
// Update existing cloned repo (git pull)
|
|
545
|
-
updateRepo: (name, branch, user) => `
|
|
607
|
+
updateRepo: (name, branch, user, buildCmd, startCmd) => `
|
|
546
608
|
APP_DIR="/var/www/${name}"
|
|
547
609
|
|
|
548
610
|
cd "$APP_DIR"
|
|
@@ -556,14 +618,17 @@ HOOK_EOF
|
|
|
556
618
|
echo "Node.js project detected."
|
|
557
619
|
sudo -u ${user} npm ci --production 2>/dev/null || sudo -u ${user} npm install --production
|
|
558
620
|
|
|
559
|
-
if
|
|
621
|
+
if [ -n "${buildCmd}" ]; then
|
|
560
622
|
echo "Building application..."
|
|
561
|
-
sudo -u ${user}
|
|
623
|
+
sudo -u ${user} ${buildCmd}
|
|
562
624
|
fi
|
|
563
625
|
|
|
564
626
|
PM2_NAME="${name}"
|
|
565
627
|
if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
|
|
566
|
-
pm2 restart "$PM2_NAME"
|
|
628
|
+
pm2 restart "$PM2_NAME" --update-env
|
|
629
|
+
else
|
|
630
|
+
echo "Starting application..."
|
|
631
|
+
pm2 start "${startCmd}" --name "$PM2_NAME"
|
|
567
632
|
fi
|
|
568
633
|
else
|
|
569
634
|
echo "Static site detected."
|
|
@@ -572,7 +637,7 @@ HOOK_EOF
|
|
|
572
637
|
echo "update-complete"
|
|
573
638
|
`,
|
|
574
639
|
// Create update script for cloned repos
|
|
575
|
-
createUpdateScript: (name, branch, user) => `
|
|
640
|
+
createUpdateScript: (name, branch, user, buildCmd, startCmd) => `
|
|
576
641
|
cat << 'SCRIPT_EOF' > /usr/local/bin/update-${name}
|
|
577
642
|
#!/bin/bash
|
|
578
643
|
set -e
|
|
@@ -592,13 +657,16 @@ sudo -u $USER git reset --hard origin/$BRANCH
|
|
|
592
657
|
if [ -f "package.json" ]; then
|
|
593
658
|
sudo -u $USER npm ci --production 2>/dev/null || sudo -u $USER npm install --production
|
|
594
659
|
|
|
595
|
-
if
|
|
596
|
-
sudo -u $USER
|
|
660
|
+
if [ -n "${buildCmd}" ]; then
|
|
661
|
+
sudo -u $USER ${buildCmd}
|
|
597
662
|
fi
|
|
598
663
|
|
|
599
664
|
PM2_NAME="${name}"
|
|
600
665
|
if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
|
|
601
|
-
pm2 restart "$PM2_NAME"
|
|
666
|
+
pm2 restart "$PM2_NAME" --update-env
|
|
667
|
+
else
|
|
668
|
+
echo "Starting application..."
|
|
669
|
+
pm2 start "${startCmd}" --name "$PM2_NAME"
|
|
602
670
|
fi
|
|
603
671
|
fi
|
|
604
672
|
|
|
@@ -612,12 +680,15 @@ SCRIPT_EOF
|
|
|
612
680
|
cat << 'EOF' > /etc/caddy/Caddyfile
|
|
613
681
|
{
|
|
614
682
|
on_demand_tls {
|
|
615
|
-
|
|
616
|
-
burst 5
|
|
683
|
+
ask http://localhost:5555/check
|
|
617
684
|
}
|
|
618
685
|
}
|
|
619
686
|
|
|
620
|
-
|
|
687
|
+
:5555 {
|
|
688
|
+
respond /check 200
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
:443 {
|
|
621
692
|
tls {
|
|
622
693
|
on_demand
|
|
623
694
|
}
|
|
@@ -628,7 +699,7 @@ https:// {
|
|
|
628
699
|
file_server
|
|
629
700
|
}
|
|
630
701
|
|
|
631
|
-
|
|
702
|
+
:80 {
|
|
632
703
|
redir https://{host}{uri} permanent
|
|
633
704
|
}
|
|
634
705
|
EOF
|
|
@@ -985,6 +1056,10 @@ function AppCommand(props) {
|
|
|
985
1056
|
caddyConfig: "pending"
|
|
986
1057
|
});
|
|
987
1058
|
const [error, setError] = useState3(null);
|
|
1059
|
+
const [buildCmd, setBuildCmd] = useState3("npm run build");
|
|
1060
|
+
const [startCmd, setStartCmd] = useState3("npm start");
|
|
1061
|
+
const [askingBuild, setAskingBuild] = useState3(false);
|
|
1062
|
+
const [askingStart, setAskingStart] = useState3(false);
|
|
988
1063
|
const [selectingMethod, setSelectingMethod] = useState3(false);
|
|
989
1064
|
const [deployMethod, setDeployMethod] = useState3(props.repo ? "clone-public" : null);
|
|
990
1065
|
const [enteringRepo, setEnteringRepo] = useState3(false);
|
|
@@ -1077,9 +1152,21 @@ function AppCommand(props) {
|
|
|
1077
1152
|
run();
|
|
1078
1153
|
}, [client, tasks.caddy]);
|
|
1079
1154
|
useEffect2(() => {
|
|
1080
|
-
if (tasks.node !== "success" || selectingMethod || deployMethod !== null) return;
|
|
1081
|
-
|
|
1155
|
+
if (tasks.node !== "success" || askingBuild || askingStart || selectingMethod || deployMethod !== null) return;
|
|
1156
|
+
setAskingBuild(true);
|
|
1082
1157
|
}, [tasks.node]);
|
|
1158
|
+
const handleBuildSubmit = (value) => {
|
|
1159
|
+
setBuildCmd(value);
|
|
1160
|
+
setAskingBuild(false);
|
|
1161
|
+
setAskingStart(true);
|
|
1162
|
+
};
|
|
1163
|
+
const handleStartSubmit = (value) => {
|
|
1164
|
+
setStartCmd(value);
|
|
1165
|
+
setAskingStart(false);
|
|
1166
|
+
setSelectingMethod(true);
|
|
1167
|
+
};
|
|
1168
|
+
useEffect2(() => {
|
|
1169
|
+
}, []);
|
|
1083
1170
|
const handleMethodSelect = (item) => {
|
|
1084
1171
|
setSelectingMethod(false);
|
|
1085
1172
|
const method = item.value;
|
|
@@ -1187,17 +1274,17 @@ function AppCommand(props) {
|
|
|
1187
1274
|
updateTask("deploy", "running");
|
|
1188
1275
|
try {
|
|
1189
1276
|
if (deployMethod === "push") {
|
|
1190
|
-
await execScript(client, SCRIPTS2.setupPushDeploy(props.name, props.branch, props.user || "deploy"), true);
|
|
1277
|
+
await execScript(client, SCRIPTS2.setupPushDeploy(props.name, props.branch, props.user || "deploy", buildCmd, startCmd), true);
|
|
1191
1278
|
} else {
|
|
1192
1279
|
if (deployMethod === "clone-private") {
|
|
1193
1280
|
await execScript(client, SCRIPTS2.testGitConnection(gitHost), true);
|
|
1194
1281
|
}
|
|
1195
1282
|
if (existingAppAction === "replace") {
|
|
1196
|
-
await execScript(client, SCRIPTS2.cloneRepoFresh(props.name, repoUrl, props.branch, props.user || "deploy"), true);
|
|
1283
|
+
await execScript(client, SCRIPTS2.cloneRepoFresh(props.name, repoUrl, props.branch, props.user || "deploy", buildCmd, startCmd), true);
|
|
1197
1284
|
} else {
|
|
1198
|
-
await execScript(client, SCRIPTS2.updateRepo(props.name, props.branch, props.user || "deploy"), true);
|
|
1285
|
+
await execScript(client, SCRIPTS2.updateRepo(props.name, props.branch, props.user || "deploy", buildCmd, startCmd), true);
|
|
1199
1286
|
}
|
|
1200
|
-
await execScript(client, SCRIPTS2.createUpdateScript(props.name, props.branch, props.user || "deploy"), true);
|
|
1287
|
+
await execScript(client, SCRIPTS2.createUpdateScript(props.name, props.branch, props.user || "deploy", buildCmd, startCmd), true);
|
|
1201
1288
|
}
|
|
1202
1289
|
updateTask("deploy", "success");
|
|
1203
1290
|
} catch (err) {
|
|
@@ -1236,6 +1323,8 @@ function AppCommand(props) {
|
|
|
1236
1323
|
const config = {
|
|
1237
1324
|
repo: repoUrl,
|
|
1238
1325
|
branch: props.branch,
|
|
1326
|
+
buildCmd,
|
|
1327
|
+
startCmd,
|
|
1239
1328
|
autoDeployType: "polling",
|
|
1240
1329
|
pollingInterval
|
|
1241
1330
|
};
|
|
@@ -1250,6 +1339,8 @@ function AppCommand(props) {
|
|
|
1250
1339
|
const config = {
|
|
1251
1340
|
repo: repoUrl,
|
|
1252
1341
|
branch: props.branch,
|
|
1342
|
+
buildCmd,
|
|
1343
|
+
startCmd,
|
|
1253
1344
|
autoDeployType: "webhook",
|
|
1254
1345
|
webhookPort: port
|
|
1255
1346
|
};
|
|
@@ -1310,137 +1401,153 @@ function AppCommand(props) {
|
|
|
1310
1401
|
const allDone = tasks.caddyConfig === "success";
|
|
1311
1402
|
const isClone = deployMethod === "clone-public" || deployMethod === "clone-private";
|
|
1312
1403
|
const deployLabel = isClone ? existingAppAction === "update" ? `Update ${props.name}` : `Clone from ${repoUrl || "repository"}` : "Setup push-to-deploy";
|
|
1313
|
-
return /* @__PURE__ */
|
|
1314
|
-
/* @__PURE__ */
|
|
1315
|
-
/* @__PURE__ */
|
|
1316
|
-
/* @__PURE__ */
|
|
1317
|
-
/* @__PURE__ */
|
|
1318
|
-
deployMethod === "clone-private" && /* @__PURE__ */
|
|
1319
|
-
/* @__PURE__ */
|
|
1320
|
-
(deployMethod === "clone-public" || deployMethod === "clone-private") && autoDeployChoice && autoDeployChoice !== "none" && /* @__PURE__ */
|
|
1321
|
-
/* @__PURE__ */
|
|
1322
|
-
|
|
1323
|
-
/* @__PURE__ */
|
|
1324
|
-
/* @__PURE__ */
|
|
1404
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
1405
|
+
/* @__PURE__ */ jsx6(Header, { title: "Application Provisioning", subtitle: `Host: ${props.host}` }),
|
|
1406
|
+
/* @__PURE__ */ jsx6(Task, { label: "Connect to server", status: tasks.connect }),
|
|
1407
|
+
/* @__PURE__ */ jsx6(Task, { label: "Install Caddy", status: tasks.caddy }),
|
|
1408
|
+
/* @__PURE__ */ jsx6(Task, { label: "Install Node.js & PM2", status: tasks.node }),
|
|
1409
|
+
deployMethod === "clone-private" && /* @__PURE__ */ jsx6(Task, { label: "Generate deploy key", status: tasks.deployKey }),
|
|
1410
|
+
/* @__PURE__ */ jsx6(Task, { label: deployLabel, status: tasks.deploy }),
|
|
1411
|
+
(deployMethod === "clone-public" || deployMethod === "clone-private") && autoDeployChoice && autoDeployChoice !== "none" && /* @__PURE__ */ jsx6(Task, { label: `Setup ${autoDeployChoice === "polling" ? "git polling" : "webhook"} for auto-deploy`, status: tasks.webhook }),
|
|
1412
|
+
/* @__PURE__ */ jsx6(Task, { label: "Configure Caddy", status: tasks.caddyConfig }),
|
|
1413
|
+
askingBuild && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1414
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Build command (optional, press Enter to skip):" }),
|
|
1415
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Command to build the app (e.g. 'npm run build')" }),
|
|
1416
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1417
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "> " }),
|
|
1418
|
+
/* @__PURE__ */ jsx6(TextInput, { value: buildCmd, onChange: setBuildCmd, onSubmit: handleBuildSubmit })
|
|
1419
|
+
] })
|
|
1420
|
+
] }),
|
|
1421
|
+
askingStart && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1422
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Start command:" }),
|
|
1423
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Command to start the app (e.g. 'npm start' or 'node server.js')" }),
|
|
1424
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1425
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "> " }),
|
|
1426
|
+
/* @__PURE__ */ jsx6(TextInput, { value: startCmd, onChange: setStartCmd, onSubmit: handleStartSubmit })
|
|
1427
|
+
] })
|
|
1325
1428
|
] }),
|
|
1326
|
-
|
|
1327
|
-
/* @__PURE__ */
|
|
1328
|
-
/* @__PURE__ */
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1429
|
+
selectingMethod && deployMethod === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1430
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Select deployment method:" }),
|
|
1431
|
+
/* @__PURE__ */ jsx6(SelectInput, { items: deployOptions, onSelect: handleMethodSelect })
|
|
1432
|
+
] }),
|
|
1433
|
+
enteringRepo && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1434
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Enter repository URL:" }),
|
|
1435
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: deployMethod === "clone-private" ? "(Use SSH URL: git@github.com:user/repo.git)" : "(e.g., https://github.com/user/repo.git)" }),
|
|
1436
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
1437
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "> " }),
|
|
1438
|
+
/* @__PURE__ */ jsx6(TextInput, { value: repoUrl, onChange: setRepoUrl, onSubmit: handleRepoSubmit })
|
|
1332
1439
|
] })
|
|
1333
1440
|
] }),
|
|
1334
|
-
selectingExistingAction && existingAppAction === null && /* @__PURE__ */
|
|
1335
|
-
/* @__PURE__ */
|
|
1441
|
+
selectingExistingAction && existingAppAction === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1442
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "yellow", children: [
|
|
1336
1443
|
"\u26A0 App directory ",
|
|
1337
1444
|
appDir,
|
|
1338
1445
|
" already exists"
|
|
1339
1446
|
] }),
|
|
1340
|
-
/* @__PURE__ */
|
|
1341
|
-
/* @__PURE__ */
|
|
1447
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: "What would you like to do?" }),
|
|
1448
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(SelectInput, { items: existingAppOptions, onSelect: handleExistingAppSelect }) })
|
|
1342
1449
|
] }),
|
|
1343
|
-
waitingForKeySetup && deployKey && !keyConfirmed && !keyVerifying && !keyVerified && !keyVerifyError && /* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
/* @__PURE__ */
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
1450
|
+
waitingForKeySetup && deployKey && !keyConfirmed && !keyVerifying && !keyVerified && !keyVerifyError && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1451
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Deploy Key Generated \u2501\u2501\u2501" }),
|
|
1452
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1453
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Public key (copy this):" }),
|
|
1454
|
+
/* @__PURE__ */ jsx6(Box6, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "cyan", children: /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: deployKey }) })
|
|
1348
1455
|
] }),
|
|
1349
|
-
/* @__PURE__ */
|
|
1350
|
-
/* @__PURE__ */
|
|
1456
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1457
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
1351
1458
|
"Add this key to ",
|
|
1352
1459
|
gitHost,
|
|
1353
1460
|
":"
|
|
1354
1461
|
] }),
|
|
1355
|
-
keyInstructions.steps.map((step, i) => /* @__PURE__ */
|
|
1462
|
+
keyInstructions.steps.map((step, i) => /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1356
1463
|
" ",
|
|
1357
1464
|
step
|
|
1358
1465
|
] }, i))
|
|
1359
1466
|
] }),
|
|
1360
|
-
/* @__PURE__ */
|
|
1361
|
-
/* @__PURE__ */
|
|
1467
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
|
|
1468
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "yellow", children: [
|
|
1362
1469
|
"Have you added the deploy key to ",
|
|
1363
1470
|
gitHost,
|
|
1364
1471
|
"? "
|
|
1365
1472
|
] }),
|
|
1366
|
-
/* @__PURE__ */
|
|
1473
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: "(y/n) " })
|
|
1367
1474
|
] })
|
|
1368
1475
|
] }),
|
|
1369
|
-
keyVerifying && /* @__PURE__ */
|
|
1370
|
-
/* @__PURE__ */
|
|
1371
|
-
/* @__PURE__ */
|
|
1372
|
-
/* @__PURE__ */
|
|
1373
|
-
/* @__PURE__ */
|
|
1476
|
+
keyVerifying && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Verifying Deploy Key \u2501\u2501\u2501" }),
|
|
1478
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
|
|
1479
|
+
/* @__PURE__ */ jsx6(Spinner2, { type: "dots" }),
|
|
1480
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1374
1481
|
" Testing SSH connection to ",
|
|
1375
1482
|
gitHost,
|
|
1376
1483
|
"..."
|
|
1377
1484
|
] })
|
|
1378
1485
|
] })
|
|
1379
1486
|
] }),
|
|
1380
|
-
keyVerified && !error && /* @__PURE__ */
|
|
1381
|
-
keyVerifyError && /* @__PURE__ */
|
|
1382
|
-
/* @__PURE__ */
|
|
1383
|
-
/* @__PURE__ */
|
|
1487
|
+
keyVerified && !error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Deploy key verified successfully!" }) }),
|
|
1488
|
+
keyVerifyError && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1489
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "red", children: "\u2501\u2501\u2501 Deploy Key Verification Failed \u2501\u2501\u2501" }),
|
|
1490
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
1384
1491
|
"\u2717 ",
|
|
1385
1492
|
keyVerifyError
|
|
1386
1493
|
] }) }),
|
|
1387
|
-
/* @__PURE__ */
|
|
1388
|
-
/* @__PURE__ */
|
|
1389
|
-
/* @__PURE__ */
|
|
1494
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1495
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Please check:" }),
|
|
1496
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1390
1497
|
" 1. The deploy key is added to ",
|
|
1391
1498
|
gitHost
|
|
1392
1499
|
] }),
|
|
1393
|
-
/* @__PURE__ */
|
|
1394
|
-
/* @__PURE__ */
|
|
1500
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 2. The key has read access to the repository" }),
|
|
1501
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 3. The repository URL is correct" })
|
|
1395
1502
|
] }),
|
|
1396
|
-
/* @__PURE__ */
|
|
1397
|
-
/* @__PURE__ */
|
|
1398
|
-
/* @__PURE__ */
|
|
1503
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1504
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Public key to add:" }),
|
|
1505
|
+
/* @__PURE__ */ jsx6(Box6, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "cyan", children: /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: deployKey }) })
|
|
1399
1506
|
] }),
|
|
1400
|
-
/* @__PURE__ */
|
|
1507
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "Press (r) to retry or (q) to quit" }) })
|
|
1401
1508
|
] }),
|
|
1402
|
-
selectingAutoDeploy && autoDeployChoice === null && /* @__PURE__ */
|
|
1403
|
-
/* @__PURE__ */
|
|
1404
|
-
/* @__PURE__ */
|
|
1509
|
+
selectingAutoDeploy && autoDeployChoice === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1510
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Enable automatic deployment?" }),
|
|
1511
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1405
1512
|
"Auto-deploy when you push to ",
|
|
1406
1513
|
props.branch,
|
|
1407
1514
|
" branch."
|
|
1408
1515
|
] }),
|
|
1409
|
-
/* @__PURE__ */
|
|
1516
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(SelectInput, { items: autoDeployOptions, onSelect: handleAutoDeploySelect }) })
|
|
1410
1517
|
] }),
|
|
1411
|
-
selectingTls && tlsChoice === null && /* @__PURE__ */
|
|
1412
|
-
/* @__PURE__ */
|
|
1413
|
-
/* @__PURE__ */
|
|
1518
|
+
selectingTls && tlsChoice === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1519
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Select TLS configuration:" }),
|
|
1520
|
+
/* @__PURE__ */ jsx6(SelectInput, { items: tlsOptions, onSelect: handleTlsSelect })
|
|
1414
1521
|
] }),
|
|
1415
|
-
error && /* @__PURE__ */
|
|
1522
|
+
error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
1416
1523
|
"Error: ",
|
|
1417
1524
|
error
|
|
1418
1525
|
] }) }),
|
|
1419
|
-
allDone && /* @__PURE__ */
|
|
1420
|
-
/* @__PURE__ */
|
|
1421
|
-
/* @__PURE__ */
|
|
1422
|
-
/* @__PURE__ */
|
|
1423
|
-
/* @__PURE__ */
|
|
1526
|
+
allDone && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1527
|
+
/* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "\u2713 Application provisioning complete" }),
|
|
1528
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: deployMethod === "push" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1529
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Git remote (add to local project):" }),
|
|
1530
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
|
|
1424
1531
|
" git remote add production ssh://",
|
|
1425
1532
|
props.user,
|
|
1426
1533
|
"@",
|
|
1427
1534
|
serverIp,
|
|
1428
1535
|
repoDir
|
|
1429
1536
|
] }),
|
|
1430
|
-
/* @__PURE__ */
|
|
1431
|
-
/* @__PURE__ */
|
|
1537
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Deploy with:" }) }),
|
|
1538
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
|
|
1432
1539
|
" git push production ",
|
|
1433
1540
|
props.branch
|
|
1434
1541
|
] })
|
|
1435
|
-
] }) : /* @__PURE__ */
|
|
1436
|
-
/* @__PURE__ */
|
|
1542
|
+
] }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1543
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
1437
1544
|
"App ",
|
|
1438
1545
|
existingAppAction === "update" ? "updated" : "deployed",
|
|
1439
1546
|
" from: ",
|
|
1440
|
-
/* @__PURE__ */
|
|
1547
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: repoUrl })
|
|
1441
1548
|
] }),
|
|
1442
|
-
/* @__PURE__ */
|
|
1443
|
-
/* @__PURE__ */
|
|
1549
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Manual update:" }) }),
|
|
1550
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
|
|
1444
1551
|
" ssh ",
|
|
1445
1552
|
props.user,
|
|
1446
1553
|
"@",
|
|
@@ -1449,35 +1556,35 @@ function AppCommand(props) {
|
|
|
1449
1556
|
props.name,
|
|
1450
1557
|
'"'
|
|
1451
1558
|
] }),
|
|
1452
|
-
autoDeployChoice === "polling" && /* @__PURE__ */
|
|
1453
|
-
/* @__PURE__ */
|
|
1454
|
-
/* @__PURE__ */
|
|
1559
|
+
autoDeployChoice === "polling" && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1560
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Auto-Deploy (Git Polling) \u2501\u2501\u2501" }),
|
|
1561
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1455
1562
|
"Polling interval: ",
|
|
1456
|
-
/* @__PURE__ */
|
|
1563
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
|
|
1457
1564
|
pollingInterval,
|
|
1458
1565
|
" seconds"
|
|
1459
1566
|
] })
|
|
1460
1567
|
] }),
|
|
1461
|
-
/* @__PURE__ */
|
|
1568
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1462
1569
|
"Branch: ",
|
|
1463
|
-
/* @__PURE__ */
|
|
1570
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: props.branch })
|
|
1464
1571
|
] }),
|
|
1465
|
-
/* @__PURE__ */
|
|
1466
|
-
/* @__PURE__ */
|
|
1572
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: "The server checks for new commits and deploys automatically." }),
|
|
1573
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1467
1574
|
"View logs: ",
|
|
1468
|
-
/* @__PURE__ */
|
|
1575
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
|
|
1469
1576
|
"journalctl -u poll-",
|
|
1470
1577
|
props.name,
|
|
1471
1578
|
" -f"
|
|
1472
1579
|
] })
|
|
1473
1580
|
] }) })
|
|
1474
1581
|
] }),
|
|
1475
|
-
autoDeployChoice === "webhook" && webhookPort > 0 && /* @__PURE__ */
|
|
1476
|
-
/* @__PURE__ */
|
|
1477
|
-
/* @__PURE__ */
|
|
1478
|
-
/* @__PURE__ */
|
|
1582
|
+
autoDeployChoice === "webhook" && webhookPort > 0 && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1583
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1584
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Auto-Deploy (Webhook) \u2501\u2501\u2501" }),
|
|
1585
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1479
1586
|
"Webhook URL: ",
|
|
1480
|
-
/* @__PURE__ */
|
|
1587
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
|
|
1481
1588
|
"http://",
|
|
1482
1589
|
serverIp,
|
|
1483
1590
|
":",
|
|
@@ -1485,58 +1592,58 @@ function AppCommand(props) {
|
|
|
1485
1592
|
"/webhook"
|
|
1486
1593
|
] })
|
|
1487
1594
|
] }),
|
|
1488
|
-
/* @__PURE__ */
|
|
1595
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1489
1596
|
"Secret: ",
|
|
1490
|
-
/* @__PURE__ */
|
|
1597
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: webhookSecret })
|
|
1491
1598
|
] }),
|
|
1492
|
-
/* @__PURE__ */
|
|
1599
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
1493
1600
|
"Branch: ",
|
|
1494
|
-
/* @__PURE__ */
|
|
1601
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: props.branch })
|
|
1495
1602
|
] })
|
|
1496
1603
|
] }),
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
/* @__PURE__ */
|
|
1499
|
-
gitHost === "github.com" && /* @__PURE__ */
|
|
1500
|
-
/* @__PURE__ */
|
|
1501
|
-
/* @__PURE__ */
|
|
1604
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
|
|
1605
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Add webhook to your repository:" }),
|
|
1606
|
+
gitHost === "github.com" && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1607
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 1. Go to your repo \u2192 Settings \u2192 Webhooks \u2192 Add webhook" }),
|
|
1608
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1502
1609
|
" 2. Payload URL: http://",
|
|
1503
1610
|
serverIp,
|
|
1504
1611
|
":",
|
|
1505
1612
|
webhookPort,
|
|
1506
1613
|
"/webhook"
|
|
1507
1614
|
] }),
|
|
1508
|
-
/* @__PURE__ */
|
|
1509
|
-
/* @__PURE__ */
|
|
1615
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 3. Content type: application/json" }),
|
|
1616
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1510
1617
|
" 4. Secret: ",
|
|
1511
1618
|
webhookSecret
|
|
1512
1619
|
] }),
|
|
1513
|
-
/* @__PURE__ */
|
|
1620
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: ' 5. Select "Just the push event"' })
|
|
1514
1621
|
] }),
|
|
1515
|
-
gitHost === "gitlab.com" && /* @__PURE__ */
|
|
1516
|
-
/* @__PURE__ */
|
|
1517
|
-
/* @__PURE__ */
|
|
1622
|
+
gitHost === "gitlab.com" && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1623
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 1. Go to your repo \u2192 Settings \u2192 Webhooks" }),
|
|
1624
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1518
1625
|
" 2. URL: http://",
|
|
1519
1626
|
serverIp,
|
|
1520
1627
|
":",
|
|
1521
1628
|
webhookPort,
|
|
1522
1629
|
"/webhook"
|
|
1523
1630
|
] }),
|
|
1524
|
-
/* @__PURE__ */
|
|
1631
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1525
1632
|
" 3. Secret token: ",
|
|
1526
1633
|
webhookSecret
|
|
1527
1634
|
] }),
|
|
1528
|
-
/* @__PURE__ */
|
|
1635
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 4. Trigger: Push events" })
|
|
1529
1636
|
] }),
|
|
1530
|
-
gitHost === "bitbucket.org" && /* @__PURE__ */
|
|
1531
|
-
/* @__PURE__ */
|
|
1532
|
-
/* @__PURE__ */
|
|
1637
|
+
gitHost === "bitbucket.org" && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1638
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 1. Go to your repo \u2192 Repository settings \u2192 Webhooks" }),
|
|
1639
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
|
|
1533
1640
|
" 2. URL: http://",
|
|
1534
1641
|
serverIp,
|
|
1535
1642
|
":",
|
|
1536
1643
|
webhookPort,
|
|
1537
1644
|
"/webhook"
|
|
1538
1645
|
] }),
|
|
1539
|
-
/* @__PURE__ */
|
|
1646
|
+
/* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 3. Triggers: Repository push" })
|
|
1540
1647
|
] })
|
|
1541
1648
|
] })
|
|
1542
1649
|
] })
|
|
@@ -1547,8 +1654,8 @@ function AppCommand(props) {
|
|
|
1547
1654
|
|
|
1548
1655
|
// src/commands/ssh-key.tsx
|
|
1549
1656
|
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
1550
|
-
import { Box as
|
|
1551
|
-
import { jsx as
|
|
1657
|
+
import { Box as Box7, Text as Text7, useApp as useApp3, useInput as useInput3 } from "ink";
|
|
1658
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1552
1659
|
function SshKeyCommand(props) {
|
|
1553
1660
|
const { exit } = useApp3();
|
|
1554
1661
|
const [client, setClient] = useState4(null);
|
|
@@ -1556,6 +1663,19 @@ function SshKeyCommand(props) {
|
|
|
1556
1663
|
const [error, setError] = useState4(null);
|
|
1557
1664
|
const [keys, setKeys] = useState4([]);
|
|
1558
1665
|
const [message, setMessage] = useState4("");
|
|
1666
|
+
const [done, setDone] = useState4(false);
|
|
1667
|
+
const goBack = () => {
|
|
1668
|
+
if (props.onBack) {
|
|
1669
|
+
props.onBack();
|
|
1670
|
+
} else {
|
|
1671
|
+
exit();
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
useInput3((input, key) => {
|
|
1675
|
+
if (done && (key.escape || input === "q")) {
|
|
1676
|
+
goBack();
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1559
1679
|
useEffect3(() => {
|
|
1560
1680
|
const run = async () => {
|
|
1561
1681
|
setStatus("running");
|
|
@@ -1594,46 +1714,60 @@ function SshKeyCommand(props) {
|
|
|
1594
1714
|
setError("Specify --add <key> or --list");
|
|
1595
1715
|
}
|
|
1596
1716
|
disconnect(sshClient);
|
|
1597
|
-
|
|
1717
|
+
setDone(true);
|
|
1598
1718
|
} catch (err) {
|
|
1599
1719
|
setStatus("error");
|
|
1600
1720
|
setError(err instanceof Error ? err.message : String(err));
|
|
1601
1721
|
if (client) disconnect(client);
|
|
1602
|
-
|
|
1722
|
+
setDone(true);
|
|
1603
1723
|
}
|
|
1604
1724
|
};
|
|
1605
1725
|
run();
|
|
1606
1726
|
}, []);
|
|
1607
1727
|
const operation = props.list ? "List SSH keys" : "Add SSH key";
|
|
1608
|
-
return /* @__PURE__ */
|
|
1609
|
-
/* @__PURE__ */
|
|
1610
|
-
/* @__PURE__ */
|
|
1611
|
-
props.list && keys.length > 0 && /* @__PURE__ */
|
|
1612
|
-
/* @__PURE__ */
|
|
1613
|
-
keys.map((key, i) => /* @__PURE__ */
|
|
1728
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1729
|
+
/* @__PURE__ */ jsx7(Header, { title: "SSH Key Management", subtitle: `Host: ${props.host}` }),
|
|
1730
|
+
/* @__PURE__ */ jsx7(Task, { label: operation, status, message }),
|
|
1731
|
+
props.list && keys.length > 0 && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
1732
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: "Authorized keys:" }),
|
|
1733
|
+
keys.map((key, i) => /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
|
|
1614
1734
|
" ",
|
|
1615
1735
|
i + 1,
|
|
1616
1736
|
". ",
|
|
1617
1737
|
key
|
|
1618
1738
|
] }, i))
|
|
1619
1739
|
] }),
|
|
1620
|
-
error && /* @__PURE__ */
|
|
1740
|
+
error && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
|
|
1621
1741
|
"Error: ",
|
|
1622
1742
|
error
|
|
1623
|
-
] }) })
|
|
1743
|
+
] }) }),
|
|
1744
|
+
done && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Press 'q' or Esc to go back" }) })
|
|
1624
1745
|
] });
|
|
1625
1746
|
}
|
|
1626
1747
|
|
|
1627
1748
|
// src/commands/status.tsx
|
|
1628
1749
|
import { useState as useState5, useEffect as useEffect4 } from "react";
|
|
1629
|
-
import { Box as
|
|
1630
|
-
import { jsx as
|
|
1750
|
+
import { Box as Box8, Text as Text8, useApp as useApp4, useInput as useInput4 } from "ink";
|
|
1751
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1631
1752
|
function StatusCommand(props) {
|
|
1632
1753
|
const { exit } = useApp4();
|
|
1633
1754
|
const [taskStatus, setTaskStatus] = useState5("pending");
|
|
1634
1755
|
const [error, setError] = useState5(null);
|
|
1635
1756
|
const [services, setServices] = useState5([]);
|
|
1636
1757
|
const [system, setSystem] = useState5(null);
|
|
1758
|
+
const [done, setDone] = useState5(false);
|
|
1759
|
+
const goBack = () => {
|
|
1760
|
+
if (props.onBack) {
|
|
1761
|
+
props.onBack();
|
|
1762
|
+
} else {
|
|
1763
|
+
exit();
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
useInput4((input, key) => {
|
|
1767
|
+
if (done && (key.escape || input === "q")) {
|
|
1768
|
+
goBack();
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1637
1771
|
useEffect4(() => {
|
|
1638
1772
|
const run = async () => {
|
|
1639
1773
|
setTaskStatus("running");
|
|
@@ -1673,66 +1807,67 @@ function StatusCommand(props) {
|
|
|
1673
1807
|
});
|
|
1674
1808
|
setServices(serviceResults);
|
|
1675
1809
|
setTaskStatus("success");
|
|
1810
|
+
setDone(true);
|
|
1676
1811
|
disconnect(client);
|
|
1677
|
-
setTimeout(() => exit(), 100);
|
|
1678
1812
|
} catch (err) {
|
|
1679
1813
|
setTaskStatus("error");
|
|
1680
1814
|
setError(err instanceof Error ? err.message : String(err));
|
|
1681
|
-
|
|
1815
|
+
setDone(true);
|
|
1682
1816
|
}
|
|
1683
1817
|
};
|
|
1684
1818
|
run();
|
|
1685
1819
|
}, []);
|
|
1686
|
-
return /* @__PURE__ */
|
|
1687
|
-
/* @__PURE__ */
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
system && /* @__PURE__ */
|
|
1690
|
-
/* @__PURE__ */
|
|
1691
|
-
/* @__PURE__ */
|
|
1820
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1821
|
+
/* @__PURE__ */ jsx8(Header, { title: "Server Status", subtitle: `Host: ${props.host}` }),
|
|
1822
|
+
/* @__PURE__ */ jsx8(Task, { label: "Checking server status", status: taskStatus }),
|
|
1823
|
+
system && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1824
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "System:" }),
|
|
1825
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
|
|
1692
1826
|
" Hostname: ",
|
|
1693
|
-
/* @__PURE__ */
|
|
1827
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", children: system.hostname })
|
|
1694
1828
|
] }),
|
|
1695
|
-
/* @__PURE__ */
|
|
1829
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
|
|
1696
1830
|
" Uptime: ",
|
|
1697
|
-
/* @__PURE__ */
|
|
1831
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", children: system.uptime })
|
|
1698
1832
|
] }),
|
|
1699
|
-
/* @__PURE__ */
|
|
1833
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
|
|
1700
1834
|
" Load: ",
|
|
1701
|
-
/* @__PURE__ */
|
|
1835
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", children: system.load })
|
|
1702
1836
|
] }),
|
|
1703
|
-
/* @__PURE__ */
|
|
1837
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
|
|
1704
1838
|
" Memory: ",
|
|
1705
|
-
/* @__PURE__ */
|
|
1839
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", children: system.memory })
|
|
1706
1840
|
] }),
|
|
1707
|
-
/* @__PURE__ */
|
|
1841
|
+
/* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
|
|
1708
1842
|
" Disk: ",
|
|
1709
|
-
/* @__PURE__ */
|
|
1843
|
+
/* @__PURE__ */ jsx8(Text8, { color: "white", children: system.disk })
|
|
1710
1844
|
] })
|
|
1711
1845
|
] }),
|
|
1712
|
-
services.length > 0 && /* @__PURE__ */
|
|
1713
|
-
/* @__PURE__ */
|
|
1714
|
-
services.map((svc) => /* @__PURE__ */
|
|
1715
|
-
/* @__PURE__ */
|
|
1716
|
-
/* @__PURE__ */
|
|
1717
|
-
/* @__PURE__ */
|
|
1846
|
+
services.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1847
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, children: "Services:" }),
|
|
1848
|
+
services.map((svc) => /* @__PURE__ */ jsxs8(Box8, { children: [
|
|
1849
|
+
/* @__PURE__ */ jsx8(Text8, { color: "gray", children: " " }),
|
|
1850
|
+
/* @__PURE__ */ jsx8(Text8, { color: svc.running ? "green" : "red", children: svc.running ? "\u25CF" : "\u25CB" }),
|
|
1851
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1718
1852
|
" ",
|
|
1719
1853
|
svc.name,
|
|
1720
1854
|
": "
|
|
1721
1855
|
] }),
|
|
1722
|
-
/* @__PURE__ */
|
|
1856
|
+
/* @__PURE__ */ jsx8(Text8, { color: svc.running ? "green" : "yellow", children: svc.status })
|
|
1723
1857
|
] }, svc.name))
|
|
1724
1858
|
] }),
|
|
1725
|
-
error && /* @__PURE__ */
|
|
1859
|
+
error && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
|
|
1726
1860
|
"Error: ",
|
|
1727
1861
|
error
|
|
1728
|
-
] }) })
|
|
1862
|
+
] }) }),
|
|
1863
|
+
done && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "Press 'q' or Esc to go back" }) })
|
|
1729
1864
|
] });
|
|
1730
1865
|
}
|
|
1731
1866
|
|
|
1732
1867
|
// src/commands/deploy.tsx
|
|
1733
1868
|
import { useState as useState6, useEffect as useEffect5 } from "react";
|
|
1734
|
-
import { Box as
|
|
1735
|
-
import { jsx as
|
|
1869
|
+
import { Box as Box9, Text as Text9, useApp as useApp5, useInput as useInput5 } from "ink";
|
|
1870
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1736
1871
|
function DeployCommand(props) {
|
|
1737
1872
|
const { exit } = useApp5();
|
|
1738
1873
|
const [status, setStatus] = useState6("pending");
|
|
@@ -1751,33 +1886,42 @@ function DeployCommand(props) {
|
|
|
1751
1886
|
setOutput(result.stdout.trim().split("\n"));
|
|
1752
1887
|
setStatus("success");
|
|
1753
1888
|
disconnect(client);
|
|
1754
|
-
|
|
1889
|
+
if (!props.onBack) {
|
|
1890
|
+
setTimeout(() => exit(), 100);
|
|
1891
|
+
}
|
|
1755
1892
|
} catch (err) {
|
|
1756
1893
|
setStatus("error");
|
|
1757
1894
|
setError(err instanceof Error ? err.message : String(err));
|
|
1758
|
-
|
|
1895
|
+
if (!props.onBack) {
|
|
1896
|
+
setTimeout(() => exit(), 100);
|
|
1897
|
+
}
|
|
1759
1898
|
}
|
|
1760
1899
|
};
|
|
1761
1900
|
run();
|
|
1762
1901
|
}, []);
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1902
|
+
useInput5((input, key) => {
|
|
1903
|
+
if ((status === "success" || status === "error") && props.onBack) {
|
|
1904
|
+
props.onBack();
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
1908
|
+
/* @__PURE__ */ jsx9(Header, { title: "Deploy Application", subtitle: `App: ${props.name} | Host: ${props.host}` }),
|
|
1909
|
+
/* @__PURE__ */ jsx9(Task, { label: `Deploying ${props.name}`, status }),
|
|
1910
|
+
output.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: output.map((line, i) => /* @__PURE__ */ jsx9(Text9, { color: "gray", children: line }, i)) }),
|
|
1911
|
+
error && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
|
|
1768
1912
|
"Error: ",
|
|
1769
1913
|
error
|
|
1770
1914
|
] }) }),
|
|
1771
|
-
status === "success" && /* @__PURE__ */
|
|
1915
|
+
status === "success" && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "\u2713 Deployment complete. Press any key to return." }) })
|
|
1772
1916
|
] });
|
|
1773
1917
|
}
|
|
1774
1918
|
|
|
1775
1919
|
// src/commands/config.tsx
|
|
1776
|
-
import { useState as useState7, useEffect as useEffect6 } from "react";
|
|
1777
|
-
import { Box as
|
|
1920
|
+
import React7, { useState as useState7, useEffect as useEffect6 } from "react";
|
|
1921
|
+
import { Box as Box10, Text as Text10, useApp as useApp6, useInput as useInput6 } from "ink";
|
|
1778
1922
|
import SelectInput2 from "ink-select-input";
|
|
1779
1923
|
import TextInput2 from "ink-text-input";
|
|
1780
|
-
import { jsx as
|
|
1924
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1781
1925
|
var SCRIPTS3 = {
|
|
1782
1926
|
// Read current config
|
|
1783
1927
|
getConfig: (name) => `
|
|
@@ -1791,12 +1935,65 @@ var SCRIPTS3 = {
|
|
|
1791
1935
|
cd "$APP_DIR"
|
|
1792
1936
|
REPO=$(git remote get-url origin 2>/dev/null || echo "")
|
|
1793
1937
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
1794
|
-
echo '{"repo":"'"$REPO"'","branch":"'"$BRANCH"'","webhookEnabled":false,"webhookPort":0}'
|
|
1938
|
+
echo '{"repo":"'"$REPO"'","branch":"'"$BRANCH"'","webhookEnabled":false,"webhookPort":0,"buildCmd":"npm run build","startCmd":"npm start"}'
|
|
1795
1939
|
else
|
|
1796
1940
|
echo '{"error":"App not found or not a git repository"}'
|
|
1797
1941
|
fi
|
|
1798
1942
|
fi
|
|
1799
1943
|
`,
|
|
1944
|
+
// Get full app footprint
|
|
1945
|
+
getAppFootprint: (name, user) => `
|
|
1946
|
+
echo '{'
|
|
1947
|
+
|
|
1948
|
+
# Paths
|
|
1949
|
+
APP_DIR="/var/www/${name}"
|
|
1950
|
+
echo '"paths": {'
|
|
1951
|
+
echo '"appDir": "'$APP_DIR'",'
|
|
1952
|
+
echo '"config": "'$APP_DIR/.provisor.json'",'
|
|
1953
|
+
echo '"updateScript": "/usr/local/bin/update-'$name'",'
|
|
1954
|
+
echo '"webhookService": "/etc/systemd/system/webhook-'$name'.service",'
|
|
1955
|
+
echo '"pollScript": "/usr/local/bin/poll-'$name'.sh",'
|
|
1956
|
+
echo '"pollService": "/etc/systemd/system/poll-'$name'.service",'
|
|
1957
|
+
echo '"pollTimer": "/etc/systemd/system/poll-'$name'.timer"'
|
|
1958
|
+
echo '},'
|
|
1959
|
+
|
|
1960
|
+
# Statuses
|
|
1961
|
+
echo '"status": {'
|
|
1962
|
+
|
|
1963
|
+
# PM2 Status
|
|
1964
|
+
# We try to use pm2 jlist. If running as deploy user, we might need sudo -u deploy?
|
|
1965
|
+
# Usually pm2 is run by the user.
|
|
1966
|
+
PM2_STATUS="offline"
|
|
1967
|
+
if command -v pm2 >/dev/null 2>&1; then
|
|
1968
|
+
# Try current user first
|
|
1969
|
+
if pm2 describe $name > /dev/null 2>&1; then
|
|
1970
|
+
PM2_STATUS=$(pm2 jlist | grep -oP '"name":"'$name'".*?"status":"\\K[^"]+')
|
|
1971
|
+
else
|
|
1972
|
+
# Try sudo -u user
|
|
1973
|
+
PM2_STATUS=$(sudo -u ${user} pm2 jlist 2>/dev/null | grep -oP '"name":"'$name'".*?"status":"\\K[^"]+' || echo "offline")
|
|
1974
|
+
fi
|
|
1975
|
+
elif sudo -u ${user} command -v pm2 >/dev/null 2>&1; then
|
|
1976
|
+
PM2_STATUS=$(sudo -u ${user} pm2 jlist 2>/dev/null | grep -oP '"name":"'$name'".*?"status":"\\K[^"]+' || echo "offline")
|
|
1977
|
+
fi
|
|
1978
|
+
# Cleanup grep result
|
|
1979
|
+
PM2_STATUS=$(echo $PM2_STATUS | tr -d '
|
|
1980
|
+
')
|
|
1981
|
+
if [ -z "$PM2_STATUS" ]; then PM2_STATUS="offline"; fi
|
|
1982
|
+
echo '"pm2": "'$PM2_STATUS'",'
|
|
1983
|
+
|
|
1984
|
+
# Webhook Status
|
|
1985
|
+
WEBHOOK="inactive"
|
|
1986
|
+
if systemctl is-active --quiet webhook-$name; then WEBHOOK="active"; fi
|
|
1987
|
+
echo '"webhook": "'$WEBHOOK'",'
|
|
1988
|
+
|
|
1989
|
+
# Polling Status
|
|
1990
|
+
POLL="inactive"
|
|
1991
|
+
if systemctl is-active --quiet poll-$name.timer; then POLL="active"; fi
|
|
1992
|
+
echo '"polling": "'$POLL'"'
|
|
1993
|
+
|
|
1994
|
+
echo '}'
|
|
1995
|
+
echo '}'
|
|
1996
|
+
`,
|
|
1800
1997
|
// Save config
|
|
1801
1998
|
saveConfig: (name, config) => `
|
|
1802
1999
|
CONFIG_FILE="/var/www/${name}/.provisor.json"
|
|
@@ -2087,6 +2284,30 @@ DAEMON_EOF
|
|
|
2087
2284
|
rm -f /usr/local/bin/poll-${name}-daemon.sh
|
|
2088
2285
|
|
|
2089
2286
|
echo "polling-disabled"
|
|
2287
|
+
`,
|
|
2288
|
+
// Update Log Settings
|
|
2289
|
+
updateLogSettings: (name, source, path3) => `
|
|
2290
|
+
CONFIG_FILE="/var/www/${name}/.provisor.json"
|
|
2291
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
2292
|
+
TMP_FILE="/tmp/.provisor.json.tmp"
|
|
2293
|
+
# Use jq if available for safe editing, otherwise fallback to simple replacement or append
|
|
2294
|
+
# Since we don't assume jq, let's read the file in nodejs on the server to be safe
|
|
2295
|
+
|
|
2296
|
+
cat << 'NODE_EOF' > /tmp/update-config-${name}.js
|
|
2297
|
+
const fs = require('fs');
|
|
2298
|
+
const config = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
|
|
2299
|
+
if ('${source}') config.logSource = '${source}';
|
|
2300
|
+
if ('${path3}') config.logPath = '${path3}';
|
|
2301
|
+
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(config));
|
|
2302
|
+
NODE_EOF
|
|
2303
|
+
|
|
2304
|
+
node /tmp/update-config-${name}.js
|
|
2305
|
+
rm /tmp/update-config-${name}.js
|
|
2306
|
+
chmod 600 "$CONFIG_FILE"
|
|
2307
|
+
echo "log-settings-updated"
|
|
2308
|
+
else
|
|
2309
|
+
echo "config-not-found"
|
|
2310
|
+
fi
|
|
2090
2311
|
`
|
|
2091
2312
|
};
|
|
2092
2313
|
var configActions = [
|
|
@@ -2099,7 +2320,10 @@ var configActions = [
|
|
|
2099
2320
|
{ label: "Disable webhook", value: "disable-webhook" },
|
|
2100
2321
|
{ label: "Change polling interval", value: "polling-interval" },
|
|
2101
2322
|
{ label: "Enable git polling", value: "enable-polling" },
|
|
2102
|
-
{ label: "Disable git polling", value: "disable-polling" }
|
|
2323
|
+
{ label: "Disable git polling", value: "disable-polling" },
|
|
2324
|
+
{ label: "Change log source (pm2, file, systemd)", value: "log-source" },
|
|
2325
|
+
{ label: "Set custom log path", value: "log-path" },
|
|
2326
|
+
{ label: "< Back", value: "__back__" }
|
|
2103
2327
|
];
|
|
2104
2328
|
function ConfigCommand(props) {
|
|
2105
2329
|
const { exit } = useApp6();
|
|
@@ -2115,6 +2339,8 @@ function ConfigCommand(props) {
|
|
|
2115
2339
|
const [config, setConfig] = useState7(null);
|
|
2116
2340
|
const [deployKey, setDeployKey] = useState7(null);
|
|
2117
2341
|
const [webhookStatus, setWebhookStatus] = useState7("");
|
|
2342
|
+
const [pollingStatus, setPollingStatus] = useState7("");
|
|
2343
|
+
const [configLoaded, setConfigLoaded] = useState7(false);
|
|
2118
2344
|
useEffect6(() => {
|
|
2119
2345
|
if (props.show) setAction("show");
|
|
2120
2346
|
else if (props.repo) {
|
|
@@ -2133,7 +2359,15 @@ function ConfigCommand(props) {
|
|
|
2133
2359
|
setAction("polling-interval");
|
|
2134
2360
|
setInputValue(String(props.pollingInterval));
|
|
2135
2361
|
} else if (props.enablePolling) setAction("enable-polling");
|
|
2362
|
+
else if (props.enablePolling) setAction("enable-polling");
|
|
2136
2363
|
else if (props.disablePolling) setAction("disable-polling");
|
|
2364
|
+
else if (props.logSource) {
|
|
2365
|
+
setAction("log-source");
|
|
2366
|
+
setInputValue(props.logSource);
|
|
2367
|
+
} else if (props.logPath) {
|
|
2368
|
+
setAction("log-path");
|
|
2369
|
+
setInputValue(props.logPath);
|
|
2370
|
+
}
|
|
2137
2371
|
}, []);
|
|
2138
2372
|
useEffect6(() => {
|
|
2139
2373
|
const run = async () => {
|
|
@@ -2149,6 +2383,30 @@ function ConfigCommand(props) {
|
|
|
2149
2383
|
};
|
|
2150
2384
|
run();
|
|
2151
2385
|
}, []);
|
|
2386
|
+
const clientRef = React7.useRef(null);
|
|
2387
|
+
useEffect6(() => {
|
|
2388
|
+
clientRef.current = client;
|
|
2389
|
+
return () => {
|
|
2390
|
+
if (clientRef.current) disconnect(clientRef.current);
|
|
2391
|
+
};
|
|
2392
|
+
}, [client]);
|
|
2393
|
+
useInput6((input, key) => {
|
|
2394
|
+
if (key.escape) {
|
|
2395
|
+
if (enteringValue) {
|
|
2396
|
+
setEnteringValue(false);
|
|
2397
|
+
} else if (action && (output.length > 0 || error)) {
|
|
2398
|
+
setAction(null);
|
|
2399
|
+
setOutput([]);
|
|
2400
|
+
setError(null);
|
|
2401
|
+
setInputValue("");
|
|
2402
|
+
setSelectingAction(true);
|
|
2403
|
+
} else {
|
|
2404
|
+
if (client) disconnect(client);
|
|
2405
|
+
if (props.onBack) props.onBack();
|
|
2406
|
+
else exit();
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2152
2410
|
useEffect6(() => {
|
|
2153
2411
|
if (status === "success" && action === null && !selectingAction) {
|
|
2154
2412
|
setSelectingAction(true);
|
|
@@ -2170,6 +2428,15 @@ function ConfigCommand(props) {
|
|
|
2170
2428
|
} else if (selectedAction === "polling-interval") {
|
|
2171
2429
|
setInputLabel("Enter polling interval in seconds (e.g., 10, 30, 60):");
|
|
2172
2430
|
setEnteringValue(true);
|
|
2431
|
+
} else if (selectedAction === "log-source") {
|
|
2432
|
+
setInputLabel("Enter log source (pm2, file, or systemd):");
|
|
2433
|
+
setEnteringValue(true);
|
|
2434
|
+
} else if (selectedAction === "log-path") {
|
|
2435
|
+
setEnteringValue(true);
|
|
2436
|
+
} else if (selectedAction === "__back__") {
|
|
2437
|
+
if (client) disconnect(client);
|
|
2438
|
+
if (props.onBack) props.onBack();
|
|
2439
|
+
else exit();
|
|
2173
2440
|
}
|
|
2174
2441
|
};
|
|
2175
2442
|
const handleInputSubmit = (value) => {
|
|
@@ -2178,7 +2445,7 @@ function ConfigCommand(props) {
|
|
|
2178
2445
|
};
|
|
2179
2446
|
useEffect6(() => {
|
|
2180
2447
|
if (!client || action === null || enteringValue) return;
|
|
2181
|
-
if ((action === "repo" || action === "branch" || action === "webhook-secret" || action === "polling-interval") && !inputValue) return;
|
|
2448
|
+
if ((action === "repo" || action === "branch" || action === "webhook-secret" || action === "polling-interval" || action === "log-source" || action === "log-path") && !inputValue) return;
|
|
2182
2449
|
const run = async () => {
|
|
2183
2450
|
try {
|
|
2184
2451
|
const user = props.user || "deploy";
|
|
@@ -2191,31 +2458,62 @@ function ConfigCommand(props) {
|
|
|
2191
2458
|
setError(configData.error);
|
|
2192
2459
|
return;
|
|
2193
2460
|
}
|
|
2461
|
+
const footprintResult = await exec(client, SCRIPTS3.getAppFootprint(props.name, user));
|
|
2462
|
+
let footprint = {};
|
|
2463
|
+
try {
|
|
2464
|
+
footprint = JSON.parse(footprintResult.stdout.trim());
|
|
2465
|
+
} catch (e) {
|
|
2466
|
+
console.error("Failed to parse footprint JSON", footprintResult.stdout);
|
|
2467
|
+
}
|
|
2194
2468
|
const keyResult = await exec(client, SCRIPTS3.getDeployKey(props.name, user));
|
|
2195
2469
|
const webhookResult = await exec(client, SCRIPTS3.getWebhookStatus(props.name));
|
|
2196
2470
|
const pollingResult = await exec(client, SCRIPTS3.getPollingStatus(props.name));
|
|
2471
|
+
lines.push("--- STATUS ---");
|
|
2472
|
+
const pm2Status = footprint.status?.pm2 === "online" ? "\u{1F7E2} Online" : "\u{1F534} Offline";
|
|
2473
|
+
lines.push(`App (PM2): ${pm2Status}`);
|
|
2474
|
+
const whStatus = footprint.status?.webhook === "active" ? "\u{1F7E2} Active" : "\u26AB Inactive";
|
|
2475
|
+
lines.push(`Webhook: ${whStatus}`);
|
|
2476
|
+
const plStatus = footprint.status?.polling === "active" ? "\u{1F7E2} Active" : "\u26AB Inactive";
|
|
2477
|
+
lines.push(`Polling: ${plStatus}`);
|
|
2478
|
+
lines.push("");
|
|
2479
|
+
lines.push("--- CONFIGURATION ---");
|
|
2197
2480
|
lines.push(`Repository: ${configData.repo || "Not configured"}`);
|
|
2198
|
-
lines.push(`Branch:
|
|
2481
|
+
lines.push(`Branch: ${configData.branch || "main"}`);
|
|
2482
|
+
lines.push(`Build Cmd: ${configData.buildCmd || "npm run build"}`);
|
|
2483
|
+
lines.push(`Start Cmd: ${configData.startCmd || "npm start"}`);
|
|
2199
2484
|
lines.push(`Deploy Key: ${keyResult.stdout.trim() ? "Configured" : "Not configured"}`);
|
|
2485
|
+
lines.push(`Log Source: ${configData.logSource || "pm2"}`);
|
|
2486
|
+
if (configData.logPath) {
|
|
2487
|
+
lines.push(`Log Path: ${configData.logPath}`);
|
|
2488
|
+
}
|
|
2200
2489
|
const webhookStatusStr = webhookResult.stdout.trim();
|
|
2201
2490
|
if (webhookStatusStr.startsWith("running:")) {
|
|
2202
2491
|
const port = webhookStatusStr.split(":")[1];
|
|
2203
|
-
lines.push(`Webhook:
|
|
2204
|
-
} else {
|
|
2205
|
-
lines.push("Webhook: Not configured");
|
|
2492
|
+
lines.push(`Webhook Port: ${port}`);
|
|
2206
2493
|
}
|
|
2207
2494
|
const pollingStatusStr = pollingResult.stdout.trim();
|
|
2208
2495
|
if (pollingStatusStr.startsWith("running:")) {
|
|
2209
2496
|
const parts = pollingStatusStr.split(":");
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2497
|
+
lines.push(`Polling Interval: ${parts[1]}s (${parts[2] || "systemd"})`);
|
|
2498
|
+
}
|
|
2499
|
+
lines.push("");
|
|
2500
|
+
lines.push("--- FOOTPRINT (Server Paths) ---");
|
|
2501
|
+
if (footprint.paths) {
|
|
2502
|
+
lines.push(`App Directory: ${footprint.paths.appDir}`);
|
|
2503
|
+
lines.push(`Config File: ${footprint.paths.config}`);
|
|
2504
|
+
lines.push(`Update Script: ${footprint.paths.updateScript}`);
|
|
2505
|
+
if (footprint.status?.webhook === "active") {
|
|
2506
|
+
lines.push(`Webhook Service: ${footprint.paths.webhookService}`);
|
|
2507
|
+
}
|
|
2508
|
+
if (footprint.status?.polling === "active") {
|
|
2509
|
+
lines.push(`Poll Script: ${footprint.paths.pollScript}`);
|
|
2510
|
+
lines.push(`Poll Service: ${footprint.paths.pollService}`);
|
|
2511
|
+
lines.push(`Poll Timer: ${footprint.paths.pollTimer}`);
|
|
2512
|
+
}
|
|
2215
2513
|
}
|
|
2216
2514
|
if (keyResult.stdout.trim()) {
|
|
2217
2515
|
lines.push("");
|
|
2218
|
-
lines.push("
|
|
2516
|
+
lines.push("--- PUBLIC KEY ---");
|
|
2219
2517
|
lines.push(keyResult.stdout.trim());
|
|
2220
2518
|
}
|
|
2221
2519
|
break;
|
|
@@ -2303,34 +2601,95 @@ function ConfigCommand(props) {
|
|
|
2303
2601
|
lines.push("Git polling disabled.");
|
|
2304
2602
|
break;
|
|
2305
2603
|
}
|
|
2604
|
+
case "log-source": {
|
|
2605
|
+
if (!["pm2", "file", "systemd"].includes(inputValue)) {
|
|
2606
|
+
setError("Invalid source. Must be pm2, file, or systemd.");
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
await execScript(client, SCRIPTS3.updateLogSettings(props.name, inputValue, ""), true);
|
|
2610
|
+
lines.push(`Log source updated to: ${inputValue}`);
|
|
2611
|
+
break;
|
|
2612
|
+
}
|
|
2613
|
+
case "log-path": {
|
|
2614
|
+
await execScript(client, SCRIPTS3.updateLogSettings(props.name, "", inputValue), true);
|
|
2615
|
+
lines.push(`Log path updated to: ${inputValue}`);
|
|
2616
|
+
break;
|
|
2617
|
+
}
|
|
2306
2618
|
}
|
|
2307
2619
|
setOutput(lines);
|
|
2308
|
-
disconnect(client);
|
|
2309
|
-
setTimeout(() => exit(), 100);
|
|
2310
2620
|
} catch (err) {
|
|
2311
2621
|
setError(`Operation failed: ${err instanceof Error ? err.message : err}`);
|
|
2312
|
-
if (client) disconnect(client);
|
|
2313
|
-
setTimeout(() => exit(), 100);
|
|
2314
2622
|
}
|
|
2315
2623
|
};
|
|
2316
2624
|
run();
|
|
2317
2625
|
}, [client, action, inputValue, enteringValue]);
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2626
|
+
useEffect6(() => {
|
|
2627
|
+
if (!client || configLoaded) return;
|
|
2628
|
+
const fetchConfig = async () => {
|
|
2629
|
+
try {
|
|
2630
|
+
const user = props.user || "deploy";
|
|
2631
|
+
const [cfgRes, keyRes, whRes, pollRes] = await Promise.all([
|
|
2632
|
+
exec(client, SCRIPTS3.getConfig(props.name)),
|
|
2633
|
+
exec(client, SCRIPTS3.getDeployKey(props.name, user)),
|
|
2634
|
+
exec(client, SCRIPTS3.getWebhookStatus(props.name)),
|
|
2635
|
+
exec(client, SCRIPTS3.getPollingStatus(props.name))
|
|
2636
|
+
]);
|
|
2637
|
+
try {
|
|
2638
|
+
const cfgData = JSON.parse(cfgRes.stdout.trim());
|
|
2639
|
+
setConfig(cfgData);
|
|
2640
|
+
} catch (e) {
|
|
2641
|
+
setConfig({});
|
|
2642
|
+
}
|
|
2643
|
+
setDeployKey(keyRes.stdout.trim());
|
|
2644
|
+
setWebhookStatus(whRes.stdout.trim());
|
|
2645
|
+
setPollingStatus(pollRes.stdout.trim());
|
|
2646
|
+
setConfigLoaded(true);
|
|
2647
|
+
} catch (e) {
|
|
2648
|
+
setConfigLoaded(true);
|
|
2649
|
+
}
|
|
2650
|
+
};
|
|
2651
|
+
fetchConfig();
|
|
2652
|
+
}, [client]);
|
|
2653
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2654
|
+
/* @__PURE__ */ jsx10(Header, { title: "App Configuration", subtitle: `App: ${props.name} | Host: ${props.host}` }),
|
|
2655
|
+
/* @__PURE__ */ jsx10(Task, { label: "Connect to server", status }),
|
|
2656
|
+
status === "success" && !configLoaded && /* @__PURE__ */ jsx10(Task, { label: "Fetching current configuration", status: "running" }),
|
|
2657
|
+
selectingAction && action === null && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
2658
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: "Select action:" }),
|
|
2659
|
+
/* @__PURE__ */ jsx10(
|
|
2660
|
+
SelectInput2,
|
|
2661
|
+
{
|
|
2662
|
+
items: configActions.map((item) => {
|
|
2663
|
+
if (!configLoaded) return item;
|
|
2664
|
+
let label = item.label;
|
|
2665
|
+
const c = config || {};
|
|
2666
|
+
if (item.value === "repo" && c.repo) label += ` (current: ${c.repo})`;
|
|
2667
|
+
else if (item.value === "branch" && c.branch) label += ` (current: ${c.branch})`;
|
|
2668
|
+
else if (item.value === "polling-interval" && pollingStatus.startsWith("running:")) {
|
|
2669
|
+
const interval = pollingStatus.split(":")[1];
|
|
2670
|
+
label += ` (current: ${interval}s)`;
|
|
2671
|
+
} else if (item.value === "log-source" && c.logSource) label += ` (current: ${c.logSource})`;
|
|
2672
|
+
else if (item.value === "delete-key" && !deployKey) return null;
|
|
2673
|
+
else if (item.value === "new-key" && deployKey) label = "Regenerate deploy key";
|
|
2674
|
+
return { ...item, label };
|
|
2675
|
+
}).filter(Boolean),
|
|
2676
|
+
onSelect: handleActionSelect
|
|
2677
|
+
},
|
|
2678
|
+
configLoaded ? "loaded" : "loading"
|
|
2679
|
+
)
|
|
2324
2680
|
] }),
|
|
2325
|
-
enteringValue && /* @__PURE__ */
|
|
2326
|
-
/* @__PURE__ */
|
|
2327
|
-
/* @__PURE__ */
|
|
2328
|
-
/* @__PURE__ */
|
|
2329
|
-
/* @__PURE__ */
|
|
2681
|
+
enteringValue && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
2682
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: inputLabel }),
|
|
2683
|
+
/* @__PURE__ */ jsxs10(Box10, { children: [
|
|
2684
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", children: "> " }),
|
|
2685
|
+
/* @__PURE__ */ jsx10(TextInput2, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit })
|
|
2330
2686
|
] })
|
|
2331
2687
|
] }),
|
|
2332
|
-
output.length > 0 && /* @__PURE__ */
|
|
2333
|
-
|
|
2688
|
+
output.length > 0 && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
2689
|
+
output.map((line, i) => /* @__PURE__ */ jsx10(Text10, { color: line.startsWith("ssh-") ? "cyan" : "white", children: line }, i)),
|
|
2690
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Press Esc to go back" }) })
|
|
2691
|
+
] }),
|
|
2692
|
+
error && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
|
|
2334
2693
|
"Error: ",
|
|
2335
2694
|
error
|
|
2336
2695
|
] }) })
|
|
@@ -2339,7 +2698,7 @@ function ConfigCommand(props) {
|
|
|
2339
2698
|
|
|
2340
2699
|
// src/commands/update.tsx
|
|
2341
2700
|
import { useState as useState8, useEffect as useEffect7 } from "react";
|
|
2342
|
-
import { Box as
|
|
2701
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
2343
2702
|
import Spinner3 from "ink-spinner";
|
|
2344
2703
|
import SelectInput3 from "ink-select-input";
|
|
2345
2704
|
|
|
@@ -2349,7 +2708,7 @@ import { execSync } from "child_process";
|
|
|
2349
2708
|
// package.json
|
|
2350
2709
|
var package_default = {
|
|
2351
2710
|
name: "@it-club/provisor",
|
|
2352
|
-
version: "0.
|
|
2711
|
+
version: "0.3.0",
|
|
2353
2712
|
description: "Server provisioning and deployment CLI tool",
|
|
2354
2713
|
type: "module",
|
|
2355
2714
|
bin: {
|
|
@@ -2456,7 +2815,7 @@ function performUpdate() {
|
|
|
2456
2815
|
}
|
|
2457
2816
|
|
|
2458
2817
|
// src/commands/update.tsx
|
|
2459
|
-
import { jsx as
|
|
2818
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2460
2819
|
function UpdateCommand({ check = false }) {
|
|
2461
2820
|
const [stage, setStage] = useState8("checking");
|
|
2462
2821
|
const [updateInfo, setUpdateInfo] = useState8(null);
|
|
@@ -2493,51 +2852,51 @@ function UpdateCommand({ check = false }) {
|
|
|
2493
2852
|
}
|
|
2494
2853
|
};
|
|
2495
2854
|
if (stage === "checking") {
|
|
2496
|
-
return /* @__PURE__ */
|
|
2497
|
-
/* @__PURE__ */
|
|
2498
|
-
/* @__PURE__ */
|
|
2855
|
+
return /* @__PURE__ */ jsxs11(Box11, { children: [
|
|
2856
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner3, { type: "dots" }) }),
|
|
2857
|
+
/* @__PURE__ */ jsx11(Text11, { children: " Checking for updates..." })
|
|
2499
2858
|
] });
|
|
2500
2859
|
}
|
|
2501
2860
|
if (stage === "error") {
|
|
2502
|
-
return /* @__PURE__ */
|
|
2861
|
+
return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
|
|
2503
2862
|
"\u2717 Error: ",
|
|
2504
2863
|
error || resultMessage
|
|
2505
2864
|
] }) });
|
|
2506
2865
|
}
|
|
2507
2866
|
if (stage === "show-result" && updateInfo) {
|
|
2508
|
-
return /* @__PURE__ */
|
|
2509
|
-
/* @__PURE__ */
|
|
2510
|
-
/* @__PURE__ */
|
|
2511
|
-
/* @__PURE__ */
|
|
2867
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
|
|
2868
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
2869
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Current version: " }),
|
|
2870
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", children: updateInfo.currentVersion })
|
|
2512
2871
|
] }),
|
|
2513
|
-
/* @__PURE__ */
|
|
2514
|
-
/* @__PURE__ */
|
|
2515
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
2873
|
+
/* @__PURE__ */ jsx11(Text11, { children: "Latest version: " }),
|
|
2874
|
+
/* @__PURE__ */ jsx11(Text11, { color: updateInfo.updateAvailable ? "green" : "cyan", children: updateInfo.latestVersion })
|
|
2516
2875
|
] }),
|
|
2517
|
-
updateInfo.updateAvailable ? /* @__PURE__ */
|
|
2518
|
-
/* @__PURE__ */
|
|
2519
|
-
/* @__PURE__ */
|
|
2520
|
-
updateInfo.releaseUrl && /* @__PURE__ */
|
|
2876
|
+
updateInfo.updateAvailable ? /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
|
|
2877
|
+
/* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "\u26A1 A new version is available!" }),
|
|
2878
|
+
/* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Run `provisor update` to install." }),
|
|
2879
|
+
updateInfo.releaseUrl && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
|
|
2521
2880
|
"Release notes: ",
|
|
2522
2881
|
updateInfo.releaseUrl
|
|
2523
2882
|
] })
|
|
2524
|
-
] }) : /* @__PURE__ */
|
|
2883
|
+
] }) : /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713 You are using the latest version." }) })
|
|
2525
2884
|
] });
|
|
2526
2885
|
}
|
|
2527
2886
|
if (stage === "confirm" && updateInfo) {
|
|
2528
|
-
return /* @__PURE__ */
|
|
2529
|
-
/* @__PURE__ */
|
|
2530
|
-
/* @__PURE__ */
|
|
2531
|
-
/* @__PURE__ */
|
|
2532
|
-
/* @__PURE__ */
|
|
2533
|
-
/* @__PURE__ */
|
|
2887
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
|
|
2888
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
2889
|
+
/* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "\u26A1 Update available: " }),
|
|
2890
|
+
/* @__PURE__ */ jsx11(Text11, { children: updateInfo.currentVersion }),
|
|
2891
|
+
/* @__PURE__ */ jsx11(Text11, { color: "gray", children: " \u2192 " }),
|
|
2892
|
+
/* @__PURE__ */ jsx11(Text11, { color: "green", children: updateInfo.latestVersion })
|
|
2534
2893
|
] }),
|
|
2535
|
-
updateInfo.releaseUrl && /* @__PURE__ */
|
|
2894
|
+
updateInfo.releaseUrl && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
|
|
2536
2895
|
"Release notes: ",
|
|
2537
2896
|
updateInfo.releaseUrl
|
|
2538
2897
|
] }),
|
|
2539
|
-
/* @__PURE__ */
|
|
2540
|
-
/* @__PURE__ */
|
|
2898
|
+
/* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { children: "Install update? " }) }),
|
|
2899
|
+
/* @__PURE__ */ jsx11(
|
|
2541
2900
|
SelectInput3,
|
|
2542
2901
|
{
|
|
2543
2902
|
items: [
|
|
@@ -2550,46 +2909,1478 @@ function UpdateCommand({ check = false }) {
|
|
|
2550
2909
|
] });
|
|
2551
2910
|
}
|
|
2552
2911
|
if (stage === "updating") {
|
|
2553
|
-
return /* @__PURE__ */
|
|
2554
|
-
/* @__PURE__ */
|
|
2555
|
-
/* @__PURE__ */
|
|
2912
|
+
return /* @__PURE__ */ jsxs11(Box11, { children: [
|
|
2913
|
+
/* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner3, { type: "dots" }) }),
|
|
2914
|
+
/* @__PURE__ */ jsx11(Text11, { children: " Installing update..." })
|
|
2556
2915
|
] });
|
|
2557
2916
|
}
|
|
2558
2917
|
if (stage === "done") {
|
|
2559
|
-
return /* @__PURE__ */
|
|
2560
|
-
/* @__PURE__ */
|
|
2918
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
2919
|
+
/* @__PURE__ */ jsxs11(Text11, { color: "green", children: [
|
|
2561
2920
|
"\u2713 ",
|
|
2562
2921
|
resultMessage
|
|
2563
2922
|
] }),
|
|
2564
|
-
updateInfo?.updateAvailable && resultMessage.includes("successfully") && /* @__PURE__ */
|
|
2923
|
+
updateInfo?.updateAvailable && resultMessage.includes("successfully") && /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Run `provisor --version` to verify." })
|
|
2565
2924
|
] });
|
|
2566
2925
|
}
|
|
2567
2926
|
return null;
|
|
2568
2927
|
}
|
|
2569
2928
|
|
|
2929
|
+
// src/commands/logs.tsx
|
|
2930
|
+
import { useState as useState9, useEffect as useEffect8 } from "react";
|
|
2931
|
+
import { Box as Box12, Text as Text12, useApp as useApp7, useInput as useInput7, useStdin } from "ink";
|
|
2932
|
+
import SelectInput4 from "ink-select-input";
|
|
2933
|
+
import Spinner4 from "ink-spinner";
|
|
2934
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2935
|
+
var LOG_OPTIONS = [
|
|
2936
|
+
{ label: "Application Logs (PM2)", value: "app" },
|
|
2937
|
+
{ label: "Build/Deploy Logs", value: "build" },
|
|
2938
|
+
{ label: "Web Server Logs (Caddy)", value: "caddy" },
|
|
2939
|
+
{ label: "Git Polling Audit Logs", value: "audit" }
|
|
2940
|
+
];
|
|
2941
|
+
function LogsCommand(props) {
|
|
2942
|
+
const { exit } = useApp7();
|
|
2943
|
+
const { isRawModeSupported } = useStdin();
|
|
2944
|
+
const [client, setClient] = useState9(null);
|
|
2945
|
+
const [connecting, setConnecting] = useState9(true);
|
|
2946
|
+
const [error, setError] = useState9(null);
|
|
2947
|
+
const [logType, setLogType] = useState9(null);
|
|
2948
|
+
const [logs, setLogs] = useState9([]);
|
|
2949
|
+
const [appName, setAppName] = useState9(props.name || "");
|
|
2950
|
+
const [config, setConfig] = useState9(null);
|
|
2951
|
+
useInput7((input, key) => {
|
|
2952
|
+
if (key.escape || key.ctrl && input === "c") {
|
|
2953
|
+
if (logType) {
|
|
2954
|
+
if (client) disconnect(client);
|
|
2955
|
+
if (props.onBack) {
|
|
2956
|
+
props.onBack();
|
|
2957
|
+
} else {
|
|
2958
|
+
exit();
|
|
2959
|
+
}
|
|
2960
|
+
} else {
|
|
2961
|
+
if (client) disconnect(client);
|
|
2962
|
+
if (props.onBack) {
|
|
2963
|
+
props.onBack();
|
|
2964
|
+
} else {
|
|
2965
|
+
exit();
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
useEffect8(() => {
|
|
2971
|
+
const connectSSH = async () => {
|
|
2972
|
+
try {
|
|
2973
|
+
const sshClient = await connect(props);
|
|
2974
|
+
setClient(sshClient);
|
|
2975
|
+
if (props.name) {
|
|
2976
|
+
const configCmd = `
|
|
2977
|
+
if [ -f "/var/www/${props.name}/.provisor.json" ]; then
|
|
2978
|
+
cat "/var/www/${props.name}/.provisor.json"
|
|
2979
|
+
else
|
|
2980
|
+
echo "{}"
|
|
2981
|
+
fi
|
|
2982
|
+
`;
|
|
2983
|
+
const result = await exec(sshClient, configCmd);
|
|
2984
|
+
try {
|
|
2985
|
+
setConfig(JSON.parse(result.stdout.trim()));
|
|
2986
|
+
} catch (e) {
|
|
2987
|
+
setConfig({});
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
setConnecting(false);
|
|
2991
|
+
} catch (err) {
|
|
2992
|
+
setError(`Connection failed: ${err instanceof Error ? err.message : err}`);
|
|
2993
|
+
setConnecting(false);
|
|
2994
|
+
}
|
|
2995
|
+
};
|
|
2996
|
+
connectSSH();
|
|
2997
|
+
return () => {
|
|
2998
|
+
if (client) disconnect(client);
|
|
2999
|
+
};
|
|
3000
|
+
}, []);
|
|
3001
|
+
const handleSelect = (item) => {
|
|
3002
|
+
setLogType(item.value);
|
|
3003
|
+
startStreaming(item.value);
|
|
3004
|
+
};
|
|
3005
|
+
const startStreaming = async (type) => {
|
|
3006
|
+
if (!client) return;
|
|
3007
|
+
setLogs(["Connecting to log stream..."]);
|
|
3008
|
+
let command = "";
|
|
3009
|
+
const user = props.user || "deploy";
|
|
3010
|
+
const home = user === "root" ? "/root" : `/home/${user}`;
|
|
3011
|
+
switch (type) {
|
|
3012
|
+
case "app":
|
|
3013
|
+
const source = config?.logSource || "pm2";
|
|
3014
|
+
const path3 = config?.logPath;
|
|
3015
|
+
if (source === "file" && path3) {
|
|
3016
|
+
command = `tail -f ${path3}`;
|
|
3017
|
+
} else if (source === "systemd" && path3) {
|
|
3018
|
+
command = `sudo journalctl -u ${path3} -f -n 50`;
|
|
3019
|
+
} else {
|
|
3020
|
+
command = `tail -f ${home}/.pm2/logs/${appName}-out.log ${home}/.pm2/logs/${appName}-error.log`;
|
|
3021
|
+
}
|
|
3022
|
+
break;
|
|
3023
|
+
case "caddy":
|
|
3024
|
+
command = "sudo journalctl -u caddy -f -n 50";
|
|
3025
|
+
break;
|
|
3026
|
+
case "audit":
|
|
3027
|
+
command = `
|
|
3028
|
+
if [ -f "/var/log/poll-${appName}.log" ]; then
|
|
3029
|
+
tail -f "/var/log/poll-${appName}.log"
|
|
3030
|
+
else
|
|
3031
|
+
sudo journalctl -u poll-${appName} -f -n 50
|
|
3032
|
+
fi
|
|
3033
|
+
`;
|
|
3034
|
+
break;
|
|
3035
|
+
case "build":
|
|
3036
|
+
setLogs(["Build logs are only available during deployment currently."]);
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
try {
|
|
3040
|
+
await execStream(
|
|
3041
|
+
client,
|
|
3042
|
+
command,
|
|
3043
|
+
(data) => setLogs((prev) => [...prev, ...data.split("\n")].slice(-50)),
|
|
3044
|
+
// Keep last 50 lines
|
|
3045
|
+
(data) => setLogs((prev) => [...prev, ...data.split("\n")].slice(-50))
|
|
3046
|
+
);
|
|
3047
|
+
} catch (err) {
|
|
3048
|
+
}
|
|
3049
|
+
};
|
|
3050
|
+
if (error) {
|
|
3051
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
|
|
3052
|
+
/* @__PURE__ */ jsx12(Header, { title: "Server Logs" }),
|
|
3053
|
+
/* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
|
|
3054
|
+
"Error: ",
|
|
3055
|
+
error
|
|
3056
|
+
] })
|
|
3057
|
+
] });
|
|
3058
|
+
}
|
|
3059
|
+
if (connecting) {
|
|
3060
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
|
|
3061
|
+
/* @__PURE__ */ jsx12(Header, { title: "Server Logs" }),
|
|
3062
|
+
/* @__PURE__ */ jsxs12(Text12, { children: [
|
|
3063
|
+
/* @__PURE__ */ jsx12(Spinner4, { type: "dots" }),
|
|
3064
|
+
" Connecting to server..."
|
|
3065
|
+
] })
|
|
3066
|
+
] });
|
|
3067
|
+
}
|
|
3068
|
+
if (!appName) {
|
|
3069
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
|
|
3070
|
+
/* @__PURE__ */ jsx12(Header, { title: "Server Logs" }),
|
|
3071
|
+
/* @__PURE__ */ jsx12(Text12, { color: "red", children: "App name is required. Use -n <app>" })
|
|
3072
|
+
] });
|
|
3073
|
+
}
|
|
3074
|
+
if (!logType) {
|
|
3075
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
|
|
3076
|
+
/* @__PURE__ */ jsx12(Header, { title: `Logs: ${appName}` }),
|
|
3077
|
+
/* @__PURE__ */ jsx12(Text12, { children: "Select log to view:" }),
|
|
3078
|
+
/* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(SelectInput4, { items: LOG_OPTIONS, onSelect: handleSelect }) })
|
|
3079
|
+
] });
|
|
3080
|
+
}
|
|
3081
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
|
|
3082
|
+
/* @__PURE__ */ jsx12(Header, { title: `Logs: ${appName} (${logType})` }),
|
|
3083
|
+
/* @__PURE__ */ jsx12(
|
|
3084
|
+
Box12,
|
|
3085
|
+
{
|
|
3086
|
+
flexDirection: "column",
|
|
3087
|
+
borderStyle: "single",
|
|
3088
|
+
padding: 1,
|
|
3089
|
+
height: 20,
|
|
3090
|
+
children: logs.map((line, i) => /* @__PURE__ */ jsx12(Text12, { wrap: "wrap", children: line }, i))
|
|
3091
|
+
}
|
|
3092
|
+
),
|
|
3093
|
+
/* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Press Ctrl+C to exit" })
|
|
3094
|
+
] });
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// src/utils/server.ts
|
|
3098
|
+
import fs from "fs";
|
|
3099
|
+
import path from "path";
|
|
3100
|
+
import os from "os";
|
|
3101
|
+
var CONFIG_DIR = path.join(os.homedir(), ".provisor");
|
|
3102
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "servers.json");
|
|
3103
|
+
function ensureConfigDir() {
|
|
3104
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
3105
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
function loadServers() {
|
|
3109
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
3110
|
+
return {};
|
|
3111
|
+
}
|
|
3112
|
+
try {
|
|
3113
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
3114
|
+
} catch (err) {
|
|
3115
|
+
return {};
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
function saveServers(servers) {
|
|
3119
|
+
ensureConfigDir();
|
|
3120
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(servers, null, 2));
|
|
3121
|
+
}
|
|
3122
|
+
var ServerManager = {
|
|
3123
|
+
addServer: (alias, config) => {
|
|
3124
|
+
const servers = loadServers();
|
|
3125
|
+
servers[alias] = config;
|
|
3126
|
+
saveServers(servers);
|
|
3127
|
+
},
|
|
3128
|
+
removeServer: (alias) => {
|
|
3129
|
+
const servers = loadServers();
|
|
3130
|
+
delete servers[alias];
|
|
3131
|
+
saveServers(servers);
|
|
3132
|
+
},
|
|
3133
|
+
getServer: (alias) => {
|
|
3134
|
+
const servers = loadServers();
|
|
3135
|
+
return servers[alias] || null;
|
|
3136
|
+
},
|
|
3137
|
+
getApps: (alias) => {
|
|
3138
|
+
const servers = loadServers();
|
|
3139
|
+
return servers[alias]?.apps || null;
|
|
3140
|
+
},
|
|
3141
|
+
updateApps: (alias, apps) => {
|
|
3142
|
+
const servers = loadServers();
|
|
3143
|
+
if (servers[alias]) {
|
|
3144
|
+
servers[alias].apps = apps;
|
|
3145
|
+
saveServers(servers);
|
|
3146
|
+
}
|
|
3147
|
+
},
|
|
3148
|
+
listServers: () => {
|
|
3149
|
+
return loadServers();
|
|
3150
|
+
}
|
|
3151
|
+
};
|
|
3152
|
+
|
|
3153
|
+
// src/commands/servers.tsx
|
|
3154
|
+
import { useState as useState11, useEffect as useEffect10 } from "react";
|
|
3155
|
+
import { Box as Box14, Text as Text14, useApp as useApp9 } from "ink";
|
|
3156
|
+
import SelectInput6 from "ink-select-input";
|
|
3157
|
+
import TextInput3 from "ink-text-input";
|
|
3158
|
+
|
|
3159
|
+
// src/commands/caddy.tsx
|
|
3160
|
+
import { useState as useState10, useEffect as useEffect9 } from "react";
|
|
3161
|
+
import { Box as Box13, Text as Text13, useApp as useApp8, useInput as useInput8 } from "ink";
|
|
3162
|
+
import SelectInput5 from "ink-select-input";
|
|
3163
|
+
import Spinner5 from "ink-spinner";
|
|
3164
|
+
import fs2 from "fs";
|
|
3165
|
+
import os2 from "os";
|
|
3166
|
+
import path2 from "path";
|
|
3167
|
+
import { spawn } from "child_process";
|
|
3168
|
+
import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3169
|
+
function CaddyCommand(props) {
|
|
3170
|
+
const { exit } = useApp8();
|
|
3171
|
+
const [client, setClient] = useState10(null);
|
|
3172
|
+
const [connectStatus, setConnectStatus] = useState10("pending");
|
|
3173
|
+
const [error, setError] = useState10(null);
|
|
3174
|
+
const [mode, setMode] = useState10("menu");
|
|
3175
|
+
const [caddyContent, setCaddyContent] = useState10("");
|
|
3176
|
+
const [logsContent, setLogsContent] = useState10("");
|
|
3177
|
+
const [saveStatus, setSaveStatus] = useState10("");
|
|
3178
|
+
const [restartStatus, setRestartStatus] = useState10("pending");
|
|
3179
|
+
const [restartMessage, setRestartMessage] = useState10("");
|
|
3180
|
+
const goBack = () => {
|
|
3181
|
+
if (client) disconnect(client);
|
|
3182
|
+
if (props.onBack) {
|
|
3183
|
+
props.onBack();
|
|
3184
|
+
} else {
|
|
3185
|
+
exit();
|
|
3186
|
+
}
|
|
3187
|
+
};
|
|
3188
|
+
const items = [
|
|
3189
|
+
{ label: "View Configuration", value: "view" },
|
|
3190
|
+
{ label: "Edit Configuration", value: "edit" },
|
|
3191
|
+
{ label: "View Logs", value: "logs" },
|
|
3192
|
+
{ label: "Restart Caddy", value: "restart" },
|
|
3193
|
+
{ label: "< Back", value: "cancel" }
|
|
3194
|
+
];
|
|
3195
|
+
useEffect9(() => {
|
|
3196
|
+
const run = async () => {
|
|
3197
|
+
setConnectStatus("running");
|
|
3198
|
+
try {
|
|
3199
|
+
const sshClient = await connect(props);
|
|
3200
|
+
setClient(sshClient);
|
|
3201
|
+
setConnectStatus("success");
|
|
3202
|
+
} catch (err) {
|
|
3203
|
+
setConnectStatus("error");
|
|
3204
|
+
setError(`Connection failed: ${err instanceof Error ? err.message : err}`);
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
run();
|
|
3208
|
+
return () => {
|
|
3209
|
+
if (client) disconnect(client);
|
|
3210
|
+
};
|
|
3211
|
+
}, []);
|
|
3212
|
+
const handleSelect = async (item) => {
|
|
3213
|
+
if (item.value === "cancel") {
|
|
3214
|
+
goBack();
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
if (!client) return;
|
|
3218
|
+
if (item.value === "view") {
|
|
3219
|
+
setMode("view");
|
|
3220
|
+
try {
|
|
3221
|
+
const result = await exec(client, "cat /etc/caddy/Caddyfile");
|
|
3222
|
+
setCaddyContent(result.stdout);
|
|
3223
|
+
} catch (err) {
|
|
3224
|
+
setError(`Failed to read Caddyfile: ${err}`);
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
if (item.value === "logs") {
|
|
3228
|
+
setMode("logs");
|
|
3229
|
+
try {
|
|
3230
|
+
const result = await exec(client, 'journalctl -u caddy --no-pager -n 50 2>/dev/null || echo "No logs available"');
|
|
3231
|
+
setLogsContent(result.stdout);
|
|
3232
|
+
} catch (err) {
|
|
3233
|
+
setError(`Failed to read logs: ${err}`);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
if (item.value === "restart") {
|
|
3237
|
+
setMode("restarting");
|
|
3238
|
+
setRestartStatus("running");
|
|
3239
|
+
setRestartMessage("Restarting Caddy service...");
|
|
3240
|
+
try {
|
|
3241
|
+
await exec(client, "sudo systemctl restart caddy");
|
|
3242
|
+
setRestartStatus("success");
|
|
3243
|
+
setRestartMessage("Caddy restarted successfully!");
|
|
3244
|
+
const ports = await exec(client, 'ss -tlnp 2>/dev/null | grep -E ":(80|443)" || echo "none"');
|
|
3245
|
+
const output = ports.stdout.trim();
|
|
3246
|
+
const has443 = output.includes(":443");
|
|
3247
|
+
const has80 = output.includes(":80");
|
|
3248
|
+
if (has80 && has443) {
|
|
3249
|
+
setRestartMessage("Caddy restarted! Ports 80 & 443 now listening.");
|
|
3250
|
+
} else if (has80) {
|
|
3251
|
+
setRestartMessage("Caddy restarted! Port 80 listening. Port 443 may need a request to activate.");
|
|
3252
|
+
} else {
|
|
3253
|
+
setRestartMessage("Caddy restarted but no ports detected. Check logs.");
|
|
3254
|
+
}
|
|
3255
|
+
} catch (err) {
|
|
3256
|
+
setRestartStatus("error");
|
|
3257
|
+
setRestartMessage(`Failed to restart: ${err instanceof Error ? err.message : err}`);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
if (item.value === "edit") {
|
|
3261
|
+
setMode("edit");
|
|
3262
|
+
try {
|
|
3263
|
+
const result = await exec(client, "cat /etc/caddy/Caddyfile");
|
|
3264
|
+
const currentContent = result.stdout;
|
|
3265
|
+
const tempFile = path2.join(os2.tmpdir(), `Caddyfile-${Date.now()}`);
|
|
3266
|
+
fs2.writeFileSync(tempFile, currentContent);
|
|
3267
|
+
const editor = process.env.EDITOR || "nano";
|
|
3268
|
+
const child = spawn(editor, [tempFile], {
|
|
3269
|
+
stdio: "inherit"
|
|
3270
|
+
});
|
|
3271
|
+
child.on("exit", async (code) => {
|
|
3272
|
+
if (code === 0) {
|
|
3273
|
+
setMode("saving");
|
|
3274
|
+
setSaveStatus("Reading changes...");
|
|
3275
|
+
const newContent = fs2.readFileSync(tempFile, "utf-8");
|
|
3276
|
+
if (newContent !== currentContent) {
|
|
3277
|
+
setSaveStatus("Uploading changes...");
|
|
3278
|
+
const b64 = Buffer.from(newContent).toString("base64");
|
|
3279
|
+
const writeCmd = `echo "${b64}" | base64 -d > /etc/caddy/Caddyfile`;
|
|
3280
|
+
await exec(client, writeCmd);
|
|
3281
|
+
setSaveStatus("Reloading Caddy...");
|
|
3282
|
+
await exec(client, "caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy");
|
|
3283
|
+
setSaveStatus("Success! Caddy reloaded.");
|
|
3284
|
+
setTimeout(() => {
|
|
3285
|
+
setMode("view");
|
|
3286
|
+
setCaddyContent(newContent);
|
|
3287
|
+
setSaveStatus("");
|
|
3288
|
+
}, 1500);
|
|
3289
|
+
} else {
|
|
3290
|
+
setSaveStatus("No changes made.");
|
|
3291
|
+
setTimeout(() => {
|
|
3292
|
+
setMode("view");
|
|
3293
|
+
setCaddyContent(currentContent);
|
|
3294
|
+
setSaveStatus("");
|
|
3295
|
+
}, 1500);
|
|
3296
|
+
}
|
|
3297
|
+
fs2.unlinkSync(tempFile);
|
|
3298
|
+
} else {
|
|
3299
|
+
setError("Editor exited with error code " + code);
|
|
3300
|
+
setMode("menu");
|
|
3301
|
+
}
|
|
3302
|
+
});
|
|
3303
|
+
} catch (err) {
|
|
3304
|
+
setError(`Failed to edit: ${err}`);
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
useInput8((input, key) => {
|
|
3309
|
+
if (mode === "view" || mode === "logs" || mode === "restarting" && (restartStatus === "success" || restartStatus === "error")) {
|
|
3310
|
+
if (key.escape || input === "q") {
|
|
3311
|
+
setMode("menu");
|
|
3312
|
+
setCaddyContent("");
|
|
3313
|
+
setLogsContent("");
|
|
3314
|
+
setRestartStatus("pending");
|
|
3315
|
+
setRestartMessage("");
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
3319
|
+
if (error) {
|
|
3320
|
+
return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3321
|
+
/* @__PURE__ */ jsx13(Header, { title: "Caddy Configuration" }),
|
|
3322
|
+
/* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
|
|
3323
|
+
"Error: ",
|
|
3324
|
+
error
|
|
3325
|
+
] }),
|
|
3326
|
+
/* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" }) })
|
|
3327
|
+
] });
|
|
3328
|
+
}
|
|
3329
|
+
return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3330
|
+
/* @__PURE__ */ jsx13(Header, { title: "Caddy Configuration" }),
|
|
3331
|
+
/* @__PURE__ */ jsx13(Task, { label: "Connect to server", status: connectStatus }),
|
|
3332
|
+
connectStatus === "success" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
|
|
3333
|
+
mode === "menu" && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
3334
|
+
/* @__PURE__ */ jsx13(Text13, { children: "Select an action:" }),
|
|
3335
|
+
/* @__PURE__ */ jsx13(SelectInput5, { items, onSelect: handleSelect })
|
|
3336
|
+
] }),
|
|
3337
|
+
mode === "view" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3338
|
+
/* @__PURE__ */ jsx13(Text13, { color: "green", children: "Current Configuration (/etc/caddy/Caddyfile):" }),
|
|
3339
|
+
/* @__PURE__ */ jsx13(Box13, { borderStyle: "single", padding: 1, borderColor: "gray", children: caddyContent ? /* @__PURE__ */ jsx13(Text13, { children: caddyContent }) : /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text13, { color: "green", children: [
|
|
3340
|
+
/* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
|
|
3341
|
+
" Loading..."
|
|
3342
|
+
] }) }) }),
|
|
3343
|
+
/* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" })
|
|
3344
|
+
] }),
|
|
3345
|
+
mode === "logs" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3346
|
+
/* @__PURE__ */ jsx13(Text13, { color: "green", children: "Caddy Logs (last 50 lines):" }),
|
|
3347
|
+
/* @__PURE__ */ jsx13(Box13, { borderStyle: "single", padding: 1, borderColor: "gray", flexDirection: "column", children: logsContent ? /* @__PURE__ */ jsx13(Text13, { children: logsContent }) : /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text13, { color: "green", children: [
|
|
3348
|
+
/* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
|
|
3349
|
+
" Loading..."
|
|
3350
|
+
] }) }) }),
|
|
3351
|
+
/* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" })
|
|
3352
|
+
] }),
|
|
3353
|
+
mode === "restarting" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3354
|
+
/* @__PURE__ */ jsx13(Task, { label: "Restart Caddy", status: restartStatus, message: restartMessage }),
|
|
3355
|
+
(restartStatus === "success" || restartStatus === "error") && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" }) })
|
|
3356
|
+
] }),
|
|
3357
|
+
mode === "edit" && /* @__PURE__ */ jsxs13(Box13, { children: [
|
|
3358
|
+
/* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "Opening external editor..." }),
|
|
3359
|
+
/* @__PURE__ */ jsx13(Text13, { color: "gray", children: " Check your terminal focus." })
|
|
3360
|
+
] }),
|
|
3361
|
+
mode === "saving" && /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text13, { color: "green", children: [
|
|
3362
|
+
/* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
|
|
3363
|
+
" ",
|
|
3364
|
+
saveStatus
|
|
3365
|
+
] }) })
|
|
3366
|
+
] })
|
|
3367
|
+
] });
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
// src/commands/servers.tsx
|
|
3371
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3372
|
+
function ServerCommand(props) {
|
|
3373
|
+
const { exit } = useApp9();
|
|
3374
|
+
const [mode, setMode] = useState11(
|
|
3375
|
+
props.action === "add" ? "add-alias" : props.action === "remove" ? "remove" : "list"
|
|
3376
|
+
);
|
|
3377
|
+
const [selectedServer, setSelectedServer] = useState11(null);
|
|
3378
|
+
const [newAlias, setNewAlias] = useState11(props.alias || "");
|
|
3379
|
+
const [newHost, setNewHost] = useState11(props.host || "");
|
|
3380
|
+
const [newUser, setNewUser] = useState11(props.user || "root");
|
|
3381
|
+
const [servers, setServers] = useState11({});
|
|
3382
|
+
useEffect10(() => {
|
|
3383
|
+
setServers(ServerManager.listServers());
|
|
3384
|
+
}, []);
|
|
3385
|
+
useEffect10(() => {
|
|
3386
|
+
if (props.action === "add" && props.alias && props.host) {
|
|
3387
|
+
ServerManager.addServer(props.alias, {
|
|
3388
|
+
host: props.host,
|
|
3389
|
+
user: props.user || "root"
|
|
3390
|
+
});
|
|
3391
|
+
console.log(`Server '${props.alias}' added.`);
|
|
3392
|
+
exit();
|
|
3393
|
+
}
|
|
3394
|
+
}, []);
|
|
3395
|
+
const handleAliasSubmit = (value) => {
|
|
3396
|
+
if (!value) return;
|
|
3397
|
+
setNewAlias(value);
|
|
3398
|
+
setMode("add-host");
|
|
3399
|
+
};
|
|
3400
|
+
const handleHostSubmit = (value) => {
|
|
3401
|
+
if (!value) return;
|
|
3402
|
+
setNewHost(value);
|
|
3403
|
+
setMode("add-user");
|
|
3404
|
+
};
|
|
3405
|
+
const handleUserSubmit = (value) => {
|
|
3406
|
+
const user = value || "root";
|
|
3407
|
+
ServerManager.addServer(newAlias, { host: newHost, user });
|
|
3408
|
+
setServers(ServerManager.listServers());
|
|
3409
|
+
setMode("list");
|
|
3410
|
+
};
|
|
3411
|
+
const handleRemoveSelect = (item) => {
|
|
3412
|
+
ServerManager.removeServer(item.value);
|
|
3413
|
+
setServers(ServerManager.listServers());
|
|
3414
|
+
setMode("list");
|
|
3415
|
+
};
|
|
3416
|
+
if (mode === "list") {
|
|
3417
|
+
const listItems = Object.entries(servers).map(([alias, config]) => ({
|
|
3418
|
+
label: `${alias} (${config.user}@${config.host})`,
|
|
3419
|
+
value: alias
|
|
3420
|
+
}));
|
|
3421
|
+
const options = [
|
|
3422
|
+
{ label: "+ Add New Server", value: "__add__" },
|
|
3423
|
+
...listItems.length > 0 ? [{ label: "- Remove Server", value: "__remove__" }] : [],
|
|
3424
|
+
...listItems
|
|
3425
|
+
];
|
|
3426
|
+
return /* @__PURE__ */ jsx14(
|
|
3427
|
+
Layout,
|
|
3428
|
+
{
|
|
3429
|
+
title: "Saved Servers",
|
|
3430
|
+
footerStatus: "Use Enter to Select, Arrows to Move",
|
|
3431
|
+
footerTips: ["Select a server to view options"],
|
|
3432
|
+
children: /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(
|
|
3433
|
+
SelectInput6,
|
|
3434
|
+
{
|
|
3435
|
+
items: options,
|
|
3436
|
+
onSelect: (item) => {
|
|
3437
|
+
if (item.value === "__add__") setMode("add-alias");
|
|
3438
|
+
else if (item.value === "__remove__") setMode("remove");
|
|
3439
|
+
else {
|
|
3440
|
+
setSelectedServer(item.value);
|
|
3441
|
+
setMode("actions");
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
) })
|
|
3446
|
+
}
|
|
3447
|
+
);
|
|
3448
|
+
}
|
|
3449
|
+
if (mode === "actions" && selectedServer) {
|
|
3450
|
+
const config = servers[selectedServer];
|
|
3451
|
+
const actionItems = [
|
|
3452
|
+
{ label: "Manage Caddy Configuration", value: "caddy" },
|
|
3453
|
+
{ label: "< Back", value: "__back__" }
|
|
3454
|
+
];
|
|
3455
|
+
return /* @__PURE__ */ jsx14(Layout, { title: `Server: ${selectedServer}`, children: /* @__PURE__ */ jsx14(
|
|
3456
|
+
SelectInput6,
|
|
3457
|
+
{
|
|
3458
|
+
items: actionItems,
|
|
3459
|
+
onSelect: (item) => {
|
|
3460
|
+
if (item.value === "__back__") {
|
|
3461
|
+
setMode("list");
|
|
3462
|
+
setSelectedServer(null);
|
|
3463
|
+
}
|
|
3464
|
+
if (item.value === "caddy") {
|
|
3465
|
+
setMode("caddy");
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
) });
|
|
3470
|
+
}
|
|
3471
|
+
if (mode === "caddy" && selectedServer) {
|
|
3472
|
+
const config = servers[selectedServer];
|
|
3473
|
+
return /* @__PURE__ */ jsx14(CaddyCommand, { host: config.host, user: config.user }, config.keyPath);
|
|
3474
|
+
}
|
|
3475
|
+
if (mode === "remove") {
|
|
3476
|
+
const listItems = Object.entries(servers).map(([alias, config]) => ({
|
|
3477
|
+
label: `Remove ${alias} (${config.host})`,
|
|
3478
|
+
value: alias
|
|
3479
|
+
}));
|
|
3480
|
+
return /* @__PURE__ */ jsxs14(Layout, { title: "Remove Server", footerStatus: "Removing Server", children: [
|
|
3481
|
+
/* @__PURE__ */ jsx14(Text14, { children: "Select server to remove:" }),
|
|
3482
|
+
/* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(
|
|
3483
|
+
SelectInput6,
|
|
3484
|
+
{
|
|
3485
|
+
items: [...listItems, { label: "< Back", value: "__back__" }],
|
|
3486
|
+
onSelect: (item) => {
|
|
3487
|
+
if (item.value === "__back__") setMode("list");
|
|
3488
|
+
else handleRemoveSelect(item);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
) })
|
|
3492
|
+
] });
|
|
3493
|
+
}
|
|
3494
|
+
return /* @__PURE__ */ jsxs14(Layout, { title: "Add Server", footerStatus: mode === "add-alias" ? "Enter Alias" : mode === "add-host" ? "Enter Host IP" : "Enter SSH User", children: [
|
|
3495
|
+
mode === "add-alias" && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
3496
|
+
/* @__PURE__ */ jsx14(Text14, { children: "Enter server alias (e.g. production):" }),
|
|
3497
|
+
/* @__PURE__ */ jsxs14(Box14, { children: [
|
|
3498
|
+
/* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "> " }),
|
|
3499
|
+
/* @__PURE__ */ jsx14(TextInput3, { value: newAlias, onChange: setNewAlias, onSubmit: handleAliasSubmit })
|
|
3500
|
+
] })
|
|
3501
|
+
] }),
|
|
3502
|
+
mode === "add-host" && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
3503
|
+
/* @__PURE__ */ jsx14(Text14, { children: "Enter IP address or hostname:" }),
|
|
3504
|
+
/* @__PURE__ */ jsxs14(Box14, { children: [
|
|
3505
|
+
/* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "> " }),
|
|
3506
|
+
/* @__PURE__ */ jsx14(TextInput3, { value: newHost, onChange: setNewHost, onSubmit: handleHostSubmit })
|
|
3507
|
+
] })
|
|
3508
|
+
] }),
|
|
3509
|
+
mode === "add-user" && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
3510
|
+
/* @__PURE__ */ jsx14(Text14, { children: "Enter SSH user (default: root):" }),
|
|
3511
|
+
/* @__PURE__ */ jsxs14(Box14, { children: [
|
|
3512
|
+
/* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "> " }),
|
|
3513
|
+
/* @__PURE__ */ jsx14(TextInput3, { value: newUser, onChange: setNewUser, onSubmit: handleUserSubmit })
|
|
3514
|
+
] })
|
|
3515
|
+
] })
|
|
3516
|
+
] });
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
// src/commands/main.tsx
|
|
3520
|
+
import { useState as useState14 } from "react";
|
|
3521
|
+
import { Box as Box17, Text as Text17, useApp as useApp11, useInput as useInput10 } from "ink";
|
|
3522
|
+
import SelectInput8 from "ink-select-input";
|
|
3523
|
+
import TextInput4 from "ink-text-input";
|
|
3524
|
+
|
|
3525
|
+
// src/commands/appList.tsx
|
|
3526
|
+
import { useState as useState12, useEffect as useEffect11 } from "react";
|
|
3527
|
+
import { Box as Box15, Text as Text15 } from "ink";
|
|
3528
|
+
import SelectInput7 from "ink-select-input";
|
|
3529
|
+
|
|
3530
|
+
// src/utils/scan.ts
|
|
3531
|
+
var SCAN_SCRIPT = `
|
|
3532
|
+
# JSON output array
|
|
3533
|
+
echo "["
|
|
3534
|
+
|
|
3535
|
+
FIRST=1
|
|
3536
|
+
|
|
3537
|
+
# Iterate directories in /var/www
|
|
3538
|
+
for DIR in /var/www/*; do
|
|
3539
|
+
if [ -d "$DIR" ]; then
|
|
3540
|
+
NAME=$(basename "$DIR")
|
|
3541
|
+
if [ "$NAME" != "html" ]; then
|
|
3542
|
+
if [ "$FIRST" != "1" ]; then echo ","; fi
|
|
3543
|
+
FIRST=0
|
|
3544
|
+
|
|
3545
|
+
echo "{"
|
|
3546
|
+
echo ""name": "$NAME","
|
|
3547
|
+
echo ""path": "$DIR","
|
|
3548
|
+
|
|
3549
|
+
# Check .provisor.json
|
|
3550
|
+
if [ -f "$DIR/.provisor.json" ]; then
|
|
3551
|
+
CONTENT=$(cat "$DIR/.provisor.json" | tr -d '
|
|
3552
|
+
')
|
|
3553
|
+
echo ""config": $CONTENT,"
|
|
3554
|
+
fi
|
|
3555
|
+
|
|
3556
|
+
# Check Git info
|
|
3557
|
+
if [ -d "$DIR/.git" ]; then
|
|
3558
|
+
cd "$DIR"
|
|
3559
|
+
REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
|
|
3560
|
+
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
3561
|
+
echo ""gitRemote": "$REMOTE","
|
|
3562
|
+
echo ""gitBranch": "$BRANCH","
|
|
3563
|
+
fi
|
|
3564
|
+
|
|
3565
|
+
# Check Services
|
|
3566
|
+
WEBHOOK="false"
|
|
3567
|
+
if systemctl is-active --quiet webhook-$NAME 2>/dev/null; then WEBHOOK="true"; fi
|
|
3568
|
+
|
|
3569
|
+
POLL="false"
|
|
3570
|
+
if systemctl is-active --quiet poll-$NAME.timer 2>/dev/null; then POLL="true"; fi
|
|
3571
|
+
|
|
3572
|
+
# Basic check for PM2 (might need sudo or user switch, assuming running as deploy user generally)
|
|
3573
|
+
# This part is tricky if we don't have pm2 in path of the non-interactive shell or different user
|
|
3574
|
+
# We'll skip deep pm2 check for now to avoid complexity, or try simple process check
|
|
3575
|
+
|
|
3576
|
+
echo ""services": {"
|
|
3577
|
+
echo ""webhook": $WEBHOOK,"
|
|
3578
|
+
echo ""poll": $POLL"
|
|
3579
|
+
echo "}"
|
|
3580
|
+
|
|
3581
|
+
echo "}"
|
|
3582
|
+
fi
|
|
3583
|
+
fi
|
|
3584
|
+
done
|
|
3585
|
+
|
|
3586
|
+
echo "]"
|
|
3587
|
+
`;
|
|
3588
|
+
async function scanServer(options) {
|
|
3589
|
+
let client = null;
|
|
3590
|
+
try {
|
|
3591
|
+
client = await connect(options);
|
|
3592
|
+
const result = await exec(client, SCAN_SCRIPT);
|
|
3593
|
+
const nodeScript = `
|
|
3594
|
+
const fs = require('fs');
|
|
3595
|
+
const path = require('path');
|
|
3596
|
+
const { execSync } = require('child_process');
|
|
3597
|
+
|
|
3598
|
+
const apps = [];
|
|
3599
|
+
const wwwDir = '/var/www';
|
|
3600
|
+
|
|
3601
|
+
try {
|
|
3602
|
+
if (fs.existsSync(wwwDir)) {
|
|
3603
|
+
const dirs = fs.readdirSync(wwwDir);
|
|
3604
|
+
for (const name of dirs) {
|
|
3605
|
+
if (name === 'html') continue;
|
|
3606
|
+
const dirPath = path.join(wwwDir, name);
|
|
3607
|
+
if (fs.statSync(dirPath).isDirectory()) {
|
|
3608
|
+
const app = { name, path: dirPath, services: {} };
|
|
3609
|
+
|
|
3610
|
+
// Read config
|
|
3611
|
+
const configPath = path.join(dirPath, '.provisor.json');
|
|
3612
|
+
if (fs.existsSync(configPath)) {
|
|
3613
|
+
try {
|
|
3614
|
+
app.config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
3615
|
+
} catch (e) {}
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
// Git info
|
|
3619
|
+
try {
|
|
3620
|
+
const remote = execSync('git -C ' + dirPath + ' remote get-url origin', { stdio: 'pipe' }).toString().trim();
|
|
3621
|
+
app.gitRemote = remote;
|
|
3622
|
+
const branch = execSync('git -C ' + dirPath + ' rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
|
|
3623
|
+
app.gitBranch = branch;
|
|
3624
|
+
} catch (e) {}
|
|
3625
|
+
|
|
3626
|
+
// Systemd checks
|
|
3627
|
+
try {
|
|
3628
|
+
execSync('systemctl is-active webhook-' + name, { stdio: 'ignore' });
|
|
3629
|
+
app.services.webhook = true;
|
|
3630
|
+
} catch (e) { app.services.webhook = false; }
|
|
3631
|
+
|
|
3632
|
+
try {
|
|
3633
|
+
execSync('systemctl is-active poll-' + name + '.timer', { stdio: 'ignore' });
|
|
3634
|
+
app.services.poll = true;
|
|
3635
|
+
} catch (e) { app.services.poll = false; }
|
|
3636
|
+
|
|
3637
|
+
apps.push(app);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
} catch (e) {
|
|
3642
|
+
// ignore dir errors
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
console.log(JSON.stringify(apps));
|
|
3646
|
+
`;
|
|
3647
|
+
await exec(client, `cat << 'EOF' > /tmp/scan-apps.js
|
|
3648
|
+
${nodeScript}
|
|
3649
|
+
EOF`);
|
|
3650
|
+
const nodeResult = await exec(client, "node /tmp/scan-apps.js");
|
|
3651
|
+
await exec(client, "rm /tmp/scan-apps.js");
|
|
3652
|
+
try {
|
|
3653
|
+
const apps = JSON.parse(nodeResult.stdout.trim());
|
|
3654
|
+
return apps;
|
|
3655
|
+
} catch (e) {
|
|
3656
|
+
console.error("Failed to parse scan result:", nodeResult.stdout);
|
|
3657
|
+
return [];
|
|
3658
|
+
}
|
|
3659
|
+
} catch (err) {
|
|
3660
|
+
console.error("Scan failed:", err);
|
|
3661
|
+
throw err;
|
|
3662
|
+
} finally {
|
|
3663
|
+
if (client) disconnect(client);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/commands/appList.tsx
|
|
3668
|
+
import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3669
|
+
function AppListCommand({ alias, config, onSelectApp, onProvision, onBack }) {
|
|
3670
|
+
const [apps, setApps] = useState12(null);
|
|
3671
|
+
const [scanning, setScanning] = useState12(false);
|
|
3672
|
+
const [error, setError] = useState12(null);
|
|
3673
|
+
useEffect11(() => {
|
|
3674
|
+
let mounted = true;
|
|
3675
|
+
const load = async () => {
|
|
3676
|
+
const cached = ServerManager.getApps(alias);
|
|
3677
|
+
if (cached && cached.length > 0) {
|
|
3678
|
+
if (mounted) setApps(cached);
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
if (mounted) setScanning(true);
|
|
3682
|
+
try {
|
|
3683
|
+
const found = await scanServer(config);
|
|
3684
|
+
if (mounted) {
|
|
3685
|
+
setApps(found);
|
|
3686
|
+
ServerManager.updateApps(alias, found);
|
|
3687
|
+
}
|
|
3688
|
+
} catch (err) {
|
|
3689
|
+
if (mounted) {
|
|
3690
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3691
|
+
setApps([]);
|
|
3692
|
+
}
|
|
3693
|
+
} finally {
|
|
3694
|
+
if (mounted) setScanning(false);
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
if (apps === null && !scanning && !error) {
|
|
3698
|
+
load();
|
|
3699
|
+
}
|
|
3700
|
+
return () => {
|
|
3701
|
+
mounted = false;
|
|
3702
|
+
};
|
|
3703
|
+
}, [alias, config]);
|
|
3704
|
+
const handleRescan = async () => {
|
|
3705
|
+
setScanning(true);
|
|
3706
|
+
setError(null);
|
|
3707
|
+
setApps(null);
|
|
3708
|
+
try {
|
|
3709
|
+
const found = await scanServer(config);
|
|
3710
|
+
setApps(found);
|
|
3711
|
+
ServerManager.updateApps(alias, found);
|
|
3712
|
+
} catch (err) {
|
|
3713
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3714
|
+
setApps([]);
|
|
3715
|
+
} finally {
|
|
3716
|
+
setScanning(false);
|
|
3717
|
+
}
|
|
3718
|
+
};
|
|
3719
|
+
if (scanning) {
|
|
3720
|
+
return /* @__PURE__ */ jsx15(Layout, { title: `Scanning ${alias}...`, footerStatus: "Please wait", children: /* @__PURE__ */ jsx15(Text15, { children: "Connecting to server and scanning for apps..." }) });
|
|
3721
|
+
}
|
|
3722
|
+
if (error) {
|
|
3723
|
+
return /* @__PURE__ */ jsxs15(Layout, { title: `Error Scanning ${alias}`, footerStatus: "Scan Failed", children: [
|
|
3724
|
+
/* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
|
|
3725
|
+
"Failed to scan server: ",
|
|
3726
|
+
error
|
|
3727
|
+
] }),
|
|
3728
|
+
/* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(
|
|
3729
|
+
SelectInput7,
|
|
3730
|
+
{
|
|
3731
|
+
items: [{ label: "Retry", value: "retry" }, { label: "< Back", value: "back" }],
|
|
3732
|
+
onSelect: (item) => {
|
|
3733
|
+
if (item.value === "retry") {
|
|
3734
|
+
setApps(null);
|
|
3735
|
+
} else onBack();
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
) })
|
|
3739
|
+
] });
|
|
3740
|
+
}
|
|
3741
|
+
const listItems = (apps || []).map((app) => ({
|
|
3742
|
+
label: app.name,
|
|
3743
|
+
value: app.name
|
|
3744
|
+
}));
|
|
3745
|
+
const menuItems = [
|
|
3746
|
+
...listItems,
|
|
3747
|
+
{ label: "+ Provision New App", value: "__provision__" },
|
|
3748
|
+
{ label: "\u21BB Re-scan", value: "__rescan__" },
|
|
3749
|
+
{ label: "< Back", value: "__back__" }
|
|
3750
|
+
];
|
|
3751
|
+
return /* @__PURE__ */ jsx15(
|
|
3752
|
+
Layout,
|
|
3753
|
+
{
|
|
3754
|
+
title: `Apps on ${alias}`,
|
|
3755
|
+
subtitle: `${(apps || []).length} found`,
|
|
3756
|
+
footerStatus: "Select an App",
|
|
3757
|
+
children: /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(
|
|
3758
|
+
SelectInput7,
|
|
3759
|
+
{
|
|
3760
|
+
items: menuItems,
|
|
3761
|
+
onSelect: (item) => {
|
|
3762
|
+
if (item.value === "__back__") onBack();
|
|
3763
|
+
else if (item.value === "__rescan__") {
|
|
3764
|
+
handleRescan();
|
|
3765
|
+
} else if (item.value === "__provision__") {
|
|
3766
|
+
onProvision();
|
|
3767
|
+
} else {
|
|
3768
|
+
onSelectApp(item.value);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
) })
|
|
3773
|
+
}
|
|
3774
|
+
);
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
// src/commands/diagnostics.tsx
|
|
3778
|
+
import { useState as useState13, useEffect as useEffect12 } from "react";
|
|
3779
|
+
import { Box as Box16, Text as Text16, useApp as useApp10, useInput as useInput9 } from "ink";
|
|
3780
|
+
import Spinner6 from "ink-spinner";
|
|
3781
|
+
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3782
|
+
function DiagnosticsCommand(props) {
|
|
3783
|
+
const { exit } = useApp10();
|
|
3784
|
+
const [checks, setChecks] = useState13([
|
|
3785
|
+
{ name: "Caddy Service", status: "pending" },
|
|
3786
|
+
{ name: "Firewall (UFW)", status: "pending" },
|
|
3787
|
+
{ name: "Port Listeners", status: "pending" },
|
|
3788
|
+
{ name: "Caddy Logs", status: "pending" },
|
|
3789
|
+
{ name: "HTTP Connectivity", status: "pending" }
|
|
3790
|
+
]);
|
|
3791
|
+
const [done, setDone] = useState13(false);
|
|
3792
|
+
const [overallStatus, setOverallStatus] = useState13("running");
|
|
3793
|
+
const goBack = () => {
|
|
3794
|
+
if (props.onBack) {
|
|
3795
|
+
props.onBack();
|
|
3796
|
+
} else {
|
|
3797
|
+
exit();
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3800
|
+
useInput9((input, key) => {
|
|
3801
|
+
if (done && (key.escape || input === "q")) {
|
|
3802
|
+
goBack();
|
|
3803
|
+
}
|
|
3804
|
+
});
|
|
3805
|
+
const updateCheck = (index, update) => {
|
|
3806
|
+
setChecks((prev) => {
|
|
3807
|
+
const newChecks = [...prev];
|
|
3808
|
+
newChecks[index] = { ...newChecks[index], ...update };
|
|
3809
|
+
return newChecks;
|
|
3810
|
+
});
|
|
3811
|
+
};
|
|
3812
|
+
useEffect12(() => {
|
|
3813
|
+
const run = async () => {
|
|
3814
|
+
let hasErrors = false;
|
|
3815
|
+
let hasWarnings = false;
|
|
3816
|
+
try {
|
|
3817
|
+
const client = await connect(props);
|
|
3818
|
+
updateCheck(0, { status: "running" });
|
|
3819
|
+
try {
|
|
3820
|
+
const caddyStatus = await exec(client, 'systemctl is-active caddy 2>/dev/null || echo "not-found"');
|
|
3821
|
+
const status = caddyStatus.stdout.trim();
|
|
3822
|
+
if (status === "active") {
|
|
3823
|
+
updateCheck(0, { status: "success", result: "Running" });
|
|
3824
|
+
} else if (status === "inactive") {
|
|
3825
|
+
updateCheck(0, { status: "error", result: "Stopped - run: sudo systemctl start caddy" });
|
|
3826
|
+
hasErrors = true;
|
|
3827
|
+
} else {
|
|
3828
|
+
updateCheck(0, { status: "error", result: `Status: ${status}` });
|
|
3829
|
+
hasErrors = true;
|
|
3830
|
+
}
|
|
3831
|
+
} catch (err) {
|
|
3832
|
+
updateCheck(0, { status: "error", result: "Failed to check" });
|
|
3833
|
+
hasErrors = true;
|
|
3834
|
+
}
|
|
3835
|
+
updateCheck(1, { status: "running" });
|
|
3836
|
+
try {
|
|
3837
|
+
const ufwStatus = await exec(client, 'ufw status 2>/dev/null || echo "ufw not installed"');
|
|
3838
|
+
const output = ufwStatus.stdout.trim();
|
|
3839
|
+
if (output.includes("inactive")) {
|
|
3840
|
+
updateCheck(1, { status: "success", result: "Disabled (all ports open)" });
|
|
3841
|
+
} else if (output.includes("not installed")) {
|
|
3842
|
+
updateCheck(1, { status: "success", result: "Not installed" });
|
|
3843
|
+
} else {
|
|
3844
|
+
const has80 = output.includes("80") || output.includes("HTTP");
|
|
3845
|
+
const has443 = output.includes("443") || output.includes("HTTPS");
|
|
3846
|
+
if (has80 && has443) {
|
|
3847
|
+
updateCheck(1, { status: "success", result: "Ports 80/443 allowed" });
|
|
3848
|
+
} else {
|
|
3849
|
+
const missing = [];
|
|
3850
|
+
if (!has80) missing.push("80");
|
|
3851
|
+
if (!has443) missing.push("443");
|
|
3852
|
+
updateCheck(1, {
|
|
3853
|
+
status: "error",
|
|
3854
|
+
result: `Missing ports: ${missing.join(", ")}`,
|
|
3855
|
+
details: ["Run: sudo ufw allow 80 && sudo ufw allow 443"]
|
|
3856
|
+
});
|
|
3857
|
+
hasErrors = true;
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
} catch (err) {
|
|
3861
|
+
updateCheck(1, { status: "warning", result: "Could not check firewall" });
|
|
3862
|
+
hasWarnings = true;
|
|
3863
|
+
}
|
|
3864
|
+
updateCheck(2, { status: "running" });
|
|
3865
|
+
try {
|
|
3866
|
+
const ports = await exec(client, 'ss -tlnp 2>/dev/null | grep -E ":(80|443)" || echo "none"');
|
|
3867
|
+
const output = ports.stdout.trim();
|
|
3868
|
+
if (output === "none" || output === "") {
|
|
3869
|
+
updateCheck(2, {
|
|
3870
|
+
status: "error",
|
|
3871
|
+
result: "Nothing listening on 80/443",
|
|
3872
|
+
details: ["Caddy may not be running or configured correctly"]
|
|
3873
|
+
});
|
|
3874
|
+
hasErrors = true;
|
|
3875
|
+
} else {
|
|
3876
|
+
const lines = output.split("\n").filter((l) => l.trim());
|
|
3877
|
+
const has80 = lines.some((l) => l.includes(":80 ") || l.includes(":80 "));
|
|
3878
|
+
const has443 = lines.some((l) => l.includes(":443 ") || l.includes(":443 "));
|
|
3879
|
+
if (has80 && has443) {
|
|
3880
|
+
updateCheck(2, { status: "success", result: "Ports 80 & 443 listening" });
|
|
3881
|
+
} else {
|
|
3882
|
+
const listening = [];
|
|
3883
|
+
if (has80) listening.push("80");
|
|
3884
|
+
if (has443) listening.push("443");
|
|
3885
|
+
updateCheck(2, {
|
|
3886
|
+
status: "warning",
|
|
3887
|
+
result: `Only port ${listening.join(", ")} listening`
|
|
3888
|
+
});
|
|
3889
|
+
hasWarnings = true;
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
} catch (err) {
|
|
3893
|
+
updateCheck(2, { status: "warning", result: "Could not check ports" });
|
|
3894
|
+
hasWarnings = true;
|
|
3895
|
+
}
|
|
3896
|
+
updateCheck(3, { status: "running" });
|
|
3897
|
+
try {
|
|
3898
|
+
const logs = await exec(client, 'journalctl -u caddy --no-pager -n 10 2>/dev/null | tail -5 || echo "no logs"');
|
|
3899
|
+
const output = logs.stdout.trim();
|
|
3900
|
+
if (output.toLowerCase().includes("error") || output.toLowerCase().includes("failed")) {
|
|
3901
|
+
const errorLines = output.split("\n").filter(
|
|
3902
|
+
(l) => l.toLowerCase().includes("error") || l.toLowerCase().includes("failed")
|
|
3903
|
+
).slice(0, 3);
|
|
3904
|
+
updateCheck(3, {
|
|
3905
|
+
status: "warning",
|
|
3906
|
+
result: "Recent errors found",
|
|
3907
|
+
details: errorLines.map((l) => l.slice(0, 80))
|
|
3908
|
+
});
|
|
3909
|
+
hasWarnings = true;
|
|
3910
|
+
} else if (output === "no logs") {
|
|
3911
|
+
updateCheck(3, { status: "success", result: "No logs available" });
|
|
3912
|
+
} else {
|
|
3913
|
+
updateCheck(3, { status: "success", result: "No recent errors" });
|
|
3914
|
+
}
|
|
3915
|
+
} catch (err) {
|
|
3916
|
+
updateCheck(3, { status: "warning", result: "Could not read logs" });
|
|
3917
|
+
hasWarnings = true;
|
|
3918
|
+
}
|
|
3919
|
+
updateCheck(4, { status: "running" });
|
|
3920
|
+
try {
|
|
3921
|
+
const httpTest = await exec(client, 'curl -s -o /dev/null -w "%{http_code}" http://localhost:80 2>/dev/null || echo "failed"');
|
|
3922
|
+
const code = httpTest.stdout.trim();
|
|
3923
|
+
if (code === "failed" || code === "000") {
|
|
3924
|
+
updateCheck(4, {
|
|
3925
|
+
status: "error",
|
|
3926
|
+
result: "Cannot connect to localhost:80",
|
|
3927
|
+
details: ["Web server may not be running"]
|
|
3928
|
+
});
|
|
3929
|
+
hasErrors = true;
|
|
3930
|
+
} else if (code.startsWith("2") || code.startsWith("3")) {
|
|
3931
|
+
updateCheck(4, { status: "success", result: `HTTP ${code} OK` });
|
|
3932
|
+
} else if (code === "404") {
|
|
3933
|
+
updateCheck(4, { status: "warning", result: "HTTP 404 - Check /var/www/app exists" });
|
|
3934
|
+
hasWarnings = true;
|
|
3935
|
+
} else {
|
|
3936
|
+
updateCheck(4, { status: "warning", result: `HTTP ${code}` });
|
|
3937
|
+
hasWarnings = true;
|
|
3938
|
+
}
|
|
3939
|
+
} catch (err) {
|
|
3940
|
+
updateCheck(4, { status: "warning", result: "Could not test HTTP" });
|
|
3941
|
+
hasWarnings = true;
|
|
3942
|
+
}
|
|
3943
|
+
disconnect(client);
|
|
3944
|
+
if (hasErrors) {
|
|
3945
|
+
setOverallStatus("error");
|
|
3946
|
+
} else if (hasWarnings) {
|
|
3947
|
+
setOverallStatus("warning");
|
|
3948
|
+
} else {
|
|
3949
|
+
setOverallStatus("success");
|
|
3950
|
+
}
|
|
3951
|
+
setDone(true);
|
|
3952
|
+
} catch (err) {
|
|
3953
|
+
setChecks((prev) => prev.map((c) => ({ ...c, status: "error", result: "Connection failed" })));
|
|
3954
|
+
setOverallStatus("error");
|
|
3955
|
+
setDone(true);
|
|
3956
|
+
}
|
|
3957
|
+
};
|
|
3958
|
+
run();
|
|
3959
|
+
}, []);
|
|
3960
|
+
const getStatusIcon = (status) => {
|
|
3961
|
+
switch (status) {
|
|
3962
|
+
case "pending":
|
|
3963
|
+
return "\u25CB";
|
|
3964
|
+
case "running":
|
|
3965
|
+
return "\u25D0";
|
|
3966
|
+
case "success":
|
|
3967
|
+
return "\u25CF";
|
|
3968
|
+
case "error":
|
|
3969
|
+
return "\u2717";
|
|
3970
|
+
default:
|
|
3971
|
+
return "\u25CB";
|
|
3972
|
+
}
|
|
3973
|
+
};
|
|
3974
|
+
const getStatusColor = (status) => {
|
|
3975
|
+
switch (status) {
|
|
3976
|
+
case "pending":
|
|
3977
|
+
return "gray";
|
|
3978
|
+
case "running":
|
|
3979
|
+
return "yellow";
|
|
3980
|
+
case "success":
|
|
3981
|
+
return "green";
|
|
3982
|
+
case "error":
|
|
3983
|
+
return "red";
|
|
3984
|
+
default:
|
|
3985
|
+
return "gray";
|
|
3986
|
+
}
|
|
3987
|
+
};
|
|
3988
|
+
return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
|
|
3989
|
+
/* @__PURE__ */ jsx16(Header, { title: "Server Diagnostics", subtitle: `Host: ${props.host}` }),
|
|
3990
|
+
/* @__PURE__ */ jsx16(Box16, { marginTop: 1, flexDirection: "column", children: checks.map((check, i) => /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
|
|
3991
|
+
/* @__PURE__ */ jsxs16(Box16, { children: [
|
|
3992
|
+
check.status === "running" ? /* @__PURE__ */ jsxs16(Text16, { color: "yellow", children: [
|
|
3993
|
+
/* @__PURE__ */ jsx16(Spinner6, { type: "dots" }),
|
|
3994
|
+
" "
|
|
3995
|
+
] }) : /* @__PURE__ */ jsxs16(Text16, { color: getStatusColor(check.status), children: [
|
|
3996
|
+
getStatusIcon(check.status),
|
|
3997
|
+
" "
|
|
3998
|
+
] }),
|
|
3999
|
+
/* @__PURE__ */ jsxs16(Text16, { children: [
|
|
4000
|
+
check.name,
|
|
4001
|
+
": "
|
|
4002
|
+
] }),
|
|
4003
|
+
check.result && /* @__PURE__ */ jsx16(Text16, { color: getStatusColor(check.status), children: check.result })
|
|
4004
|
+
] }),
|
|
4005
|
+
check.details && check.details.map((detail, j) => /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
|
|
4006
|
+
" \u2192 ",
|
|
4007
|
+
detail
|
|
4008
|
+
] }, j))
|
|
4009
|
+
] }, i)) }),
|
|
4010
|
+
done && /* @__PURE__ */ jsxs16(Box16, { marginTop: 1, flexDirection: "column", children: [
|
|
4011
|
+
/* @__PURE__ */ jsxs16(Box16, { children: [
|
|
4012
|
+
/* @__PURE__ */ jsxs16(Text16, { bold: true, children: [
|
|
4013
|
+
"Overall: ",
|
|
4014
|
+
" "
|
|
4015
|
+
] }),
|
|
4016
|
+
overallStatus === "success" && /* @__PURE__ */ jsx16(Text16, { color: "green", children: "All checks passed!" }),
|
|
4017
|
+
overallStatus === "warning" && /* @__PURE__ */ jsx16(Text16, { color: "yellow", children: "Some warnings - review above" }),
|
|
4018
|
+
overallStatus === "error" && /* @__PURE__ */ jsx16(Text16, { color: "red", children: "Issues found - see above for fixes" })
|
|
4019
|
+
] }),
|
|
4020
|
+
/* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "Press 'q' or Esc to go back" }) })
|
|
4021
|
+
] })
|
|
4022
|
+
] });
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
// src/commands/main.tsx
|
|
4026
|
+
import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
4027
|
+
function MainCommand() {
|
|
4028
|
+
const { exit } = useApp11();
|
|
4029
|
+
const [stack, setStack] = useState14([{ name: "root" }]);
|
|
4030
|
+
const [inputValue, setInputValue] = useState14("");
|
|
4031
|
+
const push = (name, props) => {
|
|
4032
|
+
setInputValue("");
|
|
4033
|
+
setStack((prev) => [...prev, { name, props }]);
|
|
4034
|
+
};
|
|
4035
|
+
const pop = () => {
|
|
4036
|
+
if (stack.length <= 1) {
|
|
4037
|
+
exit();
|
|
4038
|
+
} else {
|
|
4039
|
+
setStack((prev) => prev.slice(0, -1));
|
|
4040
|
+
}
|
|
4041
|
+
};
|
|
4042
|
+
const current = stack[stack.length - 1];
|
|
4043
|
+
useInput10((input, key) => {
|
|
4044
|
+
if (key.escape) {
|
|
4045
|
+
const currentView = stack[stack.length - 1].name;
|
|
4046
|
+
if (["app-logs", "app-config", "app-deploy", "server-status", "server-ssh-keys", "server-caddy", "server-diagnostics"].includes(currentView)) {
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
pop();
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
if (current.name === "root") {
|
|
4053
|
+
const items = [
|
|
4054
|
+
{ label: "Servers", value: "server-list" },
|
|
4055
|
+
{ label: "Exit", value: "exit" }
|
|
4056
|
+
];
|
|
4057
|
+
return /* @__PURE__ */ jsx17(
|
|
4058
|
+
Layout,
|
|
4059
|
+
{
|
|
4060
|
+
title: "Provisor",
|
|
4061
|
+
subtitle: "Main Menu",
|
|
4062
|
+
footerStatus: "Main Menu",
|
|
4063
|
+
footerTips: ["Select an Option", "Esc to Exit"],
|
|
4064
|
+
children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", marginTop: 1, children: [
|
|
4065
|
+
/* @__PURE__ */ jsx17(Text17, { bold: true, children: "Select category:" }),
|
|
4066
|
+
/* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
|
|
4067
|
+
SelectInput8,
|
|
4068
|
+
{
|
|
4069
|
+
items,
|
|
4070
|
+
onSelect: (item) => {
|
|
4071
|
+
if (item.value === "exit") exit();
|
|
4072
|
+
else push(item.value);
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
) })
|
|
4076
|
+
] })
|
|
4077
|
+
}
|
|
4078
|
+
);
|
|
4079
|
+
}
|
|
4080
|
+
if (current.name === "server-list") {
|
|
4081
|
+
const servers = ServerManager.listServers();
|
|
4082
|
+
const items = Object.entries(servers).map(([alias, conf]) => ({
|
|
4083
|
+
label: `${alias} (${conf.user}@${conf.host})`,
|
|
4084
|
+
value: alias
|
|
4085
|
+
}));
|
|
4086
|
+
const menuItems = [
|
|
4087
|
+
...items,
|
|
4088
|
+
{ label: "< Back", value: "__back__" }
|
|
4089
|
+
];
|
|
4090
|
+
return /* @__PURE__ */ jsx17(Layout, { title: "Servers", footerStatus: "Select a Server", children: /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
|
|
4091
|
+
SelectInput8,
|
|
4092
|
+
{
|
|
4093
|
+
items: menuItems,
|
|
4094
|
+
onSelect: (item) => {
|
|
4095
|
+
if (item.value === "__back__") pop();
|
|
4096
|
+
else {
|
|
4097
|
+
push("server-dashboard", { alias: item.value, config: servers[item.value] });
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
) }) });
|
|
4102
|
+
}
|
|
4103
|
+
if (current.name === "server-dashboard") {
|
|
4104
|
+
const { alias, config } = current.props;
|
|
4105
|
+
const items = [
|
|
4106
|
+
{ label: "Apps", value: "app-list" },
|
|
4107
|
+
{ label: "Run Diagnostics", value: "server-diagnostics" },
|
|
4108
|
+
{ label: "Server Status", value: "server-status" },
|
|
4109
|
+
{ label: "Manage SSH Keys", value: "server-ssh-keys" },
|
|
4110
|
+
{ label: "Manage Caddy Configuration", value: "server-caddy" },
|
|
4111
|
+
{ label: "Delete Server", value: "delete" },
|
|
4112
|
+
{ label: "< Back", value: "__back__" }
|
|
4113
|
+
];
|
|
4114
|
+
return /* @__PURE__ */ jsx17(
|
|
4115
|
+
Layout,
|
|
4116
|
+
{
|
|
4117
|
+
title: `Server: ${alias}`,
|
|
4118
|
+
subtitle: config.host,
|
|
4119
|
+
footerStatus: "Server Dashboard",
|
|
4120
|
+
children: /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
|
|
4121
|
+
SelectInput8,
|
|
4122
|
+
{
|
|
4123
|
+
items,
|
|
4124
|
+
onSelect: (item) => {
|
|
4125
|
+
if (item.value === "__back__") pop();
|
|
4126
|
+
else if (item.value === "app-list") {
|
|
4127
|
+
push("app-list", { alias, config });
|
|
4128
|
+
} else if (item.value === "delete") {
|
|
4129
|
+
push("delete-server-confirm", { alias });
|
|
4130
|
+
} else if (item.value === "server-status") {
|
|
4131
|
+
push("server-status", { alias, config });
|
|
4132
|
+
} else if (item.value === "server-ssh-keys") {
|
|
4133
|
+
push("server-ssh-keys", { alias, config });
|
|
4134
|
+
} else if (item.value === "server-caddy") {
|
|
4135
|
+
push("server-caddy", { alias, config });
|
|
4136
|
+
} else if (item.value === "server-diagnostics") {
|
|
4137
|
+
push("server-diagnostics", { alias, config });
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
) })
|
|
4142
|
+
}
|
|
4143
|
+
);
|
|
4144
|
+
}
|
|
4145
|
+
if (current.name === "app-list") {
|
|
4146
|
+
const { alias, config } = current.props;
|
|
4147
|
+
return /* @__PURE__ */ jsx17(
|
|
4148
|
+
AppListCommand,
|
|
4149
|
+
{
|
|
4150
|
+
alias,
|
|
4151
|
+
config,
|
|
4152
|
+
onBack: pop,
|
|
4153
|
+
onSelectApp: (appName) => push("app-dashboard", { alias, config, appName }),
|
|
4154
|
+
onProvision: () => push("app-dashboard", { alias, config, askingName: true })
|
|
4155
|
+
}
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
if (current.name === "delete-server-confirm") {
|
|
4159
|
+
const { alias } = current.props;
|
|
4160
|
+
return /* @__PURE__ */ jsx17(Layout, { title: "Delete Server", footerStatus: "Confirm Deletion", children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
|
|
4161
|
+
/* @__PURE__ */ jsxs17(Text17, { color: "red", bold: true, children: [
|
|
4162
|
+
"Are you sure you want to delete server '",
|
|
4163
|
+
alias,
|
|
4164
|
+
"'?"
|
|
4165
|
+
] }),
|
|
4166
|
+
/* @__PURE__ */ jsx17(Text17, { children: "This will only remove it from your local configuration." }),
|
|
4167
|
+
/* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
|
|
4168
|
+
SelectInput8,
|
|
4169
|
+
{
|
|
4170
|
+
items: [
|
|
4171
|
+
{ label: "No, Cancel", value: "no" },
|
|
4172
|
+
{ label: "Yes, Delete", value: "yes" }
|
|
4173
|
+
],
|
|
4174
|
+
onSelect: (item) => {
|
|
4175
|
+
if (item.value === "yes") {
|
|
4176
|
+
ServerManager.removeServer(alias);
|
|
4177
|
+
setStack([{ name: "root" }, { name: "server-list" }]);
|
|
4178
|
+
} else {
|
|
4179
|
+
pop();
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
) })
|
|
4184
|
+
] }) });
|
|
4185
|
+
}
|
|
4186
|
+
if (current.name === "app-dashboard") {
|
|
4187
|
+
const { alias, config, askingName, appName } = current.props;
|
|
4188
|
+
if (askingName) {
|
|
4189
|
+
return /* @__PURE__ */ jsx17(Layout, { title: "Select App", footerStatus: "Enter App Name", children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", marginTop: 1, children: [
|
|
4190
|
+
/* @__PURE__ */ jsx17(Text17, { children: "Enter Application Name:" }),
|
|
4191
|
+
/* @__PURE__ */ jsxs17(Box17, { children: [
|
|
4192
|
+
/* @__PURE__ */ jsx17(Text17, { color: "cyan", children: "> " }),
|
|
4193
|
+
/* @__PURE__ */ jsx17(
|
|
4194
|
+
TextInput4,
|
|
4195
|
+
{
|
|
4196
|
+
value: inputValue,
|
|
4197
|
+
onChange: setInputValue,
|
|
4198
|
+
onSubmit: (val) => {
|
|
4199
|
+
setStack((prev) => {
|
|
4200
|
+
const newStack = [...prev];
|
|
4201
|
+
newStack[newStack.length - 1] = {
|
|
4202
|
+
name: "app-dashboard",
|
|
4203
|
+
props: { alias, config, appName: val, askingName: false }
|
|
4204
|
+
};
|
|
4205
|
+
return newStack;
|
|
4206
|
+
});
|
|
4207
|
+
setInputValue("");
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
)
|
|
4211
|
+
] })
|
|
4212
|
+
] }) });
|
|
4213
|
+
}
|
|
4214
|
+
const items = [
|
|
4215
|
+
{ label: "View Logs", value: "logs" },
|
|
4216
|
+
{ label: "Configuration", value: "config" },
|
|
4217
|
+
{ label: "Deploy", value: "deploy" },
|
|
4218
|
+
{ label: "< Back", value: "__back__" }
|
|
4219
|
+
];
|
|
4220
|
+
return /* @__PURE__ */ jsx17(Layout, { title: `App: ${appName}`, subtitle: `Server: ${alias}`, footerStatus: "App Dashboard", children: /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
|
|
4221
|
+
SelectInput8,
|
|
4222
|
+
{
|
|
4223
|
+
items,
|
|
4224
|
+
onSelect: (item) => {
|
|
4225
|
+
if (item.value === "__back__") pop();
|
|
4226
|
+
else if (item.value === "logs") push("app-logs", { config, appName });
|
|
4227
|
+
else if (item.value === "config") push("app-config", { config, appName });
|
|
4228
|
+
else if (item.value === "deploy") push("app-deploy", { config, appName });
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
) }) });
|
|
4232
|
+
}
|
|
4233
|
+
if (current.name === "app-logs") {
|
|
4234
|
+
const { config, appName } = current.props;
|
|
4235
|
+
return /* @__PURE__ */ jsx17(
|
|
4236
|
+
LogsCommand,
|
|
4237
|
+
{
|
|
4238
|
+
host: config.host,
|
|
4239
|
+
user: config.user,
|
|
4240
|
+
name: appName,
|
|
4241
|
+
onBack: pop
|
|
4242
|
+
}
|
|
4243
|
+
);
|
|
4244
|
+
}
|
|
4245
|
+
if (current.name === "app-config") {
|
|
4246
|
+
const { config, appName } = current.props;
|
|
4247
|
+
return /* @__PURE__ */ jsx17(
|
|
4248
|
+
ConfigCommand,
|
|
4249
|
+
{
|
|
4250
|
+
host: config.host,
|
|
4251
|
+
user: config.user,
|
|
4252
|
+
name: appName,
|
|
4253
|
+
onBack: pop
|
|
4254
|
+
}
|
|
4255
|
+
);
|
|
4256
|
+
}
|
|
4257
|
+
if (current.name === "app-deploy") {
|
|
4258
|
+
const { config, appName } = current.props;
|
|
4259
|
+
return /* @__PURE__ */ jsx17(
|
|
4260
|
+
DeployCommand,
|
|
4261
|
+
{
|
|
4262
|
+
host: config.host,
|
|
4263
|
+
user: config.user,
|
|
4264
|
+
name: appName,
|
|
4265
|
+
onBack: pop
|
|
4266
|
+
}
|
|
4267
|
+
);
|
|
4268
|
+
}
|
|
4269
|
+
if (current.name === "server-status") {
|
|
4270
|
+
const { config } = current.props;
|
|
4271
|
+
return /* @__PURE__ */ jsx17(
|
|
4272
|
+
StatusCommand,
|
|
4273
|
+
{
|
|
4274
|
+
host: config.host,
|
|
4275
|
+
user: config.user,
|
|
4276
|
+
onBack: pop
|
|
4277
|
+
}
|
|
4278
|
+
);
|
|
4279
|
+
}
|
|
4280
|
+
if (current.name === "server-ssh-keys") {
|
|
4281
|
+
const { config } = current.props;
|
|
4282
|
+
return /* @__PURE__ */ jsx17(
|
|
4283
|
+
SshKeyCommand,
|
|
4284
|
+
{
|
|
4285
|
+
host: config.host,
|
|
4286
|
+
user: config.user,
|
|
4287
|
+
list: true,
|
|
4288
|
+
onBack: pop
|
|
4289
|
+
}
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
4292
|
+
if (current.name === "server-caddy") {
|
|
4293
|
+
const { config } = current.props;
|
|
4294
|
+
return /* @__PURE__ */ jsx17(
|
|
4295
|
+
CaddyCommand,
|
|
4296
|
+
{
|
|
4297
|
+
host: config.host,
|
|
4298
|
+
user: config.user,
|
|
4299
|
+
onBack: pop
|
|
4300
|
+
}
|
|
4301
|
+
);
|
|
4302
|
+
}
|
|
4303
|
+
if (current.name === "server-diagnostics") {
|
|
4304
|
+
const { config } = current.props;
|
|
4305
|
+
return /* @__PURE__ */ jsx17(
|
|
4306
|
+
DiagnosticsCommand,
|
|
4307
|
+
{
|
|
4308
|
+
host: config.host,
|
|
4309
|
+
user: config.user,
|
|
4310
|
+
onBack: pop
|
|
4311
|
+
}
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4314
|
+
return /* @__PURE__ */ jsxs17(Text17, { children: [
|
|
4315
|
+
"State Error: Unknown View ",
|
|
4316
|
+
current.name
|
|
4317
|
+
] });
|
|
4318
|
+
}
|
|
4319
|
+
|
|
2570
4320
|
// src/cli.tsx
|
|
2571
|
-
import { jsx as
|
|
4321
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
4322
|
+
var resolveHostAlias = (options) => {
|
|
4323
|
+
if (options.host) {
|
|
4324
|
+
const savedServer = ServerManager.getServer(options.host);
|
|
4325
|
+
if (savedServer) {
|
|
4326
|
+
options.host = savedServer.host;
|
|
4327
|
+
if (!options.user && savedServer.user) options.user = savedServer.user;
|
|
4328
|
+
if (!options.key && savedServer.keyPath) options.key = savedServer.keyPath;
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
};
|
|
2572
4332
|
program.name("provisor").description("Server provisioning and deployment CLI").version(package_default.version);
|
|
2573
|
-
program.
|
|
2574
|
-
|
|
4333
|
+
program.option("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to create", "deploy").option("-k, --key <path>", "Path to SSH private key for root access").option("-p, --port <port>", "SSH port", "22").action((options) => {
|
|
4334
|
+
resolveHostAlias(options);
|
|
4335
|
+
render(/* @__PURE__ */ jsx18(MainCommand, {}));
|
|
4336
|
+
});
|
|
4337
|
+
program.command("init").description("Initialize a new server").option("-h, --host <host>", "Server IP address").option("-u, --user <user>", "SSH user (default: root)").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
|
|
4338
|
+
resolveHostAlias(options);
|
|
4339
|
+
render(/* @__PURE__ */ jsx18(InitCommand, { ...options }));
|
|
2575
4340
|
});
|
|
2576
4341
|
program.command("app").description("Provision application (Caddy, Node.js, Git deploy)").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").option("-b, --branch <branch>", "Deploy branch", "main").option("-n, --name <name>", "Application name", "app").option("-r, --repo <url>", "Clone from repository URL (GitHub, GitLab, etc.)").action((options) => {
|
|
2577
|
-
|
|
4342
|
+
resolveHostAlias(options);
|
|
4343
|
+
render(/* @__PURE__ */ jsx18(AppCommand, { ...options }));
|
|
2578
4344
|
});
|
|
2579
4345
|
program.command("ssh-key").description("Manage SSH keys on the server").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").option("--add <pubkey>", "Add a new public key").option("--list", "List authorized keys").action((options) => {
|
|
2580
|
-
|
|
4346
|
+
resolveHostAlias(options);
|
|
4347
|
+
render(/* @__PURE__ */ jsx18(SshKeyCommand, { ...options }));
|
|
2581
4348
|
});
|
|
2582
4349
|
program.command("status").description("Check server status and services").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
|
|
2583
|
-
|
|
4350
|
+
resolveHostAlias(options);
|
|
4351
|
+
render(/* @__PURE__ */ jsx18(StatusCommand, { ...options }));
|
|
2584
4352
|
});
|
|
2585
4353
|
program.command("deploy").description("Trigger deployment for an application").requiredOption("-h, --host <host>", "Server hostname or IP").requiredOption("-n, --name <name>", "Application name").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
|
|
2586
|
-
|
|
4354
|
+
resolveHostAlias(options);
|
|
4355
|
+
render(/* @__PURE__ */ jsx18(DeployCommand, { ...options }));
|
|
2587
4356
|
});
|
|
2588
4357
|
program.command("config").description("Manage application configuration").requiredOption("-h, --host <host>", "Server hostname or IP").requiredOption("-n, --name <name>", "Application name").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").option("--show", "Show current configuration").option("--repo <url>", "Change repository URL").option("--branch <branch>", "Change deploy branch").option("--new-key", "Generate new deploy key").option("--delete-key", "Delete deploy key").option("--webhook-secret <secret>", "Update webhook secret").option("--disable-webhook", "Disable webhook").option("--polling-interval <seconds>", "Set git polling interval in seconds", parseInt).option("--enable-polling", "Enable git polling for auto-deploy").option("--disable-polling", "Disable git polling").action((options) => {
|
|
2589
|
-
|
|
4358
|
+
resolveHostAlias(options);
|
|
4359
|
+
render(/* @__PURE__ */ jsx18(ConfigCommand, { ...options }));
|
|
4360
|
+
});
|
|
4361
|
+
program.command("caddy").description("Manage Caddy configuration").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
|
|
4362
|
+
resolveHostAlias(options);
|
|
4363
|
+
render(/* @__PURE__ */ jsx18(CaddyCommand, { ...options }));
|
|
2590
4364
|
});
|
|
2591
4365
|
program.command("update").description("Check for updates and update the CLI").option("--check", "Only check for updates, do not install").action((options) => {
|
|
2592
|
-
render(/* @__PURE__ */
|
|
4366
|
+
render(/* @__PURE__ */ jsx18(UpdateCommand, { ...options }));
|
|
4367
|
+
});
|
|
4368
|
+
program.command("logs").description("View application and server logs").requiredOption("-h, --host <host>", "Server hostname or IP").requiredOption("-n, --name <name>", "Application name").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
|
|
4369
|
+
resolveHostAlias(options);
|
|
4370
|
+
render(/* @__PURE__ */ jsx18(LogsCommand, { ...options }));
|
|
4371
|
+
});
|
|
4372
|
+
program.command("server").description("Manage saved servers").argument("[action]", "Action to perform: add, list, remove").argument("[alias]", "Server alias").argument("[host]", "Server host (IP)").option("-u, --user <user>", "SSH user").action((action, alias, host, options) => {
|
|
4373
|
+
render(
|
|
4374
|
+
/* @__PURE__ */ jsx18(
|
|
4375
|
+
ServerCommand,
|
|
4376
|
+
{
|
|
4377
|
+
action,
|
|
4378
|
+
alias,
|
|
4379
|
+
host,
|
|
4380
|
+
user: options.user
|
|
4381
|
+
}
|
|
4382
|
+
)
|
|
4383
|
+
);
|
|
2593
4384
|
});
|
|
2594
4385
|
program.parse();
|
|
2595
4386
|
//# sourceMappingURL=cli.js.map
|