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.
- package/distTs/package.json +10 -2
- package/distTs/src/agent/linux.js +53 -3
- package/distTs/src/agent/manager.js +3 -6
- package/distTs/src/agent/osx.js +51 -3
- package/distTs/src/agent/system.js +400 -21
- package/distTs/src/agent/windows.js +4 -7
- package/distTs/src/command/agent/debug.js +2 -2
- package/distTs/src/command/agent/install.js +37 -10
- package/distTs/src/command/agent/restart.js +2 -2
- package/distTs/src/command/agent/run.js +3 -0
- package/distTs/src/command/agent/start.js +2 -2
- package/distTs/src/command/agent/status.js +19 -0
- package/distTs/src/command/agent/stop.js +2 -2
- package/distTs/src/command/agent/uninstall.js +5 -8
- package/distTs/src/command/agent/update.js +2 -2
- package/distTs/src/command/artifact/publish.js +3 -1
- package/distTs/src/command/version.js +13 -5
- package/distTs/src/logger.js +40 -4
- package/distTs/src/texts.js +35 -19
- package/distTs/src/utils.js +98 -3
- package/node_modules/asn1/Jenkinsfile +65 -0
- package/node_modules/asn1/LICENSE +19 -0
- package/node_modules/asn1/README.md +50 -0
- package/node_modules/asn1/lib/ber/errors.js +13 -0
- package/node_modules/asn1/lib/ber/index.js +27 -0
- package/node_modules/asn1/lib/ber/reader.js +262 -0
- package/node_modules/asn1/lib/ber/types.js +36 -0
- package/node_modules/asn1/lib/ber/writer.js +317 -0
- package/node_modules/asn1/lib/index.js +20 -0
- package/node_modules/asn1/package.json +31 -0
- package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
- package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
- package/node_modules/bcrypt-pbkdf/README.md +45 -0
- package/node_modules/bcrypt-pbkdf/index.js +556 -0
- package/node_modules/bcrypt-pbkdf/package.json +15 -0
- package/node_modules/safer-buffer/LICENSE +21 -0
- package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
- package/node_modules/safer-buffer/Readme.md +156 -0
- package/node_modules/safer-buffer/dangerous.js +58 -0
- package/node_modules/safer-buffer/package.json +34 -0
- package/node_modules/safer-buffer/safer.js +77 -0
- package/node_modules/safer-buffer/tests.js +406 -0
- package/node_modules/ssh2/.eslintignore +4 -0
- package/node_modules/ssh2/.eslintrc.js +5 -0
- package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
- package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
- package/node_modules/ssh2/LICENSE +19 -0
- package/node_modules/ssh2/README.md +1529 -0
- package/node_modules/ssh2/SFTP.md +413 -0
- package/node_modules/ssh2/examples/server-chat.js +238 -0
- package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
- package/node_modules/ssh2/install.js +27 -0
- package/node_modules/ssh2/lib/Channel.js +295 -0
- package/node_modules/ssh2/lib/agent.js +1123 -0
- package/node_modules/ssh2/lib/client.js +2176 -0
- package/node_modules/ssh2/lib/http-agents.js +84 -0
- package/node_modules/ssh2/lib/index.js +44 -0
- package/node_modules/ssh2/lib/keygen.js +582 -0
- package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
- package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
- package/node_modules/ssh2/lib/protocol/constants.js +356 -0
- package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
- package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
- package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
- package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
- package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
- package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
- package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
- package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
- package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
- package/node_modules/ssh2/lib/protocol/utils.js +356 -0
- package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
- package/node_modules/ssh2/lib/server.js +1380 -0
- package/node_modules/ssh2/lib/utils.js +336 -0
- package/node_modules/ssh2/package.json +49 -0
- package/node_modules/ssh2/test/common.js +316 -0
- package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
- package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
- package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
- package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
- package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
- package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
- package/node_modules/ssh2/test/test-exec.js +578 -0
- package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
- package/node_modules/ssh2/test/test-keygen.js +60 -0
- package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
- package/node_modules/ssh2/test/test-openssh.js +261 -0
- package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
- package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
- package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
- package/node_modules/ssh2/test/test-sftp.js +842 -0
- package/node_modules/ssh2/test/test-shell.js +109 -0
- package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
- package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
- package/node_modules/ssh2/test/test-userauth.js +611 -0
- package/node_modules/ssh2/test/test-worker-imports.js +25 -0
- package/node_modules/ssh2/test/test.js +20 -0
- package/node_modules/ssh2/util/build_pagent.bat +2 -0
- package/node_modules/ssh2/util/pagent.c +88 -0
- package/node_modules/ssh2/util/pagent.exe +0 -0
- package/node_modules/tweetnacl/.npmignore +4 -0
- package/node_modules/tweetnacl/AUTHORS.md +28 -0
- package/node_modules/tweetnacl/CHANGELOG.md +221 -0
- package/node_modules/tweetnacl/LICENSE +24 -0
- package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
- package/node_modules/tweetnacl/README.md +459 -0
- package/node_modules/tweetnacl/nacl-fast.js +2388 -0
- package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
- package/node_modules/tweetnacl/nacl.d.ts +98 -0
- package/node_modules/tweetnacl/nacl.js +1175 -0
- package/node_modules/tweetnacl/nacl.min.js +1 -0
- package/node_modules/tweetnacl/package.json +58 -0
- package/package.json +10 -2
|
@@ -12,6 +12,10 @@ const logger_1 = __importDefault(require("../logger"));
|
|
|
12
12
|
const manager_1 = __importDefault(require("./manager"));
|
|
13
13
|
const output_1 = __importDefault(require("../output"));
|
|
14
14
|
class AgentSystem {
|
|
15
|
+
// Set by `agent run`, which is only ever started by the service - see getServiceConfig in
|
|
16
|
+
// the platform classes. Nothing else needs it: a standalone agent runs in the process that
|
|
17
|
+
// installed or started it, and that one resolves its own directory from disk.
|
|
18
|
+
serviceMode = false;
|
|
15
19
|
// extend
|
|
16
20
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
17
21
|
async install(_id, _host, _token, _port, _start, _user, _pass, _debug) {
|
|
@@ -36,14 +40,125 @@ class AgentSystem {
|
|
|
36
40
|
getSystemConfigPath() {
|
|
37
41
|
return '';
|
|
38
42
|
}
|
|
43
|
+
// Where a standalone install kept its lock before those files moved to the user
|
|
44
|
+
// directory. Still the path for an install made back then - see isUserOwnedInstall.
|
|
39
45
|
getSystemStandalonePath() {
|
|
40
|
-
return '';
|
|
46
|
+
return path_1.default.join(this.getSystemConfigDir(), 'pid.lock');
|
|
41
47
|
}
|
|
42
48
|
// extend
|
|
43
49
|
getSystemConfigDir() {
|
|
44
50
|
return '';
|
|
45
51
|
}
|
|
52
|
+
// `--app` installs no service and runs the agent as the invoking user, so its files
|
|
53
|
+
// belong to that user: a system location was the only thing making the mode need root.
|
|
54
|
+
// They get their own directory rather than sitting next to cfg.json, so nothing here can
|
|
55
|
+
// collide with the legacy `agent.json` that loadNewAgentConfig migrates out of the home
|
|
56
|
+
// directory.
|
|
57
|
+
getStandaloneConfigDir() {
|
|
58
|
+
return path_1.default.join((0, utils_1.getHomeDirectory)(), 'agent');
|
|
59
|
+
}
|
|
60
|
+
getStandaloneConfigPath() {
|
|
61
|
+
return path_1.default.join(this.getStandaloneConfigDir(), 'agent.json');
|
|
62
|
+
}
|
|
63
|
+
getStandalonePidPath() {
|
|
64
|
+
return path_1.default.join(this.getStandaloneConfigDir(), 'pid.lock');
|
|
65
|
+
}
|
|
66
|
+
// True when the install this cli would act on is the user's own, which is the case that
|
|
67
|
+
// needs no admin rights. A service install never is, and neither is a standalone install
|
|
68
|
+
// written before the move: that one is still in the system directory and has to keep
|
|
69
|
+
// being managed from there, root and all.
|
|
70
|
+
isUserOwnedInstall() {
|
|
71
|
+
// A service reads and writes the system directory whatever `--user` it was given, and
|
|
72
|
+
// that user may well have an `--app` install of their own in their home - which is not
|
|
73
|
+
// the install this process is
|
|
74
|
+
if (this.serviceMode)
|
|
75
|
+
return false;
|
|
76
|
+
// One agent per machine, `--app` or service. So when a system config exists it is the
|
|
77
|
+
// install: it is machine-wide and every account can read it, while a config sitting in
|
|
78
|
+
// someone's home next to it is a leftover. Letting that leftover win is what made
|
|
79
|
+
// `sudo bdy agent update` update nothing and `agent user` answer 'not a service'.
|
|
80
|
+
if ((0, node_fs_1.existsSync)(this.getSystemConfigPath()))
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
return (0, node_fs_1.existsSync)(this.getStandaloneConfigPath());
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// A home directory that cannot even be resolved holds no install
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Every home an `--app` install could be sitting in. The containers are read rather than
|
|
91
|
+
// resolved out of /etc/passwd or dscl: a home somewhere else is a home this cannot see,
|
|
92
|
+
// which is the same class of miss as one it cannot read.
|
|
93
|
+
getHomeDirs() {
|
|
94
|
+
const containers = (0, utils_1.isWindows)()
|
|
95
|
+
? ['C:\\Users']
|
|
96
|
+
: process.platform === 'darwin'
|
|
97
|
+
? ['/Users']
|
|
98
|
+
: ['/home'];
|
|
99
|
+
// Root's own, which is not inside any of those
|
|
100
|
+
const homes = (0, utils_1.isWindows)() ? [] : ['/root', '/var/root'];
|
|
101
|
+
containers.forEach((root) => {
|
|
102
|
+
try {
|
|
103
|
+
(0, node_fs_1.readdirSync)(root).forEach((entry) => homes.push(path_1.default.join(root, entry)));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// a container that is not there, or not this account's to read
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return homes;
|
|
110
|
+
}
|
|
111
|
+
getStandaloneConfigPathIn(home) {
|
|
112
|
+
return path_1.default.join(home, utils_1.HOME_DIR_NAME, 'agent', 'agent.json');
|
|
113
|
+
}
|
|
114
|
+
// A standalone install is only visible from the account that made it, so a service install
|
|
115
|
+
// has to go looking rather than land on top of one. Only ever called with root behind it,
|
|
116
|
+
// and best-effort by nature - a home this cannot read holds an install this cannot see, which
|
|
117
|
+
// is why the port check stays as the backstop for whatever is actually running.
|
|
118
|
+
findAppInstalls() {
|
|
119
|
+
if ((0, utils_1.isDocker)())
|
|
120
|
+
return [];
|
|
121
|
+
return this.getHomeDirs().filter((home) => {
|
|
122
|
+
try {
|
|
123
|
+
return (0, node_fs_1.existsSync)(this.getStandaloneConfigPathIn(home));
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}).map((home) => this.getStandaloneConfigPathIn(home));
|
|
129
|
+
}
|
|
130
|
+
getActiveConfigPath() {
|
|
131
|
+
if (this.isUserOwnedInstall())
|
|
132
|
+
return this.getStandaloneConfigPath();
|
|
133
|
+
return this.getSystemConfigPath();
|
|
134
|
+
}
|
|
135
|
+
getActivePidPath() {
|
|
136
|
+
if (this.isUserOwnedInstall())
|
|
137
|
+
return this.getStandalonePidPath();
|
|
138
|
+
return this.getSystemStandalonePath();
|
|
139
|
+
}
|
|
140
|
+
// Where a config with this `standalone` flag belongs. A standalone config already in the
|
|
141
|
+
// system directory was written before the move and is rewritten in place: a second one in
|
|
142
|
+
// the user directory would leave two configs for one agent, and the one that lost would
|
|
143
|
+
// be the one uninstall still has to find.
|
|
144
|
+
getConfigPathForWrite(standalone) {
|
|
145
|
+
if (!standalone)
|
|
146
|
+
return this.getSystemConfigPath();
|
|
147
|
+
if (!(0, node_fs_1.existsSync)(this.getStandaloneConfigPath()) &&
|
|
148
|
+
(0, node_fs_1.existsSync)(this.getSystemConfigPath())) {
|
|
149
|
+
return this.getSystemConfigPath();
|
|
150
|
+
}
|
|
151
|
+
return this.getStandaloneConfigPath();
|
|
152
|
+
}
|
|
153
|
+
// The lock is read back by the cli command that stops the agent, which for an install in the
|
|
154
|
+
// system directory can be a different account than the one that wrote it - see
|
|
155
|
+
// applySystemModes for the rest of the modes in there.
|
|
156
|
+
getPidFileMode() {
|
|
157
|
+
return this.isUserOwnedInstall() ? 0o600 : 0o644;
|
|
158
|
+
}
|
|
46
159
|
getNewAgentConfigDir() {
|
|
160
|
+
if (this.isUserOwnedInstall())
|
|
161
|
+
return this.getStandaloneConfigDir();
|
|
47
162
|
return this.getSystemConfigDir();
|
|
48
163
|
}
|
|
49
164
|
getAgentConfigDir() {
|
|
@@ -61,9 +176,30 @@ class AgentSystem {
|
|
|
61
176
|
return Promise.resolve();
|
|
62
177
|
}
|
|
63
178
|
// extend
|
|
179
|
+
// The account the installed service runs as, read back from the service config - the only
|
|
180
|
+
// place it is recorded. An update needs it: it is the one path that re-applies the rights
|
|
181
|
+
// without being told a user, and handing the directory to root there would stop a service
|
|
182
|
+
// that was moved to someone else from writing in it.
|
|
183
|
+
//
|
|
184
|
+
// `''` is root, and `null` means this could not be established - a service config that is
|
|
185
|
+
// missing, unreadable or not in the shape this cli writes. The two must stay apart: treating
|
|
186
|
+
// 'do not know' as 'root' is what would take the directory off a service user we failed to
|
|
187
|
+
// recognise, and its agent would then crash-loop unable to write its own log.
|
|
188
|
+
readServiceUser() {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
// extend
|
|
64
192
|
hasAdminRights() {
|
|
65
193
|
return Promise.resolve(false);
|
|
66
194
|
}
|
|
195
|
+
// What the agent commands ask instead of hasAdminRights: managing an install that lives
|
|
196
|
+
// in the user's own directory takes no rights at all - there is no service to control and
|
|
197
|
+
// no system file to rewrite. Everything else still needs them.
|
|
198
|
+
async hasRightsToManage() {
|
|
199
|
+
if (this.isUserOwnedInstall())
|
|
200
|
+
return true;
|
|
201
|
+
return this.hasAdminRights();
|
|
202
|
+
}
|
|
67
203
|
// extend
|
|
68
204
|
isSupported() {
|
|
69
205
|
return Promise.resolve(false);
|
|
@@ -101,7 +237,7 @@ class AgentSystem {
|
|
|
101
237
|
}
|
|
102
238
|
fixBinaryRights() {
|
|
103
239
|
return new Promise((resolve, reject) => {
|
|
104
|
-
(0, node_fs_1.chmod)(this.getSystemBinaryPath(),
|
|
240
|
+
(0, node_fs_1.chmod)(this.getSystemBinaryPath(), 0o755, (err) => {
|
|
105
241
|
if (err)
|
|
106
242
|
reject(err);
|
|
107
243
|
else
|
|
@@ -110,11 +246,11 @@ class AgentSystem {
|
|
|
110
246
|
});
|
|
111
247
|
}
|
|
112
248
|
loadSystemConfig() {
|
|
113
|
-
const str = (0, node_fs_1.readFileSync)(this.
|
|
249
|
+
const str = (0, node_fs_1.readFileSync)(this.getActiveConfigPath(), 'utf-8');
|
|
114
250
|
return JSON.parse(str);
|
|
115
251
|
}
|
|
116
252
|
loadStandaloneProcConfig() {
|
|
117
|
-
return (0, node_fs_1.readFileSync)(this.
|
|
253
|
+
return (0, node_fs_1.readFileSync)(this.getActivePidPath(), 'utf-8');
|
|
118
254
|
}
|
|
119
255
|
loadNewAgentConfig() {
|
|
120
256
|
const exists = (0, node_fs_1.existsSync)(this.getAgentConfigPath());
|
|
@@ -122,7 +258,7 @@ class AgentSystem {
|
|
|
122
258
|
(0, node_fs_1.cpSync)(this.getAgentConfigPath(), this.getNewAgentConfigPath(), {
|
|
123
259
|
force: true,
|
|
124
260
|
});
|
|
125
|
-
(0, node_fs_1.chmodSync)(this.getNewAgentConfigPath(),
|
|
261
|
+
(0, node_fs_1.chmodSync)(this.getNewAgentConfigPath(), 0o600);
|
|
126
262
|
this.clearAgentFiles();
|
|
127
263
|
}
|
|
128
264
|
const str = (0, node_fs_1.readFileSync)(this.getNewAgentConfigPath(), 'utf-8');
|
|
@@ -136,21 +272,253 @@ class AgentSystem {
|
|
|
136
272
|
(0, node_fs_1.rmSync)(this.getSystemBinaryBackupPath(), { force: true });
|
|
137
273
|
(0, node_fs_1.rmSync)(this.getUpdateTempDir(), { recursive: true, force: true });
|
|
138
274
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
275
|
+
// `force` only silences ENOENT, so one path this process may not delete would otherwise take
|
|
276
|
+
// the rest of the list with it - and these lists span two directories with different owners.
|
|
277
|
+
tryRemove(p, recursive = false) {
|
|
278
|
+
try {
|
|
279
|
+
(0, node_fs_1.rmSync)(p, { force: true, recursive });
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
// An uninstall reports what it managed to do; a file it cannot delete is not a reason to
|
|
283
|
+
// leave the ones it can
|
|
284
|
+
logger_1.default.error(err);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// The two locations can hold two different agents - a service one in the system directory, an
|
|
288
|
+
// `--app` one in the home directory of whoever is running this - and root, or an elevated
|
|
289
|
+
// shell on windows, is able to delete either. So each of these clears only its own, host key
|
|
290
|
+
// included: nothing may go looking for that through getNewAgentConfigDir afterwards, because
|
|
291
|
+
// the config that resolution keys on is exactly what these have just removed.
|
|
292
|
+
clearStandaloneFiles() {
|
|
293
|
+
const dir = this.getStandaloneConfigDir();
|
|
294
|
+
this.tryRemove(this.getStandaloneConfigPath());
|
|
295
|
+
this.tryRemove(this.getStandalonePidPath());
|
|
296
|
+
this.tryRemove(path_1.default.join(dir, 'srv.json'));
|
|
297
|
+
}
|
|
298
|
+
// extend
|
|
299
|
+
clearServiceFiles() {
|
|
300
|
+
const dir = this.getSystemConfigDir();
|
|
301
|
+
this.tryRemove(this.getSystemBinaryPath());
|
|
302
|
+
this.tryRemove(this.getSystemConfigPath());
|
|
303
|
+
// The lock of a standalone install made before those files moved out of here
|
|
304
|
+
this.tryRemove(this.getSystemStandalonePath());
|
|
305
|
+
this.tryRemove(path_1.default.join(dir, 'srv.json'));
|
|
306
|
+
// Only a service install ever swaps its own binary - see isStandaloneInstall - so whatever
|
|
307
|
+
// is left of an update in here is always that install's
|
|
142
308
|
this.clearUpdateFiles();
|
|
143
309
|
}
|
|
310
|
+
// The install this cli resolved to, and only that one. Callers that already know which kind
|
|
311
|
+
// they are unmaking - an install rolling its own service back - say so directly instead.
|
|
312
|
+
clearSystemFiles() {
|
|
313
|
+
if (this.isUserOwnedInstall())
|
|
314
|
+
this.clearStandaloneFiles();
|
|
315
|
+
else
|
|
316
|
+
this.clearServiceFiles();
|
|
317
|
+
}
|
|
144
318
|
clearAgentFiles() {
|
|
145
319
|
(0, node_fs_1.rmSync)(this.getAgentConfigPath(), { force: true });
|
|
146
320
|
}
|
|
147
321
|
clearNewAgentFiles() {
|
|
148
322
|
(0, node_fs_1.rmSync)(this.getNewAgentConfigPath(), { force: true });
|
|
149
323
|
}
|
|
324
|
+
// Only ever sets the mode on a directory it creates. An install made before the modes were
|
|
325
|
+
// tightened has this at 0777 and its service may be running as a user who can only write in
|
|
326
|
+
// here *because* of that - taking it to 0755 from a command like `agent debug`, which knows
|
|
327
|
+
// nothing about that user, would break the running agent. Tightening an existing directory
|
|
328
|
+
// is applySystemModes' job, and that only runs where the owner is set in the same breath.
|
|
150
329
|
ensureSystemConfigDir() {
|
|
151
330
|
const p = this.getSystemConfigDir();
|
|
331
|
+
if ((0, node_fs_1.existsSync)(p))
|
|
332
|
+
return;
|
|
152
333
|
(0, node_fs_1.mkdirSync)(p, { recursive: true });
|
|
153
|
-
|
|
334
|
+
try {
|
|
335
|
+
(0, node_fs_1.chmodSync)(p, 0o755);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
// do nothing, the dir may be owned by the service user rather than by this process
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// fs.chown takes a numeric uid and node has no getpwnam, so the name has to be looked up.
|
|
342
|
+
// Not a shell call: `user` comes off the command line.
|
|
343
|
+
resolveUserIds(user) {
|
|
344
|
+
const { execFile } = require('node:child_process');
|
|
345
|
+
const read = (flag) => new Promise((resolve, reject) => {
|
|
346
|
+
execFile('id', [flag, user], (err, stdout) => {
|
|
347
|
+
const id = Number.parseInt(String(stdout).trim(), 10);
|
|
348
|
+
if (err || Number.isNaN(id)) {
|
|
349
|
+
// `id` failing is how the answer arrives, not something to show: what the caller
|
|
350
|
+
// needs to read is which user could not be found, not the command that looked
|
|
351
|
+
if (err)
|
|
352
|
+
logger_1.default.error(err);
|
|
353
|
+
reject(new Error((0, texts_1.ERR_AGENT_USER_UNKNOWN)(user)));
|
|
354
|
+
}
|
|
355
|
+
else
|
|
356
|
+
resolve(id);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
return Promise.all([read('-u'), read('-g')]).then(([uid, gid]) => ({
|
|
360
|
+
uid,
|
|
361
|
+
gid,
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
// Opens a path without ever following a symlink at the end of it, so the descriptor is bound
|
|
365
|
+
// to the inode this call meant: whatever the name is pointed at afterwards, an fchmod or
|
|
366
|
+
// fchown through this still lands on the file that was checked. O_NONBLOCK because a fifo
|
|
367
|
+
// left here would otherwise hold the open until someone writes to it, and an install that
|
|
368
|
+
// hangs is a worse answer than one that skips a file. Windows has neither flag, and no
|
|
369
|
+
// caller reaches it there.
|
|
370
|
+
openNoFollow(p, directory = false) {
|
|
371
|
+
let flags = node_fs_1.constants.O_RDONLY;
|
|
372
|
+
if (!(0, utils_1.isWindows)())
|
|
373
|
+
flags |= node_fs_1.constants.O_NOFOLLOW | node_fs_1.constants.O_NONBLOCK;
|
|
374
|
+
if (directory && node_fs_1.constants.O_DIRECTORY)
|
|
375
|
+
flags |= node_fs_1.constants.O_DIRECTORY;
|
|
376
|
+
return (0, node_fs_1.openSync)(p, flags);
|
|
377
|
+
}
|
|
378
|
+
// Hands the system directory to the account the service runs as, which is the other half of
|
|
379
|
+
// dropping 0777: everything in here is created by the root process that installs the agent,
|
|
380
|
+
// and the service still has to be able to write its host key, its log and a self-update.
|
|
381
|
+
// Re-run by changeUser, so moving the service to another user keeps working - and the
|
|
382
|
+
// default target is root, so moving it back takes the rights back off the old one.
|
|
383
|
+
//
|
|
384
|
+
// Always runs *before* applySystemModes, never after. Only one of the two can fail first, and
|
|
385
|
+
// the orders are not equally bad: ownership handed over and the mode left at the old 0777
|
|
386
|
+
// leaves everyone able to work, while a tightened mode over the wrong owner locks the service
|
|
387
|
+
// user out of a directory that was serving them a moment earlier.
|
|
388
|
+
async chownSystemDir(user) {
|
|
389
|
+
// Windows has no ownership to hand over this way: the service user is granted access to
|
|
390
|
+
// the same directory with icacls instead, see AgentWindows.grantAccess
|
|
391
|
+
if ((0, utils_1.isWindows)())
|
|
392
|
+
return;
|
|
393
|
+
const dir = this.getSystemConfigDir();
|
|
394
|
+
// Before anything is opened, because this can reject: an update takes the user out of the
|
|
395
|
+
// service config and that account may have been deleted since. A descriptor opened first
|
|
396
|
+
// would be left open on the way out.
|
|
397
|
+
const { uid, gid } = await this.resolveUserIds(user || 'root');
|
|
398
|
+
// Runs as root over a directory the service user writes to, so a symlink in here is
|
|
399
|
+
// something they could have put there: following one would hand root's ownership of any
|
|
400
|
+
// path on the machine to that account, /etc/passwd included. The open refusing is how that
|
|
401
|
+
// is found out - a check followed by a chown on the same name is two different lookups.
|
|
402
|
+
let fd;
|
|
403
|
+
try {
|
|
404
|
+
fd = this.openNoFollow(dir, true);
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
throw new Error((0, texts_1.ERR_AGENT_DIR_REPLACED)(dir));
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
// The directory itself is the one that has to land, so this is allowed to throw: without
|
|
411
|
+
// it the service cannot write, which is the failure the caller has to hear about
|
|
412
|
+
(0, node_fs_1.fchownSync)(fd, uid, gid);
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
(0, node_fs_1.closeSync)(fd);
|
|
416
|
+
}
|
|
417
|
+
// Direct children only. The one path component resolved here is the directory itself, and
|
|
418
|
+
// replacing that takes write access to /etc, which is root's alone - so lchown, which does
|
|
419
|
+
// not follow the last component, cannot be steered anywhere. A path below a subdirectory
|
|
420
|
+
// would resolve a component the service user does control: swapped for a symlink between
|
|
421
|
+
// this readdir and the call, it hands them root's ownership of whatever it points at. Node
|
|
422
|
+
// has no openat to walk down safely, and nothing under here needs one.
|
|
423
|
+
(0, node_fs_1.readdirSync)(dir, { withFileTypes: true }).forEach((entry) => {
|
|
424
|
+
const p = path_1.default.join(dir, entry.name);
|
|
425
|
+
if (entry.isDirectory()) {
|
|
426
|
+
// `update/` is the only one that ever turns up here, and swapBinaryToVersion removes
|
|
427
|
+
// and re-creates it as the service user before it writes anything into it
|
|
428
|
+
logger_1.default.error((0, texts_1.LOG_AGENT_RIGHTS_DIR_SKIPPED)(p));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
(0, node_fs_1.lchownSync)(p, uid, gid);
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
// Leaving one file behind is not worth undoing the handover for
|
|
436
|
+
logger_1.default.error(err);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
// The modes below are applied where each file is written, so that only covers what this
|
|
441
|
+
// version wrote. This re-applies them to a whole directory, which is how an install made by
|
|
442
|
+
// an older version gets off 0777/0666. Only ever called directly after chownSystemDir, and
|
|
443
|
+
// never before it: on their own the tighter modes would lock out a service user who does not
|
|
444
|
+
// own these yet.
|
|
445
|
+
applySystemModes() {
|
|
446
|
+
const modes = [
|
|
447
|
+
[this.getSystemConfigDir(), 0o755],
|
|
448
|
+
// Ordinary users still read this one: it is how `agent status` and the tunnel commands
|
|
449
|
+
// find the install without sudo. They just cannot write it any more.
|
|
450
|
+
[this.getSystemConfigPath(), 0o644],
|
|
451
|
+
[this.getSystemStandalonePath(), 0o644],
|
|
452
|
+
// The agent's ssh host key - nothing but the agent process ever reads it
|
|
453
|
+
[path_1.default.join(this.getSystemConfigDir(), 'srv.json'), 0o600],
|
|
454
|
+
[this.getUpdateStatePath(), 0o600],
|
|
455
|
+
[this.getSystemBinaryPath(), 0o755],
|
|
456
|
+
[this.getSystemBinaryBackupPath(), 0o755],
|
|
457
|
+
[path_1.default.join(this.getSystemConfigDir(), 'cli.log'), 0o640],
|
|
458
|
+
[path_1.default.join(this.getSystemConfigDir(), 'cli.1.log'), 0o640],
|
|
459
|
+
];
|
|
460
|
+
modes.forEach(([p, mode]) => {
|
|
461
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
462
|
+
return;
|
|
463
|
+
// Windows has no symlink to worry about here and no flag to refuse one with. It also has
|
|
464
|
+
// no caller: only the two posix classes apply the modes.
|
|
465
|
+
if ((0, utils_1.isWindows)()) {
|
|
466
|
+
try {
|
|
467
|
+
(0, node_fs_1.chmodSync)(p, mode);
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
logger_1.default.error(err);
|
|
471
|
+
}
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
// chmod follows symlinks and there is no portable lchmod, so a link left here by the
|
|
475
|
+
// service user - or by anyone, on a directory still at the old 0777 - would take this
|
|
476
|
+
// root process to a file of their choosing. Checking the name first does not help: the
|
|
477
|
+
// chmod is a second lookup, and the name can be a link by then. So the mode goes on the
|
|
478
|
+
// descriptor, and the open is what refuses a link in the first place.
|
|
479
|
+
let fd;
|
|
480
|
+
try {
|
|
481
|
+
fd = this.openNoFollow(p);
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
logger_1.default.error((0, texts_1.ERR_AGENT_DIR_REPLACED)(p));
|
|
485
|
+
logger_1.default.error(err);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
// Anything that is not a file or a directory was not put here by this cli, and its mode
|
|
490
|
+
// is not ours to set - a device node or a socket, on a directory still open to everyone
|
|
491
|
+
const st = (0, node_fs_1.fstatSync)(fd);
|
|
492
|
+
if (st.isFile() || st.isDirectory())
|
|
493
|
+
(0, node_fs_1.fchmodSync)(fd, mode);
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
// A mode we cannot set is not worth failing an install over
|
|
497
|
+
logger_1.default.error(err);
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
(0, node_fs_1.closeSync)(fd);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
ensureStandaloneConfigDir() {
|
|
505
|
+
const p = this.getStandaloneConfigDir();
|
|
506
|
+
(0, node_fs_1.mkdirSync)(p, { recursive: true });
|
|
507
|
+
try {
|
|
508
|
+
(0, node_fs_1.chmodSync)(p, 0o700);
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
// do nothing, a dir left by another user is still theirs to keep
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// The directory the install this cli acts on writes to. Called where the old code called
|
|
515
|
+
// ensureSystemConfigDir: for a standalone install in the user directory that one would
|
|
516
|
+
// both fail without root and be the wrong place.
|
|
517
|
+
ensureActiveConfigDir() {
|
|
518
|
+
if (this.isUserOwnedInstall())
|
|
519
|
+
this.ensureStandaloneConfigDir();
|
|
520
|
+
else
|
|
521
|
+
this.ensureSystemConfigDir();
|
|
154
522
|
}
|
|
155
523
|
saveFile(p, c) {
|
|
156
524
|
return new Promise((resolve, reject) => {
|
|
@@ -158,6 +526,10 @@ class AgentSystem {
|
|
|
158
526
|
if (err)
|
|
159
527
|
reject(err);
|
|
160
528
|
else {
|
|
529
|
+
// The service config, and it carries the token in its arguments. Left at 0644
|
|
530
|
+
// deliberately: launchd and systemd have their own expectations of these files, and
|
|
531
|
+
// the token is readable off `ps` by every local user anyway - the fix for that is
|
|
532
|
+
// getting it out of the command line, not tightening this mode.
|
|
161
533
|
(0, node_fs_1.chmod)(p, 0o644, (err) => {
|
|
162
534
|
if (err)
|
|
163
535
|
reject(err);
|
|
@@ -190,7 +562,7 @@ class AgentSystem {
|
|
|
190
562
|
if (err)
|
|
191
563
|
reject(err);
|
|
192
564
|
else {
|
|
193
|
-
(0, node_fs_1.chmod)(dest,
|
|
565
|
+
(0, node_fs_1.chmod)(dest, 0o600, (err) => {
|
|
194
566
|
if (err)
|
|
195
567
|
reject(err);
|
|
196
568
|
else
|
|
@@ -284,7 +656,7 @@ class AgentSystem {
|
|
|
284
656
|
encoding: 'utf-8',
|
|
285
657
|
});
|
|
286
658
|
try {
|
|
287
|
-
(0, node_fs_1.chmodSync)(this.getUpdateStatePath(),
|
|
659
|
+
(0, node_fs_1.chmodSync)(this.getUpdateStatePath(), 0o600);
|
|
288
660
|
}
|
|
289
661
|
catch {
|
|
290
662
|
// do nothing
|
|
@@ -400,7 +772,7 @@ class AgentSystem {
|
|
|
400
772
|
if (!(0, node_fs_1.existsSync)(staged)) {
|
|
401
773
|
throw new Error(`Archive did not contain '${path_1.default.basename(target)}'`);
|
|
402
774
|
}
|
|
403
|
-
(0, node_fs_1.chmodSync)(staged,
|
|
775
|
+
(0, node_fs_1.chmodSync)(staged, 0o755);
|
|
404
776
|
const runs = await this.verifyBinary(staged, version);
|
|
405
777
|
if (!runs)
|
|
406
778
|
return false;
|
|
@@ -505,8 +877,8 @@ class AgentSystem {
|
|
|
505
877
|
clearStandaloneProcConfig() {
|
|
506
878
|
try {
|
|
507
879
|
logger_1.default.info(texts_1.LOG_REMOVING_AGENT_PROC_ID);
|
|
508
|
-
this.
|
|
509
|
-
(0, node_fs_1.rmSync)(this.
|
|
880
|
+
this.ensureActiveConfigDir();
|
|
881
|
+
(0, node_fs_1.rmSync)(this.getActivePidPath(), {
|
|
510
882
|
force: true,
|
|
511
883
|
});
|
|
512
884
|
return true;
|
|
@@ -520,12 +892,12 @@ class AgentSystem {
|
|
|
520
892
|
saveStandaloneProcConfig() {
|
|
521
893
|
try {
|
|
522
894
|
logger_1.default.info(texts_1.LOG_SAVING_AGENT_PROC_ID);
|
|
523
|
-
this.
|
|
524
|
-
const p = this.
|
|
895
|
+
this.ensureActiveConfigDir();
|
|
896
|
+
const p = this.getActivePidPath();
|
|
525
897
|
(0, node_fs_1.writeFileSync)(p, String(process.pid), {
|
|
526
898
|
encoding: 'utf-8',
|
|
527
899
|
});
|
|
528
|
-
(0, node_fs_1.chmodSync)(p,
|
|
900
|
+
(0, node_fs_1.chmodSync)(p, this.getPidFileMode());
|
|
529
901
|
return true;
|
|
530
902
|
}
|
|
531
903
|
catch (err) {
|
|
@@ -546,8 +918,13 @@ class AgentSystem {
|
|
|
546
918
|
saveSystemConfig(id, host, token, port, standalone, debug) {
|
|
547
919
|
try {
|
|
548
920
|
logger_1.default.info(texts_1.LOG_SAVING_AGENT_SYSTEM_CONFIG);
|
|
549
|
-
this.
|
|
550
|
-
|
|
921
|
+
const p = this.getConfigPathForWrite(standalone);
|
|
922
|
+
const inUserDir = p === this.getStandaloneConfigPath();
|
|
923
|
+
if (inUserDir)
|
|
924
|
+
this.ensureStandaloneConfigDir();
|
|
925
|
+
else
|
|
926
|
+
this.ensureSystemConfigDir();
|
|
927
|
+
(0, node_fs_1.writeFileSync)(p, JSON.stringify({
|
|
551
928
|
id,
|
|
552
929
|
host,
|
|
553
930
|
token,
|
|
@@ -557,7 +934,9 @@ class AgentSystem {
|
|
|
557
934
|
}), {
|
|
558
935
|
encoding: 'utf-8',
|
|
559
936
|
});
|
|
560
|
-
|
|
937
|
+
// Taken from where the file actually landed rather than from getActiveFileMode: this
|
|
938
|
+
// is the one write that can create the file the resolution keys on
|
|
939
|
+
(0, node_fs_1.chmodSync)(p, inUserDir ? 0o600 : 0o644);
|
|
561
940
|
return true;
|
|
562
941
|
}
|
|
563
942
|
catch (err) {
|
|
@@ -581,7 +960,7 @@ class AgentSystem {
|
|
|
581
960
|
encoding: 'utf-8',
|
|
582
961
|
});
|
|
583
962
|
try {
|
|
584
|
-
(0, node_fs_1.chmodSync)(this.getNewAgentConfigPath(),
|
|
963
|
+
(0, node_fs_1.chmodSync)(this.getNewAgentConfigPath(), 0o600);
|
|
585
964
|
}
|
|
586
965
|
catch {
|
|
587
966
|
// do nothing, file can already have proper mode
|
|
@@ -29,9 +29,6 @@ class AgentWindows extends system_1.default {
|
|
|
29
29
|
getSystemConfigPath() {
|
|
30
30
|
return path_1.default.join(this.getSystemConfigDir(), 'agent.json');
|
|
31
31
|
}
|
|
32
|
-
getSystemStandalonePath() {
|
|
33
|
-
return path_1.default.join(this.getSystemConfigDir(), 'pid.lock');
|
|
34
|
-
}
|
|
35
32
|
getNssmArchivePath() {
|
|
36
33
|
return path_1.default.join(this.getSystemConfigDir(), 'nssm.zip');
|
|
37
34
|
}
|
|
@@ -53,10 +50,10 @@ class AgentWindows extends system_1.default {
|
|
|
53
50
|
getSystemBinaryPath() {
|
|
54
51
|
return path_1.default.join(this.getSystemConfigDir(), 'bdy.exe');
|
|
55
52
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
// The service half only: nssm is what runs the service, so an `--app` uninstall has no
|
|
54
|
+
// business removing it
|
|
55
|
+
clearServiceFiles() {
|
|
56
|
+
super.clearServiceFiles();
|
|
60
57
|
(0, fs_1.rmSync)(path_1.default.join(this.getSystemConfigDir(), 'nssm'), {
|
|
61
58
|
force: true,
|
|
62
59
|
recursive: true,
|
|
@@ -11,8 +11,8 @@ commandAgentDebug.argument('<on/off>');
|
|
|
11
11
|
commandAgentDebug.action(async (debug) => {
|
|
12
12
|
const AgentManager = require('../../agent/manager').default;
|
|
13
13
|
const ApiAgent = require('../../tunnel/api/agent').default;
|
|
14
|
-
const
|
|
15
|
-
if (!
|
|
14
|
+
const hasRights = await AgentManager.system.hasRightsToManage();
|
|
15
|
+
if (!hasRights) {
|
|
16
16
|
output_1.default.exitError(texts_1.ERR_AGENT_ADMIN_RIGHTS);
|
|
17
17
|
}
|
|
18
18
|
if (!commandAgentDebug.agentInstalled) {
|
|
@@ -9,6 +9,7 @@ const texts_1 = require("../../texts");
|
|
|
9
9
|
const cfg_1 = __importDefault(require("../../tunnel/cfg"));
|
|
10
10
|
const utils_1 = require("../../utils");
|
|
11
11
|
const logger_1 = __importDefault(require("../../logger"));
|
|
12
|
+
const node_net_1 = require("node:net");
|
|
12
13
|
const removeAllAndExit = async (txt, id, host, token) => {
|
|
13
14
|
const AgentManager = require('../../agent/manager').default;
|
|
14
15
|
const ApiBuddy = require('../../tunnel/api/buddy').default;
|
|
@@ -25,7 +26,9 @@ const removeAllAndExit = async (txt, id, host, token) => {
|
|
|
25
26
|
// do nothing
|
|
26
27
|
}
|
|
27
28
|
try {
|
|
28
|
-
|
|
29
|
+
// Named rather than resolved: this only ever unmakes the service install started above, and
|
|
30
|
+
// the account running it can have an `--app` install of its own that a resolver would pick
|
|
31
|
+
AgentManager.system.clearServiceFiles();
|
|
29
32
|
}
|
|
30
33
|
catch {
|
|
31
34
|
// do nothing
|
|
@@ -36,14 +39,19 @@ const removeAllAndExit = async (txt, id, host, token) => {
|
|
|
36
39
|
catch {
|
|
37
40
|
// do nothing
|
|
38
41
|
}
|
|
39
|
-
try {
|
|
40
|
-
AgentManager.system.clearNewAgentFiles();
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// do nothing
|
|
44
|
-
}
|
|
45
42
|
output_1.default.exitError(txt);
|
|
46
43
|
};
|
|
44
|
+
// A standalone install in another user's directory cannot be seen from here, so the config
|
|
45
|
+
// check below no longer proves the machine is free - see agent/system.ts isUserOwnedInstall.
|
|
46
|
+
// The port is what two agents actually compete for, and this binds it the way the agent does
|
|
47
|
+
// (manager.ts listens without a host), so the answer is the one that will matter on start.
|
|
48
|
+
const isPortFree = (port) => new Promise((resolve) => {
|
|
49
|
+
const srv = (0, node_net_1.createServer)();
|
|
50
|
+
srv.once('error', () => resolve(false));
|
|
51
|
+
srv.listen(port, () => {
|
|
52
|
+
srv.close(() => resolve(true));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
47
55
|
const prepareTags = (tags) => {
|
|
48
56
|
const validTags = [];
|
|
49
57
|
if (tags) {
|
|
@@ -235,14 +243,33 @@ commandAgentInstall.option('--agent-note <note>', texts_1.OPT_COMMAND_AGENT_NOTE
|
|
|
235
243
|
commandAgentInstall.action(async (options) => {
|
|
236
244
|
const AgentManager = require('../../agent/manager').default;
|
|
237
245
|
options.start = options.start === 'true' || options.start === true;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
246
|
+
// `--app` installs no service: the config goes to this user's own directory and the agent
|
|
247
|
+
// runs as this user, so there is nothing left in the mode that needs root. A service
|
|
248
|
+
// install writes to LaunchDaemons/systemd/ProgramData and still does.
|
|
249
|
+
if (!options.app) {
|
|
250
|
+
const hasAdminRights = await AgentManager.system.hasAdminRights();
|
|
251
|
+
if (!hasAdminRights) {
|
|
252
|
+
output_1.default.exitError(texts_1.ERR_AGENT_ADMIN_RIGHTS);
|
|
253
|
+
}
|
|
241
254
|
}
|
|
242
255
|
const hasSystemConfig = AgentManager.system.hasSystemConfig();
|
|
243
256
|
if (hasSystemConfig) {
|
|
244
257
|
output_1.default.exitError(texts_1.TXT_AGENT_ALREADY_INSTALLED);
|
|
245
258
|
}
|
|
259
|
+
// One agent per machine. The check above only sees the system config and this account's own
|
|
260
|
+
// standalone one - an `--app` install in another home is invisible from there, and a service
|
|
261
|
+
// installed on top of it would fight it for the port and the agent id.
|
|
262
|
+
if (!options.app) {
|
|
263
|
+
const apps = AgentManager.system.findAppInstalls();
|
|
264
|
+
if (apps.length) {
|
|
265
|
+
output_1.default.exitError((0, texts_1.ERR_AGENT_APP_INSTALL_PRESENT)(apps[0]));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const port = input_1.default.port(options.port);
|
|
269
|
+
const free = await isPortFree(port);
|
|
270
|
+
if (!free) {
|
|
271
|
+
output_1.default.exitError((0, texts_1.ERR_AGENT_PORT_TAKEN)(port));
|
|
272
|
+
}
|
|
246
273
|
if (options.app) {
|
|
247
274
|
await installApp(options);
|
|
248
275
|
}
|