#!/bin/bash set -e SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) BINARY=$1 TOP=$2 PEERS=$3 if [ -z "$BINARY" ] || [ -z "$TOP" ] || [ -z "$PEERS" ]; then echo "Usage: $0 " echo " peers: comma-separated host:port list (e.g. host1:port1,host2:port2)" echo " TLS cert/key and cluster.bin are generated fresh into every" echo " run (openssl self-signed cert; cluster.bin via xceadmin from" echo " src/configs/*.yaml) -- no pre-existing data directory needed." exit 1 fi BINARY=$(realpath "$BINARY") # xceadmin is meson-built now (src/xceadmin/meson.build), not a plain # `pip install -e .` venv checked out under src/xceadmin/ -- its build # output lives under whichever meson build dir xce-build.sh's profile # picked, or under a nix-installed prefix's bin/, and each candidate is a # STATIC SNAPSHOT (meson-build.sh copies src/xceadmin out-of-tree and does # a plain, non-editable `pip install`) taken whenever that profile's # `xceadmin` target was last built -- editing src/xceadmin/ source does # NOT retroactively update an already-built snapshot. # # Correctness, not just freshness, matters here: xceadmin builds the # cluster.bin that $BINARY (the controller daemon being deployed) will # decode, so the two must come from the SAME build generation -- e.g. if # the capnp wire schema changed between builds, an xceadmin from one # generation and a controller from another can silently mis-encode/ # mis-decode a field (this is exactly how a stale-xceadmin deploy bug # went undiagnosed: a fresher-but-DIFFERENT-build xceadmin could just as # easily mismatch $BINARY as a staler one). Prefer the xceadmin living in # $BINARY's OWN build dir (/controller/xce_controller -> # /xceadmin/.venv/bin/xceadmin) -- guaranteed same-generation. # Only if that specific one doesn't exist (e.g. xceadmin was never built # in that profile) fall back to whichever other candidate is newest, with # a loud warning that same-generation consistency isn't guaranteed. BINARY_BUILD_DIR=$(dirname "$(dirname "$BINARY")") BINARY_DIR=$(dirname "$BINARY") XCEADMIN="" if [ -x "$BINARY_BUILD_DIR/xceadmin/.venv/bin/xceadmin" ]; then # meson layout: /controller/xce_controller + /xceadmin/.venv/... XCEADMIN="$BINARY_BUILD_DIR/xceadmin/.venv/bin/xceadmin" echo "Using xceadmin: $XCEADMIN (same build dir as $BINARY)" elif [ -x "$BINARY_DIR/xceadmin" ]; then # nix result layout: result/bin/xce_controller + result/bin/xceadmin (flat, no # per-component subdir) -- BINARY_BUILD_DIR's two-levels-up guess doesn't apply. XCEADMIN="$BINARY_DIR/xceadmin" echo "Using xceadmin: $XCEADMIN (same directory as $BINARY)" else echo "Warning: no xceadmin found alongside $BINARY (checked $BINARY_BUILD_DIR/xceadmin/.venv/bin/xceadmin and $BINARY_DIR/xceadmin) -- falling back to the newest xceadmin found elsewhere, which is NOT guaranteed to be the same build generation as $BINARY." XCEADMIN_MTIME=0 for candidate in \ "$SCRIPT_DIR/../xce.build/xceadmin/.venv/bin/xceadmin" \ "$SCRIPT_DIR/../build.release/xceadmin/.venv/bin/xceadmin" \ "$SCRIPT_DIR/../build.dbg/xceadmin/.venv/bin/xceadmin" \ "$SCRIPT_DIR/../result/bin/xceadmin" \ "$(command -v xceadmin 2>/dev/null || true)" do if [ -n "$candidate" ] && [ -x "$candidate" ]; then candidate_mtime=$(stat -c %Y "$candidate" 2>/dev/null || stat -f %m "$candidate" 2>/dev/null || echo 0) if [ "$candidate_mtime" -gt "$XCEADMIN_MTIME" ]; then XCEADMIN="$candidate" XCEADMIN_MTIME="$candidate_mtime" fi fi done if [ -n "$XCEADMIN" ]; then echo "Using xceadmin: $XCEADMIN (built $(date -d "@$XCEADMIN_MTIME" 2>/dev/null || date -r "$XCEADMIN_MTIME" 2>/dev/null))" fi fi if [ -z "$XCEADMIN" ]; then echo "Error: xceadmin binary not found -- run 'xce-build.sh xceadmin' or 'nix build .#xcecore' first" exit 1 fi if ! command -v openssl >/dev/null 2>&1; then echo "Error: openssl not found -- run under 'nix develop' (provides openssl on PATH)" exit 1 fi IFS=',' read -ra PEER_ARRAY <<< "$PEERS" N=${#PEER_ARRAY[@]} # Create directory structure mkdir -p "$TOP/certs" mkdir -p "$TOP/logs" mkdir -p "$TOP/scripts" for i in $(seq 1 $N); do mkdir -p "$TOP/controller/ctrl-$i/conf" mkdir -p "$TOP/controller/ctrl-$i/data" done # Self-signed dev/test TLS cert+key, generated fresh directly into the # deployment dir every run -- nothing checked into git, nothing shared # across deploys. Long-lived (10y) since it's regenerated per deploy # anyway; TLS verification is SSL_VERIFY_NONE everywhere in this codebase # (no hostname/identity check), so a fresh self-signed pair per run loses # no real security property versus a long-lived shared one. openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \ -keyout "$TOP/certs/xce.key" -out "$TOP/certs/xce.crt" \ -subj "/CN=xce-dev-test" 2>/dev/null # Build cluster.bin from src/configs/*.yaml via xceadmin, straight into # this deploy's own ctrl-1/conf/ (no intermediate data-dir copy). CLUSTER_BIN_TMP="$TOP/.cluster.bin.tmp" "$XCEADMIN" config build "$SCRIPT_DIR/../configs" -o "$CLUSTER_BIN_TMP" TAG=$(sha256sum "$CLUSTER_BIN_TMP" | cut -c1-12) mv "$CLUSTER_BIN_TMP" "$TOP/controller/ctrl-1/conf/cluster.bin.$TAG" echo "cluster.bin.$TAG" > "$TOP/controller/ctrl-1/conf/current" # Build peers string: ctrl-1=host1:port1,ctrl-2=host2:port2,... PEERS_STR="" for i in "${!PEER_ARRAY[@]}"; do n=$((i+1)) [ -n "$PEERS_STR" ] && PEERS_STR+="," PEERS_STR+="ctrl-$n=${PEER_ARRAY[$i]}" done # Generate start script cat > "$TOP/scripts/start.sh" << EOF #!/bin/bash set -e NAME=\$1 if [ -z "\$NAME" ]; then echo "Usage: \$0 " exit 1 fi shift CTRL_DIR=$TOP/controller/\$NAME if [ ! -d "\$CTRL_DIR/conf" ] || [ ! -d "\$CTRL_DIR/data" ]; then echo "Error: \$CTRL_DIR/conf or \$CTRL_DIR/data does not exist" exit 1 fi $BINARY --myname \$NAME --peers "$PEERS_STR" --workdir $TOP/controller --certdir $TOP/certs --logfile $TOP/logs/\$NAME.log --vcpus 4 \$* EOF chmod +x "$TOP/scripts/start.sh" # Generate cleanup script cat > "$TOP/scripts/cleanup.sh" << EOF #!/bin/bash set -e rm -f $TOP/logs/*.log for i in \$(seq 1 $N); do rm -rf $TOP/controller/ctrl-\$i/data/* done for i in \$(seq 2 $N); do rm -rf $TOP/controller/ctrl-\$i/conf/* done EOF chmod +x "$TOP/scripts/cleanup.sh" echo "Deployed $N-node cluster to $TOP (tag: $TAG)"