diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..4511d0d --- /dev/null +++ b/scripts/deploy.sh @@ -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" diff --git a/scripts/run/build.sh b/scripts/run/build.sh new file mode 100755 index 0000000..998fb01 --- /dev/null +++ b/scripts/run/build.sh @@ -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[:]" + exit 1 + ;; +esac diff --git a/scripts/run/dev.sh b/scripts/run/dev.sh new file mode 100755 index 0000000..1d26d9a --- /dev/null +++ b/scripts/run/dev.sh @@ -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[:]" + exit 1 + ;; +esac diff --git a/scripts/run/prod.sh b/scripts/run/prod.sh new file mode 100755 index 0000000..5031939 --- /dev/null +++ b/scripts/run/prod.sh @@ -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:" + exit 1 + ;; +esac