315 lines
11 KiB
Bash
Executable File
315 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# install_source.sh - user-local OpenSquilla installer (no sudo).
|
|
#
|
|
# Installer contract:
|
|
# - installs into a user-owned prefix (never /usr/local, /opt, or admin paths)
|
|
# - prefers uv tool install; falls back to pip --user; errors clearly if neither exists
|
|
# - defaults to the "recommended" runtime profile (memory + bundled v4 router)
|
|
# and allows `OPENSQUILLA_INSTALL_PROFILE=core` to opt back down
|
|
# - prints a post-install banner documenting the default bind
|
|
# (127.0.0.1:18791) and the explicit opt-in required to expose the gateway
|
|
# on the network (--listen 0.0.0.0 or OPENSQUILLA_LISTEN=0.0.0.0)
|
|
# - adds an extra WARNING when the operator requested network exposure at
|
|
# install time via OPENSQUILLA_LISTEN=0.0.0.0
|
|
#
|
|
# Dry-run: export OPENSQUILLA_INSTALL_DRY_RUN=1 to print the install plan + banner
|
|
# without touching the system.
|
|
|
|
set -euo pipefail
|
|
|
|
cli_profile=""
|
|
cli_extras=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--profile)
|
|
cli_profile="${2:?install_source.sh: --profile requires a value}"
|
|
shift 2
|
|
;;
|
|
--profile=*)
|
|
cli_profile="${1#*=}"
|
|
shift
|
|
;;
|
|
--extras)
|
|
cli_extras="${2:?install_source.sh: --extras requires a value}"
|
|
shift 2
|
|
;;
|
|
--extras=*)
|
|
cli_extras="${1#*=}"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
cat <<HELP
|
|
Usage: bash scripts/install_source.sh [--profile recommended|core] [--extras name[,name]]
|
|
|
|
Environment equivalents:
|
|
OPENSQUILLA_INSTALL_PROFILE=recommended|core
|
|
OPENSQUILLA_INSTALL_EXTRAS=matrix
|
|
OPENSQUILLA_INSTALL_DRY_RUN=1
|
|
HELP
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "install_source.sh: unknown argument '$1'." >&2
|
|
echo "install_source.sh: run 'bash scripts/install_source.sh --help' for usage." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# --- prefix resolution ------------------------------------------------------
|
|
|
|
if [[ -n "${OPENSQUILLA_PREFIX:-}" ]]; then
|
|
prefix="${OPENSQUILLA_PREFIX}"
|
|
elif [[ -n "${XDG_DATA_HOME:-}" ]]; then
|
|
prefix="${XDG_DATA_HOME}/opensquilla"
|
|
else
|
|
prefix="${HOME}/.local"
|
|
fi
|
|
|
|
dry_run="${OPENSQUILLA_INSTALL_DRY_RUN:-0}"
|
|
profile="${cli_profile:-${OPENSQUILLA_INSTALL_PROFILE:-recommended}}"
|
|
|
|
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=()
|
|
if (( ${#raw_extras[@]} > 0 )); then
|
|
for extra in "${raw_extras[@]}"; do
|
|
[[ -n "${extra}" ]] || continue
|
|
if [[ "${valid_extras}" != *" ${extra} "* ]]; then
|
|
echo "install_source.sh: unsupported extra '${extra}'." >&2
|
|
echo "install_source.sh: supported extras:${valid_extras}" >&2
|
|
exit 1
|
|
fi
|
|
duplicate=0
|
|
if (( ${#install_extras[@]} > 0 )); then
|
|
for existing in "${install_extras[@]}"; do
|
|
if [[ "${existing}" == "${extra}" ]]; then
|
|
duplicate=1
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
if [[ "${duplicate}" -eq 0 ]]; then
|
|
install_extras+=("${extra}")
|
|
fi
|
|
done
|
|
fi
|
|
|
|
case "${profile}" in
|
|
core|minimal)
|
|
profile="core"
|
|
target_extras=()
|
|
;;
|
|
recommended)
|
|
target_extras=(recommended)
|
|
;;
|
|
*)
|
|
echo "install_source.sh: unsupported OPENSQUILLA_INSTALL_PROFILE='${profile}'." >&2
|
|
echo "install_source.sh: supported profiles: core, recommended" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if (( ${#install_extras[@]} > 0 )); then
|
|
target_extras+=("${install_extras[@]}")
|
|
fi
|
|
if (( ${#target_extras[@]} > 0 )); then
|
|
joined_extras="$(IFS=,; echo "${target_extras[*]}")"
|
|
install_target=".[${joined_extras}]"
|
|
else
|
|
install_target="."
|
|
fi
|
|
|
|
check_squilla_router_assets() {
|
|
local mode="${1:-strict}"
|
|
if [[ "${profile}" != "recommended" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local model_root="src/opensquilla/squilla_router/models"
|
|
local pointer_line="version https://git-lfs.github.com/spec/v1"
|
|
local required=(
|
|
"${model_root}/v4.2_phase3_inference/lgbm_main.bin"
|
|
"${model_root}/v4.2_phase3_inference/router.runtime.yaml"
|
|
"${model_root}/v4.2_phase3_inference/mlp/model.onnx"
|
|
"${model_root}/v4.2_phase3_inference/features/tfidf.pkl"
|
|
"${model_root}/v4.2_phase3_inference/bge_onnx/model.onnx"
|
|
)
|
|
local missing=()
|
|
local pointers=()
|
|
local path=""
|
|
for path in "${required[@]}"; do
|
|
if [[ ! -f "${path}" ]]; then
|
|
missing+=("${path}")
|
|
continue
|
|
fi
|
|
if LC_ALL=C grep -q -m 1 -F -x "${pointer_line}" "${path}" 2>/dev/null; then
|
|
pointers+=("${path}")
|
|
fi
|
|
done
|
|
if (( ${#missing[@]} > 0 || ${#pointers[@]} > 0 )); then
|
|
if [[ "${mode}" == "warn" ]]; then
|
|
echo "install_source.sh: dry-run note — real recommended install would fail until bundled squilla-router v4 assets are available in this checkout." >&2
|
|
else
|
|
echo "install_source.sh: bundled squilla-router v4 assets are unavailable in this checkout." >&2
|
|
fi
|
|
if (( ${#missing[@]} > 0 )); then
|
|
echo "install_source.sh: missing assets: ${missing[*]}" >&2
|
|
fi
|
|
if (( ${#pointers[@]} > 0 )); then
|
|
echo "install_source.sh: Git LFS pointer files detected: ${pointers[*]}" >&2
|
|
fi
|
|
echo 'install_source.sh: run `git lfs install` once, then:' >&2
|
|
echo 'install_source.sh: git lfs pull --include="src/opensquilla/squilla_router/models/**"' >&2
|
|
echo 'install_source.sh: or retry with OPENSQUILLA_INSTALL_PROFILE=core for the minimal runtime.' >&2
|
|
if [[ "${mode}" == "warn" ]]; then
|
|
return 0
|
|
fi
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# --- installer selection ----------------------------------------------------
|
|
|
|
installer=""
|
|
install_args=()
|
|
if command -v uv >/dev/null 2>&1; then
|
|
installer="uv"
|
|
install_args=(uv tool install --python 3.12 --force --reinstall-package opensquilla "${install_target}")
|
|
elif command -v python3 >/dev/null 2>&1 \
|
|
&& python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 12) else 1)'; then
|
|
installer="pip"
|
|
install_args=(python3 -m pip install --user "${install_target}")
|
|
else
|
|
# No uv, and the ambient python3 is missing or older than 3.12. Do NOT
|
|
# silently pip-install onto an unsupported interpreter: that leaves a
|
|
# broken `opensquilla` on PATH and makes coding mode fall back to manual
|
|
# edits. Fail loud and point at uv, which provisions its own 3.12.
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
_ambient_py="$(python3 -V 2>&1)"
|
|
else
|
|
_ambient_py="none"
|
|
fi
|
|
echo "install_source.sh: cannot install - uv not found and python3 (${_ambient_py}) is older than 3.12." >&2
|
|
echo "install_source.sh: OpenSquilla requires Python >= 3.12 (pyproject 'requires-python')." >&2
|
|
echo "install_source.sh: easiest fix - install uv; it brings its own 3.12, no system Python needed:" >&2
|
|
echo "install_source.sh: curl -LsSf https://astral.sh/uv/install.sh | sh" >&2
|
|
echo "install_source.sh: then re-run: bash scripts/install_source.sh" >&2
|
|
exit 1
|
|
fi
|
|
install_cmd="${install_args[*]}"
|
|
|
|
# --- banner -----------------------------------------------------------------
|
|
|
|
print_banner() {
|
|
cat <<BANNER
|
|
----------------------------------------------------------------------------
|
|
OpenSquilla installed via ${installer} -> ${prefix} (profile: ${profile})
|
|
Extras: $(if (( ${#install_extras[@]} > 0 )); then IFS=,; echo "${install_extras[*]}"; else echo "none"; fi)
|
|
|
|
Default gateway bind: 127.0.0.1:18791 (loopback only)
|
|
Network exposure is opt-in only. To expose the gateway on the network you
|
|
must use one of:
|
|
- CLI flag: opensquilla gateway run --listen 0.0.0.0
|
|
- Env var: OPENSQUILLA_LISTEN=0.0.0.0 opensquilla gateway run
|
|
|
|
Reminder: only expose 0.0.0.0 behind a trusted reverse proxy or VPN. The
|
|
gateway's first-class auth assumes loopback-scope by default.
|
|
----------------------------------------------------------------------------
|
|
BANNER
|
|
}
|
|
|
|
print_listen_warning() {
|
|
cat <<WARNING
|
|
WARNING: you have selected network-exposed default - ensure you
|
|
understand the blast radius. The gateway will bind to 0.0.0.0 and be
|
|
reachable from every interface on this host.
|
|
WARNING
|
|
}
|
|
|
|
verify_install() {
|
|
# Catch a broken/partial install now, not mid-task. A non-runnable
|
|
# code-task is exactly what makes coding mode silently degrade.
|
|
# Prefer the JUST-installed binary over any stale `opensquilla` earlier
|
|
# on PATH (uv tool / pip --user land outside the default PATH).
|
|
local bin=""
|
|
if [[ "${installer}" == "uv" ]]; then
|
|
local uv_bin
|
|
uv_bin="$(uv tool dir --bin 2>/dev/null || true)"
|
|
[[ -n "${uv_bin}" && -x "${uv_bin}/opensquilla" ]] && bin="${uv_bin}/opensquilla"
|
|
fi
|
|
if [[ -z "${bin}" && -x "${HOME}/.local/bin/opensquilla" ]]; then
|
|
bin="${HOME}/.local/bin/opensquilla"
|
|
fi
|
|
if [[ -z "${bin}" ]] && command -v opensquilla >/dev/null 2>&1; then
|
|
bin="opensquilla"
|
|
fi
|
|
# Coding mode requires `opensquilla code-task`, so verify THAT, not just --version.
|
|
if [[ -n "${bin}" ]] && "${bin}" code-task --help >/dev/null 2>&1; then
|
|
echo "install_source.sh: verified - 'opensquilla code-task' is runnable"
|
|
else
|
|
echo "install_source.sh: WARNING - 'opensquilla code-task' is not runnable yet." >&2
|
|
echo "install_source.sh: run 'uv tool update-shell' (or open a new shell), then: opensquilla code-task --help" >&2
|
|
fi
|
|
command -v git >/dev/null 2>&1 || echo "install_source.sh: WARNING - 'git' not found; code-task cannot clone repositories without it." >&2
|
|
command -v node >/dev/null 2>&1 || echo "install_source.sh: note - 'node' not found (only needed for code-task build-mode apps)." >&2
|
|
}
|
|
|
|
if [[ "${dry_run}" = "1" ]]; then
|
|
echo "install_source.sh: dry-run — would run: ${install_cmd}"
|
|
echo "install_source.sh: dry-run — prefix: ${prefix}"
|
|
check_squilla_router_assets warn
|
|
print_banner
|
|
if [[ "${OPENSQUILLA_LISTEN:-}" = "0.0.0.0" ]]; then
|
|
print_listen_warning
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# --- execute ---------------------------------------------------------------
|
|
|
|
check_squilla_router_assets
|
|
|
|
echo "install_source.sh: installing via ${installer} into prefix ${prefix}"
|
|
echo "install_source.sh: running: ${install_cmd}"
|
|
"${install_args[@]}"
|
|
|
|
verify_install
|
|
|
|
# Write an install receipt to aid `opensquilla uninstall`. Best-effort.
|
|
write_install_receipt() {
|
|
home="${OPENSQUILLA_STATE_DIR:-${HOME}/.opensquilla}"
|
|
receipt="${home}/install-receipt.json"
|
|
installed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
|
|
if [[ "${installer}" == "uv" ]]; then
|
|
method="uv-tool"
|
|
else
|
|
method="pip"
|
|
fi
|
|
mkdir -p "${home}" 2>/dev/null || return 0
|
|
cat >"${receipt}" 2>/dev/null <<RECEIPT || return 0
|
|
{
|
|
"version": 1,
|
|
"install_method": "${method}",
|
|
"installed_at": "${installed_at}",
|
|
"entrypoints": [],
|
|
"owned_paths": [],
|
|
"data_root": "${home}"
|
|
}
|
|
RECEIPT
|
|
chmod 600 "${receipt}" 2>/dev/null || true
|
|
}
|
|
write_install_receipt || true
|
|
|
|
print_banner
|
|
if [[ "${OPENSQUILLA_LISTEN:-}" = "0.0.0.0" ]]; then
|
|
print_listen_warning
|
|
fi
|