@lifeaitools/clauth 1.30.4 → 1.30.5
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/cli/commands/serve.js +23 -21
- package/cli/enrollment-script.js +33 -13
- package/cli/index.js +3 -2
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -12610,14 +12610,14 @@ async function actionInstall(opts) {
|
|
|
12610
12610
|
// 2. No password → install watchdog that starts daemon in locked mode
|
|
12611
12611
|
// The browser dashboard opens, user enters password there.
|
|
12612
12612
|
// For passwordless restart, user can later run: clauth serve seal
|
|
12613
|
-
const pw = opts.pw || null;
|
|
12613
|
+
const pw = opts.pw || (opts.pwStdin ? fs.readFileSync(0, "utf8").replace(/\r?\n$/, "") : null);
|
|
12614
12614
|
|
|
12615
12615
|
if (platform === "win32") {
|
|
12616
12616
|
await installWindows(pw, tunnelHostname, execSync);
|
|
12617
12617
|
} else if (platform === "darwin") {
|
|
12618
12618
|
await installMacOS(pw, tunnelHostname, execSync);
|
|
12619
12619
|
} else {
|
|
12620
|
-
await installLinux(pw, tunnelHostname, execSync);
|
|
12620
|
+
await installLinux(pw, tunnelHostname, execSync, opts.pwStdin === true);
|
|
12621
12621
|
}
|
|
12622
12622
|
}
|
|
12623
12623
|
|
|
@@ -12851,7 +12851,7 @@ async function installMacOS(pw, tunnelHostname, execSync) {
|
|
|
12851
12851
|
}
|
|
12852
12852
|
|
|
12853
12853
|
// ── Linux: encrypted file + systemd user service ───────────
|
|
12854
|
-
async function installLinux(pw, tunnelHostname, execSync) {
|
|
12854
|
+
async function installLinux(pw, tunnelHostname, execSync, forceBootKey = false) {
|
|
12855
12855
|
const configDir = path.join(os.homedir(), ".config", "clauth");
|
|
12856
12856
|
const bootKeyPath = path.join(configDir, "boot.key");
|
|
12857
12857
|
const systemdDir = path.join(os.homedir(), ".config", "systemd", "user");
|
|
@@ -12864,7 +12864,7 @@ async function installLinux(pw, tunnelHostname, execSync) {
|
|
|
12864
12864
|
let useSecretTool = false;
|
|
12865
12865
|
const spinner = ora("Storing password securely...").start();
|
|
12866
12866
|
|
|
12867
|
-
if (pw) {
|
|
12867
|
+
if (pw && !forceBootKey) {
|
|
12868
12868
|
try {
|
|
12869
12869
|
execSync("which secret-tool", { encoding: "utf8", stdio: "pipe" });
|
|
12870
12870
|
execFileSync("secret-tool", ["store", "--label=clauth daemon password", "service", "clauth", "account", "daemon"], {
|
|
@@ -12874,23 +12874,25 @@ async function installLinux(pw, tunnelHostname, execSync) {
|
|
|
12874
12874
|
});
|
|
12875
12875
|
useSecretTool = true;
|
|
12876
12876
|
spinner.succeed(chalk.green("Password stored via secret-tool (GNOME Keyring)"));
|
|
12877
|
-
} catch {
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
12888
|
-
}
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12877
|
+
} catch {}
|
|
12878
|
+
}
|
|
12879
|
+
|
|
12880
|
+
if (pw && !useSecretTool) {
|
|
12881
|
+
// A desktop keyring cannot reliably unlock for a headless systemd service.
|
|
12882
|
+
try {
|
|
12883
|
+
const machineId = fs.readFileSync("/etc/machine-id", "utf8").trim();
|
|
12884
|
+
const encrypted = execFileSync("openssl", ["enc", "-aes-256-cbc", "-pbkdf2", "-iter", "100000", "-pass", `pass:${machineId}`, "-base64"], {
|
|
12885
|
+
input: pw,
|
|
12886
|
+
encoding: "utf8",
|
|
12887
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
12888
|
+
}).trim();
|
|
12889
|
+
fs.writeFileSync(bootKeyPath, encrypted, { mode: 0o600 });
|
|
12890
|
+
spinner.succeed(chalk.green(`Password encrypted -> boot.key (${forceBootKey ? "headless mode" : "openssl fallback"})`));
|
|
12891
|
+
} catch (err) {
|
|
12892
|
+
spinner.fail(chalk.red(`Password storage failed: ${err.message}`));
|
|
12893
|
+
process.exit(1);
|
|
12894
|
+
}
|
|
12895
|
+
} else if (!pw) {
|
|
12894
12896
|
spinner.succeed(chalk.yellow("No password supplied; daemon will start locked"));
|
|
12895
12897
|
}
|
|
12896
12898
|
|
package/cli/enrollment-script.js
CHANGED
|
@@ -32,20 +32,40 @@ function windowsScript({ supabaseUrl, anonKey, enrollmentCode }) {
|
|
|
32
32
|
].join("\r\n");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function linuxScript({ supabaseUrl, anonKey, enrollmentCode }) {
|
|
36
|
-
return [
|
|
37
|
-
"#!/usr/bin/env sh",
|
|
38
|
-
"set -eu",
|
|
39
|
-
"label=$(hostname)",
|
|
40
|
-
"command -v
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
35
|
+
function linuxScript({ supabaseUrl, anonKey, enrollmentCode }) {
|
|
36
|
+
return [
|
|
37
|
+
"#!/usr/bin/env sh",
|
|
38
|
+
"set -eu",
|
|
39
|
+
"label=$(hostname)",
|
|
40
|
+
"as_root() { if [ \"$(id -u)\" -eq 0 ]; then \"$@\"; elif command -v sudo >/dev/null; then sudo \"$@\"; else echo 'Root or sudo is required to install prerequisites.' >&2; exit 1; fi; }",
|
|
41
|
+
"install_prerequisites() {",
|
|
42
|
+
" if command -v apt-get >/dev/null; then as_root apt-get update; as_root apt-get install -y nodejs npm openssl;",
|
|
43
|
+
" elif command -v dnf >/dev/null; then as_root dnf install -y nodejs npm openssl;",
|
|
44
|
+
" else echo 'Headless enrollment supports systemd hosts with apt-get or dnf. Install Node.js 18+, npm, and openssl, then rerun this script.' >&2; exit 1; fi",
|
|
45
|
+
"}",
|
|
46
|
+
"if ! command -v systemctl >/dev/null || ! command -v loginctl >/dev/null; then echo 'Headless enrollment requires systemd and loginctl.' >&2; exit 1; fi",
|
|
47
|
+
"if ! command -v node >/dev/null || ! command -v npm >/dev/null || ! command -v openssl >/dev/null; then install_prerequisites; fi",
|
|
48
|
+
"node_major=$(node -p \"process.versions.node.split('.')[0]\")",
|
|
49
|
+
"if [ \"$node_major\" -lt 18 ]; then echo 'Node.js 18+ is required.' >&2; exit 1; fi",
|
|
50
|
+
"if ! npm install -g @lifeaitools/clauth@latest; then as_root npm install -g @lifeaitools/clauth@latest; fi",
|
|
51
|
+
["clauth setup", `--supabase-url ${posixShellQuote(supabaseUrl)}`, `--anon-key ${posixShellQuote(anonKey)}`, `--enrollment-code ${posixShellQuote(enrollmentCode)}`, '--label "$label"'].join(" "),
|
|
52
|
+
"user_name=$(id -un)",
|
|
53
|
+
"if loginctl enable-linger \"$user_name\"; then echo \"Linger enabled for $user_name.\"; elif as_root loginctl enable-linger \"$user_name\"; then echo \"Linger enabled for $user_name.\"; else echo 'Could not enable linger for unattended restart.' >&2; exit 1; fi",
|
|
54
|
+
"if ! test -t 0; then echo 'Headless enrollment requires an interactive TTY to set the vault password.' >&2; exit 1; fi",
|
|
55
|
+
"restore_echo() { stty echo 2>/dev/null || true; }",
|
|
56
|
+
"trap restore_echo EXIT HUP INT TERM",
|
|
57
|
+
"printf 'Re-enter the vault password to enable unattended restart: ' >&2",
|
|
58
|
+
"stty -echo",
|
|
59
|
+
"IFS= read -r vault_password",
|
|
60
|
+
"stty echo",
|
|
61
|
+
"trap - EXIT HUP INT TERM",
|
|
62
|
+
"printf '\\n' >&2",
|
|
63
|
+
"[ -n \"$vault_password\" ] || { echo 'A vault password is required for unattended restart.' >&2; exit 1; }",
|
|
64
|
+
"printf %s \"$vault_password\" | clauth serve install --pw-stdin",
|
|
65
|
+
"unset vault_password",
|
|
46
66
|
"rm -f -- \"$0\"",
|
|
47
|
-
].join("\n");
|
|
48
|
-
}
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
49
69
|
|
|
50
70
|
export function writeEnrollmentScript({ supabaseUrl, anonKey, enrollmentCode, label, target = "windows", appDir } = {}) {
|
|
51
71
|
if (!["windows", "linux"].includes(target)) throw new Error(`Unsupported enrollment target: ${target}`);
|
package/cli/index.js
CHANGED
|
@@ -1007,8 +1007,9 @@ program
|
|
|
1007
1007
|
.option("--tunnel <hostname>", "Fixed tunnel hostname (e.g. clauth.prtrust.fund) — uses named Cloudflare Tunnel instead of random URL")
|
|
1008
1008
|
.option("--staged", "Start on staging port (52438) for blue-green verification before make-live")
|
|
1009
1009
|
.option("--isolated", "Run on a non-live port without touching live PID files, browser, or boot-key credentials")
|
|
1010
|
-
.option("--from-boot-key", "Internal: password came from boot.key auto-unlock (degrade gracefully on verify failure)")
|
|
1011
|
-
.option("--pw-env", "Internal: read the daemon password from CLAUTH_BOOT_PASSWORD")
|
|
1010
|
+
.option("--from-boot-key", "Internal: password came from boot.key auto-unlock (degrade gracefully on verify failure)")
|
|
1011
|
+
.option("--pw-env", "Internal: read the daemon password from CLAUTH_BOOT_PASSWORD")
|
|
1012
|
+
.option("--pw-stdin", "Internal: read the installer password from standard input")
|
|
1012
1013
|
.option("--action <action>", "Internal: action override for daemon child")
|
|
1013
1014
|
.addHelpText("after", `
|
|
1014
1015
|
Actions:
|