261 lines
11 KiB
Cheetah
261 lines
11 KiB
Cheetah
|
|
#!/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)"
|