@rubytech/create-maxy 1.0.626 → 1.0.627
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/dist/index.js +123 -2
- package/dist/uninstall.js +59 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync } from "node:fs";
|
|
4
|
-
import { resolve, join } from "node:path";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, accessSync, constants as fsConstants } from "node:fs";
|
|
4
|
+
import { resolve, join, dirname } from "node:path";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
|
|
7
7
|
// Brand manifest — read from payload to derive all brand-specific installation values.
|
|
@@ -1260,6 +1260,107 @@ function setupAccount() {
|
|
|
1260
1260
|
}
|
|
1261
1261
|
}
|
|
1262
1262
|
// ---------------------------------------------------------------------------
|
|
1263
|
+
// Tunnel script shortcuts
|
|
1264
|
+
//
|
|
1265
|
+
// The cloudflare plugin's SKILL.md, PLUGIN.md, and reference docs encode the
|
|
1266
|
+
// invocation `~/setup-tunnel.sh` (and `~/reset-tunnel.sh`). The filesystem
|
|
1267
|
+
// reality is <INSTALL_DIR>/platform/plugins/cloudflare/scripts/*.sh. Without
|
|
1268
|
+
// the symlink the agent's first SKILL-compliant invocation fails with exit
|
|
1269
|
+
// 127 — the discipline-violation loop Task 555 exists to close.
|
|
1270
|
+
//
|
|
1271
|
+
// Collision discipline:
|
|
1272
|
+
// - absent path → create
|
|
1273
|
+
// - symlink → same target → no-op
|
|
1274
|
+
// - symlink → different path inside INSTALL_DIR → replace (idempotent upgrade)
|
|
1275
|
+
// - symlink → dangling → repair + log
|
|
1276
|
+
// - symlink → live target outside INSTALL_DIR → exit 1 (cross-brand collision)
|
|
1277
|
+
// - regular file → exit 1
|
|
1278
|
+
// - unreadable symlink → exit 1
|
|
1279
|
+
// ---------------------------------------------------------------------------
|
|
1280
|
+
function createTunnelSymlink(linkPath, target) {
|
|
1281
|
+
const targetAbs = resolve(target);
|
|
1282
|
+
let lstat = null;
|
|
1283
|
+
try {
|
|
1284
|
+
lstat = lstatSync(linkPath);
|
|
1285
|
+
}
|
|
1286
|
+
catch (err) {
|
|
1287
|
+
const code = err.code;
|
|
1288
|
+
if (code !== "ENOENT") {
|
|
1289
|
+
console.error(`[create-maxy:error] ${linkPath} lstat failed: ${err.message}`);
|
|
1290
|
+
process.exit(1);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (lstat === null) {
|
|
1294
|
+
symlinkSync(targetAbs, linkPath);
|
|
1295
|
+
console.log(` [create-maxy] symlink ${linkPath} → ${targetAbs}`);
|
|
1296
|
+
logFile(` symlink created: ${linkPath} → ${targetAbs}`);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (!lstat.isSymbolicLink()) {
|
|
1300
|
+
console.error(`[create-maxy:collision] ${linkPath} already exists target=<regular-file>`);
|
|
1301
|
+
console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
|
|
1302
|
+
process.exit(1);
|
|
1303
|
+
}
|
|
1304
|
+
let resolvedTarget;
|
|
1305
|
+
try {
|
|
1306
|
+
const raw = readlinkSync(linkPath);
|
|
1307
|
+
resolvedTarget = resolve(dirname(linkPath), raw);
|
|
1308
|
+
}
|
|
1309
|
+
catch (err) {
|
|
1310
|
+
console.error(`[create-maxy:collision] ${linkPath} symlink unreadable: ${err.message}`);
|
|
1311
|
+
console.error(` Remove the file and re-run: npx -y @rubytech/create-maxy`);
|
|
1312
|
+
process.exit(1);
|
|
1313
|
+
}
|
|
1314
|
+
if (resolvedTarget === targetAbs) {
|
|
1315
|
+
console.log(` [create-maxy] symlink ${linkPath} already points to ${targetAbs}`);
|
|
1316
|
+
logFile(` symlink unchanged: ${linkPath} → ${targetAbs}`);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const insideInstallDir = resolvedTarget === INSTALL_DIR || resolvedTarget.startsWith(INSTALL_DIR + "/");
|
|
1320
|
+
let targetExists = false;
|
|
1321
|
+
try {
|
|
1322
|
+
lstatSync(resolvedTarget);
|
|
1323
|
+
targetExists = true;
|
|
1324
|
+
}
|
|
1325
|
+
catch { /* resolvedTarget absent → dangling */ }
|
|
1326
|
+
if (insideInstallDir) {
|
|
1327
|
+
unlinkSync(linkPath);
|
|
1328
|
+
symlinkSync(targetAbs, linkPath);
|
|
1329
|
+
console.log(` [create-maxy] symlink replaced: ${linkPath} → ${targetAbs}`);
|
|
1330
|
+
logFile(` symlink replaced (stale same-brand): ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (!targetExists) {
|
|
1334
|
+
unlinkSync(linkPath);
|
|
1335
|
+
symlinkSync(targetAbs, linkPath);
|
|
1336
|
+
console.log(` [create-maxy] symlink repair: dangling ${linkPath} replaced (→ ${targetAbs})`);
|
|
1337
|
+
logFile(` symlink repaired (dangling): ${linkPath} was ${resolvedTarget}, now ${targetAbs}`);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
console.error(`[create-maxy:collision] ${linkPath} already exists target=${resolvedTarget}`);
|
|
1341
|
+
console.error(` This symlink was created by a different install (not within ${INSTALL_DIR}).`);
|
|
1342
|
+
console.error(` Remove or relocate it, then re-run: npx -y @rubytech/create-maxy`);
|
|
1343
|
+
process.exit(1);
|
|
1344
|
+
}
|
|
1345
|
+
function installTunnelScripts() {
|
|
1346
|
+
const setupSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/setup-tunnel.sh");
|
|
1347
|
+
const resetSrc = join(INSTALL_DIR, "platform/plugins/cloudflare/scripts/reset-tunnel.sh");
|
|
1348
|
+
const setupLink = resolve(process.env.HOME ?? "/root", "setup-tunnel.sh");
|
|
1349
|
+
const resetLink = resolve(process.env.HOME ?? "/root", "reset-tunnel.sh");
|
|
1350
|
+
for (const src of [setupSrc, resetSrc]) {
|
|
1351
|
+
try {
|
|
1352
|
+
chmodSync(src, 0o755);
|
|
1353
|
+
}
|
|
1354
|
+
catch (err) {
|
|
1355
|
+
console.error(`[create-maxy:error] tunnel script missing or chmod failed: ${src}`);
|
|
1356
|
+
console.error(` ${err.message}`);
|
|
1357
|
+
process.exit(1);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
createTunnelSymlink(setupLink, setupSrc);
|
|
1361
|
+
createTunnelSymlink(resetLink, resetSrc);
|
|
1362
|
+
}
|
|
1363
|
+
// ---------------------------------------------------------------------------
|
|
1263
1364
|
// Cron Registration
|
|
1264
1365
|
//
|
|
1265
1366
|
// Registers platform cron jobs (heartbeat, email-fetch, email-auto-respond).
|
|
@@ -1534,6 +1635,25 @@ WantedBy=multi-user.target
|
|
|
1534
1635
|
console.error(vncLog);
|
|
1535
1636
|
process.exit(1);
|
|
1536
1637
|
}
|
|
1638
|
+
// Tunnel script smoke check — the cloudflare SKILL contract invokes
|
|
1639
|
+
// ~/setup-tunnel.sh directly. If the install-time symlink creation in
|
|
1640
|
+
// installTunnelScripts() didn't land, fail the install loudly here rather
|
|
1641
|
+
// than shipping the exact Task 456 gap Task 555 exists to close.
|
|
1642
|
+
for (const linkPath of [
|
|
1643
|
+
resolve(process.env.HOME ?? "/root", "setup-tunnel.sh"),
|
|
1644
|
+
resolve(process.env.HOME ?? "/root", "reset-tunnel.sh"),
|
|
1645
|
+
]) {
|
|
1646
|
+
try {
|
|
1647
|
+
accessSync(linkPath, fsConstants.X_OK);
|
|
1648
|
+
console.log(` [create-maxy] verify: ${linkPath} executable ✓`);
|
|
1649
|
+
}
|
|
1650
|
+
catch (err) {
|
|
1651
|
+
console.error("");
|
|
1652
|
+
console.error(` [create-maxy:error] tunnel script symlink missing or not executable: ${linkPath}`);
|
|
1653
|
+
console.error(` ${err.message}`);
|
|
1654
|
+
process.exit(1);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1537
1657
|
}
|
|
1538
1658
|
// ---------------------------------------------------------------------------
|
|
1539
1659
|
// Main
|
|
@@ -1824,6 +1944,7 @@ try {
|
|
|
1824
1944
|
buildPlatform();
|
|
1825
1945
|
setupVncViewer();
|
|
1826
1946
|
setupAccount();
|
|
1947
|
+
installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
|
|
1827
1948
|
installService();
|
|
1828
1949
|
console.log("");
|
|
1829
1950
|
console.log("================================================================");
|
package/dist/uninstall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawnSync, execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, appendFileSync, cpSync } from "node:fs";
|
|
3
|
-
import { resolve, join } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, appendFileSync, cpSync, lstatSync, readlinkSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { resolve, join, dirname } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
const HOME = homedir();
|
|
@@ -262,6 +262,62 @@ export function exportData(exportPath) {
|
|
|
262
262
|
console.log(` Export complete: ${exportPath}`);
|
|
263
263
|
}
|
|
264
264
|
// ---------------------------------------------------------------------------
|
|
265
|
+
// Step 3b: Remove tunnel script symlinks
|
|
266
|
+
//
|
|
267
|
+
// installTunnelScripts() creates ~/setup-tunnel.sh and ~/reset-tunnel.sh as
|
|
268
|
+
// symlinks into INSTALL_DIR. Must run BEFORE removeAppDirs removes INSTALL_DIR
|
|
269
|
+
// so readlink-then-compare still works.
|
|
270
|
+
//
|
|
271
|
+
// Only unlink if the symlink's resolved target is inside this brand's
|
|
272
|
+
// INSTALL_DIR. A symlink pointing elsewhere came from a different brand install
|
|
273
|
+
// on the same home (unsupported but not destructive) — leave it alone.
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
function removeTunnelSymlinks() {
|
|
276
|
+
log("3b", "Removing tunnel script symlinks...");
|
|
277
|
+
for (const linkPath of [
|
|
278
|
+
resolve(HOME, "setup-tunnel.sh"),
|
|
279
|
+
resolve(HOME, "reset-tunnel.sh"),
|
|
280
|
+
]) {
|
|
281
|
+
let lstat;
|
|
282
|
+
try {
|
|
283
|
+
lstat = lstatSync(linkPath);
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
if (err.code === "ENOENT") {
|
|
287
|
+
console.log(` ${linkPath} not found — skipping`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
console.log(` Failed to stat ${linkPath}: ${err.message}`);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!lstat.isSymbolicLink()) {
|
|
294
|
+
console.log(` ${linkPath} is not a symlink — leaving untouched`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
let resolvedTarget;
|
|
298
|
+
try {
|
|
299
|
+
const raw = readlinkSync(linkPath);
|
|
300
|
+
resolvedTarget = resolve(dirname(linkPath), raw);
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
console.log(` ${linkPath} readlink failed: ${err.message} — leaving untouched`);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const insideInstallDir = resolvedTarget === INSTALL_DIR || resolvedTarget.startsWith(INSTALL_DIR + "/");
|
|
307
|
+
if (!insideInstallDir) {
|
|
308
|
+
console.log(` ${linkPath} → ${resolvedTarget} (outside ${INSTALL_DIR}) — leaving untouched`);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
unlinkSync(linkPath);
|
|
313
|
+
console.log(` Unlinked ${linkPath}`);
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
console.log(` Failed to unlink ${linkPath}: ${err.message}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
265
321
|
// Step 4: Remove application directories
|
|
266
322
|
// ---------------------------------------------------------------------------
|
|
267
323
|
function removeAppDirs() {
|
|
@@ -550,6 +606,7 @@ export async function runUninstall(options) {
|
|
|
550
606
|
...(options.exportPath
|
|
551
607
|
? [{ name: "Export data", fn: () => exportData(options.exportPath) }]
|
|
552
608
|
: []),
|
|
609
|
+
{ name: "Remove tunnel script symlinks", fn: removeTunnelSymlinks },
|
|
553
610
|
{ name: "Remove application directories", fn: removeAppDirs },
|
|
554
611
|
{ name: "Remove Neo4j data", fn: removeNeo4jData },
|
|
555
612
|
{ name: "Purge system packages", fn: purgeSystemPackages },
|