mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-02 07:34:45 +08:00
* feat(installer): support prerelease installations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(readme): clarify 0.1.0 production readiness Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(readme): refine production readiness message Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(release): publish rolling prerelease channels Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(release): keep prereleases out of GitHub releases Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(readme): refine 0.1.0 notice Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(readme): expand 0.1.0 notice Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(readme): simplify 0.1.0 notice Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(installer): simplify prerelease alias Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(installer): require successful prerelease runs Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(release): reuse exact prerelease artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(installer): include prover in prerelease bundle Signed-off-by: Drew Newberry <anewberry@nvidia.com> * ci: remove prerelease post-publish validation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(fern): sync announcement configuration Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * docs: remove stale prerelease canary claim Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(fern): defer announcement synchronization Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(fern): simplify release announcement Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com> Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> Co-authored-by: Piotr Mlocek <pmlocek@nvidia.com>
1245 lines
36 KiB
Bash
Executable File
1245 lines
36 KiB
Bash
Executable File
#!/bin/sh
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Install OpenShell from a GitHub release.
|
|
#
|
|
# Linux installs either the Debian or RPM packages from the selected release.
|
|
# Apple Silicon macOS installs the generated Homebrew formula, so Homebrew owns
|
|
# the binary layout and launchd service lifecycle.
|
|
#
|
|
set -e
|
|
|
|
APP_NAME="openshell"
|
|
REPO="NVIDIA/OpenShell"
|
|
GITHUB_URL="https://github.com/${REPO}"
|
|
RELEASE_TAG="${OPENSHELL_VERSION:-}"
|
|
RELEASE_ASSET_DIR=""
|
|
CHECKSUMS_NAME="openshell-checksums-sha256.txt"
|
|
LOCAL_GATEWAY_PORT="17670"
|
|
HOMEBREW_TAP="nvidia/openshell"
|
|
HOMEBREW_FORMULA_NAME="openshell"
|
|
HOMEBREW_CLI_ASSET="openshell-aarch64-apple-darwin.tar.gz"
|
|
HOMEBREW_GATEWAY_ASSET="openshell-gateway-aarch64-apple-darwin.tar.gz"
|
|
HOMEBREW_DRIVER_VM_ASSET="openshell-driver-vm-aarch64-apple-darwin.tar.gz"
|
|
HOMEBREW_PROVER_ASSET="openshell-prover-aarch64-apple-darwin.tar.gz"
|
|
BREAKING_RELEASE_VERSION="0.0.37"
|
|
LINUX_PACKAGE_GLIBC_MIN_VERSION="2.28"
|
|
UPGRADE_NOTICE_ACK="${OPENSHELL_ACK_BREAKING_UPGRADE:-}"
|
|
|
|
info() {
|
|
printf '%s: %s\n' "$APP_NAME" "$*" >&2
|
|
}
|
|
|
|
warn() {
|
|
printf '%s: warning: %s\n' "$APP_NAME" "$*" >&2
|
|
}
|
|
|
|
error() {
|
|
printf '%s: error: %s\n' "$APP_NAME" "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
install.sh - Install OpenShell
|
|
|
|
USAGE:
|
|
curl -fsSL https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh -o install.sh
|
|
sh install.sh
|
|
|
|
curl -fsSL https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | sh
|
|
|
|
OPTIONS:
|
|
--help Print this help message
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
OPENSHELL_VERSION Release tag to install (default: latest tagged release).
|
|
Set OPENSHELL_VERSION=dev to install the rolling dev build.
|
|
Set OPENSHELL_VERSION=pre to install the latest prerelease.
|
|
Prereleases require an authenticated GitHub CLI session.
|
|
OPENSHELL_ACK_BREAKING_UPGRADE
|
|
Set to 1 only after backing up and cleaning up a
|
|
pre-v0.0.37 installation.
|
|
|
|
NOTES:
|
|
When OPENSHELL_VERSION is unset, this resolves the latest tagged release
|
|
from ${GITHUB_URL}/releases/latest.
|
|
|
|
Linux installs the Debian package on amd64/arm64 or the RPM packages on
|
|
x86_64/aarch64, depending on the host package manager.
|
|
macOS installs the release Homebrew formula on Apple Silicon and starts a
|
|
brew services-backed local gateway.
|
|
EOF
|
|
}
|
|
|
|
has_cmd() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
require_cmd() {
|
|
if ! has_cmd "$1"; then
|
|
error "'$1' is required"
|
|
fi
|
|
}
|
|
|
|
download() {
|
|
_url="$1"
|
|
_output="$2"
|
|
curl -fLsS --retry 3 --max-redirs 5 -o "$_output" "$_url"
|
|
}
|
|
|
|
semver_core() {
|
|
_version="${1#v}"
|
|
_version="${_version%%[-+]*}"
|
|
printf '%s\n' "$_version"
|
|
}
|
|
|
|
semver_at_least() {
|
|
_version="$(semver_core "$1")"
|
|
_minimum="$(semver_core "$2")"
|
|
|
|
_major="${_version%%.*}"
|
|
_rest="${_version#*.}"
|
|
[ "$_rest" != "$_version" ] || return 1
|
|
_minor="${_rest%%.*}"
|
|
_patch="${_rest#*.}"
|
|
_patch="${_patch%%.*}"
|
|
|
|
_min_major="${_minimum%%.*}"
|
|
_min_rest="${_minimum#*.}"
|
|
[ "$_min_rest" != "$_minimum" ] || return 1
|
|
_min_minor="${_min_rest%%.*}"
|
|
_min_patch="${_min_rest#*.}"
|
|
_min_patch="${_min_patch%%.*}"
|
|
|
|
case "$_major:$_minor:$_patch:$_min_major:$_min_minor:$_min_patch" in
|
|
*[!0-9:]* | *::*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
[ "$_major" -gt "$_min_major" ] && return 0
|
|
[ "$_major" -lt "$_min_major" ] && return 1
|
|
[ "$_minor" -gt "$_min_minor" ] && return 0
|
|
[ "$_minor" -lt "$_min_minor" ] && return 1
|
|
[ "$_patch" -ge "$_min_patch" ]
|
|
}
|
|
|
|
version_at_least_major_minor() {
|
|
_version="$1"
|
|
_minimum="$2"
|
|
|
|
_major="${_version%%.*}"
|
|
_minor="${_version#*.}"
|
|
_minor="${_minor%%.*}"
|
|
|
|
_min_major="${_minimum%%.*}"
|
|
_min_minor="${_minimum#*.}"
|
|
_min_minor="${_min_minor%%.*}"
|
|
|
|
case "$_major:$_minor:$_min_major:$_min_minor" in
|
|
*[!0-9:]* | *::*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
[ "$_major" -gt "$_min_major" ] && return 0
|
|
[ "$_major" -lt "$_min_major" ] && return 1
|
|
[ "$_minor" -ge "$_min_minor" ]
|
|
}
|
|
|
|
getconf_gnu_libc_version() {
|
|
if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_GETCONF_UNAVAILABLE:-0}" = "1" ]; then
|
|
return 127
|
|
fi
|
|
|
|
if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_GETCONF_OUTPUT+x}" = "x" ]; then
|
|
printf '%s\n' "$OPENSHELL_TEST_GETCONF_OUTPUT"
|
|
return 0
|
|
fi
|
|
|
|
getconf GNU_LIBC_VERSION 2>/dev/null
|
|
}
|
|
|
|
ldd_version_output() {
|
|
if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_LDD_UNAVAILABLE:-0}" = "1" ]; then
|
|
return 127
|
|
fi
|
|
|
|
if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" = "1" ] && [ "${OPENSHELL_TEST_LDD_OUTPUT+x}" = "x" ]; then
|
|
printf '%s\n' "$OPENSHELL_TEST_LDD_OUTPUT"
|
|
return 0
|
|
fi
|
|
|
|
ldd --version 2>&1
|
|
}
|
|
|
|
detect_glibc_version() {
|
|
_ldd_output="$(ldd_version_output 2>&1 || true)"
|
|
case "$_ldd_output" in
|
|
*[Mm][Uu][Ss][Ll]* | *[Aa][Ll][Pp][Ii][Nn][Ee]*)
|
|
return 2
|
|
;;
|
|
esac
|
|
|
|
_ldd_version="$(printf '%s\n' "$_ldd_output" | awk 'FNR == 1 && match($NF, /^[0-9]+\.[0-9]+/) { print substr($NF, RSTART, RLENGTH); exit }')"
|
|
if [ -n "$_ldd_version" ]; then
|
|
printf '%s\n' "$_ldd_version"
|
|
return 0
|
|
fi
|
|
|
|
_getconf_output="$(getconf_gnu_libc_version 2>/dev/null || true)"
|
|
case "$_getconf_output" in
|
|
glibc\ [0-9]*.[0-9]*)
|
|
printf '%s\n' "${_getconf_output#glibc }"
|
|
return 0
|
|
;;
|
|
*[Mm][Uu][Ss][Ll]* | *[Aa][Ll][Pp][Ii][Nn][Ee]*)
|
|
return 2
|
|
;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
require_linux_package_glibc() {
|
|
_glibc_status=0
|
|
_glibc_version="$(detect_glibc_version)" || _glibc_status=$?
|
|
|
|
case "$_glibc_status" in
|
|
0)
|
|
;;
|
|
2)
|
|
error "OpenShell Linux packages require glibc >= ${LINUX_PACKAGE_GLIBC_MIN_VERSION}; detected musl or unsupported libc."
|
|
;;
|
|
*)
|
|
error "OpenShell Linux packages require glibc >= ${LINUX_PACKAGE_GLIBC_MIN_VERSION}; could not detect glibc."
|
|
;;
|
|
esac
|
|
|
|
if ! version_at_least_major_minor "$_glibc_version" "$LINUX_PACKAGE_GLIBC_MIN_VERSION"; then
|
|
error "OpenShell Linux packages require glibc >= ${LINUX_PACKAGE_GLIBC_MIN_VERSION}; detected glibc ${_glibc_version}.
|
|
Please use a newer distribution or container environment."
|
|
fi
|
|
}
|
|
|
|
target_uses_breaking_gateway_model() {
|
|
case "$RELEASE_TAG" in
|
|
dev)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
semver_at_least "$RELEASE_TAG" "$BREAKING_RELEASE_VERSION"
|
|
}
|
|
|
|
installed_version_needs_breaking_upgrade_notice() {
|
|
_version="$1"
|
|
|
|
if [ -z "$_version" ]; then
|
|
return 0
|
|
fi
|
|
|
|
! semver_at_least "$_version" "$BREAKING_RELEASE_VERSION"
|
|
}
|
|
|
|
find_existing_openshell_bin() {
|
|
_path="$(command -v openshell 2>/dev/null || true)"
|
|
if [ -n "$_path" ] && [ -x "$_path" ]; then
|
|
printf '%s\n' "$_path"
|
|
return 0
|
|
fi
|
|
|
|
for _candidate in \
|
|
"${TARGET_HOME:-}/.local/bin/openshell" \
|
|
/usr/local/bin/openshell \
|
|
/usr/bin/openshell \
|
|
/opt/homebrew/bin/openshell; do
|
|
if [ -n "$_candidate" ] && [ -x "$_candidate" ]; then
|
|
printf '%s\n' "$_candidate"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
existing_openshell_version() {
|
|
_bin="$1"
|
|
_output="$("$_bin" --version 2>/dev/null | sed -n '1p' || true)"
|
|
printf '%s\n' "$_output" | awk '
|
|
{
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i ~ /^v?[0-9]+\.[0-9]+\.[0-9]+([-+][A-Za-z0-9.+~-]+)?$/) {
|
|
print $i
|
|
exit
|
|
}
|
|
}
|
|
}
|
|
'
|
|
}
|
|
|
|
print_breaking_upgrade_notice() {
|
|
_bin="$1"
|
|
_version="$2"
|
|
|
|
if [ -n "$_version" ]; then
|
|
warn "detected existing OpenShell ${_version} at ${_bin}"
|
|
else
|
|
warn "detected an existing OpenShell installation at ${_bin}"
|
|
fi
|
|
|
|
cat >&2 <<EOF
|
|
|
|
OpenShell ${BREAKING_RELEASE_VERSION} and later are incompatible with gateway
|
|
state created by earlier releases. Before installing ${RELEASE_TAG}, back up
|
|
any files, artifacts, and configuration you need from existing sandboxes.
|
|
|
|
Then clean up the old runtime with the currently installed CLI:
|
|
|
|
openshell sandbox delete --all
|
|
openshell gateway destroy
|
|
|
|
Run these commands before upgrading because 'openshell gateway destroy' is not
|
|
available in OpenShell ${BREAKING_RELEASE_VERSION} and later.
|
|
|
|
After cleanup, rerun this installer or follow the installation guide:
|
|
|
|
https://docs.nvidia.com/openshell/latest/about/installation
|
|
|
|
If you have already backed up and cleaned up the old runtime, rerun with:
|
|
|
|
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/main/install.sh | OPENSHELL_ACK_BREAKING_UPGRADE=1 sh
|
|
|
|
EOF
|
|
}
|
|
|
|
guard_breaking_upgrade() {
|
|
target_uses_breaking_gateway_model || return 0
|
|
|
|
_bin="$(find_existing_openshell_bin || true)"
|
|
[ -n "$_bin" ] || return 0
|
|
|
|
_version="$(existing_openshell_version "$_bin")"
|
|
installed_version_needs_breaking_upgrade_notice "$_version" || return 0
|
|
|
|
print_breaking_upgrade_notice "$_bin" "$_version"
|
|
|
|
if [ "$UPGRADE_NOTICE_ACK" = "1" ]; then
|
|
warn "continuing because OPENSHELL_ACK_BREAKING_UPGRADE=1 is set"
|
|
return 0
|
|
fi
|
|
|
|
error "manual cleanup is required before upgrading from this OpenShell installation"
|
|
}
|
|
|
|
resolve_release_tag() {
|
|
if [ "${OPENSHELL_VERSION:-}" = "pre" ]; then
|
|
resolve_latest_prerelease_tag
|
|
return 0
|
|
fi
|
|
|
|
if [ -n "${OPENSHELL_VERSION:-}" ]; then
|
|
echo "$OPENSHELL_VERSION"
|
|
return 0
|
|
fi
|
|
|
|
info "resolving latest version..."
|
|
_latest_url="${GITHUB_URL}/releases/latest"
|
|
_resolved="$(curl -fLsS -o /dev/null -w '%{url_effective}' "$_latest_url")" || {
|
|
error "failed to resolve latest release from ${_latest_url}"
|
|
}
|
|
|
|
case "$_resolved" in
|
|
https://github.com/${REPO}/releases/*)
|
|
;;
|
|
*)
|
|
error "unexpected redirect target: ${_resolved} (expected https://github.com/${REPO}/releases/...)"
|
|
;;
|
|
esac
|
|
|
|
_version="${_resolved##*/}"
|
|
if [ -z "$_version" ] || [ "$_version" = "latest" ]; then
|
|
error "could not determine latest release version (resolved URL: ${_resolved})"
|
|
fi
|
|
|
|
echo "$_version"
|
|
}
|
|
|
|
resolve_latest_prerelease_tag() {
|
|
require_prerelease_github_access
|
|
|
|
info "resolving latest prerelease..."
|
|
_artifact_platform="$(prerelease_artifact_platform)"
|
|
_successful_run_ids="$(gh api --paginate \
|
|
"repos/${REPO}/actions/workflows/release-tag.yml/runs?status=success&per_page=100" \
|
|
--jq '.workflow_runs[] | select(.status == "completed" and .conclusion == "success") | .id')" || {
|
|
error "failed to list successful Release Tag workflow runs"
|
|
}
|
|
_artifact_records="$(gh api --paginate \
|
|
"repos/${REPO}/actions/artifacts?per_page=100" \
|
|
--jq '.artifacts[] | select(.expired == false) | [.workflow_run.id, .name] | @tsv')" || {
|
|
error "failed to list prerelease artifacts"
|
|
}
|
|
_artifact_names="$(printf '%s\n--ARTIFACTS--\n%s\n' "$_successful_run_ids" "$_artifact_records" | awk -F '\t' '
|
|
$0 == "--ARTIFACTS--" {
|
|
reading_artifacts = 1
|
|
next
|
|
}
|
|
!reading_artifacts {
|
|
if ($1 ~ /^[0-9]+$/) successful_runs[$1] = 1
|
|
next
|
|
}
|
|
$1 in successful_runs {
|
|
print $2
|
|
}
|
|
')"
|
|
_release_tags="$(printf '%s\n' "$_artifact_names" | sed -n "s/^openshell-\(v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*-pre\.[1-9][0-9]*\)-${_artifact_platform}$/\1/p" | sort -u)"
|
|
|
|
_latest_prerelease="$(printf '%s\n' "$_release_tags" | awk '
|
|
/^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[1-9][0-9]*$/ {
|
|
tag = $0
|
|
sub(/^v/, "", tag)
|
|
split(tag, version_parts, "-pre[.]")
|
|
split(version_parts[1], core, "[.]")
|
|
sequence = version_parts[2] + 0
|
|
|
|
if (!found || core[1] + 0 > major ||
|
|
(core[1] + 0 == major && core[2] + 0 > minor) ||
|
|
(core[1] + 0 == major && core[2] + 0 == minor && core[3] + 0 > patch) ||
|
|
(core[1] + 0 == major && core[2] + 0 == minor && core[3] + 0 == patch && sequence > prerelease)) {
|
|
selected = $0
|
|
major = core[1] + 0
|
|
minor = core[2] + 0
|
|
patch = core[3] + 0
|
|
prerelease = sequence
|
|
found = 1
|
|
}
|
|
}
|
|
END {
|
|
if (found) print selected
|
|
}
|
|
')"
|
|
|
|
if [ -z "$_latest_prerelease" ]; then
|
|
error "no unexpired prerelease artifacts found"
|
|
fi
|
|
|
|
printf '%s\n' "$_latest_prerelease"
|
|
}
|
|
|
|
is_prerelease_tag() {
|
|
printf '%s\n' "$1" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[1-9][0-9]*$'
|
|
}
|
|
|
|
require_prerelease_github_access() {
|
|
require_cmd gh
|
|
if ! gh auth status --hostname github.com >/dev/null 2>&1; then
|
|
error "GitHub CLI authentication is required for prerelease artifacts; run 'gh auth login' and try again"
|
|
fi
|
|
}
|
|
|
|
prerelease_artifact_platform() {
|
|
case "${PLATFORM}:$(uname -m)" in
|
|
linux:x86_64 | linux:amd64)
|
|
case "$(linux_package_method)" in
|
|
deb) echo "linux-amd64-deb" ;;
|
|
rpm) echo "linux-x86_64-rpm" ;;
|
|
esac
|
|
;;
|
|
linux:aarch64 | linux:arm64)
|
|
case "$(linux_package_method)" in
|
|
deb) echo "linux-arm64-deb" ;;
|
|
rpm) echo "linux-aarch64-rpm" ;;
|
|
esac
|
|
;;
|
|
darwin:arm64 | darwin:aarch64) echo "macos-arm64" ;;
|
|
*) error "no prerelease artifact is available for ${PLATFORM}/$(uname -m)" ;;
|
|
esac
|
|
}
|
|
|
|
prepare_prerelease_assets() {
|
|
_destination="$1"
|
|
is_prerelease_tag "$RELEASE_TAG" || return 0
|
|
require_prerelease_github_access
|
|
|
|
_artifact_name="openshell-${RELEASE_TAG}-$(prerelease_artifact_platform)"
|
|
info "locating ${_artifact_name}..."
|
|
_run_id="$(gh api \
|
|
"repos/${REPO}/actions/artifacts?name=${_artifact_name}&per_page=100" \
|
|
--jq '[.artifacts[] | select(.expired == false)] | sort_by(.created_at) | last | .workflow_run.id')" || {
|
|
error "failed to find prerelease artifact ${_artifact_name}"
|
|
}
|
|
if [ -z "$_run_id" ] || [ "$_run_id" = "null" ] || ! printf '%s\n' "$_run_id" | grep -Eq '^[0-9]+$'; then
|
|
error "no unexpired prerelease artifact found for ${RELEASE_TAG} on this platform"
|
|
fi
|
|
|
|
RELEASE_ASSET_DIR="${_destination}/release"
|
|
info "downloading ${_artifact_name}..."
|
|
gh run download "$_run_id" \
|
|
--repo "$REPO" \
|
|
--name "$_artifact_name" \
|
|
--dir "$RELEASE_ASSET_DIR" || {
|
|
error "failed to download prerelease artifact ${_artifact_name}"
|
|
}
|
|
}
|
|
|
|
download_release_asset() {
|
|
_tag="$1"
|
|
_filename="$2"
|
|
_output="$3"
|
|
|
|
if [ -n "$RELEASE_ASSET_DIR" ]; then
|
|
_source="${RELEASE_ASSET_DIR}/${_filename}"
|
|
if [ -f "$_source" ]; then
|
|
cp "$_source" "$_output"
|
|
return 0
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
if curl -fLs --retry 3 --max-redirs 5 -o "$_output" \
|
|
"${GITHUB_URL}/releases/download/${_tag}/${_filename}"; then
|
|
return 0
|
|
fi
|
|
|
|
# GitHub normalizes `~` to `.` in release asset names, while checksum files
|
|
# can still record package filenames with `~dev` for correct version ordering.
|
|
# Download the normalized asset but verify it against the checksum entry for
|
|
# the original package filename.
|
|
_normalized="$(printf '%s' "$_filename" | tr '~' '.')"
|
|
if [ "$_normalized" != "$_filename" ]; then
|
|
if download "${GITHUB_URL}/releases/download/${_tag}/${_normalized}" "$_output"; then
|
|
info "using GitHub-normalized asset name ${_normalized}"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
as_root() {
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
"$@"
|
|
elif has_cmd sudo; then
|
|
sudo "$@"
|
|
else
|
|
error "this installer needs root privileges; rerun as root or install sudo"
|
|
fi
|
|
}
|
|
|
|
target_user() {
|
|
if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
|
|
echo "$SUDO_USER"
|
|
else
|
|
id -un
|
|
fi
|
|
}
|
|
|
|
user_home() {
|
|
_user="$1"
|
|
if has_cmd getent; then
|
|
_home="$(getent passwd "$_user" | awk -F: '{ print $6 }')"
|
|
if [ -n "$_home" ]; then
|
|
echo "$_home"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [ "$(uname -s)" = "Darwin" ] && has_cmd dscl; then
|
|
_home="$(dscl . -read "/Users/${_user}" NFSHomeDirectory 2>/dev/null | awk '{ print $2 }')"
|
|
if [ -n "$_home" ]; then
|
|
echo "$_home"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [ "$(id -un)" = "$_user" ]; then
|
|
echo "${HOME:-}"
|
|
return 0
|
|
fi
|
|
|
|
if [ "$(uname -s)" = "Darwin" ]; then
|
|
echo "/Users/${_user}"
|
|
return 0
|
|
fi
|
|
|
|
echo "/home/${_user}"
|
|
}
|
|
|
|
as_target_user() {
|
|
if [ "${PLATFORM:-}" = "darwin" ]; then
|
|
if [ "$(id -u)" -eq "$TARGET_UID" ]; then
|
|
env HOME="$TARGET_HOME" "$@"
|
|
elif has_cmd sudo; then
|
|
sudo -u "$TARGET_USER" env HOME="$TARGET_HOME" "$@"
|
|
else
|
|
error "cannot run commands as ${TARGET_USER}; install sudo or run as ${TARGET_USER}"
|
|
fi
|
|
return
|
|
fi
|
|
|
|
_bus="unix:path=${TARGET_RUNTIME_DIR}/bus"
|
|
if [ "$(id -u)" -eq "$TARGET_UID" ]; then
|
|
env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@"
|
|
elif has_cmd sudo; then
|
|
sudo -u "$TARGET_USER" env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@"
|
|
elif has_cmd runuser; then
|
|
runuser -u "$TARGET_USER" -- env HOME="$TARGET_HOME" XDG_RUNTIME_DIR="$TARGET_RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$_bus" "$@"
|
|
else
|
|
error "cannot run user service commands as ${TARGET_USER}; install sudo or run as ${TARGET_USER}"
|
|
fi
|
|
}
|
|
|
|
detect_platform() {
|
|
case "$(uname -s)" in
|
|
Linux)
|
|
echo "linux"
|
|
;;
|
|
Darwin)
|
|
echo "darwin"
|
|
;;
|
|
*)
|
|
error "unsupported OS: $(uname -s); this installer supports Linux and macOS"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
local_gateway_endpoint() {
|
|
case "${PLATFORM:-$(detect_platform)}" in
|
|
darwin)
|
|
printf 'https://localhost:%s\n' "$LOCAL_GATEWAY_PORT"
|
|
;;
|
|
*)
|
|
printf 'https://127.0.0.1:%s\n' "$LOCAL_GATEWAY_PORT"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
linux_package_method() {
|
|
if has_cmd dpkg; then
|
|
echo "deb"
|
|
elif has_cmd rpm; then
|
|
echo "rpm"
|
|
else
|
|
error "Linux installs require either dpkg or rpm"
|
|
fi
|
|
}
|
|
|
|
set_linux_target_runtime_dir() {
|
|
if [ "$(id -u)" -eq "$TARGET_UID" ] && [ -n "${XDG_RUNTIME_DIR:-}" ]; then
|
|
TARGET_RUNTIME_DIR="$XDG_RUNTIME_DIR"
|
|
else
|
|
TARGET_RUNTIME_DIR="/run/user/${TARGET_UID}"
|
|
fi
|
|
}
|
|
|
|
check_linux_deb_platform() {
|
|
require_cmd dpkg
|
|
}
|
|
|
|
check_macos_platform() {
|
|
_arch="$(uname -m)"
|
|
|
|
case "$_arch" in
|
|
arm64|aarch64)
|
|
;;
|
|
x86_64|amd64)
|
|
error "Intel macOS is not supported because no x86_64-apple-darwin release assets are published"
|
|
;;
|
|
*)
|
|
error "no macOS release build is published for architecture: ${_arch}"
|
|
;;
|
|
esac
|
|
|
|
if ! as_target_user brew --version >/dev/null 2>&1; then
|
|
error "Homebrew is required for macOS installs; install it from https://brew.sh"
|
|
fi
|
|
}
|
|
|
|
get_deb_arch() {
|
|
_arch="$(dpkg --print-architecture)"
|
|
|
|
case "$_arch" in
|
|
amd64|arm64)
|
|
echo "$_arch"
|
|
;;
|
|
*)
|
|
error "no Debian package is published for architecture: ${_arch}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
get_rpm_arch() {
|
|
if has_cmd rpm; then
|
|
_arch="$(rpm --eval '%{_arch}' 2>/dev/null || true)"
|
|
else
|
|
_arch=""
|
|
fi
|
|
|
|
if [ -z "$_arch" ]; then
|
|
_arch="$(uname -m)"
|
|
fi
|
|
|
|
case "$_arch" in
|
|
x86_64|amd64)
|
|
echo "x86_64"
|
|
;;
|
|
aarch64|arm64)
|
|
echo "aarch64"
|
|
;;
|
|
*)
|
|
error "no RPM package is published for architecture: ${_arch}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
find_deb_asset() {
|
|
_checksums="$1"
|
|
_arch="$2"
|
|
|
|
awk -v arch="$_arch" '
|
|
$2 ~ "^\\*?openshell[-_].*[-_]" arch "\\.deb$" {
|
|
sub("^\\*", "", $2)
|
|
print $2
|
|
exit
|
|
}
|
|
' "$_checksums"
|
|
}
|
|
|
|
find_rpm_asset() {
|
|
_checksums="$1"
|
|
_arch="$2"
|
|
_package="$3"
|
|
|
|
case "$_package" in
|
|
openshell)
|
|
_dev_name="openshell-dev-${_arch}.rpm"
|
|
_fallback_re="^openshell-[0-9].*\\.${_arch}\\.rpm$"
|
|
;;
|
|
openshell-gateway)
|
|
_dev_name="openshell-gateway-dev-${_arch}.rpm"
|
|
_fallback_re="^openshell-gateway-[0-9].*\\.${_arch}\\.rpm$"
|
|
;;
|
|
openshell-prover)
|
|
_dev_name="openshell-prover-dev-${_arch}.rpm"
|
|
_fallback_re="^openshell-prover-[0-9].*\\.${_arch}\\.rpm$"
|
|
;;
|
|
*)
|
|
error "unknown RPM package selector: ${_package}"
|
|
;;
|
|
esac
|
|
|
|
awk -v dev_name="$_dev_name" -v fallback_re="$_fallback_re" '
|
|
{
|
|
name = $2
|
|
sub("^\\*", "", name)
|
|
|
|
if (name == dev_name) {
|
|
selected = name
|
|
found = 1
|
|
exit
|
|
}
|
|
|
|
if (fallback == "" && name ~ fallback_re) {
|
|
fallback = name
|
|
}
|
|
}
|
|
END {
|
|
if (found) {
|
|
print selected
|
|
} else if (fallback != "") {
|
|
print fallback
|
|
}
|
|
}
|
|
' "$_checksums"
|
|
}
|
|
|
|
verify_checksum() {
|
|
_archive="$1"
|
|
_checksums="$2"
|
|
_filename="$3"
|
|
|
|
if has_cmd sha256sum; then
|
|
_expected="$(awk -v name="$_filename" '($2 == name || $2 == "*" name) { print $1; exit }' "$_checksums")"
|
|
[ -n "$_expected" ] || error "no checksum entry found for ${_filename}"
|
|
echo "$_expected $_archive" | sha256sum -c --quiet
|
|
elif has_cmd shasum; then
|
|
_expected="$(awk -v name="$_filename" '($2 == name || $2 == "*" name) { print $1; exit }' "$_checksums")"
|
|
[ -n "$_expected" ] || error "no checksum entry found for ${_filename}"
|
|
echo "$_expected $_archive" | shasum -a 256 -c --quiet
|
|
else
|
|
error "neither 'sha256sum' nor 'shasum' found; cannot verify download integrity"
|
|
fi
|
|
}
|
|
|
|
install_deb_package() {
|
|
_deb_path="$1"
|
|
|
|
if has_cmd apt-get; then
|
|
as_root env DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
|
-o Dpkg::Options::=--force-confdef \
|
|
-o Dpkg::Options::=--force-confnew \
|
|
"$_deb_path"
|
|
elif has_cmd apt; then
|
|
as_root env DEBIAN_FRONTEND=noninteractive apt install -y \
|
|
-o Dpkg::Options::=--force-confdef \
|
|
-o Dpkg::Options::=--force-confnew \
|
|
"$_deb_path"
|
|
else
|
|
as_root dpkg --force-confdef --force-confnew -i "$_deb_path"
|
|
fi
|
|
}
|
|
|
|
install_rpm_packages() {
|
|
if has_cmd dnf; then
|
|
as_root dnf install -y "$@"
|
|
elif has_cmd yum; then
|
|
as_root yum install -y "$@"
|
|
elif has_cmd zypper; then
|
|
as_root zypper --non-interactive install --allow-unsigned-rpm "$@"
|
|
elif has_cmd rpm; then
|
|
warn "installing with rpm directly; dependencies must already be installed"
|
|
as_root rpm -Uvh --replacepkgs "$@"
|
|
else
|
|
error "'dnf', 'yum', 'zypper', or 'rpm' is required to install RPM packages"
|
|
fi
|
|
}
|
|
|
|
homebrew_formula_path() {
|
|
_tap="$1"
|
|
_formula="$2"
|
|
|
|
if ! as_target_user brew tap-info "$_tap" >/dev/null 2>&1; then
|
|
info "creating local Homebrew tap ${_tap}..."
|
|
as_target_user brew tap-new --no-git "$_tap" >/dev/null
|
|
fi
|
|
|
|
_tap_dir="$(as_target_user brew --repository "$_tap" 2>/dev/null || true)"
|
|
[ -n "$_tap_dir" ] || error "could not locate Homebrew tap ${_tap}"
|
|
|
|
_formula_dir="${_tap_dir}/Formula"
|
|
as_target_user mkdir -p "$_formula_dir"
|
|
printf '%s/%s.rb\n' "$_formula_dir" "$_formula"
|
|
}
|
|
|
|
patch_homebrew_formula() {
|
|
_formula_file="$1"
|
|
_patched_file="${_formula_file}.patched"
|
|
|
|
if grep -q 'entitlements.write <<~XML' "$_formula_file"; then
|
|
info "patching Homebrew formula for idempotent postinstall..."
|
|
sed 's/entitlements\.write <<~XML/entitlements.atomic_write <<~XML/' "$_formula_file" >"$_patched_file"
|
|
mv "$_patched_file" "$_formula_file"
|
|
fi
|
|
|
|
}
|
|
|
|
patch_prerelease_homebrew_formula_urls() {
|
|
_formula_file="$1"
|
|
[ -n "$RELEASE_ASSET_DIR" ] || return 0
|
|
|
|
for _asset in "$HOMEBREW_CLI_ASSET" "$HOMEBREW_GATEWAY_ASSET" "$HOMEBREW_DRIVER_VM_ASSET" "$HOMEBREW_PROVER_ASSET"; do
|
|
if [ ! -f "${RELEASE_ASSET_DIR}/${_asset}" ]; then
|
|
error "prerelease artifact is missing the required macOS asset: ${_asset}"
|
|
fi
|
|
done
|
|
|
|
_release_asset_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}"
|
|
_local_asset_url="file://${RELEASE_ASSET_DIR}"
|
|
_patched_file="${_formula_file}.prerelease"
|
|
sed \
|
|
-e "s#${_release_asset_url}/${HOMEBREW_CLI_ASSET}#${_local_asset_url}/${HOMEBREW_CLI_ASSET}#g" \
|
|
-e "s#${_release_asset_url}/${HOMEBREW_GATEWAY_ASSET}#${_local_asset_url}/${HOMEBREW_GATEWAY_ASSET}#g" \
|
|
-e "s#${_release_asset_url}/${HOMEBREW_DRIVER_VM_ASSET}#${_local_asset_url}/${HOMEBREW_DRIVER_VM_ASSET}#g" \
|
|
-e "s#${_release_asset_url}/${HOMEBREW_PROVER_ASSET}#${_local_asset_url}/${HOMEBREW_PROVER_ASSET}#g" \
|
|
"$_formula_file" >"$_patched_file"
|
|
mv "$_patched_file" "$_formula_file"
|
|
}
|
|
|
|
start_user_gateway() {
|
|
info "restarting openshell-gateway user service as ${TARGET_USER}..."
|
|
|
|
if ! as_target_user systemctl --user daemon-reload; then
|
|
info "could not reach the user systemd manager for ${TARGET_USER}"
|
|
info "restart the gateway later with: systemctl --user enable openshell-gateway && systemctl --user restart openshell-gateway"
|
|
info "then register it with: openshell gateway add https://127.0.0.1:17670 --local --name openshell"
|
|
return 0
|
|
fi
|
|
|
|
as_target_user systemctl --user enable openshell-gateway
|
|
as_target_user systemctl --user restart openshell-gateway
|
|
as_target_user systemctl --user is-active --quiet openshell-gateway
|
|
|
|
info "registering local gateway as ${TARGET_USER}..."
|
|
register_local_gateway
|
|
wait_for_local_gateway_listener
|
|
wait_for_local_gateway_status
|
|
}
|
|
|
|
dump_local_gateway_diagnostics() {
|
|
_lines="${OPENSHELL_INSTALL_LOG_LINES:-80}"
|
|
case "$_lines" in
|
|
"" | *[!0-9]*)
|
|
_lines=80
|
|
;;
|
|
esac
|
|
|
|
info "dumping recent local gateway diagnostics..."
|
|
case "${PLATFORM:-}" in
|
|
darwin)
|
|
dump_homebrew_gateway_diagnostics "$_lines"
|
|
;;
|
|
linux)
|
|
dump_user_service_gateway_diagnostics "$_lines"
|
|
;;
|
|
*)
|
|
info "no gateway log collector is available for platform: ${PLATFORM:-unknown}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
dump_homebrew_gateway_diagnostics() {
|
|
_lines="$1"
|
|
_brew_prefix="$(as_target_user brew --prefix 2>/dev/null || true)"
|
|
[ -n "$_brew_prefix" ] || _brew_prefix="/opt/homebrew"
|
|
|
|
info "Homebrew service status:"
|
|
as_target_user brew services info "${HOMEBREW_TAP}/${HOMEBREW_FORMULA_NAME}" >&2 || true
|
|
|
|
for _log_file in \
|
|
"${_brew_prefix}/var/log/openshell/openshell-gateway.err.log" \
|
|
"${_brew_prefix}/var/log/openshell/openshell-gateway.out.log"; do
|
|
if [ -f "$_log_file" ]; then
|
|
info "last ${_lines} lines from ${_log_file}:"
|
|
tail -n "$_lines" "$_log_file" >&2 || true
|
|
else
|
|
info "gateway log not found: ${_log_file}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
dump_user_service_gateway_diagnostics() {
|
|
_lines="$1"
|
|
|
|
if has_cmd systemctl; then
|
|
info "openshell-gateway user service status:"
|
|
as_target_user systemctl --user status openshell-gateway --no-pager >&2 || true
|
|
fi
|
|
|
|
if has_cmd journalctl; then
|
|
info "last ${_lines} lines from openshell-gateway user journal:"
|
|
as_target_user journalctl --user -u openshell-gateway --no-pager -n "$_lines" >&2 || true
|
|
else
|
|
info "journalctl not found; cannot dump openshell-gateway user journal"
|
|
fi
|
|
}
|
|
|
|
wait_for_local_gateway_listener() {
|
|
_timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}"
|
|
_elapsed=0
|
|
_last_output=""
|
|
_probe_url="$(local_gateway_endpoint)/"
|
|
_mtls_dir="${TARGET_HOME}/.config/openshell/gateways/openshell/mtls"
|
|
|
|
info "waiting for local gateway listener to become reachable..."
|
|
while [ "$_elapsed" -lt "$_timeout" ]; do
|
|
if [ ! -f "${_mtls_dir}/ca.crt" ] || [ ! -f "${_mtls_dir}/tls.crt" ] || [ ! -f "${_mtls_dir}/tls.key" ]; then
|
|
_last_output="mTLS client bundle is not ready under ${_mtls_dir}"
|
|
elif _last_output="$(as_target_user curl -sS --max-time 2 --cacert "${_mtls_dir}/ca.crt" --cert "${_mtls_dir}/tls.crt" --key "${_mtls_dir}/tls.key" -o /dev/null "$_probe_url" 2>&1)"; then
|
|
info "local gateway listener is reachable"
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
_elapsed=$((_elapsed + 1))
|
|
done
|
|
|
|
[ -z "$_last_output" ] || printf '%s\n' "$_last_output" >&2
|
|
dump_local_gateway_diagnostics
|
|
error "local gateway listener did not become reachable at ${_probe_url} within ${_timeout}s"
|
|
}
|
|
|
|
wait_for_local_gateway_status() {
|
|
_timeout="${OPENSHELL_INSTALL_GATEWAY_TIMEOUT:-30}"
|
|
_elapsed=0
|
|
_status_output=""
|
|
_register_bin="${OPENSHELL_REGISTER_BIN:-openshell}"
|
|
|
|
info "waiting for openshell status to report connected..."
|
|
while [ "$_elapsed" -lt "$_timeout" ]; do
|
|
if _status_output="$(as_target_user env NO_COLOR=1 "$_register_bin" status 2>&1)"; then
|
|
case "$_status_output" in
|
|
*"Version:"*)
|
|
info "openshell status reports connected"
|
|
return 0
|
|
;;
|
|
esac
|
|
fi
|
|
sleep 1
|
|
_elapsed=$((_elapsed + 1))
|
|
done
|
|
|
|
[ -z "$_status_output" ] || printf '%s\n' "$_status_output" >&2
|
|
dump_local_gateway_diagnostics
|
|
error "openshell status did not report connected within ${_timeout}s"
|
|
}
|
|
|
|
remove_local_gateway_registration() {
|
|
[ -n "$TARGET_HOME" ] || error "cannot resolve home directory for ${TARGET_USER}"
|
|
_config_dir="${TARGET_HOME}/.config/openshell"
|
|
|
|
# The install-dev gateway is a user service. Replace the CLI registration
|
|
# directly instead of asking `gateway destroy` to tear down Docker resources.
|
|
# shellcheck disable=SC2016
|
|
as_target_user sh -c '
|
|
config_dir=$1
|
|
rm -rf "${config_dir}/gateways/local"
|
|
mkdir -p "${config_dir}/gateways/openshell"
|
|
rm -f \
|
|
"${config_dir}/gateways/openshell/metadata.json" \
|
|
"${config_dir}/gateways/openshell/edge_token" \
|
|
"${config_dir}/gateways/openshell/cf_token" \
|
|
"${config_dir}/gateways/openshell/oidc_token.json"
|
|
active="${config_dir}/active_gateway"
|
|
active_name="$(cat "$active" 2>/dev/null || true)"
|
|
if [ "$active_name" = "local" ] || [ "$active_name" = "openshell" ]; then
|
|
rm -f "$active"
|
|
fi
|
|
' sh "$_config_dir"
|
|
}
|
|
|
|
register_local_gateway() {
|
|
_register_bin="${OPENSHELL_REGISTER_BIN:-openshell}"
|
|
_endpoint="$(local_gateway_endpoint)"
|
|
|
|
if _add_output="$(as_target_user "$_register_bin" gateway add "$_endpoint" --local --name openshell 2>&1)"; then
|
|
[ -z "$_add_output" ] || print_gateway_add_output "$_add_output"
|
|
return 0
|
|
else
|
|
_add_status=$?
|
|
fi
|
|
|
|
case "$_add_output" in
|
|
*"already exists"*)
|
|
info "local gateway already exists; removing and re-adding it..."
|
|
remove_local_gateway_registration
|
|
as_target_user "$_register_bin" gateway add "$_endpoint" --local --name openshell
|
|
;;
|
|
*)
|
|
printf '%s\n' "$_add_output" >&2
|
|
return "$_add_status"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
print_gateway_add_output() {
|
|
_endpoint="$(local_gateway_endpoint)"
|
|
printf '%s\n' "$1" | while IFS= read -r _line; do
|
|
case "$_line" in
|
|
*"Gateway is not reachable at ${_endpoint}"*) ;;
|
|
*"Verify the gateway is running and the endpoint is correct."*) ;;
|
|
*) printf '%s\n' "$_line" >&2 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
install_linux_deb() {
|
|
check_linux_deb_platform
|
|
set_linux_target_runtime_dir
|
|
|
|
_arch="$(get_deb_arch)"
|
|
_tmpdir="$(mktemp -d)"
|
|
chmod 0755 "$_tmpdir"
|
|
trap 'rm -rf "$_tmpdir"' EXIT
|
|
prepare_prerelease_assets "$_tmpdir"
|
|
info "downloading ${RELEASE_TAG} release checksums..."
|
|
download_release_asset "$RELEASE_TAG" "$CHECKSUMS_NAME" "${_tmpdir}/${CHECKSUMS_NAME}" || {
|
|
error "failed to download ${CHECKSUMS_NAME} for ${RELEASE_TAG}"
|
|
}
|
|
|
|
_deb_file="$(find_deb_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch")"
|
|
if [ -z "$_deb_file" ]; then
|
|
error "no Debian package found for architecture: ${_arch}"
|
|
fi
|
|
|
|
_deb_path="${_tmpdir}/${_deb_file}"
|
|
|
|
info "selected ${_deb_file}"
|
|
|
|
info "downloading ${_deb_file}..."
|
|
download_release_asset "$RELEASE_TAG" "$_deb_file" "$_deb_path" || {
|
|
error "failed to download ${_deb_file} for ${RELEASE_TAG}"
|
|
}
|
|
chmod 0644 "$_deb_path"
|
|
|
|
info "verifying checksum..."
|
|
verify_checksum "$_deb_path" "${_tmpdir}/${CHECKSUMS_NAME}" "$_deb_file"
|
|
|
|
info "installing ${_deb_file}..."
|
|
install_deb_package "$_deb_path"
|
|
info "installed ${APP_NAME} package from ${RELEASE_TAG}"
|
|
start_user_gateway
|
|
}
|
|
|
|
install_linux_rpm() {
|
|
require_cmd rpm
|
|
set_linux_target_runtime_dir
|
|
|
|
_arch="$(get_rpm_arch)"
|
|
_tmpdir="$(mktemp -d)"
|
|
chmod 0755 "$_tmpdir"
|
|
trap 'rm -rf "$_tmpdir"' EXIT
|
|
prepare_prerelease_assets "$_tmpdir"
|
|
info "downloading ${RELEASE_TAG} release checksums..."
|
|
download_release_asset "$RELEASE_TAG" "$CHECKSUMS_NAME" "${_tmpdir}/${CHECKSUMS_NAME}" || {
|
|
error "failed to download ${CHECKSUMS_NAME} for ${RELEASE_TAG}"
|
|
}
|
|
|
|
_rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell)"
|
|
if [ -z "$_rpm_file" ]; then
|
|
error "no openshell RPM package found for architecture: ${_arch}"
|
|
fi
|
|
|
|
_gateway_rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell-gateway)"
|
|
if [ -z "$_gateway_rpm_file" ]; then
|
|
error "no openshell-gateway RPM package found for architecture: ${_arch}"
|
|
fi
|
|
|
|
_prover_rpm_file="$(find_rpm_asset "${_tmpdir}/${CHECKSUMS_NAME}" "$_arch" openshell-prover)"
|
|
if [ -z "$_prover_rpm_file" ]; then
|
|
error "no openshell-prover RPM package found for architecture: ${_arch}"
|
|
fi
|
|
|
|
info "selected ${_rpm_file}, ${_gateway_rpm_file}, and ${_prover_rpm_file}"
|
|
|
|
for _package_file in "$_rpm_file" "$_gateway_rpm_file" "$_prover_rpm_file"; do
|
|
_package_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/${_package_file}"
|
|
_package_path="${_tmpdir}/${_package_file}"
|
|
|
|
info "downloading ${_package_file}..."
|
|
download_release_asset "$RELEASE_TAG" "$_package_file" "$_package_path" || {
|
|
error "failed to download ${_package_file} for ${RELEASE_TAG}"
|
|
}
|
|
chmod 0644 "$_package_path"
|
|
|
|
info "verifying checksum for ${_package_file}..."
|
|
verify_checksum "$_package_path" "${_tmpdir}/${CHECKSUMS_NAME}" "$_package_file"
|
|
done
|
|
|
|
info "installing ${_rpm_file}, ${_gateway_rpm_file}, and ${_prover_rpm_file}..."
|
|
install_rpm_packages \
|
|
"${_tmpdir}/${_rpm_file}" \
|
|
"${_tmpdir}/${_gateway_rpm_file}" \
|
|
"${_tmpdir}/${_prover_rpm_file}"
|
|
info "installed ${APP_NAME} RPM packages from ${RELEASE_TAG}"
|
|
start_user_gateway
|
|
}
|
|
|
|
install_macos_homebrew() {
|
|
check_macos_platform
|
|
|
|
_tmpdir="$(mktemp -d)"
|
|
chmod 0755 "$_tmpdir"
|
|
trap 'rm -rf "$_tmpdir"' EXIT
|
|
prepare_prerelease_assets "$_tmpdir"
|
|
_formula_file="${_tmpdir}/openshell.rb"
|
|
_formula_url="${GITHUB_URL}/releases/download/${RELEASE_TAG}/openshell.rb"
|
|
|
|
info "downloading Homebrew formula from ${_formula_url}..."
|
|
download_release_asset "$RELEASE_TAG" "openshell.rb" "$_formula_file" || {
|
|
error "failed to download ${_formula_url}; the selected release may not include a Homebrew formula"
|
|
}
|
|
chmod 0644 "$_formula_file"
|
|
patch_prerelease_homebrew_formula_urls "$_formula_file"
|
|
patch_homebrew_formula "$_formula_file"
|
|
|
|
_tap_formula_file="$(homebrew_formula_path "$HOMEBREW_TAP" "$HOMEBREW_FORMULA_NAME")"
|
|
info "staging Homebrew formula in tap ${HOMEBREW_TAP}..."
|
|
cp "$_formula_file" "$_tap_formula_file"
|
|
chmod 0644 "$_tap_formula_file"
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
chown "$TARGET_USER" "$_tap_formula_file" 2>/dev/null || true
|
|
fi
|
|
|
|
_formula_ref="${HOMEBREW_TAP}/${HOMEBREW_FORMULA_NAME}"
|
|
|
|
if as_target_user brew list --formula openshell >/dev/null 2>&1; then
|
|
info "reinstalling OpenShell with Homebrew..."
|
|
as_target_user brew reinstall --formula "$_formula_ref"
|
|
else
|
|
info "installing OpenShell with Homebrew..."
|
|
as_target_user brew install --formula "$_formula_ref"
|
|
fi
|
|
|
|
info "restarting OpenShell Homebrew service..."
|
|
if ! as_target_user brew services restart "$_formula_ref"; then
|
|
warn "could not restart the OpenShell Homebrew service"
|
|
info "restart it later with: brew services restart ${_formula_ref}"
|
|
info "then register it with: openshell gateway add $(local_gateway_endpoint) --local --name openshell"
|
|
return 0
|
|
fi
|
|
|
|
_brew_prefix="$(as_target_user brew --prefix 2>/dev/null || true)"
|
|
if [ -n "$_brew_prefix" ] && [ -x "${_brew_prefix}/bin/openshell" ]; then
|
|
OPENSHELL_REGISTER_BIN="${_brew_prefix}/bin/openshell"
|
|
fi
|
|
|
|
info "registering local gateway as ${TARGET_USER}..."
|
|
register_local_gateway
|
|
wait_for_local_gateway_listener
|
|
wait_for_local_gateway_status
|
|
}
|
|
|
|
main() {
|
|
if [ "$#" -gt 0 ]; then
|
|
case "$1" in
|
|
--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
error "unknown option: $1"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
require_cmd curl
|
|
PLATFORM="$(detect_platform)"
|
|
RELEASE_TAG="$(resolve_release_tag)"
|
|
|
|
TARGET_USER="$(target_user)"
|
|
TARGET_UID="$(id -u "$TARGET_USER" 2>/dev/null || true)"
|
|
[ -n "$TARGET_UID" ] || error "cannot resolve uid for ${TARGET_USER}"
|
|
TARGET_HOME="$(user_home "$TARGET_USER")"
|
|
|
|
guard_breaking_upgrade
|
|
|
|
case "$PLATFORM" in
|
|
linux)
|
|
require_linux_package_glibc
|
|
case "$(linux_package_method)" in
|
|
deb)
|
|
install_linux_deb
|
|
;;
|
|
rpm)
|
|
install_linux_rpm
|
|
;;
|
|
*)
|
|
error "unsupported Linux package method"
|
|
;;
|
|
esac
|
|
;;
|
|
darwin)
|
|
install_macos_homebrew
|
|
;;
|
|
*)
|
|
error "unsupported platform: ${PLATFORM}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
if [ "${OPENSHELL_INSTALL_SH_TEST:-0}" != "1" ]; then
|
|
main "$@"
|
|
fi
|