6c82958c79
- Implement React frontend with TypeScript and Tailwind CSS - Add scrappbook.de-inspired UI design with photo galleries - Implement authentication, photo viewing, and download features - Add Docker Swarm configuration with Traefik reverse proxy - Set up Drone CI/CD pipeline for automated deployments - Add monitoring stack with Prometheus and Grafana - Create comprehensive deployment documentation - Add simple local development setup with docker-compose.local.yml Features: - Password-protected galleries with expiration warnings - Responsive photo grid with lightbox viewer - Bulk download functionality - Hot reload development environment - Email testing with Mailhog - Production-ready deployment scripts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
381 lines
9.8 KiB
Bash
Executable File
381 lines
9.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Setup script to create remaining files
|
|
|
|
echo "Creating remaining project files..."
|
|
|
|
# Create directories
|
|
mkdir -p backend/src/services
|
|
mkdir -p backend/src/utils
|
|
mkdir -p backend/src/routes
|
|
mkdir -p backend/migrations
|
|
mkdir -p backend/scripts
|
|
mkdir -p backend/__tests__
|
|
mkdir -p frontend/public
|
|
mkdir -p frontend/src/components
|
|
mkdir -p frontend/src/contexts
|
|
mkdir -p frontend/src/hooks
|
|
mkdir -p frontend/src/pages/admin
|
|
mkdir -p frontend/src/services
|
|
mkdir -p frontend/src/config
|
|
mkdir -p nginx/sites-enabled
|
|
mkdir -p scripts
|
|
mkdir -p storage/events/active
|
|
mkdir -p storage/events/archived
|
|
mkdir -p storage/thumbnails
|
|
mkdir -p data
|
|
mkdir -p logs
|
|
mkdir -p certbot/conf
|
|
mkdir -p certbot/www
|
|
|
|
# Create .gitkeep files
|
|
touch storage/events/active/.gitkeep
|
|
touch storage/events/archived/.gitkeep
|
|
touch storage/thumbnails/.gitkeep
|
|
touch data/.gitkeep
|
|
touch logs/.gitkeep
|
|
|
|
# Create remaining backend services
|
|
cat > backend/src/services/imageProcessor.js << 'EOF'
|
|
const sharp = require('sharp');
|
|
const path = require('path');
|
|
const fs = require('fs').promises;
|
|
|
|
const THUMBNAIL_WIDTH = 300;
|
|
const THUMBNAIL_PATH = path.join(__dirname, '../../../storage/thumbnails');
|
|
|
|
async function generateThumbnail(imagePath) {
|
|
const filename = path.basename(imagePath);
|
|
const thumbnailFilename = `thumb_${filename}`;
|
|
const thumbnailPath = path.join(THUMBNAIL_PATH, thumbnailFilename);
|
|
|
|
// Ensure thumbnail directory exists
|
|
await fs.mkdir(THUMBNAIL_PATH, { recursive: true });
|
|
|
|
// Generate thumbnail
|
|
await sharp(imagePath)
|
|
.resize(THUMBNAIL_WIDTH, null, {
|
|
withoutEnlargement: true,
|
|
fit: 'inside'
|
|
})
|
|
.jpeg({ quality: 80 })
|
|
.toFile(thumbnailPath);
|
|
|
|
return path.relative(path.join(__dirname, '../../../storage'), thumbnailPath);
|
|
}
|
|
|
|
module.exports = { generateThumbnail };
|
|
EOF
|
|
|
|
# Create logger utility
|
|
cat > backend/src/utils/logger.js << 'EOF'
|
|
const winston = require('winston');
|
|
const path = require('path');
|
|
|
|
const logger = winston.createLogger({
|
|
level: process.env.LOG_LEVEL || 'info',
|
|
format: winston.format.combine(
|
|
winston.format.timestamp(),
|
|
winston.format.errors({ stack: true }),
|
|
winston.format.json()
|
|
),
|
|
transports: [
|
|
new winston.transports.File({
|
|
filename: path.join(__dirname, '../../../logs/error.log'),
|
|
level: 'error'
|
|
}),
|
|
new winston.transports.File({
|
|
filename: path.join(__dirname, '../../../logs/combined.log')
|
|
})
|
|
]
|
|
});
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
logger.add(new winston.transports.Console({
|
|
format: winston.format.combine(
|
|
winston.format.colorize(),
|
|
winston.format.simple()
|
|
)
|
|
}));
|
|
}
|
|
|
|
module.exports = logger;
|
|
EOF
|
|
|
|
echo "Backend services created."
|
|
|
|
# Create migration init script
|
|
cat > backend/migrations/init.js << 'EOF'
|
|
const bcrypt = require('bcrypt');
|
|
const { db, initializeDatabase } = require('../src/database/db');
|
|
|
|
async function runMigrations() {
|
|
console.log('Running database migrations...');
|
|
|
|
try {
|
|
// Initialize tables
|
|
await initializeDatabase();
|
|
|
|
// Create default admin user if none exists
|
|
const adminExists = await db('admin_users').first();
|
|
if (!adminExists) {
|
|
const defaultPassword = 'admin123'; // Change this!
|
|
const passwordHash = await bcrypt.hash(defaultPassword, 10);
|
|
|
|
await db('admin_users').insert({
|
|
username: 'admin',
|
|
email: 'admin@example.com',
|
|
password_hash: passwordHash
|
|
});
|
|
|
|
console.log('Default admin user created:');
|
|
console.log('Username: admin');
|
|
console.log('Password: admin123');
|
|
console.log('⚠️ Please change this password immediately!');
|
|
}
|
|
|
|
console.log('Migrations completed successfully');
|
|
process.exit(0);
|
|
} catch (error) {
|
|
console.error('Migration failed:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
runMigrations();
|
|
EOF
|
|
|
|
echo "Migration script created."
|
|
|
|
# Create README
|
|
cat > README.md << 'EOF'
|
|
# Photo Sharing Platform
|
|
|
|
A secure, self-hosted photo sharing platform designed for weddings and events. Features automatic expiration, email notifications, and simple file-based management.
|
|
|
|
## Features
|
|
|
|
- 🔒 Password Protected Galleries
|
|
- ⏰ Automatic Expiration
|
|
- 📧 Email Notifications
|
|
- 📁 Simple File Management
|
|
- 📊 Analytics Integration
|
|
- 🎨 Customizable Themes
|
|
- 📱 Mobile Responsive
|
|
- ⚡ Docker Ready
|
|
|
|
## Quick Start
|
|
|
|
1. Clone the repository
|
|
2. Run `./scripts/install.sh`
|
|
3. Configure `.env` file
|
|
4. Setup SSL: `./scripts/setup-ssl.sh`
|
|
5. Start: `docker-compose -f docker-compose.prod.yml up -d`
|
|
|
|
Default credentials: admin / admin123 (change immediately!)
|
|
|
|
## Documentation
|
|
|
|
See DEPLOYMENT.md for detailed deployment instructions.
|
|
|
|
## License
|
|
|
|
MIT License
|
|
EOF
|
|
|
|
echo "README created."
|
|
|
|
# Create main installation script
|
|
cat > scripts/install.sh << 'EOF'
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
echo "Photo Sharing Platform - Docker Installation"
|
|
echo "==========================================="
|
|
|
|
# Check if running as root
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "This script must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
# Function to check if command exists
|
|
command_exists() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Check prerequisites
|
|
echo "Checking prerequisites..."
|
|
|
|
# Install Docker if not present
|
|
if ! command_exists docker; then
|
|
echo "Installing Docker..."
|
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
|
sh get-docker.sh
|
|
rm get-docker.sh
|
|
fi
|
|
|
|
# Install Docker Compose if not present
|
|
if ! command_exists docker-compose; then
|
|
echo "Installing Docker Compose..."
|
|
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
|
chmod +x /usr/local/bin/docker-compose
|
|
fi
|
|
|
|
# Create necessary directories
|
|
echo "Creating directory structure..."
|
|
mkdir -p storage/events/{active,archived}
|
|
mkdir -p storage/thumbnails
|
|
mkdir -p data
|
|
mkdir -p logs
|
|
mkdir -p nginx/sites-enabled
|
|
mkdir -p certbot/{conf,www}
|
|
|
|
# Set permissions
|
|
chmod -R 755 storage
|
|
chmod -R 755 data
|
|
chmod -R 755 logs
|
|
|
|
# Copy environment file
|
|
if [ ! -f .env ]; then
|
|
cp .env.example .env
|
|
echo "Created .env file. Please edit it with your configuration."
|
|
fi
|
|
|
|
# Generate secure passwords
|
|
echo "Generating secure passwords..."
|
|
JWT_SECRET=$(openssl rand -base64 32)
|
|
DB_PASSWORD=$(openssl rand -base64 32)
|
|
UMAMI_HASH_SALT=$(openssl rand -base64 32)
|
|
|
|
# Update .env file with generated values
|
|
sed -i "s/JWT_SECRET=.*/JWT_SECRET=$JWT_SECRET/" .env
|
|
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$DB_PASSWORD/" .env
|
|
sed -i "s/UMAMI_HASH_SALT=.*/UMAMI_HASH_SALT=$UMAMI_HASH_SALT/" .env
|
|
|
|
echo ""
|
|
echo "Installation complete!"
|
|
echo "Next steps:"
|
|
echo "1. Edit .env file with your domain names and SMTP settings"
|
|
echo "2. Run: ./scripts/setup-ssl.sh to configure SSL certificates"
|
|
echo "3. Run: docker-compose -f docker-compose.prod.yml up -d"
|
|
echo "4. Run: docker-compose -f docker-compose.prod.yml exec backend npm run migrate"
|
|
EOF
|
|
|
|
chmod +x scripts/install.sh
|
|
|
|
echo "Installation script created."
|
|
|
|
# Create nginx config
|
|
cat > nginx/nginx.conf << 'EOF'
|
|
user nginx;
|
|
worker_processes auto;
|
|
error_log /var/log/nginx/error.log warn;
|
|
pid /var/run/nginx.pid;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
include /etc/nginx/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
'$status $body_bytes_sent "$http_referer" '
|
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
|
|
access_log /var/log/nginx/access.log main;
|
|
|
|
sendfile on;
|
|
tcp_nopush on;
|
|
tcp_nodelay on;
|
|
keepalive_timeout 65;
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
|
|
|
# Security headers
|
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
add_header X-Content-Type-Options "nosniff" always;
|
|
add_header X-XSS-Protection "1; mode=block" always;
|
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
|
|
|
# Rate limiting
|
|
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
|
|
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
|
|
|
|
include /etc/nginx/sites-enabled/*.conf;
|
|
}
|
|
EOF
|
|
|
|
echo "Nginx config created."
|
|
|
|
# Create frontend package.json
|
|
cat > frontend/package.json << 'EOF'
|
|
{
|
|
"name": "photo-sharing-frontend",
|
|
"version": "1.0.0",
|
|
"private": true,
|
|
"dependencies": {
|
|
"@testing-library/jest-dom": "^5.16.5",
|
|
"@testing-library/react": "^13.4.0",
|
|
"@testing-library/user-event": "^13.5.0",
|
|
"react": "^18.2.0",
|
|
"react-dom": "^18.2.0",
|
|
"react-router-dom": "^6.8.0",
|
|
"axios": "^1.3.2",
|
|
"react-query": "^3.39.3",
|
|
"date-fns": "^2.29.3",
|
|
"react-toastify": "^9.1.1",
|
|
"react-dropzone": "^14.2.3",
|
|
"react-image-gallery": "^1.2.11",
|
|
"react-countdown": "^2.3.5",
|
|
"tailwindcss": "^3.2.4",
|
|
"autoprefixer": "^10.4.13",
|
|
"postcss": "^8.4.21",
|
|
"web-vitals": "^2.1.4"
|
|
},
|
|
"scripts": {
|
|
"start": "react-scripts start",
|
|
"build": "react-scripts build",
|
|
"test": "react-scripts test",
|
|
"eject": "react-scripts eject"
|
|
},
|
|
"eslintConfig": {
|
|
"extends": [
|
|
"react-app",
|
|
"react-app/jest"
|
|
]
|
|
},
|
|
"browserslist": {
|
|
"production": [
|
|
">0.2%",
|
|
"not dead",
|
|
"not op_mini all"
|
|
],
|
|
"development": [
|
|
"last 1 chrome version",
|
|
"last 1 firefox version",
|
|
"last 1 safari version"
|
|
]
|
|
},
|
|
"devDependencies": {
|
|
"react-scripts": "5.0.1"
|
|
},
|
|
"proxy": "http://localhost:3000"
|
|
}
|
|
EOF
|
|
|
|
echo "Frontend package.json created."
|
|
|
|
echo ""
|
|
echo "Setup script complete!"
|
|
echo "Most important files have been created."
|
|
echo ""
|
|
echo "To complete the setup:"
|
|
echo "1. Run this script: chmod +x setup-remaining-files.sh && ./setup-remaining-files.sh"
|
|
echo "2. Review and update the created files as needed"
|
|
echo "3. Install dependencies: cd backend && npm install && cd ../frontend && npm install"
|
|
echo "4. Follow the deployment instructions in the README"
|