ontoref/install/ontoref-daemon-boot

124 lines
4.5 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env bash
# install/ontoref-daemon-boot — NCL pipe bootstrap for ontoref-daemon.
#
# Implements ADR-004: config is validated by nickel export and piped directly
# to the daemon via stdin. No intermediate file is written to disk.
#
# Stages:
# 1. nickel export config.ncl → validated JSON (structural, no secrets)
# 2. SOPS decrypt (optional) → merge secrets into JSON stream
# 3. ontoref-daemon --config-stdin → reads JSON from stdin, closes stdin
#
# Usage:
# ontoref-daemon-boot [options]
# ontoref-daemon-boot --dry-run # print composed JSON to stdout, don't start daemon
# ontoref-daemon-boot --sops <file> # enable SOPS stage with encrypted secrets file
# ontoref-daemon-boot --vault <path> # enable Vault stage with kv path
#
# Environment:
# ONTOREF_CONFIG path to config.ncl (default: $HOME/.config/ontoref/config.ncl)
# ONTOREF_DAEMON path to ontoref-daemon binary (default: ~/.local/bin/ontoref-daemon)
# NICKEL_IMPORT_PATH (set automatically below; override to add extra import paths)
set -euo pipefail
CONFIG="${ONTOREF_CONFIG:-$HOME/.config/ontoref/config.ncl}"
DAEMON="${ONTOREF_DAEMON:-$HOME/.local/bin/ontoref-daemon.bin}"
DRY_RUN=0
SOPS_FILE=""
VAULT_PATH=""
PASS_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--sops) SOPS_FILE="$2"; shift 2 ;;
--vault) VAULT_PATH="$2"; shift 2 ;;
--config) CONFIG="$2"; shift 2 ;;
*) PASS_ARGS+=("$1"); shift ;;
esac
done
if [[ ! -f "$CONFIG" ]]; then
echo "error: config not found: $CONFIG" >&2
echo " run: ontoref config-setup or cp ~/.config/ontoref/config.ncl.example ~/.config/ontoref/config.ncl" >&2
exit 1
fi
if ! command -v nickel &>/dev/null; then
echo "error: nickel not found in PATH" >&2
exit 1
fi
# Resolve NICKEL_IMPORT_PATH: config dir + platform data dir schemas
# Allows config.ncl to import contracts from the shared data dir without hardcoded paths.
_config_dir="$(dirname "$CONFIG")"
if [[ "$(uname)" == "Darwin" ]]; then
_data_dir="$HOME/Library/Application Support/ontoref"
else
_data_dir="$HOME/.local/share/ontoref"
fi
export NICKEL_IMPORT_PATH="${NICKEL_IMPORT_PATH:+${NICKEL_IMPORT_PATH}:}${_config_dir}:${_config_dir}/schemas:${_data_dir}/schemas:${_data_dir}"
# Default NATS stream topology from config dir — project can override via streams_config in config.ncl
export NATS_STREAMS_CONFIG="${NATS_STREAMS_CONFIG:-${_config_dir}/streams.json}"
# Stage 0 — validate project paths and regenerate projects.ncl before nickel sees it.
# Any import in projects.ncl that points to a missing path is removed (with warning).
# This must run before nickel export — Nickel has no filesystem access.
_gen_projects="${_data_dir}/install/gen-projects.nu"
if command -v nu &>/dev/null && [[ -f "$_gen_projects" ]]; then
nu "$_gen_projects" --config-dir "$_config_dir" || {
echo "warn: gen-projects.nu failed — proceeding with existing projects.ncl" >&2
}
else
echo "warn: gen-projects.nu not found or nu not in PATH — skipping project validation" >&2
fi
# Stage 1 — structural validation via nickel export
stage1() {
nickel export --format json "$CONFIG"
}
# Stage 2 — optional secret injection
stage2() {
if [[ -n "$SOPS_FILE" ]]; then
if ! command -v sops &>/dev/null; then
echo "error: --sops requested but sops not found in PATH" >&2; exit 1
fi
if ! command -v jq &>/dev/null; then
echo "error: --sops requires jq for JSON merge" >&2; exit 1
fi
local struct_json secrets_json
struct_json="$(stage1)"
secrets_json="$(sops --decrypt "$SOPS_FILE")"
echo "$struct_json" "$secrets_json" | jq -s '.[0] * .[1]'
elif [[ -n "$VAULT_PATH" ]]; then
if ! command -v vault &>/dev/null; then
echo "error: --vault requested but vault not found in PATH" >&2; exit 1
fi
if ! command -v jq &>/dev/null; then
echo "error: --vault requires jq for JSON merge" >&2; exit 1
fi
local struct_json secrets_json
struct_json="$(stage1)"
secrets_json="$(vault kv get -format=json "$VAULT_PATH" | jq '.data.data')"
echo "$struct_json" "$secrets_json" | jq -s '.[0] * .[1]'
else
stage1
fi
}
if [[ $DRY_RUN -eq 1 ]]; then
stage2
exit 0
fi
if [[ ! -x "$DAEMON" ]]; then
echo "error: daemon not found or not executable: $DAEMON" >&2
echo " run: just install-daemon" >&2
exit 1
fi
# Stage 3 — pipe composed JSON to daemon stdin
stage2 | exec "$DAEMON" --config-stdin --config-dir "$_config_dir" "${PASS_ARGS[@]}"