Skip to content

Commit fd6f90e

Browse files
committed
ci(e2e): select OpenShell 0.0.106
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
1 parent 9b8d25b commit fd6f90e

10 files changed

Lines changed: 313 additions & 83 deletions

.github/workflows/e2e.yaml

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,7 +1733,8 @@ jobs:
17331733
E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-auth-contract
17341734
NEMOCLAW_RUN_LIVE_E2E: "1"
17351735
NEMOCLAW_NON_INTERACTIVE: "1"
1736-
NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.101"
1736+
NEMOCLAW_CANDIDATE_VERSION: "0.0.106"
1737+
NEMOCLAW_OPENSHELL_PIN_VERSION: "0.0.106"
17371738
DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c"
17381739
steps:
17391740
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -1755,7 +1756,7 @@ jobs:
17551756
provenance-json: ${{ needs.generate-matrix.outputs.cli_artifact_provenance }}
17561757

17571758
- name: Install OpenShell CLI
1758-
run: env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash scripts/install-openshell.sh
1759+
run: env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN -u NVIDIA_API_KEY -u NVIDIA_INFERENCE_API_KEY -u GITHUB_TOKEN bash tools/e2e/install-openshell-v00106-qualification.sh
17591760

17601761
- name: Pre-pull pinned gateway auth probe image
17611762
run: docker pull "$DOCKER_GRPC_PROBE_IMAGE"
@@ -1818,7 +1819,7 @@ jobs:
18181819
NEMOCLAW_OPENSHELL_CHANNEL: stable
18191820
NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1"
18201821
NEMOCLAW_RUN_LIVE_E2E: "1"
1821-
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:b58be5e40c788977ffa0e8305a8cad9c656efdf1a3fe182582a00ca870bb0edb
1822+
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:722f44669722961b7f432b0b81de25b91a58f34a61d6403bef967acaf2b3af01
18221823
steps:
18231824
- id: trusted_hermes_swap
18241825
name: Provision trusted Hermes E2E swap
@@ -2061,22 +2062,20 @@ jobs:
20612062
run: bash test/e2e/setup-mcp-test-tls.sh
20622063

20632064
- name: Install OpenShell CLI
2064-
env:
2065-
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
20662065
run: |
20672066
set -euo pipefail
2068-
bash scripts/install-openshell.sh
2067+
bash tools/e2e/install-openshell-v00106-qualification.sh
20692068
if [[ "$NEMOCLAW_MCP_BRIDGE_AGENT" == "deepagents" ]]; then
20702069
mkdir -p "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents"
20712070
jq -n '{
20722071
schemaVersion: 1,
20732072
sourceRepository: "NVIDIA/OpenShell",
2074-
releaseTag: "v0.0.101",
2075-
sourceSha: "8ddd98c3dff62619a3963f99ba1e055b67650e72",
2073+
releaseTag: "v0.0.106",
2074+
sourceSha: "c4b500a7de64d0b66e3ee8098f58d14299092162",
20762075
artifacts: {
2077-
cli: {binarySha256: "1ad48efd5e1de8f3f017a81b3a7177872f350343a1a8d8074c7e844bca4801e9"},
2078-
gateway: {binarySha256: "a6a5d754605a2144b148637b85a09291d2eeb77e08a4ee34b83685c6920448f5"},
2079-
standaloneSandbox: {binarySha256: "a2704babbb468fd0a359bfdd9844de71095b730758541b4ca8cbab77d4018920"}
2076+
cli: {binarySha256: "98ecf95113fea999e94a928043e57b04cf58a45a1b66ae8bffc73d1bc8bb1d59"},
2077+
gateway: {binarySha256: "e6cde8a54568aa1926ff6584ffd6984314c68dad64d2722509618a74094c622c"},
2078+
standaloneSandbox: {binarySha256: "019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8"}
20802079
}
20812080
}' > "$E2E_ARTIFACT_DIR/mcp-bridge-deepagents/openshell-exact-main-provenance.json"
20822081
fi
@@ -2140,7 +2139,7 @@ jobs:
21402139
NEMOCLAW_OPENSHELL_CHANNEL: stable
21412140
NEMOCLAW_OPENSHELL_EXACT_MAIN_PROOF: "1"
21422141
NEMOCLAW_RUN_LIVE_E2E: "1"
2143-
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:b58be5e40c788977ffa0e8305a8cad9c656efdf1a3fe182582a00ca870bb0edb
2142+
OPENSHELL_DOCKER_SUPERVISOR_IMAGE: ghcr.io/nvidia/openshell/supervisor@sha256:722f44669722961b7f432b0b81de25b91a58f34a61d6403bef967acaf2b3af01
21442143
steps:
21452144
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21462145
with:
@@ -2183,21 +2182,19 @@ jobs:
21832182
run: bash test/e2e/setup-mcp-test-tls.sh
21842183

21852184
- name: Install OpenShell CLI
2186-
env:
2187-
NEMOCLAW_OPENSHELL_FORCE_INSTALL: "1"
21882185
run: |
21892186
set -euo pipefail
2190-
bash scripts/install-openshell.sh
2187+
bash tools/e2e/install-openshell-v00106-qualification.sh
21912188
mkdir -p "$E2E_ARTIFACT_DIR/openshell-credential-generation-window"
21922189
jq -n '{
21932190
schemaVersion: 1,
21942191
sourceRepository: "NVIDIA/OpenShell",
2195-
releaseTag: "v0.0.101",
2196-
sourceSha: "8ddd98c3dff62619a3963f99ba1e055b67650e72",
2192+
releaseTag: "v0.0.106",
2193+
sourceSha: "c4b500a7de64d0b66e3ee8098f58d14299092162",
21972194
artifacts: {
2198-
cli: {binarySha256: "1ad48efd5e1de8f3f017a81b3a7177872f350343a1a8d8074c7e844bca4801e9"},
2199-
gateway: {binarySha256: "a6a5d754605a2144b148637b85a09291d2eeb77e08a4ee34b83685c6920448f5"},
2200-
standaloneSandbox: {binarySha256: "a2704babbb468fd0a359bfdd9844de71095b730758541b4ca8cbab77d4018920"}
2195+
cli: {binarySha256: "98ecf95113fea999e94a928043e57b04cf58a45a1b66ae8bffc73d1bc8bb1d59"},
2196+
gateway: {binarySha256: "e6cde8a54568aa1926ff6584ffd6984314c68dad64d2722509618a74094c622c"},
2197+
standaloneSandbox: {binarySha256: "019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8"}
22012198
}
22022199
}' > "$E2E_ARTIFACT_DIR/openshell-credential-generation-window/openshell-exact-main-provenance.json"
22032200

scripts/checks/dependency-pins.mts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ const OPENSHELL_RELEASE_MANIFESTS = [
3737
"openshell-sandbox-checksums-sha256.txt",
3838
] as const;
3939

40+
function expectedOpenShellE2eVersion(pins: OpenShellPins): string {
41+
// Qualify the accepted 0.0.106 migration against the exact trusted release
42+
// before the supported product pin moves from 0.0.101.
43+
return pins.minVersion === "0.0.101" && pins.maxVersion === "0.0.101"
44+
? "0.0.106"
45+
: pins.maxVersion;
46+
}
47+
4048
function isRecord(value: unknown): value is Record<string, unknown> {
4149
return typeof value === "object" && value !== null && !Array.isArray(value);
4250
}
@@ -395,7 +403,7 @@ function verifyOpenShellPins(
395403
".github/workflows/e2e.yaml gateway auth OpenShell version",
396404
failures,
397405
),
398-
pins.maxVersion,
406+
expectedOpenShellE2eVersion(pins),
399407
".github/workflows/e2e.yaml gateway auth OpenShell version",
400408
failures,
401409
);

test/dependency-pins-check.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,36 @@ describe("dependency pin drift check", () => {
206206
);
207207
});
208208

209+
it("accepts OpenShell 0.0.106 E2E qualification before the 0.0.101 product pin moves", () => {
210+
withFixture(
211+
"nemoclaw-dependency-pins-openshell-qualification-",
212+
{
213+
openshellMax: "0.0.101",
214+
openshellMin: "0.0.101",
215+
workflowPinVersion: "0.0.106",
216+
},
217+
(root) => expect(verifyDependencyPins(root)).toEqual([]),
218+
);
219+
});
220+
221+
it.each(["0.0.105", "0.0.107"])(
222+
"rejects OpenShell %s as the E2E target while the product pin is 0.0.101",
223+
(workflowPinVersion) => {
224+
withFixture(
225+
"nemoclaw-dependency-pins-openshell-unapproved-qualification-",
226+
{
227+
openshellMax: "0.0.101",
228+
openshellMin: "0.0.101",
229+
workflowPinVersion,
230+
},
231+
(root) =>
232+
expect(verifyDependencyPins(root)).toContain(
233+
`.github/workflows/e2e.yaml gateway auth OpenShell version: expected 0.0.106, found ${workflowPinVersion}`,
234+
),
235+
);
236+
},
237+
);
238+
209239
it("reports exact operational consumer drift (#5242)", () => {
210240
withFixture(
211241
"nemoclaw-dependency-pins-drift-",

test/e2e/support/e2e-workflow.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,22 @@ describe("e2e workflow boundary", () => {
457457
).toEqual(["snapshot-commands"]);
458458
});
459459

460+
it("routes the OpenShell 0.0.106 qualification installer to all five consumers", () => {
461+
const plan = buildE2eWorkflowPlan(
462+
{},
463+
{ changedFiles: ["tools/e2e/install-openshell-v00106-qualification.sh"] },
464+
);
465+
466+
expect([...plan.selectedJobs].sort()).toEqual(
467+
[
468+
"jetson-nvmap-gpu",
469+
"mcp-bridge",
470+
"openshell-credential-generation-window",
471+
"openshell-gateway-auth-contract",
472+
].sort(),
473+
);
474+
});
475+
460476
it("keeps orchestration jobs within bounded timeouts", () => {
461477
const workflow = readWorkflow() as {
462478
jobs: Record<string, { "timeout-minutes"?: number }>;

test/e2e/support/mcp-workflow-boundary.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,40 @@ import { validateMcpOpenShellWorkflowBoundary } from "../../../tools/e2e/mcp-wor
1212
import { requireFixture } from "./require-fixture";
1313

1414
describe("MCP workflow artifact boundary", () => {
15+
it.each([
16+
{
17+
expected:
18+
"mcp-bridge must run only the exact OpenShell 0.0.106 qualification install and provenance step",
19+
jobName: "mcp-bridge",
20+
},
21+
{
22+
expected:
23+
"openshell-credential-generation-window must run only the exact OpenShell 0.0.106 qualification install and provenance step",
24+
jobName: "openshell-credential-generation-window",
25+
},
26+
])("rejects a product reinstall after the exact $jobName qualification install", ({
27+
expected,
28+
jobName,
29+
}) => {
30+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-mcp-workflow-"));
31+
const workflowPath = path.join(directory, "e2e.yaml");
32+
try {
33+
const workflow = YAML.parse(fs.readFileSync(".github/workflows/e2e.yaml", "utf8")) as {
34+
jobs: Record<string, { steps: Array<{ name?: string; run?: string }> }>;
35+
};
36+
const install = workflow.jobs[jobName].steps.find(
37+
(step) => step.name === "Install OpenShell CLI",
38+
);
39+
requireFixture(install?.run, `${jobName} qualification installer fixture is missing`);
40+
install.run += "bash scripts/install-openshell.sh\n";
41+
fs.writeFileSync(workflowPath, YAML.stringify(workflow));
42+
43+
expect(validateMcpOpenShellWorkflowBoundary(workflowPath)).toContain(expected);
44+
} finally {
45+
fs.rmSync(directory, { force: true, recursive: true });
46+
}
47+
});
48+
1549
it.each([
1650
"mcp-bridge",
1751
"mcp-bridge-dev",

test/e2e/support/openshell-gateway-auth-contract-workflow-boundary.test.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { execFileSync } from "node:child_process";
5+
import { readFileSync } from "node:fs";
6+
47
import { describe, expect, it } from "vitest";
58

69
import {
@@ -9,7 +12,38 @@ import {
912
validateOpenShellGatewayAuthContractWorkflowBoundary,
1013
} from "../../../tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts";
1114

15+
const QUALIFICATION_INSTALLER_IDENTITIES = [
16+
"openshell-x86_64-unknown-linux-musl.tar.gz",
17+
"d1a885a91b3e5aaa006c36aca95dc78bed0638c1ba1a79b55f1da93211b8a0a0",
18+
"98ecf95113fea999e94a928043e57b04cf58a45a1b66ae8bffc73d1bc8bb1d59",
19+
"openshell-gateway-x86_64-unknown-linux-gnu.tar.gz",
20+
"b7760cb752a4363c2f21d32298dd0c683dc438f6edfd16c2e4242bc0baefbb7c",
21+
"e6cde8a54568aa1926ff6584ffd6984314c68dad64d2722509618a74094c622c",
22+
"openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz",
23+
"559b8aaad3a8eeab45c511e7de531d9baa98a311282dcb0c2c5f38cc2d4ca355",
24+
"019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8",
25+
] as const;
26+
27+
function expectSingleInstallerIdentities(source: string): void {
28+
for (const identity of QUALIFICATION_INSTALLER_IDENTITIES) {
29+
expect(source.match(new RegExp(identity.replaceAll(".", "\\."), "gu"))).toHaveLength(1);
30+
}
31+
}
32+
1233
describe("OpenShell gateway auth contract workflow boundary", () => {
34+
it("binds the qualification installer to one coherent OpenShell 0.0.106 release", () => {
35+
const installerPath = "tools/e2e/install-openshell-v00106-qualification.sh";
36+
const source = readFileSync(installerPath, "utf8");
37+
38+
expect(source).toContain('readonly release_tag="v0.0.106"');
39+
expect(source).not.toMatch(/v0\.0\.(?:101|105|107)/u);
40+
expectSingleInstallerIdentities(source);
41+
expect(source).toContain('printf \'%s %s\\n\' "${archive_sha256[$index]}" "$archive" | sha256sum -c -');
42+
expect(source).toContain('printf \'%s %s\\n\' "${binary_sha256[$index]}" "${extracted}/${member}" | sha256sum -c -');
43+
expect(source).toContain('printf \'%s %s\\n\' "${binary_sha256[$index]}" "${target_dir}/${member}" | sha256sum -c -');
44+
execFileSync("bash", ["-n", installerPath]);
45+
});
46+
1347
it("accepts the checked-in workflow and rejects protected trust-boundary mutations", () => {
1448
expect(validateOpenShellGatewayAuthContractWorkflowBoundary()).toEqual([]);
1549

@@ -22,6 +56,7 @@ describe("OpenShell gateway auth contract workflow boundary", () => {
2256
...job.env,
2357
DOCKER_GRPC_PROBE_IMAGE: "node:22-trixie-slim",
2458
E2E_ARTIFACT_DIR: "/tmp/gateway-auth",
59+
NEMOCLAW_CANDIDATE_VERSION: "latest",
2560
NEMOCLAW_OPENSHELL_PIN_VERSION: "latest",
2661
NVIDIA_API_KEY: "${{ secrets.NVIDIA_API_KEY }}",
2762
};
@@ -71,12 +106,13 @@ describe("OpenShell gateway auth contract workflow boundary", () => {
71106
"openshell-gateway-auth-contract must retain its 20 minute resource budget",
72107
"openshell-gateway-auth-contract must set DOCKER_GRPC_PROBE_IMAGE=node:22-trixie-slim@sha256:db8a96a63e5264607ada2d206758876ebbed6a12be2ada7517793cbfb0c2a29c",
73108
"openshell-gateway-auth-contract must set E2E_ARTIFACT_DIR=${{ github.workspace }}/e2e-artifacts/live/openshell-gateway-auth-contract",
74-
"openshell-gateway-auth-contract must set NEMOCLAW_OPENSHELL_PIN_VERSION to an exact version",
109+
"openshell-gateway-auth-contract must set NEMOCLAW_CANDIDATE_VERSION=0.0.106",
110+
"openshell-gateway-auth-contract must set NEMOCLAW_OPENSHELL_PIN_VERSION=0.0.106",
75111
"openshell-gateway-auth-contract must not expose NVIDIA_API_KEY at job scope",
76112
"openshell-gateway-auth-contract action 'actions/checkout@v6' must pin a full SHA",
77113
"openshell-gateway-auth-contract checkout must disable persisted credentials",
78114
"openshell-gateway-auth-contract must use the reviewed prepare-e2e action",
79-
"openshell-gateway-auth-contract step 'Install OpenShell CLI' must run: -u DOCKER_CONFIG",
115+
"openshell-gateway-auth-contract must run only the exact credential-free OpenShell 0.0.106 install",
80116
"openshell-gateway-auth-contract step 'Pre-pull pinned gateway auth probe image' must run: docker pull \"$DOCKER_GRPC_PROBE_IMAGE\"",
81117
"openshell-gateway-auth-contract live test must not receive workflow credentials",
82118
"openshell-gateway-auth-contract final artifact safety scan must run unconditionally with a stable id",
@@ -90,6 +126,18 @@ describe("OpenShell gateway auth contract workflow boundary", () => {
90126
);
91127
});
92128

129+
it("rejects a second product installer after the exact OpenShell 0.0.106 install", () => {
130+
const workflow = readOpenShellGatewayAuthContractWorkflow();
131+
const install = workflow.jobs["openshell-gateway-auth-contract"].steps!.find(
132+
(step) => step.name === "Install OpenShell CLI",
133+
)!;
134+
install.run += " && bash scripts/install-openshell.sh";
135+
136+
expect(validateOpenShellGatewayAuthContractWorkflow(workflow)).toContain(
137+
"openshell-gateway-auth-contract must run only the exact credential-free OpenShell 0.0.106 install",
138+
);
139+
});
140+
93141
it("rejects artifact safety commands that can mask scanner failures (#7101)", () => {
94142
const workflow = readOpenShellGatewayAuthContractWorkflow();
95143
const artifactSafety = workflow.jobs["openshell-gateway-auth-contract"].steps!.find(
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env bash
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# Install the exact OpenShell v0.0.106 Linux x86_64 release for the bounded
6+
# qualification jobs that run before NemoClaw changes its supported 0.0.101 pin.
7+
8+
set -euo pipefail
9+
10+
fail() {
11+
printf 'OpenShell v0.0.106 qualification install failed: %s\n' "$1" >&2
12+
exit 1
13+
}
14+
15+
[[ "$(uname -s)" == "Linux" ]] || fail "Linux is required"
16+
case "$(uname -m)" in
17+
x86_64 | amd64) ;;
18+
*) fail "x86_64 is required" ;;
19+
esac
20+
21+
for command_name in curl install mktemp sha256sum tar; do
22+
command -v "$command_name" >/dev/null 2>&1 || fail "$command_name is required"
23+
done
24+
25+
readonly release_tag="v0.0.106"
26+
readonly release_base="https://github.com/NVIDIA/OpenShell/releases/download/${release_tag}"
27+
readonly target_dir="/usr/local/bin"
28+
readonly -a assets=(
29+
"openshell-x86_64-unknown-linux-musl.tar.gz"
30+
"openshell-gateway-x86_64-unknown-linux-gnu.tar.gz"
31+
"openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz"
32+
)
33+
readonly -a members=("openshell" "openshell-gateway" "openshell-sandbox")
34+
readonly -a archive_sha256=(
35+
"d1a885a91b3e5aaa006c36aca95dc78bed0638c1ba1a79b55f1da93211b8a0a0"
36+
"b7760cb752a4363c2f21d32298dd0c683dc438f6edfd16c2e4242bc0baefbb7c"
37+
"559b8aaad3a8eeab45c511e7de531d9baa98a311282dcb0c2c5f38cc2d4ca355"
38+
)
39+
readonly -a binary_sha256=(
40+
"98ecf95113fea999e94a928043e57b04cf58a45a1b66ae8bffc73d1bc8bb1d59"
41+
"e6cde8a54568aa1926ff6584ffd6984314c68dad64d2722509618a74094c622c"
42+
"019301ec8618abbed8135e8d39dde7bea47e5e92813bbc17768550de34db59f8"
43+
)
44+
45+
workspace="$(mktemp -d)"
46+
trap 'rm -rf "$workspace"' EXIT
47+
48+
for index in "${!assets[@]}"; do
49+
asset="${assets[$index]}"
50+
member="${members[$index]}"
51+
archive="${workspace}/${asset}"
52+
extracted="${workspace}/extracted-${index}"
53+
mkdir -p "$extracted"
54+
55+
curl --proto '=https' --tlsv1.2 -fsSL \
56+
--connect-timeout 10 --max-time 120 --retry 3 --retry-all-errors \
57+
"${release_base}/${asset}" -o "$archive"
58+
printf '%s %s\n' "${archive_sha256[$index]}" "$archive" | sha256sum -c -
59+
60+
members_found="$(LC_ALL=C tar -tzf "$archive")"
61+
[[ "$members_found" == "$member" ]] \
62+
|| fail "$asset must contain exactly one member named $member"
63+
member_detail="$(LC_ALL=C tar -tvzf "$archive")"
64+
[[ "$member_detail" != *$'\n'* && "${member_detail:0:1}" == "-" && "${member_detail##* }" == "$member" ]] \
65+
|| fail "$asset member $member must be one regular file"
66+
67+
tar -xzf "$archive" -C "$extracted"
68+
printf '%s %s\n' "${binary_sha256[$index]}" "${extracted}/${member}" | sha256sum -c -
69+
done
70+
71+
install_command=(install -m 755)
72+
if [[ ! -w "$target_dir" ]]; then
73+
command -v sudo >/dev/null 2>&1 || fail "sudo is required to write $target_dir"
74+
install_command=(sudo install -m 755)
75+
fi
76+
77+
for index in "${!members[@]}"; do
78+
member="${members[$index]}"
79+
"${install_command[@]}" "${workspace}/extracted-${index}/${member}" "${target_dir}/${member}"
80+
printf '%s %s\n' "${binary_sha256[$index]}" "${target_dir}/${member}" | sha256sum -c -
81+
done
82+
83+
"${target_dir}/openshell" --version | grep -Eq '(^|[^0-9])0\.0\.106([^0-9]|$)' \
84+
|| fail "installed OpenShell CLI does not report 0.0.106"

0 commit comments

Comments
 (0)