refactor: consolidate deployment documentation and cleanup repository
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m23s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m4s
Version and Release / version-bump (push) Successful in 33s
Version and Release / trigger-drone (push) Has been skipped
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m23s
continuous-integration/drone/push Build is passing
Test and Lint / frontend-test (push) Successful in 2m4s
Version and Release / version-bump (push) Successful in 33s
Version and Release / trigger-drone (push) Has been skipped
- Merge all deployment docs into single comprehensive DEPLOYMENT_GUIDE.md - Add instructions for non-nginx deployment options - Reference utility scripts in deployment guide - Remove orphaned migrations folder at root level - Remove redundant deployment documentation files - Keep all utility scripts in scripts/ folder - Update CLAUDE.md to reference new deployment guide This provides a single source of truth for all deployment scenarios.
This commit is contained in:
@@ -1,12 +0,0 @@
|
|||||||
# Files to exclude from GitHub mirror
|
|
||||||
.env* export-ignore
|
|
||||||
docker-compose.prod.yml export-ignore
|
|
||||||
.claudedocs/ export-ignore
|
|
||||||
backend/data/ export-ignore
|
|
||||||
backend/storage/ export-ignore
|
|
||||||
backend/.env* export-ignore
|
|
||||||
frontend/.env* export-ignore
|
|
||||||
secrets/ export-ignore
|
|
||||||
*.key export-ignore
|
|
||||||
*.pem export-ignore
|
|
||||||
.gitea/ export-ignore
|
|
||||||
@@ -28,30 +28,12 @@ jobs:
|
|||||||
|
|
||||||
# Remove sensitive files/directories if they exist
|
# Remove sensitive files/directories if they exist
|
||||||
echo "Removing sensitive files..."
|
echo "Removing sensitive files..."
|
||||||
rm -rf .env || true
|
|
||||||
rm -rf backend/.env* || true
|
|
||||||
rm -rf frontend/.env* || true
|
|
||||||
rm -rf docker-compose.prod.yml || true
|
|
||||||
rm -rf .claudedocs/ || true
|
|
||||||
rm -rf backend/data/ || true
|
|
||||||
rm -rf backend/storage/ || true
|
|
||||||
rm -rf .gitea/ || true
|
rm -rf .gitea/ || true
|
||||||
rm -rf scripts/install-gitea-runner.sh || true
|
rm -rf scripts/ || true
|
||||||
rm -rf .drone* || true
|
rm -rf .drone* || true
|
||||||
rm -rf .github-mirror-exclude || true
|
|
||||||
rm -rf .gitattributes-github || true
|
|
||||||
rm -rf photo-sharing-prd.md || true
|
rm -rf photo-sharing-prd.md || true
|
||||||
rm -rf CLAUDE.md || true
|
rm -rf CLAUDE.md || true
|
||||||
rm -rf PRODUCTION_DEPLOYMENT_GUIDE.md || true
|
|
||||||
rm -rf logs/ || true
|
|
||||||
rm -rf frontend/.claudedocs/ || true
|
|
||||||
rm -rf test-maintenance.sh || true
|
|
||||||
rm -rf storage/ || true
|
|
||||||
rm -rf clean-git-history.sh || true
|
|
||||||
rm -rf frontend/.swarm/ || true
|
|
||||||
rm -rf backend/.hive-mind/ || true
|
|
||||||
rm -rf data/ || true
|
|
||||||
rm -rf certbot/ || true
|
|
||||||
|
|
||||||
|
|
||||||
echo "Sensitive files removal completed"
|
echo "Sensitive files removal completed"
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# Exclude patterns for GitHub mirror
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
.env*
|
|
||||||
docker-compose.prod.yml
|
|
||||||
docker-compose.traefik.yml
|
|
||||||
.claudedocs/
|
|
||||||
backend/data/
|
|
||||||
backend/storage/
|
|
||||||
backend/.env*
|
|
||||||
frontend/.env*
|
|
||||||
secrets/
|
|
||||||
*.key
|
|
||||||
*.pem
|
|
||||||
.gitea/
|
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
*.log
|
|
||||||
.DS_Store
|
|
||||||
deploy/
|
|
||||||
certbot/
|
|
||||||
nginx/
|
|
||||||
photo-sharing-prd.md
|
|
||||||
+8
-1
@@ -59,4 +59,11 @@ test-archiver/
|
|||||||
!data/.gitkeep
|
!data/.gitkeep
|
||||||
!logs/.gitkeep
|
!logs/.gitkeep
|
||||||
|
|
||||||
PRODUCTION_DEPLOYMENT_GUIDE.md
|
# development files
|
||||||
|
backend/.swarm/
|
||||||
|
.claudedocs/
|
||||||
|
backend/data/
|
||||||
|
logs/
|
||||||
|
storage/
|
||||||
|
data/
|
||||||
|
certbot/
|
||||||
|
|||||||
@@ -33,11 +33,14 @@ npm test -- path/to/test.test.js
|
|||||||
npm test -- --testNamePattern="test name"
|
npm test -- --testNamePattern="test name"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Production
|
### Production Deployment
|
||||||
```bash
|
See [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) for comprehensive deployment instructions including:
|
||||||
docker-compose -f docker-compose.prod.yml up -d # Production deployment
|
- Docker Compose deployment
|
||||||
pm2 start ecosystem.config.js # Alternative: PM2 deployment
|
- PM2 deployment
|
||||||
```
|
- Manual installation
|
||||||
|
- Non-nginx deployment options
|
||||||
|
- SSL/HTTPS setup
|
||||||
|
- Troubleshooting guide
|
||||||
|
|
||||||
**⚠️ CRITICAL PRODUCTION NOTICE:**
|
**⚠️ CRITICAL PRODUCTION NOTICE:**
|
||||||
- Production runs on a SEPARATE SERVER - never assume local changes affect production
|
- Production runs on a SEPARATE SERVER - never assume local changes affect production
|
||||||
|
|||||||
-220
@@ -1,220 +0,0 @@
|
|||||||
# 🚀 PicPeak Deployment Guide
|
|
||||||
|
|
||||||
This guide will help you deploy PicPeak in production. The entire process takes about 10-15 minutes.
|
|
||||||
|
|
||||||
## 📋 Prerequisites
|
|
||||||
|
|
||||||
- A server with Docker and Docker Compose installed
|
|
||||||
- A domain name (for SSL certificates)
|
|
||||||
- SMTP credentials for sending emails
|
|
||||||
- Basic command line knowledge
|
|
||||||
|
|
||||||
## 🏃 Quick Deploy (Recommended)
|
|
||||||
|
|
||||||
### 1. Clone and Configure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone the repository
|
|
||||||
git clone https://github.com/the-luap/picpeak.git
|
|
||||||
cd picpeak
|
|
||||||
|
|
||||||
# Copy environment template
|
|
||||||
cp .env.production.example .env
|
|
||||||
|
|
||||||
# Generate a secure JWT secret
|
|
||||||
echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env
|
|
||||||
|
|
||||||
# Edit configuration
|
|
||||||
nano .env
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Required Environment Variables
|
|
||||||
|
|
||||||
Edit your `.env` file with these essential settings:
|
|
||||||
|
|
||||||
```env
|
|
||||||
# Application URLs
|
|
||||||
FRONTEND_URL=https://your-domain.com
|
|
||||||
BACKEND_URL=https://your-domain.com
|
|
||||||
|
|
||||||
# Email Configuration (Required for notifications)
|
|
||||||
SMTP_HOST=smtp.gmail.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_USER=your-email@gmail.com
|
|
||||||
SMTP_PASS=your-app-password
|
|
||||||
SMTP_FROM=your-email@gmail.com
|
|
||||||
|
|
||||||
# Admin Configuration
|
|
||||||
ADMIN_EMAIL=admin@your-domain.com
|
|
||||||
ADMIN_PASSWORD=your-secure-password
|
|
||||||
|
|
||||||
# Database (PostgreSQL for production)
|
|
||||||
DATABASE_CLIENT=pg
|
|
||||||
DB_HOST=postgres
|
|
||||||
DB_NAME=picpeak
|
|
||||||
DB_USER=picpeak
|
|
||||||
DB_PASSWORD=secure-db-password
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Deploy with Docker Compose
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start all services
|
|
||||||
docker-compose -f docker-compose.prod.yml up -d
|
|
||||||
|
|
||||||
# Check logs
|
|
||||||
docker-compose logs -f
|
|
||||||
|
|
||||||
# Access your site at https://your-domain.com
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Configuration Options
|
|
||||||
|
|
||||||
### Storage Settings
|
|
||||||
|
|
||||||
```env
|
|
||||||
# Storage paths (default: ./storage)
|
|
||||||
STORAGE_PATH=./storage
|
|
||||||
ARCHIVE_PATH=./storage/archives
|
|
||||||
|
|
||||||
# Gallery expiration (days)
|
|
||||||
DEFAULT_EXPIRATION_DAYS=30
|
|
||||||
WARNING_DAYS_BEFORE_EXPIRY=7
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Settings
|
|
||||||
|
|
||||||
```env
|
|
||||||
# Session timeout (minutes)
|
|
||||||
SESSION_TIMEOUT=60
|
|
||||||
|
|
||||||
# Rate limiting
|
|
||||||
RATE_LIMIT_WINDOW_MS=900000 # 15 minutes
|
|
||||||
RATE_LIMIT_MAX_REQUESTS=100
|
|
||||||
```
|
|
||||||
|
|
||||||
### Analytics (Optional)
|
|
||||||
|
|
||||||
```env
|
|
||||||
# Umami Analytics
|
|
||||||
VITE_UMAMI_URL=https://analytics.your-domain.com
|
|
||||||
VITE_UMAMI_WEBSITE_ID=your-website-id
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔒 SSL/TLS Setup
|
|
||||||
|
|
||||||
The production Docker Compose includes automatic SSL via Let's Encrypt:
|
|
||||||
|
|
||||||
1. **Ensure your domain points to your server**
|
|
||||||
2. **Update nginx configuration**:
|
|
||||||
```bash
|
|
||||||
nano nginx/nginx.conf
|
|
||||||
# Replace your-domain.com with your actual domain
|
|
||||||
```
|
|
||||||
3. **Start services** - Certbot will automatically obtain certificates
|
|
||||||
|
|
||||||
## 📁 Directory Structure
|
|
||||||
|
|
||||||
After deployment, your directory structure will be:
|
|
||||||
|
|
||||||
```
|
|
||||||
picpeak/
|
|
||||||
├── backend/ # API server
|
|
||||||
├── frontend/ # React app
|
|
||||||
├── storage/ # Photo storage
|
|
||||||
│ ├── events/ # Active galleries
|
|
||||||
│ │ ├── active/ # Current photos
|
|
||||||
│ │ └── archived/ # Expired galleries
|
|
||||||
│ ├── thumbnails/ # Generated thumbnails
|
|
||||||
│ └── uploads/ # User uploads
|
|
||||||
├── data/ # Database files
|
|
||||||
└── logs/ # Application logs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔄 Maintenance
|
|
||||||
|
|
||||||
### Backup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup database and photos
|
|
||||||
./scripts/backup.sh
|
|
||||||
|
|
||||||
# Backups are stored in ./backups/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pull latest changes
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# Rebuild and restart
|
|
||||||
docker-compose -f docker-compose.prod.yml up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View all logs
|
|
||||||
docker-compose logs
|
|
||||||
|
|
||||||
# View specific service
|
|
||||||
docker-compose logs backend
|
|
||||||
docker-compose logs frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚨 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
**Photos not appearing:**
|
|
||||||
- Check storage permissions: `chmod -R 755 storage/`
|
|
||||||
- Verify file watcher is running: `docker-compose logs backend | grep watcher`
|
|
||||||
|
|
||||||
**Email not sending:**
|
|
||||||
- Test SMTP settings: Admin Panel → Settings → Email → Send Test
|
|
||||||
- Check email queue: Admin Panel → System → Email Queue
|
|
||||||
|
|
||||||
**Can't access admin panel:**
|
|
||||||
- Default login: Use email/password from `.env`
|
|
||||||
- Reset password: `docker exec picpeak-backend npm run reset-admin`
|
|
||||||
|
|
||||||
### Health Check
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check service status
|
|
||||||
docker-compose ps
|
|
||||||
|
|
||||||
# Test backend API
|
|
||||||
curl https://your-domain.com/api/health
|
|
||||||
|
|
||||||
# Check disk space
|
|
||||||
df -h storage/
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🐳 Alternative Deployment Methods
|
|
||||||
|
|
||||||
### Using Docker Swarm
|
|
||||||
|
|
||||||
For high availability deployments, see [Docker Swarm Setup](deploy/README.md).
|
|
||||||
|
|
||||||
### Manual Installation
|
|
||||||
|
|
||||||
If you prefer not to use Docker:
|
|
||||||
|
|
||||||
1. Install Node.js 18+
|
|
||||||
2. Install PostgreSQL
|
|
||||||
3. Clone repository
|
|
||||||
4. Install dependencies: `npm install` in both `/backend` and `/frontend`
|
|
||||||
5. Build frontend: `cd frontend && npm run build`
|
|
||||||
6. Start services with PM2
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
- 📘 [Documentation](https://github.com/the-luap/picpeak)
|
|
||||||
- 🐛 [Report Issues](https://github.com/the-luap/picpeak/issues)
|
|
||||||
- 💬 [Discussions](https://github.com/the-luap/picpeak/discussions)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Need help?** Open an issue on GitHub and we'll assist you!
|
|
||||||
@@ -0,0 +1,761 @@
|
|||||||
|
# 🚀 PicPeak Complete Deployment Guide
|
||||||
|
|
||||||
|
This comprehensive guide covers all deployment methods for PicPeak, including Docker, PM2, manual installation, and deployment without a reverse proxy.
|
||||||
|
|
||||||
|
## 📋 Table of Contents
|
||||||
|
|
||||||
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Security Requirements](#security-requirements)
|
||||||
|
- [Quick Start (Docker)](#quick-start-docker)
|
||||||
|
- [Deployment Methods](#deployment-methods)
|
||||||
|
- [Method 1: Docker Compose (Recommended)](#method-1-docker-compose-recommended)
|
||||||
|
- [Method 2: PM2 (Node.js Process Manager)](#method-2-pm2-nodejs-process-manager)
|
||||||
|
- [Method 3: Manual Installation](#method-3-manual-installation)
|
||||||
|
- [Method 4: Without Nginx (Direct Access)](#method-4-without-nginx-direct-access)
|
||||||
|
- [Environment Configuration](#environment-configuration)
|
||||||
|
- [Admin Setup](#admin-setup)
|
||||||
|
- [SSL/HTTPS Configuration](#sslhttps-configuration)
|
||||||
|
- [Maintenance & Operations](#maintenance--operations)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Security Checklist](#security-checklist)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Basic Requirements
|
||||||
|
- Linux server (Ubuntu 20.04+ or similar)
|
||||||
|
- Domain name (for SSL certificates)
|
||||||
|
- SMTP credentials for email notifications
|
||||||
|
- Basic command line knowledge
|
||||||
|
|
||||||
|
### Software Requirements (varies by method)
|
||||||
|
- **Docker method**: Docker and Docker Compose
|
||||||
|
- **PM2 method**: Node.js 18+, PostgreSQL 14+
|
||||||
|
- **Manual method**: Node.js 18+, PostgreSQL 14+, nginx (optional)
|
||||||
|
|
||||||
|
## 🔐 Security Requirements
|
||||||
|
|
||||||
|
### Critical: JWT Secret Setup
|
||||||
|
|
||||||
|
**NEVER use the default JWT secret in production!** The application will refuse to start if JWT_SECRET is not properly configured.
|
||||||
|
|
||||||
|
Generate a secure JWT secret:
|
||||||
|
```bash
|
||||||
|
# Generate a 64-character secret
|
||||||
|
openssl rand -base64 32
|
||||||
|
|
||||||
|
# Or for even more security (recommended)
|
||||||
|
openssl rand -base64 64
|
||||||
|
|
||||||
|
# Or use the included script
|
||||||
|
./scripts/generate-jwt-secret.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical: Database Password
|
||||||
|
|
||||||
|
Generate a strong database password:
|
||||||
|
```bash
|
||||||
|
openssl rand -base64 24
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Quick Start (Docker)
|
||||||
|
|
||||||
|
The fastest way to deploy PicPeak in production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone the repository
|
||||||
|
git clone https://github.com/the-luap/picpeak.git
|
||||||
|
cd picpeak
|
||||||
|
|
||||||
|
# 2. Use the automated install script (recommended)
|
||||||
|
sudo ./scripts/install.sh
|
||||||
|
|
||||||
|
# Or manually:
|
||||||
|
# 2. Copy production environment template
|
||||||
|
cp .env.production.example .env
|
||||||
|
|
||||||
|
# 3. Generate and add JWT secret
|
||||||
|
echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env
|
||||||
|
|
||||||
|
# 4. Edit configuration
|
||||||
|
nano .env # Update all required values
|
||||||
|
|
||||||
|
# 5. Create directories
|
||||||
|
mkdir -p storage/events/active storage/events/archived storage/thumbnails storage/uploads
|
||||||
|
mkdir -p data logs certbot/conf certbot/www
|
||||||
|
|
||||||
|
# 6. Deploy
|
||||||
|
docker-compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# 7. Check logs
|
||||||
|
docker-compose -f docker-compose.prod.yml logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Deployment Methods
|
||||||
|
|
||||||
|
### Method 1: Docker Compose (Recommended)
|
||||||
|
|
||||||
|
#### Step 1: Environment Configuration
|
||||||
|
|
||||||
|
Create `.env` file with all required variables:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# SECURITY - MUST CHANGE ALL!
|
||||||
|
JWT_SECRET=<your-64-character-secret-from-openssl>
|
||||||
|
DB_PASSWORD=<your-secure-database-password>
|
||||||
|
|
||||||
|
# Application URLs
|
||||||
|
FRONTEND_URL=https://your-domain.com
|
||||||
|
BACKEND_URL=https://your-domain.com
|
||||||
|
ADMIN_URL=https://your-domain.com
|
||||||
|
|
||||||
|
# Database (PostgreSQL for Docker)
|
||||||
|
DATABASE_CLIENT=pg
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=picpeak
|
||||||
|
DB_USER=picpeak
|
||||||
|
|
||||||
|
# Email (Example: SendGrid)
|
||||||
|
SMTP_HOST=smtp.sendgrid.net
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_SECURE=false
|
||||||
|
SMTP_USER=apikey
|
||||||
|
SMTP_PASS=your-sendgrid-api-key
|
||||||
|
EMAIL_FROM=PicPeak <noreply@your-domain.com>
|
||||||
|
|
||||||
|
# Application
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3001
|
||||||
|
LOG_LEVEL=info
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Docker Volume Permissions
|
||||||
|
|
||||||
|
Create `docker-compose.override.yml` for proper permissions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
volumes:
|
||||||
|
- ./storage:/app/storage:delegated
|
||||||
|
- ./data:/app/data:delegated
|
||||||
|
- ./logs:/app/logs:delegated
|
||||||
|
user: "1001:1001" # nodejs user
|
||||||
|
|
||||||
|
db:
|
||||||
|
volumes:
|
||||||
|
- ./postgres-data:/var/lib/postgresql/data
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Build and Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set correct permissions
|
||||||
|
chmod -R 755 storage data logs
|
||||||
|
|
||||||
|
# Build images
|
||||||
|
docker-compose -f docker-compose.prod.yml build
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker-compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# Run database migrations
|
||||||
|
docker-compose -f docker-compose.prod.yml exec backend npm run migrate
|
||||||
|
|
||||||
|
# Admin credentials will be displayed and saved to ADMIN_CREDENTIALS.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 4: Configure Nginx
|
||||||
|
|
||||||
|
Update `nginx/sites-enabled/default` with your domain:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# Redirect to HTTPS
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# SSL configuration (managed by Certbot)
|
||||||
|
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
location / {
|
||||||
|
proxy_pass http://frontend:80;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API proxy
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://backend:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Protected images
|
||||||
|
location /photos {
|
||||||
|
proxy_pass http://backend:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
location /thumbnails {
|
||||||
|
proxy_pass http://backend:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public uploads
|
||||||
|
location /uploads {
|
||||||
|
proxy_pass http://backend:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: PM2 (Node.js Process Manager)
|
||||||
|
|
||||||
|
#### Step 1: Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Node.js 18+
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
|
||||||
|
# Install PostgreSQL
|
||||||
|
sudo apt-get install -y postgresql postgresql-contrib
|
||||||
|
|
||||||
|
# Install PM2 globally
|
||||||
|
sudo npm install -g pm2
|
||||||
|
|
||||||
|
# Install nginx (if using reverse proxy)
|
||||||
|
sudo apt-get install -y nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Setup Database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create database and user
|
||||||
|
sudo -u postgres psql
|
||||||
|
CREATE DATABASE picpeak;
|
||||||
|
CREATE USER picpeak WITH ENCRYPTED PASSWORD 'your-secure-password';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE picpeak TO picpeak;
|
||||||
|
\q
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Clone and Configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone https://github.com/the-luap/picpeak.git
|
||||||
|
cd picpeak
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
cd backend && npm install
|
||||||
|
cd ../frontend && npm install
|
||||||
|
|
||||||
|
# Configure environment
|
||||||
|
cd ..
|
||||||
|
cp .env.production.example .env
|
||||||
|
nano .env # Update all values
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 4: Build Frontend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm run build
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 5: Start with PM2
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
|
||||||
|
# Start application
|
||||||
|
pm2 start ecosystem.config.js
|
||||||
|
|
||||||
|
# Save PM2 configuration
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
# Setup startup script
|
||||||
|
pm2 startup
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 6: Configure Nginx
|
||||||
|
|
||||||
|
Create `/etc/nginx/sites-available/picpeak`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# Frontend (static files)
|
||||||
|
location / {
|
||||||
|
root /path/to/picpeak/frontend/dist;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API proxy
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://localhost:3001;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Protected photos
|
||||||
|
location /photos {
|
||||||
|
proxy_pass http://localhost:3001;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Other proxied paths
|
||||||
|
location ~ ^/(thumbnails|uploads) {
|
||||||
|
proxy_pass http://localhost:3001;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable the site:
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/picpeak /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 3: Manual Installation
|
||||||
|
|
||||||
|
Similar to PM2 method but using systemd instead:
|
||||||
|
|
||||||
|
#### Create Systemd Service
|
||||||
|
|
||||||
|
Create `/etc/systemd/system/picpeak.service`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=PicPeak Photo Sharing
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=picpeak
|
||||||
|
WorkingDirectory=/home/picpeak/picpeak/backend
|
||||||
|
ExecStart=/usr/bin/node server.js
|
||||||
|
Restart=on-failure
|
||||||
|
Environment="NODE_ENV=production"
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the service:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable picpeak
|
||||||
|
sudo systemctl start picpeak
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 4: Without Nginx (Direct Access)
|
||||||
|
|
||||||
|
For deployments without a reverse proxy:
|
||||||
|
|
||||||
|
#### Option A: Direct Backend Access
|
||||||
|
|
||||||
|
1. **Configure environment for direct access**:
|
||||||
|
```env
|
||||||
|
# .env
|
||||||
|
FRONTEND_URL=http://your-domain.com:5173
|
||||||
|
BACKEND_URL=http://your-domain.com:3001
|
||||||
|
ADMIN_URL=http://your-domain.com:5173
|
||||||
|
|
||||||
|
# Enable CORS for direct access
|
||||||
|
CORS_ENABLED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run backend directly**:
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
NODE_ENV=production node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run frontend development server** (not recommended for production):
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
VITE_API_URL=http://your-domain.com:3001/api npm run dev -- --host
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Backend Serves Frontend
|
||||||
|
|
||||||
|
1. **Build frontend**:
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
VITE_API_URL=/api npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure backend to serve frontend**:
|
||||||
|
```javascript
|
||||||
|
// Add to backend/server.js after API routes
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Access everything on backend port**:
|
||||||
|
```bash
|
||||||
|
# Application available at http://your-domain.com:3001
|
||||||
|
NODE_ENV=production node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option C: Using Node.js HTTP Proxy
|
||||||
|
|
||||||
|
Create a simple proxy server:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// proxy-server.js
|
||||||
|
const express = require('express');
|
||||||
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Serve frontend static files
|
||||||
|
app.use(express.static(path.join(__dirname, 'frontend/dist')));
|
||||||
|
|
||||||
|
// Proxy API requests
|
||||||
|
app.use('/api', createProxyMiddleware({
|
||||||
|
target: 'http://localhost:3001',
|
||||||
|
changeOrigin: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Proxy other backend routes
|
||||||
|
app.use(['/photos', '/thumbnails', '/uploads'], createProxyMiddleware({
|
||||||
|
target: 'http://localhost:3001',
|
||||||
|
changeOrigin: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Catch all - serve frontend
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, 'frontend/dist/index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(80);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Environment Configuration
|
||||||
|
|
||||||
|
### Required Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `JWT_SECRET` | **CRITICAL** - Authentication secret (min 32 chars) | Use `openssl rand -base64 32` |
|
||||||
|
| `DATABASE_CLIENT` | Database type | `pg` for PostgreSQL, `sqlite3` for SQLite |
|
||||||
|
| `DB_HOST` | Database host | `localhost` or `db` (Docker) |
|
||||||
|
| `DB_PORT` | Database port | `5432` |
|
||||||
|
| `DB_NAME` | Database name | `picpeak` |
|
||||||
|
| `DB_USER` | Database user | `picpeak` |
|
||||||
|
| `DB_PASSWORD` | Database password | Strong password |
|
||||||
|
| `SMTP_HOST` | Email server | `smtp.gmail.com` |
|
||||||
|
| `SMTP_PORT` | Email port | `587` |
|
||||||
|
| `SMTP_USER` | Email username | `your-email@gmail.com` |
|
||||||
|
| `SMTP_PASS` | Email password | App-specific password |
|
||||||
|
| `EMAIL_FROM` | From address | `PicPeak <noreply@domain.com>` |
|
||||||
|
| `FRONTEND_URL` | Frontend URL | `https://your-domain.com` |
|
||||||
|
| `BACKEND_URL` | Backend URL | `https://your-domain.com` |
|
||||||
|
|
||||||
|
### Optional Configuration
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `NODE_ENV` | Environment | `production` |
|
||||||
|
| `PORT` | Backend port | `3001` |
|
||||||
|
| `LOG_LEVEL` | Logging level | `info` |
|
||||||
|
| `SESSION_TIMEOUT_MINUTES` | Session timeout | `60` |
|
||||||
|
| `RATE_LIMIT_WINDOW_MS` | Rate limit window | `900000` (15 min) |
|
||||||
|
| `RATE_LIMIT_MAX_REQUESTS` | Max requests | `100` |
|
||||||
|
| `DB_POOL_MIN` | Min DB connections | `5` |
|
||||||
|
| `DB_POOL_MAX` | Max DB connections | `25` |
|
||||||
|
| `DEFAULT_EXPIRATION_DAYS` | Gallery expiration | `30` |
|
||||||
|
| `WARNING_DAYS_BEFORE_EXPIRY` | Warning period | `7` |
|
||||||
|
|
||||||
|
### Frontend Environment
|
||||||
|
|
||||||
|
For production builds:
|
||||||
|
```bash
|
||||||
|
# frontend/.env.production
|
||||||
|
VITE_API_URL=/api # For reverse proxy
|
||||||
|
# or
|
||||||
|
VITE_API_URL=https://api.your-domain.com # For direct access
|
||||||
|
```
|
||||||
|
|
||||||
|
## 👤 Admin Setup
|
||||||
|
|
||||||
|
### Automatic Admin Creation
|
||||||
|
|
||||||
|
When you run migrations for the first time, an admin account is automatically created:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker
|
||||||
|
docker-compose -f docker-compose.prod.yml exec backend npm run migrate
|
||||||
|
|
||||||
|
# PM2/Manual
|
||||||
|
cd backend && npm run migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
========================================
|
||||||
|
✅ Admin user created successfully!
|
||||||
|
========================================
|
||||||
|
Username: admin
|
||||||
|
Password: SwiftEagle3847!
|
||||||
|
|
||||||
|
⚠️ IMPORTANT: Change password on first login
|
||||||
|
========================================
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important Admin Notes
|
||||||
|
|
||||||
|
1. **Credentials are saved** to `backend/ADMIN_CREDENTIALS.txt`
|
||||||
|
2. **Must change password** on first login (enforced)
|
||||||
|
3. **Password requirements**:
|
||||||
|
- Minimum 12 characters
|
||||||
|
- Uppercase and lowercase letters
|
||||||
|
- Numbers and special characters
|
||||||
|
- Not a common password
|
||||||
|
|
||||||
|
### Lost Admin Password
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker
|
||||||
|
docker-compose -f docker-compose.prod.yml exec backend node scripts/reset-admin-password.js
|
||||||
|
|
||||||
|
# PM2/Manual
|
||||||
|
cd backend && node scripts/reset-admin-password.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 SSL/HTTPS Configuration
|
||||||
|
|
||||||
|
### Option 1: Let's Encrypt with Certbot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Initial certificate
|
||||||
|
docker-compose -f docker-compose.prod.yml run --rm certbot certonly \
|
||||||
|
--webroot --webroot-path=/var/www/certbot \
|
||||||
|
-d your-domain.com -d www.your-domain.com
|
||||||
|
|
||||||
|
# Auto-renewal is handled by certbot container
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Using Traefik
|
||||||
|
|
||||||
|
Add to `docker-compose.override.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
frontend:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.picpeak.rule=Host(`your-domain.com`)"
|
||||||
|
- "traefik.http.routers.picpeak.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.picpeak.tls.certresolver=letsencrypt"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: CloudFlare or Other CDN
|
||||||
|
|
||||||
|
1. Set up your domain in CloudFlare
|
||||||
|
2. Enable "Full SSL/TLS encryption mode"
|
||||||
|
3. Use CloudFlare's origin certificates
|
||||||
|
|
||||||
|
## 🔧 Maintenance & Operations
|
||||||
|
|
||||||
|
### Backup Procedures
|
||||||
|
|
||||||
|
Use the included backup script or create your own:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the provided backup script
|
||||||
|
./scripts/backup.sh
|
||||||
|
|
||||||
|
# Or create custom backup script:
|
||||||
|
#!/bin/bash
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
BACKUP_DIR="./backups/$DATE"
|
||||||
|
|
||||||
|
mkdir -p $BACKUP_DIR
|
||||||
|
|
||||||
|
# Database backup
|
||||||
|
docker-compose -f docker-compose.prod.yml exec -T db \
|
||||||
|
pg_dump -U picpeak picpeak > $BACKUP_DIR/database.sql
|
||||||
|
|
||||||
|
# Files backup
|
||||||
|
tar -czf $BACKUP_DIR/storage.tar.gz storage/
|
||||||
|
|
||||||
|
echo "Backup completed: $BACKUP_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Backups
|
||||||
|
|
||||||
|
The application includes a built-in backup service. Configure via Admin Panel:
|
||||||
|
- Settings → Backup Configuration
|
||||||
|
- Set schedule (cron expression)
|
||||||
|
- Configure destination (local, rsync, S3)
|
||||||
|
- Enable email notifications
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker method
|
||||||
|
git pull
|
||||||
|
docker-compose -f docker-compose.prod.yml build
|
||||||
|
docker-compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# PM2 method
|
||||||
|
git pull
|
||||||
|
cd backend && npm install
|
||||||
|
cd ../frontend && npm install && npm run build
|
||||||
|
pm2 restart picpeak
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
#### Health Checks
|
||||||
|
```bash
|
||||||
|
# API health
|
||||||
|
curl https://your-domain.com/api/health
|
||||||
|
|
||||||
|
# Database connection
|
||||||
|
docker-compose -f docker-compose.prod.yml exec backend \
|
||||||
|
psql -U picpeak -d picpeak -c "SELECT 1"
|
||||||
|
|
||||||
|
# Service status
|
||||||
|
docker-compose -f docker-compose.prod.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logs
|
||||||
|
```bash
|
||||||
|
# Docker logs
|
||||||
|
docker-compose -f docker-compose.prod.yml logs -f
|
||||||
|
|
||||||
|
# PM2 logs
|
||||||
|
pm2 logs picpeak
|
||||||
|
|
||||||
|
# System logs
|
||||||
|
tail -f /var/log/nginx/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### JWT Secret Errors
|
||||||
|
**Error**: "Missing required environment variable: JWT_SECRET"
|
||||||
|
- **Solution**: Set JWT_SECRET in your .env file
|
||||||
|
- **Generate**: `openssl rand -base64 32`
|
||||||
|
|
||||||
|
**Error**: "JWT_SECRET is set to the insecure default value"
|
||||||
|
- **Solution**: Change from default to secure value
|
||||||
|
|
||||||
|
#### Database Connection Failed
|
||||||
|
**Error**: "connect ECONNREFUSED"
|
||||||
|
- **Check**: Database is running
|
||||||
|
- **Check**: Correct host/port in .env
|
||||||
|
- **Docker**: Use `db` as host, not `localhost`
|
||||||
|
|
||||||
|
#### Permission Errors
|
||||||
|
**Error**: "EACCES: permission denied"
|
||||||
|
```bash
|
||||||
|
# Fix Docker permissions
|
||||||
|
sudo chown -R 1001:1001 storage data logs
|
||||||
|
|
||||||
|
# Fix PM2/Manual permissions
|
||||||
|
sudo chown -R $USER:$USER storage data logs
|
||||||
|
chmod -R 755 storage
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Email Not Sending
|
||||||
|
- **Check**: SMTP credentials are correct
|
||||||
|
- **Gmail**: Use app-specific password
|
||||||
|
- **Test**: Admin Panel → Settings → Email → Test Email
|
||||||
|
- **Logs**: Check `email_queue` table for errors
|
||||||
|
|
||||||
|
#### Photos Not Appearing
|
||||||
|
- **Check**: File watcher is running
|
||||||
|
- **Permissions**: `chmod -R 755 storage/`
|
||||||
|
- **Logs**: `grep watcher` in backend logs
|
||||||
|
|
||||||
|
#### Frontend Can't Connect to Backend
|
||||||
|
- **CORS**: Ensure FRONTEND_URL matches in backend .env
|
||||||
|
- **Proxy**: Check nginx configuration
|
||||||
|
- **Direct**: Set CORS_ENABLED=true for non-proxy setup
|
||||||
|
|
||||||
|
### Debug Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all services
|
||||||
|
docker-compose -f docker-compose.prod.yml ps
|
||||||
|
|
||||||
|
# Backend shell access
|
||||||
|
docker-compose -f docker-compose.prod.yml exec backend sh
|
||||||
|
|
||||||
|
# Database access
|
||||||
|
docker-compose -f docker-compose.prod.yml exec db psql -U picpeak
|
||||||
|
|
||||||
|
# Test API
|
||||||
|
curl -I http://localhost:3001/api/health
|
||||||
|
|
||||||
|
# Check disk space
|
||||||
|
df -h storage/
|
||||||
|
|
||||||
|
# View running processes
|
||||||
|
ps aux | grep node
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Security Checklist
|
||||||
|
|
||||||
|
- [ ] **JWT_SECRET** is randomly generated (min 32 chars)
|
||||||
|
- [ ] **Database password** is strong and unique
|
||||||
|
- [ ] **Admin password** changed from auto-generated
|
||||||
|
- [ ] **SSL/HTTPS** enabled and working
|
||||||
|
- [ ] **Firewall** configured (only 80/443 open)
|
||||||
|
- [ ] **File permissions** set correctly (755 for storage)
|
||||||
|
- [ ] **Rate limiting** enabled (default: 100 req/15min)
|
||||||
|
- [ ] **CORS** properly configured
|
||||||
|
- [ ] **Environment files** not in version control
|
||||||
|
- [ ] **Backups** configured and tested
|
||||||
|
- [ ] **Monitoring** alerts set up
|
||||||
|
- [ ] **Updates** scheduled regularly
|
||||||
|
- [ ] **Access logs** being monitored
|
||||||
|
- [ ] **Email** using app-specific passwords
|
||||||
|
- [ ] **Umami analytics** configured (optional)
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
- 📘 [Documentation](https://github.com/the-luap/picpeak)
|
||||||
|
- 🐛 [Report Issues](https://github.com/the-luap/picpeak/issues)
|
||||||
|
- 💬 [Discussions](https://github.com/the-luap/picpeak/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need help?** Check the logs first, then open an issue with:
|
||||||
|
- Deployment method used
|
||||||
|
- Error messages
|
||||||
|
- Relevant log output
|
||||||
|
- Environment (without secrets)
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
# Production Deployment Guide
|
|
||||||
|
|
||||||
This comprehensive guide addresses all production deployment scenarios and common issues.
|
|
||||||
|
|
||||||
## Pre-Deployment Checklist
|
|
||||||
|
|
||||||
### 1. Environment Variables
|
|
||||||
Create a `.env` file with ALL required variables:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# CRITICAL - Must change these!
|
|
||||||
JWT_SECRET=<generate-with-openssl-rand-base64-32>
|
|
||||||
DB_PASSWORD=<strong-password>
|
|
||||||
|
|
||||||
# Application URLs (your actual domain)
|
|
||||||
ADMIN_URL=https://yourdomain.com
|
|
||||||
FRONTEND_URL=https://yourdomain.com
|
|
||||||
BACKEND_URL=https://yourdomain.com
|
|
||||||
|
|
||||||
# Database (PostgreSQL)
|
|
||||||
DATABASE_CLIENT=pg
|
|
||||||
DB_HOST=postgres # or external host
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_USER=picpeak
|
|
||||||
DB_NAME=picpeak
|
|
||||||
|
|
||||||
# Email Configuration (required for notifications)
|
|
||||||
SMTP_HOST=smtp.gmail.com
|
|
||||||
SMTP_PORT=587
|
|
||||||
SMTP_SECURE=false
|
|
||||||
SMTP_USER=your-email@gmail.com
|
|
||||||
SMTP_PASS=your-app-password # Use app-specific password
|
|
||||||
EMAIL_FROM=PicPeak <noreply@yourdomain.com>
|
|
||||||
|
|
||||||
# Port Configuration
|
|
||||||
PORT=3001
|
|
||||||
|
|
||||||
# Performance Tuning
|
|
||||||
DB_POOL_MIN=5
|
|
||||||
DB_POOL_MAX=25
|
|
||||||
NODE_ENV=production
|
|
||||||
LOG_LEVEL=info
|
|
||||||
|
|
||||||
# Optional: Umami Analytics (configured via Admin UI)
|
|
||||||
# UMAMI_URL=https://analytics.yourdomain.com
|
|
||||||
# UMAMI_WEBSITE_ID=your-website-id
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Generate Secrets
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Generate JWT Secret (REQUIRED)
|
|
||||||
openssl rand -base64 32
|
|
||||||
|
|
||||||
# Generate Database Password
|
|
||||||
openssl rand -base64 24
|
|
||||||
```
|
|
||||||
|
|
||||||
## Frontend Configuration
|
|
||||||
|
|
||||||
For production deployment behind a reverse proxy:
|
|
||||||
|
|
||||||
### Frontend Environment
|
|
||||||
```bash
|
|
||||||
# frontend/.env.production
|
|
||||||
VITE_API_URL=/api # Uses relative path for reverse proxy
|
|
||||||
|
|
||||||
# Optional: Umami fallback (primary config via Admin UI)
|
|
||||||
# VITE_UMAMI_URL=https://analytics.yourdomain.com
|
|
||||||
# VITE_UMAMI_WEBSITE_ID=your-website-id
|
|
||||||
```
|
|
||||||
|
|
||||||
This ensures all API calls use the same domain/protocol as the frontend.
|
|
||||||
|
|
||||||
### Nginx Proxy Configuration
|
|
||||||
|
|
||||||
The frontend nginx configuration already includes proper proxy settings for:
|
|
||||||
- `/api` → Backend API
|
|
||||||
- `/photos` → Protected photo access
|
|
||||||
- `/thumbnails` → Thumbnail images
|
|
||||||
- `/uploads` → Public uploads (logos, favicons)
|
|
||||||
|
|
||||||
All static assets are served through the nginx proxy, inheriting authentication headers.
|
|
||||||
|
|
||||||
## Deployment Steps
|
|
||||||
|
|
||||||
### 1. Initial Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone repository
|
|
||||||
git clone https://github.com/the-luap/wedding-photo-sharing.git
|
|
||||||
cd wedding-photo-sharing
|
|
||||||
|
|
||||||
# Create required directories
|
|
||||||
mkdir -p storage/events/active storage/events/archived storage/thumbnails storage/uploads
|
|
||||||
mkdir -p data logs
|
|
||||||
mkdir -p certbot/conf certbot/www
|
|
||||||
|
|
||||||
# Set permissions (important!)
|
|
||||||
chmod -R 755 storage data logs
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Fix Docker Volume Permissions
|
|
||||||
|
|
||||||
Create `docker-compose.override.yml` for local volume configuration:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
backend:
|
|
||||||
volumes:
|
|
||||||
- ./storage:/app/storage:delegated
|
|
||||||
- ./data:/app/data:delegated
|
|
||||||
- ./logs:/app/logs:delegated
|
|
||||||
user: "1001:1001" # nodejs user
|
|
||||||
|
|
||||||
db:
|
|
||||||
volumes:
|
|
||||||
- ./postgres-data:/var/lib/postgresql/data
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Build and Deploy
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build images
|
|
||||||
docker-compose -f docker-compose.prod.yml build
|
|
||||||
|
|
||||||
# Start services
|
|
||||||
docker-compose -f docker-compose.prod.yml up -d
|
|
||||||
|
|
||||||
# Check logs
|
|
||||||
docker-compose -f docker-compose.prod.yml logs -f backend
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Initial Admin Setup
|
|
||||||
|
|
||||||
The admin user is automatically created during database migration:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run migrations (this creates admin user)
|
|
||||||
docker-compose -f docker-compose.prod.yml exec backend npm run migrate
|
|
||||||
|
|
||||||
# Admin credentials will be displayed in console and saved to ADMIN_CREDENTIALS.txt
|
|
||||||
# Example output:
|
|
||||||
# ========================================
|
|
||||||
# ✅ Admin user created successfully!
|
|
||||||
# ========================================
|
|
||||||
# Username: admin
|
|
||||||
# Password: SwiftEagle3847!
|
|
||||||
#
|
|
||||||
# ⚠️ IMPORTANT: Change password on first login
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
# Retrieve credentials if needed
|
|
||||||
docker-compose -f docker-compose.prod.yml exec backend cat ADMIN_CREDENTIALS.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important**: You MUST change the auto-generated password on first login.
|
|
||||||
|
|
||||||
### 5. Configure Email (if using database config)
|
|
||||||
|
|
||||||
1. Login to admin panel: https://yourdomain.com/admin
|
|
||||||
2. Go to Settings > Email Configuration
|
|
||||||
3. Enter SMTP details
|
|
||||||
4. Test email sending
|
|
||||||
|
|
||||||
## Common Issues and Solutions
|
|
||||||
|
|
||||||
### Issue 1: Migration Failures
|
|
||||||
|
|
||||||
**Error**: "relation already exists"
|
|
||||||
|
|
||||||
**Solution**: The safe migration runner handles this automatically. If issues persist:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Reset migrations tracking
|
|
||||||
docker-compose -f docker-compose.prod.yml exec db psql -U picpeak -d picpeak
|
|
||||||
|
|
||||||
# In PostgreSQL:
|
|
||||||
DROP TABLE IF EXISTS migrations;
|
|
||||||
\q
|
|
||||||
|
|
||||||
# Re-run migrations
|
|
||||||
docker-compose -f docker-compose.prod.yml exec backend npm run migrate:safe
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue 2: Permission Denied Errors
|
|
||||||
|
|
||||||
**Error**: "EACCES: permission denied"
|
|
||||||
|
|
||||||
**Solution**: Fix container permissions:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Stop containers
|
|
||||||
docker-compose -f docker-compose.prod.yml down
|
|
||||||
|
|
||||||
# Fix permissions on host
|
|
||||||
sudo chown -R 1001:1001 storage data logs
|
|
||||||
|
|
||||||
# Restart
|
|
||||||
docker-compose -f docker-compose.prod.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue 3: Database Connection Failed
|
|
||||||
|
|
||||||
**Error**: "no pg_hba.conf entry"
|
|
||||||
|
|
||||||
**Solution**: Already fixed in docker-compose.prod.yml with:
|
|
||||||
- SSL disabled for internal Docker network
|
|
||||||
- Proper authentication method (scram-sha-256)
|
|
||||||
|
|
||||||
### Issue 4: Frontend Can't Connect to Backend
|
|
||||||
|
|
||||||
**Error**: CORS errors or connection refused
|
|
||||||
|
|
||||||
**Solution**: Ensure environment variables match:
|
|
||||||
- Backend: `FRONTEND_URL` must match your frontend URL
|
|
||||||
- Frontend: `VITE_API_URL` must be set during build
|
|
||||||
|
|
||||||
### Issue 5: Email Not Sending
|
|
||||||
|
|
||||||
**Solution**: Check email configuration:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check backend logs
|
|
||||||
docker-compose -f docker-compose.prod.yml logs backend | grep email
|
|
||||||
|
|
||||||
# Verify SMTP settings
|
|
||||||
# Gmail users: Use app password, not regular password
|
|
||||||
# Enable "Less secure app access" or use OAuth2
|
|
||||||
```
|
|
||||||
|
|
||||||
## SSL/HTTPS Setup
|
|
||||||
|
|
||||||
### Option 1: Using Traefik (Recommended)
|
|
||||||
|
|
||||||
Add these labels to your docker-compose override:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
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"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Using Certbot
|
|
||||||
|
|
||||||
1. Update `nginx/sites-enabled/default` with your domain
|
|
||||||
2. Run certbot:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initial certificate
|
|
||||||
docker-compose -f docker-compose.prod.yml run --rm certbot certonly \
|
|
||||||
--webroot --webroot-path=/var/www/certbot \
|
|
||||||
-d yourdomain.com -d www.yourdomain.com
|
|
||||||
|
|
||||||
# Auto-renewal is handled by the certbot container
|
|
||||||
```
|
|
||||||
|
|
||||||
## Monitoring
|
|
||||||
|
|
||||||
### Health Checks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backend health
|
|
||||||
curl http://localhost/api/health
|
|
||||||
|
|
||||||
# Database connection
|
|
||||||
docker-compose -f docker-compose.prod.yml exec backend \
|
|
||||||
psql -U picpeak -d picpeak -c "SELECT 1"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# All services
|
|
||||||
docker-compose -f docker-compose.prod.yml logs -f
|
|
||||||
|
|
||||||
# Specific service
|
|
||||||
docker-compose -f docker-compose.prod.yml logs -f backend
|
|
||||||
```
|
|
||||||
|
|
||||||
## Backup and Restore
|
|
||||||
|
|
||||||
### Backup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# backup.sh
|
|
||||||
DATE=$(date +%Y%m%d_%H%M%S)
|
|
||||||
BACKUP_DIR="./backups/$DATE"
|
|
||||||
|
|
||||||
mkdir -p $BACKUP_DIR
|
|
||||||
|
|
||||||
# Database
|
|
||||||
docker-compose -f docker-compose.prod.yml exec -T db \
|
|
||||||
pg_dump -U picpeak picpeak > $BACKUP_DIR/database.sql
|
|
||||||
|
|
||||||
# Files
|
|
||||||
tar -czf $BACKUP_DIR/storage.tar.gz storage/
|
|
||||||
|
|
||||||
echo "Backup completed: $BACKUP_DIR"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Restore
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Database
|
|
||||||
docker-compose -f docker-compose.prod.yml exec -T db \
|
|
||||||
psql -U picpeak picpeak < ./backups/20240713_120000/database.sql
|
|
||||||
|
|
||||||
# Files
|
|
||||||
tar -xzf ./backups/20240713_120000/storage.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production Best Practices
|
|
||||||
|
|
||||||
1. **Always use named volumes** in production for better data persistence
|
|
||||||
2. **Set up monitoring** with Prometheus/Grafana
|
|
||||||
3. **Enable backups** with automated scripts
|
|
||||||
4. **Use a reverse proxy** (Nginx) for SSL termination
|
|
||||||
5. **Implement rate limiting** at the Nginx level
|
|
||||||
6. **Regular updates** - Keep Docker images updated
|
|
||||||
7. **Log rotation** - Configure log rotation for application logs
|
|
||||||
|
|
||||||
## Troubleshooting Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check running containers
|
|
||||||
docker-compose -f docker-compose.prod.yml ps
|
|
||||||
|
|
||||||
# Restart a service
|
|
||||||
docker-compose -f docker-compose.prod.yml restart backend
|
|
||||||
|
|
||||||
# View real-time logs
|
|
||||||
docker-compose -f docker-compose.prod.yml logs -f --tail=100
|
|
||||||
|
|
||||||
# Execute commands in container
|
|
||||||
docker-compose -f docker-compose.prod.yml exec backend sh
|
|
||||||
|
|
||||||
# Database shell
|
|
||||||
docker-compose -f docker-compose.prod.yml exec db psql -U picpeak
|
|
||||||
|
|
||||||
# Clean restart
|
|
||||||
docker-compose -f docker-compose.prod.yml down
|
|
||||||
docker-compose -f docker-compose.prod.yml up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Checklist
|
|
||||||
|
|
||||||
- [ ] Strong JWT_SECRET (min 32 chars)
|
|
||||||
- [ ] Strong database password
|
|
||||||
- [ ] Admin password changed from auto-generated one
|
|
||||||
- [ ] SSL/HTTPS enabled
|
|
||||||
- [ ] Firewall configured (only 80/443 open)
|
|
||||||
- [ ] Regular security updates
|
|
||||||
- [ ] Backup encryption
|
|
||||||
- [ ] Access logs monitored
|
|
||||||
- [ ] Rate limiting enabled (built-in)
|
|
||||||
- [ ] File upload restrictions configured
|
|
||||||
- [ ] Password complexity requirements configured (Admin > Settings)
|
|
||||||
- [ ] Session timeout configured (default 60 min)
|
|
||||||
- [ ] Umami analytics configured (if using)
|
|
||||||
- [ ] SMTP credentials secured with app-specific password
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
For issues not covered here:
|
|
||||||
1. Check application logs
|
|
||||||
2. Review error messages carefully
|
|
||||||
3. Ensure all environment variables are set
|
|
||||||
4. Verify file permissions
|
|
||||||
5. Check Docker daemon logs
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
const knex = require('knex');
|
|
||||||
|
|
||||||
exports.up = async function(db) {
|
|
||||||
console.log('Adding watermark settings...');
|
|
||||||
|
|
||||||
// Add watermark settings to app_settings table
|
|
||||||
const watermarkSettings = [
|
|
||||||
{
|
|
||||||
setting_key: 'branding_watermark_logo_path',
|
|
||||||
setting_value: JSON.stringify(null),
|
|
||||||
setting_type: 'branding'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setting_key: 'branding_watermark_logo_url',
|
|
||||||
setting_value: JSON.stringify(null),
|
|
||||||
setting_type: 'branding'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setting_key: 'branding_watermark_position',
|
|
||||||
setting_value: JSON.stringify('bottom-right'),
|
|
||||||
setting_type: 'branding'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setting_key: 'branding_watermark_opacity',
|
|
||||||
setting_value: JSON.stringify(50),
|
|
||||||
setting_type: 'branding'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setting_key: 'branding_watermark_size',
|
|
||||||
setting_value: JSON.stringify(15),
|
|
||||||
setting_type: 'branding'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const setting of watermarkSettings) {
|
|
||||||
// Check if setting already exists
|
|
||||||
const existing = await db('app_settings')
|
|
||||||
.where('setting_key', setting.setting_key)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
await db('app_settings').insert(setting);
|
|
||||||
console.log(`Added setting: ${setting.setting_key}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Watermark settings migration completed');
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = async function(db) {
|
|
||||||
// Remove watermark settings
|
|
||||||
await db('app_settings')
|
|
||||||
.whereIn('setting_key', [
|
|
||||||
'branding_watermark_logo_path',
|
|
||||||
'branding_watermark_logo_url',
|
|
||||||
'branding_watermark_position',
|
|
||||||
'branding_watermark_opacity',
|
|
||||||
'branding_watermark_size'
|
|
||||||
])
|
|
||||||
.del();
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Test maintenance mode functionality
|
|
||||||
|
|
||||||
echo "Testing maintenance mode implementation..."
|
|
||||||
|
|
||||||
# First, let's check the current maintenance mode status
|
|
||||||
echo -e "\n1. Checking current maintenance mode status:"
|
|
||||||
curl -s http://localhost:3002/api/public/settings | jq '.general_maintenance_mode'
|
|
||||||
|
|
||||||
# Test a public gallery endpoint
|
|
||||||
echo -e "\n2. Testing public gallery endpoint (should get 503 if maintenance is on):"
|
|
||||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:3002/api/gallery/test-gallery/info
|
|
||||||
|
|
||||||
# Test admin login (should always work)
|
|
||||||
echo -e "\n\n3. Testing admin login endpoint (should always work):"
|
|
||||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:3002/api/admin/login
|
|
||||||
|
|
||||||
echo -e "\n\nDone!"
|
|
||||||
Reference in New Issue
Block a user