Files
2026-07-13 13:12:33 +08:00

283 lines
8.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# install.sh - OpenSquilla release installer for Linux and macOS.
#
# This script is safe to pipe from the public install URL. It installs uv if
# needed, installs a release wheel with uv tool, then prints the explicit next
# steps. It does not run onboarding or start the gateway.
set -euo pipefail
default_version="v0.5.0rc3"
repo_slug="${OPENSQUILLA_REPOSITORY:-opensquilla/opensquilla}"
python_version="${OPENSQUILLA_PYTHON_VERSION:-3.12}"
original_path="${PATH:-}"
cli_version=""
cli_profile=""
cli_extras=""
usage() {
cat <<HELP
Usage: bash install.sh [--version v0.5.0rc3|latest] [--profile recommended|core] [--extras name[,name]]
Environment equivalents:
OPENSQUILLA_VERSION=v0.5.0rc3
OPENSQUILLA_INSTALL_PROFILE=recommended|core
OPENSQUILLA_INSTALL_EXTRAS=matrix
OPENSQUILLA_INSTALL_DRY_RUN=1
HELP
}
while [[ $# -gt 0 ]]; do
case "$1" in
--version)
cli_version="${2:?install.sh: --version requires a value}"
shift 2
;;
--version=*)
cli_version="${1#*=}"
shift
;;
--profile)
cli_profile="${2:?install.sh: --profile requires a value}"
shift 2
;;
--profile=*)
cli_profile="${1#*=}"
shift
;;
--extras)
cli_extras="${2:?install.sh: --extras requires a value}"
shift 2
;;
--extras=*)
cli_extras="${1#*=}"
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "install.sh: unknown argument '$1'." >&2
usage >&2
exit 1
;;
esac
done
release_selector="${cli_version:-${OPENSQUILLA_VERSION:-${default_version}}}"
profile="${cli_profile:-${OPENSQUILLA_INSTALL_PROFILE:-recommended}}"
dry_run="${OPENSQUILLA_INSTALL_DRY_RUN:-0}"
is_release_version() {
[[ "$1" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+((a|b|rc)[0-9]+)?$ ]]
}
valid_extras=" matrix matrix-e2e document-extras "
extras_csv="${OPENSQUILLA_INSTALL_EXTRAS:-}"
if [[ -n "${cli_extras}" ]]; then
extras_csv="${extras_csv}${extras_csv:+,}${cli_extras}"
fi
extras_csv="${extras_csv// /,}"
raw_extras=()
if [[ -n "${extras_csv}" ]]; then
IFS=',' read -r -a raw_extras <<< "${extras_csv}"
fi
install_extras=()
for extra in ${raw_extras[@]+"${raw_extras[@]}"}; do
[[ -n "${extra}" ]] || continue
if [[ "${valid_extras}" != *" ${extra} "* ]]; then
echo "install.sh: unsupported extra '${extra}'." >&2
echo "install.sh: supported extras:${valid_extras}" >&2
exit 1
fi
duplicate=0
for existing in ${install_extras[@]+"${install_extras[@]}"}; do
if [[ "${existing}" == "${extra}" ]]; then
duplicate=1
break
fi
done
if [[ "${duplicate}" -eq 0 ]]; then
install_extras+=("${extra}")
fi
done
case "${profile}" in
core|minimal)
profile="core"
target_extras=()
;;
recommended)
target_extras=(recommended)
;;
*)
echo "install.sh: unsupported OPENSQUILLA_INSTALL_PROFILE='${profile}'." >&2
echo "install.sh: supported profiles: core, recommended" >&2
exit 1
;;
esac
if (( ${#install_extras[@]} > 0 )); then
target_extras+=("${install_extras[@]}")
fi
if (( ${#target_extras[@]} > 0 )); then
package_name="opensquilla[$(IFS=,; echo "${target_extras[*]}")]"
else
package_name="opensquilla"
fi
if [[ "${release_selector}" != "latest" && "${release_selector}" != "stable" ]] && ! is_release_version "${release_selector}"; then
echo "install.sh: unsupported OPENSQUILLA_VERSION='${release_selector}'." >&2
echo "install.sh: the release installer only supports latest, stable, or release versions like v0.5.0rc3." >&2
echo "install.sh: use git clone plus scripts/install_source.sh for main, dev, branch, or source installs." >&2
exit 1
fi
case "${release_selector}" in
latest|stable)
latest_tag=""
if command -v curl >/dev/null 2>&1; then
latest_tag="$(
curl -fsSL "https://api.github.com/repos/${repo_slug}/releases/latest" \
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
| head -n 1
)"
elif command -v wget >/dev/null 2>&1; then
latest_tag="$(
wget -qO- "https://api.github.com/repos/${repo_slug}/releases/latest" \
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
| head -n 1
)"
else
echo "install.sh: curl or wget is required to resolve the latest release." >&2
exit 1
fi
if ! is_release_version "${latest_tag}"; then
echo "install.sh: failed to resolve latest release tag for ${repo_slug}." >&2
exit 1
fi
release_version="${latest_tag#v}"
wheel_url="https://github.com/${repo_slug}/releases/download/${latest_tag}/opensquilla-${release_version}-py3-none-any.whl"
display_version="${latest_tag}"
;;
v*)
release_version="${release_selector#v}"
wheel_url="https://github.com/${repo_slug}/releases/download/${release_selector}/opensquilla-${release_version}-py3-none-any.whl"
display_version="${release_selector}"
;;
*)
release_version="${release_selector}"
release_tag="v${release_version}"
wheel_url="https://github.com/${repo_slug}/releases/download/${release_tag}/opensquilla-${release_version}-py3-none-any.whl"
display_version="${release_tag}"
;;
esac
install_spec="${package_name} @ ${wheel_url}"
install_uv() {
if command -v curl >/dev/null 2>&1; then
curl -LsSf https://astral.sh/uv/install.sh | sh
elif command -v wget >/dev/null 2>&1; then
wget -qO- https://astral.sh/uv/install.sh | sh
else
echo "install.sh: curl or wget is required to install uv." >&2
exit 1
fi
}
resolve_uv() {
if command -v uv >/dev/null 2>&1; then
command -v uv
return 0
fi
if [[ -f "${HOME}/.local/bin/env" ]]; then
# shellcheck disable=SC1091
. "${HOME}/.local/bin/env"
fi
export PATH="${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH:-}"
if command -v uv >/dev/null 2>&1; then
command -v uv
return 0
fi
return 1
}
if [[ "${dry_run}" == "1" ]]; then
echo "install.sh: dry-run - would install OpenSquilla ${display_version}"
echo "install.sh: dry-run - would run: uv tool install --python ${python_version} --force --reinstall-package opensquilla \"${install_spec}\""
exit 0
fi
uv_bin="$(resolve_uv || true)"
if [[ -z "${uv_bin}" ]]; then
echo "install.sh: uv not found; installing uv first."
install_uv
uv_bin="$(resolve_uv || true)"
fi
if [[ -z "${uv_bin}" ]]; then
echo "install.sh: uv was not found after installation." >&2
echo "install.sh: restart your terminal or run '. \"\$HOME/.local/bin/env\"', then retry." >&2
exit 1
fi
echo "install.sh: installing OpenSquilla ${display_version} (${profile})"
"${uv_bin}" tool install --python "${python_version}" --force --reinstall-package opensquilla "${install_spec}"
tool_bin_dir="$("${uv_bin}" tool dir --bin 2>/dev/null || true)"
# Write an install receipt to aid `opensquilla uninstall`. Best-effort: never
# fail the install if this cannot be written.
write_install_receipt() {
home="${OPENSQUILLA_STATE_DIR:-${HOME}/.opensquilla}"
receipt="${home}/install-receipt.json"
tool_dir="$("${uv_bin}" tool dir 2>/dev/null || true)"
installed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
mkdir -p "${home}" 2>/dev/null || return 0
cat >"${receipt}" 2>/dev/null <<RECEIPT || return 0
{
"version": 1,
"install_method": "uv-tool",
"installed_at": "${installed_at}",
"entrypoints": ["${tool_bin_dir}/opensquilla", "${tool_bin_dir}/gateway"],
"owned_paths": ["${tool_dir}/opensquilla", "${tool_bin_dir}/opensquilla", "${tool_bin_dir}/gateway"],
"data_root": "${home}"
}
RECEIPT
chmod 600 "${receipt}" 2>/dev/null || true
}
write_install_receipt || true
cat <<DONE
----------------------------------------------------------------------------
OpenSquilla installed from ${display_version}.
Next steps:
opensquilla onboard
opensquilla gateway run
Default gateway bind: 127.0.0.1:18791 (loopback only).
Do not expose the gateway on 0.0.0.0 unless it is behind a trusted reverse
proxy or VPN.
----------------------------------------------------------------------------
DONE
if [[ -n "${tool_bin_dir}" && ":${original_path}:" != *":${tool_bin_dir}:"* ]]; then
cat <<PATHNOTE
PATH note:
Your current shell may not find 'opensquilla' until PATH is refreshed.
Run one of these, then retry the next steps:
. "\$HOME/.local/bin/env"
# or open a new terminal
PATHNOTE
fi