Files
edh-stats/deploy.sh
Michael d69a14d80b Migrate frontend to SvelteKit with comprehensive deployment (#2)
* Migrate frontend to SvelteKit with comprehensive deployment
documentation

- Create new SvelteKit project structure with routing, stores, and
  components
- Implement complete authentication system with auth store and protected
  routes
- Build all application pages: Home, Login, Register, Dashboard, Games,
  Stats, Commanders, Profile, and Round Counter
- Configure Vite, TailwindCSS, PostCSS, and Nginx for production
  deployment
- Add Dockerfile.svelte for containerized builds with multi-stage
  optimization
- Create comprehensive SVELTE_DEPLOYMENT.md and SVELTE_MIGRATION.md
  guides
- Update deployment scripts and package dependencies for SvelteKit
  ecosystem

* feat: Add user authentication and game tracking pages to EDH Stats
Tracker

* Migrate frontend to SvelteKit and update Docker configuration

- Replace Alpine.js with SvelteKit for improved DX and hot module
  replacement
- Switch frontend service to `Dockerfile.dev` with volume mounts and
  Vite dev server
- Update `docker-compose.yml` to map ports 5173 and use
  `http://localhost:5173` for CORS
- Add `Dockerfile.svelte` for production static builds
- Configure Vite proxy to target `http://backend:3000` in containers and
  `localhost:3002` locally
- Migrate existing components to new routes and update authentication
  store logic
- Add Chart.js integration to stats page and handle field name mapping
  for forms
- Include static assets (`fonts/Beleren-Bold.ttf`) and update deployment
  scripts
- Document migration status, testing checklist, and known minor issues
  in guides

* Refactor frontend state properties from snake_case to camelCase

This commit standardizes frontend property access across Dashboard,
Games, and Stats pages.
Changes include:
- Renaming API data fields (e.g., `commanderName`, `playerCount`,
  `winRate`).
- Updating `startEdit` logic to normalize mixed snake_case/camelCase
  inputs.
- Replacing template literals like `_player_won` with camelCase
  versions.
- Consistent usage of `totalGames` and `wins` instead of snake_case
  variants.

* Update version to 2.1.12 and refactor commander management

- Upgrade application version to 2.1.12
- Add Footer component and include in all pages
- Refactor `/commanders` page to fetch commanders and stats separately
- Fix commander API endpoint to load all commanders instead of only
  those with stats
- Add stats merging logic to calculate wins, win rate, and avg rounds
- Split add/edit command logic into shared `loadCommanders` function
- Fix color toggle logic to work with both new and editing command modes
- Update API methods for update requests to send `PUT` for existing
  commanders
- Enhance commander delete functionality with proper API response
  handling
- Refactor dashboard and stats pages to reuse shared data loading logic
- Add chart cleanup on destroy for both dashboard and stats pages
- Implement Chart.js for Win Rate by Color and Player Count charts
- Reorganize round counter component state and timer functions
- Add localStorage persistence for round counter with pause/resume
  support
- Update game log page to integrate footer component

* Refactor auth store and backend to use stable user ID

*   Backend: Switch user lookup from username to ID in auth routes to
    maintain stability across username changes.
*   Frontend: Update user store to reflect ID-based updates.
*   UI: Refactor user menu Svelte component to use ID-based user data.
*   Profile: Switch profile page to use ID-based user data for
    validation and state management.

* format date formatting options consistently across dashboard and games
pages

* format date formatting options consistently across dashboard and games
pages

* Refactor card action buttons to use icons with semantic text

- Switch "Edit" and "Delete" button text to SVG icons in `commanders`
  and `games` pages
- Update icon colors and font styles to match standard design tokens
  (indigo/red, bold text)
- Improve responsive spacing by adding `lg:grid-cols-3`

* grids
- Clarify hover states and titles for better UX accessibility
  Bump application versions to 2.2.0 and update deployment configuration

* Convert `+page.svelte` to use template strings for multiline strings and
fix syntax errors.

* Update static version to 2.2.2 and tighten nginx cache headers
2026-04-11 10:42:46 +02:00

510 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
##############################################################################
# EDH Stats Tracker - Production Deployment Script
#
# This script builds Docker images and pushes them to GitHub Container Registry
# Usage: ./deploy.sh [VERSION] [GHCR_TOKEN]
#
# Example: ./deploy.sh 1.0.0 ghcr_xxxxxxxxxxxxx
#
# Prerequisites:
# - Docker and Docker Compose installed
# - GitHub Personal Access Token (with write:packages permission)
# - Set GITHUB_REGISTRY_USER environment variable or pass as argument
##############################################################################
set -e # Exit on any error
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
REGISTRY="ghcr.io"
GITHUB_USER="${GITHUB_USER:=$(git config --get user.name | tr ' ' '-' | tr '[:upper:]' '[:lower:]')}"
PROJECT_NAME="edh-stats"
VERSION="${1:-latest}"
GHCR_TOKEN="${2}"
# Image names
BACKEND_IMAGE="${REGISTRY}/${GITHUB_USER}/${PROJECT_NAME}-backend:${VERSION}"
FRONTEND_IMAGE="${REGISTRY}/${GITHUB_USER}/${PROJECT_NAME}-frontend:${VERSION}"
BACKEND_IMAGE_LATEST="${REGISTRY}/${GITHUB_USER}/${PROJECT_NAME}-backend:latest"
FRONTEND_IMAGE_LATEST="${REGISTRY}/${GITHUB_USER}/${PROJECT_NAME}-frontend:latest"
##############################################################################
# Helper Functions
##############################################################################
print_header() {
echo -e "\n${BLUE}════════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}════════════════════════════════════════════════════════════${NC}\n"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}$1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
##############################################################################
# Validation
##############################################################################
validate_prerequisites() {
print_header "Validating Prerequisites"
# Check if Docker is installed
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed. Please install Docker first."
exit 1
fi
print_success "Docker is installed"
# Check if Docker daemon is running
if ! docker info > /dev/null 2>&1; then
print_error "Docker daemon is not running. Please start Docker."
exit 1
fi
print_success "Docker daemon is running"
# Check if Docker buildx is available
if ! docker buildx version > /dev/null 2>&1; then
print_warning "Docker buildx not found. Creating builder..."
docker buildx create --use --name multiarch-builder > /dev/null 2>&1 || true
if ! docker buildx version > /dev/null 2>&1; then
print_error "Docker buildx is required for multi-architecture builds."
print_error "Please ensure you have Docker with buildx support."
exit 1
fi
print_success "Docker buildx enabled"
else
print_success "Docker buildx is available"
fi
# Check if Git is installed
if ! command -v git &> /dev/null; then
print_error "Git is not installed. Please install Git first."
exit 1
fi
print_success "Git is installed"
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
print_error "Not in a Git repository. Please run this script from the project root."
exit 1
fi
print_success "Running from Git repository"
}
check_github_token() {
if [ -z "$GHCR_TOKEN" ]; then
print_warning "GitHub token not provided. You'll be prompted for credentials when pushing."
print_info "Set GHCR_TOKEN environment variable or pass as second argument to skip this prompt"
read -sp "Enter GitHub Container Registry Token (or press Enter to use 'docker login'): " GHCR_TOKEN
echo
if [ -z "$GHCR_TOKEN" ]; then
print_info "Attempting to use existing Docker credentials..."
if ! docker info | grep -q "Username"; then
print_warning "No Docker credentials found. Running 'docker login'..."
docker login "${REGISTRY}"
fi
fi
fi
}
update_version_file() {
print_header "Updating Version File"
local version_file="./frontend/static/version.txt"
local current_version=""
# Check if version file exists
if [ -f "$version_file" ]; then
current_version=$(cat "$version_file")
print_info "Current version: $current_version"
fi
# Update version file with new version (strip 'v' prefix if present)
local new_version="${VERSION#v}"
echo "$new_version" > "$version_file"
print_success "Updated version file to: $new_version"
}
##############################################################################
# Build Functions
##############################################################################
build_backend() {
print_header "Building Backend Image"
print_info "Building: ${BACKEND_IMAGE}"
print_info "Building for architectures: linux/amd64"
docker buildx build \
--platform linux/amd64 \
--file ./backend/Dockerfile \
--target production \
--tag "${BACKEND_IMAGE}" \
--tag "${BACKEND_IMAGE_LATEST}" \
--build-arg NODE_ENV=production \
--push \
./backend
print_success "Backend image built and pushed successfully"
}
build_frontend() {
print_header "Building Frontend Image (SvelteKit)"
print_info "Building: ${FRONTEND_IMAGE}"
print_info "Building for architectures: linux/amd64"
# SvelteKit multi-stage build with Vite bundler
# Automatically handles cache busting with hashed filenames
docker buildx build \
--platform linux/amd64 \
--file ./frontend/Dockerfile.svelte \
--tag "${FRONTEND_IMAGE}" \
--tag "${FRONTEND_IMAGE_LATEST}" \
--push \
./frontend
print_success "Frontend image built and pushed successfully"
}
##############################################################################
# Push Functions
##############################################################################
login_to_registry() {
print_header "Authenticating with GitHub Container Registry"
if [ -n "$GHCR_TOKEN" ]; then
print_info "Logging in with provided token..."
echo "$GHCR_TOKEN" | docker login "${REGISTRY}" -u "${GITHUB_USER}" --password-stdin > /dev/null 2>&1
else
print_info "Using existing Docker authentication..."
fi
print_success "Successfully authenticated with registry"
}
push_backend() {
print_header "Pushing Backend Image"
# Images are already pushed during buildx build step
print_success "Backend image already pushed: ${BACKEND_IMAGE}"
print_success "Latest backend image pushed: ${BACKEND_IMAGE_LATEST}"
}
push_frontend() {
print_header "Pushing Frontend Image"
# Images are already pushed during buildx build step
print_success "Frontend image already pushed: ${FRONTEND_IMAGE}"
print_success "Latest frontend image pushed: ${FRONTEND_IMAGE_LATEST}"
}
##############################################################################
# Verification Functions
##############################################################################
verify_images() {
print_header "Verifying Built Images"
print_info "Note: Using buildx for optimized builds"
print_info "Images are built for linux/amd64"
print_info "Images are pushed directly to registry (not stored locally)"
print_success "Backend image built and pushed: ${BACKEND_IMAGE}"
print_success "Frontend image built and pushed: ${FRONTEND_IMAGE}"
}
##############################################################################
# Generate Configuration
##############################################################################
generate_deployment_config() {
print_header "Generating Deployment Configuration"
local config_file="docker-compose.prod.deployed.yml"
print_info "Creating deployment configuration: ${config_file}"
cat > "${config_file}" << EOF
# Generated production deployment configuration
# Version: ${VERSION}
# Generated: $(date -u +'%Y-%m-%dT%H:%M:%SZ')
# GitHub User: ${GITHUB_USER}
#
# IMPORTANT: Prerequisites
# - Traefik must be running with 'traefik-network' created
# - Create a .env file with these variables:
# DB_NAME=edh_stats
# DB_USER=postgres
# DB_PASSWORD=\$(openssl rand -base64 32)
# JWT_SECRET=\$(openssl rand -base64 32)
# CORS_ORIGIN=https://yourdomain.com
# LOG_LEVEL=warn
# ALLOW_REGISTRATION=false
# DB_SEED=false
#
# FIRST TIME SETUP:
# 1. Ensure Traefik is running and traefik-network exists
# 2. Update frontend domain in labels (edh.example.com -> yourdomain.com)
# 3. Create .env file with above variables
# 4. Run: docker-compose -f docker-compose.prod.deployed.yml up -d
# 5. Database migrations will run automatically via db-migrate service
# 6. Monitor logs: docker-compose logs -f db-migrate
services:
# PostgreSQL database service
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=\${DB_USER:-postgres}
- POSTGRES_PASSWORD=\${DB_PASSWORD}
- POSTGRES_DB=\${DB_NAME}
volumes:
- ./postgres_data:/var/lib/postgresql/data
- ./scripts:/scripts:ro
- ./backups:/backups
healthcheck:
test: ['CMD-SHELL', 'PGPASSWORD=\${DB_PASSWORD} pg_isready -U postgres -h localhost']
interval: 10s
timeout: 5s
retries: 5
networks:
- edh-stats-network
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
# Database migration service - runs once on startup
db-migrate:
image: ${BACKEND_IMAGE}
depends_on:
postgres:
condition: service_healthy
environment:
- NODE_ENV=production
- DB_HOST=\${DB_HOST:-postgres}
- DB_NAME=\${DB_NAME}
- DB_USER=\${DB_USER:-postgres}
- DB_PASSWORD=\${DB_PASSWORD}
command: node src/database/migrate.js migrate
networks:
- edh-stats-network
restart: 'no'
backend:
image: ${BACKEND_IMAGE}
ports:
- '3002:3000'
depends_on:
db-migrate:
condition: service_completed_successfully
environment:
- NODE_ENV=production
- DB_HOST=\${DB_HOST:-postgres}
- DB_NAME=\${DB_NAME}
- DB_USER=\${DB_USER:-postgres}
- DB_PASSWORD=\${DB_PASSWORD}
- JWT_SECRET=\${JWT_SECRET}
- CORS_ORIGIN=\${CORS_ORIGIN:-https://yourdomain.com}
- LOG_LEVEL=\${LOG_LEVEL:-warn}
- ALLOW_REGISTRATION=\${ALLOW_REGISTRATION:-false}
- MAX_USERS=\${MAX_USERS:-}
restart: unless-stopped
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 256M
cpus: '0.25'
healthcheck:
test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:3000/api/health']
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- edh-stats-network
stop_grace_period: 30s
frontend:
image: ${FRONTEND_IMAGE}
restart: unless-stopped
healthcheck:
test:
- CMD
- curl
- http://localhost:80/health
interval: 10s
timeout: 5s
retries: 5
networks:
- edh-stats-network
- traefik-network
depends_on:
- backend
labels:
- traefik.enable=true
- traefik.http.routers.edh-stats-frontend.rule=Host(\`edh.zlor.fi\`)
- traefik.http.routers.edh-stats-frontend.entrypoints=websecure
- traefik.http.routers.edh-stats-frontend.service=edh-stats-frontend
- traefik.http.services.edh-stats-frontend.loadbalancer.server.port=80
- traefik.http.routers.edh-stats-frontend.tls=true
- traefik.http.routers.edh-stats-frontend.tls.certresolver=letsencrypt-cloudflare
deploy:
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 128M
cpus: '0.125'
volumes:
postgres_data:
driver: local
networks:
edh-stats-network:
driver: bridge
traefik-network:
external: true
name: traefik-network
EOF
print_success "Deployment configuration generated: ${config_file}"
}
##############################################################################
# Cleanup
##############################################################################
cleanup_temp_files() {
print_header "Cleaning Up Temporary Files"
# Note: Dockerfile.prod is now a permanent file in the repository
# and should not be deleted after the build completes
print_info "No temporary files to clean up"
}
##############################################################################
# Summary
##############################################################################
print_summary() {
print_header "Deployment Summary"
echo "Backend Image: ${BACKEND_IMAGE}"
echo "Frontend Image: ${FRONTEND_IMAGE}"
echo ""
echo "Latest Tags:"
echo " Backend: ${BACKEND_IMAGE_LATEST}"
echo " Frontend: ${FRONTEND_IMAGE_LATEST}"
echo ""
echo "Registry: ${REGISTRY}"
echo "GitHub User: ${GITHUB_USER}"
echo "Version: ${VERSION}"
echo ""
echo "Version Management:"
echo " Frontend version file updated: ./frontend/static/version.txt"
echo " SvelteKit with automatic cache busting (hashed filenames)"
echo ""
echo "Next Steps:"
echo " 1. Commit version update:"
echo " git add frontend/static/version.txt"
echo " git commit -m \"Bump version to ${VERSION#v}\""
echo " 2. Pull images: docker pull ${BACKEND_IMAGE}"
echo " 3. Create .env file with PostgreSQL credentials:"
echo " DB_PASSWORD=\$(openssl rand -base64 32)"
echo " JWT_SECRET=\$(openssl rand -base64 32)"
echo " 4. Set production secrets:"
echo " - CORS_ORIGIN=https://yourdomain.com"
echo " - ALLOW_REGISTRATION=false"
echo " 5. Deploy: docker-compose -f docker-compose.prod.deployed.yml up -d"
echo " 6. Monitor migrations: docker-compose logs -f db-migrate"
echo ""
}
##############################################################################
# Main Execution
##############################################################################
main() {
print_header "EDH Stats Tracker - Production Deployment"
print_info "Starting deployment process..."
print_info "Version: ${VERSION}"
print_info "GitHub User: ${GITHUB_USER}"
print_info "Registry: ${REGISTRY}"
echo ""
# Validation
validate_prerequisites
# Check token
check_github_token
# Authenticate (must happen before build to allow --push)
login_to_registry
# Update version file
update_version_file
# Build images
build_backend
build_frontend
# Verify images
verify_images
# Push images
push_backend
push_frontend
# Generate config
generate_deployment_config
# Cleanup
cleanup_temp_files
# Summary
print_summary
print_success "Deployment completed successfully!"
}
# Run main function
main