diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 000000000..d21a59e5c --- /dev/null +++ b/SETUP.md @@ -0,0 +1,122 @@ +# PictoPy Dev Setup Guide + +This guide covers how to get the PictoPy development environment running on your machine — either automatically with a single script, or manually step by step. + +--- + +## Quick Start (Recommended) + +### Windows + +```powershell +# One-time: allow script execution for this session only +Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned + +# Run the setup script +.\dev-setup.ps1 +``` + +### Linux / macOS + +```bash +chmod +x dev-setup.sh +./dev-setup.sh +``` + +The script handles everything: installs prerequisites, creates conda environments, installs dependencies, launches all 3 services, and runs a health check. On re-runs it skips anything already installed. + +--- + +## What the Script Does + +**Phase 1 — Prerequisites:** Detects your OS and installs Node.js, Rust/Cargo, Miniconda, and platform-specific Tauri dependencies (webkit2gtk on Linux, VS C++ Build Tools + WebView2 on Windows) if missing. + +**Phase 2 — Service Setup:** Creates conda environments for the backend (`.env`) and sync microservice (`.sync-env`), installs pip dependencies, and runs `npm install` for the frontend. + +**Phase 3 — Launch:** Starts all 3 services in a single terminal with color-coded log prefixes `[BACKEND]`, `[SYNC]`, `[FRONTEND]`. Press `Ctrl+C` to stop all services cleanly. + +**Phase 4 — Health Check:** Pings both Python servers after 10 seconds to confirm they started successfully. + +--- + +## Services & Ports + +| Service | Port | Start Command | +|-------------------|-------|----------------------------------------| +| Python Backend | 52123 | `fastapi dev --port 52123` | +| Sync Microservice | 52124 | `fastapi dev --port 52124` | +| Tauri Frontend | — | `npm run tauri dev` | + +--- + +## Manual Setup + +If you prefer to set up manually or the script doesn't work on your OS: + +### Prerequisites + +- [Miniconda](https://www.anaconda.com/docs/getting-started/miniconda/install) +- [Node.js](https://nodejs.org) (v20+) +- [Rust](https://rustup.rs) +- Tauri system dependencies — see [tauri.app/start/prerequisites](https://tauri.app/start/prerequisites/) + +### Step 1 — Frontend + +```bash +cd frontend +npm install +npm run tauri dev +``` + +### Step 2 — Backend + +```bash +cd backend +conda create -p .env python=3.12 -y +conda activate ./.env +pip install -r requirements.txt +fastapi dev --port 52123 +``` + +### Step 3 — Sync Microservice + +```bash +cd sync-microservice +conda create -p .sync-env python=3.12 -y +conda activate ./.sync-env +pip install -r requirements.txt +fastapi dev --port 52124 +``` + +Run each in a separate terminal. + +--- + +## Troubleshooting + +**Missing `libGL.so.1` (OpenCV error on Linux)** +```bash +sudo apt install -y libglib2.0-dev libgl1-mesa-glx +``` + +**`gobject-2.0` not found** +```bash +sudo apt install -y libglib2.0-dev pkg-config +``` + +**Port already in use** + +Find and kill the process holding the port: +```bash +# Safe form — only kills if a process is found +lsof -ti :52123 | xargs -r kill -9 +lsof -ti :52124 | xargs -r kill -9 +``` + +**conda not found after fresh install** + +Restart your terminal. Conda adds itself to PATH in `.bashrc`/`.zshrc` but the change only takes effect in a new session. + +**Rust/cargo not found after fresh install (Windows)** + +Restart PowerShell after rustup installs, then re-run `dev-setup.ps1`. \ No newline at end of file diff --git a/dev-setup.ps1 b/dev-setup.ps1 new file mode 100644 index 000000000..370b3dc7b --- /dev/null +++ b/dev-setup.ps1 @@ -0,0 +1,277 @@ +# ============================================================================= +# PictoPy Dev Setup & Launch Script - Windows PowerShell +# Usage: Right-click -> "Run with PowerShell" OR +# Open PowerShell in PictoPy root and run: .\dev-setup.ps1 +# ============================================================================= + +# Allow script execution if blocked: +# Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned + +$ErrorActionPreference = "Stop" + +# ── Colors / Helpers ────────────────────────────────────────────────────────── +function Ok($msg) { Write-Host "✓ $msg" -ForegroundColor Green } +function Skip($msg) { Write-Host "→ $msg (already installed, skipping)" -ForegroundColor Cyan } +function Info($msg) { Write-Host "▸ $msg" -ForegroundColor Blue } +function Warn($msg) { Write-Host "⚠ $msg" -ForegroundColor Yellow } +function Fail($msg) { Write-Host "✗ $msg" -ForegroundColor Red; exit 1 } +function Header($msg) { + Write-Host "" + Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Blue + Write-Host " $msg" -ForegroundColor White + Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Blue +} + +# ── Validate we are in the PictoPy root ─────────────────────────────────────── +$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path +$FRONTEND_DIR = Join-Path $SCRIPT_DIR "frontend" +$BACKEND_DIR = Join-Path $SCRIPT_DIR "backend" +$SYNC_DIR = Join-Path $SCRIPT_DIR "sync-microservice" + +if (-not (Test-Path $FRONTEND_DIR) -or -not (Test-Path $BACKEND_DIR) -or -not (Test-Path $SYNC_DIR)) { + Fail "Please run this script from the root of the PictoPy repository." +} + +# ============================================================================= +# PHASE 1 — PREREQUISITES +# ============================================================================= +Header "Phase 1: Checking Prerequisites" + +# ── Winget ──────────────────────────────────────────────────────────────────── +if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { + Warn "winget not found. Install 'App Installer' from the Microsoft Store, then re-run." + Fail "winget is required for auto-installation." +} + +# ── Node.js ─────────────────────────────────────────────────────────────────── +if (Get-Command node -ErrorAction SilentlyContinue) { + Skip "Node.js ($(node --version))" +} else { + Info "Installing Node.js via winget..." + winget install -e --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements + if ($LASTEXITCODE -ne 0) { Fail "Node.js installation failed." } + $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") + if (-not (Get-Command node -ErrorAction SilentlyContinue)) { Fail "Node.js installed but not found in PATH. Please restart PowerShell and re-run." } + Ok "Node.js installed ($(node --version))" +} + +# ── Rust / Cargo ────────────────────────────────────────────────────────────── +if (Get-Command cargo -ErrorAction SilentlyContinue) { + Skip "Rust/Cargo ($(cargo --version))" +} else { + Info "Installing Rust via rustup..." + winget install -e --id Rustlang.Rustup --accept-source-agreements --accept-package-agreements + if ($LASTEXITCODE -ne 0) { Fail "Rust installation failed." } + $env:PATH = "$env:USERPROFILE\.cargo\bin;" + $env:PATH + if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { + Fail "Rust installed but cargo not in PATH. Please restart PowerShell and re-run." + } + Ok "Rust installed ($(cargo --version))" +} + +# ── Visual Studio C++ Build Tools (required by Tauri on Windows) ────────────── +Info "Checking Visual Studio C++ Build Tools..." +$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +$hasBuildTools = $false +if (Test-Path $vswhere) { + $vsInstalls = & $vswhere -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -format json 2>$null | ConvertFrom-Json + if ($vsInstalls.Count -gt 0) { $hasBuildTools = $true } +} +if ($hasBuildTools) { + Skip "Visual Studio C++ Build Tools" +} else { + Info "Installing Visual Studio Build Tools (this may take a while)..." + winget install -e --id Microsoft.VisualStudio.2022.BuildTools --accept-source-agreements --accept-package-agreements ` + --override "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" + if ($LASTEXITCODE -ne 0) { Fail "Visual Studio Build Tools installation failed." } + Ok "Visual Studio C++ Build Tools installed" +} + +# ── WebView2 (required by Tauri on Windows) ─────────────────────────────────── +Info "Checking WebView2 Runtime..." +$webview2Key = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" +if (Test-Path $webview2Key) { + Skip "WebView2 Runtime" +} else { + Info "Installing WebView2 Runtime..." + winget install -e --id Microsoft.EdgeWebView2Runtime --accept-source-agreements --accept-package-agreements + if ($LASTEXITCODE -ne 0) { Fail "WebView2 Runtime installation failed." } + Ok "WebView2 Runtime installed" +} + +# ── Miniconda ───────────────────────────────────────────────────────────────── +if (Get-Command conda -ErrorAction SilentlyContinue) { + Skip "Miniconda/Conda ($(conda --version))" +} else { + Info "Installing Miniconda..." + $minicondaUrl = "https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe" + $minicondaInstaller = "$env:TEMP\miniconda.exe" + Invoke-WebRequest -Uri $minicondaUrl -OutFile $minicondaInstaller + Start-Process -FilePath $minicondaInstaller -ArgumentList "/S /D=$env:USERPROFILE\miniconda3" -Wait + Remove-Item $minicondaInstaller + $env:PATH = "$env:USERPROFILE\miniconda3\Scripts;" + "$env:USERPROFILE\miniconda3;" + $env:PATH + if (-not (Get-Command conda -ErrorAction SilentlyContinue)) { + Fail "Miniconda installed but conda not in PATH. Please restart PowerShell and re-run." + } + Ok "Miniconda installed ($(conda --version))" +} + +# Initialize conda for PowerShell if not already done +$condaHook = "$env:USERPROFILE\miniconda3\shell\condabin\conda-hook.ps1" +if (Test-Path $condaHook) { & $condaHook } + +# ============================================================================= +# PHASE 2 — SERVICE SETUP +# ============================================================================= +Header "Phase 2: Setting Up Services" + +# ── Frontend ────────────────────────────────────────────────────────────────── +Info "Frontend: checking node_modules..." +if (Test-Path (Join-Path $FRONTEND_DIR "node_modules")) { + Skip "Frontend npm dependencies" +} else { + Info "Frontend: running npm install..." + Push-Location $FRONTEND_DIR + npm install + if ($LASTEXITCODE -ne 0) { Pop-Location; Fail "npm install failed." } + Pop-Location + Ok "Frontend dependencies installed" +} + +# ── Backend ─────────────────────────────────────────────────────────────────── +Info "Backend: checking conda environment..." +$BACKEND_ENV = Join-Path $BACKEND_DIR ".env" +if (Test-Path $BACKEND_ENV) { + Skip "Backend conda environment (.env)" +} else { + Info "Backend: creating conda environment with Python 3.12..." + conda create -p $BACKEND_ENV python=3.12 -y + if ($LASTEXITCODE -ne 0) { Fail "Failed to create backend conda environment." } + Ok "Backend conda environment created" +} + +Info "Backend: checking Python packages..." +conda run -p $BACKEND_ENV python -c "import fastapi" 2>$null +if ($LASTEXITCODE -eq 0) { + Skip "Backend Python packages" +} else { + Info "Backend: installing Python packages..." + conda run -p $BACKEND_ENV pip install -r (Join-Path $BACKEND_DIR "requirements.txt") + if ($LASTEXITCODE -ne 0) { Fail "Backend pip install failed." } + Ok "Backend Python packages installed" +} + +# ── Sync Microservice ───────────────────────────────────────────────────────── +Info "Sync microservice: checking conda environment..." +$SYNC_ENV = Join-Path $SYNC_DIR ".sync-env" +if (Test-Path $SYNC_ENV) { + Skip "Sync microservice conda environment (.sync-env)" +} else { + Info "Sync microservice: creating conda environment with Python 3.12..." + conda create -p $SYNC_ENV python=3.12 -y + if ($LASTEXITCODE -ne 0) { Fail "Failed to create sync microservice conda environment." } + Ok "Sync microservice conda environment created" +} + +Info "Sync microservice: checking Python packages..." +conda run -p $SYNC_ENV python -c "import fastapi" 2>$null +if ($LASTEXITCODE -eq 0) { + Skip "Sync microservice Python packages" +} else { + Info "Sync microservice: installing Python packages..." + conda run -p $SYNC_ENV pip install -r (Join-Path $SYNC_DIR "requirements.txt") + if ($LASTEXITCODE -ne 0) { Fail "Sync microservice pip install failed." } + Ok "Sync microservice Python packages installed" +} + +# ============================================================================= +# PHASE 3 — LAUNCH ALL SERVICES +# ============================================================================= +Header "Phase 3: Launching All Services" + +$jobs = @() + +# ── Launch Backend ──────────────────────────────────────────────────────────── +Info "Starting backend on port 52123..." +$backendJob = Start-Job -ScriptBlock { + Set-Location $using:BACKEND_DIR + conda run -p $using:BACKEND_ENV fastapi dev --port 52123 2>&1 | ForEach-Object { "[BACKEND] $_" } +} +$jobs += $backendJob + +# ── Launch Sync Microservice ────────────────────────────────────────────────── +Info "Starting sync microservice on port 52124..." +$syncJob = Start-Job -ScriptBlock { + Set-Location $using:SYNC_DIR + conda run -p $using:SYNC_ENV fastapi dev --port 52124 2>&1 | ForEach-Object { "[SYNC] $_" } +} +$jobs += $syncJob + +# ── Launch Frontend ─────────────────────────────────────────────────────────── +Info "Starting Tauri frontend..." +$frontendJob = Start-Job -ScriptBlock { + Set-Location $using:FRONTEND_DIR + npm run tauri dev 2>&1 | ForEach-Object { "[FRONTEND] $_" } +} +$jobs += $frontendJob + +# ============================================================================= +# PHASE 4 — HEALTH CHECK + STREAM LOGS +# ============================================================================= +Header "Phase 4: Health Check" + +Info "Waiting 10 seconds for services to start..." +Start-Sleep -Seconds 10 + +# Check backend +$allHealthy = $true +try { + Invoke-WebRequest -Uri "http://localhost:52123/docs" -UseBasicParsing -TimeoutSec 3 | Out-Null + Ok "Backend is running at http://localhost:52123" +} catch { + Warn "Backend did not respond — check [BACKEND] logs below" + $allHealthy = $false +} + +# Check sync microservice +try { + Invoke-WebRequest -Uri "http://localhost:52124/docs" -UseBasicParsing -TimeoutSec 3 | Out-Null + Ok "Sync microservice is running at http://localhost:52124" +} catch { + Warn "Sync microservice did not respond — check [SYNC] logs below" + $allHealthy = $false +} + +if (-not $allHealthy) { + Warn "One or more services failed to start. Check logs below then press Ctrl+C to exit." +} else { + Write-Host "" + Write-Host "✓ PictoPy dev environment is up!" -ForegroundColor Green + Write-Host " Press Ctrl+C to stop all services." + Write-Host "" +} + +# ── Stream logs from all jobs + handle Ctrl+C ──────────────────────────────── +try { + while ($true) { + foreach ($job in $jobs) { + $output = Receive-Job -Job $job -ErrorAction SilentlyContinue + if ($output) { + foreach ($line in $output) { + if ($line.StartsWith('[BACKEND]')) { Write-Host $line -ForegroundColor Green } + elseif ($line.StartsWith('[SYNC]')) { Write-Host $line -ForegroundColor Yellow } + else { Write-Host $line -ForegroundColor Blue } + } + } + } + Start-Sleep -Milliseconds 500 + } +} finally { + Write-Host "" + Warn "Shutting down all services..." + foreach ($job in $jobs) { + Stop-Job -Job $job -ErrorAction SilentlyContinue + Remove-Job -Job $job -ErrorAction SilentlyContinue + } + Ok "All services stopped. Goodbye!" +} \ No newline at end of file diff --git a/dev-setup.sh b/dev-setup.sh new file mode 100644 index 000000000..f56c3f41f --- /dev/null +++ b/dev-setup.sh @@ -0,0 +1,274 @@ +#!/bin/bash + +# ============================================================================= +# PictoPy Dev Setup & Launch Script +# Supports: Linux (Debian/Ubuntu/Arch) and macOS +# Usage: bash dev-setup.sh +# ============================================================================= + +set -euo pipefail + +# ── Colors ─────────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# ── Helpers ─────────────────────────────────────────────────────────────────── +ok() { echo -e "${GREEN}✓${NC} $1"; } +skip() { echo -e "${CYAN}→${NC} $1 (already installed, skipping)"; } +info() { echo -e "${BLUE}▸${NC} $1"; } +warn() { echo -e "${YELLOW}⚠${NC} $1"; } +fail() { echo -e "${RED}✗${NC} $1"; exit 1; } +header() { + echo "" + echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BOLD} $1${NC}" + echo -e "${BOLD}${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +# ── Detect OS ───────────────────────────────────────────────────────────────── +detect_os() { + if [[ "$OSTYPE" == "darwin"* ]]; then + OS="mac" + elif [[ -f /etc/debian_version ]]; then + OS="debian" + elif [[ -f /etc/arch-release ]]; then + OS="arch" + elif [[ -f /etc/fedora-release ]]; then + OS="fedora" + else + OS="linux-other" + fi + info "Detected OS: $OS" +} + +# ── Script directory ────────────────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FRONTEND_DIR="$SCRIPT_DIR/frontend" +BACKEND_DIR="$SCRIPT_DIR/backend" +SYNC_DIR="$SCRIPT_DIR/sync-microservice" + +# ── Validate PictoPy root ───────────────────────────────────────────────────── +if [[ ! -d "$FRONTEND_DIR" || ! -d "$BACKEND_DIR" || ! -d "$SYNC_DIR" ]]; then + fail "Please run this script from the root of the PictoPy repository." +fi + +# ── Process group cleanup on Ctrl+C ────────────────────────────────────────── +# Each service is started in its own process group (setsid) so kill -TERM -- -PID +# kills the entire group including fastapi/tauri child processes, not just the wrapper. +PGRPS=() +cleanup() { + echo "" + warn "Shutting down all services..." + for pgrp in "${PGRPS[@]}"; do + kill -TERM -- "-$pgrp" 2>/dev/null || true + done + ok "All services stopped. Goodbye!" + exit 0 +} +trap cleanup SIGINT SIGTERM + +# ============================================================================= +# PHASE 1 — PREREQUISITES +# ============================================================================= +header "Phase 1: Checking Prerequisites" + +detect_os + +# ── Node.js ─────────────────────────────────────────────────────────────────── +if command -v node &>/dev/null; then + skip "Node.js ($(node --version))" +else + info "Installing Node.js..." + if [[ "$OS" == "mac" ]]; then + brew install node || fail "Homebrew not found. Install it from https://brew.sh first." + elif [[ "$OS" == "debian" ]]; then + # Download to temp file and verify before executing + NODESOURCE_SCRIPT="$(mktemp)" + curl -fsSL https://deb.nodesource.com/setup_20.x -o "$NODESOURCE_SCRIPT" + sudo -E bash "$NODESOURCE_SCRIPT" + rm -f "$NODESOURCE_SCRIPT" + sudo apt-get install -y nodejs + elif [[ "$OS" == "arch" ]]; then + sudo pacman -S --noconfirm nodejs npm + else + warn "Cannot auto-install Node.js on this OS. Install manually from https://nodejs.org" + fail "Node.js is required." + fi + ok "Node.js installed ($(node --version))" +fi + +# ── Rust / Cargo ────────────────────────────────────────────────────────────── +if command -v cargo &>/dev/null; then + skip "Rust/Cargo ($(cargo --version))" +else + info "Installing Rust via rustup..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" + ok "Rust installed ($(cargo --version))" +fi + +# ── Tauri system dependencies (Linux only) ──────────────────────────────────── +if [[ "$OS" == "debian" ]]; then + info "Checking Tauri system dependencies..." + TAURI_DEPS=( + libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + libglib2.0-dev libgl1-mesa-glx pkg-config build-essential + curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev + ) + MISSING_DEPS=() + for dep in "${TAURI_DEPS[@]}"; do + dpkg -s "$dep" &>/dev/null || MISSING_DEPS+=("$dep") + done + if [[ ${#MISSING_DEPS[@]} -eq 0 ]]; then + skip "All Tauri system dependencies" + else + info "Installing missing system deps: ${MISSING_DEPS[*]}" + sudo apt-get update -qq + sudo apt-get install -y "${MISSING_DEPS[@]}" + ok "Tauri system dependencies installed" + fi +elif [[ "$OS" == "arch" ]]; then + info "Checking Tauri system dependencies (Arch)..." + sudo pacman -S --needed --noconfirm webkit2gtk base-devel curl wget file openssl \ + appmenu-gtk-module gtk3 libappindicator-gtk3 librsvg libvips + ok "Tauri system dependencies installed" +elif [[ "$OS" == "mac" ]]; then + skip "Tauri system dependencies (not needed on macOS)" +fi + +# ── Miniconda ───────────────────────────────────────────────────────────────── +if command -v conda &>/dev/null; then + skip "Miniconda/Conda ($(conda --version))" +else + info "Installing Miniconda..." + if [[ "$OS" == "mac" ]]; then + MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh" + [[ "$(uname -m)" != "arm64" ]] && MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh" + else + MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh" + fi + # Download to temp file + MINICONDA_SCRIPT="$(mktemp)" + curl -fsSL "$MINICONDA_URL" -o "$MINICONDA_SCRIPT" + bash "$MINICONDA_SCRIPT" -b -p "$HOME/miniconda3" + rm -f "$MINICONDA_SCRIPT" + export PATH="$HOME/miniconda3/bin:$PATH" + conda init bash 2>/dev/null || true + ok "Miniconda installed" + warn "You may need to restart your terminal after setup for conda to work globally." +fi + +# Ensure conda is available in this session +export PATH="$(conda info --base 2>/dev/null)/bin:$PATH" + +# ============================================================================= +# PHASE 2 — SERVICE SETUP +# ============================================================================= +header "Phase 2: Setting Up Services" + +# ── Frontend ────────────────────────────────────────────────────────────────── +info "Frontend: checking node_modules..." +if [[ -d "$FRONTEND_DIR/node_modules" ]]; then + skip "Frontend npm dependencies" +else + info "Frontend: running npm install..." + (cd "$FRONTEND_DIR" && npm install) + ok "Frontend dependencies installed" +fi + +# ── Backend ─────────────────────────────────────────────────────────────────── +info "Backend: checking conda environment..." +BACKEND_ENV="$BACKEND_DIR/.env" +if [[ -d "$BACKEND_ENV" ]]; then + skip "Backend conda environment (.env)" +else + info "Backend: creating conda environment with Python 3.12..." + conda create -p "$BACKEND_ENV" python=3.12 -y + ok "Backend conda environment created" +fi + +# Always run pip install — idempotent and catches requirements.txt changes +info "Backend: installing/syncing Python dependencies..." +conda run -p "$BACKEND_ENV" pip install -r "$BACKEND_DIR/requirements.txt" --quiet +ok "Backend Python packages ready" + +# ── Sync Microservice ───────────────────────────────────────────────────────── +info "Sync microservice: checking conda environment..." +SYNC_ENV="$SYNC_DIR/.sync-env" +if [[ -d "$SYNC_ENV" ]]; then + skip "Sync microservice conda environment (.sync-env)" +else + info "Sync microservice: creating conda environment with Python 3.12..." + conda create -p "$SYNC_ENV" python=3.12 -y + ok "Sync microservice conda environment created" +fi + +# Always run pip install — idempotent and catches requirements.txt changes +info "Sync microservice: installing/syncing Python dependencies..." +conda run -p "$SYNC_ENV" pip install -r "$SYNC_DIR/requirements.txt" --quiet +ok "Sync microservice Python packages ready" + +# ============================================================================= +# PHASE 3 — LAUNCH ALL SERVICES +# ============================================================================= +header "Phase 3: Launching All Services" + +# Launch each service in its own process group via setsid +# This ensures kill -TERM -- -PGRP kills fastapi/tauri children too + +info "Starting backend on port 52123..." +setsid bash -c "cd '$BACKEND_DIR' && conda run -p '$BACKEND_ENV' fastapi dev --port 52123 2>&1 | \ + while IFS= read -r line; do echo -e '${GREEN}[BACKEND]${NC} '"'"'$line'"'"'; done" & +PGRPS+=("$!") + +info "Starting sync microservice on port 52124..." +setsid bash -c "cd '$SYNC_DIR' && conda run -p '$SYNC_ENV' fastapi dev --port 52124 2>&1 | \ + while IFS= read -r line; do echo -e '${YELLOW}[SYNC]${NC} '"'"'$line'"'"'; done" & +PGRPS+=("$!") + +info "Starting Tauri frontend..." +setsid bash -c "cd '$FRONTEND_DIR' && npm run tauri dev 2>&1 | \ + while IFS= read -r line; do echo -e '${BLUE}[FRONTEND]${NC} '"'"'$line'"'"'; done" & +PGRPS+=("$!") + +# ============================================================================= +# PHASE 4 — HEALTH CHECK +# ============================================================================= +header "Phase 4: Health Check" + +info "Waiting 10 seconds for services to start..." +sleep 10 + +HEALTHY=true + +if curl -s --max-time 3 "http://localhost:52123/docs" &>/dev/null; then + ok "Backend is running at http://localhost:52123" +else + warn "Backend did not respond on port 52123" + HEALTHY=false +fi + +if curl -s --max-time 3 "http://localhost:52124/docs" &>/dev/null; then + ok "Sync microservice is running at http://localhost:52124" +else + warn "Sync microservice did not respond on port 52124" + HEALTHY=false +fi + +if [[ "$HEALTHY" == false ]]; then + warn "One or more services failed to start. Check logs above." + warn "Press Ctrl+C to stop all services." +else + echo "" + echo -e "${BOLD}${GREEN}✓ PictoPy dev environment is up!${NC}" + echo -e " Press ${BOLD}Ctrl+C${NC} to stop all services." + echo "" +fi + +# Keep alive so logs stream and Ctrl+C triggers cleanup +wait \ No newline at end of file