clay-server 2.16.0 → 2.17.0-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 +91 -218
- package/lib/certs/fullchain.pem +47 -0
- package/lib/certs/privkey.pem +5 -0
- package/lib/daemon.js +19 -2
- package/lib/public/app.js +22 -1
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -52,6 +52,7 @@ function openUrl(url) {
|
|
|
52
52
|
var args = process.argv.slice(2);
|
|
53
53
|
var port = _isDev ? 2635 : 2633;
|
|
54
54
|
var useHttps = true;
|
|
55
|
+
var forceMkcert = false;
|
|
55
56
|
var skipUpdate = false;
|
|
56
57
|
var debugMode = false;
|
|
57
58
|
var autoYes = false;
|
|
@@ -81,6 +82,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
81
82
|
i++;
|
|
82
83
|
} else if (args[i] === "--no-https") {
|
|
83
84
|
useHttps = false;
|
|
85
|
+
} else if (args[i] === "--local-cert") {
|
|
86
|
+
forceMkcert = true;
|
|
84
87
|
} else if (args[i] === "--no-update" || args[i] === "--skip-update") {
|
|
85
88
|
skipUpdate = true;
|
|
86
89
|
} else if (args[i] === "--dev") {
|
|
@@ -124,7 +127,8 @@ for (var i = 0; i < args.length; i++) {
|
|
|
124
127
|
console.log("Options:");
|
|
125
128
|
console.log(" -p, --port <port> Port to listen on (default: 2633)");
|
|
126
129
|
console.log(" --host <address> Address to bind to (default: 0.0.0.0)");
|
|
127
|
-
console.log(" --no-https Disable HTTPS (enabled by default
|
|
130
|
+
console.log(" --no-https Disable HTTPS (enabled by default)");
|
|
131
|
+
console.log(" --local-cert Use local certificate (mkcert) instead of builtin");
|
|
128
132
|
console.log(" --no-update Skip auto-update check on startup");
|
|
129
133
|
console.log(" --debug Enable debug panel in the web UI");
|
|
130
134
|
console.log(" -y, --yes Skip interactive prompts (accept defaults)");
|
|
@@ -564,7 +568,43 @@ function getAllIPs() {
|
|
|
564
568
|
return ips;
|
|
565
569
|
}
|
|
566
570
|
|
|
571
|
+
function getBuiltinCert() {
|
|
572
|
+
try {
|
|
573
|
+
var certDir = path.join(__dirname, "..", "lib", "certs");
|
|
574
|
+
var keyPath = path.join(certDir, "privkey.pem");
|
|
575
|
+
var certPath = path.join(certDir, "fullchain.pem");
|
|
576
|
+
if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) return null;
|
|
577
|
+
|
|
578
|
+
// Check expiry
|
|
579
|
+
var certText = execFileSync("openssl", [
|
|
580
|
+
"x509", "-in", certPath, "-noout", "-enddate"
|
|
581
|
+
], { encoding: "utf8" });
|
|
582
|
+
var m = certText.match(/notAfter=(.+)/);
|
|
583
|
+
if (m) {
|
|
584
|
+
var expiry = new Date(m[1]);
|
|
585
|
+
var now = new Date();
|
|
586
|
+
// Skip if expiring within 7 days
|
|
587
|
+
if (expiry.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000) return null;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return { key: keyPath, cert: certPath, caRoot: null, builtin: true };
|
|
591
|
+
} catch (e) {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function toClayStudioUrl(ip, port, protocol) {
|
|
597
|
+
var dashed = ip.replace(/\./g, "-");
|
|
598
|
+
return protocol + "://" + dashed + ".d.clay.studio:" + port;
|
|
599
|
+
}
|
|
600
|
+
|
|
567
601
|
function ensureCerts(ip) {
|
|
602
|
+
// Check builtin cert first (unless --local-cert flag is set)
|
|
603
|
+
if (!forceMkcert) {
|
|
604
|
+
var builtin = getBuiltinCert();
|
|
605
|
+
if (builtin) return builtin;
|
|
606
|
+
}
|
|
607
|
+
|
|
568
608
|
var homeDir = os.homedir();
|
|
569
609
|
var certDir = path.join(process.env.CLAY_HOME || path.join(homeDir, ".clay"), "certs");
|
|
570
610
|
var keyPath = path.join(certDir, "key.pem");
|
|
@@ -1395,11 +1435,13 @@ function setup(callback) {
|
|
|
1395
1435
|
async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
|
|
1396
1436
|
var ip = getLocalIP();
|
|
1397
1437
|
var hasTls = false;
|
|
1438
|
+
var hasBuiltinCert = false;
|
|
1398
1439
|
|
|
1399
1440
|
if (useHttps) {
|
|
1400
1441
|
var certPaths = ensureCerts(ip);
|
|
1401
1442
|
if (certPaths) {
|
|
1402
1443
|
hasTls = true;
|
|
1444
|
+
if (certPaths.builtin) hasBuiltinCert = true;
|
|
1403
1445
|
} else {
|
|
1404
1446
|
log(sym.warn + " " + a.yellow + "HTTPS unavailable" + a.reset + a.dim + " · mkcert not installed" + a.reset);
|
|
1405
1447
|
}
|
|
@@ -1473,6 +1515,7 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
|
|
|
1473
1515
|
host: host,
|
|
1474
1516
|
pinHash: mode === "multi" && cliPin ? generateAuthToken(cliPin) : null,
|
|
1475
1517
|
tls: hasTls,
|
|
1518
|
+
builtinCert: hasBuiltinCert,
|
|
1476
1519
|
debug: debugMode,
|
|
1477
1520
|
keepAwake: keepAwake,
|
|
1478
1521
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
@@ -1547,7 +1590,9 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
|
|
|
1547
1590
|
// Headless mode — print status and exit immediately
|
|
1548
1591
|
if (headlessMode) {
|
|
1549
1592
|
var protocol = config.tls ? "https" : "http";
|
|
1550
|
-
var url =
|
|
1593
|
+
var url = config.builtinCert
|
|
1594
|
+
? toClayStudioUrl(ip, config.port, protocol)
|
|
1595
|
+
: protocol + "://" + ip + ":" + config.port;
|
|
1551
1596
|
console.log(" " + sym.done + " Daemon started (PID " + config.pid + ")");
|
|
1552
1597
|
console.log(" " + sym.done + " " + url);
|
|
1553
1598
|
console.log(" " + sym.done + " Headless mode — exiting CLI");
|
|
@@ -1565,10 +1610,14 @@ async function forkDaemon(mode, keepAwake, extraProjects, addCwd, wantOsUsers) {
|
|
|
1565
1610
|
async function devMode(mode, keepAwake, existingPinHash) {
|
|
1566
1611
|
var ip = getLocalIP();
|
|
1567
1612
|
var hasTls = false;
|
|
1613
|
+
var hasBuiltinCert = false;
|
|
1568
1614
|
|
|
1569
1615
|
if (useHttps) {
|
|
1570
1616
|
var certPaths = ensureCerts(ip);
|
|
1571
|
-
if (certPaths)
|
|
1617
|
+
if (certPaths) {
|
|
1618
|
+
hasTls = true;
|
|
1619
|
+
if (certPaths.builtin) hasBuiltinCert = true;
|
|
1620
|
+
}
|
|
1572
1621
|
}
|
|
1573
1622
|
|
|
1574
1623
|
var portFree = await isPortFree(port);
|
|
@@ -1630,6 +1679,7 @@ async function devMode(mode, keepAwake, existingPinHash) {
|
|
|
1630
1679
|
host: host,
|
|
1631
1680
|
pinHash: existingPinHash || null,
|
|
1632
1681
|
tls: hasTls,
|
|
1682
|
+
builtinCert: hasBuiltinCert,
|
|
1633
1683
|
debug: true,
|
|
1634
1684
|
keepAwake: keepAwake || false,
|
|
1635
1685
|
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
@@ -1778,6 +1828,7 @@ async function restartDaemonWithTLS(config, callback) {
|
|
|
1778
1828
|
callback(config);
|
|
1779
1829
|
return;
|
|
1780
1830
|
}
|
|
1831
|
+
var hasBuiltinCert = !!(certPaths && certPaths.builtin);
|
|
1781
1832
|
|
|
1782
1833
|
// Shut down old daemon
|
|
1783
1834
|
stopDaemonWatcher();
|
|
@@ -1801,6 +1852,7 @@ async function restartDaemonWithTLS(config, callback) {
|
|
|
1801
1852
|
port: config.port,
|
|
1802
1853
|
pinHash: config.pinHash || null,
|
|
1803
1854
|
tls: true,
|
|
1855
|
+
builtinCert: hasBuiltinCert,
|
|
1804
1856
|
debug: config.debug || false,
|
|
1805
1857
|
keepAwake: config.keepAwake || false,
|
|
1806
1858
|
dangerouslySkipPermissions: config.dangerouslySkipPermissions || false,
|
|
@@ -1879,7 +1931,9 @@ function showServerStarted(config, ip) {
|
|
|
1879
1931
|
function showMainMenu(config, ip) {
|
|
1880
1932
|
startDaemonWatcher();
|
|
1881
1933
|
var protocol = config.tls ? "https" : "http";
|
|
1882
|
-
var url =
|
|
1934
|
+
var url = config.builtinCert
|
|
1935
|
+
? toClayStudioUrl(ip, config.port, protocol)
|
|
1936
|
+
: protocol + "://" + ip + ":" + config.port;
|
|
1883
1937
|
|
|
1884
1938
|
sendIPCCommand(socketPath(), { cmd: "get_status" }).then(function (status) {
|
|
1885
1939
|
var projs = (status && status.projects) || [];
|
|
@@ -1925,7 +1979,6 @@ function showMainMenu(config, ip) {
|
|
|
1925
1979
|
function showMenuItems() {
|
|
1926
1980
|
var items = [
|
|
1927
1981
|
{ label: "Setup notifications", value: "notifications" },
|
|
1928
|
-
{ label: "Projects", value: "projects" },
|
|
1929
1982
|
{ label: "Settings", value: "settings" },
|
|
1930
1983
|
{ label: "Shut down server", value: "shutdown" },
|
|
1931
1984
|
{ label: "Keep server alive & exit", value: "exit" },
|
|
@@ -1940,10 +1993,6 @@ function showMainMenu(config, ip) {
|
|
|
1940
1993
|
});
|
|
1941
1994
|
break;
|
|
1942
1995
|
|
|
1943
|
-
case "projects":
|
|
1944
|
-
showProjectsMenu(config, ip);
|
|
1945
|
-
break;
|
|
1946
|
-
|
|
1947
1996
|
case "settings":
|
|
1948
1997
|
showSettingsMenu(config, ip);
|
|
1949
1998
|
break;
|
|
@@ -2000,203 +2049,6 @@ function showMainMenu(config, ip) {
|
|
|
2000
2049
|
});
|
|
2001
2050
|
}
|
|
2002
2051
|
|
|
2003
|
-
// ==============================
|
|
2004
|
-
// Projects sub-menu
|
|
2005
|
-
// ==============================
|
|
2006
|
-
function showProjectsMenu(config, ip) {
|
|
2007
|
-
sendIPCCommand(socketPath(), { cmd: "get_status" }).then(function (status) {
|
|
2008
|
-
if (!status.ok) {
|
|
2009
|
-
log(a.red + "Failed to get status" + a.reset);
|
|
2010
|
-
showMainMenu(config, ip);
|
|
2011
|
-
return;
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
console.clear();
|
|
2015
|
-
printLogo();
|
|
2016
|
-
log("");
|
|
2017
|
-
log(sym.pointer + " " + a.bold + "Projects" + a.reset);
|
|
2018
|
-
log(sym.bar);
|
|
2019
|
-
|
|
2020
|
-
var projs = status.projects || [];
|
|
2021
|
-
for (var i = 0; i < projs.length; i++) {
|
|
2022
|
-
var p = projs[i];
|
|
2023
|
-
var statusIcon = p.isProcessing ? "⚡" : (p.clients > 0 ? "🟢" : "⏸");
|
|
2024
|
-
var sessionLabel = p.sessions === 1 ? "1 session" : p.sessions + " sessions";
|
|
2025
|
-
var projName = p.title || p.project;
|
|
2026
|
-
log(sym.bar + " " + a.bold + projName + a.reset + " " + sessionLabel + " " + statusIcon);
|
|
2027
|
-
log(sym.bar + " " + a.dim + p.path + a.reset);
|
|
2028
|
-
if (i < projs.length - 1) log(sym.bar);
|
|
2029
|
-
}
|
|
2030
|
-
log(sym.bar);
|
|
2031
|
-
|
|
2032
|
-
// Build menu items
|
|
2033
|
-
var items = [];
|
|
2034
|
-
|
|
2035
|
-
// Check if cwd is already registered
|
|
2036
|
-
var cwdRegistered = false;
|
|
2037
|
-
for (var j = 0; j < projs.length; j++) {
|
|
2038
|
-
if (projs[j].path === cwd) {
|
|
2039
|
-
cwdRegistered = true;
|
|
2040
|
-
break;
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
if (!cwdRegistered) {
|
|
2044
|
-
items.push({ label: "+ Add " + a.bold + path.basename(cwd) + a.reset + " " + a.dim + "(" + cwd + ")" + a.reset, value: "add_cwd" });
|
|
2045
|
-
}
|
|
2046
|
-
items.push({ label: "+ Add project...", value: "add_other" });
|
|
2047
|
-
|
|
2048
|
-
for (var k = 0; k < projs.length; k++) {
|
|
2049
|
-
var itemLabel = projs[k].title || projs[k].project;
|
|
2050
|
-
items.push({ label: itemLabel, value: "detail:" + projs[k].slug });
|
|
2051
|
-
}
|
|
2052
|
-
items.push({ label: "Back", value: "back" });
|
|
2053
|
-
|
|
2054
|
-
promptSelect("Select", items, function (choice) {
|
|
2055
|
-
if (choice === "back") {
|
|
2056
|
-
console.clear();
|
|
2057
|
-
printLogo();
|
|
2058
|
-
log("");
|
|
2059
|
-
showMainMenu(config, ip);
|
|
2060
|
-
} else if (choice === "add_cwd") {
|
|
2061
|
-
sendIPCCommand(socketPath(), { cmd: "add_project", path: cwd }).then(function (res) {
|
|
2062
|
-
if (res.ok) {
|
|
2063
|
-
log(sym.done + " " + a.green + "Added: " + res.slug + a.reset);
|
|
2064
|
-
config = loadConfig() || config;
|
|
2065
|
-
} else {
|
|
2066
|
-
log(sym.warn + " " + a.yellow + (res.error || "Failed") + a.reset);
|
|
2067
|
-
}
|
|
2068
|
-
log("");
|
|
2069
|
-
showProjectsMenu(config, ip);
|
|
2070
|
-
});
|
|
2071
|
-
} else if (choice === "add_other") {
|
|
2072
|
-
log(sym.bar);
|
|
2073
|
-
promptText("Directory path", cwd, function (dirPath) {
|
|
2074
|
-
if (dirPath === null) {
|
|
2075
|
-
showProjectsMenu(config, ip);
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
var absPath = path.resolve(dirPath);
|
|
2079
|
-
try {
|
|
2080
|
-
var stat = fs.statSync(absPath);
|
|
2081
|
-
if (!stat.isDirectory()) {
|
|
2082
|
-
log(sym.warn + " " + a.red + "Not a directory: " + absPath + a.reset);
|
|
2083
|
-
setTimeout(function () { showProjectsMenu(config, ip); }, 2000);
|
|
2084
|
-
return;
|
|
2085
|
-
}
|
|
2086
|
-
} catch (e) {
|
|
2087
|
-
log(sym.warn + " " + a.red + "Directory not found: " + absPath + a.reset);
|
|
2088
|
-
setTimeout(function () { showProjectsMenu(config, ip); }, 2000);
|
|
2089
|
-
return;
|
|
2090
|
-
}
|
|
2091
|
-
var alreadyExists = false;
|
|
2092
|
-
for (var pi = 0; pi < projs.length; pi++) {
|
|
2093
|
-
if (projs[pi].path === absPath) {
|
|
2094
|
-
alreadyExists = true;
|
|
2095
|
-
break;
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
if (alreadyExists) {
|
|
2099
|
-
log(sym.done + " " + a.yellow + "Already added: " + path.basename(absPath) + a.reset + " " + a.dim + "(" + absPath + ")" + a.reset);
|
|
2100
|
-
setTimeout(function () { showProjectsMenu(config, ip); }, 2000);
|
|
2101
|
-
return;
|
|
2102
|
-
}
|
|
2103
|
-
sendIPCCommand(socketPath(), { cmd: "add_project", path: absPath }).then(function (res) {
|
|
2104
|
-
if (res.ok) {
|
|
2105
|
-
log(sym.done + " " + a.green + "Added: " + res.slug + a.reset + " " + a.dim + "(" + absPath + ")" + a.reset);
|
|
2106
|
-
config = loadConfig() || config;
|
|
2107
|
-
} else {
|
|
2108
|
-
log(sym.warn + " " + a.yellow + (res.error || "Failed") + a.reset);
|
|
2109
|
-
}
|
|
2110
|
-
setTimeout(function () { showProjectsMenu(config, ip); }, 2000);
|
|
2111
|
-
});
|
|
2112
|
-
});
|
|
2113
|
-
} else if (choice.startsWith("detail:")) {
|
|
2114
|
-
var detailSlug = choice.substring(7);
|
|
2115
|
-
showProjectDetail(config, ip, detailSlug, projs);
|
|
2116
|
-
}
|
|
2117
|
-
});
|
|
2118
|
-
});
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// ==============================
|
|
2122
|
-
// Project detail
|
|
2123
|
-
// ==============================
|
|
2124
|
-
function showProjectDetail(config, ip, slug, projects) {
|
|
2125
|
-
var proj = null;
|
|
2126
|
-
for (var i = 0; i < projects.length; i++) {
|
|
2127
|
-
if (projects[i].slug === slug) {
|
|
2128
|
-
proj = projects[i];
|
|
2129
|
-
break;
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
if (!proj) {
|
|
2133
|
-
showProjectsMenu(config, ip);
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
var displayName = proj.title || proj.project;
|
|
2138
|
-
|
|
2139
|
-
console.clear();
|
|
2140
|
-
printLogo();
|
|
2141
|
-
log("");
|
|
2142
|
-
log(sym.pointer + " " + a.bold + displayName + a.reset + " " + a.dim + proj.slug + " · " + proj.path + a.reset);
|
|
2143
|
-
log(sym.bar);
|
|
2144
|
-
var sessionLabel = proj.sessions === 1 ? "1 session" : proj.sessions + " sessions";
|
|
2145
|
-
var clientLabel = proj.clients === 1 ? "1 client" : proj.clients + " clients";
|
|
2146
|
-
log(sym.bar + " " + sessionLabel + " · " + clientLabel);
|
|
2147
|
-
if (proj.title) {
|
|
2148
|
-
log(sym.bar + " " + a.dim + "Title: " + a.reset + proj.title);
|
|
2149
|
-
}
|
|
2150
|
-
log(sym.bar);
|
|
2151
|
-
|
|
2152
|
-
var items = [
|
|
2153
|
-
{ label: proj.title ? "Change title" : "Set title", value: "title" },
|
|
2154
|
-
{ label: "Remove project", value: "remove" },
|
|
2155
|
-
{ label: "Back", value: "back" },
|
|
2156
|
-
];
|
|
2157
|
-
|
|
2158
|
-
promptSelect("What would you like to do?", items, function (choice) {
|
|
2159
|
-
if (choice === "title") {
|
|
2160
|
-
log(sym.bar);
|
|
2161
|
-
promptText("Project title", proj.title || proj.project, function (newTitle) {
|
|
2162
|
-
if (newTitle === null) {
|
|
2163
|
-
showProjectDetail(config, ip, slug, projects);
|
|
2164
|
-
return;
|
|
2165
|
-
}
|
|
2166
|
-
var titleVal = newTitle.trim();
|
|
2167
|
-
// If same as directory name, clear custom title
|
|
2168
|
-
if (titleVal === proj.project || titleVal === "") {
|
|
2169
|
-
titleVal = null;
|
|
2170
|
-
}
|
|
2171
|
-
sendIPCCommand(socketPath(), { cmd: "set_project_title", slug: slug, title: titleVal }).then(function (res) {
|
|
2172
|
-
if (res.ok) {
|
|
2173
|
-
proj.title = titleVal;
|
|
2174
|
-
config = loadConfig() || config;
|
|
2175
|
-
log(sym.done + " " + a.green + "Title updated" + a.reset);
|
|
2176
|
-
} else {
|
|
2177
|
-
log(sym.warn + " " + a.yellow + (res.error || "Failed") + a.reset);
|
|
2178
|
-
}
|
|
2179
|
-
log("");
|
|
2180
|
-
showProjectDetail(config, ip, slug, projects);
|
|
2181
|
-
});
|
|
2182
|
-
});
|
|
2183
|
-
} else if (choice === "remove") {
|
|
2184
|
-
sendIPCCommand(socketPath(), { cmd: "remove_project", slug: slug }).then(function (res) {
|
|
2185
|
-
if (res.ok) {
|
|
2186
|
-
log(sym.done + " " + a.green + "Removed: " + slug + a.reset);
|
|
2187
|
-
config = loadConfig() || config;
|
|
2188
|
-
} else {
|
|
2189
|
-
log(sym.warn + " " + a.yellow + (res.error || "Failed") + a.reset);
|
|
2190
|
-
}
|
|
2191
|
-
log("");
|
|
2192
|
-
showProjectsMenu(config, ip);
|
|
2193
|
-
});
|
|
2194
|
-
} else {
|
|
2195
|
-
showProjectsMenu(config, ip);
|
|
2196
|
-
}
|
|
2197
|
-
});
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
2052
|
// ==============================
|
|
2201
2053
|
// Setup guide (2x2 toggle flow)
|
|
2202
2054
|
// ==============================
|
|
@@ -2229,7 +2081,7 @@ function showSetupGuide(config, ip, goBack) {
|
|
|
2229
2081
|
promptToggle("Access from outside your network?", "Requires Tailscale on both devices", false, function (remote) {
|
|
2230
2082
|
wantRemote = remote;
|
|
2231
2083
|
log(sym.bar);
|
|
2232
|
-
promptToggle("Want push notifications?", "Requires HTTPS
|
|
2084
|
+
promptToggle("Want push notifications?", "Requires HTTPS", false, function (push) {
|
|
2233
2085
|
wantPush = push;
|
|
2234
2086
|
log(sym.bar);
|
|
2235
2087
|
afterToggles();
|
|
@@ -2293,6 +2145,15 @@ function showSetupGuide(config, ip, goBack) {
|
|
|
2293
2145
|
return;
|
|
2294
2146
|
}
|
|
2295
2147
|
|
|
2148
|
+
// Builtin cert: HTTPS already active, skip mkcert flow entirely
|
|
2149
|
+
if (config.builtinCert) {
|
|
2150
|
+
log(sym.pointer + " " + a.bold + "HTTPS" + a.reset + a.dim + " · Enabled (builtin certificate)" + a.reset);
|
|
2151
|
+
log(sym.bar);
|
|
2152
|
+
showSetupQR();
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// mkcert flow (--mkcert or fallback)
|
|
2296
2157
|
var mcReady = hasMkcert();
|
|
2297
2158
|
log(sym.pointer + " " + a.bold + "HTTPS Setup (for push notifications)" + a.reset);
|
|
2298
2159
|
if (mcReady) {
|
|
@@ -2341,10 +2202,16 @@ function showSetupGuide(config, ip, goBack) {
|
|
|
2341
2202
|
}
|
|
2342
2203
|
var setupIP = wantRemote ? (tsIP || ip) : (lanIP || ip);
|
|
2343
2204
|
var setupQuery = wantRemote ? "" : "?mode=lan";
|
|
2344
|
-
//
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2205
|
+
// Builtin cert: link directly to the app with push notification guide
|
|
2206
|
+
// mkcert: use HTTP onboarding server for CA install flow
|
|
2207
|
+
var setupUrl;
|
|
2208
|
+
if (config.builtinCert) {
|
|
2209
|
+
setupUrl = toClayStudioUrl(setupIP, config.port, "https") + "?playbook=push-notifications";
|
|
2210
|
+
} else if (config.tls) {
|
|
2211
|
+
setupUrl = "http://" + setupIP + ":" + (config.port + 1) + "/setup" + setupQuery;
|
|
2212
|
+
} else {
|
|
2213
|
+
setupUrl = "http://" + setupIP + ":" + config.port + "/setup" + setupQuery;
|
|
2214
|
+
}
|
|
2348
2215
|
log(sym.pointer + " " + a.bold + "Continue on your device" + a.reset);
|
|
2349
2216
|
log(sym.bar + " " + a.dim + "Scan the QR code or open:" + a.reset);
|
|
2350
2217
|
log(sym.bar + " " + a.bold + setupUrl + a.reset);
|
|
@@ -2435,11 +2302,13 @@ function showSettingsMenu(config, ip) {
|
|
|
2435
2302
|
{ label: "Setup notifications", value: "guide" },
|
|
2436
2303
|
];
|
|
2437
2304
|
|
|
2438
|
-
if (
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2305
|
+
if (!muEnabled) {
|
|
2306
|
+
if (config.pinHash) {
|
|
2307
|
+
items.push({ label: "Change PIN", value: "pin" });
|
|
2308
|
+
items.push({ label: "Remove PIN", value: "remove_pin" });
|
|
2309
|
+
} else {
|
|
2310
|
+
items.push({ label: "Set PIN", value: "pin" });
|
|
2311
|
+
}
|
|
2443
2312
|
}
|
|
2444
2313
|
if (muEnabled) {
|
|
2445
2314
|
items.push({ label: "Disable multi-user mode", value: "disable_multi_user" });
|
|
@@ -2759,7 +2628,9 @@ function showSettingsMenu(config, ip) {
|
|
|
2759
2628
|
return;
|
|
2760
2629
|
}
|
|
2761
2630
|
var protocol = config.tls ? "https" : "http";
|
|
2762
|
-
var recoveryUrl =
|
|
2631
|
+
var recoveryUrl = config.builtinCert
|
|
2632
|
+
? toClayStudioUrl(ip, config.port, protocol) + "/recover/" + recoveryUrlPath
|
|
2633
|
+
: protocol + "://" + ip + ":" + config.port + "/recover/" + recoveryUrlPath;
|
|
2763
2634
|
log(sym.bar);
|
|
2764
2635
|
log(sym.bar + " " + a.yellow + sym.warn + " Admin Password Recovery" + a.reset);
|
|
2765
2636
|
log(sym.bar);
|
|
@@ -2849,7 +2720,9 @@ var currentVersion = require("../package.json").version;
|
|
|
2849
2720
|
if (headlessMode) {
|
|
2850
2721
|
var protocol = config.tls ? "https" : "http";
|
|
2851
2722
|
var ip = getLocalIP();
|
|
2852
|
-
var url =
|
|
2723
|
+
var url = config.builtinCert
|
|
2724
|
+
? toClayStudioUrl(ip, config.port, protocol)
|
|
2725
|
+
: protocol + "://" + ip + ":" + config.port;
|
|
2853
2726
|
console.log(" " + sym.done + " Daemon already running (PID " + config.pid + ")");
|
|
2854
2727
|
console.log(" " + sym.done + " " + url);
|
|
2855
2728
|
process.exit(0);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDgjCCAwigAwIBAgISBmtBLGUclrEfhwU9evzgyDCQMAoGCCqGSM49BAMDMDIx
|
|
3
|
+
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
|
|
4
|
+
ODAeFw0yNjAzMjMwOTQ0MDJaFw0yNjA2MjEwOTQ0MDFaMBoxGDAWBgNVBAMMDyou
|
|
5
|
+
ZC5jbGF5LnN0dWRpbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEhAD1htqRg8
|
|
6
|
+
n7evflBZ1X7UaCeqBGcvG/MNtlAKd1VVfVGFuanyUjksV9++R1EuKLhEPM3loL/3
|
|
7
|
+
Gz8+XEewGw6jggIUMIICEDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB
|
|
8
|
+
BQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU8Anzwuqwtujb+h3o/CnbJu5Z
|
|
9
|
+
+W8wHwYDVR0jBBgwFoAUjw0TovYuftFQbDMYOF1ZjiNykcowMgYIKwYBBQUHAQEE
|
|
10
|
+
JjAkMCIGCCsGAQUFBzAChhZodHRwOi8vZTguaS5sZW5jci5vcmcvMBoGA1UdEQQT
|
|
11
|
+
MBGCDyouZC5jbGF5LnN0dWRpbzATBgNVHSAEDDAKMAgGBmeBDAECATAtBgNVHR8E
|
|
12
|
+
JjAkMCKgIKAehhxodHRwOi8vZTguYy5sZW5jci5vcmcvMTcuY3JsMIIBBQYKKwYB
|
|
13
|
+
BAHWeQIEAgSB9gSB8wDxAHcAFoMtq/CpJQ8P8DqlRf/Iv8gj0IdL9gQpJ/jnHzMT
|
|
14
|
+
9foAAAGdGkoIlgAABAMASDBGAiEAhJFJwEIag1Bzt0WtYgMzLdJn/k+Is2RukdDo
|
|
15
|
+
G5sXpyMCIQCQOX9nOoaVIXxF1KXiavbAY5QIyJRuvK7Fn6WeL58YQQB2AMs49xWJ
|
|
16
|
+
fIShRF9bwd37yW7ymlnNRwppBYWwyxTDFFjnAAABnRpKCJgAAAQDAEcwRQIhAI98
|
|
17
|
+
cmflulGQJMfD10jbstVwodGpzl5licg6FTxcYachAiBZV1cZnPfasTzcteXyjCuz
|
|
18
|
+
c1wayYAtch+0soAWvBKZRzAKBggqhkjOPQQDAwNoADBlAjAwJO4ti4AJJTtMxYsr
|
|
19
|
+
Jf5052oDrD2POtoiPksruQVVacsq0T/9VYVX+X2vElCrxFwCMQDcSToBRWGpv/G3
|
|
20
|
+
JBpbEAB1qhk1Z9lYPQKH6gRvtp35XJWY0PucGRWgQUrXuZcxGlA=
|
|
21
|
+
-----END CERTIFICATE-----
|
|
22
|
+
-----BEGIN CERTIFICATE-----
|
|
23
|
+
MIIEVjCCAj6gAwIBAgIQY5WTY8JOcIJxWRi/w9ftVjANBgkqhkiG9w0BAQsFADBP
|
|
24
|
+
MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy
|
|
25
|
+
Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa
|
|
26
|
+
Fw0yNzAzMTIyMzU5NTlaMDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF
|
|
27
|
+
bmNyeXB0MQswCQYDVQQDEwJFODB2MBAGByqGSM49AgEGBSuBBAAiA2IABNFl8l7c
|
|
28
|
+
S7QMApzSsvru6WyrOq44ofTUOTIzxULUzDMMNMchIJBwXOhiLxxxs0LXeb5GDcHb
|
|
29
|
+
R6EToMffgSZjO9SNHfY9gjMy9vQr5/WWOrQTZxh7az6NSNnq3u2ubT6HTKOB+DCB
|
|
30
|
+
9TAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB
|
|
31
|
+
MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI8NE6L2Ln7RUGwzGDhdWY4j
|
|
32
|
+
cpHKMB8GA1UdIwQYMBaAFHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEB
|
|
33
|
+
BCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzATBgNVHSAE
|
|
34
|
+
DDAKMAgGBmeBDAECATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veDEuYy5sZW5j
|
|
35
|
+
ci5vcmcvMA0GCSqGSIb3DQEBCwUAA4ICAQBnE0hGINKsCYWi0Xx1ygxD5qihEjZ0
|
|
36
|
+
RI3tTZz1wuATH3ZwYPIp97kWEayanD1j0cDhIYzy4CkDo2jB8D5t0a6zZWzlr98d
|
|
37
|
+
AQFNh8uKJkIHdLShy+nUyeZxc5bNeMp1Lu0gSzE4McqfmNMvIpeiwWSYO9w82Ob8
|
|
38
|
+
otvXcO2JUYi3svHIWRm3+707DUbL51XMcY2iZdlCq4Wa9nbuk3WTU4gr6LY8MzVA
|
|
39
|
+
aDQG2+4U3eJ6qUF10bBnR1uuVyDYs9RhrwucRVnfuDj29CMLTsplM5f5wSV5hUpm
|
|
40
|
+
Uwp/vV7M4w4aGunt74koX71n4EdagCsL/Yk5+mAQU0+tue0JOfAV/R6t1k+Xk9s2
|
|
41
|
+
HMQFeoxppfzAVC04FdG9M+AC2JWxmFSt6BCuh3CEey3fE52Qrj9YM75rtvIjsm/1
|
|
42
|
+
Hl+u//Wqxnu1ZQ4jpa+VpuZiGOlWrqSP9eogdOhCGisnyewWJwRQOqK16wiGyZeR
|
|
43
|
+
xs/Bekw65vwSIaVkBruPiTfMOo0Zh4gVa8/qJgMbJbyrwwG97z/PRgmLKCDl8z3d
|
|
44
|
+
tA0Z7qq7fta0Gl24uyuB05dqI5J1LvAzKuWdIjT1tP8qCoxSE/xpix8hX2dt3h+/
|
|
45
|
+
jujUgFPFZ0EVZ0xSyBNRF3MboGZnYXFUxpNjTWPKpagDHJQmqrAcDmWJnMsFY3jS
|
|
46
|
+
u1igv3OefnWjSQ==
|
|
47
|
+
-----END CERTIFICATE-----
|
package/lib/daemon.js
CHANGED
|
@@ -60,10 +60,26 @@ if (config.osUsers) {
|
|
|
60
60
|
// --- TLS ---
|
|
61
61
|
var tlsOptions = null;
|
|
62
62
|
if (config.tls) {
|
|
63
|
+
// 1. Check builtin cert (shipped with package)
|
|
64
|
+
var builtinKeyPath = path.join(__dirname, "certs", "privkey.pem");
|
|
65
|
+
var builtinCertPath = path.join(__dirname, "certs", "fullchain.pem");
|
|
66
|
+
|
|
67
|
+
// 2. User cert (mkcert, etc.)
|
|
63
68
|
var os = require("os");
|
|
64
69
|
var certDir = path.join(process.env.CLAY_HOME || process.env.CLAUDE_RELAY_HOME || path.join(os.homedir(), ".clay"), "certs");
|
|
65
|
-
var
|
|
66
|
-
var
|
|
70
|
+
var userKeyPath = path.join(certDir, "key.pem");
|
|
71
|
+
var userCertPath = path.join(certDir, "cert.pem");
|
|
72
|
+
|
|
73
|
+
var keyPath, certPath;
|
|
74
|
+
if (config.builtinCert !== false && fs.existsSync(builtinKeyPath) && fs.existsSync(builtinCertPath)) {
|
|
75
|
+
keyPath = builtinKeyPath;
|
|
76
|
+
certPath = builtinCertPath;
|
|
77
|
+
config.builtinCert = true;
|
|
78
|
+
} else {
|
|
79
|
+
keyPath = userKeyPath;
|
|
80
|
+
certPath = userCertPath;
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
try {
|
|
68
84
|
tlsOptions = {
|
|
69
85
|
key: fs.readFileSync(keyPath),
|
|
@@ -119,6 +135,7 @@ var listenHost = config.host || "0.0.0.0";
|
|
|
119
135
|
var relay = createServer({
|
|
120
136
|
tlsOptions: tlsOptions,
|
|
121
137
|
caPath: caRoot,
|
|
138
|
+
builtinCert: config.builtinCert || false,
|
|
122
139
|
pinHash: config.pinHash || null,
|
|
123
140
|
port: config.port,
|
|
124
141
|
debug: config.debug || false,
|
package/lib/public/app.js
CHANGED
|
@@ -5192,6 +5192,21 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
5192
5192
|
// --- Playbook Engine ---
|
|
5193
5193
|
initPlaybook();
|
|
5194
5194
|
|
|
5195
|
+
// Auto-open playbook from URL param (e.g. ?playbook=push-notifications)
|
|
5196
|
+
(function () {
|
|
5197
|
+
var params = new URLSearchParams(window.location.search);
|
|
5198
|
+
var pbId = params.get("playbook");
|
|
5199
|
+
if (pbId) {
|
|
5200
|
+
// Small delay to ensure DOM and playbook registry are ready
|
|
5201
|
+
setTimeout(function () { openPlaybook(pbId); }, 300);
|
|
5202
|
+
// Clean up URL
|
|
5203
|
+
params.delete("playbook");
|
|
5204
|
+
var clean = params.toString();
|
|
5205
|
+
var newUrl = window.location.pathname + (clean ? "?" + clean : "") + window.location.hash;
|
|
5206
|
+
window.history.replaceState(null, "", newUrl);
|
|
5207
|
+
}
|
|
5208
|
+
})();
|
|
5209
|
+
|
|
5195
5210
|
// --- In-session search (Cmd+F / Ctrl+F) ---
|
|
5196
5211
|
initSessionSearch({
|
|
5197
5212
|
messagesEl: messagesEl,
|
|
@@ -6751,7 +6766,13 @@ import { initLongPress } from './modules/longpress.js';
|
|
|
6751
6766
|
modal.querySelector(".pwa-modal-backdrop").addEventListener("click", closeModal);
|
|
6752
6767
|
|
|
6753
6768
|
confirmBtn.addEventListener("click", function () {
|
|
6754
|
-
//
|
|
6769
|
+
// Builtin cert (*.d.clay.studio): open push notification guide directly
|
|
6770
|
+
if (location.hostname.endsWith(".d.clay.studio")) {
|
|
6771
|
+
closeModal();
|
|
6772
|
+
openPlaybook("push-notifications");
|
|
6773
|
+
return;
|
|
6774
|
+
}
|
|
6775
|
+
// mkcert / other: redirect to onboarding setup page
|
|
6755
6776
|
var port = parseInt(location.port, 10);
|
|
6756
6777
|
var setupUrl;
|
|
6757
6778
|
if (!port) {
|