Add VM deployment automation
Created turnkey deployment for Hyper-V VMs and fresh Windows installs. FILES: - deploy-to-vm.ps1: Automated PowerShell deployment script - Checks prerequisites - Builds images (offline packages) - Starts all services - Initializes database - Sets admin password - One command deployment - DEPLOY-TO-VM.md: Complete deployment guide - Multiple copy methods - Manual deployment steps - Troubleshooting - Offline deployment notes USAGE: .\deploy-to-vm.ps1 -SiteName 'west-jefferson' -AdminPassword 'SecurePass!' TIMELINE: 5 minutes from repo copy to running system
This commit is contained in:
177
DEPLOY-TO-VM.md
Normal file
177
DEPLOY-TO-VM.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Deploy ShopDB to Hyper-V VM
|
||||||
|
|
||||||
|
Quick guide to deploy the Docker stack to a Windows 11 Hyper-V VM.
|
||||||
|
|
||||||
|
## Prerequisites on VM
|
||||||
|
|
||||||
|
1. **Docker Desktop** installed and running
|
||||||
|
2. **Git Bash** or **WSL** installed (for bash commands)
|
||||||
|
3. **PowerShell** (already on Windows 11)
|
||||||
|
|
||||||
|
## Step 1: Copy Files to VM
|
||||||
|
|
||||||
|
**Method A: Shared Folder**
|
||||||
|
```powershell
|
||||||
|
# On host PC, share the repo
|
||||||
|
# Then on VM, copy from network share
|
||||||
|
Copy-Item -Recurse \\host-pc\shopdb-flask C:\shopdb
|
||||||
|
```
|
||||||
|
|
||||||
|
**Method B: Git Clone**
|
||||||
|
```bash
|
||||||
|
# On VM
|
||||||
|
cd C:\
|
||||||
|
git clone git@github.com:ge-aero/shopdb-flask.git
|
||||||
|
cd shopdb-flask
|
||||||
|
git checkout wj
|
||||||
|
```
|
||||||
|
|
||||||
|
**Method C: Export/Import**
|
||||||
|
```powershell
|
||||||
|
# On host PC
|
||||||
|
Compress-Archive -Path C:\Users\270015376\Documents\github\flask-shopdb -DestinationPath C:\shopdb-deploy.zip
|
||||||
|
|
||||||
|
# Copy shopdb-deploy.zip to VM, then on VM:
|
||||||
|
Expand-Archive -Path C:\shopdb-deploy.zip -DestinationPath C:\shopdb
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Run Deployment Script
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# On VM, open PowerShell as Administrator
|
||||||
|
cd C:\shopdb
|
||||||
|
|
||||||
|
# Option 1: Quick deploy with defaults
|
||||||
|
.\deploy-to-vm.ps1
|
||||||
|
|
||||||
|
# Option 2: Specify site name and password
|
||||||
|
.\deploy-to-vm.ps1 -SiteName "west-jefferson" -AdminPassword "YourSecurePassword123!"
|
||||||
|
|
||||||
|
# Option 3: See all options
|
||||||
|
.\deploy-to-vm.ps1 -Help
|
||||||
|
```
|
||||||
|
|
||||||
|
## What the Script Does
|
||||||
|
|
||||||
|
1. ✅ Checks Docker Desktop is running
|
||||||
|
2. ✅ Creates .env from template
|
||||||
|
3. ✅ Builds frontend (if needed)
|
||||||
|
4. ✅ Builds Docker images (2-3 minutes, offline packages)
|
||||||
|
5. ✅ Starts all services (db + api + backup)
|
||||||
|
6. ✅ Runs database migrations
|
||||||
|
7. ✅ Sets admin password
|
||||||
|
|
||||||
|
## Manual Deployment (Alternative)
|
||||||
|
|
||||||
|
If you prefer to run commands manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create environment
|
||||||
|
cp .env.docker-dev .env
|
||||||
|
|
||||||
|
# 2. Build frontend (if not already built)
|
||||||
|
cd frontend
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# 3. Build and start Docker stack
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml --profile backup build
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml --profile backup up -d
|
||||||
|
|
||||||
|
# 4. Initialize database
|
||||||
|
docker exec flask-shopdb-api-1 flask db upgrade
|
||||||
|
|
||||||
|
# 5. Reset admin password (optional)
|
||||||
|
docker exec flask-shopdb-db-1 mysql -uroot -proot-dev-password-not-for-production shopdb_flask -e \
|
||||||
|
"UPDATE users SET passwordhash='scrypt:32768:8:1\$B4LhpPrM6qxijInK\$b99b47a0bb5fd45761a1823f401f70a3ceccf71a5d38dc433378aa07426ef8eaf7e679ff3dd7b0ce9772600f83368ac642e9419b9beaa49f87483318bae34547', failedlogins=0, lockeduntil=NULL WHERE username='admin';"
|
||||||
|
# (Password is: admin)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Access
|
||||||
|
|
||||||
|
**URL:** https://localhost:5001 (or https://vm-ip-address:5001 from host)
|
||||||
|
|
||||||
|
**Login:**
|
||||||
|
- Username: `admin`
|
||||||
|
- Password: Whatever you set in deploy script (default: `ChangeMe123!`)
|
||||||
|
|
||||||
|
## Verify Deployment
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Check all services are running
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# Should see:
|
||||||
|
# - flask-shopdb-db-1
|
||||||
|
# - flask-shopdb-api-1
|
||||||
|
# - flask-shopdb-backup-store-1
|
||||||
|
# - flask-shopdb-backup-watcher-1
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker logs -f flask-shopdb-api-1
|
||||||
|
|
||||||
|
# Test backup
|
||||||
|
# Go to https://localhost:5001/settings/shopdbbackups
|
||||||
|
# Click "Back Up Now"
|
||||||
|
# Wait 30 seconds, should show as "Checked"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Docker not running:**
|
||||||
|
```powershell
|
||||||
|
# Start Docker Desktop manually
|
||||||
|
& "C:\Program Files\Docker\Docker\Docker Desktop.exe"
|
||||||
|
Start-Sleep -Seconds 30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Port 5001 already in use:**
|
||||||
|
```powershell
|
||||||
|
# Edit .env and change API_PORT
|
||||||
|
# API_PORT=5002
|
||||||
|
```
|
||||||
|
|
||||||
|
**Can't access from host PC:**
|
||||||
|
```powershell
|
||||||
|
# On VM, check Windows Firewall
|
||||||
|
New-NetFirewallRule -DisplayName "ShopDB" -Direction Inbound -LocalPort 5001 -Protocol TCP -Action Allow
|
||||||
|
```
|
||||||
|
|
||||||
|
**SSL certificate warnings:**
|
||||||
|
- Normal for self-signed certs
|
||||||
|
- Click "Advanced" → "Proceed to localhost (unsafe)"
|
||||||
|
|
||||||
|
## Stop/Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml down
|
||||||
|
|
||||||
|
# Start (after first deployment)
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml --profile backup up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Update After Code Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Pull latest code
|
||||||
|
git pull origin wj
|
||||||
|
|
||||||
|
# 2. Rebuild frontend (if changed)
|
||||||
|
cd frontend && npm run build && cd ..
|
||||||
|
|
||||||
|
# 3. Rebuild and restart
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml build
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Offline Deployment Notes
|
||||||
|
|
||||||
|
**This deployment works completely offline** because:
|
||||||
|
- ✅ Frontend pre-built and checked into repo
|
||||||
|
- ✅ Restic binary pre-downloaded (8.6MB in repo)
|
||||||
|
- ✅ No npm/pip downloads during Docker build
|
||||||
|
- ✅ All dependencies baked into images
|
||||||
|
|
||||||
|
Perfect for air-gapped or restricted network environments!
|
||||||
136
deploy-to-vm.ps1
Normal file
136
deploy-to-vm.ps1
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# ShopDB Docker Deployment Script for Hyper-V VM
|
||||||
|
# Run this ON THE VM after copying the repo
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$SiteName = "shopdb-vm",
|
||||||
|
[string]$AdminPassword = "ChangeMe123!",
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($Help) {
|
||||||
|
Write-Host @"
|
||||||
|
ShopDB Docker Deployment Script
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
.\deploy-to-vm.ps1 [-SiteName <name>] [-AdminPassword <password>]
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-SiteName Site identifier (default: shopdb-vm)
|
||||||
|
-AdminPassword Initial admin password (default: ChangeMe123!)
|
||||||
|
-Help Show this help
|
||||||
|
|
||||||
|
Example:
|
||||||
|
.\deploy-to-vm.ps1 -SiteName "west-jefferson" -AdminPassword "SecurePass123!"
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
- Docker Desktop installed and running
|
||||||
|
- Git Bash or WSL available
|
||||||
|
"@
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "ShopDB Docker Deployment" -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Check Docker is running
|
||||||
|
Write-Host "[1/8] Checking Docker Desktop..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
docker ps | Out-Null
|
||||||
|
Write-Host " OK: Docker is running" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: Docker Desktop is not running" -ForegroundColor Red
|
||||||
|
Write-Host " Please start Docker Desktop and try again" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if we're in the repo
|
||||||
|
Write-Host "[2/8] Checking repository..." -ForegroundColor Yellow
|
||||||
|
if (-not (Test-Path "docker-compose.prebuilt.yml")) {
|
||||||
|
Write-Host " ERROR: Not in shopdb-flask directory" -ForegroundColor Red
|
||||||
|
Write-Host " Please cd to the repository root" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host " OK: In repository root" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Create .env if it doesn't exist
|
||||||
|
Write-Host "[3/8] Configuring environment..." -ForegroundColor Yellow
|
||||||
|
if (-not (Test-Path ".env")) {
|
||||||
|
Copy-Item ".env.docker-dev" ".env"
|
||||||
|
Write-Host " Created .env from template" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Update site name
|
||||||
|
(Get-Content ".env") -replace 'SHOPDB_SITE=shopdb-dev', "SHOPDB_SITE=$SiteName" | Set-Content ".env"
|
||||||
|
(Get-Content ".env") -replace 'SHOPDB_ENV_LABEL=DOCKER-DEV', "SHOPDB_ENV_LABEL=$SiteName" | Set-Content ".env"
|
||||||
|
Write-Host " Configured for site: $SiteName" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host " OK: .env already exists" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build frontend if needed
|
||||||
|
Write-Host "[4/8] Checking frontend build..." -ForegroundColor Yellow
|
||||||
|
if (-not (Test-Path "frontend/dist/index.html")) {
|
||||||
|
Write-Host " Building frontend (this may take 2-3 minutes)..." -ForegroundColor Yellow
|
||||||
|
Set-Location frontend
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
Set-Location ..
|
||||||
|
Write-Host " OK: Frontend built" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host " OK: Frontend already built" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build Docker images
|
||||||
|
Write-Host "[5/8] Building Docker images..." -ForegroundColor Yellow
|
||||||
|
Write-Host " This will take 2-3 minutes (using offline packages)..." -ForegroundColor Yellow
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml build
|
||||||
|
Write-Host " OK: Images built" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
Write-Host "[6/8] Starting services..." -ForegroundColor Yellow
|
||||||
|
docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml --profile backup up -d
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
Write-Host " OK: Services started" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
Write-Host "[7/8] Initializing database..." -ForegroundColor Yellow
|
||||||
|
docker exec flask-shopdb-api-1 flask db upgrade
|
||||||
|
Write-Host " OK: Database initialized" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Set admin password
|
||||||
|
Write-Host "[8/8] Setting admin password..." -ForegroundColor Yellow
|
||||||
|
$passwordHash = docker exec flask-shopdb-api-1 python -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('$AdminPassword'))"
|
||||||
|
$dbPassword = (Get-Content ".env" | Select-String "MYSQL_ROOT_PASSWORD=(.+)" | ForEach-Object { $_.Matches.Groups[1].Value })
|
||||||
|
|
||||||
|
docker exec flask-shopdb-db-1 mysql -uroot -p$dbPassword shopdb_flask -e @"
|
||||||
|
UPDATE users SET
|
||||||
|
passwordhash = '$passwordHash',
|
||||||
|
failedlogins = 0,
|
||||||
|
lockeduntil = NULL
|
||||||
|
WHERE username = 'admin';
|
||||||
|
"@
|
||||||
|
Write-Host " OK: Admin password set" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host "Deployment Complete!" -ForegroundColor Green
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Access ShopDB at: https://localhost:5001" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Login credentials:" -ForegroundColor Yellow
|
||||||
|
Write-Host " Username: admin" -ForegroundColor White
|
||||||
|
Write-Host " Password: $AdminPassword" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Services running:" -ForegroundColor Yellow
|
||||||
|
docker ps --format "table {{.Names}}\t{{.Status}}"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "To view logs:" -ForegroundColor Yellow
|
||||||
|
Write-Host " docker logs -f flask-shopdb-api-1" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "To stop:" -ForegroundColor Yellow
|
||||||
|
Write-Host " docker compose -f docker-compose.prebuilt.yml -f docker-compose.backup.yml down" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
Reference in New Issue
Block a user