#!/bin/bash # # Dataflow Deploy Script # First run: full install (database, schema, UI, nginx, systemd) # Subsequent runs: update functions, UI, and restart service # set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # ── Helpers ─────────────────────────────────────────────────────────────────── BOLD='\033[1m' DIM='\033[2m' GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' RESET='\033[0m' section() { echo ""; echo -e "${BOLD}── $1 ──${RESET}"; } step() { printf " %-42s" "$1..."; } ok() { echo -e "${GREEN}✓${RESET}"; } fail() { echo -e "${RED}✗ $1${RESET}"; exit 1; } info() { echo -e " ${DIM}$1${RESET}"; } warn() { echo -e " ${YELLOW}$1${RESET}"; } confirm() { # confirm "Section name" — prints section header, asks to proceed # Returns 0 to proceed, 1 to skip section "$1" read -p " Proceed? [Y/n]: " _yn [[ ! "$_yn" =~ ^[Nn]$ ]] } # ── Mode detection ──────────────────────────────────────────────────────────── section "Mode" if [ ! -f .env ]; then echo " No .env found — first-time install" MODE=install else echo " .env found — update mode" MODE=update export $(cat .env | grep -v '^#' | xargs) fi # ── Phase 1: Collect all config ─────────────────────────────────────────────── if [ "$MODE" = "install" ]; then section "PostgreSQL Admin" info "Needed to create the app user and database" read -p " Admin username [postgres]: " ADMIN_USER; ADMIN_USER=${ADMIN_USER:-postgres} read -s -p " Admin password: " ADMIN_PASS; echo "" section "Application Database" read -p " Host [localhost]: " DB_HOST; DB_HOST=${DB_HOST:-localhost} read -p " Port [5432]: " DB_PORT; DB_PORT=${DB_PORT:-5432} read -p " Database [dataflow]: " DB_NAME; DB_NAME=${DB_NAME:-dataflow} read -p " App user [dataflow]: " DB_USER; DB_USER=${DB_USER:-dataflow} read -s -p " App password: " DB_PASSWORD; echo "" section "API" read -p " Port [3020]: " API_PORT; API_PORT=${API_PORT:-3020} read -p " Environment [production]:" NODE_ENV; NODE_ENV=${NODE_ENV:-production} section "Nginx" read -p " Set up nginx reverse proxy? [Y/n]: " _yn if [[ ! "$_yn" =~ ^[Nn]$ ]]; then read -p " Domain (e.g. dataflow.example.com): " NGINX_DOMAIN fi DO_DEPS=y; DO_DB=y; DO_SCHEMA=y; DO_FN=y; DO_BUILD=y; DO_SERVICE=y; DO_RESTART=y else section "Database" echo " Current: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}" read -p " Change target? [y/N]: " _yn if [[ "$_yn" =~ ^[Yy]$ ]]; then read -p " Host [${DB_HOST}]: " _in; DB_HOST=${_in:-$DB_HOST} read -p " Port [${DB_PORT}]: " _in; DB_PORT=${_in:-$DB_PORT} read -p " Database [${DB_NAME}]: " _in; DB_NAME=${_in:-$DB_NAME} read -p " User [${DB_USER}]: " _in; DB_USER=${_in:-$DB_USER} read -s -p " Password (blank = keep): " _in; echo "" if [ -n "$_in" ]; then DB_PASSWORD=$_in; fi CHANGE_DB=y fi section "Select steps to run" read -p " Redeploy SQL functions? [Y/n]: " DO_FN; DO_FN=${DO_FN:-y} read -p " Rebuild UI? [Y/n]: " DO_BUILD; DO_BUILD=${DO_BUILD:-y} read -p " Set up / update nginx? [y/N]: " DO_NGINX read -p " Restart API service? [Y/n]: " DO_RESTART; DO_RESTART=${DO_RESTART:-y} if [[ "$DO_NGINX" =~ ^[Yy]$ ]]; then read -p " Nginx domain: " NGINX_DOMAIN fi fi # ── Phase 2: Plan summary ───────────────────────────────────────────────────── section "Plan" echo " Mode: $( [ "$MODE" = "install" ] && echo "First-time install" || echo "Update" )" echo " Database: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}" echo " API port: ${API_PORT:-3020}" echo "" echo " Steps:" if [ "$MODE" = "install" ]; then echo " • Install Node.js dependencies" echo " • Test admin connection and create DB user/database" echo " • Deploy schema and SQL functions" [ -n "$NGINX_DOMAIN" ] && echo " • Configure nginx → $NGINX_DOMAIN" \ || echo " • Nginx — skipped (no domain provided)" echo " • Build UI" echo " • Install systemd service" echo " • Start service" else [ "${CHANGE_DB}" = "y" ] && echo " • Update .env with new database target" [[ ! "$DO_FN" =~ ^[Nn]$ ]] && echo " • Redeploy SQL functions" \ || echo " • SQL functions — skipped" [[ ! "$DO_BUILD" =~ ^[Nn]$ ]] && echo " • Rebuild UI" \ || echo " • UI build — skipped" [ -n "$NGINX_DOMAIN" ] && echo " • Configure nginx → $NGINX_DOMAIN" \ || echo " • Nginx — skipped" [[ ! "$DO_RESTART" =~ ^[Nn]$ ]] && echo " • Restart API service" \ || echo " • Service restart — skipped" fi echo "" read -p " Continue? [Y/n]: " _yn [[ "$_yn" =~ ^[Nn]$ ]] && echo " Aborted." && exit 0 # ── Phase 3: Execute ────────────────────────────────────────────────────────── if [ "$MODE" = "install" ]; then # Dependencies if confirm "Dependencies"; then step "API (npm install)" npm install --omit=dev -q && ok || fail "npm install failed" step "UI (npm install)" cd ui && npm install -q && cd .. && ok || fail "ui npm install failed" else info "skipped" fi # PostgreSQL if confirm "PostgreSQL"; then step "Testing admin connection" export PGPASSWORD="$ADMIN_PASS" psql -U "$ADMIN_USER" -h "$DB_HOST" -p "$DB_PORT" -d postgres -c '\q' 2>/dev/null && ok \ || fail "Cannot connect as $ADMIN_USER" step "Creating user '$DB_USER'" if psql -U "$ADMIN_USER" -h "$DB_HOST" -p "$DB_PORT" -d postgres -tAc \ "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" 2>/dev/null | grep -q 1; then echo -e "${DIM}already exists${RESET}" else psql -U "$ADMIN_USER" -h "$DB_HOST" -p "$DB_PORT" -d postgres \ -c "CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD';" > /dev/null && ok \ || fail "Could not create user" fi step "Creating database '$DB_NAME'" if psql -U "$ADMIN_USER" -h "$DB_HOST" -p "$DB_PORT" -lqt \ | cut -d'|' -f1 | grep -qw "$DB_NAME"; then echo -e "${DIM}already exists${RESET}" else psql -U "$ADMIN_USER" -h "$DB_HOST" -p "$DB_PORT" -d postgres \ -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" > /dev/null && ok \ || fail "Could not create database" fi unset PGPASSWORD step "Writing .env" cat > .env << ENVEOF # Database Configuration DB_HOST=$DB_HOST DB_PORT=$DB_PORT DB_NAME=$DB_NAME DB_USER=$DB_USER DB_PASSWORD=$DB_PASSWORD # API Configuration API_PORT=$API_PORT NODE_ENV=$NODE_ENV ENVEOF ok export PGPASSWORD="$DB_PASSWORD" step "Deploying schema" psql -U "$DB_USER" -h "$DB_HOST" -p "$DB_PORT" -d "$DB_NAME" -f database/schema.sql -q && ok \ || fail "Schema deploy failed" step "Deploying functions" psql -U "$DB_USER" -h "$DB_HOST" -p "$DB_PORT" -d "$DB_NAME" -f database/functions.sql -q && ok \ || fail "Functions deploy failed" else info "skipped" fi else # Update: save .env if DB target changed section ".env" if [ "${CHANGE_DB}" = "y" ]; then step "Writing updated .env" cat > .env << ENVEOF # Database Configuration DB_HOST=$DB_HOST DB_PORT=$DB_PORT DB_NAME=$DB_NAME DB_USER=$DB_USER DB_PASSWORD=$DB_PASSWORD # API Configuration API_PORT=${API_PORT:-3020} NODE_ENV=${NODE_ENV:-production} ENVEOF ok else info "no changes" fi # Test connection section "Database Connection" step "Testing" export PGPASSWORD="$DB_PASSWORD" psql -U "$DB_USER" -h "$DB_HOST" -p "$DB_PORT" -d "$DB_NAME" -c '\q' 2>/dev/null && ok \ || fail "Cannot connect — check credentials" # SQL functions if [[ ! "$DO_FN" =~ ^[Nn]$ ]]; then if confirm "SQL Functions"; then step "Deploying functions" psql -U "$DB_USER" -h "$DB_HOST" -p "$DB_PORT" -d "$DB_NAME" -f database/functions.sql -q && ok \ || fail "Functions deploy failed" else info "skipped" fi else section "SQL Functions" info "skipped" fi fi # ── UI ──────────────────────────────────────────────────────────────────────── if [ "$MODE" = "install" ] || [[ ! "$DO_BUILD" =~ ^[Nn]$ ]]; then if confirm "UI Build"; then step "Building" cd ui && npm run build > /dev/null 2>&1 && cd .. && ok \ || fail "UI build failed" else info "skipped" fi else section "UI Build" info "skipped" fi # ── Nginx ───────────────────────────────────────────────────────────────────── if [ -n "$NGINX_DOMAIN" ]; then if confirm "Nginx ($NGINX_DOMAIN)"; then CONF_NAME=$(echo "$NGINX_DOMAIN" | cut -d. -f1) CONF_PATH="/etc/nginx/sites-enabled/$CONF_NAME" CERT_PATH="/etc/letsencrypt/live/$NGINX_DOMAIN/fullchain.pem" TMP_CONF=$(mktemp) if [ -f "$CERT_PATH" ]; then cat > "$TMP_CONF" << NGINXEOF server { listen 80; listen [::]:80; server_name $NGINX_DOMAIN; location / { return 301 https://\$host\$request_uri; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name $NGINX_DOMAIN; ssl_certificate $CERT_PATH; ssl_certificate_key /etc/letsencrypt/live/$NGINX_DOMAIN/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; keepalive_timeout 70; sendfile on; client_max_body_size 80m; location / { proxy_pass http://localhost:${API_PORT:-3020}; } } NGINXEOF else cat > "$TMP_CONF" << NGINXEOF server { listen 80; listen [::]:80; server_name $NGINX_DOMAIN; location / { proxy_pass http://localhost:${API_PORT:-3020}; } } NGINXEOF fi step "Writing /etc/nginx/sites-enabled/$CONF_NAME" sudo cp "$TMP_CONF" "$CONF_PATH" && rm "$TMP_CONF" && ok \ || fail "Could not write nginx config (check sudo)" step "Testing nginx config" sudo nginx -t > /dev/null 2>&1 && ok \ || fail "nginx config invalid — run: sudo nginx -t" step "Reloading nginx" sudo systemctl reload nginx && ok \ || fail "nginx reload failed" if [ ! -f "$CERT_PATH" ]; then warn "No SSL cert found for $NGINX_DOMAIN" read -p " Run certbot now? [Y/n]: " _yn if [[ ! "$_yn" =~ ^[Nn]$ ]]; then step "Running certbot" sudo certbot --nginx -d "$NGINX_DOMAIN" --non-interactive --agree-tos \ --redirect -m "admin@$NGINX_DOMAIN" > /dev/null 2>&1 && ok \ || fail "certbot failed — run manually: sudo certbot --nginx -d $NGINX_DOMAIN" fi fi else info "skipped" fi else section "Nginx" info "skipped" fi # ── Systemd service ─────────────────────────────────────────────────────────── SERVICE_FILE="/etc/systemd/system/dataflow.service" if confirm "Systemd Service"; then if [ -f "$SERVICE_FILE" ]; then info "already installed" else step "Installing service" sudo cp "$SCRIPT_DIR/dataflow.service" "$SERVICE_FILE" && ok \ || fail "Could not install service" step "Enabling on boot" sudo systemctl daemon-reload && sudo systemctl enable dataflow > /dev/null 2>&1 && ok \ || fail "Could not enable service" fi else info "skipped" fi # ── API Server ──────────────────────────────────────────────────────────────── if [ "$MODE" = "install" ] || [[ ! "$DO_RESTART" =~ ^[Nn]$ ]]; then if confirm "API Server"; then if [ -f "$SERVICE_FILE" ]; then step "Restarting dataflow service" sudo systemctl restart dataflow && sleep 1 systemctl is-active --quiet dataflow && ok \ || fail "Service failed — check: journalctl -u dataflow -n 30" else info "systemd service not installed — start manually: node api/server.js" fi else info "skipped" fi else section "API Server" info "skipped" fi # ── Done ────────────────────────────────────────────────────────────────────── section "Done" echo " API: http://localhost:${API_PORT:-3020}" [ -n "$NGINX_DOMAIN" ] && echo " Web: https://$NGINX_DOMAIN" echo " Logs: journalctl -u dataflow -f" echo ""