Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·170 lines (151 loc) · 5.29 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·170 lines (151 loc) · 5.29 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
#!/bin/sh
set -eu
# Cairn Code installer (Linux & macOS)
# Usage: curl -fsSL https://raw.githubusercontent.com/cairn/cairn/main/install.sh | sh
REPO="cairn/cairn"
BINARY_NAME="cairn"
ASSET_NAME="cairn-code-linux-x86_64"
# Validate OS
OS="$(uname -s)"
ARCH="$(uname -m)"
case "$OS" in
Linux)
case "$ARCH" in
x86_64|amd64)
ASSET_NAME="cairn-code-linux-x86_64"
;;
aarch64|arm64)
ASSET_NAME="cairn-code-linux-aarch64"
;;
*)
echo "Error: Unsupported architecture '${ARCH}' on Linux." >&2
exit 1
;;
esac
;;
Darwin)
case "$ARCH" in
arm64|aarch64)
ASSET_NAME="cairn-code-macos-aarch64"
;;
x86_64|amd64)
ASSET_NAME="cairn-code-macos-x86_64"
;;
*)
echo "Error: Unsupported architecture '${ARCH}' on macOS." >&2
exit 1
;;
esac
;;
*)
echo "Error: Unsupported operating system '${OS}'. Cairn Code supports macOS and Linux." >&2
exit 1
;;
esac
# Require curl
if ! command -v curl >/dev/null 2>&1; then
echo "Error: curl is required to download Cairn Code." >&2
exit 1
fi
# Resolve "latest" to a pinned version tag before downloading anything.
# Fetching the binary and the checksum file from two separate "latest" URLs
# is a TOCTOU race: if a new release is published between the two downloads,
# the checksum will not match the binary and the install fails. Pinning both
# downloads to the same immutable release tag eliminates that window.
echo "Resolving latest release version..."
if ! RELEASE_JSON="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest")"; then
echo "Error: Failed to resolve the latest release version from the GitHub API." >&2
exit 1
fi
TAG="$(printf '%s\n' "$RELEASE_JSON" | sed -n 's/^[[:space:]]*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p')"
if [ -z "$TAG" ]; then
echo "Error: Could not parse a release tag from the GitHub API response." >&2
echo "Refusing to install an unverified binary." >&2
exit 1
fi
# The tag is interpolated into download URLs below; reject anything that
# does not look like a version tag.
case "$TAG" in
*[!A-Za-z0-9._-]*)
echo "Error: Refusing to use unexpected release tag '${TAG}'." >&2
exit 1
;;
esac
echo "Latest release is ${TAG}."
# Determine install directory
if [ -n "${CAIRN_INSTALL_DIR:-}" ]; then
INSTALL_DIR="$CAIRN_INSTALL_DIR"
elif [ -w "/usr/local/bin" ] && [ "$(id -u)" -eq 0 ]; then
INSTALL_DIR="/usr/local/bin"
else
INSTALL_DIR="${HOME}/.local/bin"
fi
mkdir -p "$INSTALL_DIR"
# Download release binary and checksums, both pinned to the resolved tag
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${TAG}/${ASSET_NAME}"
CHECKSUM_URL="https://github.com/${REPO}/releases/download/${TAG}/sha256sum.txt"
TMP_FILE="$(mktemp "${TMPDIR:-/tmp}/cairn.XXXXXX")"
TMP_SUMS="$(mktemp "${TMPDIR:-/tmp}/cairn-sums.XXXXXX")"
trap 'rm -f "$TMP_FILE" "$TMP_SUMS"' EXIT
echo "Downloading Cairn Code from ${DOWNLOAD_URL}..."
if ! curl -fsSL "$DOWNLOAD_URL" -o "$TMP_FILE"; then
echo "Error: Failed to download ${ASSET_NAME} from ${DOWNLOAD_URL}." >&2
if [ "$OS" = "Darwin" ]; then
echo "macOS release binaries may not be published yet for release ${TAG}." >&2
fi
exit 1
fi
echo "Downloading checksums from ${CHECKSUM_URL}..."
if ! curl -fsSL "$CHECKSUM_URL" -o "$TMP_SUMS"; then
echo "Error: Failed to download sha256sum.txt from ${CHECKSUM_URL}." >&2
echo "Refusing to install an unverified binary." >&2
exit 1
fi
# Verify the binary against the published checksum before installing
echo "Verifying checksum..."
EXPECTED="$(grep " ${ASSET_NAME}$" "$TMP_SUMS" | awk '{print $1}')"
if [ -z "$EXPECTED" ]; then
echo "Error: No checksum found for ${ASSET_NAME} in sha256sum.txt." >&2
echo "Refusing to install an unverified binary." >&2
exit 1
fi
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL="$(sha256sum "$TMP_FILE" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
ACTUAL="$(shasum -a 256 "$TMP_FILE" | awk '{print $1}')"
else
echo "Error: Neither sha256sum nor shasum is available to verify the download." >&2
echo "Refusing to install an unverified binary." >&2
exit 1
fi
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "Error: Checksum mismatch for ${ASSET_NAME}." >&2
echo " Expected: ${EXPECTED}" >&2
echo " Actual: ${ACTUAL}" >&2
echo "The download may be corrupted or tampered with. Aborting." >&2
exit 1
fi
echo "Checksum verified."
chmod +x "$TMP_FILE"
DEST="${INSTALL_DIR}/${BINARY_NAME}"
mv -f "$TMP_FILE" "$DEST"
trap - EXIT
# Clean up the checksums file
rm -f "$TMP_SUMS"
echo "Installed Cairn Code to ${DEST}"
# Verify installation
if "$DEST" --version >/dev/null 2>&1; then
VERSION="$("$DEST" --version)"
echo "Successfully installed: ${VERSION}"
fi
# Check PATH
case ":${PATH}:" in
*:"${INSTALL_DIR}":*)
;;
*)
echo ""
echo "Note: '${INSTALL_DIR}' is not in your PATH."
echo "Add it to your shell configuration (e.g. ~/.bashrc or ~/.zshrc):"
echo " export PATH=\"${INSTALL_DIR}:\$PATH\""
;;
esac