clay-server 2.22.1 → 2.22.2-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -222
- package/lib/daemon.js +36 -18
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -2011,14 +2011,6 @@ function showMainMenu(config, ip, setupCode) {
|
|
|
2011
2011
|
log("");
|
|
2012
2012
|
}
|
|
2013
2013
|
|
|
2014
|
-
// Always show setup code if one exists (persists until admin is created)
|
|
2015
|
-
var displayCode = setupCode || getSetupCode();
|
|
2016
|
-
if (displayCode) {
|
|
2017
|
-
log(" " + a.yellow + sym.warn + " Setup code: " + a.bold + displayCode + a.reset);
|
|
2018
|
-
log(" " + a.dim + "Open Clay in your browser and enter this code to create the admin account." + a.reset);
|
|
2019
|
-
log("");
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
2014
|
showMenuItems();
|
|
2023
2015
|
}
|
|
2024
2016
|
|
|
@@ -2036,7 +2028,6 @@ function showMainMenu(config, ip, setupCode) {
|
|
|
2036
2028
|
|
|
2037
2029
|
function showMenuItems() {
|
|
2038
2030
|
var items = [
|
|
2039
|
-
{ label: "Setup notifications", value: "notifications" },
|
|
2040
2031
|
{ label: "Settings", value: "settings" },
|
|
2041
2032
|
{ label: "Shut down server", value: "shutdown" },
|
|
2042
2033
|
{ label: "Keep server alive & exit", value: "exit" },
|
|
@@ -2044,13 +2035,6 @@ function showMainMenu(config, ip, setupCode) {
|
|
|
2044
2035
|
|
|
2045
2036
|
promptSelect("What would you like to do?", items, function (choice) {
|
|
2046
2037
|
switch (choice) {
|
|
2047
|
-
case "notifications":
|
|
2048
|
-
showSetupGuide(config, ip, function () {
|
|
2049
|
-
config = loadConfig() || config;
|
|
2050
|
-
showMainMenu(config, ip);
|
|
2051
|
-
});
|
|
2052
|
-
break;
|
|
2053
|
-
|
|
2054
2038
|
case "settings":
|
|
2055
2039
|
showSettingsMenu(config, ip);
|
|
2056
2040
|
break;
|
|
@@ -2109,190 +2093,6 @@ function showMainMenu(config, ip, setupCode) {
|
|
|
2109
2093
|
// ==============================
|
|
2110
2094
|
// Setup guide (2x2 toggle flow)
|
|
2111
2095
|
// ==============================
|
|
2112
|
-
function showSetupGuide(config, ip, goBack) {
|
|
2113
|
-
var protocol = config.tls ? "https" : "http";
|
|
2114
|
-
var wantRemote = false;
|
|
2115
|
-
var wantPush = false;
|
|
2116
|
-
|
|
2117
|
-
console.clear();
|
|
2118
|
-
printLogo();
|
|
2119
|
-
log("");
|
|
2120
|
-
log(sym.pointer + " " + a.bold + "Setup Notifications" + a.reset);
|
|
2121
|
-
log(sym.bar);
|
|
2122
|
-
|
|
2123
|
-
function redraw(renderFn) {
|
|
2124
|
-
console.clear();
|
|
2125
|
-
printLogo();
|
|
2126
|
-
log("");
|
|
2127
|
-
log(sym.pointer + " " + a.bold + "Setup Notifications" + a.reset);
|
|
2128
|
-
log(sym.bar);
|
|
2129
|
-
if (wantRemote) log(sym.done + " Access from outside your network? " + a.dim + "·" + a.reset + " " + a.green + "Yes" + a.reset);
|
|
2130
|
-
else log(sym.done + " Access from outside your network? " + a.dim + "· No" + a.reset);
|
|
2131
|
-
log(sym.bar);
|
|
2132
|
-
if (wantPush) log(sym.done + " Want push notifications? " + a.dim + "·" + a.reset + " " + a.green + "Yes" + a.reset);
|
|
2133
|
-
else log(sym.done + " Want push notifications? " + a.dim + "· No" + a.reset);
|
|
2134
|
-
log(sym.bar);
|
|
2135
|
-
renderFn();
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
promptToggle("Access from outside your network?", "Requires Tailscale on both devices", false, function (remote) {
|
|
2139
|
-
wantRemote = remote;
|
|
2140
|
-
log(sym.bar);
|
|
2141
|
-
promptToggle("Want push notifications?", "Requires HTTPS", false, function (push) {
|
|
2142
|
-
wantPush = push;
|
|
2143
|
-
log(sym.bar);
|
|
2144
|
-
afterToggles();
|
|
2145
|
-
});
|
|
2146
|
-
});
|
|
2147
|
-
|
|
2148
|
-
function afterToggles() {
|
|
2149
|
-
if (!wantRemote && !wantPush) {
|
|
2150
|
-
log(sym.done + " " + a.green + "All set!" + a.reset + a.dim + " · No additional setup needed." + a.reset);
|
|
2151
|
-
log(sym.end);
|
|
2152
|
-
log("");
|
|
2153
|
-
promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
|
|
2154
|
-
goBack();
|
|
2155
|
-
});
|
|
2156
|
-
return;
|
|
2157
|
-
}
|
|
2158
|
-
if (wantRemote) {
|
|
2159
|
-
renderTailscale();
|
|
2160
|
-
} else {
|
|
2161
|
-
renderHttps();
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
function renderTailscale() {
|
|
2166
|
-
var tsIP = getTailscaleIP();
|
|
2167
|
-
|
|
2168
|
-
log(sym.pointer + " " + a.bold + "Tailscale Setup" + a.reset);
|
|
2169
|
-
if (tsIP) {
|
|
2170
|
-
log(sym.bar + " " + a.green + "Tailscale is running" + a.reset + a.dim + " · " + tsIP + a.reset);
|
|
2171
|
-
log(sym.bar);
|
|
2172
|
-
log(sym.bar + " On your phone/tablet:");
|
|
2173
|
-
log(sym.bar + " " + a.dim + "1. Install Tailscale (App Store / Google Play)" + a.reset);
|
|
2174
|
-
log(sym.bar + " " + a.dim + "2. Sign in with the same account" + a.reset);
|
|
2175
|
-
log(sym.bar);
|
|
2176
|
-
renderHttps();
|
|
2177
|
-
} else {
|
|
2178
|
-
log(sym.bar + " " + a.yellow + "Tailscale not found on this machine." + a.reset);
|
|
2179
|
-
log(sym.bar + " " + a.dim + "Install: " + a.reset + "https://tailscale.com/download");
|
|
2180
|
-
log(sym.bar + " " + a.dim + "Then run: " + a.reset + "tailscale up");
|
|
2181
|
-
log(sym.bar);
|
|
2182
|
-
log(sym.bar + " On your phone/tablet:");
|
|
2183
|
-
log(sym.bar + " " + a.dim + "1. Install Tailscale (App Store / Google Play)" + a.reset);
|
|
2184
|
-
log(sym.bar + " " + a.dim + "2. Sign in with the same account" + a.reset);
|
|
2185
|
-
log(sym.bar);
|
|
2186
|
-
promptSelect("Select", [
|
|
2187
|
-
{ label: "Re-check", value: "recheck" },
|
|
2188
|
-
{ label: "Back", value: "back" },
|
|
2189
|
-
], function (choice) {
|
|
2190
|
-
if (choice === "recheck") {
|
|
2191
|
-
redraw(renderTailscale);
|
|
2192
|
-
} else {
|
|
2193
|
-
goBack();
|
|
2194
|
-
}
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
function renderHttps() {
|
|
2200
|
-
if (!wantPush) {
|
|
2201
|
-
showSetupQR();
|
|
2202
|
-
return;
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
// Builtin cert: HTTPS already active, skip mkcert flow entirely
|
|
2206
|
-
if (config.builtinCert) {
|
|
2207
|
-
log(sym.pointer + " " + a.bold + "HTTPS" + a.reset + a.dim + " · Enabled (builtin certificate)" + a.reset);
|
|
2208
|
-
log(sym.bar);
|
|
2209
|
-
showSetupQR();
|
|
2210
|
-
return;
|
|
2211
|
-
}
|
|
2212
|
-
|
|
2213
|
-
// mkcert flow (--mkcert or fallback)
|
|
2214
|
-
var mcReady = hasMkcert();
|
|
2215
|
-
log(sym.pointer + " " + a.bold + "HTTPS Setup (for push notifications)" + a.reset);
|
|
2216
|
-
if (mcReady) {
|
|
2217
|
-
log(sym.bar + " " + a.green + "mkcert is installed" + a.reset);
|
|
2218
|
-
if (!config.tls) {
|
|
2219
|
-
log(sym.bar + " " + a.dim + "Restarting server with HTTPS..." + a.reset);
|
|
2220
|
-
restartDaemonWithTLS(config, function (newConfig) {
|
|
2221
|
-
config = newConfig;
|
|
2222
|
-
log(sym.bar);
|
|
2223
|
-
showSetupQR();
|
|
2224
|
-
});
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
log(sym.bar);
|
|
2228
|
-
showSetupQR();
|
|
2229
|
-
} else {
|
|
2230
|
-
log(sym.bar + " " + a.yellow + "mkcert not found." + a.reset);
|
|
2231
|
-
var mkcertHint = process.platform === "win32"
|
|
2232
|
-
? "choco install mkcert && mkcert -install"
|
|
2233
|
-
: process.platform === "darwin"
|
|
2234
|
-
? "brew install mkcert && mkcert -install"
|
|
2235
|
-
: "apt install mkcert && mkcert -install";
|
|
2236
|
-
log(sym.bar + " " + a.dim + "Install: " + a.reset + mkcertHint);
|
|
2237
|
-
log(sym.bar);
|
|
2238
|
-
promptSelect("Select", [
|
|
2239
|
-
{ label: "Re-check", value: "recheck" },
|
|
2240
|
-
{ label: "Back", value: "back" },
|
|
2241
|
-
], function (choice) {
|
|
2242
|
-
if (choice === "recheck") {
|
|
2243
|
-
redraw(renderHttps);
|
|
2244
|
-
} else {
|
|
2245
|
-
goBack();
|
|
2246
|
-
}
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
function showSetupQR() {
|
|
2252
|
-
var tsIP = getTailscaleIP();
|
|
2253
|
-
var lanIP = null;
|
|
2254
|
-
if (!wantRemote) {
|
|
2255
|
-
var allIPs = getAllIPs();
|
|
2256
|
-
for (var j = 0; j < allIPs.length; j++) {
|
|
2257
|
-
if (!allIPs[j].startsWith("100.")) { lanIP = allIPs[j]; break; }
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
var setupIP = wantRemote ? (tsIP || ip) : (lanIP || ip);
|
|
2261
|
-
var setupQuery = wantRemote ? "" : "?mode=lan";
|
|
2262
|
-
// Builtin cert: link directly to the app with push notification guide
|
|
2263
|
-
// mkcert: use HTTP onboarding server for CA install flow
|
|
2264
|
-
var setupUrl;
|
|
2265
|
-
if (config.builtinCert) {
|
|
2266
|
-
setupUrl = toClayStudioUrl(setupIP, config.port, "https") + "/pwa";
|
|
2267
|
-
} else if (config.tls) {
|
|
2268
|
-
setupUrl = "http://" + setupIP + ":" + (config.port + 1) + "/setup" + setupQuery;
|
|
2269
|
-
} else {
|
|
2270
|
-
setupUrl = "http://" + setupIP + ":" + config.port + "/setup" + setupQuery;
|
|
2271
|
-
}
|
|
2272
|
-
log(sym.pointer + " " + a.bold + "Continue on your device" + a.reset);
|
|
2273
|
-
log(sym.bar + " " + a.dim + "Scan the QR code or open:" + a.reset);
|
|
2274
|
-
log(sym.bar + " " + a.bold + setupUrl + a.reset);
|
|
2275
|
-
log(sym.bar);
|
|
2276
|
-
qrcode.generate(setupUrl, { small: !isBasicTerm }, function (code) {
|
|
2277
|
-
var lines = code.split("\n").map(function (l) { return " " + sym.bar + " " + l; }).join("\n");
|
|
2278
|
-
console.log(lines);
|
|
2279
|
-
log(sym.bar);
|
|
2280
|
-
if (wantRemote) {
|
|
2281
|
-
log(sym.bar + " " + a.dim + "Can't connect? Make sure Tailscale is installed on your phone too." + a.reset);
|
|
2282
|
-
} else {
|
|
2283
|
-
log(sym.bar + " " + a.dim + "Can't connect? Your phone must be on the same Wi-Fi network." + a.reset);
|
|
2284
|
-
}
|
|
2285
|
-
log(sym.bar);
|
|
2286
|
-
log(sym.done + " " + a.dim + "Setup complete." + a.reset);
|
|
2287
|
-
log(sym.end);
|
|
2288
|
-
log("");
|
|
2289
|
-
promptSelect("Back?", [{ label: "Back", value: "back" }], function () {
|
|
2290
|
-
goBack();
|
|
2291
|
-
});
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
2096
|
// ==============================
|
|
2297
2097
|
// Settings sub-menu
|
|
2298
2098
|
// ==============================
|
|
@@ -2308,16 +2108,6 @@ function showSettingsMenu(config, ip) {
|
|
|
2308
2108
|
log(sym.bar);
|
|
2309
2109
|
|
|
2310
2110
|
// Detect current state
|
|
2311
|
-
var tsIP = getTailscaleIP();
|
|
2312
|
-
var tsOk = tsIP !== null;
|
|
2313
|
-
var mcOk = hasMkcert();
|
|
2314
|
-
|
|
2315
|
-
var tsStatus = tsOk
|
|
2316
|
-
? a.green + "Connected" + a.reset + a.dim + " · " + tsIP + a.reset
|
|
2317
|
-
: a.dim + "Not detected" + a.reset;
|
|
2318
|
-
var mcStatus = mcOk
|
|
2319
|
-
? a.green + "Installed" + a.reset
|
|
2320
|
-
: a.dim + "Not found" + a.reset;
|
|
2321
2111
|
var tlsStatus = config.tls
|
|
2322
2112
|
? a.green + "Enabled" + a.reset
|
|
2323
2113
|
: a.dim + "Disabled" + a.reset;
|
|
@@ -2328,8 +2118,6 @@ function showSettingsMenu(config, ip) {
|
|
|
2328
2118
|
? a.green + "On" + a.reset
|
|
2329
2119
|
: a.dim + "Off" + a.reset;
|
|
2330
2120
|
|
|
2331
|
-
log(sym.bar + " Tailscale " + tsStatus);
|
|
2332
|
-
log(sym.bar + " mkcert " + mcStatus);
|
|
2333
2121
|
log(sym.bar + " HTTPS " + tlsStatus);
|
|
2334
2122
|
var muEnabled = isMultiUser();
|
|
2335
2123
|
var muStatus = muEnabled
|
|
@@ -2355,9 +2143,7 @@ function showSettingsMenu(config, ip) {
|
|
|
2355
2143
|
log(sym.bar);
|
|
2356
2144
|
|
|
2357
2145
|
// Build items
|
|
2358
|
-
var items = [
|
|
2359
|
-
{ label: "Setup notifications", value: "guide" },
|
|
2360
|
-
];
|
|
2146
|
+
var items = [];
|
|
2361
2147
|
|
|
2362
2148
|
if (!muEnabled) {
|
|
2363
2149
|
if (config.pinHash) {
|
|
@@ -2392,13 +2178,6 @@ function showSettingsMenu(config, ip) {
|
|
|
2392
2178
|
|
|
2393
2179
|
promptSelect("Select", items, function (choice) {
|
|
2394
2180
|
switch (choice) {
|
|
2395
|
-
case "guide":
|
|
2396
|
-
showSetupGuide(config, ip, function () {
|
|
2397
|
-
config = loadConfig() || config;
|
|
2398
|
-
showSettingsMenu(config, ip);
|
|
2399
|
-
});
|
|
2400
|
-
break;
|
|
2401
|
-
|
|
2402
2181
|
case "pin":
|
|
2403
2182
|
log(sym.bar);
|
|
2404
2183
|
promptPin(function (pin) {
|
package/lib/daemon.js
CHANGED
|
@@ -1343,25 +1343,43 @@ if (config.keepAwake && process.platform === "darwin") {
|
|
|
1343
1343
|
// --- Spawn new daemon and graceful restart ---
|
|
1344
1344
|
function spawnAndRestart() {
|
|
1345
1345
|
try {
|
|
1346
|
-
var { spawn: spawnRestart } = require("child_process");
|
|
1347
|
-
var { logPath: restartLogPath, configPath: restartConfigPath } = require("./config");
|
|
1348
|
-
var daemonScript = path.join(__dirname, "daemon.js");
|
|
1349
|
-
var logFd = fs.openSync(restartLogPath(), "a");
|
|
1350
|
-
var child = spawnRestart(process.execPath, [daemonScript], {
|
|
1351
|
-
detached: true,
|
|
1352
|
-
windowsHide: true,
|
|
1353
|
-
stdio: ["ignore", logFd, logFd],
|
|
1354
|
-
env: Object.assign({}, process.env, {
|
|
1355
|
-
CLAY_CONFIG: restartConfigPath(),
|
|
1356
|
-
}),
|
|
1357
|
-
});
|
|
1358
|
-
child.unref();
|
|
1359
|
-
fs.closeSync(logFd);
|
|
1360
|
-
config.pid = child.pid;
|
|
1361
|
-
saveConfig(config);
|
|
1362
|
-
console.log("[daemon] Spawned new daemon (PID " + child.pid + "), shutting down...");
|
|
1363
1346
|
updateHandoff = true;
|
|
1364
|
-
|
|
1347
|
+
ipc.close();
|
|
1348
|
+
relay.destroyAll();
|
|
1349
|
+
if (relay.onboardingServer) relay.onboardingServer.close();
|
|
1350
|
+
|
|
1351
|
+
// Close the server first so the port is released before spawning the new daemon
|
|
1352
|
+
relay.server.close(function () {
|
|
1353
|
+
try {
|
|
1354
|
+
var { spawn: spawnRestart } = require("child_process");
|
|
1355
|
+
var { logPath: restartLogPath, configPath: restartConfigPath } = require("./config");
|
|
1356
|
+
var daemonScript = path.join(__dirname, "daemon.js");
|
|
1357
|
+
var logFd = fs.openSync(restartLogPath(), "a");
|
|
1358
|
+
var child = spawnRestart(process.execPath, [daemonScript], {
|
|
1359
|
+
detached: true,
|
|
1360
|
+
windowsHide: true,
|
|
1361
|
+
stdio: ["ignore", logFd, logFd],
|
|
1362
|
+
env: Object.assign({}, process.env, {
|
|
1363
|
+
CLAY_CONFIG: restartConfigPath(),
|
|
1364
|
+
}),
|
|
1365
|
+
});
|
|
1366
|
+
child.unref();
|
|
1367
|
+
fs.closeSync(logFd);
|
|
1368
|
+
config.pid = child.pid;
|
|
1369
|
+
saveConfig(config);
|
|
1370
|
+
console.log("[daemon] Spawned new daemon (PID " + child.pid + "), exiting.");
|
|
1371
|
+
process.exit(120);
|
|
1372
|
+
} catch (e) {
|
|
1373
|
+
console.error("[daemon] Restart failed:", e.message);
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
// Force exit after 5 seconds if server.close hangs
|
|
1379
|
+
setTimeout(function () {
|
|
1380
|
+
console.error("[daemon] Forced exit after timeout during restart");
|
|
1381
|
+
process.exit(1);
|
|
1382
|
+
}, 5000);
|
|
1365
1383
|
} catch (e) {
|
|
1366
1384
|
console.error("[daemon] Restart failed:", e.message);
|
|
1367
1385
|
relay.broadcastAll({ type: "toast", level: "error", message: "Restart failed: " + e.message });
|