Add complete frontend implementation and Docker deployment setup

- 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>
This commit is contained in:
2025-07-06 20:23:13 +02:00
parent 032bbae50d
commit 6c82958c79
73 changed files with 10611 additions and 2 deletions
+134
View File
@@ -0,0 +1,134 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}Photo Sharing Platform Backup Script${NC}"
echo "===================================="
# Configuration
BACKUP_DIR="/opt/photo-sharing/backup"
STACK_NAME="photo-sharing"
RETENTION_DAYS=30
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_NAME="backup-${TIMESTAMP}"
# Create backup directory
mkdir -p $BACKUP_DIR/$BACKUP_NAME
# Function to check if service is running
check_service() {
local service=$1
if docker service ps ${STACK_NAME}_${service} --format "{{.CurrentState}}" | grep -q "Running"; then
return 0
else
return 1
fi
}
# Backup database
echo -e "${GREEN}Backing up database...${NC}"
if check_service "db"; then
DB_CONTAINER=$(docker ps -q -f name=${STACK_NAME}_db -f status=running | head -1)
if [ ! -z "$DB_CONTAINER" ]; then
docker exec $DB_CONTAINER pg_dumpall -U postgres > $BACKUP_DIR/$BACKUP_NAME/database.sql
echo -e "${GREEN}Database backup completed${NC}"
else
echo -e "${RED}Database container not found${NC}"
fi
else
echo -e "${YELLOW}Database service not running, skipping...${NC}"
fi
# Backup photos
echo -e "${GREEN}Backing up photos...${NC}"
if [ -d "/opt/photo-sharing/storage" ]; then
tar -czf $BACKUP_DIR/$BACKUP_NAME/photos.tar.gz -C /opt/photo-sharing storage/
echo -e "${GREEN}Photos backup completed${NC}"
else
echo -e "${YELLOW}Photos directory not found, skipping...${NC}"
fi
# Backup application data
echo -e "${GREEN}Backing up application data...${NC}"
if [ -d "/opt/photo-sharing/data" ]; then
tar -czf $BACKUP_DIR/$BACKUP_NAME/app-data.tar.gz -C /opt/photo-sharing data/
echo -e "${GREEN}Application data backup completed${NC}"
else
echo -e "${YELLOW}Application data directory not found, skipping...${NC}"
fi
# Backup Docker volumes
echo -e "${GREEN}Backing up Docker volumes...${NC}"
for volume in $(docker volume ls -q | grep ${STACK_NAME}); do
echo "Backing up volume: $volume"
docker run --rm \
-v $volume:/data \
-v $BACKUP_DIR/$BACKUP_NAME:/backup \
alpine tar -czf /backup/volume-${volume}.tar.gz -C /data .
done
# Backup configurations
echo -e "${GREEN}Backing up configurations...${NC}"
if [ -f "../../.env.production" ]; then
cp ../../.env.production $BACKUP_DIR/$BACKUP_NAME/
fi
# Export Docker secrets (encrypted)
echo -e "${GREEN}Exporting Docker secrets info...${NC}"
docker secret ls --filter "label=com.docker.stack.namespace=$STACK_NAME" > $BACKUP_DIR/$BACKUP_NAME/secrets-list.txt
# Create backup manifest
echo -e "${GREEN}Creating backup manifest...${NC}"
cat > $BACKUP_DIR/$BACKUP_NAME/manifest.json << EOF
{
"timestamp": "$TIMESTAMP",
"stack_name": "$STACK_NAME",
"hostname": "$(hostname)",
"docker_version": "$(docker version --format '{{.Server.Version}}')",
"services": $(docker service ls --filter "label=com.docker.stack.namespace=$STACK_NAME" --format '{{json .}}' | jq -s .),
"backup_contents": [
"database.sql",
"photos.tar.gz",
"app-data.tar.gz",
"volume-*.tar.gz",
".env.production",
"secrets-list.txt"
]
}
EOF
# Compress entire backup
echo -e "${GREEN}Compressing backup...${NC}"
cd $BACKUP_DIR
tar -czf ${BACKUP_NAME}.tar.gz $BACKUP_NAME/
rm -rf $BACKUP_NAME/
# Upload to S3 (optional)
if [ ! -z "$S3_BACKUP_BUCKET" ] && command -v aws &> /dev/null; then
echo -e "${GREEN}Uploading to S3...${NC}"
aws s3 cp ${BACKUP_NAME}.tar.gz s3://${S3_BACKUP_BUCKET}/photo-sharing/
fi
# Clean up old backups
echo -e "${GREEN}Cleaning up old backups...${NC}"
find $BACKUP_DIR -name "backup-*.tar.gz" -mtime +$RETENTION_DAYS -delete
# Show backup summary
BACKUP_SIZE=$(du -h $BACKUP_DIR/${BACKUP_NAME}.tar.gz | cut -f1)
echo ""
echo -e "${GREEN}Backup completed successfully!${NC}"
echo -e "Backup file: $BACKUP_DIR/${BACKUP_NAME}.tar.gz"
echo -e "Backup size: $BACKUP_SIZE"
echo -e "Retention: $RETENTION_DAYS days"
# Verify backup
echo ""
echo -e "${GREEN}Verifying backup...${NC}"
tar -tzf $BACKUP_DIR/${BACKUP_NAME}.tar.gz | head -10
echo "..."
echo -e "${GREEN}Backup verification complete${NC}"
+111
View File
@@ -0,0 +1,111 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}Docker Secrets Creation Script${NC}"
echo "==============================="
# Function to create or update a secret
create_secret() {
local secret_name=$1
local secret_value=$2
# Check if secret exists
if docker secret ls | grep -q $secret_name; then
echo -e "${YELLOW}Secret '$secret_name' already exists. Skipping...${NC}"
else
echo "$secret_value" | docker secret create $secret_name -
echo -e "${GREEN}Created secret: $secret_name${NC}"
fi
}
# Function to generate random password
generate_password() {
openssl rand -base64 32 | tr -d "=+/" | cut -c1-25
}
# Check if in swarm mode
if ! docker info | grep -q "Swarm: active"; then
echo -e "${RED}Docker is not in swarm mode. Run init-swarm.sh first.${NC}"
exit 1
fi
# Load environment variables if .env.production exists
if [ -f "../../.env.production" ]; then
echo -e "${GREEN}Loading environment variables from .env.production${NC}"
source ../../.env.production
fi
# JWT Secret
if [ -z "$JWT_SECRET" ]; then
JWT_SECRET=$(generate_password)
echo -e "${YELLOW}Generated JWT_SECRET: $JWT_SECRET${NC}"
fi
create_secret "jwt_secret" "$JWT_SECRET"
# Database credentials
if [ -z "$DB_USER" ]; then
DB_USER="photoapp"
fi
if [ -z "$DB_PASSWORD" ]; then
DB_PASSWORD=$(generate_password)
echo -e "${YELLOW}Generated DB_PASSWORD: $DB_PASSWORD${NC}"
fi
create_secret "db_user" "$DB_USER"
create_secret "db_password" "$DB_PASSWORD"
# SMTP credentials
if [ -z "$SMTP_USER" ]; then
read -p "Enter SMTP username: " SMTP_USER
fi
if [ -z "$SMTP_PASS" ]; then
read -sp "Enter SMTP password: " SMTP_PASS
echo
fi
create_secret "smtp_user" "$SMTP_USER"
create_secret "smtp_pass" "$SMTP_PASS"
# Traefik dashboard auth (username:password)
if [ -z "$TRAEFIK_USER" ]; then
TRAEFIK_USER="admin"
fi
if [ -z "$TRAEFIK_PASSWORD" ]; then
TRAEFIK_PASSWORD=$(generate_password)
echo -e "${YELLOW}Generated TRAEFIK_PASSWORD: $TRAEFIK_PASSWORD${NC}"
fi
# Generate htpasswd format
TRAEFIK_AUTH=$(docker run --rm httpd:alpine htpasswd -nb $TRAEFIK_USER $TRAEFIK_PASSWORD)
create_secret "traefik_dashboard_auth" "$TRAEFIK_AUTH"
# OAuth secrets (optional)
if [ ! -z "$OAUTH_CLIENT_SECRET" ]; then
create_secret "oauth_client_secret" "$OAUTH_CLIENT_SECRET"
fi
if [ ! -z "$OAUTH_SECRET" ]; then
create_secret "oauth_secret" "$OAUTH_SECRET"
fi
# Drone CI secrets
if [ ! -z "$DRONE_RPC_SECRET" ]; then
create_secret "drone_rpc_secret" "$DRONE_RPC_SECRET"
fi
echo ""
echo -e "${GREEN}Secrets creation complete!${NC}"
echo ""
echo -e "${YELLOW}Important: Save these generated values in a secure location:${NC}"
echo "JWT_SECRET=$JWT_SECRET"
echo "DB_PASSWORD=$DB_PASSWORD"
echo "TRAEFIK_USER=$TRAEFIK_USER"
echo "TRAEFIK_PASSWORD=$TRAEFIK_PASSWORD"
echo ""
echo -e "${GREEN}Next steps:${NC}"
echo "1. Update .env.production with the generated values"
echo "2. Deploy Traefik: ./deploy-traefik.sh"
echo "3. Deploy the application: ./deploy.sh"
+196
View File
@@ -0,0 +1,196 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${GREEN}Photo Sharing Platform Deployment Script${NC}"
echo "========================================"
# Default values
STACK_NAME="photo-sharing"
ENV_FILE="../../.env.production"
REGISTRY_URL="${REGISTRY_URL:-}"
VERSION="${VERSION:-latest}"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--env)
ENV_FILE="$2"
shift 2
;;
--registry)
REGISTRY_URL="$2"
shift 2
;;
--version)
VERSION="$2"
shift 2
;;
--stack-name)
STACK_NAME="$2"
shift 2
;;
--help)
echo "Usage: $0 [options]"
echo "Options:"
echo " --env FILE Path to environment file (default: ../../.env.production)"
echo " --registry URL Docker registry URL"
echo " --version VERSION Image version to deploy (default: latest)"
echo " --stack-name NAME Stack name (default: photo-sharing)"
exit 0
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
exit 1
;;
esac
done
# Check if Docker is in swarm mode
if ! docker info | grep -q "Swarm: active"; then
echo -e "${RED}Docker is not in swarm mode. Run init-swarm.sh first.${NC}"
exit 1
fi
# Check if environment file exists
if [ ! -f "$ENV_FILE" ]; then
echo -e "${RED}Environment file not found: $ENV_FILE${NC}"
echo "Please create it from .env.production.example"
exit 1
fi
# Load environment variables
echo -e "${GREEN}Loading environment variables...${NC}"
set -a
source "$ENV_FILE"
set +a
# Export deployment variables
export REGISTRY_URL
export VERSION
# Validate required environment variables
required_vars=(
"FRONTEND_HOST"
"BACKEND_HOST"
"ADMIN_URL"
"FRONTEND_URL"
"ACME_EMAIL"
"DB_NAME"
"EMAIL_FROM"
)
echo -e "${GREEN}Validating configuration...${NC}"
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
echo -e "${RED}Missing required environment variable: $var${NC}"
exit 1
fi
done
# Check if Traefik is running
if ! docker service ls | grep -q "traefik_traefik"; then
echo -e "${YELLOW}Traefik is not running. Deploy it first with:${NC}"
echo "cd ../traefik && docker stack deploy -c docker-compose.traefik.yml traefik"
exit 1
fi
# Check if secrets exist
echo -e "${GREEN}Checking Docker secrets...${NC}"
required_secrets=(
"jwt_secret"
"db_user"
"db_password"
"smtp_user"
"smtp_pass"
)
for secret in "${required_secrets[@]}"; do
if ! docker secret ls | grep -q $secret; then
echo -e "${RED}Missing required secret: $secret${NC}"
echo "Run create-secrets.sh first"
exit 1
fi
done
# Pull latest images if registry is specified
if [ ! -z "$REGISTRY_URL" ]; then
echo -e "${GREEN}Pulling latest images...${NC}"
docker pull ${REGISTRY_URL}/photo-sharing-backend:${VERSION} || true
docker pull ${REGISTRY_URL}/photo-sharing-frontend:${VERSION} || true
fi
# Deploy the stack
echo -e "${GREEN}Deploying stack: $STACK_NAME${NC}"
echo -e "${BLUE}Version: $VERSION${NC}"
echo -e "${BLUE}Registry: ${REGISTRY_URL:-local}${NC}"
cd ..
docker stack deploy \
-c docker-stack.yml \
--with-registry-auth \
$STACK_NAME
# Wait for services to start
echo -e "${GREEN}Waiting for services to start...${NC}"
sleep 10
# Check service status
echo -e "${GREEN}Service status:${NC}"
docker service ls --filter "label=com.docker.stack.namespace=$STACK_NAME"
# Wait for database to be ready
echo -e "${GREEN}Waiting for database to be ready...${NC}"
max_attempts=30
attempt=1
while [ $attempt -le $max_attempts ]; do
if docker exec $(docker ps -q -f name=${STACK_NAME}_db) pg_isready -U postgres > /dev/null 2>&1; then
echo -e "${GREEN}Database is ready!${NC}"
break
fi
echo -n "."
sleep 2
attempt=$((attempt + 1))
done
if [ $attempt -gt $max_attempts ]; then
echo -e "${RED}Database failed to start in time${NC}"
exit 1
fi
# Run database migrations
echo -e "${GREEN}Running database migrations...${NC}"
sleep 5
docker exec $(docker ps -q -f name=${STACK_NAME}_backend -f status=running | head -1) npm run migrate || {
echo -e "${YELLOW}Migration failed. This might be normal if migrations already ran.${NC}"
}
# Show deployment information
echo ""
echo -e "${GREEN}Deployment complete!${NC}"
echo ""
echo -e "${BLUE}Access URLs:${NC}"
echo "Frontend: https://${FRONTEND_HOST}"
echo "Backend API: https://${BACKEND_HOST}/api"
if [ ! -z "$UMAMI_HOST" ]; then
echo "Analytics: https://${UMAMI_HOST}"
fi
if [ ! -z "$TRAEFIK_HOST" ]; then
echo "Traefik Dashboard: https://${TRAEFIK_HOST}/dashboard/"
fi
echo ""
echo -e "${BLUE}Useful commands:${NC}"
echo "View logs: docker service logs ${STACK_NAME}_backend"
echo "Scale service: docker service scale ${STACK_NAME}_backend=5"
echo "Update service: docker service update ${STACK_NAME}_backend"
echo "Remove stack: docker stack rm $STACK_NAME"
echo ""
echo -e "${GREEN}Health check:${NC}"
curl -s -o /dev/null -w "Frontend: %{http_code}\n" https://${FRONTEND_HOST}/health || true
curl -s -o /dev/null -w "Backend: %{http_code}\n" https://${BACKEND_HOST}/api/health || true
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}Docker Swarm Initialization Script${NC}"
echo "======================================"
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Check if Docker is installed
if ! command -v docker &> /dev/null; then
echo -e "${RED}Docker is not installed. Please install Docker first.${NC}"
exit 1
fi
# Check if already in swarm mode
if docker info | grep -q "Swarm: active"; then
echo -e "${YELLOW}This node is already part of a swarm.${NC}"
docker node ls
exit 0
fi
# Initialize swarm
echo -e "${GREEN}Initializing Docker Swarm...${NC}"
ADVERTISE_ADDR=${1:-$(hostname -I | awk '{print $1}')}
docker swarm init --advertise-addr $ADVERTISE_ADDR
# Create overlay networks
echo -e "${GREEN}Creating overlay networks...${NC}"
docker network create --driver overlay --attachable traefik-public || true
docker network create --driver overlay --attachable monitoring || true
# Label the node
echo -e "${GREEN}Labeling manager node...${NC}"
NODE_ID=$(docker info -f '{{.Swarm.NodeID}}')
docker node update --label-add db=true $NODE_ID
docker node update --label-add monitoring=true $NODE_ID
# Create required directories
echo -e "${GREEN}Creating required directories...${NC}"
mkdir -p /opt/photo-sharing/{storage,data,logs,backup}
mkdir -p /opt/traefik/letsencrypt
mkdir -p /opt/monitoring/{prometheus,grafana,loki}
# Set permissions
chown -R 1000:1000 /opt/photo-sharing
chmod -R 755 /opt/photo-sharing
echo -e "${GREEN}Swarm initialization complete!${NC}"
echo ""
echo "Manager join token:"
docker swarm join-token manager
echo ""
echo "Worker join token:"
docker swarm join-token worker
echo ""
echo -e "${GREEN}Next steps:${NC}"
echo "1. Join worker nodes using the token above"
echo "2. Create secrets using create-secrets.sh"
echo "3. Deploy Traefik using deploy-traefik.sh"
echo "4. Deploy the application stack using deploy.sh"