From 1c7fa781ad41eaf95318f1abca7960cba77c00e4 Mon Sep 17 00:00:00 2001 From: paul Date: Sun, 13 Jul 2025 21:24:07 +0200 Subject: [PATCH] fix: configure PostgreSQL for production and clean up deployment - Fix database configuration to use PostgreSQL in production - Add knexfile.js to support both SQLite (dev) and PostgreSQL (prod) - Create admin user creation script (scripts/create-admin.js) - Clean up docker-compose files: - Remove redundant docker-compose.yml and docker-compose.local.yml - Create docker-compose.dev.yml for development - Update docker-compose.prod.yml with proper DB configuration - Clean up environment files: - Update .env.example for development - Update .env.production.example with proper settings - Remove redundant .env.local - Update backend .env.example with database configuration options - Create comprehensive DEPLOYMENT.md with admin setup instructions - Fix production database name consistency (picpeak instead of photoapp) --- .env.example | 48 ++- .env.production.example | 64 +-- DEPLOYMENT.md | 711 +++++++++++++------------------- backend/.env.example | 11 +- backend/knexfile.js | 46 +++ backend/scripts/create-admin.js | 77 ++++ backend/src/database/db.js | 79 ++-- docker-compose.dev.yml | 61 +++ docker-compose.local.yml | 109 ----- docker-compose.prod.yml | 13 +- docker-compose.yml | 49 --- 11 files changed, 577 insertions(+), 691 deletions(-) create mode 100644 backend/knexfile.js create mode 100644 backend/scripts/create-admin.js create mode 100644 docker-compose.dev.yml delete mode 100644 docker-compose.local.yml delete mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example index 8da9d03..b008cf9 100644 --- a/.env.example +++ b/.env.example @@ -1,26 +1,34 @@ -# JWT Secret for authentication -# IMPORTANT: Generate a secure random secret with: openssl rand -hex 32 -# NEVER use the default value or commit the actual secret to version control -JWT_SECRET=CHANGE_ME_TO_A_64_CHARACTER_SECURE_RANDOM_STRING_GENERATED_BY_OPENSSL +# Environment Configuration Template +# Copy this file to .env and adjust values for your environment -# URLs -ADMIN_URL=https://admin.photos.yourdomain.com -FRONTEND_URL=https://photos.yourdomain.com +# Development: Use docker-compose.dev.yml +# Production: Use docker-compose.prod.yml with .env.production.example -# Database (for PostgreSQL in production) -DB_USER=photoapp -DB_PASSWORD=secure-password-here -DB_NAME=photo_sharing +# JWT Secret (CRITICAL for production) +# Generate with: openssl rand -base64 32 +JWT_SECRET=dev-secret-change-in-production + +# Application URLs +ADMIN_URL=http://localhost:3005 +FRONTEND_URL=http://localhost:3005 + +# Database Configuration +# SQLite is used for development by default +# For production PostgreSQL config, see .env.production.example +DATABASE_CLIENT=sqlite3 +DATABASE_PATH=./data/photo_sharing.db # Email Configuration -SMTP_HOST=smtp.gmail.com -SMTP_PORT=587 +# Development: Uses Mailhog (included in docker-compose.dev.yml) +# Production: Configure real SMTP server +SMTP_HOST=mailhog +SMTP_PORT=1025 SMTP_SECURE=false -SMTP_USER=your-email@gmail.com -SMTP_PASS=your-app-password -EMAIL_FROM=noreply@yourdomain.com +SMTP_USER= +SMTP_PASS= +EMAIL_FROM=noreply@localhost -# Umami Analytics -UMAMI_URL=https://analytics.yourdomain.com -UMAMI_WEBSITE_ID=your-website-id -UMAMI_HASH_SALT=random-salt-here +# Optional: Umami Analytics +UMAMI_URL= +UMAMI_WEBSITE_ID= +UMAMI_HASH_SALT= \ No newline at end of file diff --git a/.env.production.example b/.env.production.example index 8bb4757..0da0232 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,60 +1,32 @@ +# Production Environment Configuration Template +# Copy this file to .env and fill in your values + # Application URLs -FRONTEND_HOST=photos.yourdomain.com -BACKEND_HOST=api.photos.yourdomain.com -ADMIN_URL=https://admin.photos.yourdomain.com -FRONTEND_URL=https://photos.yourdomain.com +ADMIN_URL=https://yourdomain.com +FRONTEND_URL=https://yourdomain.com -# Database Configuration -DB_NAME=photo_sharing -DB_USER=photoapp -DB_PASSWORD=your-secure-password-here +# Security - CRITICAL: Generate a secure random JWT secret +# You can generate one with: openssl rand -base64 32 +JWT_SECRET=your-secure-random-jwt-secret-here -# JWT Configuration -JWT_SECRET=your-jwt-secret-here +# Database Configuration (PostgreSQL) +DB_USER=picpeak +DB_PASSWORD=your-secure-database-password +DB_NAME=picpeak # Email Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 -SMTP_SECURE=false +SMTP_SECURE=true SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password EMAIL_FROM=noreply@yourdomain.com -# Umami Analytics +# Umami Analytics (Optional) UMAMI_URL=https://analytics.yourdomain.com -UMAMI_HOST=analytics.yourdomain.com UMAMI_WEBSITE_ID=your-website-id -UMAMI_HASH_SALT=your-random-salt -UMAMI_DB_PASSWORD=umami-db-password +UMAMI_HASH_SALT=your-random-hash-salt -# Traefik Configuration -TRAEFIK_HOST=traefik.yourdomain.com -ACME_EMAIL=admin@yourdomain.com -TRAEFIK_DASHBOARD_AUTH=admin:$2y$10$... # Use htpasswd to generate - -# Docker Registry (optional) -REGISTRY_URL=registry.yourdomain.com -VERSION=latest - -# Monitoring -DOMAIN=yourdomain.com -GRAFANA_USER=admin -GRAFANA_PASSWORD=your-grafana-password - -# OAuth Configuration (optional) -OAUTH_AUTH_URL=https://auth.yourdomain.com/oauth2/auth -OAUTH_TOKEN_URL=https://auth.yourdomain.com/oauth2/token -OAUTH_USER_URL=https://auth.yourdomain.com/oauth2/userinfo -OAUTH_CLIENT_ID=photo-sharing -OAUTH_CLIENT_SECRET=your-oauth-secret -OAUTH_SECRET=your-random-secret -COOKIE_DOMAIN=.yourdomain.com -OAUTH_WHITELIST=admin@yourdomain.com - -# Backup Configuration (optional) -S3_BACKUP_BUCKET=your-backup-bucket - -# Drone CI Configuration -DRONE_RPC_SECRET=your-drone-secret -DRONE_GITHUB_CLIENT_ID=your-github-client-id -DRONE_GITHUB_CLIENT_SECRET=your-github-client-secret \ No newline at end of file +# First Admin User (for initial setup) +# Run: docker-compose exec backend node scripts/create-admin.js --email admin@yourdomain.com +ADMIN_EMAIL=admin@yourdomain.com \ No newline at end of file diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 622d6b9..02c907a 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,483 +1,346 @@ -# Photo Sharing Platform - Production Deployment Guide +# PicPeak Deployment Guide -This guide covers deploying the photo sharing platform using Docker Swarm, Traefik, and Drone CI/CD. +This guide covers deploying PicPeak for development and production environments. ## Table of Contents -- [Prerequisites](#prerequisites) -- [Infrastructure Setup](#infrastructure-setup) -- [Docker Swarm Setup](#docker-swarm-setup) -- [Traefik Setup](#traefik-setup) -- [Application Deployment](#application-deployment) -- [CI/CD with Drone](#cicd-with-drone) -- [Monitoring](#monitoring) -- [Backup and Recovery](#backup-and-recovery) +- [Quick Start (Development)](#quick-start-development) +- [Production Deployment](#production-deployment) +- [Admin User Setup](#admin-user-setup) +- [Configuration Reference](#configuration-reference) - [Troubleshooting](#troubleshooting) -## Prerequisites +## Quick Start (Development) -### Hardware Requirements -- **Manager Node**: 2 CPU cores, 4GB RAM, 50GB storage -- **Worker Nodes**: 2 CPU cores, 2GB RAM, 20GB storage -- **Storage**: SSD recommended for database and photo storage - -### Software Requirements -- Ubuntu 20.04+ or similar Linux distribution -- Docker Engine 20.10+ -- Docker Compose 2.0+ -- Git -- SSL certificates (automated with Let's Encrypt) - -### Network Requirements -- Ports 80, 443 open for web traffic -- Port 2377 for Swarm management -- Ports 7946, 4789 for Swarm networking -- Static IP or reliable dynamic DNS - -## Infrastructure Setup - -### 1. Install Docker +### 1. Clone and Setup ```bash -# Install Docker -curl -fsSL https://get.docker.com | sh +git clone https://github.com/yourusername/picpeak.git +cd picpeak -# Add user to docker group -sudo usermod -aG docker $USER - -# Enable Docker service -sudo systemctl enable docker -sudo systemctl start docker -``` - -### 2. Configure Firewall - -```bash -# Allow Docker Swarm ports -sudo ufw allow 2377/tcp -sudo ufw allow 7946/tcp -sudo ufw allow 7946/udp -sudo ufw allow 4789/udp - -# Allow web traffic -sudo ufw allow 80/tcp -sudo ufw allow 443/tcp -``` - -## Docker Swarm Setup - -### 1. Initialize Swarm - -On the manager node: - -```bash -cd deploy/scripts -sudo ./init-swarm.sh -``` - -This script will: -- Initialize Docker Swarm -- Create overlay networks -- Label nodes for service placement -- Create required directories - -### 2. Join Worker Nodes - -On each worker node, run the join command displayed by the init script: - -```bash -docker swarm join --token SWMTKN-1-xxx... manager-ip:2377 -``` - -### 3. Verify Swarm - -```bash -docker node ls -``` - -## Application Configuration - -### 1. Environment Setup - -```bash # Copy environment template -cp .env.production.example .env.production +cp .env.example .env -# Edit with your values -nano .env.production -``` - -Required configurations: -- Domain names for frontend, backend, and services -- SMTP credentials for email -- Database passwords -- JWT secrets - -### 2. Create Docker Secrets - -```bash -cd deploy/scripts -./create-secrets.sh -``` - -This will create all required secrets in Docker Swarm. Save the generated passwords! - -## Traefik Setup - -### 1. Deploy Traefik - -```bash -cd deploy/traefik - -# Create traefik network -docker network create --driver overlay traefik-public - -# Deploy Traefik stack -docker stack deploy -c docker-compose.traefik.yml traefik -``` - -### 2. Verify Traefik - -```bash -# Check service status -docker service ls | grep traefik - -# View logs -docker service logs traefik_traefik -``` - -Access Traefik dashboard at: `https://traefik.yourdomain.com/dashboard/` - -## Application Deployment - -### 1. Build Images (if using local registry) - -```bash -# Build frontend -cd frontend -docker build -t photo-sharing-frontend:latest . - -# Build backend -cd ../backend -docker build -t photo-sharing-backend:latest . -``` - -### 2. Deploy Application Stack - -```bash -cd deploy/scripts -./deploy.sh -``` - -Options: -- `--env FILE`: Specify environment file -- `--registry URL`: Docker registry URL -- `--version VERSION`: Image version to deploy - -### 3. Verify Deployment - -```bash -# Check all services -docker service ls - -# Check specific service -docker service ps photo-sharing_backend - -# View logs -docker service logs photo-sharing_backend -f -``` - -### 4. Run Database Migrations - -The deploy script automatically runs migrations, but you can run manually: - -```bash -docker exec $(docker ps -q -f name=photo-sharing_backend) npm run migrate -``` - -## CI/CD with Drone - -### 1. Drone Server Setup - -Deploy Drone server on your CI infrastructure: - -```bash -docker run \ - --volume=/var/lib/drone:/data \ - --env=DRONE_GITHUB_CLIENT_ID=your-id \ - --env=DRONE_GITHUB_CLIENT_SECRET=your-secret \ - --env=DRONE_RPC_SECRET=your-rpc-secret \ - --env=DRONE_SERVER_HOST=drone.yourdomain.com \ - --env=DRONE_SERVER_PROTO=https \ - --publish=80:80 \ - --publish=443:443 \ - --restart=always \ - --detach=true \ - --name=drone \ - drone/drone:2 -``` - -### 2. Drone Runner Setup - -On build servers: - -```bash -docker run -d \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -e DRONE_RPC_PROTO=https \ - -e DRONE_RPC_HOST=drone.yourdomain.com \ - -e DRONE_RPC_SECRET=your-rpc-secret \ - -e DRONE_RUNNER_CAPACITY=2 \ - -e DRONE_RUNNER_NAME=runner-1 \ - -p 3000:3000 \ - --restart always \ - --name runner \ - drone/drone-runner-docker:1 -``` - -### 3. Repository Setup - -1. Enable repository in Drone UI -2. Add secrets in Drone: - - `docker_username` - - `docker_password` - - `docker_registry` - - `staging_swarm_host` - - `staging_swarm_user` - - `staging_swarm_key` - - `prod_swarm_host` - - `prod_swarm_user` - - `prod_swarm_key` - - `slack_webhook` - -### 4. Deployment Workflow - -- Push to `develop` → Deploy to staging -- Create tag → Deploy to production -- Automatic rollback on failure - -## Monitoring - -### 1. Deploy Monitoring Stack - -```bash -cd deploy/monitoring - -# Deploy monitoring services -docker stack deploy -c docker-compose.monitoring.yml monitoring +# Start development environment +docker-compose -f docker-compose.dev.yml up -d ``` ### 2. Access Services -- Grafana: `https://grafana.yourdomain.com` -- Prometheus: `https://prometheus.yourdomain.com` -- Alertmanager: `https://alerts.yourdomain.com` +- Frontend: http://localhost:3005 +- Backend API: http://localhost:3001 +- MailHog (email testing): http://localhost:8025 -### 3. Configure Alerts +### 3. Create Admin User -Create alert rules in `deploy/monitoring/alerts/`: +```bash +docker-compose -f docker-compose.dev.yml exec backend node scripts/create-admin.js \ + --email admin@localhost \ + --username admin \ + --password admin123 +``` + +## Production Deployment + +### Prerequisites + +- Docker and Docker Compose installed +- Domain with DNS configured +- SSL/TLS handled by reverse proxy (Traefik, Nginx, etc.) + +### 1. Environment Setup + +```bash +# Copy production template +cp .env.production.example .env + +# Generate secure secrets +echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env +echo "DB_PASSWORD=$(openssl rand -base64 24)" >> .env +``` + +Edit `.env` with your configuration: + +```env +# Your domain +ADMIN_URL=https://yourdomain.com +FRONTEND_URL=https://yourdomain.com + +# Database (PostgreSQL) +DB_USER=picpeak +DB_NAME=picpeak +# DB_PASSWORD already generated above + +# Email +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_SECURE=true +SMTP_USER=your-email@gmail.com +SMTP_PASS=your-app-password +EMAIL_FROM=noreply@yourdomain.com +``` + +### 2. Frontend Configuration + +```bash +# Configure frontend for production +echo "VITE_API_URL=/api" > frontend/.env.production +``` + +### 3. Deploy with Docker Compose + +```bash +# Build and start services +docker-compose -f docker-compose.prod.yml up -d + +# Check status +docker-compose -f docker-compose.prod.yml ps + +# View logs +docker-compose -f docker-compose.prod.yml logs -f +``` + +### 4. Deploy with Traefik + +If using Traefik, create `docker-compose.override.yml`: ```yaml -groups: - - name: photo-sharing - rules: - - alert: ServiceDown - expr: up{job="photo-sharing-backend"} == 0 - for: 5m - annotations: - summary: "Photo sharing backend is down" +version: '3.8' + +services: + frontend: + labels: + - "traefik.enable=true" + - "traefik.http.routers.picpeak.rule=Host(`yourdomain.com`)" + - "traefik.http.routers.picpeak.entrypoints=websecure" + - "traefik.http.routers.picpeak.tls.certresolver=letsencrypt" + - "traefik.http.services.picpeak.loadbalancer.server.port=80" + networks: + - traefik + - picpeak + +networks: + traefik: + external: true ``` -## Backup and Recovery +## Admin User Setup -### 1. Automated Backups +### Create First Admin -Set up cron job for automated backups: +After deployment, create your admin user: ```bash -# Edit crontab -crontab -e +# Production +docker-compose -f docker-compose.prod.yml exec backend node scripts/create-admin.js \ + --email admin@yourdomain.com \ + --username admin \ + --password yourSecurePassword -# Add daily backup at 2 AM -0 2 * * * /opt/photo-sharing/deploy/scripts/backup.sh +# Auto-generate password +docker-compose -f docker-compose.prod.yml exec backend node scripts/create-admin.js \ + --email admin@yourdomain.com ``` -### 2. Manual Backup +The script will display: +- ✅ Admin user created successfully! +- Email: admin@yourdomain.com +- Username: admin +- Login URL: https://yourdomain.com/admin/login +- Password: (save this if auto-generated!) + +### Managing Admin Users ```bash -cd deploy/scripts -./backup.sh +# List admin users +docker-compose -f docker-compose.prod.yml exec backend \ + psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \ + -c "SELECT id, username, email, is_active, last_login FROM admin_users;" + +# Deactivate user +docker-compose -f docker-compose.prod.yml exec backend \ + psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \ + -c "UPDATE admin_users SET is_active = false WHERE email = 'user@example.com';" ``` -### 3. Restore from Backup +## Configuration Reference + +### Database Configuration + +PicPeak automatically detects the environment and uses: +- **Development**: SQLite (`./data/photo_sharing.db`) +- **Production**: PostgreSQL (configured via environment variables) + +### Environment Variables + +#### Required for Production + +| Variable | Description | Example | +|----------|-------------|---------| +| `JWT_SECRET` | JWT signing key | `openssl rand -base64 32` | +| `DB_PASSWORD` | PostgreSQL password | `openssl rand -base64 24` | +| `ADMIN_URL` | Admin panel URL | `https://yourdomain.com` | +| `FRONTEND_URL` | Frontend URL | `https://yourdomain.com` | +| `EMAIL_FROM` | Sender email | `noreply@yourdomain.com` | + +#### Email Configuration + +| Variable | Description | Example | +|----------|-------------|---------| +| `SMTP_HOST` | SMTP server | `smtp.gmail.com` | +| `SMTP_PORT` | SMTP port | `587` | +| `SMTP_SECURE` | Use TLS | `true` | +| `SMTP_USER` | SMTP username | `your-email@gmail.com` | +| `SMTP_PASS` | SMTP password | App-specific password | + +### Storage Paths + +- Photos: `./storage/events/active/` +- Archives: `./storage/events/archived/` +- Thumbnails: `./storage/thumbnails/` +- Uploads: `./storage/uploads/` + +## Backup and Restore + +### Backup Database ```bash -# Extract backup -tar -xzf backup-20240615-020000.tar.gz +# PostgreSQL backup +docker-compose -f docker-compose.prod.yml exec db \ + pg_dump -U picpeak picpeak > backup-$(date +%Y%m%d).sql -# Restore database -docker exec -i $(docker ps -q -f name=photo-sharing_db) \ - psql -U postgres photo_sharing < backup-20240615-020000/database.sql - -# Restore photos -tar -xzf backup-20240615-020000/photos.tar.gz -C /opt/photo-sharing/ - -# Restore volumes -docker run --rm \ - -v photo-sharing_app-data:/data \ - -v $(pwd)/backup-20240615-020000:/backup \ - alpine tar -xzf /backup/volume-photo-sharing_app-data.tar.gz -C /data +# Backup storage +tar -czf storage-backup-$(date +%Y%m%d).tar.gz ./storage ``` -## Maintenance - -### 1. Scaling Services +### Restore Database ```bash -# Scale backend to 5 replicas -docker service scale photo-sharing_backend=5 +# PostgreSQL restore +docker-compose -f docker-compose.prod.yml exec -T db \ + psql -U picpeak picpeak < backup-20240115.sql -# Scale frontend to 3 replicas -docker service scale photo-sharing_frontend=3 +# Restore storage +tar -xzf storage-backup-20240115.tar.gz ``` -### 2. Rolling Updates - -```bash -# Update backend image -docker service update \ - --image registry.yourdomain.com/photo-sharing-backend:v2.0 \ - photo-sharing_backend -``` - -### 3. Drain Node for Maintenance - -```bash -# Drain node -docker node update --availability drain worker-1 - -# Perform maintenance... - -# Activate node -docker node update --availability active worker-1 -``` - -## Troubleshooting - -### Common Issues - -#### 1. Service Won't Start -```bash -# Check service status -docker service ps photo-sharing_backend --no-trunc - -# View detailed logs -docker service logs photo-sharing_backend --details -``` - -#### 2. Database Connection Issues -```bash -# Check database logs -docker service logs photo-sharing_db - -# Test connection -docker exec $(docker ps -q -f name=photo-sharing_db) \ - pg_isready -U postgres -``` - -#### 3. Traefik Certificate Issues -```bash -# Check Traefik logs -docker service logs traefik_traefik | grep acme - -# Remove and regenerate certificates -rm -rf /opt/traefik/letsencrypt/acme.json -docker service update --force traefik_traefik -``` - -#### 4. Storage Issues -```bash -# Check disk usage -df -h - -# Clean up Docker -docker system prune -a -``` - -### Debug Mode - -Enable debug logging: - -```bash -# Update service with debug logging -docker service update \ - --env-add LOG_LEVEL=debug \ - photo-sharing_backend -``` +## Monitoring ### Health Checks ```bash -# Check all endpoints -curl -f https://photos.yourdomain.com/health -curl -f https://api.photos.yourdomain.com/api/health -curl -f https://traefik.yourdomain.com/ping +# Backend health +curl https://yourdomain.com/api/health + +# Frontend health +curl https://yourdomain.com/health ``` -## Security Best Practices +### Logs -1. **Regular Updates** - - Keep Docker and system packages updated - - Update application dependencies regularly - - Monitor security advisories +```bash +# All services +docker-compose -f docker-compose.prod.yml logs -f -2. **Access Control** - - Use strong passwords for all services - - Enable 2FA where possible - - Restrict SSH access to specific IPs - - Use Docker secrets for sensitive data +# Specific service +docker-compose -f docker-compose.prod.yml logs -f backend -3. **Network Security** - - Use internal networks for service communication - - Enable firewall rules - - Use TLS for all external communication - - Regular security scans with Trivy +# Last 100 lines +docker-compose -f docker-compose.prod.yml logs --tail=100 backend +``` -4. **Backup Security** - - Encrypt backups at rest - - Test restore procedures regularly - - Store backups in multiple locations - - Rotate old backups +## Troubleshooting -## Performance Tuning +### Backend Won't Start -1. **Database Optimization** - ```sql - -- Add indexes for common queries - CREATE INDEX idx_photos_event_id ON photos(event_id); - CREATE INDEX idx_access_logs_event_id ON access_logs(event_id); +1. Check database connection: + ```bash + docker-compose -f docker-compose.prod.yml logs db ``` -2. **Image Optimization** - - Use CDN for static assets - - Enable aggressive caching - - Optimize image sizes before upload - -3. **Service Limits** - ```yaml - deploy: - resources: - limits: - cpus: '2' - memory: 1G - reservations: - cpus: '0.5' - memory: 256M +2. Verify environment variables: + ```bash + docker-compose -f docker-compose.prod.yml exec backend env | grep DB_ ``` -## Support +### Can't Login as Admin -For issues and questions: -- Check logs: `docker service logs ` -- Review documentation: [README.md](README.md) -- Check monitoring dashboards -- Contact: admin@yourdomain.com \ No newline at end of file +1. Verify admin user exists: + ```bash + docker-compose -f docker-compose.prod.yml exec backend \ + psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \ + -c "SELECT * FROM admin_users;" + ``` + +2. Reset admin password: + ```bash + # Create new admin with different email + docker-compose -f docker-compose.prod.yml exec backend \ + node scripts/create-admin.js --email newadmin@yourdomain.com + ``` + +### Photos Not Loading + +1. Check file permissions: + ```bash + ls -la ./storage/events/active/ + ``` + +2. Verify nginx proxy configuration: + ```bash + docker-compose -f docker-compose.prod.yml exec frontend \ + cat /etc/nginx/conf.d/default.conf + ``` + +### Email Not Sending + +1. Check email configuration: + ```bash + docker-compose -f docker-compose.prod.yml exec backend env | grep SMTP_ + ``` + +2. View email queue: + ```bash + docker-compose -f docker-compose.prod.yml exec backend \ + psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \ + -c "SELECT * FROM email_queue WHERE status = 'failed';" + ``` + +## Maintenance + +### Update Application + +```bash +# Pull latest changes +git pull + +# Rebuild images +docker-compose -f docker-compose.prod.yml build + +# Restart services +docker-compose -f docker-compose.prod.yml up -d +``` + +### Clean Up + +```bash +# Remove unused images +docker image prune -a + +# Clean up logs +docker-compose -f docker-compose.prod.yml logs --tail=0 -f + +# Remove old archives +find ./storage/events/archived -name "*.zip" -mtime +90 -delete +``` + +## Security Checklist + +- [ ] Generated secure `JWT_SECRET` +- [ ] Generated secure `DB_PASSWORD` +- [ ] HTTPS enabled via reverse proxy +- [ ] Changed default admin credentials +- [ ] Configured real SMTP server +- [ ] Set file permissions: `chmod 600 .env` +- [ ] Firewall configured +- [ ] Regular backups scheduled +- [ ] Monitoring enabled \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example index e9a14cf..94a77c7 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -24,10 +24,19 @@ ARCHIVE_PATH=./storage/events/archived # Logging LOG_LEVEL=info -# Database (for production, consider PostgreSQL) +# Database Configuration +# Development: Use SQLite DATABASE_CLIENT=sqlite3 DATABASE_PATH=./data/photo_sharing.db +# Production: Use PostgreSQL +# DATABASE_CLIENT=pg +# DB_HOST=localhost +# DB_PORT=5432 +# DB_USER=picpeak +# DB_PASSWORD=your-secure-password +# DB_NAME=picpeak + # Umami Analytics (optional) UMAMI_URL= UMAMI_WEBSITE_ID= diff --git a/backend/knexfile.js b/backend/knexfile.js new file mode 100644 index 0000000..9c8eb36 --- /dev/null +++ b/backend/knexfile.js @@ -0,0 +1,46 @@ +require('dotenv').config(); + +const path = require('path'); + +// Database configuration for different environments +const config = { + development: { + client: process.env.DATABASE_CLIENT || 'sqlite3', + connection: process.env.DATABASE_CLIENT === 'pg' ? { + host: process.env.DB_HOST || 'localhost', + port: process.env.DB_PORT || 5432, + user: process.env.DB_USER || 'postgres', + password: process.env.DB_PASSWORD || 'postgres', + database: process.env.DB_NAME || 'photo_sharing' + } : { + filename: path.join(__dirname, process.env.DATABASE_PATH || './data/photo_sharing.db') + }, + useNullAsDefault: process.env.DATABASE_CLIENT !== 'pg', + migrations: { + directory: './migrations' + }, + seeds: { + directory: './seeds' + } + }, + + production: { + client: process.env.DATABASE_CLIENT || 'pg', + connection: { + host: process.env.DB_HOST || 'postgres', + port: process.env.DB_PORT || 5432, + user: process.env.DB_USER || 'postgres', + password: process.env.DB_PASSWORD || 'postgres', + database: process.env.DB_NAME || 'photo_sharing' + }, + pool: { + min: 2, + max: 10 + }, + migrations: { + directory: './migrations' + } + } +}; + +module.exports = config[process.env.NODE_ENV || 'development']; \ No newline at end of file diff --git a/backend/scripts/create-admin.js b/backend/scripts/create-admin.js new file mode 100644 index 0000000..02ba5cb --- /dev/null +++ b/backend/scripts/create-admin.js @@ -0,0 +1,77 @@ +#!/usr/bin/env node + +/** + * Script to create an admin user + * Usage: node scripts/create-admin.js --email admin@example.com --username admin --password yourpassword + * + * If no password is provided, a random one will be generated and displayed + */ + +require('dotenv').config(); +const bcrypt = require('bcryptjs'); +const { db } = require('../src/database/db'); +const crypto = require('crypto'); + +// Parse command line arguments +const args = process.argv.slice(2); +const getArg = (name) => { + const index = args.findIndex(arg => arg === `--${name}`); + return index !== -1 && args[index + 1] ? args[index + 1] : null; +}; + +const email = getArg('email'); +const username = getArg('username') || email?.split('@')[0] || 'admin'; +let password = getArg('password'); + +// Validate email +if (!email) { + console.error('Error: Email is required. Use --email admin@example.com'); + process.exit(1); +} + +// Generate password if not provided +if (!password) { + password = crypto.randomBytes(12).toString('base64').slice(0, 16); + console.log(`Generated password: ${password}`); + console.log('Please save this password securely!'); +} + +async function createAdmin() { + try { + // Check if user already exists + const existingUser = await db('admin_users') + .where('email', email) + .orWhere('username', username) + .first(); + + if (existingUser) { + console.error(`Error: User with email "${email}" or username "${username}" already exists`); + process.exit(1); + } + + // Hash password + const passwordHash = await bcrypt.hash(password, 10); + + // Create admin user + await db('admin_users').insert({ + username, + email, + password_hash: passwordHash, + is_active: true, + created_at: new Date(), + updated_at: new Date() + }); + + console.log(`✅ Admin user created successfully!`); + console.log(` Email: ${email}`); + console.log(` Username: ${username}`); + console.log(` Login URL: ${process.env.ADMIN_URL || 'http://localhost:3000'}/admin/login`); + + process.exit(0); + } catch (error) { + console.error('Error creating admin user:', error.message); + process.exit(1); + } +} + +createAdmin(); \ No newline at end of file diff --git a/backend/src/database/db.js b/backend/src/database/db.js index 94d8050..773f6b8 100644 --- a/backend/src/database/db.js +++ b/backend/src/database/db.js @@ -1,13 +1,7 @@ const knex = require('knex'); -const path = require('path'); +const knexConfig = require('../../knexfile'); -const db = knex({ - client: 'sqlite3', - connection: { - filename: path.join(__dirname, '../../data/photo_sharing.db') - }, - useNullAsDefault: true -}); +const db = knex(knexConfig); async function initializeDatabase() { // Events table @@ -35,37 +29,42 @@ async function initializeDatabase() { } else { // Check if color_theme needs to be updated to TEXT type // This is needed for larger theme configurations - try { - await db.raw(` - CREATE TABLE IF NOT EXISTS events_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - slug TEXT UNIQUE NOT NULL, - event_type TEXT NOT NULL, - event_name TEXT NOT NULL, - event_date DATE NOT NULL, - host_email TEXT NOT NULL, - admin_email TEXT NOT NULL, - password_hash TEXT NOT NULL, - welcome_message TEXT, - color_theme TEXT, - share_link TEXT UNIQUE NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - expires_at DATETIME NOT NULL, - is_active BOOLEAN DEFAULT 1, - is_archived BOOLEAN DEFAULT 0, - archive_path TEXT, - archived_at DATETIME, - allow_user_uploads BOOLEAN DEFAULT 0, - upload_category_id INTEGER - ) - `); - - await db.raw(`INSERT INTO events_new SELECT * FROM events`); - await db.raw(`DROP TABLE events`); - await db.raw(`ALTER TABLE events_new RENAME TO events`); - } catch (error) { - // If the migration fails, it might already have been applied - console.log('Color theme migration may have already been applied'); + const isPostgres = knexConfig.client === 'pg'; + + if (!isPostgres) { + // SQLite-specific migration + try { + await db.raw(` + CREATE TABLE IF NOT EXISTS events_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + slug TEXT UNIQUE NOT NULL, + event_type TEXT NOT NULL, + event_name TEXT NOT NULL, + event_date DATE NOT NULL, + host_email TEXT NOT NULL, + admin_email TEXT NOT NULL, + password_hash TEXT NOT NULL, + welcome_message TEXT, + color_theme TEXT, + share_link TEXT UNIQUE NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + expires_at DATETIME NOT NULL, + is_active BOOLEAN DEFAULT 1, + is_archived BOOLEAN DEFAULT 0, + archive_path TEXT, + archived_at DATETIME, + allow_user_uploads BOOLEAN DEFAULT 0, + upload_category_id INTEGER + ) + `); + + await db.raw(`INSERT INTO events_new SELECT * FROM events`); + await db.raw(`DROP TABLE events`); + await db.raw(`ALTER TABLE events_new RENAME TO events`); + } catch (error) { + // If the migration fails, it might already have been applied + console.log('Color theme migration may have already been applied'); + } } } @@ -225,4 +224,4 @@ async function logActivity(activityType, metadata = {}, eventId = null, actor = } } -module.exports = { db, initializeDatabase, logActivity }; +module.exports = { db, initializeDatabase, logActivity }; \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..51a4c40 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,61 @@ +# Development Docker Compose Configuration +version: '3.8' + +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "3001:3000" + environment: + - NODE_ENV=development + - PORT=3000 + - JWT_SECRET=dev-secret-change-in-production + - ADMIN_URL=http://localhost:3005 + - FRONTEND_URL=http://localhost:3005 + - DATABASE_CLIENT=sqlite3 + - DATABASE_PATH=./data/photo_sharing.db + # Email - uses Mailhog + - SMTP_HOST=mailhog + - SMTP_PORT=1025 + - SMTP_SECURE=false + - EMAIL_FROM=noreply@photo-sharing.local + volumes: + - ./backend:/app + - /app/node_modules + - ./storage:/app/storage + - ./data:/app/data + - ./logs:/app/logs + depends_on: + - mailhog + command: sh -c "npm install && npm run dev" + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "3005:80" + environment: + - NODE_ENV=development + volumes: + - ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - backend + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + + mailhog: + image: mailhog/mailhog:latest + ports: + - "1025:1025" # SMTP + - "8025:8025" # Web UI \ No newline at end of file diff --git a/docker-compose.local.yml b/docker-compose.local.yml deleted file mode 100644 index de7b88e..0000000 --- a/docker-compose.local.yml +++ /dev/null @@ -1,109 +0,0 @@ -version: '3.8' - -services: - backend: - build: - context: ./backend - dockerfile: Dockerfile.dev - ports: - - "3001:3000" - environment: - - NODE_ENV=development - - PORT=3000 - - JWT_SECRET=b375996f704bb1541ad9297e7710a215fce0b59ecf1c43236aeea32ec01e7b27 - - ADMIN_URL=http://localhost:3005 - - FRONTEND_URL=http://localhost:3005 - # Email - uses Mailhog - - SMTP_HOST=mailhog - - SMTP_PORT=1025 - - SMTP_SECURE=false - - SMTP_USER= - - SMTP_PASS= - - EMAIL_FROM=noreply@photo-sharing.local - # Storage path - - STORAGE_PATH=/app/storage - # Umami Analytics (optional) - - UMAMI_URL= - - UMAMI_WEBSITE_ID= - volumes: - - ./backend:/app - - /app/node_modules - - ./storage:/app/storage - - ./data:/app/data - - ./logs:/app/logs - depends_on: - - mailhog - command: sh -c "npm install && npm run dev" - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"] - interval: 30s - timeout: 10s - retries: 3 - - frontend: - build: - context: ./frontend - dockerfile: Dockerfile.dev - args: - - VITE_API_URL=http://localhost:3001 - - VITE_UMAMI_URL= - - VITE_UMAMI_WEBSITE_ID= - ports: - - "3005:80" - environment: - - NODE_ENV=development - volumes: - - ./frontend/dist:/usr/share/nginx/html - - ./frontend/nginx.dev.conf:/etc/nginx/conf.d/default.conf:ro - depends_on: - - backend - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost/health"] - interval: 30s - timeout: 10s - retries: 3 - - # Development frontend with hot reload - frontend-dev: - image: node:18-alpine - working_dir: /app - ports: - - "3002:5173" - environment: - - NODE_ENV=development - - VITE_API_URL=http://localhost:3001 - - VITE_UMAMI_URL= - - VITE_UMAMI_WEBSITE_ID= - volumes: - - ./frontend:/app - - /app/node_modules - command: sh -c "npm install --legacy-peer-deps && npm run dev -- --host" - depends_on: - - backend - - mailhog: - image: mailhog/mailhog:latest - ports: - - "1025:1025" # SMTP - - "8025:8025" # Web UI - - # Optional: File watcher service - file-watcher: - build: - context: ./backend - dockerfile: Dockerfile.dev - environment: - - NODE_ENV=development - - STORAGE_PATH=/app/storage - volumes: - - ./backend:/app - - /app/node_modules - - ./storage:/app/storage - - ./data:/app/data - command: node src/services/fileWatcher.js - depends_on: - - backend - -volumes: - node_modules_backend: - node_modules_frontend: \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7af325f..143a635 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -14,12 +14,21 @@ services: - JWT_SECRET=${JWT_SECRET} - ADMIN_URL=${ADMIN_URL} - FRONTEND_URL=${FRONTEND_URL} + # Database + - DATABASE_CLIENT=pg + - DB_HOST=db + - DB_PORT=5432 + - DB_USER=${DB_USER:-picpeak} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME:-picpeak} + # Email - SMTP_HOST=${SMTP_HOST} - SMTP_PORT=${SMTP_PORT} - SMTP_SECURE=${SMTP_SECURE} - SMTP_USER=${SMTP_USER} - SMTP_PASS=${SMTP_PASS} - EMAIL_FROM=${EMAIL_FROM} + # Analytics - UMAMI_URL=${UMAMI_URL} - UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID} volumes: @@ -70,7 +79,7 @@ services: image: postgres:14-alpine restart: unless-stopped environment: - - POSTGRES_USER=${DB_USER:-photoapp} + - POSTGRES_USER=${DB_USER:-picpeak} - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=${DB_NAME:-picpeak} volumes: @@ -82,7 +91,7 @@ services: image: ghcr.io/umami-software/umami:postgresql-latest restart: unless-stopped environment: - DATABASE_URL: postgresql://${DB_USER:-photoapp}:${DB_PASSWORD}@db:5432/umami + DATABASE_URL: postgresql://${DB_USER:-picpeak}:${DB_PASSWORD}@db:5432/umami DATABASE_TYPE: postgresql HASH_SALT: ${UMAMI_HASH_SALT} depends_on: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 3e5fb58..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,49 +0,0 @@ -# docker-compose.yml - Development configuration - -services: - backend: - build: - context: ./backend - dockerfile: Dockerfile - ports: - - "3001:3000" - environment: - - NODE_ENV=development - - PORT=3000 - - JWT_SECRET=b55e4b3e9f212e1d1836c2ee2ec5ac47672350acdf1391c894d3433646579ad0 - - ADMIN_URL=http://localhost:3001 - - FRONTEND_URL=http://localhost:3005 - - SMTP_HOST=mailhog - - SMTP_PORT=1025 - - SMTP_SECURE=false - - SMTP_USER= - - SMTP_PASS= - - EMAIL_FROM=noreply@localhost - volumes: - - ./backend:/app - - /app/node_modules - - ./storage:/app/storage - - ./data:/app/data - - ./logs:/app/logs - depends_on: - - mailhog - command: node server.js - - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - ports: - - "3005:80" - environment: - - REACT_APP_API_URL=http://localhost:3001 - volumes: - - ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro - depends_on: - - backend - - mailhog: - image: mailhog/mailhog:latest - ports: - - "1025:1025" - - "8025:8025"