Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
202 lines (177 loc) · 6.85 KB
/
Copy pathinstall.sh
File metadata and controls
202 lines (177 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env bash
set -euo pipefail
CLI_VERSION="${CLI_VERSION:-latest}"
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ACTION_PATH="${GITHUB_ACTION_PATH:-$(pwd)}"
BIN_PATH="${ACTION_PATH}/linear-release"
LEGACY_VERSIONS_PATH="${SCRIPT_PATH}/legacy-versions.txt"
CHECKSUMS_FILE_PATH="${SCRIPT_PATH}/cli-checksums.txt"
RELEASES_API="https://api.github.com/repos/linear/linear-release/releases"
error() {
echo "::error::$*" >&2
}
is_legacy_version() {
grep -Fqx -- "$1" "$LEGACY_VERSIONS_PATH"
}
fetch_release() {
local endpoint="$1"
local api_curl_args=(
-fsSL
-H "Accept: application/vnd.github+json"
-H "X-GitHub-Api-Version: 2026-03-10"
)
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
api_curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
if ! curl "${api_curl_args[@]}" "${RELEASES_API}/${endpoint}"; then
error "Could not fetch CLI release metadata from api.github.com. A 401 means github_token isn't valid for github.com; a 403 usually means the anonymous rate limit. Set github_token to a github.com token, such as a fine-grained personal access token with no permissions."
return 1
fi
}
release_asset_url() {
local release_json="$1"
local asset_name="$2"
local count
count=$(jq --arg name "$asset_name" '[.assets[] | select(.name == $name and .state == "uploaded")] | length' <<<"$release_json")
if [[ "$count" -ne 1 ]]; then
error "Expected exactly one '$asset_name' asset, found $count."
return 1
fi
jq -r --arg name "$asset_name" '.assets[] | select(.name == $name and .state == "uploaded") | .browser_download_url' <<<"$release_json"
}
sha256() {
local file="$1"
if command -v sha256sum &>/dev/null; then
sha256sum "$file" | awk '{print $1}'
elif command -v shasum &>/dev/null; then
shasum -a 256 "$file" | awk '{print $1}'
else
error "SHA-256 verification requires sha256sum or shasum."
return 1
fi
}
case "${RUNNER_OS:-}" in
Linux)
ARCH="$(uname -m)"
if [[ "$ARCH" == "x86_64" || "$ARCH" == "amd64" ]]; then
ASSET="linear-release-linux-x64"
elif [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
ASSET="linear-release-linux-arm64"
else
echo "::error::Unsupported Linux arch: $ARCH. Supported: x86_64, aarch64."
exit 1
fi
;;
macOS)
ARCH="$(uname -m)"
if [[ "$ARCH" == "arm64" ]]; then
ASSET="linear-release-darwin-arm64"
elif [[ "$ARCH" == "x86_64" ]]; then
ASSET="linear-release-darwin-x64"
else
echo "::error::Unsupported macOS arch: $ARCH. Only x86_64 and arm64 are supported."
exit 1
fi
;;
*)
error "Unsupported OS: ${RUNNER_OS:-unknown}"
exit 1
;;
esac
if [[ ! -f "$LEGACY_VERSIONS_PATH" ]]; then
error "Legacy release metadata not found at $LEGACY_VERSIONS_PATH."
exit 1
fi
if [[ ! -f "$CHECKSUMS_FILE_PATH" ]]; then
error "CLI checksum metadata not found at $CHECKSUMS_FILE_PATH."
exit 1
fi
RELEASE_JSON=""
RESOLVED_VERSION="$CLI_VERSION"
if [[ "$CLI_VERSION" == "latest" ]]; then
if ! command -v jq &>/dev/null; then
error "jq is required to resolve and verify the latest CLI release."
exit 1
fi
RELEASE_JSON=$(fetch_release "latest")
RESOLVED_VERSION=$(jq -er '.tag_name | select(type == "string" and length > 0)' <<<"$RELEASE_JSON")
echo "Resolved latest Linear Release CLI to $RESOLVED_VERSION"
fi
EXPECTED_SHA256=""
CHECKSUMS_URL=""
VERIFY_SOURCE=""
COMMITTED_COUNT=$(awk -v tag="$RESOLVED_VERSION" -v asset="$ASSET" '$1 == tag && $3 == asset {count++} END {print count + 0}' "$CHECKSUMS_FILE_PATH")
if [[ "$COMMITTED_COUNT" -gt 1 ]]; then
error "Expected at most one committed checksum for '$ASSET' in $RESOLVED_VERSION, found $COMMITTED_COUNT."
exit 1
fi
if [[ "$COMMITTED_COUNT" -eq 1 ]]; then
EXPECTED_SHA256=$(awk -v tag="$RESOLVED_VERSION" -v asset="$ASSET" '$1 == tag && $3 == asset {print $2}' "$CHECKSUMS_FILE_PATH")
if [[ ! "$EXPECTED_SHA256" =~ ^[[:xdigit:]]{64}$ ]]; then
error "Malformed committed SHA-256 checksum for '$ASSET'."
exit 1
fi
VERIFY_SOURCE="committed checksums for $RESOLVED_VERSION"
URL="https://github.com/linear/linear-release/releases/download/$RESOLVED_VERSION/$ASSET"
elif ! is_legacy_version "$RESOLVED_VERSION"; then
if ! command -v jq &>/dev/null; then
error "jq is required to verify CLI release $RESOLVED_VERSION."
exit 1
fi
if [[ -z "$RELEASE_JSON" ]]; then
ENCODED_VERSION=$(jq -rn --arg version "$RESOLVED_VERSION" '$version | @uri')
RELEASE_JSON=$(fetch_release "tags/${ENCODED_VERSION}")
fi
RELEASE_TAG=$(jq -er '.tag_name | select(type == "string" and length > 0)' <<<"$RELEASE_JSON")
if [[ "$RELEASE_TAG" != "$RESOLVED_VERSION" ]]; then
error "Release metadata returned tag '$RELEASE_TAG', expected '$RESOLVED_VERSION'."
exit 1
fi
if [[ "$(jq -r '.immutable' <<<"$RELEASE_JSON")" != "true" ]]; then
error "CLI release $RESOLVED_VERSION is not immutable. Refusing to execute its assets."
exit 1
fi
URL=$(release_asset_url "$RELEASE_JSON" "$ASSET")
CHECKSUMS_URL=$(release_asset_url "$RELEASE_JSON" "checksums.txt")
VERIFY_SOURCE="immutable release $RESOLVED_VERSION"
else
URL="https://github.com/linear/linear-release/releases/download/$RESOLVED_VERSION/$ASSET"
echo "::notice::CLI release $RESOLVED_VERSION predates artifact verification; continuing with the legacy installation path."
fi
echo "Downloading Linear Release CLI from $URL"
curl_args=(-fsSL)
# Authenticate when a token is set for a higher rate limit; curl drops the header on the cross-host redirect to the asset CDN, so it's only sent to github.com.
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
curl_args+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
TEMP_ROOT="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
TEMP_DIR=$(mktemp -d "${TEMP_ROOT%/}/linear-release.XXXXXX")
trap 'rm -rf "$TEMP_DIR"' EXIT
DOWNLOADED_BIN="${TEMP_DIR}/${ASSET}"
curl "${curl_args[@]}" "$URL" -o "$DOWNLOADED_BIN"
if [[ -n "$CHECKSUMS_URL" ]]; then
CHECKSUMS_PATH="${TEMP_DIR}/checksums.txt"
curl "${curl_args[@]}" "$CHECKSUMS_URL" -o "$CHECKSUMS_PATH"
MATCH_COUNT=$(awk -v asset="$ASSET" '$2 == asset {count++} END {print count + 0}' "$CHECKSUMS_PATH")
if [[ "$MATCH_COUNT" -ne 1 ]]; then
error "Expected exactly one checksum for '$ASSET', found $MATCH_COUNT."
exit 1
fi
EXPECTED_SHA256=$(awk -v asset="$ASSET" '$2 == asset {print $1}' "$CHECKSUMS_PATH")
if [[ ! "$EXPECTED_SHA256" =~ ^[[:xdigit:]]{64}$ ]]; then
error "Malformed SHA-256 checksum for '$ASSET'."
exit 1
fi
fi
if [[ -n "$EXPECTED_SHA256" ]]; then
EXPECTED_SHA256=$(tr '[:upper:]' '[:lower:]' <<<"$EXPECTED_SHA256")
ACTUAL_SHA256=$(sha256 "$DOWNLOADED_BIN")
if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
error "SHA-256 checksum mismatch for '$ASSET'."
exit 1
fi
echo "Verified SHA-256 checksum for $ASSET from $VERIFY_SOURCE"
fi
chmod +x "$DOWNLOADED_BIN"
mv -f "$DOWNLOADED_BIN" "$BIN_PATH"
echo "Linear Release CLI installed at $BIN_PATH"