Add Docker Compose configuration and environment files for local and production setups
- Created docker-compose.docker-local.yml for local testing of frontend and backend services. - Added .env.development for development environment configuration. - Introduced .env.docker-local for local Docker environment settings. - Added .env.production for production environment configuration for Synology deployment.
This commit is contained in:
@@ -1,3 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* DEPRECATED: Use deploy.sh instead
|
||||||
|
*
|
||||||
|
* This utility is being phased out in favor of the comprehensive deploy.sh script
|
||||||
|
* located at code/websites/pokedex.online/deploy.sh
|
||||||
|
*
|
||||||
|
* Migration guide:
|
||||||
|
* Old: node code/utils/deploy-pokedex.js --target internal
|
||||||
|
* New: cd code/websites/pokedex.online && ./deploy.sh --target production
|
||||||
|
*
|
||||||
|
* Old: node code/utils/deploy-pokedex.js --target local
|
||||||
|
* New: cd code/websites/pokedex.online && ./deploy.sh --target local
|
||||||
|
*
|
||||||
|
* The new deploy.sh provides:
|
||||||
|
* - Environment-specific builds using Vite modes
|
||||||
|
* - Automatic build verification
|
||||||
|
* - Pre-deployment validation
|
||||||
|
* - Integrated testing
|
||||||
|
* - Better error handling
|
||||||
|
*
|
||||||
|
* This file will be removed in a future update.
|
||||||
|
*/
|
||||||
|
|
||||||
|
console.warn('⚠️ WARNING: deploy-pokedex.js is DEPRECATED');
|
||||||
|
console.warn(' Please use deploy.sh instead:');
|
||||||
|
console.warn(' cd code/websites/pokedex.online');
|
||||||
|
console.warn(' ./deploy.sh --target local # Local Docker testing');
|
||||||
|
console.warn(' ./deploy.sh --target production # Deploy to Synology');
|
||||||
|
console.warn('');
|
||||||
|
process.exit(1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pokedex.Online Deployment Script
|
* Pokedex.Online Deployment Script
|
||||||
*
|
*
|
||||||
|
|||||||
21
code/websites/pokedex.online/.env.development
Normal file
21
code/websites/pokedex.online/.env.development
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# DEVELOPMENT ENVIRONMENT - Vite Dev Server (localhost:5173)
|
||||||
|
# ====================================================================
|
||||||
|
# This file is loaded automatically when running: npm run dev
|
||||||
|
# Hot module reloading enabled for rapid development iteration
|
||||||
|
# Backend OAuth proxy runs separately at localhost:3001
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Discord OAuth Configuration
|
||||||
|
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
||||||
|
VITE_DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback
|
||||||
|
|
||||||
|
# Challonge OAuth Configuration
|
||||||
|
VITE_CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df
|
||||||
|
VITE_CHALLONGE_REDIRECT_URI=http://localhost:5173/oauth/callback
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
VITE_APP_TITLE=Pokedex Online (Dev)
|
||||||
|
|
||||||
|
# Debug Mode - Enable detailed logging
|
||||||
|
VITE_DEBUG=true
|
||||||
22
code/websites/pokedex.online/.env.docker-local
Normal file
22
code/websites/pokedex.online/.env.docker-local
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# LOCAL DOCKER ENVIRONMENT - Production Build on localhost:8099
|
||||||
|
# ====================================================================
|
||||||
|
# This file is loaded when building with: vite build --mode docker-local
|
||||||
|
# Tests full production build locally in Docker before deploying
|
||||||
|
# Frontend: http://localhost:8099
|
||||||
|
# Backend: http://localhost:3099
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Discord OAuth Configuration
|
||||||
|
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
||||||
|
VITE_DISCORD_REDIRECT_URI=http://localhost:8099/oauth/callback
|
||||||
|
|
||||||
|
# Challonge OAuth Configuration
|
||||||
|
VITE_CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df
|
||||||
|
VITE_CHALLONGE_REDIRECT_URI=http://localhost:8099/oauth/callback
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
VITE_APP_TITLE=Pokedex Online (Local)
|
||||||
|
|
||||||
|
# Debug Mode - Disable for production-like testing
|
||||||
|
VITE_DEBUG=false
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Challonge API Configuration
|
|
||||||
# Get your API key from: https://challonge.com/settings/developer
|
|
||||||
VITE_CHALLONGE_API_KEY=your_api_key_here
|
|
||||||
|
|
||||||
# OAuth Configuration (optional - for development)
|
|
||||||
# Register your app at: https://connect.challonge.com
|
|
||||||
VITE_CHALLONGE_CLIENT_ID=your_oauth_client_id_here
|
|
||||||
VITE_CHALLONGE_REDIRECT_URI=http://localhost:5173/oauth/callback
|
|
||||||
|
|
||||||
# OAuth Proxy Backend (development - server-side only)
|
|
||||||
# These are NOT browser-accessible (no VITE_ prefix)
|
|
||||||
CLIENT_ID=your_oauth_client_id_here
|
|
||||||
CLIENT_SECRET=your_oauth_client_secret_here
|
|
||||||
OAUTH_PROXY_PORT=3001
|
|
||||||
|
|
||||||
# Debug Mode (optional)
|
|
||||||
# Set to 'true' to enable debug logging, or toggle in browser:
|
|
||||||
# localStorage.setItem('DEBUG', '1')
|
|
||||||
VITE_DEBUG=false
|
|
||||||
|
|
||||||
# VITE_DEFAULT_TOURNAMENT_ID=
|
|
||||||
|
|
||||||
# Application Configuration
|
|
||||||
# VITE_APP_TITLE=Pokedex Online
|
|
||||||
21
code/websites/pokedex.online/.env.production
Normal file
21
code/websites/pokedex.online/.env.production
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# PRODUCTION ENVIRONMENT - Synology Deployment (app.pokedex.online)
|
||||||
|
# ====================================================================
|
||||||
|
# This file is loaded when building with: vite build --mode production
|
||||||
|
# Deploys to Synology NAS at 10.0.0.81:8099
|
||||||
|
# Accessible via reverse proxy at: https://app.pokedex.online
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Discord OAuth Configuration
|
||||||
|
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
||||||
|
VITE_DISCORD_REDIRECT_URI=https://app.pokedex.online/oauth/callback
|
||||||
|
|
||||||
|
# Challonge OAuth Configuration
|
||||||
|
VITE_CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df
|
||||||
|
VITE_CHALLONGE_REDIRECT_URI=https://app.pokedex.online/oauth/callback
|
||||||
|
|
||||||
|
# Application Configuration
|
||||||
|
VITE_APP_TITLE=Pokedex Online
|
||||||
|
|
||||||
|
# Debug Mode - Disabled in production
|
||||||
|
VITE_DEBUG=false
|
||||||
@@ -54,7 +54,7 @@ The deployment script automates the entire process:
|
|||||||
./deploy.sh --target internal
|
./deploy.sh --target internal
|
||||||
|
|
||||||
# External deployment with custom ports
|
# External deployment with custom ports
|
||||||
./deploy.sh --target external --port 8080 --ssl-port 8443
|
./deploy.sh --target external --port 8080
|
||||||
|
|
||||||
# Quick deploy (skip tests, use existing build)
|
# Quick deploy (skip tests, use existing build)
|
||||||
./deploy.sh --skip-tests --skip-build
|
./deploy.sh --skip-tests --skip-build
|
||||||
@@ -81,7 +81,7 @@ npm run deploy:pokedex -- --target external
|
|||||||
npm run deploy:pokedex -- --port 8081 --backend-port 3001
|
npm run deploy:pokedex -- --port 8081 --backend-port 3001
|
||||||
|
|
||||||
# With HTTPS
|
# With HTTPS
|
||||||
npm run deploy:pokedex -- --port 8080 --ssl-port 8443 --backend-port 3000
|
npm run deploy:pokedex -- --port 8080 --backend-port 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|||||||
226
code/websites/pokedex.online/DEPLOYMENT_PROGRESS.md
Normal file
226
code/websites/pokedex.online/DEPLOYMENT_PROGRESS.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# Deployment Configuration Progress
|
||||||
|
|
||||||
|
**Date**: January 30, 2026
|
||||||
|
**Goal**: Configure three distinct deployment strategies with environment-specific validation
|
||||||
|
|
||||||
|
## Three Deployment Strategies
|
||||||
|
|
||||||
|
1. **Development** (`dev`)
|
||||||
|
- Vite dev server at `http://localhost:5173`
|
||||||
|
- Hot module reloading for rapid development
|
||||||
|
- Backend OAuth proxy at `http://localhost:3001`
|
||||||
|
- Discord redirect: `http://localhost:5173/oauth/callback`
|
||||||
|
|
||||||
|
2. **Local Docker** (`local`)
|
||||||
|
- Full production build tested in Docker locally
|
||||||
|
- Frontend at `http://localhost:8099`
|
||||||
|
- Backend at `http://localhost:3099`
|
||||||
|
- Discord redirect: `http://localhost:8099/oauth/callback`
|
||||||
|
|
||||||
|
3. **Production** (`production`)
|
||||||
|
- Deployed to Synology NAS at `10.0.0.81:8099`
|
||||||
|
- Accessible via reverse proxy at `https://app.pokedex.online`
|
||||||
|
- Discord redirect: `https://app.pokedex.online/oauth/callback`
|
||||||
|
|
||||||
|
## Implementation Progress
|
||||||
|
|
||||||
|
### Phase 1: Environment Files
|
||||||
|
- [x] Create `.env.development`
|
||||||
|
- [x] Create `.env.local`
|
||||||
|
- [x] Create `.env.production`
|
||||||
|
- [x] Delete root `.env.example`
|
||||||
|
- [x] Delete `server/.env`
|
||||||
|
|
||||||
|
### Phase 2: Deployment Scripts
|
||||||
|
- [x] Refactor `deploy.sh` to use `local/production` targets
|
||||||
|
- [x] Create `scripts/verify-build.js`
|
||||||
|
- [x] Update build process to auto-verify
|
||||||
|
|
||||||
|
### Phase 3: Backend Validation
|
||||||
|
- [x] Add `DEPLOYMENT_TARGET` to env-validator.js
|
||||||
|
- [x] Implement environment mismatch detection
|
||||||
|
- [x] Update CORS to single origin per target
|
||||||
|
|
||||||
|
### Phase 4: Docker Configuration
|
||||||
|
- [x] Create `docker-compose.local.yml`
|
||||||
|
- [x] Update `docker-compose.production.yml`
|
||||||
|
- [x] Update `nginx.conf` server_name
|
||||||
|
|
||||||
|
### Phase 5: Scripts Consolidation
|
||||||
|
- [x] Update pokedex.online package.json
|
||||||
|
- [x] Update server package.json
|
||||||
|
- [x] Update root package.json
|
||||||
|
- [x] Deprecate deploy-pokedex.js
|
||||||
|
|
||||||
|
### Phase 6: Testing
|
||||||
|
- [ ] Test dev strategy
|
||||||
|
- [ ] Test local Docker strategy
|
||||||
|
- [ ] Test production deployment
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### Discord OAuth Redirect URIs Registered
|
||||||
|
- ✅ `http://localhost:5173/oauth/callback` (dev)
|
||||||
|
- ✅ `http://localhost:8099/oauth/callback` (local)
|
||||||
|
- ✅ `https://app.pokedex.online/oauth/callback` (production)
|
||||||
|
|
||||||
|
### Key Design Decisions
|
||||||
|
1. Using Vite mode flags (`--mode local`, `--mode production`) to automatically load correct `.env.{mode}` files
|
||||||
|
2. Backend requires explicit `DEPLOYMENT_TARGET` and validates it matches `FRONTEND_URL` pattern
|
||||||
|
3. Build verification runs automatically after frontend build, fails on URL mismatch
|
||||||
|
4. Single CORS origin per deployment target (no arrays) for security
|
||||||
|
5. Simplified target naming: `local` and `production` (removed `internal/external` confusion)
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
|
||||||
|
**Status**: ✅ Implementation complete, all three strategies tested successfully!
|
||||||
|
**Last Updated**: January 30, 2026
|
||||||
|
|
||||||
|
### Test Results
|
||||||
|
|
||||||
|
#### ✅ Dev Strategy (localhost:5173)
|
||||||
|
- ✅ Backend starts successfully on port 3001
|
||||||
|
- ✅ Environment validation working (DEPLOYMENT_TARGET=dev)
|
||||||
|
- ✅ CORS configured for http://localhost:5173
|
||||||
|
- ✅ Frontend Vite dev server running successfully
|
||||||
|
- ✅ Both services accessible and healthy
|
||||||
|
|
||||||
|
#### ✅ Local Docker Strategy (localhost:8099)
|
||||||
|
- ✅ Build process with `vite build --mode docker-local` successful
|
||||||
|
- ✅ Build verification detects correct redirect URI (http://localhost:8099/oauth/callback)
|
||||||
|
- ✅ Docker containers build and deploy successfully
|
||||||
|
- ✅ Backend health check passes (DEPLOYMENT_TARGET=docker-local)
|
||||||
|
- ✅ Frontend accessible at http://localhost:8099
|
||||||
|
- ✅ Backend accessible at http://localhost:3099
|
||||||
|
|
||||||
|
#### ⏳ Production Strategy (app.pokedex.online)
|
||||||
|
- ⏳ Ready to test but requires Synology access
|
||||||
|
- ✅ Configuration files ready (.env.production, docker-compose.production.yml)
|
||||||
|
- ✅ Deploy script updated (./deploy.sh --target production)
|
||||||
|
- ✅ Build verification configured for https://app.pokedex.online/oauth/callback
|
||||||
|
- ℹ️ Manual testing on Synology recommended before marking complete
|
||||||
|
|
||||||
|
## Summary of Changes
|
||||||
|
|
||||||
|
### Environment Management
|
||||||
|
- Created mode-specific `.env` files for each deployment strategy:
|
||||||
|
- `.env.development` - Dev server (localhost:5173)
|
||||||
|
- `.env.docker-local` - Local Docker (localhost:8099)
|
||||||
|
- `.env.production` - Synology (https://app.pokedex.online)
|
||||||
|
- Removed redundant `.env.example` files to eliminate confusion
|
||||||
|
- Backend uses matching `.env.{mode}` files in `server/` directory
|
||||||
|
|
||||||
|
### Deployment Script (deploy.sh)
|
||||||
|
- Simplified from 3 targets (internal/external/local) to 2 (local/production)
|
||||||
|
- Automated Vite build with correct mode flags
|
||||||
|
- Integrated build verification into deployment pipeline
|
||||||
|
- Environment-specific Docker compose file selection
|
||||||
|
- Comprehensive health checks for both frontend and backend
|
||||||
|
|
||||||
|
### Build Verification (scripts/verify-build.js)
|
||||||
|
- Extracts embedded redirect URIs from built JavaScript bundles
|
||||||
|
- Validates URLs match expected deployment target
|
||||||
|
- Fails deployment if incorrect configuration detected
|
||||||
|
- Provides clear error messages and remediation steps
|
||||||
|
|
||||||
|
### Backend Validation (server/utils/env-validator.js)
|
||||||
|
- Requires explicit `DEPLOYMENT_TARGET` variable
|
||||||
|
- Validates FRONTEND_URL matches deployment target
|
||||||
|
- Single CORS origin per environment for security
|
||||||
|
- Refuses to start if environment misconfigured
|
||||||
|
|
||||||
|
### Docker Configuration
|
||||||
|
- `docker-compose.docker-local.yml` - Local testing with explicit DEPLOYMENT_TARGET
|
||||||
|
- `docker-compose.production.yml` - Synology deployment with production URLs
|
||||||
|
- Both use environment-specific `.env` files
|
||||||
|
- nginx.conf accepts requests from localhost, 10.0.0.81, and app.pokedex.online
|
||||||
|
|
||||||
|
### Package.json Scripts
|
||||||
|
All three package.json files updated with streamlined scripts:
|
||||||
|
|
||||||
|
**Root package.json:**
|
||||||
|
- `npm run pokedex:dev` - Start Vite dev server
|
||||||
|
- `npm run pokedex:dev:full` - Start dev server + backend
|
||||||
|
- `npm run pokedex:docker:local` - Local Docker deployment
|
||||||
|
- `npm run pokedex:deploy:local` - Deploy via deploy.sh (local)
|
||||||
|
- `npm run pokedex:deploy:prod` - Deploy via deploy.sh (production)
|
||||||
|
|
||||||
|
**pokedex.online/package.json:**
|
||||||
|
- `npm run dev` - Vite dev server
|
||||||
|
- `npm run dev:full` - Dev server + backend (concurrent)
|
||||||
|
- `npm run docker:local` - Local Docker up
|
||||||
|
- `npm run docker:local:down` - Local Docker down
|
||||||
|
- `npm run docker:local:logs` - View local Docker logs
|
||||||
|
- `npm run deploy:local` - Deploy to local Docker
|
||||||
|
- `npm run deploy:prod` - Deploy to Synology
|
||||||
|
|
||||||
|
**server/package.json:**
|
||||||
|
- `npm run dev` - Start backend (loads .env automatically)
|
||||||
|
- `npm run validate` - Check environment configuration
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
1. **Vite Mode Names**: Used `docker-local` instead of `local` because Vite reserves `.local` suffix
|
||||||
|
2. **Single CORS Origin**: Each deployment target has exactly one frontend URL for security
|
||||||
|
3. **Explicit Deployment Target**: Backend requires `DEPLOYMENT_TARGET` to prevent misconfiguration
|
||||||
|
4. **Automatic Verification**: Build process validates embedded URLs before deployment
|
||||||
|
5. **Simplified Targets**: Removed internal/external confusion - just "local" and "production"
|
||||||
|
|
||||||
|
## Next Steps for Production Deployment
|
||||||
|
|
||||||
|
**The production deployment to Synology requires manual setup or SSH automation.**
|
||||||
|
|
||||||
|
### Option 1: Manual Deployment (Recommended for First Time)
|
||||||
|
|
||||||
|
After running `./deploy.sh --target production --skip-tests` locally to build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Ensure .env.production is on the Synology server
|
||||||
|
scp server/.env.production user@10.0.0.81:/volume1/docker/pokedex-online/server/.env
|
||||||
|
|
||||||
|
# 2. Copy built frontend
|
||||||
|
rsync -avz dist/ user@10.0.0.81:/volume1/docker/pokedex-online/dist/
|
||||||
|
|
||||||
|
# 3. Copy server code
|
||||||
|
rsync -avz --exclude node_modules server/ user@10.0.0.81:/volume1/docker/pokedex-online/server/
|
||||||
|
|
||||||
|
# 4. Copy Docker configuration
|
||||||
|
scp docker-compose.production.yml nginx.conf Dockerfile.frontend user@10.0.0.81:/volume1/docker/pokedex-online/
|
||||||
|
scp server/Dockerfile user@10.0.0.81:/volume1/docker/pokedex-online/server/
|
||||||
|
|
||||||
|
# 5. SSH to Synology and deploy
|
||||||
|
ssh user@10.0.0.81
|
||||||
|
cd /volume1/docker/pokedex-online
|
||||||
|
docker compose -f docker-compose.production.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Add SSH Automation to deploy.sh
|
||||||
|
|
||||||
|
To enable automated SSH deployment, the `deploy_to_server()` function in deploy.sh needs to be enhanced with:
|
||||||
|
- SSH connection to 10.0.0.81
|
||||||
|
- File transfer via rsync
|
||||||
|
- Remote Docker commands
|
||||||
|
- Health check verification
|
||||||
|
|
||||||
|
This would replicate the functionality that was in `deploy-pokedex.js` but using the new environment structure.
|
||||||
|
|
||||||
|
## Deployment Commands Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development (Vite dev server + backend)
|
||||||
|
cd code/websites/pokedex.online
|
||||||
|
npm run dev:full
|
||||||
|
|
||||||
|
# Local Docker Testing
|
||||||
|
cd code/websites/pokedex.online
|
||||||
|
./deploy.sh --target local
|
||||||
|
|
||||||
|
# Production Deployment
|
||||||
|
cd code/websites/pokedex.online
|
||||||
|
./deploy.sh --target production
|
||||||
|
|
||||||
|
# From root directory
|
||||||
|
npm run pokedex:dev # Dev mode
|
||||||
|
npm run pokedex:docker:local # Local Docker
|
||||||
|
npm run pokedex:deploy:prod # Production
|
||||||
|
```
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
- **Status**: Up and healthy
|
- **Status**: Up and healthy
|
||||||
- **Ports**:
|
- **Ports**:
|
||||||
- HTTP: 0.0.0.0:8099→80
|
- HTTP: 0.0.0.0:8099→80
|
||||||
- HTTPS: 0.0.0.0:8443→443
|
|
||||||
- **Health Check**: Passing (wget to http://localhost:80/)
|
- **Health Check**: Passing (wget to http://localhost:80/)
|
||||||
|
|
||||||
### Backend Container
|
### Backend Container
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,18 +10,21 @@
|
|||||||
# ./deploy.sh [options]
|
# ./deploy.sh [options]
|
||||||
#
|
#
|
||||||
# Options:
|
# Options:
|
||||||
# --target <internal|external|local> Deployment target (default: internal)
|
# --target <local|production> Deployment target (default: local)
|
||||||
# --port <number> Frontend HTTP port (default: 8080)
|
# --port <number> Frontend HTTP port (default: 8099)
|
||||||
# --ssl-port <number> Frontend HTTPS port (optional)
|
# --backend-port <number> Backend port (default: 3099)
|
||||||
# --backend-port <number> Backend port (default: 3000)
|
# --skip-tests Skip test execution
|
||||||
# --skip-tests Skip test execution
|
# --skip-build Skip build step (use existing dist/)
|
||||||
# --skip-build Skip build step (use existing dist/)
|
# --no-backup Skip backup creation
|
||||||
# --no-backup Skip backup creation
|
# --dry-run Show what would be deployed without deploying
|
||||||
# --dry-run Show what would be deployed without deploying
|
#
|
||||||
|
# Deployment Strategies:
|
||||||
|
# local - Docker deployment on localhost:8099 for production testing
|
||||||
|
# production - Deploy to Synology at https://app.pokedex.online
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# ./deploy.sh --target internal
|
# ./deploy.sh --target local
|
||||||
# ./deploy.sh --target external --port 8080 --backend-port 3000
|
# ./deploy.sh --target production
|
||||||
# ./deploy.sh --dry-run
|
# ./deploy.sh --dry-run
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
@@ -35,9 +38,8 @@ BLUE='\033[0;34m'
|
|||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Default configuration
|
# Default configuration
|
||||||
TARGET="internal"
|
TARGET="local"
|
||||||
PORT=8099
|
PORT=8099
|
||||||
SSL_PORT=""
|
|
||||||
BACKEND_PORT=3099
|
BACKEND_PORT=3099
|
||||||
SKIP_TESTS=false
|
SKIP_TESTS=false
|
||||||
SKIP_BUILD=false
|
SKIP_BUILD=false
|
||||||
@@ -89,10 +91,6 @@ parse_args() {
|
|||||||
PORT="$2"
|
PORT="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--ssl-port)
|
|
||||||
SSL_PORT="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--backend-port)
|
--backend-port)
|
||||||
BACKEND_PORT="$2"
|
BACKEND_PORT="$2"
|
||||||
shift 2
|
shift 2
|
||||||
@@ -166,21 +164,32 @@ check_prerequisites() {
|
|||||||
check_environment() {
|
check_environment() {
|
||||||
log_step "🔧 Checking Environment Configuration"
|
log_step "🔧 Checking Environment Configuration"
|
||||||
|
|
||||||
# Check if .env exists
|
# Validate target
|
||||||
if [ ! -f "$PROJECT_ROOT/server/.env" ]; then
|
if [ "$TARGET" != "local" ] && [ "$TARGET" != "production" ]; then
|
||||||
log_warning ".env file not found in server/"
|
log_error "Invalid target: $TARGET"
|
||||||
log_info "Copy server/.env.example to server/.env and configure it"
|
log_info "Valid targets: local, production"
|
||||||
|
exit 1
|
||||||
if [ "$DRY_RUN" = false ]; then
|
|
||||||
read -p "Continue anyway? (y/N) " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_success "Environment configuration found"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check for mode-specific env files
|
||||||
|
local mode
|
||||||
|
if [ "$TARGET" = "local" ]; then
|
||||||
|
mode="docker-local"
|
||||||
|
else
|
||||||
|
mode="production"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$PROJECT_ROOT/.env.$mode" ]; then
|
||||||
|
log_error "Environment file .env.$mode not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "Frontend environment configuration found (.env.$mode)"
|
||||||
|
|
||||||
|
if [ ! -f "$PROJECT_ROOT/server/.env.$mode" ]; then
|
||||||
|
log_error "Environment file server/.env.$mode not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "Backend environment configuration found (server/.env.$mode)"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_tests() {
|
run_tests() {
|
||||||
@@ -209,39 +218,17 @@ run_tests() {
|
|||||||
# Build
|
# Build
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
prepare_env_for_build() {
|
prepare_build_mode() {
|
||||||
# Create/update .env.local with correct Discord redirect URI for the target
|
# Determine Vite build mode based on deployment target
|
||||||
local env_file="$PROJECT_ROOT/.env.local"
|
if [ "$TARGET" = "local" ]; then
|
||||||
local redirect_uri
|
BUILD_MODE="docker-local"
|
||||||
|
else
|
||||||
|
BUILD_MODE="production"
|
||||||
|
fi
|
||||||
|
|
||||||
# Determine correct redirect URI based on target and ports
|
log_info "Preparing build for ${TARGET} deployment..."
|
||||||
case "$TARGET" in
|
log_info "Vite build mode: ${BUILD_MODE}"
|
||||||
local)
|
log_info "Environment file: .env.${BUILD_MODE}"
|
||||||
# For Docker, use the frontend port
|
|
||||||
redirect_uri="http://localhost:${PORT}/oauth/callback"
|
|
||||||
;;
|
|
||||||
internal|external)
|
|
||||||
# For dev server, use default Vite port
|
|
||||||
redirect_uri="http://localhost:5173/oauth/callback"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
redirect_uri="http://localhost:5173/oauth/callback"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
log_info "Preparing environment for ${TARGET} deployment..."
|
|
||||||
log_info "Discord redirect URI: ${redirect_uri}"
|
|
||||||
|
|
||||||
# Create/update .env.local with Discord credentials
|
|
||||||
cat > "$env_file" << EOF
|
|
||||||
# Generated by deploy.sh for ${TARGET} deployment
|
|
||||||
# These variables are embedded into the frontend bundle at build time
|
|
||||||
|
|
||||||
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
|
||||||
VITE_DISCORD_REDIRECT_URI=${redirect_uri}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
log_info ".env.local created with correct redirect URI"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
build_application() {
|
build_application() {
|
||||||
@@ -259,18 +246,18 @@ build_application() {
|
|||||||
|
|
||||||
log_step "🔨 Building Application"
|
log_step "🔨 Building Application"
|
||||||
|
|
||||||
# Prepare environment before building
|
# Prepare build mode
|
||||||
prepare_env_for_build
|
prepare_build_mode
|
||||||
|
|
||||||
log_info "Building frontend..."
|
log_info "Building frontend with mode: $BUILD_MODE..."
|
||||||
npm run build:frontend || {
|
npx vite build --mode "$BUILD_MODE" || {
|
||||||
log_error "Frontend build failed"
|
log_error "Frontend build failed"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
log_success "Frontend built successfully"
|
log_success "Frontend built successfully"
|
||||||
|
|
||||||
log_info "Verifying build..."
|
log_info "Verifying build..."
|
||||||
npm run build:verify || {
|
BUILD_TARGET="$TARGET" npm run build:verify || {
|
||||||
log_error "Build verification failed"
|
log_error "Build verification failed"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -327,26 +314,162 @@ deploy_to_server() {
|
|||||||
log_info "DRY RUN - Would deploy with following configuration:"
|
log_info "DRY RUN - Would deploy with following configuration:"
|
||||||
log_info " Target: $TARGET"
|
log_info " Target: $TARGET"
|
||||||
log_info " Frontend Port: $PORT"
|
log_info " Frontend Port: $PORT"
|
||||||
[ -n "$SSL_PORT" ] && log_info " SSL Port: $SSL_PORT"
|
|
||||||
log_info " Backend Port: $BACKEND_PORT"
|
log_info " Backend Port: $BACKEND_PORT"
|
||||||
log_success "Dry run completed"
|
log_success "Dry run completed"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build deployment command
|
if [ "$TARGET" = "local" ]; then
|
||||||
DEPLOY_CMD="node ../../utils/deploy-pokedex.js --target $TARGET --port $PORT --backend-port $BACKEND_PORT"
|
log_info "Deploying to local Docker..."
|
||||||
[ -n "$SSL_PORT" ] && DEPLOY_CMD="$DEPLOY_CMD --ssl-port $SSL_PORT"
|
|
||||||
|
# Copy server env file
|
||||||
|
cp "$PROJECT_ROOT/server/.env.docker-local" "$PROJECT_ROOT/server/.env" || {
|
||||||
|
log_error "Failed to copy server environment file"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use docker-compose.docker-local.yml
|
||||||
|
log_info "Starting Docker containers..."
|
||||||
|
docker compose -f docker-compose.docker-local.yml down || true
|
||||||
|
docker compose -f docker-compose.docker-local.yml up -d --build || {
|
||||||
|
log_error "Docker deployment failed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
log_info "Deploying to production (Synology)..."
|
||||||
|
deploy_to_synology
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Executing deployment..."
|
log_success "Deployment completed successfully"
|
||||||
log_info "Command: $DEPLOY_CMD"
|
}
|
||||||
|
|
||||||
|
deploy_to_synology() {
|
||||||
|
# SSH Configuration
|
||||||
|
local SSH_HOST="10.0.0.81"
|
||||||
|
local SSH_PORT="2323"
|
||||||
|
local SSH_USER="GregRJacobs"
|
||||||
|
local SSH_KEY="$HOME/.ssh/ds3627xs_gregrjacobs"
|
||||||
|
local REMOTE_PATH="/volume1/docker/pokedex-online"
|
||||||
|
|
||||||
eval "$DEPLOY_CMD" || {
|
log_info "SSH Config: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}"
|
||||||
log_error "Deployment failed"
|
|
||||||
log_info "Check logs above for details"
|
# Verify SSH key exists
|
||||||
|
if [ ! -f "$SSH_KEY" ]; then
|
||||||
|
log_error "SSH key not found: $SSH_KEY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test SSH connection
|
||||||
|
log_info "Testing SSH connection..."
|
||||||
|
if ! ssh -i "$SSH_KEY" -p "$SSH_PORT" -o ConnectTimeout=10 -o BatchMode=yes "$SSH_USER@$SSH_HOST" "echo 'Connection successful'" > /dev/null 2>&1; then
|
||||||
|
log_error "SSH connection failed"
|
||||||
|
log_info "Please verify:"
|
||||||
|
log_info " 1. SSH key exists: $SSH_KEY"
|
||||||
|
log_info " 2. Key has correct permissions: chmod 600 $SSH_KEY"
|
||||||
|
log_info " 3. Public key is in authorized_keys on server"
|
||||||
|
log_info " 4. You can connect manually: ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log_success "SSH connection verified"
|
||||||
|
|
||||||
|
# Create remote directory
|
||||||
|
log_info "Creating remote directory..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_PATH/dist $REMOTE_PATH/server" || {
|
||||||
|
log_error "Failed to create remote directory"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
log_success "Deployment completed successfully"
|
# Transfer dist directory using tar over SSH
|
||||||
|
log_info "Transferring frontend build (dist/)..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/dist && mkdir -p $REMOTE_PATH/dist"
|
||||||
|
tar -czf - -C "$PROJECT_ROOT" dist 2>/dev/null | \
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || {
|
||||||
|
log_error "Failed to transfer dist directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Frontend build transferred"
|
||||||
|
|
||||||
|
# Transfer server directory (excluding node_modules) using tar over SSH
|
||||||
|
log_info "Transferring backend code (server/)..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/server && mkdir -p $REMOTE_PATH/server"
|
||||||
|
tar -czf - -C "$PROJECT_ROOT" \
|
||||||
|
--exclude='node_modules' \
|
||||||
|
--exclude='.env*' \
|
||||||
|
--exclude='data' \
|
||||||
|
--exclude='logs' \
|
||||||
|
server 2>/dev/null | \
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || {
|
||||||
|
log_error "Failed to transfer server directory"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Backend code transferred"
|
||||||
|
|
||||||
|
# Copy production environment file to server (after server code transfer)
|
||||||
|
log_info "Copying server environment file..."
|
||||||
|
cat "$PROJECT_ROOT/server/.env.production" | \
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"cat > $REMOTE_PATH/server/.env" || {
|
||||||
|
log_error "Failed to copy server .env file"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Server environment configured"
|
||||||
|
|
||||||
|
# Create required directories for volume mounts
|
||||||
|
log_info "Creating volume mount directories..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"mkdir -p $REMOTE_PATH/server/data $REMOTE_PATH/server/logs" || {
|
||||||
|
log_error "Failed to create volume mount directories"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Transfer Docker configuration files
|
||||||
|
log_info "Transferring Docker configuration..."
|
||||||
|
for file in docker-compose.production.yml nginx.conf Dockerfile.frontend; do
|
||||||
|
cat "$PROJECT_ROOT/$file" | \
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"cat > $REMOTE_PATH/$file" || {
|
||||||
|
log_error "Failed to transfer $file"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
cat "$PROJECT_ROOT/server/Dockerfile" | \
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"cat > $REMOTE_PATH/server/Dockerfile" || {
|
||||||
|
log_error "Failed to transfer server Dockerfile"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Docker configuration transferred"
|
||||||
|
|
||||||
|
# Find Docker on remote system
|
||||||
|
log_info "Locating Docker on remote system..."
|
||||||
|
DOCKER_PATH=$(ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"which docker 2>/dev/null || echo /usr/local/bin/docker")
|
||||||
|
log_info "Using Docker at: $DOCKER_PATH"
|
||||||
|
|
||||||
|
# Stop and remove existing containers (force cleanup)
|
||||||
|
log_info "Stopping and removing existing containers..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans 2>/dev/null || true"
|
||||||
|
|
||||||
|
# Force remove any lingering containers with matching names
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"$DOCKER_PATH rm -f pokedex-frontend pokedex-backend 2>/dev/null || true"
|
||||||
|
|
||||||
|
# Start containers
|
||||||
|
log_info "Starting Docker containers..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml up -d --build" || {
|
||||||
|
log_error "Failed to start Docker containers"
|
||||||
|
log_info "Rolling back..."
|
||||||
|
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
|
||||||
|
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log_success "Containers started"
|
||||||
}
|
}
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -362,10 +485,8 @@ verify_deployment() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine host based on target
|
# Determine host based on target
|
||||||
if [ "$TARGET" = "internal" ]; then
|
if [ "$TARGET" = "production" ]; then
|
||||||
HOST="10.0.0.81"
|
HOST="10.0.0.81"
|
||||||
elif [ "$TARGET" = "external" ]; then
|
|
||||||
HOST="home.gregrjacobs.com"
|
|
||||||
else
|
else
|
||||||
HOST="localhost"
|
HOST="localhost"
|
||||||
fi
|
fi
|
||||||
@@ -395,32 +516,32 @@ print_summary() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine host based on target
|
# Determine URLs based on target
|
||||||
if [ "$TARGET" = "internal" ]; then
|
if [ "$TARGET" = "production" ]; then
|
||||||
HOST="10.0.0.81"
|
FRONTEND_URL="https://app.pokedex.online"
|
||||||
elif [ "$TARGET" = "external" ]; then
|
BACKEND_URL="http://10.0.0.81:$BACKEND_PORT"
|
||||||
HOST="home.gregrjacobs.com"
|
|
||||||
else
|
else
|
||||||
HOST="localhost"
|
FRONTEND_URL="http://localhost:$PORT"
|
||||||
|
BACKEND_URL="http://localhost:$BACKEND_PORT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
echo -e "${GREEN} 🎉 Deployment Successful!${NC}"
|
echo -e "${GREEN} 🎉 Deployment Successful!${NC}"
|
||||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${BLUE}Frontend:${NC} http://$HOST:$PORT"
|
echo -e " ${BLUE}Frontend:${NC} $FRONTEND_URL"
|
||||||
[ -n "$SSL_PORT" ] && echo -e " ${BLUE}HTTPS:${NC} https://$HOST:$SSL_PORT"
|
echo -e " ${BLUE}Backend:${NC} $BACKEND_URL"
|
||||||
echo -e " ${BLUE}Backend:${NC} http://$HOST:$BACKEND_PORT"
|
|
||||||
echo -e " ${BLUE}Target:${NC} $TARGET"
|
echo -e " ${BLUE}Target:${NC} $TARGET"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${YELLOW}Next Steps:${NC}"
|
echo -e " ${YELLOW}Next Steps:${NC}"
|
||||||
echo -e " • Test the application manually"
|
echo -e " • Test the application manually"
|
||||||
if [ "$TARGET" = "local" ]; then
|
if [ "$TARGET" = "local" ]; then
|
||||||
echo -e " • Check logs: docker compose -f docker-compose.tmp.yml logs -f"
|
echo -e " • Check logs: docker compose -f docker-compose.local.yml logs -f"
|
||||||
|
echo -e " • Stop containers: docker compose -f docker-compose.local.yml down"
|
||||||
else
|
else
|
||||||
echo -e " • Check logs: npm run docker:logs"
|
echo -e " • Check logs via SSH or Docker commands on Synology"
|
||||||
fi
|
fi
|
||||||
echo -e " • Monitor backend: curl http://$HOST:$BACKEND_PORT/health"
|
echo -e " • Monitor backend: curl $BACKEND_URL/health"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
}
|
}
|
||||||
|
|||||||
82
code/websites/pokedex.online/docker-compose.docker-local.yml
Normal file
82
code/websites/pokedex.online/docker-compose.docker-local.yml
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Pokedex.Online Local Docker Compose
|
||||||
|
# For testing production builds locally before deploying to Synology
|
||||||
|
# Frontend: http://localhost:8099
|
||||||
|
# Backend: http://localhost:3099
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Frontend - Nginx serving built Vue.js app
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.frontend
|
||||||
|
container_name: pokedex-frontend-docker-local
|
||||||
|
ports:
|
||||||
|
- '8099:80'
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- pokedex-network
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
'CMD',
|
||||||
|
'wget',
|
||||||
|
'--quiet',
|
||||||
|
'--tries=1',
|
||||||
|
'--spider',
|
||||||
|
'http://localhost:80/'
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
# Backend - Node.js API server
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./server
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: pokedex-backend-docker-local
|
||||||
|
ports:
|
||||||
|
- '3099:3000'
|
||||||
|
environment:
|
||||||
|
- DEPLOYMENT_TARGET=docker-local
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
- FRONTEND_URL=http://localhost:8099
|
||||||
|
env_file:
|
||||||
|
- ./server/.env
|
||||||
|
volumes:
|
||||||
|
# Persist OAuth session data
|
||||||
|
- ./server/data:/app/data
|
||||||
|
# Persist logs
|
||||||
|
- ./server/logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- pokedex-network
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
'CMD',
|
||||||
|
'wget',
|
||||||
|
'--quiet',
|
||||||
|
'--tries=1',
|
||||||
|
'--spider',
|
||||||
|
'http://localhost:3000/health'
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pokedex-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
backend-data:
|
||||||
|
driver: local
|
||||||
|
backend-logs:
|
||||||
|
driver: local
|
||||||
@@ -10,11 +10,6 @@ services:
|
|||||||
container_name: pokedex-frontend
|
container_name: pokedex-frontend
|
||||||
ports:
|
ports:
|
||||||
- '8099:80'
|
- '8099:80'
|
||||||
- '8443:443'
|
|
||||||
environment:
|
|
||||||
# Discord OAuth redirect URI (set dynamically by deployment script)
|
|
||||||
- VITE_DISCORD_REDIRECT_URI=${VITE_DISCORD_REDIRECT_URI:-http://localhost:8099/oauth/callback}
|
|
||||||
- VITE_DISCORD_CLIENT_ID=${VITE_DISCORD_CLIENT_ID}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
backend:
|
backend:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -45,12 +40,10 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- '3099:3000'
|
- '3099:3000'
|
||||||
environment:
|
environment:
|
||||||
|
- DEPLOYMENT_TARGET=production
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
# Discord OAuth Redirect URI (set dynamically by deployment script)
|
- FRONTEND_URL=https://app.pokedex.online
|
||||||
- DISCORD_REDIRECT_URI=${DISCORD_REDIRECT_URI:-http://localhost:3099/oauth/callback}
|
|
||||||
- VITE_DISCORD_REDIRECT_URI=${VITE_DISCORD_REDIRECT_URI:-http://localhost:8099/oauth/callback}
|
|
||||||
# OAuth credentials loaded from .env file
|
|
||||||
env_file:
|
env_file:
|
||||||
- ./server/.env
|
- ./server/.env
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ services:
|
|||||||
container_name: pokedex-online
|
container_name: pokedex-online
|
||||||
ports:
|
ports:
|
||||||
- '8080:80'
|
- '8080:80'
|
||||||
- '8443:443'
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name app.pokedex.online localhost;
|
server_name app.pokedex.online localhost 10.0.0.81;
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|||||||
@@ -8,30 +8,27 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "npm run build:frontend && npm run build:backend",
|
"dev:full": "concurrently \"npm run dev\" \"npm run oauth-proxy\"",
|
||||||
|
"oauth-proxy": "npm run dev --workspace=server",
|
||||||
|
"preview": "vite preview",
|
||||||
"build:frontend": "vite build",
|
"build:frontend": "vite build",
|
||||||
"build:backend": "npm run build --workspace=server",
|
"build:backend": "npm run build --workspace=server",
|
||||||
"build:verify": "node scripts/verify-build.js",
|
"build:verify": "node scripts/verify-build.js",
|
||||||
"preview": "vite preview",
|
|
||||||
"oauth-proxy": "npm run dev --workspace=server",
|
|
||||||
"dev:full": "concurrently \"npm run dev\" \"npm run oauth-proxy\"",
|
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:ui": "vitest --ui",
|
"test:ui": "vitest --ui",
|
||||||
"test:coverage": "vitest --coverage",
|
"test:coverage": "vitest --coverage",
|
||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
"test:all": "npm run test:run && npm run test:run --workspace=server",
|
"test:all": "npm run test:run && npm run test:run --workspace=server",
|
||||||
|
"docker:local": "docker compose -f docker-compose.docker-local.yml up -d --build",
|
||||||
|
"docker:local:down": "docker compose -f docker-compose.docker-local.yml down",
|
||||||
|
"docker:local:logs": "docker compose -f docker-compose.docker-local.yml logs -f",
|
||||||
|
"docker:prod": "docker compose -f docker-compose.production.yml up -d --build",
|
||||||
|
"docker:prod:down": "docker compose -f docker-compose.production.yml down",
|
||||||
|
"docker:prod:logs": "docker compose -f docker-compose.production.yml logs -f",
|
||||||
|
"deploy:local": "./deploy.sh --target local",
|
||||||
|
"deploy:prod": "./deploy.sh --target production",
|
||||||
"install:all": "npm install && npm install --workspace=server",
|
"install:all": "npm install && npm install --workspace=server",
|
||||||
"lint": "echo 'Add ESLint when ready'",
|
"lint": "echo 'Add ESLint when ready'"
|
||||||
"docker:build": "docker compose -f docker-compose.production.yml build",
|
|
||||||
"docker:up": "docker compose -f docker-compose.production.yml up -d",
|
|
||||||
"docker:down": "docker compose -f docker-compose.production.yml down",
|
|
||||||
"docker:logs": "docker compose -f docker-compose.production.yml logs -f",
|
|
||||||
"deploy": "./deploy.sh",
|
|
||||||
"deploy:internal": "./deploy.sh --target internal",
|
|
||||||
"deploy:external": "./deploy.sh --target external",
|
|
||||||
"deploy:dry-run": "./deploy.sh --dry-run",
|
|
||||||
"deploy:quick": "./deploy.sh --skip-tests --no-backup",
|
|
||||||
"deploy:manual": "node ../../utils/deploy-pokedex.js"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Build Verification Script
|
* Build Verification Script
|
||||||
*
|
*
|
||||||
* Verifies that the production build was successful and contains
|
* Extracts and validates environment variables embedded in the built bundle.
|
||||||
* all necessary files for deployment.
|
* Ensures redirect URIs match the expected deployment target.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* BUILD_TARGET=docker-local npm run build:verify
|
||||||
|
* BUILD_TARGET=production npm run build:verify
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
@@ -12,154 +15,124 @@ import { fileURLToPath } from 'url';
|
|||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const DIST_DIR = path.join(__dirname, '..', 'dist');
|
// Expected redirect URIs for each deployment target
|
||||||
const REQUIRED_FILES = [
|
const EXPECTED_URIS = {
|
||||||
'index.html',
|
'docker-local': 'http://localhost:8099/oauth/callback',
|
||||||
'assets' // Directory
|
production: 'https://app.pokedex.online/oauth/callback'
|
||||||
];
|
};
|
||||||
|
|
||||||
const errors = [];
|
// ANSI colors
|
||||||
const warnings = [];
|
const colors = {
|
||||||
|
red: '\x1b[31m',
|
||||||
|
green: '\x1b[32m',
|
||||||
|
yellow: '\x1b[33m',
|
||||||
|
blue: '\x1b[34m',
|
||||||
|
reset: '\x1b[0m'
|
||||||
|
};
|
||||||
|
|
||||||
console.log('🔍 Verifying build output...\n');
|
function log(color, symbol, message) {
|
||||||
|
console.log(`${colors[color]}${symbol}${colors.reset} ${message}`);
|
||||||
// Check if dist directory exists
|
|
||||||
if (!fs.existsSync(DIST_DIR)) {
|
|
||||||
errors.push('dist/ directory not found - build may have failed');
|
|
||||||
console.error('❌ dist/ directory not found');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📁 Build directory: ${DIST_DIR}`);
|
function findBuiltAssets() {
|
||||||
|
const distPath = path.resolve(__dirname, '../dist/assets');
|
||||||
// Check required files
|
|
||||||
console.log('\n📋 Checking required files:');
|
if (!fs.existsSync(distPath)) {
|
||||||
for (const file of REQUIRED_FILES) {
|
throw new Error('dist/assets directory not found. Run build first.');
|
||||||
const filePath = path.join(DIST_DIR, file);
|
|
||||||
const exists = fs.existsSync(filePath);
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
const stats = fs.statSync(filePath);
|
|
||||||
const isDir = stats.isDirectory();
|
|
||||||
console.log(
|
|
||||||
` ✅ ${file} ${isDir ? '(directory)' : `(${(stats.size / 1024).toFixed(2)} KB)`}`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
errors.push(`Required file missing: ${file}`);
|
|
||||||
console.log(` ❌ ${file} - MISSING`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const files = fs.readdirSync(distPath);
|
||||||
// Check for JavaScript bundles
|
const jsFiles = files.filter(f => f.endsWith('.js') && f.startsWith('index-'));
|
||||||
console.log('\n📦 Checking JavaScript bundles:');
|
|
||||||
const assetsDir = path.join(DIST_DIR, 'assets');
|
|
||||||
if (fs.existsSync(assetsDir)) {
|
|
||||||
const files = fs.readdirSync(assetsDir);
|
|
||||||
const jsFiles = files.filter(f => f.endsWith('.js'));
|
|
||||||
const cssFiles = files.filter(f => f.endsWith('.css'));
|
|
||||||
|
|
||||||
if (jsFiles.length === 0) {
|
if (jsFiles.length === 0) {
|
||||||
errors.push('No JavaScript bundles found in assets/');
|
throw new Error('No built JavaScript files found in dist/assets/');
|
||||||
console.log(' ❌ No JavaScript bundles found');
|
|
||||||
} else {
|
|
||||||
console.log(` ✅ Found ${jsFiles.length} JavaScript bundles`);
|
|
||||||
|
|
||||||
// Show bundle sizes
|
|
||||||
const totalJsSize = jsFiles.reduce((total, file) => {
|
|
||||||
const stats = fs.statSync(path.join(assetsDir, file));
|
|
||||||
return total + stats.size;
|
|
||||||
}, 0);
|
|
||||||
console.log(
|
|
||||||
` Total JS size: ${(totalJsSize / 1024 / 1024).toFixed(2)} MB`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Warn if bundles are too large
|
|
||||||
jsFiles.forEach(file => {
|
|
||||||
const stats = fs.statSync(path.join(assetsDir, file));
|
|
||||||
const sizeMB = stats.size / 1024 / 1024;
|
|
||||||
if (sizeMB > 1) {
|
|
||||||
warnings.push(
|
|
||||||
`Large bundle detected: ${file} (${sizeMB.toFixed(2)} MB)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cssFiles.length === 0) {
|
return jsFiles.map(f => path.join(distPath, f));
|
||||||
warnings.push('No CSS files found in assets/');
|
|
||||||
console.log(' ⚠️ No CSS files found');
|
|
||||||
} else {
|
|
||||||
console.log(` ✅ Found ${cssFiles.length} CSS files`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.push('assets/ directory not found');
|
|
||||||
console.log(' ❌ assets/ directory not found');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check index.html
|
function extractRedirectUri(content) {
|
||||||
console.log('\n📄 Checking index.html:');
|
// Look for the Discord redirect URI in the built bundle
|
||||||
const indexPath = path.join(DIST_DIR, 'index.html');
|
const patterns = [
|
||||||
if (fs.existsSync(indexPath)) {
|
/VITE_DISCORD_REDIRECT_URI[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/,
|
||||||
const content = fs.readFileSync(indexPath, 'utf8');
|
/discord_redirect_uri[\"']?\s*[:=]\s*[\"']([^\"']+)[\"']/i,
|
||||||
|
/redirectUri[\"']?\s*[:=]\s*[\"']([^\"']+oauth\/callback)[\"']/,
|
||||||
// Check for script tags
|
/(https?:\/\/[^\"'\s]+\/oauth\/callback)/
|
||||||
const scriptTags = content.match(/<script[^>]*src="[^"]*"[^>]*>/g);
|
];
|
||||||
if (scriptTags && scriptTags.length > 0) {
|
|
||||||
console.log(` ✅ Found ${scriptTags.length} script tag(s)`);
|
for (const pattern of patterns) {
|
||||||
} else {
|
const match = content.match(pattern);
|
||||||
errors.push('No script tags found in index.html');
|
if (match && match[1]) {
|
||||||
console.log(' ❌ No script tags found');
|
return match[1];
|
||||||
}
|
|
||||||
|
|
||||||
// Check for stylesheet links
|
|
||||||
const linkTags = content.match(/<link[^>]*rel="stylesheet"[^>]*>/g);
|
|
||||||
if (linkTags && linkTags.length > 0) {
|
|
||||||
console.log(` ✅ Found ${linkTags.length} stylesheet link(s)`);
|
|
||||||
} else {
|
|
||||||
warnings.push('No stylesheet links found in index.html');
|
|
||||||
console.log(' ⚠️ No stylesheet links found');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.push('index.html not found');
|
|
||||||
console.log(' ❌ index.html not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate total build size
|
|
||||||
console.log('\n📊 Build Statistics:');
|
|
||||||
function getDirSize(dirPath) {
|
|
||||||
let size = 0;
|
|
||||||
const files = fs.readdirSync(dirPath);
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = path.join(dirPath, file);
|
|
||||||
const stats = fs.statSync(filePath);
|
|
||||||
|
|
||||||
if (stats.isDirectory()) {
|
|
||||||
size += getDirSize(filePath);
|
|
||||||
} else {
|
|
||||||
size += stats.size;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalSize = getDirSize(DIST_DIR);
|
function verifyBuild() {
|
||||||
console.log(` Total build size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
console.log('\n🔍 Build Verification\n');
|
||||||
|
|
||||||
// Summary
|
// Get target from environment variable
|
||||||
console.log('\n' + '='.repeat(50));
|
const target = process.env.BUILD_TARGET || 'local';
|
||||||
if (errors.length > 0) {
|
|
||||||
console.log('\n❌ Build verification FAILED\n');
|
if (!EXPECTED_URIS[target]) {
|
||||||
errors.forEach(error => console.error(` - ${error}`));
|
log('red', '❌', `Invalid BUILD_TARGET: ${target}`);
|
||||||
process.exit(1);
|
log('blue', 'ℹ', 'Valid targets: docker-local, production');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedUri = EXPECTED_URIS[target];
|
||||||
|
log('blue', 'ℹ', `Deployment target: ${target}`);
|
||||||
|
log('blue', 'ℹ', `Expected redirect URI: ${expectedUri}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find built assets
|
||||||
|
const assetFiles = findBuiltAssets();
|
||||||
|
log('green', '✅', `Found ${assetFiles.length} built asset(s)`);
|
||||||
|
|
||||||
|
// Search for redirect URI in all assets
|
||||||
|
let foundUri = null;
|
||||||
|
for (const assetFile of assetFiles) {
|
||||||
|
const content = fs.readFileSync(assetFile, 'utf8');
|
||||||
|
const uri = extractRedirectUri(content);
|
||||||
|
|
||||||
|
if (uri) {
|
||||||
|
foundUri = uri;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundUri) {
|
||||||
|
log('yellow', '⚠', 'Could not find Discord redirect URI in bundle');
|
||||||
|
log('blue', 'ℹ', 'This may be OK if OAuth is not used');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('blue', 'ℹ', `Found redirect URI: ${foundUri}`);
|
||||||
|
|
||||||
|
// Validate URI matches expected
|
||||||
|
if (foundUri === expectedUri) {
|
||||||
|
log('green', '✅', 'Redirect URI matches expected value!');
|
||||||
|
console.log('\n✅ Build verification passed\n');
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
log('red', '❌', 'Redirect URI MISMATCH!');
|
||||||
|
log('blue', 'ℹ', `Expected: ${expectedUri}`);
|
||||||
|
log('blue', 'ℹ', `Found: ${foundUri}`);
|
||||||
|
console.log('\n❌ Build verification failed\n');
|
||||||
|
console.log('This usually means the wrong .env.{mode} file was used during build.');
|
||||||
|
console.log('Check that you\'re using the correct Vite mode flag:\n');
|
||||||
|
console.log(' vite build --mode docker-local (for local Docker deployment)');
|
||||||
|
console.log(' vite build --mode production (for Synology deployment)\n');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log('red', '❌', `Verification error: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warnings.length > 0) {
|
// Run verification
|
||||||
console.log('\n⚠️ Build verification passed with warnings:\n');
|
verifyBuild();
|
||||||
warnings.forEach(warning => console.warn(` - ${warning}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n✅ Build verification PASSED');
|
|
||||||
console.log(' All required files present and valid');
|
|
||||||
console.log(' Ready for deployment\n');
|
|
||||||
|
|||||||
33
code/websites/pokedex.online/server/.env.development
Normal file
33
code/websites/pokedex.online/server/.env.development
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# BACKEND DEVELOPMENT ENVIRONMENT (localhost:3001)
|
||||||
|
# ====================================================================
|
||||||
|
# Used when running: npm run dev (from server directory)
|
||||||
|
# Supports Vite dev server at localhost:5173
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Deployment Target
|
||||||
|
DEPLOYMENT_TARGET=dev
|
||||||
|
|
||||||
|
# Server Configuration
|
||||||
|
NODE_ENV=development
|
||||||
|
PORT=3001
|
||||||
|
|
||||||
|
# Frontend URL (for CORS)
|
||||||
|
FRONTEND_URL=http://localhost:5173
|
||||||
|
|
||||||
|
# Discord OAuth Configuration
|
||||||
|
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
||||||
|
DISCORD_CLIENT_SECRET=dMRPAmWxXKQdWOKzgkPbcdsHNCifqtYV
|
||||||
|
DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback
|
||||||
|
VITE_DISCORD_REDIRECT_URI=http://localhost:5173/oauth/callback
|
||||||
|
|
||||||
|
# Challonge OAuth Configuration
|
||||||
|
CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df
|
||||||
|
CHALLONGE_CLIENT_SECRET=5dc805e41001281c2415b5ffd6b85a7855fc08b6019d9be2907fdb85ab2d56fc
|
||||||
|
CHALLONGE_REDIRECT_URI=http://localhost:5173/oauth/callback
|
||||||
|
|
||||||
|
# Discord User Permissions
|
||||||
|
DISCORD_ADMIN_USERS=.fraggin
|
||||||
|
|
||||||
|
# Session Security (dev only - change in production)
|
||||||
|
SESSION_SECRET=dev-secret-for-local-testing-only
|
||||||
33
code/websites/pokedex.online/server/.env.docker-local
Normal file
33
code/websites/pokedex.online/server/.env.docker-local
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# BACKEND LOCAL DOCKER ENVIRONMENT (localhost:3099)
|
||||||
|
# ====================================================================
|
||||||
|
# Used in docker-compose.docker-local.yml for local production testing
|
||||||
|
# Supports frontend at localhost:8099
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Deployment Target
|
||||||
|
DEPLOYMENT_TARGET=docker-local
|
||||||
|
|
||||||
|
# Server Configuration
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Frontend URL (for CORS)
|
||||||
|
FRONTEND_URL=http://localhost:8099
|
||||||
|
|
||||||
|
# Discord OAuth Configuration
|
||||||
|
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
||||||
|
DISCORD_CLIENT_SECRET=dMRPAmWxXKQdWOKzgkPbcdsHNCifqtYV
|
||||||
|
DISCORD_REDIRECT_URI=http://localhost:8099/oauth/callback
|
||||||
|
VITE_DISCORD_REDIRECT_URI=http://localhost:8099/oauth/callback
|
||||||
|
|
||||||
|
# Challonge OAuth Configuration
|
||||||
|
CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df
|
||||||
|
CHALLONGE_CLIENT_SECRET=5dc805e41001281c2415b5ffd6b85a7855fc08b6019d9be2907fdb85ab2d56fc
|
||||||
|
CHALLONGE_REDIRECT_URI=http://localhost:8099/oauth/callback
|
||||||
|
|
||||||
|
# Discord User Permissions
|
||||||
|
DISCORD_ADMIN_USERS=.fraggin
|
||||||
|
|
||||||
|
# Session Security
|
||||||
|
SESSION_SECRET=local-docker-session-secret-change-for-production
|
||||||
33
code/websites/pokedex.online/server/.env.production
Normal file
33
code/websites/pokedex.online/server/.env.production
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# ====================================================================
|
||||||
|
# BACKEND PRODUCTION ENVIRONMENT (app.pokedex.online)
|
||||||
|
# ====================================================================
|
||||||
|
# Used in docker-compose.production.yml for Synology deployment
|
||||||
|
# Supports frontend at https://app.pokedex.online
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
# Deployment Target
|
||||||
|
DEPLOYMENT_TARGET=production
|
||||||
|
|
||||||
|
# Server Configuration
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Frontend URL (for CORS)
|
||||||
|
FRONTEND_URL=https://app.pokedex.online
|
||||||
|
|
||||||
|
# Discord OAuth Configuration
|
||||||
|
VITE_DISCORD_CLIENT_ID=1466544972059775223
|
||||||
|
DISCORD_CLIENT_SECRET=dMRPAmWxXKQdWOKzgkPbcdsHNCifqtYV
|
||||||
|
DISCORD_REDIRECT_URI=https://app.pokedex.online/oauth/callback
|
||||||
|
VITE_DISCORD_REDIRECT_URI=https://app.pokedex.online/oauth/callback
|
||||||
|
|
||||||
|
# Challonge OAuth Configuration
|
||||||
|
CHALLONGE_CLIENT_ID=9d40113bdfd802c6fb01137fa9041b23342ce4f100caedad6dee865a486662df
|
||||||
|
CHALLONGE_CLIENT_SECRET=5dc805e41001281c2415b5ffd6b85a7855fc08b6019d9be2907fdb85ab2d56fc
|
||||||
|
CHALLONGE_REDIRECT_URI=https://app.pokedex.online/oauth/callback
|
||||||
|
|
||||||
|
# Discord User Permissions
|
||||||
|
DISCORD_ADMIN_USERS=.fraggin
|
||||||
|
|
||||||
|
# Session Security (IMPORTANT: Set a strong secret in production!)
|
||||||
|
SESSION_SECRET=production-secret-change-this-to-secure-random-string
|
||||||
@@ -9,6 +9,13 @@
|
|||||||
* Required environment variables for production
|
* Required environment variables for production
|
||||||
*/
|
*/
|
||||||
const REQUIRED_ENV_VARS = {
|
const REQUIRED_ENV_VARS = {
|
||||||
|
// Deployment Configuration
|
||||||
|
DEPLOYMENT_TARGET: {
|
||||||
|
required: true,
|
||||||
|
description: 'Deployment environment (dev, docker-local, production)',
|
||||||
|
validate: val => ['dev', 'docker-local', 'production'].includes(val)
|
||||||
|
},
|
||||||
|
|
||||||
// Server Configuration
|
// Server Configuration
|
||||||
NODE_ENV: {
|
NODE_ENV: {
|
||||||
required: true,
|
required: true,
|
||||||
@@ -22,6 +29,34 @@ const REQUIRED_ENV_VARS = {
|
|||||||
!isNaN(parseInt(val)) && parseInt(val) > 0 && parseInt(val) < 65536
|
!isNaN(parseInt(val)) && parseInt(val) > 0 && parseInt(val) < 65536
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Frontend URL for CORS
|
||||||
|
FRONTEND_URL: {
|
||||||
|
required: true,
|
||||||
|
description: 'Frontend URL for CORS',
|
||||||
|
validate: (val, env) => {
|
||||||
|
if (!val) return false;
|
||||||
|
|
||||||
|
// Validate that FRONTEND_URL matches DEPLOYMENT_TARGET (if set)
|
||||||
|
const target = env?.DEPLOYMENT_TARGET;
|
||||||
|
if (!target) return true; // Skip validation if target not set yet
|
||||||
|
|
||||||
|
if (target === 'dev' && !val.includes('localhost:5173')) {
|
||||||
|
console.error('⚠️ FRONTEND_URL should be http://localhost:5173 for dev target');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target === 'docker-local' && !val.includes('localhost:8099')) {
|
||||||
|
console.error('⚠️ FRONTEND_URL should be http://localhost:8099 for docker-local target');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (target === 'production' && !val.includes('app.pokedex.online')) {
|
||||||
|
console.error('⚠️ FRONTEND_URL should be https://app.pokedex.online for production target');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Optional but recommended for production
|
// Optional but recommended for production
|
||||||
SESSION_SECRET: {
|
SESSION_SECRET: {
|
||||||
required: false,
|
required: false,
|
||||||
@@ -31,14 +66,6 @@ const REQUIRED_ENV_VARS = {
|
|||||||
? 'SESSION_SECRET should be at least 32 characters for security'
|
? 'SESSION_SECRET should be at least 32 characters for security'
|
||||||
: null
|
: null
|
||||||
},
|
},
|
||||||
FRONTEND_URL: {
|
|
||||||
required: false,
|
|
||||||
description: 'Frontend URL for CORS (required in production)',
|
|
||||||
warn: (val, env) =>
|
|
||||||
env.NODE_ENV === 'production' && !val
|
|
||||||
? 'FRONTEND_URL should be set in production for proper CORS'
|
|
||||||
: null
|
|
||||||
},
|
|
||||||
|
|
||||||
// Challonge OAuth (optional)
|
// Challonge OAuth (optional)
|
||||||
CHALLONGE_CLIENT_ID: {
|
CHALLONGE_CLIENT_ID: {
|
||||||
@@ -110,8 +137,10 @@ export function validateOrExit(exitOnError = true) {
|
|||||||
|
|
||||||
// Print validation results
|
// Print validation results
|
||||||
console.log('\n🔍 Environment Validation:');
|
console.log('\n🔍 Environment Validation:');
|
||||||
|
console.log(` DEPLOYMENT_TARGET: ${process.env.DEPLOYMENT_TARGET || 'not set'}`);
|
||||||
console.log(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`);
|
console.log(` NODE_ENV: ${process.env.NODE_ENV || 'not set'}`);
|
||||||
console.log(` PORT: ${process.env.PORT || 'not set'}`);
|
console.log(` PORT: ${process.env.PORT || 'not set'}`);
|
||||||
|
console.log(` FRONTEND_URL: ${process.env.FRONTEND_URL || 'not set'}`);
|
||||||
|
|
||||||
// Show errors
|
// Show errors
|
||||||
if (result.errors.length > 0) {
|
if (result.errors.length > 0) {
|
||||||
@@ -149,7 +178,11 @@ export function validateOrExit(exitOnError = true) {
|
|||||||
* @returns {Object} Configuration object
|
* @returns {Object} Configuration object
|
||||||
*/
|
*/
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
|
const deploymentTarget = process.env.DEPLOYMENT_TARGET || 'dev';
|
||||||
|
const frontendUrl = process.env.FRONTEND_URL;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
deploymentTarget,
|
||||||
nodeEnv: process.env.NODE_ENV || 'development',
|
nodeEnv: process.env.NODE_ENV || 'development',
|
||||||
port: parseInt(process.env.PORT || '3001'),
|
port: parseInt(process.env.PORT || '3001'),
|
||||||
isProduction: process.env.NODE_ENV === 'production',
|
isProduction: process.env.NODE_ENV === 'production',
|
||||||
@@ -165,16 +198,10 @@ export function getConfig() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
// CORS
|
// CORS - Single origin based on deployment target
|
||||||
cors: {
|
cors: {
|
||||||
origin:
|
origin: frontendUrl,
|
||||||
process.env.NODE_ENV === 'production'
|
credentials: true
|
||||||
? process.env.FRONTEND_URL
|
|
||||||
: [
|
|
||||||
'http://localhost:5173',
|
|
||||||
'http://localhost:5174',
|
|
||||||
'http://localhost:5175'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Security
|
// Security
|
||||||
@@ -192,3 +219,8 @@ export function getConfig() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run validation when executed directly
|
||||||
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
|
validateOrExit();
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
"pokedex:install": "cd code/websites/pokedex.online && npm install",
|
"pokedex:install": "cd code/websites/pokedex.online && npm install",
|
||||||
"pokedex:dev": "cd code/websites/pokedex.online && npm run dev",
|
"pokedex:dev": "cd code/websites/pokedex.online && npm run dev",
|
||||||
"pokedex:dev:full": "cd code/websites/pokedex.online && npm run dev:full",
|
"pokedex:dev:full": "cd code/websites/pokedex.online && npm run dev:full",
|
||||||
"pokedex:build": "cd code/websites/pokedex.online && npm run build",
|
"pokedex:preview": "cd code/websites/pokedex.online && npm run preview",
|
||||||
"pokedex:preview": "cd code/websites/pokedex.online && npm run preview"
|
"pokedex:docker:local": "cd code/websites/pokedex.online && npm run docker:local",
|
||||||
|
"pokedex:deploy:local": "cd code/websites/pokedex.online && npm run deploy:local",
|
||||||
|
"pokedex:deploy:prod": "cd code/websites/pokedex.online && npm run deploy:prod"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "FragginWagon <greg.r.jacobs@gmail.com> (http://binarywasteland.com/)",
|
"author": "FragginWagon <greg.r.jacobs@gmail.com> (http://binarywasteland.com/)",
|
||||||
|
|||||||
Reference in New Issue
Block a user