@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.
@@ -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
- // Fallback: encrypt with openssl and store in file.
12879
- try {
12880
- const machineId = fs.readFileSync("/etc/machine-id", "utf8").trim();
12881
- const encrypted = execFileSync("openssl", ["enc", "-aes-256-cbc", "-pbkdf2", "-iter", "100000", "-pass", `pass:${machineId}`, "-base64"], {
12882
- input: pw,
12883
- encoding: "utf8",
12884
- stdio: ["pipe", "pipe", "pipe"],
12885
- }).trim();
12886
- fs.writeFileSync(bootKeyPath, encrypted, { mode: 0o600 });
12887
- spinner.succeed(chalk.green("Password encrypted -> boot.key (openssl fallback)"));
12888
- } catch (err) {
12889
- spinner.fail(chalk.red(`Password storage failed: ${err.message}`));
12890
- process.exit(1);
12891
- }
12892
- }
12893
- } else {
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
 
@@ -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 node >/dev/null || { echo 'Node.js 18+ is required.' >&2; exit 1; }",
41
- "command -v npm >/dev/null || { echo 'npm is required.' >&2; exit 1; }",
42
- "npm install -g @lifeaitools/clauth@latest",
43
- ["clauth setup", `--supabase-url ${posixShellQuote(supabaseUrl)}`, `--anon-key ${posixShellQuote(anonKey)}`, `--enrollment-code ${posixShellQuote(enrollmentCode)}`, '--label "$label"'].join(" "),
44
- "clauth serve install",
45
- "echo 'For reboot persistence on a headless host, run: loginctl enable-linger \"$USER\"'",
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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.30.4",
3
+ "version": "1.30.5",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {