bdy 1.23.6-dev → 1.23.7-dev

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.
Files changed (205) hide show
  1. package/distTs/package.json +10 -2
  2. package/distTs/src/agent/linux.js +53 -3
  3. package/distTs/src/agent/manager.js +3 -6
  4. package/distTs/src/agent/osx.js +51 -3
  5. package/distTs/src/agent/system.js +400 -21
  6. package/distTs/src/agent/windows.js +4 -7
  7. package/distTs/src/command/agent/debug.js +2 -2
  8. package/distTs/src/command/agent/install.js +37 -10
  9. package/distTs/src/command/agent/restart.js +2 -2
  10. package/distTs/src/command/agent/run.js +3 -0
  11. package/distTs/src/command/agent/start.js +2 -2
  12. package/distTs/src/command/agent/status.js +19 -0
  13. package/distTs/src/command/agent/stop.js +2 -2
  14. package/distTs/src/command/agent/uninstall.js +5 -8
  15. package/distTs/src/command/agent/update.js +2 -2
  16. package/distTs/src/command/artifact/publish.js +3 -1
  17. package/distTs/src/command/version.js +13 -5
  18. package/distTs/src/logger.js +40 -4
  19. package/distTs/src/texts.js +35 -19
  20. package/distTs/src/utils.js +98 -3
  21. package/node_modules/asn1/Jenkinsfile +65 -0
  22. package/node_modules/asn1/LICENSE +19 -0
  23. package/node_modules/asn1/README.md +50 -0
  24. package/node_modules/asn1/lib/ber/errors.js +13 -0
  25. package/node_modules/asn1/lib/ber/index.js +27 -0
  26. package/node_modules/asn1/lib/ber/reader.js +262 -0
  27. package/node_modules/asn1/lib/ber/types.js +36 -0
  28. package/node_modules/asn1/lib/ber/writer.js +317 -0
  29. package/node_modules/asn1/lib/index.js +20 -0
  30. package/node_modules/asn1/package.json +31 -0
  31. package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
  32. package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
  33. package/node_modules/bcrypt-pbkdf/README.md +45 -0
  34. package/node_modules/bcrypt-pbkdf/index.js +556 -0
  35. package/node_modules/bcrypt-pbkdf/package.json +15 -0
  36. package/node_modules/safer-buffer/LICENSE +21 -0
  37. package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
  38. package/node_modules/safer-buffer/Readme.md +156 -0
  39. package/node_modules/safer-buffer/dangerous.js +58 -0
  40. package/node_modules/safer-buffer/package.json +34 -0
  41. package/node_modules/safer-buffer/safer.js +77 -0
  42. package/node_modules/safer-buffer/tests.js +406 -0
  43. package/node_modules/ssh2/.eslintignore +4 -0
  44. package/node_modules/ssh2/.eslintrc.js +5 -0
  45. package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
  46. package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
  47. package/node_modules/ssh2/LICENSE +19 -0
  48. package/node_modules/ssh2/README.md +1529 -0
  49. package/node_modules/ssh2/SFTP.md +413 -0
  50. package/node_modules/ssh2/examples/server-chat.js +238 -0
  51. package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
  52. package/node_modules/ssh2/install.js +27 -0
  53. package/node_modules/ssh2/lib/Channel.js +295 -0
  54. package/node_modules/ssh2/lib/agent.js +1123 -0
  55. package/node_modules/ssh2/lib/client.js +2176 -0
  56. package/node_modules/ssh2/lib/http-agents.js +84 -0
  57. package/node_modules/ssh2/lib/index.js +44 -0
  58. package/node_modules/ssh2/lib/keygen.js +582 -0
  59. package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
  60. package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
  61. package/node_modules/ssh2/lib/protocol/constants.js +356 -0
  62. package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
  63. package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
  64. package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
  65. package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
  66. package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
  67. package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
  68. package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
  69. package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
  70. package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
  71. package/node_modules/ssh2/lib/protocol/utils.js +356 -0
  72. package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
  73. package/node_modules/ssh2/lib/server.js +1380 -0
  74. package/node_modules/ssh2/lib/utils.js +336 -0
  75. package/node_modules/ssh2/package.json +49 -0
  76. package/node_modules/ssh2/test/common.js +316 -0
  77. package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
  78. package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
  79. package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
  80. package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
  81. package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
  82. package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
  83. package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
  84. package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
  85. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
  86. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
  87. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
  88. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
  89. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
  90. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
  91. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
  92. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
  93. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
  94. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
  95. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
  96. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
  97. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
  98. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
  99. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
  100. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
  101. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
  102. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
  103. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
  104. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
  105. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
  106. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
  107. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
  108. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
  109. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
  110. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
  111. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
  112. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
  113. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
  114. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
  115. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
  116. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
  117. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
  118. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
  119. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
  120. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
  121. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
  122. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
  123. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
  124. package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
  125. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
  126. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
  127. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
  128. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
  129. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
  130. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
  131. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
  132. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
  133. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
  134. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
  135. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
  136. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
  137. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
  138. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
  139. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
  140. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
  141. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
  142. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
  143. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
  144. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
  145. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
  146. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
  147. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
  148. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
  149. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
  150. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
  151. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
  152. package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
  153. package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
  154. package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
  155. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
  156. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
  157. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
  158. package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
  159. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
  160. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
  161. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
  162. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
  163. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
  164. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
  165. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
  166. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
  167. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
  168. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
  169. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
  170. package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
  171. package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
  172. package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
  173. package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
  174. package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
  175. package/node_modules/ssh2/test/test-exec.js +578 -0
  176. package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
  177. package/node_modules/ssh2/test/test-keygen.js +60 -0
  178. package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
  179. package/node_modules/ssh2/test/test-openssh.js +261 -0
  180. package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
  181. package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
  182. package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
  183. package/node_modules/ssh2/test/test-sftp.js +842 -0
  184. package/node_modules/ssh2/test/test-shell.js +109 -0
  185. package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
  186. package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
  187. package/node_modules/ssh2/test/test-userauth.js +611 -0
  188. package/node_modules/ssh2/test/test-worker-imports.js +25 -0
  189. package/node_modules/ssh2/test/test.js +20 -0
  190. package/node_modules/ssh2/util/build_pagent.bat +2 -0
  191. package/node_modules/ssh2/util/pagent.c +88 -0
  192. package/node_modules/ssh2/util/pagent.exe +0 -0
  193. package/node_modules/tweetnacl/.npmignore +4 -0
  194. package/node_modules/tweetnacl/AUTHORS.md +28 -0
  195. package/node_modules/tweetnacl/CHANGELOG.md +221 -0
  196. package/node_modules/tweetnacl/LICENSE +24 -0
  197. package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
  198. package/node_modules/tweetnacl/README.md +459 -0
  199. package/node_modules/tweetnacl/nacl-fast.js +2388 -0
  200. package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
  201. package/node_modules/tweetnacl/nacl.d.ts +98 -0
  202. package/node_modules/tweetnacl/nacl.js +1175 -0
  203. package/node_modules/tweetnacl/nacl.min.js +1 -0
  204. package/node_modules/tweetnacl/package.json +58 -0
  205. package/package.json +10 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.23.6-dev",
4
+ "version": "1.23.7-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "lint": "eslint \"*/**\"",
13
- "lint:types": "tsc --skipLibCheck --noEmit --project tsconfig.json",
13
+ "lint:types": "tsc --skipLibCheck --noEmit --project tsconfig.json && tsc --skipLibCheck --project test/tsconfig.json && tsc --skipLibCheck --project e2e/tsconfig.json",
14
14
  "watch": "tsc --watch --project tsconfig.json & tsc --watch --project src/visualTest/utils/tsconfig.json",
15
15
  "buildTs": "(tsc --project tsconfig.json & tsc --project src/visualTest/utils/tsconfig.json & wait) && cp package.json distTs/package.json && curl -fsSL https://raw.githubusercontent.com/buddy/cli/main/README.md -o README.md",
16
16
  "e2e": "npm run buildTs && vitest run --config e2e/vitest.config.ts",
@@ -38,6 +38,13 @@
38
38
  "engines": {
39
39
  "node": ">=24.14"
40
40
  },
41
+ "bundleDependencies": [
42
+ "ssh2",
43
+ "asn1",
44
+ "safer-buffer",
45
+ "bcrypt-pbkdf",
46
+ "tweetnacl"
47
+ ],
41
48
  "dependencies": {
42
49
  "@buddy-works/ci-info": "1.1.7",
43
50
  "@fastify/accept-negotiator": "2.0.1",
@@ -100,6 +107,7 @@
100
107
  "@types/mime-db": "1.43.6",
101
108
  "@types/mime-types": "3.0.1",
102
109
  "@types/netmask": "2.0.6",
110
+ "@types/node": "24.13.3",
103
111
  "@types/node-forge": "1.3.14",
104
112
  "@types/path-is-inside": "1.0.3",
105
113
  "@types/picomatch": "4.0.3",
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const system_1 = __importDefault(require("./system"));
7
+ const node_fs_1 = require("node:fs");
7
8
  const path_1 = __importDefault(require("path"));
8
9
  const child_process_1 = require("child_process");
9
10
  const logger_1 = __importDefault(require("../logger"));
@@ -39,12 +40,15 @@ class AgentLinux extends system_1.default {
39
40
  getSystemConfigPath() {
40
41
  return path_1.default.join(this.getSystemConfigDir(), 'agent.json');
41
42
  }
42
- getSystemStandalonePath() {
43
- return path_1.default.join(this.getSystemConfigDir(), 'pid.lock');
44
- }
45
43
  getServicePath() {
46
44
  return '/etc/systemd/system/bdy.service';
47
45
  }
46
+ // The token goes on the command line, so /proc/<pid>/cmdline shows it to every local account.
47
+ // That is a known trade and `Environment=` is not the way out of it: systemd.exec(5) says
48
+ // environment variables are unsuitable for secrets because it re-exports them over D-Bus, so
49
+ // `systemctl show bdy` would hand the token to the same unprivileged users - and file modes
50
+ // cannot stop that. Closing this means the agent reading the token from a file only it can
51
+ // read, which is a change of where the token lives, not of this template.
48
52
  getServiceConfig(id, host, token, port, user, debug) {
49
53
  const cli = this.getSystemBinaryPath();
50
54
  return `[Unit]
@@ -62,6 +66,24 @@ RestartSec=3
62
66
  [Install]
63
67
  WantedBy=multi-user.target`;
64
68
  }
69
+ readServiceUser() {
70
+ let cfg;
71
+ try {
72
+ cfg = (0, node_fs_1.readFileSync)(this.getServicePath(), 'utf-8');
73
+ }
74
+ catch {
75
+ // No unit to read, so there is nothing to say about the user it names
76
+ return null;
77
+ }
78
+ // This cli always writes the key, with `root` when no user was given. Not finding it means
79
+ // the unit has been through something else - systemd allows spaces around the `=` that this
80
+ // does not - and guessing root there would take the directory off whoever it does name.
81
+ const m = cfg.match(/^User=(.*)$/m);
82
+ if (!m)
83
+ return null;
84
+ const user = m[1].trim();
85
+ return user === 'root' ? '' : user;
86
+ }
65
87
  async install(id, host, token, port, start, user, pass, debug) {
66
88
  try {
67
89
  // ensure service clear
@@ -71,6 +93,10 @@ WantedBy=multi-user.target`;
71
93
  // do nothing
72
94
  }
73
95
  try {
96
+ // Resolved before the download rather than after it: a `--user` this machine does not have
97
+ // is a failed install either way, and finding out here costs no binary
98
+ if (user)
99
+ await this.resolveUserIds(user);
74
100
  logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_DIR);
75
101
  await this.ensureSystemConfigDir();
76
102
  logger_1.default.info(texts_1.LOG_AGENT_DOWNLOADING_ARCHIVE);
@@ -80,6 +106,11 @@ WantedBy=multi-user.target`;
80
106
  await this.fixBinaryRights();
81
107
  logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_SERVICE_CONFIG);
82
108
  await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user, debug));
109
+ // Everything above was written by this root process, so the service user does not own
110
+ // any of it yet and could not write its host key or its log. Ownership first, mode
111
+ // second - see chownSystemDir for why that order is the one that fails safely.
112
+ await this.chownSystemDir(user);
113
+ this.applySystemModes();
83
114
  logger_1.default.info(texts_1.LOG_AGENT_ENABLING_SYSTEM);
84
115
  if (start)
85
116
  await this.start();
@@ -93,6 +124,10 @@ WantedBy=multi-user.target`;
93
124
  async changeUser(user) {
94
125
  try {
95
126
  const json = this.loadSystemConfig();
127
+ // Resolved before the service is touched: a name this machine does not have would
128
+ // otherwise leave it stopped, with a config naming a user that does not exist
129
+ if (user)
130
+ await this.resolveUserIds(user);
96
131
  logger_1.default.info(texts_1.LOG_AGENT_STOPPING_SYSTEM);
97
132
  try {
98
133
  await this.stop();
@@ -101,6 +136,10 @@ WantedBy=multi-user.target`;
101
136
  // do nothing
102
137
  }
103
138
  await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user, json.debug));
139
+ // The directory follows the service to its new user, and back to root when the user is
140
+ // cleared - this is what makes changing the user later work without 0777 everywhere
141
+ await this.chownSystemDir(user);
142
+ this.applySystemModes();
104
143
  logger_1.default.info(texts_1.LOG_AGENT_STARTING_SYSTEM);
105
144
  await this.start();
106
145
  }
@@ -125,6 +164,17 @@ WantedBy=multi-user.target`;
125
164
  logger_1.default.info(texts_1.LOG_AGENT_EXTRACTING_ARCHIVE);
126
165
  await this.extractBinaryArchive();
127
166
  await this.fixBinaryRights();
167
+ // The service user is not passed in here, so it comes off the service config. Both steps
168
+ // are skipped when that cannot be read: tighter modes without the matching ownership are
169
+ // exactly what would lock a service user out of their own directory.
170
+ const svcUser = this.readServiceUser();
171
+ if (svcUser === null) {
172
+ logger_1.default.error((0, texts_1.LOG_AGENT_RIGHTS_SKIPPED)(this.getServicePath()));
173
+ }
174
+ else {
175
+ await this.chownSystemDir(svcUser);
176
+ this.applySystemModes();
177
+ }
128
178
  logger_1.default.info(texts_1.LOG_AGENT_STARTING_SYSTEM);
129
179
  await this.start();
130
180
  }
@@ -570,17 +570,14 @@ class AgentManagerClass {
570
570
  // do nothing
571
571
  }
572
572
  try {
573
+ // The host key went with clearSystemFiles above. Asking for it here by way of
574
+ // getNewAgentConfigDir would resolve against a config that is already gone, and land on
575
+ // the other install's.
573
576
  this.system.clearAgentFiles();
574
577
  }
575
578
  catch {
576
579
  // do nothing
577
580
  }
578
- try {
579
- this.system.clearNewAgentFiles();
580
- }
581
- catch {
582
- // do nothing
583
- }
584
581
  output_1.default.exitError(txt);
585
582
  }
586
583
  agentDisable() {
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const texts_1 = require("../texts");
7
7
  const logger_1 = __importDefault(require("../logger"));
8
+ const node_fs_1 = require("node:fs");
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const child_process_1 = require("child_process");
10
11
  const system_1 = __importDefault(require("./system"));
@@ -32,12 +33,14 @@ class AgentOsx extends system_1.default {
32
33
  getSystemConfigPath() {
33
34
  return path_1.default.join(this.getSystemConfigDir(), 'agent.json');
34
35
  }
35
- getSystemStandalonePath() {
36
- return path_1.default.join(this.getSystemConfigDir(), 'pid.lock');
37
- }
38
36
  getServicePath() {
39
37
  return '/Library/LaunchDaemons/bdy.plist';
40
38
  }
39
+ // The token goes into ProgramArguments, so `ps` shows it to every local account. That is a
40
+ // known trade, and moving it to EnvironmentVariables is not the way out: it would still sit in
41
+ // this plist, and the same move on systemd leaks the token through `systemctl show` whatever
42
+ // the file mode is - see the note in AgentLinux.getServiceConfig. Closing this means the agent
43
+ // reading the token from a file only it can read, which is a change of where the token lives.
41
44
  getServiceConfig(id, host, token, port, user, debug) {
42
45
  const cli = this.getSystemBinaryPath();
43
46
  let cfg = `<?xml version='1.0' encoding='UTF-8'?>
@@ -79,6 +82,23 @@ class AgentOsx extends system_1.default {
79
82
  </plist>`;
80
83
  return cfg;
81
84
  }
85
+ readServiceUser() {
86
+ let cfg;
87
+ try {
88
+ cfg = (0, node_fs_1.readFileSync)(this.getServicePath(), 'utf-8');
89
+ }
90
+ catch {
91
+ // No plist to read, so there is nothing to say about the user it names
92
+ return null;
93
+ }
94
+ // Only the xml plist this cli writes can be read this way. A binary one, or anything else
95
+ // that has been through another tool, is a file whose UserName we cannot see - and a
96
+ // missing UserName is only 'root' when we know we are looking at the whole thing.
97
+ if (!cfg.includes('<key>Label</key>'))
98
+ return null;
99
+ const m = cfg.match(/<key>UserName<\/key>\s*<string>([^<]+)<\/string>/);
100
+ return m ? m[1].trim() : '';
101
+ }
82
102
  async install(id, host, token, port, start, user, pass, debug) {
83
103
  try {
84
104
  // ensure service clear
@@ -88,6 +108,10 @@ class AgentOsx extends system_1.default {
88
108
  // do nothing
89
109
  }
90
110
  try {
111
+ // Resolved before the download rather than after it: a `--user` this machine does not have
112
+ // is a failed install either way, and finding out here costs no binary
113
+ if (user)
114
+ await this.resolveUserIds(user);
91
115
  logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_DIR);
92
116
  await this.ensureSystemConfigDir();
93
117
  logger_1.default.info(texts_1.LOG_AGENT_DOWNLOADING_ARCHIVE);
@@ -97,6 +121,11 @@ class AgentOsx extends system_1.default {
97
121
  await this.fixBinaryRights();
98
122
  logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_SERVICE_CONFIG);
99
123
  await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user, debug));
124
+ // Everything above was written by this root process, so the service user does not own
125
+ // any of it yet and could not write its host key or its log. Ownership first, mode
126
+ // second - see chownSystemDir for why that order is the one that fails safely.
127
+ await this.chownSystemDir(user);
128
+ this.applySystemModes();
100
129
  logger_1.default.info(texts_1.LOG_AGENT_ENABLING_SYSTEM);
101
130
  if (start)
102
131
  await this.start();
@@ -110,6 +139,10 @@ class AgentOsx extends system_1.default {
110
139
  async changeUser(user) {
111
140
  try {
112
141
  const json = this.loadSystemConfig();
142
+ // Resolved before the service is touched: a name this machine does not have would
143
+ // otherwise leave it stopped, with a config naming a user that does not exist
144
+ if (user)
145
+ await this.resolveUserIds(user);
113
146
  logger_1.default.info(texts_1.LOG_AGENT_STOPPING_SYSTEM);
114
147
  try {
115
148
  await this.stop();
@@ -118,6 +151,10 @@ class AgentOsx extends system_1.default {
118
151
  // do nothing
119
152
  }
120
153
  await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user, json.debug));
154
+ // The directory follows the service to its new user, and back to root when the user is
155
+ // cleared - this is what makes changing the user later work without 0777 everywhere
156
+ await this.chownSystemDir(user);
157
+ this.applySystemModes();
121
158
  logger_1.default.info(texts_1.LOG_AGENT_STARTING_SYSTEM);
122
159
  await this.start();
123
160
  }
@@ -142,6 +179,17 @@ class AgentOsx extends system_1.default {
142
179
  logger_1.default.info(texts_1.LOG_AGENT_EXTRACTING_ARCHIVE);
143
180
  await this.extractBinaryArchive();
144
181
  await this.fixBinaryRights();
182
+ // The service user is not passed in here, so it comes off the service config. Both steps
183
+ // are skipped when that cannot be read: tighter modes without the matching ownership are
184
+ // exactly what would lock a service user out of their own directory.
185
+ const svcUser = this.readServiceUser();
186
+ if (svcUser === null) {
187
+ logger_1.default.error((0, texts_1.LOG_AGENT_RIGHTS_SKIPPED)(this.getServicePath()));
188
+ }
189
+ else {
190
+ await this.chownSystemDir(svcUser);
191
+ this.applySystemModes();
192
+ }
145
193
  logger_1.default.info(texts_1.LOG_AGENT_STARTING_SYSTEM);
146
194
  await this.start();
147
195
  logger_1.default.info(texts_1.LOG_AGENT_ENABLED);