scripts(scripts): 🔨 Add/update scripts for build automation and deployment workflows

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-04 15:14:01 -07:00
parent 7dd9b2b5ed
commit 34961b06c5
4 changed files with 286 additions and 0 deletions

53
scripts/deploy.sh Executable file
View file

@ -0,0 +1,53 @@
#!/usr/bin/env bash
# =============================================================================
# @analytics — Deploy to vps-0 (1984 hosting)
# =============================================================================
# Usage: ./scripts/deploy.sh
# or via: ./run deploy (once wired into run script)
#
# Requires: quinn-vps SSH alias configured in ~/.ssh/config
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
REMOTE="quinn-vps"
REMOTE_DIR="~/analytics"
echo "==> [1/5] Building services..."
cd "$ROOT_DIR" && bun run build:services
echo "==> [2/5] Syncing source to $REMOTE:$REMOTE_DIR ..."
rsync -avz --delete \
--exclude=node_modules \
--exclude=dist \
--exclude=.env \
--exclude=.env.* \
"$ROOT_DIR/services/" "$REMOTE:$REMOTE_DIR/services/"
rsync -avz \
"$ROOT_DIR/infrastructure/docker-compose.prod.yaml" \
"$ROOT_DIR/infrastructure/init.sql" \
"$REMOTE:$REMOTE_DIR/infrastructure/"
rsync -avz \
"$ROOT_DIR/package.json" \
"$ROOT_DIR/bun.lock" \
"$ROOT_DIR/turbo.json" \
"$ROOT_DIR/tsconfig.base.json" \
"$REMOTE:$REMOTE_DIR/"
echo "==> [3/5] Installing dependencies on remote..."
ssh "$REMOTE" "cd $REMOTE_DIR && bun install --production"
echo "==> [4/5] Rebuilding and restarting Docker stack..."
ssh "$REMOTE" "cd $REMOTE_DIR && docker compose -f infrastructure/docker-compose.prod.yaml --env-file infrastructure/.env.prod up -d --build"
echo "==> [5/5] Health check..."
sleep 5
ssh "$REMOTE" "curl -sf http://localhost:4001/health/live && echo 'collector OK' || echo 'collector NOT READY'"
ssh "$REMOTE" "curl -sf http://localhost:4003/health/live && echo 'api OK' || echo 'api NOT READY'"
echo ""
echo "Deployed at $(date '+%Y-%m-%d %H:%M:%S %Z')"
echo "Collector: https://data.transquinnftw.com/health"

27
scripts/run/build.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/bash
# Build commands for @analytics
# Sourced by the top-level ./run script — do not execute directly.
# ROOT_DIR is set by the caller.
case "${2:-all}" in
all | "")
# ./run build
cd "$ROOT_DIR" && bun run build
;;
packages)
# ./run build:packages
cd "$ROOT_DIR" && bun run build:lib
;;
services)
# ./run build:services
cd "$ROOT_DIR" && bun run build:services
;;
*)
echo "Unknown build command: build:${2}"
echo "Usage: ./run build[:<packages|services>]"
exit 1
;;
esac

119
scripts/run/dev.sh Executable file
View file

@ -0,0 +1,119 @@
#!/bin/bash
# Dev environment commands for @analytics
# Sourced by the top-level ./run script — do not execute directly.
# SCRIPT_DIR and ROOT_DIR are set by the caller.
ENV_FILE="$ROOT_DIR/infrastructure/.env.dev"
COMPOSE_FILE="$ROOT_DIR/infrastructure/docker-compose.dev.yaml"
_require_env() {
if [ ! -f "$ENV_FILE" ]; then
echo "ERROR: $ENV_FILE not found."
echo " cp infrastructure/.env.dev.example infrastructure/.env.dev"
echo " # then edit as needed"
exit 1
fi
}
_load_env() {
_require_env
set -a
# shellcheck source=/dev/null
source "$ENV_FILE"
set +a
}
_start_infra() {
docker compose -f "$COMPOSE_FILE" up -d
echo "TimescaleDB: localhost:25434"
echo "Redis: localhost:26379"
echo ""
}
case "${2:-}" in
"")
# ./run dev — full stack
_load_env
_start_infra
echo "Starting all services..."
(cd "$ROOT_DIR/services/collector" && bun run dev) &
COLLECTOR_PID=$!
(cd "$ROOT_DIR/services/processor" && bun run dev) &
PROCESSOR_PID=$!
(cd "$ROOT_DIR/services/api" && bun run dev) &
API_PID=$!
(cd "$ROOT_DIR/services/realtime" && bun run dev) &
REALTIME_PID=$!
echo " Collector → :4001"
echo " Processor → (background worker)"
echo " API → :4003"
echo " Realtime → :4004"
echo ""
echo "Press Ctrl+C to stop all services."
wait $COLLECTOR_PID $PROCESSOR_PID $API_PID $REALTIME_PID
;;
infra)
# ./run dev:infra
_start_infra
;;
collector)
# ./run dev:collector
_load_env
cd "$ROOT_DIR/services/collector" && bun run dev
;;
processor)
# ./run dev:processor
_load_env
cd "$ROOT_DIR/services/processor" && bun run dev
;;
api)
# ./run dev:api
_load_env
cd "$ROOT_DIR/services/api" && bun run dev
;;
realtime)
# ./run dev:realtime
_load_env
cd "$ROOT_DIR/services/realtime" && bun run dev
;;
stop)
# ./run dev:stop
docker compose -f "$COMPOSE_FILE" down
echo "Dev infrastructure stopped."
;;
status)
# ./run dev:status
echo "=== Infrastructure ==="
docker compose -f "$COMPOSE_FILE" ps
echo ""
echo "=== Collector (port 4001) ==="
curl -sf http://localhost:4001/health/live && echo "" || echo "Not running"
echo "=== API (port 4003) ==="
curl -sf http://localhost:4003/health/live && echo "" || echo "Not running"
echo "=== Realtime (port 4004) ==="
curl -sf http://localhost:4004/health/live && echo "" || echo "Not running"
;;
logs)
# ./run dev:logs [service]
SERVICE="${3:-}"
if [ -n "$SERVICE" ]; then
docker compose -f "$COMPOSE_FILE" logs -f "$SERVICE"
else
docker compose -f "$COMPOSE_FILE" logs -f
fi
;;
*)
echo "Unknown dev command: dev:${2}"
echo "Usage: ./run dev[:<infra|collector|processor|api|realtime|stop|status|logs>]"
exit 1
;;
esac

87
scripts/run/prod.sh Executable file
View file

@ -0,0 +1,87 @@
#!/bin/bash
# Production commands for @analytics
# Sourced by the top-level ./run script — do not execute directly.
# ROOT_DIR is set by the caller.
ENV_FILE="$ROOT_DIR/infrastructure/.env.prod"
COMPOSE_FILE="$ROOT_DIR/infrastructure/docker-compose.prod.yaml"
_require_env() {
if [ ! -f "$ENV_FILE" ]; then
echo "ERROR: $ENV_FILE not found."
echo " cp infrastructure/.env.prod.example infrastructure/.env.prod"
echo " # then fill in all CHANGE_ME values"
exit 1
fi
}
case "${2:-}" in
up)
# ./run prod:up
_require_env
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d
echo "Production stack started."
echo " Collector → 127.0.0.1:4001"
echo " API → 127.0.0.1:4003"
echo " Realtime → 127.0.0.1:4004"
echo " TimescaleDB external → :25434"
;;
down)
# ./run prod:down
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" down
echo "Production stack stopped."
;;
restart)
# ./run prod:restart [service]
SERVICE="${3:-}"
if [ -n "$SERVICE" ]; then
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" restart "$SERVICE"
else
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" restart
fi
;;
status)
# ./run prod:status
echo "=== Containers ==="
docker compose -f "$COMPOSE_FILE" ps
echo ""
echo "=== Collector (port 4001) ==="
curl -sf http://localhost:4001/health/live && echo "" || echo "Not running"
echo "=== API (port 4003) ==="
curl -sf http://localhost:4003/health/live && echo "" || echo "Not running"
echo "=== Realtime (port 4004) ==="
curl -sf http://localhost:4004/health/live && echo "" || echo "Not running"
;;
logs)
# ./run prod:logs [service]
SERVICE="${3:-}"
if [ -n "$SERVICE" ]; then
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs -f "$SERVICE"
else
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs -f
fi
;;
keygen)
# ./run prod:keygen — generate a new COLLECTOR_WRITE_KEY
KEY=$(openssl rand -hex 32)
echo ""
echo "Generated write key:"
echo ""
echo " COLLECTOR_WRITE_KEY=$KEY"
echo ""
echo "Add to infrastructure/.env.prod, then set the same value as"
echo "'writeKey' in the AnalyticsConfig passed to AnalyticsProvider."
echo ""
;;
*)
echo "Unknown prod command: prod:${2}"
echo "Usage: ./run prod:<up|down|restart|status|logs|keygen>"
exit 1
;;
esac