-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·296 lines (254 loc) · 10.4 KB
/
install.sh
File metadata and controls
executable file
·296 lines (254 loc) · 10.4 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#!/bin/sh
set -e
# CreateOS CLI installer
# Usage:
# curl -sfL https://raw.githubusercontent.com/NodeOps-app/createos-cli/main/install.sh | sh -
#
# Environment variables:
# CREATEOS_VERSION Pin a specific version (e.g. v1.2.3). Defaults to latest stable.
# CREATEOS_CHANNEL Set to "nightly" to install the nightly build.
# CREATEOS_INSTALL_DIR Override install directory. Defaults to /usr/local/bin.
GITHUB_REPO="NodeOps-app/createos-cli"
BINARY_NAME="createos"
# ---------------------------------------------------------------------------
# Colours (suppressed when not a TTY)
# ---------------------------------------------------------------------------
if [ -t 1 ]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
else
RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET=''
fi
info() { printf "${CYAN}[createos]${RESET} %s\n" "$*"; }
success() { printf "${GREEN}[createos]${RESET} %s\n" "$*"; }
warn() { printf "${YELLOW}[createos]${RESET} %s\n" "$*" >&2; }
fatal() { printf "${RED}[createos] error:${RESET} %s\n" "$*" >&2; exit 1; }
# ---------------------------------------------------------------------------
# Detect OS
# ---------------------------------------------------------------------------
detect_os() {
OS="$(uname -s)"
case "${OS}" in
Linux) OS="linux" ;;
Darwin) OS="darwin" ;;
*) fatal "Unsupported operating system: ${OS}. Only Linux and macOS are supported." ;;
esac
}
# ---------------------------------------------------------------------------
# Detect architecture
# ---------------------------------------------------------------------------
detect_arch() {
ARCH="$(uname -m)"
case "${ARCH}" in
x86_64 | amd64) ARCH="amd64" ;;
aarch64 | arm64 | armv8) ARCH="arm64" ;;
*) fatal "Unsupported architecture: ${ARCH}. Only x86_64 and arm64 are supported." ;;
esac
}
# ---------------------------------------------------------------------------
# Prompt for channel if not set and stdin is a TTY
# ---------------------------------------------------------------------------
prompt_channel() {
# Already set via env — skip prompt
if [ -n "${CREATEOS_CHANNEL:-}" ]; then
return
fi
# Pinned version set — stable implied, skip prompt
if [ -n "${CREATEOS_VERSION:-}" ]; then
CREATEOS_CHANNEL="stable"
return
fi
# Non-interactive (piped): default to stable silently
if [ ! -t 0 ]; then
CREATEOS_CHANNEL="stable"
return
fi
printf "\n${BOLD} Which release channel would you like to install?${RESET}\n\n"
printf " ${BOLD}1) stable${RESET} — official releases, production-ready (recommended)\n"
printf " ${BOLD}2) nightly${RESET} — built daily from main, may contain unreleased features\n\n"
printf " Enter 1 or 2 [default: 1]: "
read -r CHOICE </dev/tty
case "${CHOICE}" in
2) CREATEOS_CHANNEL="nightly" ;;
*) CREATEOS_CHANNEL="stable" ;;
esac
printf "\n"
}
# ---------------------------------------------------------------------------
# Resolve the download version
# ---------------------------------------------------------------------------
resolve_version() {
CHANNEL="${CREATEOS_CHANNEL:-stable}"
if [ "${CHANNEL}" = "nightly" ]; then
VERSION="nightly"
info "Channel: nightly"
return
fi
if [ -n "${CREATEOS_VERSION:-}" ]; then
VERSION="${CREATEOS_VERSION}"
info "Version: ${VERSION} (pinned)"
return
fi
info "Fetching latest stable release..."
LATEST_URL="https://api.github.com/repos/${GITHUB_REPO}/releases/latest"
if command -v curl > /dev/null 2>&1; then
RELEASE_JSON="$(curl -fsSL --retry 3 "${LATEST_URL}" 2>&1)" || {
fatal "No stable release found. If you want the nightly build, run:\n curl -sfL https://raw.githubusercontent.com/${GITHUB_REPO}/main/install.sh | CREATEOS_CHANNEL=nightly sh -"
}
VERSION="$(printf '%s' "${RELEASE_JSON}" | grep '"tag_name"' | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')"
elif command -v wget > /dev/null 2>&1; then
RELEASE_JSON="$(wget -qO- "${LATEST_URL}")" || {
fatal "No stable release found. If you want the nightly build, run:\n curl -sfL https://raw.githubusercontent.com/${GITHUB_REPO}/main/install.sh | CREATEOS_CHANNEL=nightly sh -"
}
VERSION="$(printf '%s' "${RELEASE_JSON}" | grep '"tag_name"' | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')"
else
fatal "curl or wget is required to download createos."
fi
[ -n "${VERSION}" ] || fatal "Could not determine the latest version. Check your internet connection."
# Validate format to guard against tampered API responses
case "${VERSION}" in
v[0-9]*.[0-9]*.[0-9]*) ;;
*) fatal "Unexpected version format received: '${VERSION}'. Aborting." ;;
esac
info "Latest version: ${VERSION}"
}
# ---------------------------------------------------------------------------
# Build asset names and URLs
# ---------------------------------------------------------------------------
build_urls() {
ASSET="${BINARY_NAME}-${OS}-${ARCH}"
CHECKSUM_ASSET="${ASSET}.sha256"
if [ "${VERSION}" = "nightly" ]; then
BASE_URL="https://github.com/${GITHUB_REPO}/releases/download/nightly"
else
BASE_URL="https://github.com/${GITHUB_REPO}/releases/download/${VERSION}"
fi
BINARY_URL="${BASE_URL}/${ASSET}"
CHECKSUM_URL="${BASE_URL}/${CHECKSUM_ASSET}"
}
# ---------------------------------------------------------------------------
# Resolve install directory (prefer /usr/local/bin, fall back to ~/.local/bin)
# ---------------------------------------------------------------------------
resolve_install_dir() {
if [ -n "${CREATEOS_INSTALL_DIR:-}" ]; then
INSTALL_DIR="${CREATEOS_INSTALL_DIR}"
return
fi
if [ -w "/usr/local/bin" ]; then
INSTALL_DIR="/usr/local/bin"
elif [ "$(id -u)" = "0" ]; then
INSTALL_DIR="/usr/local/bin"
else
INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "${INSTALL_DIR}"
# Warn if not on PATH
case ":${PATH}:" in
*":${INSTALL_DIR}:"*) ;;
*) warn "${INSTALL_DIR} is not in your PATH. Add it: export PATH=\"\$HOME/.local/bin:\$PATH\"" ;;
esac
fi
}
# ---------------------------------------------------------------------------
# Download helpers (curl or wget)
# ---------------------------------------------------------------------------
# download_silent: no progress output — used for small files (checksums, API calls)
download_silent() {
URL="$1"
DEST="$2"
if command -v curl > /dev/null 2>&1; then
curl -fsSL --retry 3 --retry-delay 2 -o "${DEST}" "${URL}"
elif command -v wget > /dev/null 2>&1; then
wget -qO "${DEST}" "${URL}"
else
fatal "curl or wget is required."
fi
}
# download_progress: shows progress bar — used for the binary
download_progress() {
URL="$1"
DEST="$2"
if command -v curl > /dev/null 2>&1; then
curl -fL --retry 3 --retry-delay 2 --progress-bar -o "${DEST}" "${URL}"
elif command -v wget > /dev/null 2>&1; then
wget --show-progress -qO "${DEST}" "${URL}" 2>&1
else
fatal "curl or wget is required."
fi
}
# ---------------------------------------------------------------------------
# Verify SHA256 checksum
# ---------------------------------------------------------------------------
verify_checksum() {
BINARY_PATH="$1"
EXPECTED="$2"
# Validate expected hash is a 64-char hex string before trusting it
case "${EXPECTED}" in
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ;;
*) fatal "Checksum file contains unexpected content — aborting." ;;
esac
if command -v sha256sum > /dev/null 2>&1; then
ACTUAL="$(sha256sum "${BINARY_PATH}" | awk '{print $1}')"
elif command -v shasum > /dev/null 2>&1; then
ACTUAL="$(shasum -a 256 "${BINARY_PATH}" | awk '{print $1}')"
else
fatal "sha256sum or shasum is required to verify the download. Please install one and retry."
fi
if [ "${ACTUAL}" != "${EXPECTED}" ]; then
fatal "Checksum mismatch — the download may be corrupted or tampered with.\n expected: ${EXPECTED}\n got: ${ACTUAL}"
fi
success "Checksum verified."
}
# ---------------------------------------------------------------------------
# Install
# ---------------------------------------------------------------------------
install_binary() {
INSTALL_PATH="${INSTALL_DIR}/${BINARY_NAME}"
TMP_DIR="$(mktemp -d)"
TMP_BINARY="${TMP_DIR}/${ASSET}"
TMP_CHECKSUM="${TMP_DIR}/${CHECKSUM_ASSET}"
trap 'rm -rf "${TMP_DIR}"' EXIT
info "Downloading ${ASSET}..."
download_progress "${BINARY_URL}" "${TMP_BINARY}" \
|| fatal "Failed to download binary from ${BINARY_URL}"
download_silent "${CHECKSUM_URL}" "${TMP_CHECKSUM}" \
|| fatal "Failed to download checksum from ${CHECKSUM_URL}"
EXPECTED_HASH="$(cat "${TMP_CHECKSUM}" | tr -d '[:space:]')"
verify_checksum "${TMP_BINARY}" "${EXPECTED_HASH}"
chmod 755 "${TMP_BINARY}"
# Use sudo only if we need it
if [ -w "${INSTALL_DIR}" ]; then
mv "${TMP_BINARY}" "${INSTALL_PATH}"
else
info "Root access required to install to ${INSTALL_DIR}."
sudo mv "${TMP_BINARY}" "${INSTALL_PATH}"
fi
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
printf "\n${BOLD} CreateOS CLI Installer${RESET}\n\n"
detect_os
detect_arch
prompt_channel
resolve_version
build_urls
resolve_install_dir
info "OS: ${OS} / Arch: ${ARCH}"
info "Install directory: ${INSTALL_DIR}"
install_binary
success "createos ${VERSION} installed to ${INSTALL_DIR}/${BINARY_NAME}"
printf "\n Run ${CYAN}createos --help${RESET} to get started.\n\n"
}
main