ontoref-code/install/install.sh.tmpl
2026-07-10 01:44:59 +01:00

260 lines
11 KiB
Bash

#!/bin/sh
# ontoref installer — fetch a per-platform OCI bundle and install the CLI + daemon.
#
# Generated by `ore workflow generate` from install/install.sh.tmpl. The registry
# and version values below come from the distribution catalog in
# .ontoref/ontology/workflow.ncl — do not edit this file; edit the .tmpl and
# regenerate.
#
# Usage:
# curl -fsSL @@INSTALLER_URL@@ | sh
# curl -fsSL @@INSTALLER_URL@@ | sh -s -- --prefix ~/.local --version 0.1.6
# sh install.sh --uninstall [--purge]
#
# The daemon is an OPTIONAL accelerator (ADR-029). This installs a CLI + data
# layer that work without it; run the daemon via the container image if wanted:
# docker run --rm -p 7891:7891 @@REGISTRY@@/@@IMAGE_REPO@@:@@DEFAULT_VERSION@@
set -eu
REGISTRY="@@REGISTRY@@"
BUNDLE_REPO="@@BUNDLE_REPO@@"
IMAGE_REPO="@@IMAGE_REPO@@"
DEFAULT_VERSION="@@DEFAULT_VERSION@@"
PREFIX="${HOME}/.local"
DATA_DIR="${HOME}/.local/share/ontoref"
CONFIG_DIR="${HOME}/.config/ontoref"
VERSION="${DEFAULT_VERSION}"
DO_UNINSTALL=0
DO_PURGE=0
log() { printf ' %s\n' "$*"; }
ok() { printf '\033[32m✓\033[0m %s\n' "$*"; }
warn() { printf '\033[33mwarn\033[0m %s\n' "$*" >&2; }
die() { printf '\033[31merror\033[0m %s\n' "$*" >&2; exit 1; }
usage() {
cat <<EOF
ontoref installer
--prefix DIR install binaries under DIR/bin (default: ${HOME}/.local)
--data-dir DIR install the data layer in DIR (default: ${HOME}/.local/share/ontoref)
--version VER bundle version tag (default: ${DEFAULT_VERSION})
--uninstall remove installed binaries + wrappers (keeps config/data)
--purge with --uninstall, also remove data + config
-h, --help show this help
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--prefix) PREFIX="$2"; shift 2 ;;
--data-dir) DATA_DIR="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
--uninstall) DO_UNINSTALL=1; shift ;;
--purge) DO_PURGE=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1 (try --help)" ;;
esac
done
BIN_DIR="${PREFIX}/bin"
# ── Uninstall ─────────────────────────────────────────────────────────────────
if [ "$DO_UNINSTALL" -eq 1 ]; then
for b in ontoref ontoref-daemon ontoref-daemon.bin ontoref-tier nickel; do
[ -e "${BIN_DIR}/${b}" ] && rm -f "${BIN_DIR}/${b}" && log "removed ${BIN_DIR}/${b}"
done
if [ "$DO_PURGE" -eq 1 ]; then
rm -rf "$DATA_DIR" && log "removed ${DATA_DIR}"
rm -rf "$CONFIG_DIR" && log "removed ${CONFIG_DIR}"
else
log "kept data (${DATA_DIR}) and config (${CONFIG_DIR}) — use --purge to remove"
fi
ok "uninstalled"
exit 0
fi
# ── Platform detection ────────────────────────────────────────────────────────
os="$(uname -s)"
case "$os" in
Linux) ;;
Darwin)
die "no macOS bundle is published (Linux only). Options:
• build from source: cargo build --release -p ontoref-daemon && nu install/install.nu
• run the daemon in a container:
docker run --rm -p 7891:7891 ${REGISTRY}/${IMAGE_REPO}:${DEFAULT_VERSION}" ;;
*) die "unsupported OS: ${os} (Linux only)" ;;
esac
machine="$(uname -m)"
case "$machine" in
x86_64|amd64) arch="amd64" ;;
aarch64|arm64) arch="arm64" ;;
*) die "unsupported architecture: ${machine}" ;;
esac
REF="${REGISTRY}/${BUNDLE_REPO}:${VERSION}-linux-${arch}"
TARBALL_NAME="ontoref-linux-${arch}.tar.gz"
log "platform: linux/${arch} ref: ${REF}"
need() { command -v "$1" >/dev/null 2>&1; }
need tar || die "tar is required"
need curl || die "curl is required"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT INT TERM
# ── Fetch the bundle: prefer oras, fall back to the OCI Distribution API ──────
fetch_with_oras() {
log "fetching via oras"
( cd "$WORK" && oras pull "$REF" ) >/dev/null 2>&1
}
# Minimal anonymous-pull client over the OCI Distribution API (curl + grep/sed).
# Handles the optional Bearer-token dance for public namespaces.
registry_token() {
_repo="$1"
_chal="$(curl -fsSL -o /dev/null -D - "https://${REGISTRY}/v2/" 2>/dev/null \
| tr -d '\r' | grep -i '^www-authenticate:' || true)"
[ -z "$_chal" ] && { echo ""; return 0; }
_realm="$(printf '%s' "$_chal" | sed -n 's/.*realm="\([^"]*\)".*/\1/p')"
_service="$(printf '%s' "$_chal" | sed -n 's/.*service="\([^"]*\)".*/\1/p')"
[ -z "$_realm" ] && { echo ""; return 0; }
curl -fsSL "${_realm}?service=${_service}&scope=repository:${_repo}:pull" 2>/dev/null \
| grep -o '"token":"[^"]*"' | head -1 | sed 's/"token":"\([^"]*\)"/\1/'
}
fetch_with_curl() {
log "oras not found — fetching via OCI Distribution API (curl)"
_tag="${VERSION}-linux-${arch}"
_tok="$(registry_token "$BUNDLE_REPO")"
_auth=""
[ -n "$_tok" ] && _auth="Authorization: Bearer ${_tok}"
_man="${WORK}/manifest.json"
curl -fsSL ${_auth:+-H "$_auth"} \
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
-H "Accept: application/vnd.oci.artifact.manifest.v1+json" \
"https://${REGISTRY}/v2/${BUNDLE_REPO}/manifests/${_tag}" -o "$_man" \
|| die "manifest fetch failed for ${REF}"
# Pull each layer blob, naming it by its title annotation when present.
# Layers appear as {"mediaType":..,"digest":"sha256:..","annotations":{"org.opencontainers.image.title":"name"}}
printf '%s' "$(cat "$_man")" | tr '}' '}\n' | grep '"digest"' | while IFS= read -r line; do
_dig="$(printf '%s' "$line" | grep -o 'sha256:[0-9a-f]\{64\}' | head -1)"
[ -z "$_dig" ] && continue
_title="$(printf '%s' "$line" | sed -n 's/.*"org.opencontainers.image.title":"\([^"]*\)".*/\1/p')"
[ -z "$_title" ] && _title="$_dig"
curl -fsSL ${_auth:+-H "$_auth"} \
"https://${REGISTRY}/v2/${BUNDLE_REPO}/blobs/${_dig}" -o "${WORK}/${_title}" \
|| die "blob fetch failed: ${_dig}"
done
}
if need oras; then
fetch_with_oras
else
fetch_with_curl
fi
TARBALL="${WORK}/${TARBALL_NAME}"
[ -f "$TARBALL" ] || die "bundle ${TARBALL_NAME} not present after fetch"
# ── Verify integrity ──────────────────────────────────────────────────────────
if [ -f "${TARBALL}.sha256" ]; then
expected="$(awk '{print $1}' "${TARBALL}.sha256")"
if need sha256sum; then
actual="$(sha256sum "$TARBALL" | awk '{print $1}')"
elif need shasum; then
actual="$(shasum -a 256 "$TARBALL" | awk '{print $1}')"
else
actual=""
warn "no sha256 tool — skipping checksum verification"
fi
if [ -n "$actual" ]; then
[ "$actual" = "$expected" ] || die "checksum mismatch: expected ${expected}, got ${actual}"
ok "checksum verified"
fi
else
warn "no .sha256 sidecar — skipping checksum verification"
fi
# Optional signature verification (best-effort; needs the public key out of band).
if need cosign && [ -f "${WORK}/cosign.pub" ]; then
cosign verify-blob --key "${WORK}/cosign.pub" --signature "${TARBALL}.sig" "$TARBALL" \
>/dev/null 2>&1 && ok "cosign signature verified" || warn "cosign verification failed (continuing)"
fi
# ── Extract ───────────────────────────────────────────────────────────────────
STAGE="${WORK}/stage"
mkdir -p "$STAGE"
tar -xzf "$TARBALL" -C "$STAGE"
[ -f "${STAGE}/bin/ontoref-daemon.bin" ] || die "bundle missing bin/ontoref-daemon.bin"
# ── Upgrade-safe: stop a running daemon before replacing its binary ───────────
if need pkill; then
pkill -x ontoref-daemon.bin 2>/dev/null || true
pkill -x ontoref-daemon 2>/dev/null || true
fi
# ── Place binaries + wrappers ─────────────────────────────────────────────────
mkdir -p "$BIN_DIR"
install -m 0755 "${STAGE}/bin/ontoref-daemon.bin" "${BIN_DIR}/ontoref-daemon.bin"
install -m 0755 "${STAGE}/bin/ontoref-daemon-boot" "${BIN_DIR}/ontoref-daemon"
install -m 0755 "${STAGE}/bin/nickel" "${BIN_DIR}/nickel"
[ -f "${STAGE}/bin/ontoref-tier" ] && install -m 0755 "${STAGE}/bin/ontoref-tier" "${BIN_DIR}/ontoref-tier"
# Bake the data dir as ONTOREF_ROOT so the wrapper is self-contained (mirrors
# install.nu §3): replace the placeholder default with the resolved DATA_DIR.
sed "s|ONTOREF_ROOT=\"\${ONTOREF_ROOT:-ontoref}\"|ONTOREF_ROOT=\"\${ONTOREF_ROOT:-${DATA_DIR}}\"|" \
"${STAGE}/bin/ontoref-global" > "${BIN_DIR}/ontoref"
chmod 0755 "${BIN_DIR}/ontoref"
ok "binaries → ${BIN_DIR}"
# ── Place the data layer ──────────────────────────────────────────────────────
mkdir -p "$DATA_DIR"
# cp -a preserves the tree; we replace reflection/ontology/etc wholesale so an
# upgrade never leaves stale files behind in those subtrees.
for sub in reflection ontology domains templates public install; do
if [ -d "${STAGE}/data/${sub}" ]; then
rm -rf "${DATA_DIR:?}/${sub}"
cp -a "${STAGE}/data/${sub}" "${DATA_DIR}/${sub}"
fi
done
ok "data → ${DATA_DIR}"
# ── Seed the config skeleton (idempotent — never clobber existing config) ─────
mkdir -p "$CONFIG_DIR"
if [ -f "${STAGE}/config/config.ncl" ]; then
cp "${STAGE}/config/config.ncl" "${CONFIG_DIR}/config.ncl.example"
[ -f "${CONFIG_DIR}/config.ncl" ] || cp "${STAGE}/config/config.ncl" "${CONFIG_DIR}/config.ncl"
fi
for f in projects.ncl remote-projects.ncl streams.json; do
[ -f "${STAGE}/config/${f}" ] && [ ! -f "${CONFIG_DIR}/${f}" ] && cp "${STAGE}/config/${f}" "${CONFIG_DIR}/${f}"
done
if [ -d "${STAGE}/config/schemas" ]; then
mkdir -p "${CONFIG_DIR}/schemas"
for f in "${STAGE}/config/schemas/"*; do
[ -e "$f" ] || continue
bn="$(basename "$f")"
[ -f "${CONFIG_DIR}/schemas/${bn}" ] || cp "$f" "${CONFIG_DIR}/schemas/${bn}"
done
fi
ok "config → ${CONFIG_DIR}"
# ── Runtime dependency: nushell (required by the CLI wrapper) ─────────────────
if ! need nu; then
warn "Nushell (nu) >= 0.110 is required by the 'ontoref' CLI but was not found."
warn "Install it: https://www.nushell.sh/book/installation.html (the daemon itself does not need it)"
fi
case ":${PATH}:" in
*":${BIN_DIR}:"*) ;;
*) warn "${BIN_DIR} is not on PATH — add it: export PATH=\"${BIN_DIR}:\$PATH\"" ;;
esac
printf '\n'
ok "ontoref ${VERSION} installed"
log "next: ontoref describe project (run inside an ontoref-onboarded project)"
log " ontoref-daemon (optional accelerator — caches NCL export)"