#!/usr/bin/env bash # Start this project's services against a personal data server that exists. # # ./scripts/dev-stack.sh start what is not already up # ./scripts/dev-stack.sh stop stop what this started # ./scripts/dev-stack.sh status say what is up # # The server is not one of these. It belongs to didbot, it is somebody's to # run, and this repository reads what is on it — so this refuses to start # without one rather than standing up its own. Point it elsewhere with # VIBESCROBBLE_PDS_URL. # # The dev-*.sh scripts each run one service in the foreground, because the log # is the point. This runs three at once, which means backgrounding them and # putting each log in a file — so `tail -f` is how you watch one, and a single # service you are working on is better run in its own terminal. set -euo pipefail cd "$(dirname "$0")/.." HERE="$(pwd)" # Per checkout, not per machine. Two of these running at once is the ordinary # case on a machine several people or agents share, and a `stop` that read a # shared directory would stop somebody else's services as readily as its own. LOGS="${VIBESCROBBLE_LOGS:-${XDG_RUNTIME_DIR:-/tmp}/vibescrobble-stack/$(printf '%s' "$HERE" | cksum | cut -d' ' -f1)}" mkdir -p "$LOGS" . "$(dirname "$0")/dev-pds-url.sh" INDEX_PORT="${VIBESCROBBLE_INDEX_PORT:-3002}" QUERY_PORT="${VIBESCROBBLE_QUERY_PORT:-3003}" WEB_PORT="${VIBESCROBBLE_WEB_PORT:-8137}" # Where this project's own command is linked. On the path already for most # people; the script says so when it is not. BIN="${VIBESCROBBLE_BIN:-$HOME/.local/bin}" say() { printf '\033[1m%s\033[0m\n' "$*"; } # By pidfile and by what /proc says is running, never by a pattern over # command lines: a pattern matches this script, and killing that is a kill # that takes out the thing doing the killing. pidfile() { echo "$LOGS/$1.pid"; } alive() { local pid pid="$(cat "$(pidfile "$1")" 2>/dev/null || true)" [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null } # Whether something already answers there. Each service has its own path, # because a server that 404s the root is still a server. serving() { local port="$1" path="$2" curl -sf -m 1 -o /dev/null "http://127.0.0.1:${port}${path}" 2>/dev/null } start() { local name="$1" port="$2" path="$3" shift 3 if alive "$name"; then echo " $name already up (pid $(cat "$(pidfile "$name")"))" return 0 fi # Something already serving is used, not replaced. Whoever started it is # watching its log, and these scripts claim a port by signalling whoever # holds it. if serving "$port" "$path"; then echo " $name already serving on $port; using it" return 0 fi ( cd "$HERE" && exec "$@" ) > "$LOGS/$name.log" 2>&1 & echo $! > "$(pidfile "$name")" echo " $name -> $LOGS/$name.log" } stop_all() { local name pid waited for name in web query index; do pid="$(cat "$(pidfile "$name")" 2>/dev/null || true)" if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then # The process group, because each of these is a script that # exec'd or spawned a build. kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true # Waited for, because a start straight after a stop otherwise # races the port the old one still holds. waited=0 while kill -0 "$pid" 2>/dev/null && [ "$waited" -lt 100 ]; do sleep 0.1 waited=$((waited + 1)) done kill -9 -- "-$pid" 2>/dev/null || kill -9 "$pid" 2>/dev/null || true echo " stopped $name" fi rm -f "$(pidfile "$name")" done } status() { local name port path for pair in "index:$INDEX_PORT:/view" "query:$QUERY_PORT:/view" "web:$WEB_PORT:/"; do name="${pair%%:*}" port="$(echo "$pair" | cut -d: -f2)" path="$(echo "$pair" | cut -d: -f3)" if alive "$name"; then printf ' %-6s up pid %s\n' "$name" "$(cat "$(pidfile "$name")")" elif serving "$port" "$path"; then # Up, and somebody else's. `stop` will leave it alone, which is # the whole reason to tell these apart. printf ' %-6s up on %s, not started here\n' "$name" "$port" else printf ' %-6s down\n' "$name" fi done } case "${1:-start}" in stop) say "stopping"; stop_all; exit 0 ;; status) say "stack"; status; exit 0 ;; start) ;; *) echo "usage: $0 [start|stop|status]" >&2; exit 1 ;; esac if ! pds_curl -sf -m 3 -o /dev/null "${PDS_URL}/health" 2>/dev/null; then say "no personal data server at ${PDS_URL}" echo " Nothing here indexes an empty network, and this repository does not" echo " run a server: that is didbot's, and somebody's to start." echo echo " cd ../didbot && ./scripts/dev-pds.sh" echo echo " Point elsewhere with VIBESCROBBLE_PDS_URL." exit 1 fi # Symlink rather than a copy, and the debug build rather than an install: a # rebuild then takes effect without anybody remembering to install again. say "building vibescrobble" cargo build --quiet -p vibescrobble-cli --bin vibescrobble mkdir -p "$BIN" ln -sf "$HERE/target/debug/vibescrobble" "$BIN/vibescrobble" echo " $BIN/vibescrobble -> $HERE/target/debug/vibescrobble" case ":$PATH:" in *":$BIN:"*) ;; *) say "$BIN is not on your PATH" echo " An agent told to run vibescrobble will not find it. Add it, or" echo " set VIBESCROBBLE_BIN to somewhere that is." ;; esac echo say "starting against ${PDS_URL}" # Job control, so each service is its own process group and `stop` can take a # build down with the script that started it. set -m start index "$INDEX_PORT" /view ./scripts/dev-index.sh start query "$QUERY_PORT" /view ./scripts/dev-query.sh # The canvas last, and only once the query service answers. It binds instantly # because it is a static file server, so starting it first means opening it # during the minutes everything behind it is still compiling — where it # reports there is no query service, and does not look again. printf ' waiting for the query service' for _ in $(seq 600); do serving "$QUERY_PORT" /view && break printf '.' sleep 1 done echo start web "$WEB_PORT" / ./scripts/dev-web.sh echo say "up" echo " server ${PDS_URL} (not started here)" echo " index http://localhost:${INDEX_PORT}/view" echo " query http://localhost:${QUERY_PORT}" echo " web http://localhost:${WEB_PORT} (dev/#mock for invented data)" echo echo " logs $LOGS" echo " command $BIN/vibescrobble" echo " stop $0 stop"