provisioning/justfiles/crate.just

475 lines
20 KiB
Text

# Platform crate — build, install, asset deployment
# ===================================================
# Generic recipes that work for any service binary in the Rust workspace.
# Accepts short aliases and auto-resolves to full package + binary names.
#
# Short alias → cargo package / output binary
# daemon → provisioning-daemon / provisioning-daemon
# orch → orchestrator / provisioning-orchestrator
# cc → control-center / provisioning-control-center
# vault → vault-service / provisioning-vault-service
# ai → ai-service / provisioning-ai-service
# mcp → provisioning-mcp / provisioning-mcp-server
# ncl-sync → ncl-sync / provisioning-ncl-sync
# tool → provisioning-tool / provisioning-tool
# rag → platform-rag / provisioning-rag
# cli → prvng-cli / prvng-cli
#
# Standalone crates (excluded from workspace — build via crate-level Cargo.toml):
# nu-daemon → nu-daemon / provisioning-nu-daemon [standalone]
#
# _resolve returns 3 fields: PKG BIN STANDALONE
# STANDALONE="standalone" → cargo build uses crates/<PKG>/Cargo.toml directly
# STANDALONE="" → cargo build uses workspace Cargo.toml -p <PKG>
#
# Usage:
# just crate-build # interactive picker
# just crate-build daemon # short alias
# just crate-install daemon # install binary only
# just crate-assets daemon # copy templates/assets
# just crate-deploy daemon # build → install → assets
# just crate-deploy nu-daemon # standalone build (excluded from workspace)
alias cdy := crate-deploy
_pf_root := parent_directory(source_file()) / ".." / "platform"
_pf_bin_dir := env_var_or_default("HOME", "/tmp") / ".local" / "bin"
_pf_data := env_var_or_default("HOME", "/tmp") / ".local" / "share" / "provisioning"
# ─── 1. Build ──────────────────────────────────────────────────────────────────
# Build a platform crate in release mode.
[doc("Build a platform crate release binary. Omit TARGET for interactive picker.")]
crate-build target="":
#!/usr/bin/env bash
set -euo pipefail
PLATFORM="{{_pf_root}}"
_pick_target() {
local choices=(daemon orchestrator control-center vault-service ai-service provisioning-mcp ncl-sync provisioning-tool platform-rag prvng-cli nu-daemon)
echo "Available targets:" >&2
local i=1
for c in "${choices[@]}"; do
printf ' %2d) %s\n' "$i" "$c" >&2
i=$((i+1))
done
local n
read -rp "Select [1-${#choices[@]}]: " n
printf '%s\n' "${choices[$((n-1))]}"
}
_resolve() {
case "$1" in
daemon|provisioning-daemon) echo "provisioning-daemon provisioning-daemon" ;;
orch|orchestrator) echo "orchestrator provisioning-orchestrator" ;;
cc|control-center|control) echo "control-center provisioning-control-center" ;;
vault|vault-service) echo "vault-service provisioning-vault-service" ;;
ai|ai-service) echo "ai-service provisioning-ai-service" ;;
mcp|provisioning-mcp) echo "provisioning-mcp provisioning-mcp-server" ;;
ncl-sync) echo "ncl-sync provisioning-ncl-sync" ;;
tool|provisioning-tool) echo "provisioning-tool provisioning-tool" ;;
rag|platform-rag) echo "platform-rag provisioning-rag" ;;
cli|prvng-cli) echo "prvng-cli prvng-cli" ;;
nu-daemon|nu_daemon) echo "nu-daemon provisioning-nu-daemon standalone" ;;
*) echo "error: unknown target '$1'" >&2; return 1 ;;
esac
}
INPUT="{{target}}"
[[ -z "$INPUT" ]] && INPUT="$(_pick_target)"
[[ -z "$INPUT" ]] && { echo "error: no target selected" >&2; exit 1; }
read -r PKG BIN STANDALONE <<< "$(_resolve "$INPUT")"
if [[ "$STANDALONE" == "standalone" ]]; then
echo "=== build: cargo build --release (standalone: crates/${PKG}) ==="
cargo build --release \
--manifest-path "${PLATFORM}/crates/${PKG}/Cargo.toml"
BUILT="${PLATFORM}/crates/${PKG}/target/release/${BIN}"
else
echo "=== build: cargo build --release -p ${PKG} ==="
cargo build --release \
--manifest-path "${PLATFORM}/Cargo.toml" \
-p "${PKG}"
BUILT="${PLATFORM}/target/release/${BIN}"
fi
SIZE=$(du -sh "$BUILT" 2>/dev/null | cut -f1 || echo "?")
echo " ok: ${BUILT} (${SIZE})"
# ─── 2. Install ────────────────────────────────────────────────────────────────
# Install the release binary to ~/.local/share/provisioning/bin/.
# The release binary must already exist; run crate-build first.
[doc("Install a platform crate binary locally. Run crate-build first.")]
crate-install target="":
#!/usr/bin/env bash
set -euo pipefail
PLATFORM="{{_pf_root}}"
BIN_DIR="{{_pf_bin_dir}}"
_pick_target() {
local choices=(daemon orchestrator control-center vault-service ai-service provisioning-mcp ncl-sync provisioning-tool platform-rag prvng-cli nu-daemon)
echo "Available targets:" >&2
local i=1
for c in "${choices[@]}"; do
printf ' %2d) %s\n' "$i" "$c" >&2
i=$((i+1))
done
local n
read -rp "Select [1-${#choices[@]}]: " n
printf '%s\n' "${choices[$((n-1))]}"
}
_resolve() {
case "$1" in
daemon|provisioning-daemon) echo "provisioning-daemon provisioning-daemon" ;;
orch|orchestrator) echo "orchestrator provisioning-orchestrator" ;;
cc|control-center|control) echo "control-center provisioning-control-center" ;;
vault|vault-service) echo "vault-service provisioning-vault-service" ;;
ai|ai-service) echo "ai-service provisioning-ai-service" ;;
mcp|provisioning-mcp) echo "provisioning-mcp provisioning-mcp-server" ;;
ncl-sync) echo "ncl-sync provisioning-ncl-sync" ;;
tool|provisioning-tool) echo "provisioning-tool provisioning-tool" ;;
rag|platform-rag) echo "platform-rag provisioning-rag" ;;
cli|prvng-cli) echo "prvng-cli prvng-cli" ;;
nu-daemon|nu_daemon) echo "nu-daemon provisioning-nu-daemon standalone" ;;
*) echo "error: unknown target '$1'" >&2; return 1 ;;
esac
}
INPUT="{{target}}"
[[ -z "$INPUT" ]] && INPUT="$(_pick_target)"
[[ -z "$INPUT" ]] && { echo "error: no target selected" >&2; exit 1; }
read -r PKG BIN STANDALONE <<< "$(_resolve "$INPUT")"
if [[ "$STANDALONE" == "standalone" ]]; then
BUILT="${PLATFORM}/crates/${PKG}/target/release/${BIN}"
else
BUILT="${PLATFORM}/target/release/${BIN}"
fi
if [[ ! -f "$BUILT" ]]; then
echo "error: release binary not found: ${BUILT}" >&2
echo " run: just crate-build ${INPUT}" >&2
exit 1
fi
echo "=== install: ${BIN_DIR}/${BIN} ==="
mkdir -p "${BIN_DIR}"
install -m 0755 "${BUILT}" "${BIN_DIR}/${BIN}"
echo " ok: ${BIN_DIR}/${BIN}"
# Emit PATH hint if the bin dir is not in PATH
if ! echo "$PATH" | tr ':' '\n' | grep -qxF "${BIN_DIR}"; then
echo " hint: add to PATH: export PATH=\"\$HOME/.local/share/provisioning/bin:\$PATH\""
fi
# ─── 3. Assets ─────────────────────────────────────────────────────────────────
# Copy templates and static assets to their install destinations.
#
# Destination is read from the crate's NCL service config (nickel export + jq).
# If the configured path is inside the source tree the step is skipped — the
# daemon already reads directly from there and no copy is needed.
# Falls back to the default convention when NCL is unavailable or the field
# is not set.
[doc("Install templates and assets for a platform crate.")]
crate-assets target="":
#!/usr/bin/env bash
set -euo pipefail
PLATFORM="{{_pf_root}}"
DATA="{{_pf_data}}"
_pick_target() {
local choices=(daemon orchestrator control-center vault-service ai-service provisioning-mcp ncl-sync provisioning-tool platform-rag prvng-cli nu-daemon)
echo "Available targets:" >&2
local i=1
for c in "${choices[@]}"; do
printf ' %2d) %s\n' "$i" "$c" >&2
i=$((i+1))
done
local n
read -rp "Select [1-${#choices[@]}]: " n
printf '%s\n' "${choices[$((n-1))]}"
}
_resolve() {
case "$1" in
daemon|provisioning-daemon) echo "provisioning-daemon provisioning-daemon" ;;
orch|orchestrator) echo "orchestrator provisioning-orchestrator" ;;
cc|control-center|control) echo "control-center provisioning-control-center" ;;
vault|vault-service) echo "vault-service provisioning-vault-service" ;;
ai|ai-service) echo "ai-service provisioning-ai-service" ;;
mcp|provisioning-mcp) echo "provisioning-mcp provisioning-mcp-server" ;;
ncl-sync) echo "ncl-sync provisioning-ncl-sync" ;;
tool|provisioning-tool) echo "provisioning-tool provisioning-tool" ;;
rag|platform-rag) echo "platform-rag provisioning-rag" ;;
cli|prvng-cli) echo "prvng-cli prvng-cli" ;;
nu-daemon|nu_daemon) echo "nu-daemon provisioning-nu-daemon standalone" ;;
*) echo "error: unknown target '$1'" >&2; return 1 ;;
esac
}
# Read a string field from the crate's NCL service config via sed.
# Avoids nickel schema resolution issues — works on raw config text.
_ncl_field() {
local service="$1" field="$2"
local ncl_cfg
if [[ "$(uname -s)" == "Darwin" ]]; then
ncl_cfg="${HOME}/Library/Application Support/provisioning/platform/config/${service}.ncl"
else
ncl_cfg="${HOME}/.config/provisioning/platform/config/${service}.ncl"
fi
[[ -f "$ncl_cfg" ]] || return
sed -n "s/^[[:space:]]*${field}[[:space:]]*=[[:space:]]*\"\([^\"]*\)\".*/\1/p" \
"$ncl_cfg" 2>/dev/null | head -1
}
# Returns true if PATH is inside (or equal to) the source crate directory.
_is_source_tree() {
local path="$1" crate_src="$2"
local canon_path canon_src
canon_path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")"
canon_src="$(cd "$crate_src" 2>/dev/null && pwd)"
[[ "$canon_path" == "$canon_src"* ]]
}
# Sync one asset group: SRC → DST, with source-tree detection.
# $1=label $2=src_dir $3=dst_dir $4=crate_src_root
_sync_group() {
local label="$1" src="$2" dst="$3" crate_src="$4"
if [[ ! -d "$src" ]]; then
echo " skip [${label}]: source not found (${src})"
return
fi
if _is_source_tree "$dst" "$crate_src"; then
echo " skip [${label}]: destination is the source tree"
echo " → ${dst}"
echo " daemon reads directly from there — no copy needed"
return
fi
echo " sync [${label}]: ${src}"
echo " → ${dst}"
mkdir -p "${dst}"
rsync -a --delete "${src}/" "${dst}/"
local count
count=$(find "${dst}" -type f | wc -l | tr -d ' ')
echo " ok: ${count} files"
}
INPUT="{{target}}"
[[ -z "$INPUT" ]] && INPUT="$(_pick_target)"
[[ -z "$INPUT" ]] && { echo "error: no target selected" >&2; exit 1; }
read -r PKG _BIN _STANDALONE <<< "$(_resolve "$INPUT")"
echo "=== crate-assets: ${PKG} ==="
case "$PKG" in
provisioning-daemon)
CRATE_SRC="${PLATFORM}/crates/provisioning-daemon"
# ui/templates — destination from NCL ui_templates_dir or convention
UI_DST="$(_ncl_field "provisioning-daemon" "ui_templates_dir")"
UI_DST="${UI_DST:-${DATA}/provisioning-daemon/ui/templates}"
_sync_group "ui_templates" \
"${CRATE_SRC}/ui/templates" \
"${UI_DST}" \
"${CRATE_SRC}"
# ontology_templates — destination from NCL ontology_templates or convention
ONT_DST="$(_ncl_field "provisioning-daemon" "ontology_templates")"
ONT_DST="${ONT_DST:-${DATA}/provisioning-daemon/ontology-templates}"
_sync_group "ontology_templates" \
"${CRATE_SRC}/ontology_templates" \
"${ONT_DST}" \
"${CRATE_SRC}"
;;
*)
echo " ${PKG}: no file-system assets (uses embedded resources)"
;;
esac
echo "=== done ==="
# ─── 4. Deploy (chain) ─────────────────────────────────────────────────────────
# Build → install → assets in sequence. Stops on first failure.
# Usage: just crate-deploy [target]
[doc("Full deploy: build release → install binary → install assets.")]
crate-deploy target="":
#!/usr/bin/env bash
set -euo pipefail
PLATFORM="{{_pf_root}}"
BIN_DIR="{{_pf_bin_dir}}"
DATA="{{_pf_data}}"
_pick_target() {
local choices=(daemon orchestrator control-center vault-service ai-service provisioning-mcp ncl-sync provisioning-tool platform-rag prvng-cli nu-daemon)
echo "Available targets:" >&2
local i=1
for c in "${choices[@]}"; do
printf ' %2d) %s\n' "$i" "$c" >&2
i=$((i+1))
done
local n
read -rp "Select [1-${#choices[@]}]: " n
printf '%s\n' "${choices[$((n-1))]}"
}
_resolve() {
case "$1" in
daemon|provisioning-daemon) echo "provisioning-daemon provisioning-daemon" ;;
orch|orchestrator) echo "orchestrator provisioning-orchestrator" ;;
cc|control-center|control) echo "control-center provisioning-control-center" ;;
vault|vault-service) echo "vault-service provisioning-vault-service" ;;
ai|ai-service) echo "ai-service provisioning-ai-service" ;;
mcp|provisioning-mcp) echo "provisioning-mcp provisioning-mcp-server" ;;
ncl-sync) echo "ncl-sync provisioning-ncl-sync" ;;
tool|provisioning-tool) echo "provisioning-tool provisioning-tool" ;;
rag|platform-rag) echo "platform-rag provisioning-rag" ;;
cli|prvng-cli) echo "prvng-cli prvng-cli" ;;
nu-daemon|nu_daemon) echo "nu-daemon provisioning-nu-daemon standalone" ;;
*) echo "error: unknown target '$1'" >&2; return 1 ;;
esac
}
_ncl_field() {
local service="$1" field="$2"
local ncl_cfg
if [[ "$(uname -s)" == "Darwin" ]]; then
ncl_cfg="${HOME}/Library/Application Support/provisioning/platform/config/${service}.ncl"
else
ncl_cfg="${HOME}/.config/provisioning/platform/config/${service}.ncl"
fi
[[ -f "$ncl_cfg" ]] || return
sed -n "s/^[[:space:]]*${field}[[:space:]]*=[[:space:]]*\"\([^\"]*\)\".*/\1/p" \
"$ncl_cfg" 2>/dev/null | head -1
}
_is_source_tree() {
local path="$1" crate_src="$2"
local canon_path canon_src
canon_path="$(cd "$(dirname "$path")" 2>/dev/null && pwd)/$(basename "$path")"
canon_src="$(cd "$crate_src" 2>/dev/null && pwd)"
[[ "$canon_path" == "$canon_src"* ]]
}
_sync_group() {
local label="$1" src="$2" dst="$3" crate_src="$4"
if [[ ! -d "$src" ]]; then
echo " skip [${label}]: source not found (${src})"
return
fi
if _is_source_tree "$dst" "$crate_src"; then
echo " skip [${label}]: destination is source tree"
echo " → ${dst}"
echo " service reads templates directly from there"
return
fi
echo " sync [${label}]: ${src}"
echo " → ${dst}"
mkdir -p "${dst}"
rsync -a --delete "${src}/" "${dst}/"
local count
count=$(find "${dst}" -type f | wc -l | tr -d ' ')
echo " ok: ${count} files copied"
}
# Returns "running" or empty.
# Match binary path: [/]<bin> followed by end-of-string OR a space (arguments).
# The bare $ anchor fails when the process has CLI arguments like --config.
_svc_status() {
pgrep -f "[/]${1}( |$)" >/dev/null 2>&1 && echo "running" || true
}
INPUT="{{target}}"
[[ -z "$INPUT" ]] && INPUT="$(_pick_target)"
[[ -z "$INPUT" ]] && { echo "error: no target selected" >&2; exit 1; }
read -r PKG BIN STANDALONE <<< "$(_resolve "$INPUT")"
SVC="${PKG//-/_}"
echo "=== crate-deploy: ${PKG} ==="
echo ""
# ── pre: stop service if running ───────────────────────────────────────────
WAS_RUNNING=false
if [[ "$(_svc_status "$PKG")" == "running" ]]; then
WAS_RUNNING=true
echo " service ${PKG} is running — stopping before overwrite"
provisioning platform stop "$PKG" >/dev/null 2>&1 || true
sleep 2
pgrep -f "[/]${BIN}$" >/dev/null 2>&1 && echo " warning: process still running" || echo " stopped: ok"
echo ""
fi
# ── step 1: build ──────────────────────────────────────────────────────────
echo "--- [1/3] build ---"
if [[ "$STANDALONE" == "standalone" ]]; then
cargo build --release \
--manifest-path "${PLATFORM}/crates/${PKG}/Cargo.toml"
BUILT="${PLATFORM}/crates/${PKG}/target/release/${BIN}"
else
cargo build --release \
--manifest-path "${PLATFORM}/Cargo.toml" \
-p "${PKG}"
BUILT="${PLATFORM}/target/release/${BIN}"
fi
SIZE=$(du -sh "$BUILT" 2>/dev/null | cut -f1 || echo "?")
echo " ok: ${BUILT} (${SIZE})"
echo ""
# ── step 2: install binary ─────────────────────────────────────────────────
echo "--- [2/3] install ---"
mkdir -p "${BIN_DIR}"
DEST="${BIN_DIR}/${BIN}"
if [[ -f "$DEST" ]]; then
install -m 0755 "${BUILT}" "${DEST}"
echo " overwritten: ${DEST}"
else
install -m 0755 "${BUILT}" "${DEST}"
echo " installed: ${DEST}"
fi
if ! echo "$PATH" | tr ':' '\n' | grep -qxF "${BIN_DIR}"; then
echo " hint: add to PATH: export PATH=\"${BIN_DIR}:\$PATH\""
fi
echo ""
# ── step 3: assets ─────────────────────────────────────────────────────────
echo "--- [3/3] assets ---"
case "$PKG" in
provisioning-daemon)
CRATE_SRC="${PLATFORM}/crates/provisioning-daemon"
UI_DST="$(_ncl_field "provisioning-daemon" "ui_templates_dir")"
UI_DST="${UI_DST:-${DATA}/provisioning-daemon/ui/templates}"
_sync_group "ui_templates" "${CRATE_SRC}/ui/templates" "${UI_DST}" "${CRATE_SRC}"
ONT_DST="$(_ncl_field "provisioning-daemon" "ontology_templates")"
ONT_DST="${ONT_DST:-${DATA}/provisioning-daemon/ontology-templates}"
_sync_group "ontology_templates" "${CRATE_SRC}/ontology_templates" "${ONT_DST}" "${CRATE_SRC}"
;;
*)
echo " ${PKG}: no file-system assets"
;;
esac
echo ""
# ── post: always start (restart if was running, start if was stopped) ────────
echo "--- start ---"
provisioning platform start "$PKG" >/dev/null 2>&1 || true
sleep 2
LOG="${HOME}/.provisioning/logs/${PKG}.log"
if pgrep -f "[/]${BIN}( |$)" >/dev/null 2>&1; then
echo " started: ok"
echo " logs: ${LOG}"
else
echo " warning: process not running after start"
echo " check: ${LOG}"
fi
echo ""
echo "=== done: ${BIN} deployed ==="