feat: Add Linux deployment scripts and server logs view
- Add deployment/linux/ with deploy.sh for deploying from Claude-Agent LXC to Windows server - Add ServerLogsView.vue for viewing server logs from frontend - Add shared/routes/system.py for system health endpoints - Update CLAUDE.md with quick deploy instructions - Improve Windows deployment scripts (ROA2WEB-Console.ps1) - Fix OCR service validation and worker pool improvements - Update environment config examples - Various script permission and startup fixes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
215
deployment/linux/README.md
Normal file
215
deployment/linux/README.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# ROA2WEB Linux Deployment
|
||||
|
||||
Deploy ROA2WEB to Windows IIS production server from Linux/LXC (claude-agent).
|
||||
|
||||
## Quick Deploy (TLDR)
|
||||
|
||||
```bash
|
||||
# Din orice director ROA2WEB (main, worktree, sau branch):
|
||||
# Exemple locații posibile:
|
||||
# /workspace/roa2web # clone principal
|
||||
# /workspace/.worktrees/roa2web/fix/fix-bon # worktree pentru fix
|
||||
# /workspace/.worktrees/roa2web/feature/xxx # worktree pentru feature
|
||||
|
||||
# Deploy complet (frontend + backend)
|
||||
./deployment/linux/deploy.sh
|
||||
|
||||
# Doar frontend
|
||||
./deployment/linux/deploy.sh frontend
|
||||
|
||||
# Doar backend
|
||||
./deployment/linux/deploy.sh backend
|
||||
|
||||
# Test conexiune SSH
|
||||
./deployment/linux/deploy.sh test
|
||||
```
|
||||
|
||||
**După deploy**: Serverul face auto-deploy în maxim 5 minute (scheduled task).
|
||||
|
||||
## Current Setup (claude-agent LXC)
|
||||
|
||||
SSH-ul este **deja configurat** pe acest LXC:
|
||||
|
||||
```bash
|
||||
# Verificare conexiune (ar trebui să funcționeze direct)
|
||||
ssh roa2web-prod "echo OK"
|
||||
```
|
||||
|
||||
**Configurare existentă** (`~/.ssh/config`):
|
||||
```
|
||||
Host roa2web-prod
|
||||
HostName 10.0.20.36
|
||||
Port 22122
|
||||
User romfast
|
||||
IdentityFile ~/.ssh/roa2web_deploy
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Node.js 16+** - pentru build frontend
|
||||
2. **SSH access** - deja configurat pe claude-agent LXC
|
||||
|
||||
## First-Time Setup (doar dacă SSH nu funcționează)
|
||||
|
||||
### 1. Generate SSH Key
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/roa2web_deploy -C "roa2web-deploy-lxc"
|
||||
```
|
||||
|
||||
### 2. Configure SSH Host
|
||||
|
||||
Add to `~/.ssh/config`:
|
||||
|
||||
```
|
||||
Host roa2web-prod
|
||||
HostName 10.0.20.36
|
||||
Port 22122
|
||||
User romfast
|
||||
IdentityFile ~/.ssh/roa2web_deploy
|
||||
IdentitiesOnly yes
|
||||
StrictHostKeyChecking accept-new
|
||||
```
|
||||
|
||||
### 3. Add Public Key to Server
|
||||
|
||||
Copy the content of `~/.ssh/roa2web_deploy.pub`:
|
||||
|
||||
```bash
|
||||
cat ~/.ssh/roa2web_deploy.pub
|
||||
```
|
||||
|
||||
On Windows server (via RDP or SSH):
|
||||
1. Connect: `ssh romfast@10.0.20.36 -p 22122`
|
||||
2. Add key to `C:\Users\romfast\.ssh\authorized_keys`
|
||||
|
||||
### 4. Test Connection
|
||||
|
||||
```bash
|
||||
ssh roa2web-prod "echo OK"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
cd /workspace/.worktrees/roa2web/fix/fix-bon
|
||||
|
||||
# Full deployment (frontend + backend)
|
||||
./deployment/linux/deploy.sh
|
||||
|
||||
# Frontend only
|
||||
./deployment/linux/deploy.sh frontend
|
||||
|
||||
# Backend only
|
||||
./deployment/linux/deploy.sh backend
|
||||
|
||||
# Test SSH connection
|
||||
./deployment/linux/deploy.sh test
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
[LXC] npm run build → dist/
|
||||
↓
|
||||
Package: deploy-package-TIMESTAMP/
|
||||
├── frontend/
|
||||
├── backend/
|
||||
├── shared/
|
||||
└── scripts/
|
||||
↓
|
||||
SCP → C:\Temp\deploy-TIMESTAMP\
|
||||
↓
|
||||
[Server] Check-And-Deploy.ps1 (scheduled task, every 5 min)
|
||||
↓
|
||||
Auto-deploy to C:\inetpub\wwwroot\roa2web\
|
||||
```
|
||||
|
||||
## Server Configuration
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| Host | 10.0.20.36 |
|
||||
| SSH Port | 22122 |
|
||||
| User | romfast |
|
||||
| Remote Path | C:\Temp |
|
||||
| Install Path | C:\inetpub\wwwroot\roa2web |
|
||||
| Logs Path | C:\inetpub\wwwroot\roa2web\logs |
|
||||
| **Scripts Path** | **C:\TEMP\ROA2WEB-Scripts** |
|
||||
|
||||
### Permanent Scripts Location
|
||||
|
||||
Scripturile de deploy (`Check-And-Deploy.ps1`, `ROA2WEB-Console.ps1`) rulează din:
|
||||
```
|
||||
C:\TEMP\ROA2WEB-Scripts\
|
||||
```
|
||||
|
||||
**IMPORTANT**: Când modifici scripturile, trebuie să le copiezi și aici:
|
||||
```bash
|
||||
# După deploy.sh, copiază scripturile actualizate în locația permanentă:
|
||||
ssh roa2web-prod "powershell -Command \"Copy-Item -Path 'C:\\Temp\\deploy-*\\scripts\\*.ps1' -Destination 'C:\\TEMP\\ROA2WEB-Scripts\\' -Force\""
|
||||
```
|
||||
|
||||
### What Deploy Preserves
|
||||
|
||||
Deploy-ul păstrează automat (NU le șterge):
|
||||
- **`.env`** - Configurația mediului (credențiale, setări)
|
||||
- **`data/`** - Directorul cu baze de date SQLite:
|
||||
- `data/receipts/receipts_prod.db` - Bonuri fiscale
|
||||
- `data/telegram/telegram_prod.db` - Sesiuni Telegram
|
||||
- `data/cache/` - Cache SQLite
|
||||
- `data/receipts/uploads/` - Fișiere uploadate
|
||||
|
||||
## Production Logs
|
||||
|
||||
```bash
|
||||
# View backend stderr (errors)
|
||||
ssh roa2web-prod "powershell -Command \"Get-Content 'C:\\inetpub\\wwwroot\\roa2web\\logs\\backend-stderr.log' -Tail 100\""
|
||||
|
||||
# View backend stdout (info logs)
|
||||
ssh roa2web-prod "powershell -Command \"Get-Content 'C:\\inetpub\\wwwroot\\roa2web\\logs\\backend-stdout.log' -Tail 100\""
|
||||
|
||||
# Filter OCR errors
|
||||
ssh roa2web-prod "powershell -Command \"Get-Content 'C:\\inetpub\\wwwroot\\roa2web\\logs\\backend-stderr.log' -Tail 200\"" | grep -i ocr
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SSH Connection Failed
|
||||
|
||||
```bash
|
||||
# Check SSH config
|
||||
cat ~/.ssh/config
|
||||
|
||||
# Test with verbose output
|
||||
ssh -v roa2web-prod "echo test"
|
||||
|
||||
# Check if key exists
|
||||
ls -la ~/.ssh/roa2web_deploy*
|
||||
```
|
||||
|
||||
### Build Failed
|
||||
|
||||
```bash
|
||||
# Check Node.js version
|
||||
node --version
|
||||
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
```
|
||||
|
||||
### Transfer Failed
|
||||
|
||||
```bash
|
||||
# Test SCP manually
|
||||
scp test.txt roa2web-prod:C:/Temp/
|
||||
|
||||
# Check Windows firewall (port 22122)
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Windows Deployment](../windows/docs/WINDOWS_DEPLOYMENT.md)
|
||||
- [Two-Tier IIS Architecture](../windows/docs/TWO-TIER-IIS-DEPLOYMENT.md)
|
||||
- [ROA2WEB Console](../windows/scripts/ROA2WEB-Console.ps1)
|
||||
381
deployment/linux/deploy.sh
Executable file
381
deployment/linux/deploy.sh
Executable file
@@ -0,0 +1,381 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ROA2WEB Linux Deployment Script
|
||||
# Builds and deploys to Windows IIS server via SSH/SCP
|
||||
#
|
||||
# Usage:
|
||||
# ./deploy.sh # Full deploy (frontend + backend)
|
||||
# ./deploy.sh frontend # Frontend only
|
||||
# ./deploy.sh backend # Backend only
|
||||
# ./deploy.sh test # Test SSH connection only
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# =============================================================================
|
||||
# CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
# SSH Configuration (matches deploy-config.json)
|
||||
SSH_HOST="roa2web-prod" # Uses ~/.ssh/config
|
||||
REMOTE_PATH="C:/Temp"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
log_step() {
|
||||
echo -e "\n${CYAN}[*] $1${NC}" >&2
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN} [OK] $1${NC}" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED} [ERROR] $1${NC}" >&2
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW} [WARN] $1${NC}" >&2
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e " [INFO] $1" >&2
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# SSH CONNECTION TEST
|
||||
# =============================================================================
|
||||
|
||||
test_ssh_connection() {
|
||||
log_step "Testing SSH connection to $SSH_HOST..."
|
||||
|
||||
if ssh -o ConnectTimeout=10 "$SSH_HOST" "echo 'Connection successful'" 2>/dev/null; then
|
||||
log_success "SSH connection working"
|
||||
return 0
|
||||
else
|
||||
log_error "SSH connection failed"
|
||||
echo ""
|
||||
echo "Please ensure:"
|
||||
echo " 1. SSH key is configured in ~/.ssh/config"
|
||||
echo " 2. Public key is added to server's authorized_keys"
|
||||
echo " 3. Server is reachable on port 22122"
|
||||
echo ""
|
||||
echo "SSH config should contain:"
|
||||
echo " Host roa2web-prod"
|
||||
echo " HostName 10.0.20.36"
|
||||
echo " Port 22122"
|
||||
echo " User Administrator"
|
||||
echo " IdentityFile ~/.ssh/roa2web_deploy"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# BUILD FRONTEND
|
||||
# =============================================================================
|
||||
|
||||
build_frontend() {
|
||||
log_step "Building frontend..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Check Node.js
|
||||
if ! command -v node &> /dev/null; then
|
||||
log_error "Node.js not found. Please install Node.js 16+"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_VERSION=$(node --version)
|
||||
log_info "Node.js: $NODE_VERSION"
|
||||
|
||||
# Check if node_modules exists
|
||||
if [ ! -d "node_modules" ]; then
|
||||
log_step "Installing npm dependencies..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# Build production
|
||||
log_step "Running production build..."
|
||||
NODE_ENV=production npm run build
|
||||
|
||||
if [ ! -d "dist" ]; then
|
||||
log_error "Build failed: dist/ directory not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Count files
|
||||
FILE_COUNT=$(find dist -type f | wc -l)
|
||||
TOTAL_SIZE=$(du -sh dist | cut -f1)
|
||||
log_success "Build completed: $FILE_COUNT files ($TOTAL_SIZE)"
|
||||
|
||||
# Verify web.config
|
||||
if [ -f "dist/web.config" ]; then
|
||||
log_success "web.config present in build"
|
||||
else
|
||||
log_warning "web.config NOT found in build"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CREATE DEPLOYMENT PACKAGE
|
||||
# =============================================================================
|
||||
|
||||
create_package() {
|
||||
local COMPONENT=$1
|
||||
local TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
local PACKAGE_DIR="$PROJECT_ROOT/deploy-package-$TIMESTAMP"
|
||||
|
||||
log_step "Creating deployment package..."
|
||||
log_info "Package: $PACKAGE_DIR"
|
||||
|
||||
mkdir -p "$PACKAGE_DIR"
|
||||
|
||||
# Frontend
|
||||
if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "frontend" ]; then
|
||||
log_step "Packaging frontend..."
|
||||
mkdir -p "$PACKAGE_DIR/frontend"
|
||||
cp -r "$PROJECT_ROOT/dist/"* "$PACKAGE_DIR/frontend/"
|
||||
log_success "Frontend packaged"
|
||||
fi
|
||||
|
||||
# Backend
|
||||
if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "backend" ]; then
|
||||
log_step "Packaging backend..."
|
||||
mkdir -p "$PACKAGE_DIR/backend"
|
||||
|
||||
# Copy backend files (excluding venv, __pycache__, logs, .env)
|
||||
rsync -av --progress \
|
||||
--exclude 'venv' \
|
||||
--exclude '__pycache__' \
|
||||
--exclude '*.pyc' \
|
||||
--exclude '*.pyo' \
|
||||
--exclude '.pytest_cache' \
|
||||
--exclude 'logs' \
|
||||
--exclude '.env' \
|
||||
--exclude '.env.local' \
|
||||
--exclude '*.log' \
|
||||
"$PROJECT_ROOT/backend/" "$PACKAGE_DIR/backend/"
|
||||
|
||||
# Copy .env.example if exists
|
||||
if [ -f "$PROJECT_ROOT/backend/.env.example" ]; then
|
||||
cp "$PROJECT_ROOT/backend/.env.example" "$PACKAGE_DIR/backend/"
|
||||
fi
|
||||
|
||||
log_success "Backend packaged"
|
||||
fi
|
||||
|
||||
# Shared modules
|
||||
if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "backend" ]; then
|
||||
log_step "Packaging shared modules..."
|
||||
mkdir -p "$PACKAGE_DIR/shared"
|
||||
|
||||
rsync -av --progress \
|
||||
--exclude '__pycache__' \
|
||||
--exclude '*.pyc' \
|
||||
--exclude 'tests' \
|
||||
"$PROJECT_ROOT/shared/" "$PACKAGE_DIR/shared/"
|
||||
|
||||
log_success "Shared modules packaged"
|
||||
fi
|
||||
|
||||
# Config templates
|
||||
log_step "Packaging config templates..."
|
||||
if [ -d "$PROJECT_ROOT/deployment/config" ]; then
|
||||
mkdir -p "$PACKAGE_DIR/config"
|
||||
cp -r "$PROJECT_ROOT/deployment/config/"* "$PACKAGE_DIR/config/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Deployment scripts
|
||||
log_step "Packaging deployment scripts..."
|
||||
mkdir -p "$PACKAGE_DIR/scripts"
|
||||
|
||||
SCRIPTS=(
|
||||
"ROA2WEB-Console.ps1"
|
||||
"Install-ROA2WEB.ps1"
|
||||
"Check-And-Deploy.ps1"
|
||||
)
|
||||
|
||||
for script in "${SCRIPTS[@]}"; do
|
||||
if [ -f "$PROJECT_ROOT/deployment/windows/scripts/$script" ]; then
|
||||
cp "$PROJECT_ROOT/deployment/windows/scripts/$script" "$PACKAGE_DIR/scripts/"
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Deployment scripts packaged"
|
||||
|
||||
# Create README
|
||||
cat > "$PACKAGE_DIR/README.txt" << EOF
|
||||
================================================================================
|
||||
ROA2WEB DEPLOYMENT PACKAGE
|
||||
Generated: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
From: Linux/LXC deployment script
|
||||
================================================================================
|
||||
|
||||
CONTENTS:
|
||||
---------
|
||||
backend/ Unified FastAPI backend
|
||||
frontend/ Vue.js SPA (production build)
|
||||
shared/ Shared Python modules
|
||||
config/ Configuration templates
|
||||
scripts/ PowerShell deployment scripts
|
||||
|
||||
DEPLOYMENT:
|
||||
-----------
|
||||
Server will auto-deploy within 5 minutes (Check-And-Deploy.ps1 scheduled task)
|
||||
|
||||
Or manually:
|
||||
cd scripts
|
||||
.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll
|
||||
|
||||
================================================================================
|
||||
EOF
|
||||
|
||||
# Calculate package size
|
||||
PACKAGE_SIZE=$(du -sh "$PACKAGE_DIR" | cut -f1)
|
||||
FILE_COUNT=$(find "$PACKAGE_DIR" -type f | wc -l)
|
||||
|
||||
log_success "Package created: $FILE_COUNT files ($PACKAGE_SIZE)"
|
||||
|
||||
# Return path via global variable (avoid stdout pollution from rsync)
|
||||
CREATED_PACKAGE_DIR="$PACKAGE_DIR"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# TRANSFER TO SERVER
|
||||
# =============================================================================
|
||||
|
||||
transfer_to_server() {
|
||||
local PACKAGE_DIR=$1
|
||||
local TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
local REMOTE_DEPLOY_DIR="deploy-$TIMESTAMP"
|
||||
local REMOTE_FULL_PATH="C:\\Temp\\$REMOTE_DEPLOY_DIR"
|
||||
|
||||
log_step "Transferring to server..."
|
||||
log_info "Remote path: C:\\Temp\\$REMOTE_DEPLOY_DIR"
|
||||
|
||||
# Create remote directory
|
||||
log_step "Creating remote directory..."
|
||||
ssh "$SSH_HOST" "New-Item -ItemType Directory -Path 'C:\\Temp\\$REMOTE_DEPLOY_DIR' -Force"
|
||||
log_success "Remote directory created"
|
||||
|
||||
# Transfer files one directory at a time (Bitvise SCP compatibility)
|
||||
log_step "Uploading files via SCP..."
|
||||
|
||||
for dir in "$PACKAGE_DIR"/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
local dirname=$(basename "$dir")
|
||||
log_info "Uploading $dirname/..."
|
||||
scp -r "$dir" "roa2web-prod:C:\\Temp\\$REMOTE_DEPLOY_DIR\\$dirname"
|
||||
fi
|
||||
done
|
||||
|
||||
# Upload root files (README, etc.)
|
||||
for file in "$PACKAGE_DIR"/*; do
|
||||
if [ -f "$file" ]; then
|
||||
local filename=$(basename "$file")
|
||||
scp "$file" "roa2web-prod:C:\\Temp\\$REMOTE_DEPLOY_DIR\\$filename"
|
||||
fi
|
||||
done
|
||||
|
||||
log_success "Transfer completed"
|
||||
|
||||
# Verify transfer
|
||||
log_step "Verifying transfer..."
|
||||
REMOTE_FILE_COUNT=$(ssh "$SSH_HOST" "(Get-ChildItem -Path 'C:\\Temp\\$REMOTE_DEPLOY_DIR' -Recurse -File).Count")
|
||||
log_success "Remote files: $REMOTE_FILE_COUNT"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo -e "${GREEN} DEPLOYMENT PACKAGE UPLOADED${NC}"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo " Remote path: C:\\Temp\\$REMOTE_DEPLOY_DIR"
|
||||
echo ""
|
||||
echo " Server will auto-deploy within 5 minutes."
|
||||
echo " Or manually deploy:"
|
||||
echo " ssh $SSH_HOST"
|
||||
echo " cd C:\\Temp\\$REMOTE_DEPLOY_DIR\\scripts"
|
||||
echo " .\\ROA2WEB-Console.ps1"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CLEANUP
|
||||
# =============================================================================
|
||||
|
||||
cleanup_local_package() {
|
||||
local PACKAGE_DIR=$1
|
||||
|
||||
log_step "Cleaning up local package..."
|
||||
rm -rf "$PACKAGE_DIR"
|
||||
log_success "Local package removed"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MAIN
|
||||
# =============================================================================
|
||||
|
||||
main() {
|
||||
local COMPONENT=${1:-all}
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " ROA2WEB Linux Deployment Script"
|
||||
echo " Component: $COMPONENT"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Handle test command
|
||||
if [ "$COMPONENT" = "test" ]; then
|
||||
test_ssh_connection
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Validate component
|
||||
case $COMPONENT in
|
||||
all|frontend|backend)
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 [all|frontend|backend|test]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Test SSH connection first
|
||||
if ! test_ssh_connection; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build frontend if needed
|
||||
if [ "$COMPONENT" = "all" ] || [ "$COMPONENT" = "frontend" ]; then
|
||||
build_frontend
|
||||
fi
|
||||
|
||||
# Create package (sets CREATED_PACKAGE_DIR global variable)
|
||||
create_package "$COMPONENT"
|
||||
|
||||
# Transfer to server
|
||||
transfer_to_server "$CREATED_PACKAGE_DIR"
|
||||
|
||||
# Cleanup
|
||||
cleanup_local_package "$CREATED_PACKAGE_DIR"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Deployment completed successfully!${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Run main with all arguments
|
||||
main "$@"
|
||||
@@ -301,6 +301,10 @@ function Invoke-Deployment {
|
||||
# Capture exit code IMMEDIATELY (before any other command that might reset it)
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
# Run OCR dependency check with auto-install
|
||||
Write-Log -Message "Checking and installing OCR dependencies..." -Level "INFO"
|
||||
& $consoleScript -NonInteractive -Action InstallOCR 2>&1 | ForEach-Object { Write-Log -Message $_ -Level "INFO" }
|
||||
|
||||
Pop-Location
|
||||
|
||||
# Check if exit code indicates success (0 = success)
|
||||
|
||||
@@ -72,6 +72,8 @@ $script:Config = @{
|
||||
FrontendPath = Join-Path $InstallPath "frontend"
|
||||
LogsPath = Join-Path $InstallPath "logs"
|
||||
TempPath = Join-Path $InstallPath "temp"
|
||||
# IMPORTANT: venv is OUTSIDE InstallPath to survive deployments!
|
||||
VenvPath = "C:\inetpub\wwwroot\roa2web-venv"
|
||||
PythonVersion = $PythonVersion
|
||||
ServicePort = $ServicePort
|
||||
IISSiteName = $IISSiteName
|
||||
@@ -302,24 +304,46 @@ function New-DirectoryStructure {
|
||||
}
|
||||
|
||||
function Install-PythonDependencies {
|
||||
Write-Step "Installing Python dependencies..."
|
||||
Write-Step "Setting up Python virtual environment..."
|
||||
|
||||
$requirementsPath = Join-Path $Config.BackendPath "requirements.txt"
|
||||
$venvPath = $Config.VenvPath
|
||||
$venvPython = Join-Path $venvPath "Scripts\python.exe"
|
||||
$venvPip = Join-Path $venvPath "Scripts\pip.exe"
|
||||
|
||||
# Create venv if it doesn't exist
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
Write-Step "Creating virtual environment at $venvPath..."
|
||||
try {
|
||||
& python -m venv $venvPath
|
||||
Write-Success "Virtual environment created"
|
||||
} catch {
|
||||
throw "Failed to create virtual environment: $_"
|
||||
}
|
||||
} else {
|
||||
Write-Success "Virtual environment already exists"
|
||||
}
|
||||
|
||||
# Upgrade pip in venv
|
||||
Write-Step "Upgrading pip in virtual environment..."
|
||||
try {
|
||||
& $venvPython -m pip install --upgrade pip
|
||||
Write-Success "Pip upgraded"
|
||||
} catch {
|
||||
Write-Warning "Could not upgrade pip: $_"
|
||||
}
|
||||
|
||||
# Install dependencies
|
||||
if (-not (Test-Path $requirementsPath)) {
|
||||
Write-Warning "requirements.txt not found at $requirementsPath"
|
||||
Write-Warning "Please copy backend files first, then run this script again"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Step "Installing Python dependencies in virtual environment..."
|
||||
try {
|
||||
# Upgrade pip first
|
||||
& python -m pip install --upgrade pip
|
||||
|
||||
# Install dependencies
|
||||
& python -m pip install -r $requirementsPath
|
||||
|
||||
Write-Success "Python dependencies installed successfully"
|
||||
& $venvPip install -r $requirementsPath
|
||||
Write-Success "Python dependencies installed successfully in venv"
|
||||
} catch {
|
||||
throw "Failed to install Python dependencies: $_"
|
||||
}
|
||||
@@ -359,16 +383,21 @@ function New-WindowsService {
|
||||
Write-Success "Existing service removed"
|
||||
}
|
||||
|
||||
# Get Python path
|
||||
$pythonPath = (Get-Command python).Source
|
||||
# Get Python path from venv
|
||||
$venvPython = Join-Path $Config.VenvPath "Scripts\python.exe"
|
||||
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
throw "Virtual environment Python not found at $venvPython. Run Install-PythonDependencies first."
|
||||
}
|
||||
|
||||
$uvicornModule = "uvicorn"
|
||||
$appModule = "main:app"
|
||||
|
||||
# NSSM service creation
|
||||
try {
|
||||
# Install service
|
||||
# Install service using venv Python
|
||||
# NOTE: Using --workers 1 because Telegram bot requires single instance (polling conflict)
|
||||
& nssm install $Config.ServiceName $pythonPath "-m" $uvicornModule $appModule "--host" "127.0.0.1" "--port" $Config.ServicePort.ToString() "--workers" "1"
|
||||
& nssm install $Config.ServiceName $venvPython "-m" $uvicornModule $appModule "--host" "127.0.0.1" "--port" $Config.ServicePort.ToString() "--workers" "1"
|
||||
|
||||
# Set service configuration
|
||||
& nssm set $Config.ServiceName DisplayName $Config.ServiceDisplayName
|
||||
@@ -530,6 +559,7 @@ function Show-Summary {
|
||||
Write-Host "`nInstallation Details:" -ForegroundColor Yellow
|
||||
Write-Host " Install Path: $($Config.InstallPath)"
|
||||
Write-Host " Backend Path: $($Config.BackendPath)"
|
||||
Write-Host " Virtual Env: $($Config.VenvPath)"
|
||||
Write-Host " Frontend Path: $($Config.FrontendPath)"
|
||||
Write-Host " Service Name: $($Config.ServiceName)"
|
||||
Write-Host " Service Port: $($Config.ServicePort)"
|
||||
|
||||
@@ -54,7 +54,7 @@ param(
|
||||
|
||||
[ValidateSet("DeployBackend", "DeployFrontend", "DeployAll",
|
||||
"StartService", "StopService", "RestartService",
|
||||
"Status", "ViewLogs")]
|
||||
"Status", "ViewLogs", "CheckOCR", "InstallOCR")]
|
||||
[string]$Action = "",
|
||||
|
||||
[string]$PackagePath = ""
|
||||
@@ -84,6 +84,8 @@ $script:Config = @{
|
||||
# Installation Paths
|
||||
InstallRoot = "C:\inetpub\wwwroot\roa2web"
|
||||
BackendPath = "C:\inetpub\wwwroot\roa2web\backend"
|
||||
# IMPORTANT: venv is OUTSIDE roa2web to survive deployments!
|
||||
VenvPath = "C:\inetpub\wwwroot\roa2web-venv"
|
||||
FrontendPath = "C:\inetpub\wwwroot\roa2web\frontend"
|
||||
SharedPath = "C:\inetpub\wwwroot\roa2web\shared"
|
||||
ConfigPath = "C:\inetpub\wwwroot\roa2web\config"
|
||||
@@ -132,6 +134,128 @@ function Write-Info {
|
||||
Write-Host " [*] $Message" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
function Get-PythonPaths {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Returns Python and pip paths, preferring venv if available
|
||||
#>
|
||||
$venvPython = Join-Path $Config.VenvPath "Scripts\python.exe"
|
||||
$venvPip = Join-Path $Config.VenvPath "Scripts\pip.exe"
|
||||
|
||||
if (Test-Path $venvPython) {
|
||||
return @{
|
||||
Python = $venvPython
|
||||
Pip = $venvPip
|
||||
IsVenv = $true
|
||||
}
|
||||
} else {
|
||||
# Fallback to global Python
|
||||
$globalPython = (Get-Command python -ErrorAction SilentlyContinue).Source
|
||||
$globalPip = (Get-Command pip -ErrorAction SilentlyContinue).Source
|
||||
return @{
|
||||
Python = $globalPython
|
||||
Pip = $globalPip
|
||||
IsVenv = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-Venv {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates virtual environment at external location if it doesn't exist.
|
||||
External location (C:\inetpub\wwwroot\roa2web-venv) survives deployments.
|
||||
#>
|
||||
$venvPath = $Config.VenvPath
|
||||
$venvPython = Join-Path $venvPath "Scripts\python.exe"
|
||||
$venvPip = Join-Path $venvPath "Scripts\pip.exe"
|
||||
|
||||
# If venv exists and is valid (pip works), we're good
|
||||
if (Test-Path $venvPython) {
|
||||
# Verify pip is functional (not broken by move)
|
||||
try {
|
||||
$pipTest = & $venvPip --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Success "Virtual environment already exists at $venvPath"
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Venv exists but pip is broken, recreating..."
|
||||
Remove-Item -Path $venvPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Venv exists but pip test failed, recreating..."
|
||||
Remove-Item -Path $venvPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Write-Step "Creating virtual environment at external location..."
|
||||
Write-Info "Path: $venvPath (survives deployments)"
|
||||
try {
|
||||
$globalPython = (Get-Command python -ErrorAction Stop).Source
|
||||
& $globalPython -m venv $venvPath
|
||||
if (Test-Path $venvPython) {
|
||||
Write-Success "Virtual environment created"
|
||||
|
||||
# Upgrade pip
|
||||
Write-Step "Upgrading pip in venv..."
|
||||
& $venvPython -m pip install --upgrade pip 2>&1 | Out-Null
|
||||
Write-Success "Pip upgraded"
|
||||
|
||||
return $true
|
||||
} else {
|
||||
Write-Error "Failed to create virtual environment"
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Failed to create venv: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Update-ServiceToUseVenv {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Updates NSSM service to use venv Python
|
||||
#>
|
||||
$venvPython = Join-Path $Config.VenvPath "Scripts\python.exe"
|
||||
|
||||
if (-not (Test-Path $venvPython)) {
|
||||
Write-Warning "Venv Python not found: $venvPython"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Check if nssm is available
|
||||
$nssmPath = Get-Command nssm -ErrorAction SilentlyContinue
|
||||
if (-not $nssmPath) {
|
||||
Write-Warning "NSSM not found in PATH"
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
# Get current application path
|
||||
$currentApp = & nssm get $Config.ServiceName Application 2>&1
|
||||
|
||||
if ($currentApp -eq $venvPython) {
|
||||
Write-Success "Service already configured to use venv Python"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Step "Updating service to use venv Python..."
|
||||
|
||||
# Stop service first
|
||||
Stop-ROAService | Out-Null
|
||||
|
||||
# Update service application
|
||||
& nssm set $Config.ServiceName Application $venvPython
|
||||
Write-Success "Service updated to use: $venvPython"
|
||||
|
||||
return $true
|
||||
} catch {
|
||||
Write-Error "Failed to update service: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-ForKeyPress {
|
||||
Write-Host "`nPress any key to continue..." -ForegroundColor Gray
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
@@ -304,6 +428,294 @@ function Test-ServiceHealth {
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# OCR DEPENDENCY CHECK
|
||||
# =============================================================================
|
||||
|
||||
function Test-OCRDependencies {
|
||||
param(
|
||||
[switch]$AutoInstall,
|
||||
[switch]$Silent
|
||||
)
|
||||
|
||||
if (-not $Silent) {
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
|
||||
Write-Host " OCR Dependencies Check$(if ($AutoInstall) { ' (Auto-Install Enabled)' })" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
$allOk = $true
|
||||
|
||||
# Get Python paths (prefer venv)
|
||||
$pyPaths = Get-PythonPaths
|
||||
|
||||
# Check Python/venv
|
||||
if (-not $Silent) { Write-Step "Checking Python installation..." }
|
||||
|
||||
if ($pyPaths.IsVenv) {
|
||||
if (-not $Silent) {
|
||||
$venvPythonVersion = & $pyPaths.Python --version 2>&1
|
||||
Write-Success "Virtual environment: $($Config.VenvPath)"
|
||||
Write-Success "Python (venv): $venvPythonVersion"
|
||||
}
|
||||
# Ensure service uses venv Python
|
||||
if ($AutoInstall) {
|
||||
# Install requirements.txt if exists
|
||||
$requirementsFile = Join-Path $Config.BackendPath "requirements.txt"
|
||||
if (Test-Path $requirementsFile) {
|
||||
if (-not $Silent) { Write-Step "Installing base requirements in venv..." }
|
||||
try {
|
||||
& $pyPaths.Pip install -r $requirementsFile 2>&1 | Out-Null
|
||||
if (-not $Silent) { Write-Success "Base requirements installed" }
|
||||
} catch {
|
||||
if (-not $Silent) { Write-Warning "Failed to install requirements: $_" }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $Silent) { Write-Step "Ensuring service uses venv Python..." }
|
||||
$serviceUpdated = Update-ServiceToUseVenv
|
||||
if ($serviceUpdated -and -not $Silent) {
|
||||
Write-Success "Service configured to use venv"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# No venv - create one if AutoInstall
|
||||
if ($AutoInstall) {
|
||||
if (-not $Silent) { Write-Warning "Virtual environment not found - creating..." }
|
||||
$venvCreated = Initialize-Venv
|
||||
if ($venvCreated) {
|
||||
# Refresh paths
|
||||
$pyPaths = Get-PythonPaths
|
||||
|
||||
# Update service to use venv Python
|
||||
if (-not $Silent) { Write-Step "Updating service to use virtual environment..." }
|
||||
Update-ServiceToUseVenv | Out-Null
|
||||
} else {
|
||||
if (-not $Silent) { Write-Error "Could not create virtual environment" }
|
||||
return $false
|
||||
}
|
||||
} else {
|
||||
# Check global Python
|
||||
if ($pyPaths.Python) {
|
||||
$pythonVersion = cmd /c "python --version 2>&1"
|
||||
if (-not $Silent) {
|
||||
Write-Warning "Using global Python (venv recommended)"
|
||||
Write-Success "Python: $pythonVersion"
|
||||
}
|
||||
} else {
|
||||
if (-not $Silent) { Write-Error "Python not found" }
|
||||
return $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Determine pip executable to use
|
||||
$pipExe = if ($pyPaths.Pip -and (Test-Path $pyPaths.Pip)) { "`"$($pyPaths.Pip)`"" } else { "pip" }
|
||||
if (-not $Silent -and $pyPaths.IsVenv) { Write-Info "Using pip from venv: $pipExe" }
|
||||
|
||||
# Check and optionally install Python packages
|
||||
if (-not $Silent) { Write-Step "Checking Python OCR packages..." }
|
||||
|
||||
$packages = @(
|
||||
@{ Name = "torch"; Package = "torch"; Required = $true; Description = "PyTorch (for docTR)" },
|
||||
@{ Name = "torchvision"; Package = "torchvision"; Required = $true; Description = "TorchVision (for docTR)" },
|
||||
@{ Name = "python-doctr"; Package = "python-doctr"; Required = $true; Description = "docTR OCR engine" },
|
||||
@{ Name = "pytesseract"; Package = "pytesseract"; Required = $true; Description = "Tesseract Python wrapper" },
|
||||
@{ Name = "paddleocr"; Package = "paddleocr"; Required = $true; Description = "PaddleOCR engine" }
|
||||
)
|
||||
|
||||
foreach ($pkg in $packages) {
|
||||
# Use venv pip to check packages
|
||||
$pipOutput = cmd /c "$pipExe show $($pkg.Package) 2>&1"
|
||||
$isInstalled = $pipOutput -match "Version:"
|
||||
|
||||
if ($isInstalled) {
|
||||
$versionLine = $pipOutput | Where-Object { $_ -match "^Version:" }
|
||||
$version = if ($versionLine) { ($versionLine -split ":")[1].Trim() } else { "unknown" }
|
||||
if (-not $Silent) { Write-Success "$($pkg.Package): $version" }
|
||||
} else {
|
||||
if ($pkg.Required) {
|
||||
if ($AutoInstall) {
|
||||
if (-not $Silent) { Write-Warning "$($pkg.Package): NOT INSTALLED - Installing..." }
|
||||
try {
|
||||
# Use venv pip to install
|
||||
$installCmd = "$pipExe install `"$($pkg.Name)`""
|
||||
if (-not $Silent) { Write-Info " Running: $installCmd" }
|
||||
$installResult = Invoke-Expression "cmd /c $installCmd 2>&1"
|
||||
# Verify installation
|
||||
$verifyOutput = cmd /c "$pipExe show $($pkg.Package) 2>&1"
|
||||
if ($verifyOutput -match "Version:") {
|
||||
if (-not $Silent) { Write-Success "$($pkg.Package): Installed successfully" }
|
||||
} else {
|
||||
if (-not $Silent) {
|
||||
Write-Error "$($pkg.Package): Installation FAILED"
|
||||
# Show last few lines of pip output for debugging
|
||||
$errorLines = ($installResult | Select-Object -Last 5) -join "`n"
|
||||
if ($errorLines) {
|
||||
Write-Host " Pip output:" -ForegroundColor Gray
|
||||
Write-Host " $errorLines" -ForegroundColor Gray
|
||||
}
|
||||
Write-Info " Try manually: $pipExe install `"$($pkg.Name)`""
|
||||
}
|
||||
$allOk = $false
|
||||
}
|
||||
} catch {
|
||||
if (-not $Silent) { Write-Error "$($pkg.Package): Installation error - $_" }
|
||||
$allOk = $false
|
||||
}
|
||||
} else {
|
||||
if (-not $Silent) {
|
||||
Write-Error "$($pkg.Package): NOT INSTALLED - $($pkg.Description)"
|
||||
Write-Info " Install with: pip install $($pkg.Name)"
|
||||
}
|
||||
$allOk = $false
|
||||
}
|
||||
} else {
|
||||
if (-not $Silent) { Write-Warning "$($pkg.Package): Not installed (optional)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check external tools
|
||||
if (-not $Silent) { Write-Step "Checking external OCR tools..." }
|
||||
|
||||
# Check for Chocolatey (used for auto-install)
|
||||
$chocoAvailable = $null -ne (Get-Command choco -ErrorAction SilentlyContinue)
|
||||
|
||||
# Install Chocolatey if needed and AutoInstall is enabled
|
||||
if ($AutoInstall -and -not $chocoAvailable) {
|
||||
if (-not $Silent) { Write-Warning "Chocolatey: NOT FOUND - Installing..." }
|
||||
try {
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||
Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||
# Refresh environment
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$chocoAvailable = $null -ne (Get-Command choco -ErrorAction SilentlyContinue)
|
||||
if ($chocoAvailable) {
|
||||
if (-not $Silent) { Write-Success "Chocolatey: Installed successfully" }
|
||||
} else {
|
||||
if (-not $Silent) { Write-Warning "Chocolatey: Installed but not in PATH - restart PowerShell" }
|
||||
}
|
||||
} catch {
|
||||
if (-not $Silent) { Write-Error "Chocolatey: Installation failed - $_" }
|
||||
}
|
||||
}
|
||||
|
||||
# Tesseract
|
||||
$tesseractPath = Get-Command tesseract -ErrorAction SilentlyContinue
|
||||
if ($tesseractPath) {
|
||||
$tessVersion = cmd /c "tesseract --version 2>&1" | Select-Object -First 1
|
||||
if (-not $Silent) {
|
||||
Write-Success "Tesseract: $tessVersion"
|
||||
Write-Info " Path: $($tesseractPath.Source)"
|
||||
}
|
||||
} else {
|
||||
if ($AutoInstall) {
|
||||
if ($chocoAvailable) {
|
||||
if (-not $Silent) { Write-Warning "Tesseract: NOT FOUND - Installing via Chocolatey..." }
|
||||
try {
|
||||
$result = cmd /c "choco install tesseract -y 2>&1"
|
||||
# Refresh PATH
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$tesseractPath = Get-Command tesseract -ErrorAction SilentlyContinue
|
||||
if ($tesseractPath) {
|
||||
if (-not $Silent) { Write-Success "Tesseract: Installed successfully" }
|
||||
} else {
|
||||
if (-not $Silent) { Write-Error "Tesseract: Installation completed but not in PATH - restart PowerShell" }
|
||||
$allOk = $false
|
||||
}
|
||||
} catch {
|
||||
if (-not $Silent) { Write-Error "Tesseract: Chocolatey install failed - $_" }
|
||||
$allOk = $false
|
||||
}
|
||||
} else {
|
||||
if (-not $Silent) {
|
||||
Write-Error "Tesseract: NOT FOUND - Chocolatey not available for auto-install"
|
||||
Write-Info " Install Chocolatey first: https://chocolatey.org/install"
|
||||
Write-Info " Then run: choco install tesseract -y"
|
||||
}
|
||||
$allOk = $false
|
||||
}
|
||||
} else {
|
||||
if (-not $Silent) {
|
||||
Write-Error "Tesseract: NOT FOUND in PATH"
|
||||
Write-Info " Install with: choco install tesseract -y"
|
||||
Write-Info " Or download from: https://github.com/UB-Mannheim/tesseract/wiki"
|
||||
}
|
||||
$allOk = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Poppler (for PDF support)
|
||||
$popplerPath = Get-Command pdftoppm -ErrorAction SilentlyContinue
|
||||
if ($popplerPath) {
|
||||
if (-not $Silent) {
|
||||
Write-Success "Poppler: Found (pdftoppm)"
|
||||
Write-Info " Path: $($popplerPath.Source)"
|
||||
}
|
||||
} else {
|
||||
if ($AutoInstall -and $chocoAvailable) {
|
||||
if (-not $Silent) { Write-Warning "Poppler: NOT FOUND - Installing via Chocolatey..." }
|
||||
try {
|
||||
$result = cmd /c "choco install poppler -y 2>&1"
|
||||
# Refresh PATH
|
||||
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
$popplerPath = Get-Command pdftoppm -ErrorAction SilentlyContinue
|
||||
if ($popplerPath) {
|
||||
if (-not $Silent) { Write-Success "Poppler: Installed successfully" }
|
||||
} else {
|
||||
if (-not $Silent) { Write-Warning "Poppler: Installation completed but not in PATH - restart PowerShell" }
|
||||
}
|
||||
} catch {
|
||||
if (-not $Silent) { Write-Warning "Poppler: Chocolatey install failed - $_" }
|
||||
}
|
||||
} else {
|
||||
if (-not $Silent) {
|
||||
Write-Warning "Poppler: NOT FOUND in PATH (required for PDF OCR)"
|
||||
Write-Info " Install with: choco install poppler -y"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check .env OCR settings
|
||||
if (-not $Silent) {
|
||||
Write-Step "Checking OCR configuration in .env..."
|
||||
$envPath = Join-Path $Config.BackendPath ".env"
|
||||
if (Test-Path $envPath) {
|
||||
$envContent = Get-Content $envPath -Raw
|
||||
|
||||
$ocrSettings = @(
|
||||
"OCR_ENABLE_PADDLEOCR",
|
||||
"OCR_ENABLE_TESSERACT",
|
||||
"OCR_DEFAULT_ENGINE",
|
||||
"OCR_WORKERS"
|
||||
)
|
||||
|
||||
foreach ($setting in $ocrSettings) {
|
||||
if ($envContent -match "$setting\s*=\s*(.+)") {
|
||||
Write-Info " $setting = $($Matches[1].Trim())"
|
||||
} else {
|
||||
Write-Warning " ${setting}: NOT CONFIGURED"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Warning ".env file not found at: $envPath"
|
||||
}
|
||||
|
||||
Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan
|
||||
if ($allOk) {
|
||||
Write-Host " Result: All required OCR dependencies are installed" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " Result: Some required dependencies are MISSING" -ForegroundColor Red
|
||||
Write-Host " Run with -AutoInstall to install missing packages" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host ("=" * 70) -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
return $allOk
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# BACKUP FUNCTIONS
|
||||
# =============================================================================
|
||||
@@ -400,6 +812,23 @@ function Deploy-Backend {
|
||||
Write-Info "Preserving .env file"
|
||||
}
|
||||
|
||||
# Preserve data directory (contains SQLite databases with production data!)
|
||||
$dataDir = Join-Path $Config.BackendPath "data"
|
||||
$dataTempPath = Join-Path $env:TEMP "roa2web-data-backup-$(Get-Date -Format 'yyyyMMddHHmmss')"
|
||||
$dataBackup = $null
|
||||
if (Test-Path $dataDir) {
|
||||
Write-Info "Preserving data directory (SQLite databases, uploads, cache)"
|
||||
Copy-Item -Path $dataDir -Destination $dataTempPath -Recurse -Force
|
||||
$dataBackup = $dataTempPath
|
||||
}
|
||||
|
||||
# Delete old venv inside backend if it exists (it has hardcoded paths and can't be moved)
|
||||
$oldVenvPath = Join-Path $Config.BackendPath "venv"
|
||||
if (Test-Path $oldVenvPath) {
|
||||
Write-Info "Removing old venv from backend directory (will use external venv)"
|
||||
Remove-Item -Path $oldVenvPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Remove-Item -Path $Config.BackendPath -Recurse -Force
|
||||
Copy-Item -Path $sourceBe -Destination $Config.BackendPath -Recurse -Force
|
||||
|
||||
@@ -408,6 +837,18 @@ function Deploy-Backend {
|
||||
Set-Content -Path $envFile -Value $envBackup -Force
|
||||
Write-Success ".env file restored"
|
||||
}
|
||||
|
||||
# Restore data directory
|
||||
if ($dataBackup -and (Test-Path $dataBackup)) {
|
||||
# Remove the empty data dir from package and restore the preserved one
|
||||
$newDataDir = Join-Path $Config.BackendPath "data"
|
||||
if (Test-Path $newDataDir) {
|
||||
Remove-Item -Path $newDataDir -Recurse -Force
|
||||
}
|
||||
Copy-Item -Path $dataBackup -Destination $newDataDir -Recurse -Force
|
||||
Remove-Item -Path $dataBackup -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Success "Data directory restored (SQLite databases preserved)"
|
||||
}
|
||||
} else {
|
||||
Copy-Item -Path $sourceBe -Destination $Config.BackendPath -Recurse -Force
|
||||
}
|
||||
@@ -423,10 +864,81 @@ function Deploy-Backend {
|
||||
Write-Success "Shared modules deployed"
|
||||
}
|
||||
|
||||
# Setup virtual environment
|
||||
Write-Step "Setting up Python virtual environment..."
|
||||
$venvCreated = Initialize-Venv
|
||||
if (-not $venvCreated) {
|
||||
Write-Warning "Could not create/verify virtual environment"
|
||||
}
|
||||
|
||||
# Install requirements.txt
|
||||
$requirementsFile = Join-Path $Config.BackendPath "requirements.txt"
|
||||
if (Test-Path $requirementsFile) {
|
||||
$pyPaths = Get-PythonPaths
|
||||
if ($pyPaths.IsVenv) {
|
||||
Write-Step "Installing Python dependencies in venv..."
|
||||
$pipExe = $pyPaths.Pip
|
||||
|
||||
# Verify pip is functional before installing
|
||||
$pipVersion = & $pipExe --version 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Pip is not functional: $pipVersion"
|
||||
Write-Info "Try: Remove-Item -Recurse $($Config.VenvPath); then redeploy"
|
||||
} else {
|
||||
Write-Info "Using pip: $pipVersion"
|
||||
|
||||
# Install requirements (ignore warnings, check exit code only)
|
||||
$oldErrorAction = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
$pipOutput = & $pipExe install -r $requirementsFile 2>&1
|
||||
$pipExitCode = $LASTEXITCODE
|
||||
$ErrorActionPreference = $oldErrorAction
|
||||
|
||||
# Log warnings but don't fail on them
|
||||
$pipOutput | ForEach-Object {
|
||||
if ($_ -match "WARNING:") {
|
||||
Write-Warning $_
|
||||
}
|
||||
}
|
||||
|
||||
if ($pipExitCode -eq 0) {
|
||||
# Verify uvicorn installed (critical dependency)
|
||||
$uvicornCheck = & $pipExe show uvicorn 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Success "Python dependencies installed"
|
||||
} else {
|
||||
Write-Error "Dependencies install failed - uvicorn not found"
|
||||
Write-Info "Manual fix: $pipExe install -r $requirementsFile"
|
||||
}
|
||||
} else {
|
||||
Write-Error "Pip install failed with exit code $pipExitCode"
|
||||
Write-Info "Manual fix: $pipExe install -r $requirementsFile"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Warning "No venv found - skipping requirements.txt installation"
|
||||
}
|
||||
}
|
||||
|
||||
# Update service to use venv Python
|
||||
$pyPaths = Get-PythonPaths
|
||||
if ($pyPaths.IsVenv) {
|
||||
Update-ServiceToUseVenv | Out-Null
|
||||
}
|
||||
|
||||
# Start service
|
||||
Start-Sleep -Seconds 2
|
||||
if (Start-ROAService) {
|
||||
Write-Success "Backend deployment completed successfully"
|
||||
|
||||
# Check and auto-install OCR dependencies after deployment
|
||||
Write-Step "Checking and installing OCR dependencies..."
|
||||
$ocrOk = Test-OCRDependencies -AutoInstall
|
||||
if (-not $ocrOk) {
|
||||
Write-Warning "Some OCR dependencies could not be installed automatically"
|
||||
Write-Info "Manual installation may be required for external tools (Tesseract, Poppler)"
|
||||
}
|
||||
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Backend deployed but service start failed"
|
||||
@@ -670,6 +1182,7 @@ function Show-MainMenu {
|
||||
Write-Host " === Monitoring ===" -ForegroundColor Cyan
|
||||
Write-Host " [7] View Status" -ForegroundColor White
|
||||
Write-Host " [8] View Logs" -ForegroundColor White
|
||||
Write-Host " [9] Check/Install OCR Dependencies" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " [Q] Quit" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
@@ -738,9 +1251,23 @@ function Show-MainMenu {
|
||||
Wait-ForKeyPress
|
||||
return "Continue"
|
||||
}
|
||||
"9" {
|
||||
$ocrOk = Test-OCRDependencies
|
||||
if (-not $ocrOk) {
|
||||
Write-Host ""
|
||||
Write-Host " Install missing dependencies? (Y/N): " -ForegroundColor Yellow -NoNewline
|
||||
$installChoice = Read-Host
|
||||
if ($installChoice -eq "Y" -or $installChoice -eq "y") {
|
||||
Write-Host ""
|
||||
Test-OCRDependencies -AutoInstall | Out-Null
|
||||
}
|
||||
}
|
||||
Wait-ForKeyPress
|
||||
return "Continue"
|
||||
}
|
||||
"Q" { return "Quit" }
|
||||
default {
|
||||
Write-Host "Invalid choice. Please select 1-8 or Q." -ForegroundColor Red
|
||||
Write-Host "Invalid choice. Please select 1-9 or Q." -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} while ($true)
|
||||
@@ -806,6 +1333,14 @@ function Main {
|
||||
Show-Logs
|
||||
exit 0
|
||||
}
|
||||
"CheckOCR" {
|
||||
$success = Test-OCRDependencies
|
||||
exit $(if ($success) { 0 } else { 1 })
|
||||
}
|
||||
"InstallOCR" {
|
||||
$success = Test-OCRDependencies -AutoInstall
|
||||
exit $(if ($success) { 0 } else { 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user