fleetctl 4.92.3 → 4.93.1

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 (2) hide show
  1. package/package.json +2 -2
  2. package/run.js +62 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetctl",
3
- "version": "v4.92.3",
3
+ "version": "v4.93.1",
4
4
  "description": "Installer for the fleetctl CLI tool",
5
5
  "bin": {
6
6
  "fleetctl": "./run.js"
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "homepage": "https://fleetdm.com",
18
18
  "dependencies": {
19
- "axios": "1.18.0",
19
+ "axios": "1.20.0",
20
20
  "rimraf": "6.1.2",
21
21
  "tar": "7.5.22"
22
22
  },
package/run.js CHANGED
@@ -2,16 +2,29 @@
2
2
  /* eslint-disable @typescript-eslint/no-var-requires */
3
3
 
4
4
  const { spawnSync } = require("child_process");
5
- const { existsSync, mkdirSync } = require("fs");
6
- const { type } = require("os");
5
+ const {
6
+ chmodSync,
7
+ existsSync,
8
+ lstatSync,
9
+ mkdirSync,
10
+ mkdtempSync,
11
+ renameSync,
12
+ rmSync,
13
+ } = require("fs");
14
+ const { constants: osConstants, type } = require("os");
7
15
  const { join } = require("path");
8
16
  const { arch } = require("process");
17
+ const { pipeline } = require("stream/promises");
9
18
 
10
19
  const axios = require("axios");
11
20
  const { rimrafSync } = require("rimraf");
12
21
  const { extract } = require("tar");
13
22
  const { version } = require("./package.json");
14
23
 
24
+ // Fail closed: if anything below leaves a promise pending (e.g. a download stream that stops
25
+ // without an error event), the event loop drains and Node would otherwise exit 0 silently.
26
+ process.exitCode = 1;
27
+
15
28
  // Strip any v4.0.0-1 style suffix (but not -rc1) so that the correct package is
16
29
  // downloaded if there is a mistake in the NPM publish and we need to release a
17
30
  // -1, etc. (because NPM packages are immutable and can't be fixed after a mistake).
@@ -34,36 +47,52 @@ const platform = (() => {
34
47
  case "Darwin":
35
48
  return "macos";
36
49
  default:
37
- throw new Error(`platform ${type} unrecognized`);
50
+ throw new Error(`platform ${type()} unrecognized`);
38
51
  }
39
52
  })();
40
53
 
41
- const binName = platform === "windows" ? "fleetctl.exe" : "fleetctl";
54
+ const binName = platform.startsWith("windows") ? "fleetctl.exe" : "fleetctl";
42
55
  const binPath = join(installDir, binName);
43
56
 
44
57
  const install = async () => {
45
58
  const url = `https://github.com/fleetdm/fleet/releases/download/fleet-${strippedVersion}/fleetctl_${strippedVersion}_${platform}.tar.gz`;
46
59
 
47
- mkdirSync(installDir, { recursive: true });
60
+ mkdirSync(binDir, { recursive: true });
61
+ // Extract into a temporary directory and rename it into place, so an interrupted
62
+ // download never leaves a partial binary where the next run would find it.
63
+ const tmpDir = mkdtempSync(join(binDir, "tmp-"));
48
64
 
49
65
  try {
50
- const response = await axios({ url, responseType: "stream" });
66
+ let response;
67
+ try {
68
+ response = await axios({ url, responseType: "stream" });
69
+ } catch (err) {
70
+ if (axios.isAxiosError(err)) {
71
+ throw new Error(`download archive ${url}: ${err.message}`);
72
+ }
73
+ throw err;
74
+ }
51
75
 
76
+ // Unlike stream.pipe(), pipeline() rejects on errors and premature close from either side.
52
77
  // Strip the outer directory when extracting. Just get the binary.
53
- const tarWriter = extract({ strip: 1, cwd: installDir });
54
- response.data.pipe(tarWriter);
55
-
56
- // Need to return a promise with the writer to ensure we can await for it to complete.
57
- return new Promise((resolve, reject) => {
58
- tarWriter.on("finish", resolve);
59
- tarWriter.on("error", reject);
60
- });
61
- } catch (err) {
62
- if (axios.isAxiosError(err)) {
63
- throw new Error(`download archive ${url}: ${err.message}`);
64
- } else {
65
- throw err;
78
+ await pipeline(response.data, extract({ strip: 1, cwd: tmpDir }));
79
+
80
+ // Validate before caching: a bad entry here would otherwise be reused on every later run.
81
+ const extracted = join(tmpDir, binName);
82
+ const stat = existsSync(extracted) ? lstatSync(extracted) : null;
83
+ if (!stat || !stat.isFile()) {
84
+ throw new Error(`archive ${url} does not contain a ${binName} file`);
85
+ }
86
+ if (!platform.startsWith("windows") && !(stat.mode & 0o111)) {
87
+ throw new Error(`${binName} in archive ${url} is not executable`);
66
88
  }
89
+ // mkdtempSync creates the directory as 0700; keep it traversable for other users when
90
+ // installed as root (sudo npm install -g fleetctl), like the plain mkdirSync it replaced.
91
+ chmodSync(tmpDir, 0o755);
92
+ renameSync(tmpDir, installDir);
93
+ } catch (err) {
94
+ rmSync(tmpDir, { recursive: true, force: true });
95
+ throw err;
67
96
  }
68
97
  };
69
98
 
@@ -110,14 +139,26 @@ const run = async () => {
110
139
 
111
140
  const [, , ...args] = process.argv;
112
141
  const options = { cwd: process.cwd(), stdio: "inherit" };
113
- const { status, error } = spawnSync(binPath, args, options);
142
+ const { status, signal, error } = spawnSync(binPath, args, options);
114
143
 
115
144
  if (error) {
116
145
  console.error(error);
117
146
  process.exit(1);
118
147
  }
148
+ if (status === null) {
149
+ // Killed by a signal: status is null and process.exit(null) would report success.
150
+ // Mirror the shell convention (128 + signal number) and stay quiet on SIGPIPE, which is
151
+ // routine when the output is piped to a command that exits early (e.g. `| head`).
152
+ if (signal !== "SIGPIPE") {
153
+ console.error(`Error: fleetctl was terminated by signal ${signal}`);
154
+ }
155
+ process.exit(128 + (osConstants.signals[signal] || 0));
156
+ }
119
157
 
120
158
  process.exit(status);
121
159
  };
122
160
 
123
- run();
161
+ run().catch((err) => {
162
+ console.error(`Error: ${err.message}`);
163
+ process.exit(1);
164
+ });