# 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//Cargo.toml directly # STANDALONE="" → cargo build uses workspace Cargo.toml -p # # 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: [/] 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 ==="