diff --git a/.gitignore b/.gitignore index c42bbb2..c481f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ examples/example vsr_boot_marker *.pem *.exe -.claude/ \ No newline at end of file +.claude/ +cluster-data/ \ No newline at end of file diff --git a/cluster.sh b/cluster.sh new file mode 100755 index 0000000..74fbb52 --- /dev/null +++ b/cluster.sh @@ -0,0 +1,177 @@ +#!/bin/bash +# +# Cluster management script for ToastyFS. +# Manages a 3-node server cluster with 1 HTTP proxy. +# +# Usage: +# ./cluster.sh start - Build (if needed) and start all nodes + proxy +# ./cluster.sh stop - Stop all running nodes and the proxy +# ./cluster.sh status - Show the status of each process +# ./cluster.sh restart - Stop then start +# ./cluster.sh clean - Stop everything and remove data/log directories +# + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DATA_DIR="$SCRIPT_DIR/cluster-data" +LOG_DIR="$DATA_DIR/logs" +PID_DIR="$DATA_DIR/pids" + +NODE1_ADDR="127.0.0.1:8001" +NODE2_ADDR="127.0.0.1:8002" +NODE3_ADDR="127.0.0.1:8003" + +build() { + echo "Building ToastyFS..." + (cd "$SCRIPT_DIR" && bash build.sh) + echo "Build complete." +} + +ensure_dirs() { + mkdir -p "$LOG_DIR" "$PID_DIR" + mkdir -p "$DATA_DIR/chunks-node1" + mkdir -p "$DATA_DIR/chunks-node2" + mkdir -p "$DATA_DIR/chunks-node3" +} + +start_node() { + local name="$1" + local addr="$2" + local peer1="$3" + local peer2="$4" + local chunks="$5" + + local pidfile="$PID_DIR/$name.pid" + local logfile="$LOG_DIR/$name.log" + + if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then + echo " $name is already running (pid $(cat "$pidfile"))" + return + fi + + "$SCRIPT_DIR/toasty" \ + --addr "$addr" \ + --peer "$peer1" \ + --peer "$peer2" \ + --chunks "$chunks" \ + > "$logfile" 2>&1 & + + local pid=$! + echo "$pid" > "$pidfile" + echo " $name started (pid $pid) on $addr" +} + +start_proxy() { + local pidfile="$PID_DIR/proxy.pid" + local logfile="$LOG_DIR/proxy.log" + + if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then + echo " proxy is already running (pid $(cat "$pidfile"))" + return + fi + + "$SCRIPT_DIR/toasty_proxy" \ + --server "$NODE1_ADDR" \ + --server "$NODE2_ADDR" \ + --server "$NODE3_ADDR" \ + > "$logfile" 2>&1 & + + local pid=$! + echo "$pid" > "$pidfile" + echo " proxy started (pid $pid) on 127.0.0.1:3000" +} + +stop_process() { + local name="$1" + local pidfile="$PID_DIR/$name.pid" + + if [ ! -f "$pidfile" ]; then + echo " $name is not running (no pid file)" + return + fi + + local pid + pid=$(cat "$pidfile") + + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" + # Wait briefly for graceful shutdown + local i=0 + while kill -0 "$pid" 2>/dev/null && [ $i -lt 10 ]; do + sleep 0.1 + i=$((i + 1)) + done + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + echo " $name stopped (was pid $pid)" + else + echo " $name was not running (stale pid $pid)" + fi + + rm -f "$pidfile" +} + +do_start() { + if [ ! -f "$SCRIPT_DIR/toasty" ] || [ ! -f "$SCRIPT_DIR/toasty_proxy" ]; then + build + fi + + ensure_dirs + + echo "Starting cluster..." + start_node "node1" "$NODE1_ADDR" "$NODE2_ADDR" "$NODE3_ADDR" "$DATA_DIR/chunks-node1" + start_node "node2" "$NODE2_ADDR" "$NODE1_ADDR" "$NODE3_ADDR" "$DATA_DIR/chunks-node2" + start_node "node3" "$NODE3_ADDR" "$NODE1_ADDR" "$NODE2_ADDR" "$DATA_DIR/chunks-node3" + + # Give servers a moment to start listening before launching the proxy + sleep 1 + + start_proxy + echo "Cluster is running. HTTP proxy at http://127.0.0.1:3000" +} + +do_stop() { + echo "Stopping cluster..." + stop_process "proxy" + stop_process "node1" + stop_process "node2" + stop_process "node3" + echo "Cluster stopped." +} + +do_status() { + local all_stopped=true + for name in node1 node2 node3 proxy; do + local pidfile="$PID_DIR/$name.pid" + if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then + echo " $name: running (pid $(cat "$pidfile"))" + all_stopped=false + else + echo " $name: stopped" + fi + done + if $all_stopped; then + echo "Cluster is not running." + fi +} + +do_clean() { + do_stop + echo "Removing cluster data..." + rm -rf "$DATA_DIR" + echo "Clean complete." +} + +case "${1:-}" in + start) do_start ;; + stop) do_stop ;; + status) do_status ;; + restart) do_stop; do_start ;; + clean) do_clean ;; + *) + echo "Usage: $0 {start|stop|status|restart|clean}" + exit 1 + ;; +esac