Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e343106af5 | |||
| 2f848eb602 | |||
| 1c7fa781ad |
+28
-20
@@ -1,26 +1,34 @@
|
|||||||
# JWT Secret for authentication
|
# Environment Configuration Template
|
||||||
# IMPORTANT: Generate a secure random secret with: openssl rand -hex 32
|
# Copy this file to .env and adjust values for your environment
|
||||||
# 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
|
|
||||||
|
|
||||||
# URLs
|
# Development: Use docker-compose.dev.yml
|
||||||
ADMIN_URL=https://admin.photos.yourdomain.com
|
# Production: Use docker-compose.prod.yml with .env.production.example
|
||||||
FRONTEND_URL=https://photos.yourdomain.com
|
|
||||||
|
|
||||||
# Database (for PostgreSQL in production)
|
# JWT Secret (CRITICAL for production)
|
||||||
DB_USER=photoapp
|
# Generate with: openssl rand -base64 32
|
||||||
DB_PASSWORD=secure-password-here
|
JWT_SECRET=dev-secret-change-in-production
|
||||||
DB_NAME=photo_sharing
|
|
||||||
|
# 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
|
# Email Configuration
|
||||||
SMTP_HOST=smtp.gmail.com
|
# Development: Uses Mailhog (included in docker-compose.dev.yml)
|
||||||
SMTP_PORT=587
|
# Production: Configure real SMTP server
|
||||||
|
SMTP_HOST=mailhog
|
||||||
|
SMTP_PORT=1025
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=false
|
||||||
SMTP_USER=your-email@gmail.com
|
SMTP_USER=
|
||||||
SMTP_PASS=your-app-password
|
SMTP_PASS=
|
||||||
EMAIL_FROM=noreply@yourdomain.com
|
EMAIL_FROM=noreply@localhost
|
||||||
|
|
||||||
# Umami Analytics
|
# Optional: Umami Analytics
|
||||||
UMAMI_URL=https://analytics.yourdomain.com
|
UMAMI_URL=
|
||||||
UMAMI_WEBSITE_ID=your-website-id
|
UMAMI_WEBSITE_ID=
|
||||||
UMAMI_HASH_SALT=random-salt-here
|
UMAMI_HASH_SALT=
|
||||||
+18
-46
@@ -1,60 +1,32 @@
|
|||||||
|
# Production Environment Configuration Template
|
||||||
|
# Copy this file to .env and fill in your values
|
||||||
|
|
||||||
# Application URLs
|
# Application URLs
|
||||||
FRONTEND_HOST=photos.yourdomain.com
|
ADMIN_URL=https://yourdomain.com
|
||||||
BACKEND_HOST=api.photos.yourdomain.com
|
FRONTEND_URL=https://yourdomain.com
|
||||||
ADMIN_URL=https://admin.photos.yourdomain.com
|
|
||||||
FRONTEND_URL=https://photos.yourdomain.com
|
|
||||||
|
|
||||||
# Database Configuration
|
# Security - CRITICAL: Generate a secure random JWT secret
|
||||||
DB_NAME=photo_sharing
|
# You can generate one with: openssl rand -base64 32
|
||||||
DB_USER=photoapp
|
JWT_SECRET=your-secure-random-jwt-secret-here
|
||||||
DB_PASSWORD=your-secure-password-here
|
|
||||||
|
|
||||||
# JWT Configuration
|
# Database Configuration (PostgreSQL)
|
||||||
JWT_SECRET=your-jwt-secret-here
|
DB_USER=picpeak
|
||||||
|
DB_PASSWORD=your-secure-database-password
|
||||||
|
DB_NAME=picpeak
|
||||||
|
|
||||||
# Email Configuration
|
# Email Configuration
|
||||||
SMTP_HOST=smtp.gmail.com
|
SMTP_HOST=smtp.gmail.com
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=true
|
||||||
SMTP_USER=your-email@gmail.com
|
SMTP_USER=your-email@gmail.com
|
||||||
SMTP_PASS=your-app-password
|
SMTP_PASS=your-app-password
|
||||||
EMAIL_FROM=noreply@yourdomain.com
|
EMAIL_FROM=noreply@yourdomain.com
|
||||||
|
|
||||||
# Umami Analytics
|
# Umami Analytics (Optional)
|
||||||
UMAMI_URL=https://analytics.yourdomain.com
|
UMAMI_URL=https://analytics.yourdomain.com
|
||||||
UMAMI_HOST=analytics.yourdomain.com
|
|
||||||
UMAMI_WEBSITE_ID=your-website-id
|
UMAMI_WEBSITE_ID=your-website-id
|
||||||
UMAMI_HASH_SALT=your-random-salt
|
UMAMI_HASH_SALT=your-random-hash-salt
|
||||||
UMAMI_DB_PASSWORD=umami-db-password
|
|
||||||
|
|
||||||
# Traefik Configuration
|
# First Admin User (for initial setup)
|
||||||
TRAEFIK_HOST=traefik.yourdomain.com
|
# Run: docker-compose exec backend node scripts/create-admin.js --email admin@yourdomain.com
|
||||||
ACME_EMAIL=admin@yourdomain.com
|
ADMIN_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
|
|
||||||
+287
-424
@@ -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
|
## Table of Contents
|
||||||
- [Prerequisites](#prerequisites)
|
- [Quick Start (Development)](#quick-start-development)
|
||||||
- [Infrastructure Setup](#infrastructure-setup)
|
- [Production Deployment](#production-deployment)
|
||||||
- [Docker Swarm Setup](#docker-swarm-setup)
|
- [Admin User Setup](#admin-user-setup)
|
||||||
- [Traefik Setup](#traefik-setup)
|
- [Configuration Reference](#configuration-reference)
|
||||||
- [Application Deployment](#application-deployment)
|
|
||||||
- [CI/CD with Drone](#cicd-with-drone)
|
|
||||||
- [Monitoring](#monitoring)
|
|
||||||
- [Backup and Recovery](#backup-and-recovery)
|
|
||||||
- [Troubleshooting](#troubleshooting)
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
## Prerequisites
|
## Quick Start (Development)
|
||||||
|
|
||||||
### Hardware Requirements
|
### 1. Clone and Setup
|
||||||
- **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
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install Docker
|
git clone https://github.com/yourusername/picpeak.git
|
||||||
curl -fsSL https://get.docker.com | sh
|
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
|
# Copy environment template
|
||||||
cp .env.production.example .env.production
|
cp .env.example .env
|
||||||
|
|
||||||
# Edit with your values
|
# Start development environment
|
||||||
nano .env.production
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
```
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Access Services
|
### 2. Access Services
|
||||||
|
|
||||||
- Grafana: `https://grafana.yourdomain.com`
|
- Frontend: http://localhost:3005
|
||||||
- Prometheus: `https://prometheus.yourdomain.com`
|
- Backend API: http://localhost:3001
|
||||||
- Alertmanager: `https://alerts.yourdomain.com`
|
- 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
|
```yaml
|
||||||
groups:
|
version: '3.8'
|
||||||
- name: photo-sharing
|
|
||||||
rules:
|
services:
|
||||||
- alert: ServiceDown
|
frontend:
|
||||||
expr: up{job="photo-sharing-backend"} == 0
|
labels:
|
||||||
for: 5m
|
- "traefik.enable=true"
|
||||||
annotations:
|
- "traefik.http.routers.picpeak.rule=Host(`yourdomain.com`)"
|
||||||
summary: "Photo sharing backend is down"
|
- "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
|
```bash
|
||||||
# Edit crontab
|
# Production
|
||||||
crontab -e
|
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
|
# Auto-generate password
|
||||||
0 2 * * * /opt/photo-sharing/deploy/scripts/backup.sh
|
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
|
```bash
|
||||||
cd deploy/scripts
|
# List admin users
|
||||||
./backup.sh
|
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
|
```bash
|
||||||
# Extract backup
|
# PostgreSQL backup
|
||||||
tar -xzf backup-20240615-020000.tar.gz
|
docker-compose -f docker-compose.prod.yml exec db \
|
||||||
|
pg_dump -U picpeak picpeak > backup-$(date +%Y%m%d).sql
|
||||||
|
|
||||||
# Restore database
|
# Backup storage
|
||||||
docker exec -i $(docker ps -q -f name=photo-sharing_db) \
|
tar -czf storage-backup-$(date +%Y%m%d).tar.gz ./storage
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Maintenance
|
### Restore Database
|
||||||
|
|
||||||
### 1. Scaling Services
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Scale backend to 5 replicas
|
# PostgreSQL restore
|
||||||
docker service scale photo-sharing_backend=5
|
docker-compose -f docker-compose.prod.yml exec -T db \
|
||||||
|
psql -U picpeak picpeak < backup-20240115.sql
|
||||||
|
|
||||||
# Scale frontend to 3 replicas
|
# Restore storage
|
||||||
docker service scale photo-sharing_frontend=3
|
tar -xzf storage-backup-20240115.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Rolling Updates
|
## Monitoring
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
### Health Checks
|
### Health Checks
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check all endpoints
|
# Backend health
|
||||||
curl -f https://photos.yourdomain.com/health
|
curl https://yourdomain.com/api/health
|
||||||
curl -f https://api.photos.yourdomain.com/api/health
|
|
||||||
curl -f https://traefik.yourdomain.com/ping
|
# Frontend health
|
||||||
|
curl https://yourdomain.com/health
|
||||||
```
|
```
|
||||||
|
|
||||||
## Security Best Practices
|
### Logs
|
||||||
|
|
||||||
1. **Regular Updates**
|
```bash
|
||||||
- Keep Docker and system packages updated
|
# All services
|
||||||
- Update application dependencies regularly
|
docker-compose -f docker-compose.prod.yml logs -f
|
||||||
- Monitor security advisories
|
|
||||||
|
|
||||||
2. **Access Control**
|
# Specific service
|
||||||
- Use strong passwords for all services
|
docker-compose -f docker-compose.prod.yml logs -f backend
|
||||||
- Enable 2FA where possible
|
|
||||||
- Restrict SSH access to specific IPs
|
|
||||||
- Use Docker secrets for sensitive data
|
|
||||||
|
|
||||||
3. **Network Security**
|
# Last 100 lines
|
||||||
- Use internal networks for service communication
|
docker-compose -f docker-compose.prod.yml logs --tail=100 backend
|
||||||
- Enable firewall rules
|
```
|
||||||
- Use TLS for all external communication
|
|
||||||
- Regular security scans with Trivy
|
|
||||||
|
|
||||||
4. **Backup Security**
|
## Troubleshooting
|
||||||
- Encrypt backups at rest
|
|
||||||
- Test restore procedures regularly
|
|
||||||
- Store backups in multiple locations
|
|
||||||
- Rotate old backups
|
|
||||||
|
|
||||||
## Performance Tuning
|
### Backend Won't Start
|
||||||
|
|
||||||
1. **Database Optimization**
|
1. Check database connection:
|
||||||
```sql
|
```bash
|
||||||
-- Add indexes for common queries
|
docker-compose -f docker-compose.prod.yml logs db
|
||||||
CREATE INDEX idx_photos_event_id ON photos(event_id);
|
|
||||||
CREATE INDEX idx_access_logs_event_id ON access_logs(event_id);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Image Optimization**
|
2. Verify environment variables:
|
||||||
- Use CDN for static assets
|
```bash
|
||||||
- Enable aggressive caching
|
docker-compose -f docker-compose.prod.yml exec backend env | grep DB_
|
||||||
- Optimize image sizes before upload
|
|
||||||
|
|
||||||
3. **Service Limits**
|
|
||||||
```yaml
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: '2'
|
|
||||||
memory: 1G
|
|
||||||
reservations:
|
|
||||||
cpus: '0.5'
|
|
||||||
memory: 256M
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Support
|
### Can't Login as Admin
|
||||||
|
|
||||||
For issues and questions:
|
1. Verify admin user exists:
|
||||||
- Check logs: `docker service logs <service_name>`
|
```bash
|
||||||
- Review documentation: [README.md](README.md)
|
docker-compose -f docker-compose.prod.yml exec backend \
|
||||||
- Check monitoring dashboards
|
psql postgresql://picpeak:$DB_PASSWORD@db:5432/picpeak \
|
||||||
- Contact: admin@yourdomain.com
|
-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
|
||||||
+10
-1
@@ -24,10 +24,19 @@ ARCHIVE_PATH=./storage/events/archived
|
|||||||
# Logging
|
# Logging
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
# Database (for production, consider PostgreSQL)
|
# Database Configuration
|
||||||
|
# Development: Use SQLite
|
||||||
DATABASE_CLIENT=sqlite3
|
DATABASE_CLIENT=sqlite3
|
||||||
DATABASE_PATH=./data/photo_sharing.db
|
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 Analytics (optional)
|
||||||
UMAMI_URL=
|
UMAMI_URL=
|
||||||
UMAMI_WEBSITE_ID=
|
UMAMI_WEBSITE_ID=
|
||||||
|
|||||||
@@ -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'];
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"description": "Backend for PicPeak event photo sharing platform",
|
"description": "Backend for PicPeak event photo sharing platform",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Executable
+77
@@ -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();
|
||||||
+37
-38
@@ -1,13 +1,7 @@
|
|||||||
const knex = require('knex');
|
const knex = require('knex');
|
||||||
const path = require('path');
|
const knexConfig = require('../../knexfile');
|
||||||
|
|
||||||
const db = knex({
|
const db = knex(knexConfig);
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: path.join(__dirname, '../../data/photo_sharing.db')
|
|
||||||
},
|
|
||||||
useNullAsDefault: true
|
|
||||||
});
|
|
||||||
|
|
||||||
async function initializeDatabase() {
|
async function initializeDatabase() {
|
||||||
// Events table
|
// Events table
|
||||||
@@ -35,37 +29,42 @@ async function initializeDatabase() {
|
|||||||
} else {
|
} else {
|
||||||
// Check if color_theme needs to be updated to TEXT type
|
// Check if color_theme needs to be updated to TEXT type
|
||||||
// This is needed for larger theme configurations
|
// This is needed for larger theme configurations
|
||||||
try {
|
const isPostgres = knexConfig.client === 'pg';
|
||||||
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`);
|
if (!isPostgres) {
|
||||||
await db.raw(`DROP TABLE events`);
|
// SQLite-specific migration
|
||||||
await db.raw(`ALTER TABLE events_new RENAME TO events`);
|
try {
|
||||||
} catch (error) {
|
await db.raw(`
|
||||||
// If the migration fails, it might already have been applied
|
CREATE TABLE IF NOT EXISTS events_new (
|
||||||
console.log('Color theme migration may have already been applied');
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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:
|
|
||||||
+11
-2
@@ -14,12 +14,21 @@ services:
|
|||||||
- JWT_SECRET=${JWT_SECRET}
|
- JWT_SECRET=${JWT_SECRET}
|
||||||
- ADMIN_URL=${ADMIN_URL}
|
- ADMIN_URL=${ADMIN_URL}
|
||||||
- FRONTEND_URL=${FRONTEND_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_HOST=${SMTP_HOST}
|
||||||
- SMTP_PORT=${SMTP_PORT}
|
- SMTP_PORT=${SMTP_PORT}
|
||||||
- SMTP_SECURE=${SMTP_SECURE}
|
- SMTP_SECURE=${SMTP_SECURE}
|
||||||
- SMTP_USER=${SMTP_USER}
|
- SMTP_USER=${SMTP_USER}
|
||||||
- SMTP_PASS=${SMTP_PASS}
|
- SMTP_PASS=${SMTP_PASS}
|
||||||
- EMAIL_FROM=${EMAIL_FROM}
|
- EMAIL_FROM=${EMAIL_FROM}
|
||||||
|
# Analytics
|
||||||
- UMAMI_URL=${UMAMI_URL}
|
- UMAMI_URL=${UMAMI_URL}
|
||||||
- UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID}
|
- UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID}
|
||||||
volumes:
|
volumes:
|
||||||
@@ -70,7 +79,7 @@ services:
|
|||||||
image: postgres:14-alpine
|
image: postgres:14-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=${DB_USER:-photoapp}
|
- POSTGRES_USER=${DB_USER:-picpeak}
|
||||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||||
- POSTGRES_DB=${DB_NAME:-picpeak}
|
- POSTGRES_DB=${DB_NAME:-picpeak}
|
||||||
volumes:
|
volumes:
|
||||||
@@ -82,7 +91,7 @@ services:
|
|||||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
image: ghcr.io/umami-software/umami:postgresql-latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
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
|
DATABASE_TYPE: postgresql
|
||||||
HASH_SALT: ${UMAMI_HASH_SALT}
|
HASH_SALT: ${UMAMI_HASH_SALT}
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -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"
|
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"@tiptap/extension-link": "^2.25.0",
|
"@tiptap/extension-link": "^2.25.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.3",
|
"version": "1.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user