hostctl 0.1.35 → 0.1.37

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.d.ts CHANGED
@@ -1282,9 +1282,11 @@ declare namespace k3s {
1282
1282
  interface CopyIdParams {
1283
1283
  public_key: string;
1284
1284
  user?: string;
1285
+ sudo?: boolean;
1285
1286
  }
1286
1287
  interface CopyIdResult {
1287
1288
  success: boolean;
1289
+ changed: boolean;
1288
1290
  }
1289
1291
  declare const _default$w: TaskFn<CopyIdParams, CopyIdResult>;
1290
1292
 
@@ -1309,6 +1311,8 @@ interface GrantNopasswdParams {
1309
1311
  interface GrantNopasswdResult {
1310
1312
  /** True if the operation was successful. */
1311
1313
  success: boolean;
1314
+ /** True if any state was changed. */
1315
+ changed: boolean;
1312
1316
  /** The path to the created sudoers file. */
1313
1317
  filePath: string;
1314
1318
  }
package/dist/index.js CHANGED
@@ -2552,7 +2552,7 @@ import process3 from "process";
2552
2552
  import * as cmdr from "commander";
2553
2553
 
2554
2554
  // src/version.ts
2555
- var version = "0.1.35";
2555
+ var version = "0.1.37";
2556
2556
 
2557
2557
  // src/cli.ts
2558
2558
  import JSON5 from "json5";
@@ -5402,8 +5402,10 @@ async function runFn2(context) {
5402
5402
  }
5403
5403
  const sudoPrefix = sudo ? ["sudo"] : [];
5404
5404
  if (append !== void 0) {
5405
- const stdin = `${append}${KeySequence.NEWLINE}${KeySequence.CTRL_D}`;
5406
- const { success } = await exec(["tee", "-a", file], { sudo, stdin });
5405
+ const escapedContent = append.replace(/'/g, "'\\''");
5406
+ const command = `echo '${escapedContent}' >> "${file}"`;
5407
+ info(`Appending with command: ${command}`);
5408
+ const { success } = await exec([...sudoPrefix, "sh", "-c", command], { sudo });
5407
5409
  return { changed: true, success };
5408
5410
  }
5409
5411
  if (block !== void 0) {
@@ -5498,11 +5500,12 @@ ${marker_end}`;
5498
5500
  }
5499
5501
  return { changed: true, success: result.success };
5500
5502
  } else {
5501
- const stdin = `${line}${KeySequence.NEWLINE}${KeySequence.CTRL_D}`;
5502
- info(`Appending line with tee: '${line}'`);
5503
- const result = await exec([...sudoPrefix, "tee", "-a", file], { sudo, stdin });
5503
+ const escapedLine = line.replace(/'/g, "'\\''");
5504
+ const command = `echo '${escapedLine}' >> "${file}"`;
5505
+ info(`Appending with command: ${command}`);
5506
+ const result = await exec([...sudoPrefix, "sh", "-c", command], { sudo });
5504
5507
  if (!result.success) {
5505
- info(`tee command failed. stderr: ${result.stderr}`);
5508
+ info(`Append command failed. stderr: ${result.stderr}`);
5506
5509
  }
5507
5510
  return { changed: true, success: result.success };
5508
5511
  }
@@ -6398,44 +6401,45 @@ var get_username_default = task(run45, { name: "core.user.get_username", descrip
6398
6401
 
6399
6402
  // src/core/ssh/copy_id.ts
6400
6403
  async function run46(context) {
6401
- const { params, run: runTask, exec, log, info } = context;
6402
- let { public_key, user } = params;
6404
+ const { params, run: runTask, exec, log, info, error } = context;
6405
+ let { public_key, user, sudo } = params;
6403
6406
  const publicKeyTrimmed = public_key.trim();
6407
+ const sudoPrefix = sudo ? ["sudo"] : [];
6404
6408
  if (!user) {
6405
6409
  const usernameResult = await runTask(get_username_default());
6406
6410
  if (usernameResult instanceof Error) {
6407
6411
  info(`Failed to get current username: ${usernameResult.message}`);
6408
- return { success: false };
6412
+ return { success: false, changed: false };
6409
6413
  }
6410
6414
  if (typeof usernameResult?.username !== "string") {
6411
6415
  info("Could not determine current username from task result.");
6412
- return { success: false };
6416
+ return { success: false, changed: false };
6413
6417
  }
6414
6418
  user = usernameResult.username;
6415
6419
  }
6416
6420
  if (!user) {
6417
6421
  info("User for SSH key copy is undefined.");
6418
- return { success: false };
6422
+ return { success: false, changed: false };
6419
6423
  }
6420
6424
  const userExistsResult = await runTask(exists_default4({ user }));
6421
6425
  if (userExistsResult instanceof Error) {
6422
6426
  info(`Error checking if user '${user}' exists: ${userExistsResult.message}`);
6423
- return { success: false };
6427
+ return { success: false, changed: false };
6424
6428
  }
6425
6429
  if (!userExistsResult?.exists) {
6426
6430
  const hostnameResult = await runTask(hostname_default());
6427
6431
  const hostname = hostnameResult instanceof Error || !hostnameResult?.hostname ? "current host" : hostnameResult.hostname;
6428
6432
  info(`User '${user}' does not exist on ${hostname}. Cannot copy SSH ID.`);
6429
- return { success: false };
6433
+ return { success: false, changed: false };
6430
6434
  }
6431
6435
  const homeDirResult = await runTask(home_dir_default({ user }));
6432
6436
  if (homeDirResult instanceof Error) {
6433
6437
  info(`Error getting home directory for user '${user}': ${homeDirResult.message}`);
6434
- return { success: false };
6438
+ return { success: false, changed: false };
6435
6439
  }
6436
6440
  if (typeof homeDirResult?.path !== "string") {
6437
6441
  info(`Could not determine home directory path for user '${user}'.`);
6438
- return { success: false };
6442
+ return { success: false, changed: false };
6439
6443
  }
6440
6444
  const userHome = homeDirResult.path;
6441
6445
  const sshDir = path3.join(userHome, ".ssh");
@@ -6444,7 +6448,7 @@ async function run46(context) {
6444
6448
  info(
6445
6449
  `Failed to create or set permissions for ${sshDir}: ${dirCreateResult instanceof Error ? dirCreateResult.message : "Task reported failure"}`
6446
6450
  );
6447
- return { success: false };
6451
+ return { success: false, changed: false };
6448
6452
  }
6449
6453
  const authorizedKeysFile = path3.join(sshDir, "authorized_keys");
6450
6454
  const touchResult = await runTask(touch_default({ file: authorizedKeysFile, mode: "600", owner: user }));
@@ -6452,30 +6456,28 @@ async function run46(context) {
6452
6456
  info(
6453
6457
  `Failed to touch or set permissions for ${authorizedKeysFile}: ${touchResult instanceof Error ? touchResult.message : "Task reported failure"}`
6454
6458
  );
6455
- return { success: false };
6459
+ return { success: false, changed: false };
6456
6460
  }
6457
6461
  const checkKeyCommandParts = [];
6458
6462
  checkKeyCommandParts.push("sudo", "-u", user, "grep", "-xqF", publicKeyTrimmed, authorizedKeysFile);
6459
6463
  const checkKeyCmdResult = await exec(checkKeyCommandParts);
6460
6464
  if (checkKeyCmdResult.exitCode === 0) {
6461
6465
  info(`SSH key already exists in ${authorizedKeysFile} for user ${user}.`);
6462
- return { success: true };
6466
+ return { success: true, changed: false };
6463
6467
  } else if (checkKeyCmdResult.exitCode !== 1) {
6464
6468
  info(`Error checking for existing SSH key: ${checkKeyCmdResult.stderr || checkKeyCmdResult.stdout}`);
6465
- return { success: false };
6469
+ return { success: false, changed: false };
6466
6470
  }
6467
- const sudoCmd = `sudo -u ${user}`;
6468
6471
  const escapedPublicKey = publicKeyTrimmed.replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
6469
- const shellCommand = `echo "${escapedPublicKey}" | ${sudoCmd} tee -a "${authorizedKeysFile}"`;
6470
- info(`Attempting to add key with command: sh -c '${shellCommand}'`);
6471
- const addKeyResult = await exec(["sh", "-c", shellCommand]);
6472
+ const command = `echo "${escapedPublicKey}" >> "${authorizedKeysFile}"`;
6473
+ const addKeyResult = await exec([...sudoPrefix, "sh", "-c", command], { sudo });
6472
6474
  if (!addKeyResult.success) {
6473
- info(
6474
- `Failed to add SSH key for user ${user}. Exit code: ${addKeyResult.exitCode}, Error: ${addKeyResult.stderr || addKeyResult.stdout}`
6475
- );
6475
+ error(`Failed to append public key to ${authorizedKeysFile}: ${addKeyResult.stderr}`);
6476
+ return { success: false, changed: false };
6476
6477
  }
6477
6478
  return {
6478
- success: addKeyResult.success
6479
+ success: true,
6480
+ changed: true
6479
6481
  };
6480
6482
  }
6481
6483
  var copy_id_default = task(run46, { name: "core.ssh.copy_id", description: "Ssh copy id." });
@@ -6500,41 +6502,58 @@ var check_default = task(run47, { name: "core.sudoers.check", description: "Sudo
6500
6502
 
6501
6503
  // src/core/sudoers/grant-nopasswd.ts
6502
6504
  async function run48(context) {
6503
- const { params, exec, log, error } = context;
6505
+ const { params, exec, log, error, info } = context;
6504
6506
  const { user } = params;
6505
6507
  if (!user) {
6506
6508
  throw new Error("The 'user' parameter is required.");
6507
6509
  }
6508
6510
  const sudoersFileName = params.name || user;
6509
6511
  const sudoersFilePath = `/etc/sudoers.d/${sudoersFileName}`;
6510
- const sudoersContent = `${user} ALL=(ALL) NOPASSWD: ALL${KeySequence.NEWLINE}${KeySequence.CTRL_D}`;
6512
+ const sudoersContent = `${user} ALL=(ALL) NOPASSWD: ALL`;
6511
6513
  try {
6512
- const writeCmd = await exec(["sudo", "tee", sudoersFilePath], { stdin: sudoersContent });
6513
- if (!writeCmd.success) {
6514
- error(`Failed to write to ${sudoersFilePath}: ${writeCmd.stderr}`);
6515
- return { success: false, filePath: sudoersFilePath };
6516
- }
6517
- const chmodCmd = await exec(["sudo", "chmod", "0440", sudoersFilePath]);
6518
- if (!chmodCmd.success) {
6519
- error(`Failed to set permissions on ${sudoersFilePath}: ${chmodCmd.stderr}`);
6520
- return { success: false, filePath: sudoersFilePath };
6521
- }
6522
- const chownCmd = await exec(["sudo", "chown", "root:root", sudoersFilePath]);
6523
- if (!chownCmd.success) {
6524
- error(`Failed to set ownership on ${sudoersFilePath}: ${chownCmd.stderr}`);
6525
- return { success: false, filePath: sudoersFilePath };
6514
+ const editResult = await context.run(
6515
+ core_default.file.edit({
6516
+ file: sudoersFilePath,
6517
+ line: sudoersContent,
6518
+ sudo: true
6519
+ })
6520
+ );
6521
+ if (editResult instanceof Error || !editResult.success) {
6522
+ throw new Error(`Failed to edit sudoers file at ${sudoersFilePath}`);
6523
+ }
6524
+ const chmodResult = await context.run(
6525
+ core_default.file.chmod({
6526
+ path: sudoersFilePath,
6527
+ mode: "0440",
6528
+ sudo: true
6529
+ })
6530
+ );
6531
+ if (chmodResult instanceof Error || !chmodResult.success) {
6532
+ throw new Error(`Failed to set permissions on ${sudoersFilePath}`);
6533
+ }
6534
+ const chownResult = await context.run(
6535
+ core_default.file.chown({
6536
+ path: sudoersFilePath,
6537
+ owner: "root",
6538
+ group: "root",
6539
+ sudo: true
6540
+ })
6541
+ );
6542
+ if (chownResult instanceof Error || !chownResult.success) {
6543
+ throw new Error(`Failed to set ownership on ${sudoersFilePath}`);
6526
6544
  }
6527
6545
  const checkCmd = await exec(["sudo", "visudo", "-cf", sudoersFilePath]);
6528
6546
  if (!checkCmd.success) {
6529
6547
  error(`Syntax check failed for ${sudoersFilePath}: ${checkCmd.stderr}`);
6530
6548
  await exec(["sudo", "rm", "-f", sudoersFilePath]);
6531
- return { success: false, filePath: sudoersFilePath };
6549
+ return { success: false, changed: false, filePath: sudoersFilePath };
6532
6550
  }
6551
+ const changed = editResult.changed;
6533
6552
  log(Verbosity.INFO, `Successfully granted passwordless sudo to user '${user}' via ${sudoersFilePath}`);
6534
- return { success: true, filePath: sudoersFilePath };
6553
+ return { success: true, changed, filePath: sudoersFilePath };
6535
6554
  } catch (e) {
6536
6555
  error(`An unexpected error occurred: ${e.message}`);
6537
- throw e;
6556
+ return { success: false, changed: false, filePath: "" };
6538
6557
  }
6539
6558
  }
6540
6559
  var grant_nopasswd_default = task(run48, {