production configs, deployment configs and readme
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Docker ignore file
|
||||
node_modules
|
||||
.next
|
||||
.git
|
||||
.env.local
|
||||
.env.development
|
||||
*.log
|
||||
npm-debug.log*
|
||||
.DS_Store
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
@@ -0,0 +1,28 @@
|
||||
# Production Environment Configuration
|
||||
# Domain: lcc-tt-booking.mikicvi.com
|
||||
|
||||
# Database
|
||||
DATABASE_URL="./data/sqlite.db"
|
||||
|
||||
# NextAuth.js
|
||||
NEXTAUTH_URL="https://lcc-tt-booking.mikicvi.com"
|
||||
NEXTAUTH_SECRET="qHYNaq516ByAY6H4HdxacMICd05I1DqvrTitIuVtT20="
|
||||
|
||||
# Email Configuration (Gmail - Update with your credentials)
|
||||
EMAIL_USER="your-email@gmail.com"
|
||||
EMAIL_PASSWORD="your-app-password-here"
|
||||
|
||||
# Admin Configuration (Change these for production!)
|
||||
ADMIN_EMAIL="admin@lcc-tt-booking.mikicvi.com"
|
||||
ADMIN_PASSWORD="ChangeMeInProduction123!"
|
||||
|
||||
# Application Settings
|
||||
NODE_ENV="production"
|
||||
PORT="3000"
|
||||
|
||||
# Rate Limiting
|
||||
RATE_LIMIT_MAX="100"
|
||||
RATE_LIMIT_WINDOW="900000"
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL="info"
|
||||
@@ -0,0 +1,506 @@
|
||||
# Deployment Strategy for Table Tennis Booking System
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines comprehensive deployment strategies for the Table Tennis Booking System, considering both self-hosting and cloud deployment options. The application is a Next.js-based system with SQLite database, designed for production use.
|
||||
|
||||
## 1. Self-Hosting Strategy
|
||||
|
||||
### Option A: Raspberry Pi + Cloudflare Tunnel (Recommended)
|
||||
|
||||
**Architecture:**
|
||||
|
||||
```
|
||||
Internet → Cloudflare → Cloudflare Tunnel → Raspberry Pi → Docker Container
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Raspberry Pi 4 (4GB+ RAM recommended)
|
||||
- Stable internet connection
|
||||
- Cloudflare account (free tier sufficient)
|
||||
- Domain name (can be managed through Cloudflare)
|
||||
|
||||
**Setup Steps:**
|
||||
|
||||
1. **Raspberry Pi Preparation**
|
||||
|
||||
```bash
|
||||
# Update system
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Install Docker
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Install Docker Compose
|
||||
sudo apt install docker-compose -y
|
||||
```
|
||||
|
||||
2. **Application Deployment**
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <your-repo-url>
|
||||
cd tt-booking
|
||||
|
||||
# Create production environment file
|
||||
cp .env.example .env.production
|
||||
# Edit .env.production with your values
|
||||
|
||||
# Deploy with Docker
|
||||
docker-compose -f docker-compose.production.yml up -d
|
||||
```
|
||||
|
||||
3. **Cloudflare Tunnel Setup**
|
||||
|
||||
```bash
|
||||
# Install cloudflared
|
||||
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
|
||||
sudo dpkg -i cloudflared-linux-arm64.deb
|
||||
|
||||
# Authenticate
|
||||
cloudflared tunnel login
|
||||
|
||||
# Create tunnel
|
||||
cloudflared tunnel create tt-booking
|
||||
|
||||
# Configure tunnel (create config.yml)
|
||||
cloudflared tunnel route dns tt-booking yourdomain.com
|
||||
|
||||
# Run tunnel
|
||||
cloudflared tunnel run tt-booking
|
||||
```
|
||||
|
||||
**Cloudflare Tunnel Config (`~/.cloudflared/config.yml`):**
|
||||
|
||||
```yaml
|
||||
tunnel: <tunnel-id>
|
||||
credentials-file: /home/pi/.cloudflared/<tunnel-id>.json
|
||||
|
||||
ingress:
|
||||
- hostname: yourdomain.com
|
||||
service: http://localhost:3000
|
||||
- service: http_status:404
|
||||
```
|
||||
|
||||
**Production Docker Compose (`docker-compose.production.yml`):**
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
tt-booking:
|
||||
build: .
|
||||
ports:
|
||||
- '3000:3000'
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=/app/data/sqlite.db
|
||||
- NEXTAUTH_URL=https://yourdomain.com
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
- EMAIL_USER=${EMAIL_USER}
|
||||
- EMAIL_PASSWORD=${EMAIL_PASSWORD}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./backups:/app/backups
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# Backup service
|
||||
backup:
|
||||
image: alpine:latest
|
||||
volumes:
|
||||
- ./data:/data:ro
|
||||
- ./backups:/backups
|
||||
command: >
|
||||
sh -c "
|
||||
while true; do
|
||||
cp /data/sqlite.db /backups/sqlite-$(date +%Y%m%d-%H%M%S).db
|
||||
find /backups -name 'sqlite-*.db' -mtime +7 -delete
|
||||
sleep 86400
|
||||
done"
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- No need for port forwarding or exposing home IP
|
||||
- Free SSL certificates through Cloudflare
|
||||
- DDoS protection and CDN benefits
|
||||
- Easy domain management
|
||||
- Cost-effective (only domain cost ~$10-15/year)
|
||||
|
||||
**Disadvantages:**
|
||||
|
||||
- Dependent on home internet stability
|
||||
- Limited by residential bandwidth
|
||||
- Requires basic Linux administration skills
|
||||
|
||||
### Option B: Traditional Self-Hosting with Reverse Proxy
|
||||
|
||||
**Architecture:**
|
||||
|
||||
```
|
||||
Internet → Router/Firewall → Nginx → Docker Container
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Dedicated server or powerful Raspberry Pi
|
||||
- Static IP address or Dynamic DNS service
|
||||
- SSL certificate (Let's Encrypt)
|
||||
- Port forwarding configuration
|
||||
|
||||
**Setup includes all the Docker setup above, plus:**
|
||||
|
||||
1. **Nginx Configuration**
|
||||
|
||||
```bash
|
||||
# Install Nginx
|
||||
sudo apt install nginx certbot python3-certbot-nginx
|
||||
|
||||
# Configure SSL
|
||||
sudo certbot --nginx -d yourdomain.com
|
||||
```
|
||||
|
||||
2. **Updated Docker Compose with Nginx**
|
||||
Use the existing [docker-compose.yml](docker-compose.yml) with Nginx service.
|
||||
|
||||
**Advantages:**
|
||||
|
||||
- Full control over infrastructure
|
||||
- No dependency on third-party tunneling services
|
||||
- Better performance for local network access
|
||||
|
||||
**Disadvantages:**
|
||||
|
||||
- Requires static IP or Dynamic DNS
|
||||
- More complex firewall/security configuration
|
||||
- SSL certificate management overhead
|
||||
|
||||
## 2. Cloud Deployment Strategies
|
||||
|
||||
### Option A: DigitalOcean App Platform (Recommended for Small Scale)
|
||||
|
||||
**Cost Estimate:** $12-25/month
|
||||
|
||||
**Features:**
|
||||
|
||||
- Automatic deployments from Git
|
||||
- Built-in SSL certificates
|
||||
- Automatic scaling
|
||||
- Integrated monitoring
|
||||
|
||||
**Deployment:**
|
||||
|
||||
1. Connect GitHub repository
|
||||
2. Configure environment variables
|
||||
3. Add persistent volume for SQLite database
|
||||
4. Deploy with zero-config
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```yaml
|
||||
# .do/app.yaml
|
||||
name: tt-booking
|
||||
services:
|
||||
- name: web
|
||||
source_dir: /
|
||||
github:
|
||||
repo: your-username/tt-booking
|
||||
branch: main
|
||||
run_command: npm start
|
||||
environment_slug: node-js
|
||||
instance_count: 1
|
||||
instance_size_slug: basic-xxs
|
||||
envs:
|
||||
- key: NODE_ENV
|
||||
value: production
|
||||
- key: DATABASE_URL
|
||||
value: /app/data/sqlite.db
|
||||
```
|
||||
|
||||
### Option B: Railway (Developer-Friendly)
|
||||
|
||||
**Cost Estimate:** $5-20/month
|
||||
|
||||
**Features:**
|
||||
|
||||
- Git-based deployments
|
||||
- Built-in databases available
|
||||
- Simple pricing model
|
||||
- Excellent developer experience
|
||||
|
||||
**Deployment:**
|
||||
|
||||
```bash
|
||||
# Install Railway CLI
|
||||
npm install -g @railway/cli
|
||||
|
||||
# Login and deploy
|
||||
railway login
|
||||
railway init
|
||||
railway up
|
||||
```
|
||||
|
||||
### Option C: Vercel + PlanetScale (Serverless)
|
||||
|
||||
**Cost Estimate:** $0-20/month (depending on usage)
|
||||
|
||||
**Modifications needed:**
|
||||
|
||||
- Replace SQLite with PlanetScale MySQL
|
||||
- Update database schema for MySQL compatibility
|
||||
- Modify connection configuration
|
||||
|
||||
**Deployment:**
|
||||
|
||||
```bash
|
||||
# Install Vercel CLI
|
||||
npm install -g vercel
|
||||
|
||||
# Deploy
|
||||
vercel --prod
|
||||
```
|
||||
|
||||
### Option D: AWS/GCP/Azure (Enterprise Scale)
|
||||
|
||||
**Cost Estimate:** $30-100+/month
|
||||
|
||||
**AWS Deployment Options:**
|
||||
|
||||
1. **ECS Fargate + RDS**
|
||||
|
||||
- Container-based deployment
|
||||
- Managed database
|
||||
- Auto-scaling capabilities
|
||||
|
||||
2. **Elastic Beanstalk**
|
||||
|
||||
- Simple deployment model
|
||||
- Built-in monitoring
|
||||
- Easy environment management
|
||||
|
||||
3. **App Runner**
|
||||
- Serverless container platform
|
||||
- Automatic scaling
|
||||
- Pay-per-use pricing
|
||||
|
||||
## 3. Database Considerations
|
||||
|
||||
### For Self-Hosting
|
||||
|
||||
- **SQLite**: Perfect for small to medium loads
|
||||
- **Backup Strategy**: Automated daily backups to external storage
|
||||
- **Monitoring**: Simple file-based health checks
|
||||
|
||||
### For Cloud Deployment
|
||||
|
||||
- **Small Scale**: Keep SQLite with persistent volumes
|
||||
- **Medium Scale**: PostgreSQL (Railway, DigitalOcean Managed DB)
|
||||
- **Large Scale**: Multi-region database (AWS RDS, Google Cloud SQL)
|
||||
|
||||
## 4. Monitoring and Maintenance
|
||||
|
||||
### Essential Monitoring
|
||||
|
||||
```bash
|
||||
# Add to crontab for health checks
|
||||
*/5 * * * * curl -f https://yourdomain.com/api/health || echo "App down" | mail -s "Alert" admin@example.com
|
||||
```
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
1. **Database Backups**: Daily automated SQLite file copies
|
||||
2. **Environment Config**: Version controlled `.env` files
|
||||
3. **Application Code**: Git-based source control
|
||||
|
||||
### Update Strategy
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# update.sh
|
||||
cd /path/to/tt-booking
|
||||
git pull origin main
|
||||
docker-compose -f docker-compose.production.yml down
|
||||
docker-compose -f docker-compose.production.yml up -d --build
|
||||
```
|
||||
|
||||
## 5. Security Considerations
|
||||
|
||||
### Self-Hosting Security Checklist
|
||||
|
||||
- [ ] Firewall configured (only necessary ports open)
|
||||
- [ ] Regular OS updates automated
|
||||
- [ ] Non-root user for application
|
||||
- [ ] SSL certificates properly configured
|
||||
- [ ] Database backups encrypted
|
||||
- [ ] Rate limiting configured (already in nginx.conf)
|
||||
- [ ] Log monitoring for suspicious activity
|
||||
|
||||
### Cloud Security
|
||||
|
||||
- [ ] Environment variables properly secured
|
||||
- [ ] Database access restricted
|
||||
- [ ] API rate limiting enabled
|
||||
- [ ] Regular dependency updates
|
||||
- [ ] Security headers configured (already in app)
|
||||
|
||||
## 6. Cost Comparison
|
||||
|
||||
| Deployment Method | Monthly Cost | Effort | Scalability | Reliability |
|
||||
| ------------------------- | ------------ | -------- | ----------- | ----------- |
|
||||
| Raspberry Pi + CF Tunnel | $1-2 | Medium | Low | Medium |
|
||||
| Traditional Self-Host | $5-10 | High | Low | Medium |
|
||||
| DigitalOcean App Platform | $12-25 | Low | Medium | High |
|
||||
| Railway | $5-20 | Very Low | Medium | High |
|
||||
| Vercel + PlanetScale | $0-20 | Low | High | High |
|
||||
| AWS/GCP/Azure | $30-100+ | High | Very High | Very High |
|
||||
|
||||
## 7. Recommended Approach
|
||||
|
||||
### For Personal/Small Group Use:
|
||||
|
||||
**Raspberry Pi + Cloudflare Tunnel** - Most cost-effective with good reliability
|
||||
|
||||
### For Small Business:
|
||||
|
||||
**Railway or DigitalOcean App Platform** - Balance of simplicity and reliability
|
||||
|
||||
### For Enterprise:
|
||||
|
||||
**AWS/GCP with proper CI/CD pipeline** - Maximum scalability and reliability
|
||||
|
||||
## 8. Local Development Best Practices
|
||||
|
||||
### Standalone Development
|
||||
|
||||
```bash
|
||||
# Quick development setup
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Docker Development
|
||||
|
||||
```bash
|
||||
# Development with Docker
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Production-like Local Testing
|
||||
|
||||
```bash
|
||||
# Test production build locally
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## 9. Health Check Endpoint
|
||||
|
||||
The application includes a health check endpoint at `/api/health` for monitoring purposes. You should create this endpoint:
|
||||
|
||||
```typescript
|
||||
// app/api/health/route.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Basic database connectivity check
|
||||
await db.select().from(settings).limit(1);
|
||||
|
||||
return NextResponse.json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({ status: 'unhealthy', error: 'Database connection failed' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. Environment Variables for Production
|
||||
|
||||
Create a `.env.production` file with the following required variables:
|
||||
|
||||
```bash
|
||||
# Application
|
||||
NODE_ENV=production
|
||||
NEXTAUTH_URL=https://yourdomain.com
|
||||
NEXTAUTH_SECRET=your-very-long-random-secret-here
|
||||
|
||||
# Database
|
||||
DATABASE_URL=/app/data/sqlite.db
|
||||
|
||||
# Email Configuration (optional)
|
||||
EMAIL_USER=your-email@gmail.com
|
||||
EMAIL_PASSWORD=your-app-specific-password
|
||||
|
||||
# Admin Account
|
||||
ADMIN_EMAIL=admin@yourdomain.com
|
||||
ADMIN_PASSWORD=secure-admin-password
|
||||
|
||||
# Optional: Rate limiting
|
||||
RATE_LIMIT_MAX=100
|
||||
RATE_LIMIT_WINDOW=900000
|
||||
```
|
||||
|
||||
## 11. Docker Production Optimization
|
||||
|
||||
Create a production-optimized `Dockerfile.production`:
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci --only=production && npm cache clean --force
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Create data directory for SQLite
|
||||
RUN mkdir -p /app/data && chown nextjs:nodejs /app/data
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
This deployment strategy provides multiple pathways depending on your technical expertise, budget, and scaling requirements. The Cloudflare Tunnel approach is particularly attractive for self-hosting as it eliminates many traditional networking complexities while maintaining security and reliability.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Multi-stage production Dockerfile for LCC Table Tennis Booking
|
||||
FROM node:22-slim AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN \
|
||||
if [ -f package-lock.json ]; then npm ci --ignore-scripts; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Rebuild better-sqlite3 for Alpine Linux
|
||||
RUN npm rebuild better-sqlite3
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Create system user and group
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy necessary files from builder stage
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
# Create public directory if it doesn't exist
|
||||
RUN mkdir -p public
|
||||
|
||||
# Create directories for data and backups
|
||||
RUN mkdir -p /app/data /app/backups /app/logs && \
|
||||
chown -R nextjs:nodejs /app/data /app/backups /app/logs
|
||||
|
||||
# Create startup script
|
||||
COPY --chown=nextjs:nodejs <<EOF /app/start.sh
|
||||
#!/bin/sh
|
||||
set -e
|
||||
echo "🚀 Starting TT Booking Production..."
|
||||
echo "🌟 Starting server..."
|
||||
exec node server.js
|
||||
EOF
|
||||
|
||||
RUN chmod +x /app/start.sh
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||
|
||||
CMD ["/app/start.sh"]
|
||||
@@ -0,0 +1,149 @@
|
||||
# LCC Table Tennis Booking - Production Setup
|
||||
|
||||
## Quick Start
|
||||
|
||||
Your production environment is configured for domain: **lcc-tt-booking.mikicvi.com**
|
||||
|
||||
### 1. Deploy the Application
|
||||
|
||||
```bash
|
||||
# Make deployment script executable (if not already done)
|
||||
chmod +x deploy.sh
|
||||
|
||||
# Deploy to production
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### 2. Set Up Cloudflare Tunnel
|
||||
|
||||
```bash
|
||||
# Run the tunnel setup script
|
||||
./setup-tunnel.sh
|
||||
|
||||
# Follow the instructions provided by the script
|
||||
```
|
||||
|
||||
## Configuration Files Created
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
- **`.env.production`** - Production environment variables
|
||||
- **`docker-compose.production.yml`** - Production Docker Compose configuration
|
||||
- **`Dockerfile.production`** - Optimized production Docker build
|
||||
|
||||
### Security & Secrets
|
||||
|
||||
- **NEXTAUTH_SECRET**: `qHYNaq516ByAY6H4HdxacMICd05I1DqvrTitIuVtT20=` (pre-generated)
|
||||
- **Domain**: `lcc-tt-booking.mikicvi.com`
|
||||
- **Admin Email**: `admin@lcc-tt-booking.mikicvi.com`
|
||||
|
||||
### Scripts & Automation
|
||||
|
||||
- **`deploy.sh`** - One-command production deployment
|
||||
- **`setup-tunnel.sh`** - Cloudflare Tunnel setup assistant
|
||||
- **`cloudflare-tunnel-config.yml`** - Tunnel configuration template
|
||||
|
||||
### Health & Monitoring
|
||||
|
||||
- **`/api/health`** - Health check endpoint with database connectivity and memory usage
|
||||
- **Automated backups** - Daily SQLite backups (30-day retention)
|
||||
- **Log rotation** - Automatic log management
|
||||
|
||||
## Production Checklist
|
||||
|
||||
### Before Going Live:
|
||||
|
||||
- [ ] Update email credentials in `.env.production`
|
||||
- [ ] Change admin password from default
|
||||
- [ ] Set up Cloudflare Tunnel
|
||||
- [ ] Test health check endpoint
|
||||
- [ ] Verify SSL certificate is working
|
||||
|
||||
### After Deployment:
|
||||
|
||||
- [ ] Test all booking functionality
|
||||
- [ ] Verify admin panel access
|
||||
- [ ] Check automated backups are working
|
||||
- [ ] Set up monitoring alerts (optional)
|
||||
|
||||
## Management Commands
|
||||
|
||||
```bash
|
||||
# View application logs
|
||||
docker-compose -f docker-compose.production.yml logs -f tt-booking
|
||||
|
||||
# Restart application
|
||||
docker-compose -f docker-compose.production.yml restart tt-booking
|
||||
|
||||
# Stop all services
|
||||
docker-compose -f docker-compose.production.yml down
|
||||
|
||||
# Update and redeploy
|
||||
./deploy.sh
|
||||
|
||||
# Access database backup
|
||||
ls -la backups/
|
||||
|
||||
# Check application health
|
||||
curl http://localhost:3000/api/health
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
/Users/mikicv/Documents/tt-booking/
|
||||
├── .env.production # Production environment variables
|
||||
├── docker-compose.production.yml # Production Docker Compose
|
||||
├── Dockerfile.production # Production Docker build
|
||||
├── deploy.sh # Deployment script
|
||||
├── setup-tunnel.sh # Cloudflare Tunnel setup
|
||||
├── cloudflare-tunnel-config.yml # Tunnel configuration
|
||||
├── data/ # SQLite database storage
|
||||
├── backups/ # Automated backups
|
||||
└── logs/ # Application logs
|
||||
```
|
||||
|
||||
## Default Admin Access
|
||||
|
||||
- **URL**: https://lcc-tt-booking.mikicvi.com/admin
|
||||
- **Email**: admin@lcc-tt-booking.mikicvi.com
|
||||
- **Password**: ChangeMeInProduction123! (⚠️ CHANGE THIS!)
|
||||
|
||||
## Support & Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **Container won't start**: Check `docker-compose -f docker-compose.production.yml logs`
|
||||
2. **Database issues**: Ensure `data/` directory permissions are correct
|
||||
3. **Tunnel not working**: Verify Cloudflare DNS settings and tunnel configuration
|
||||
|
||||
### Health Check:
|
||||
|
||||
The health endpoint (`/api/health`) provides:
|
||||
|
||||
- Application status
|
||||
- Database connectivity
|
||||
- Memory usage
|
||||
- Uptime information
|
||||
|
||||
### Backup Verification:
|
||||
|
||||
```bash
|
||||
# List all backups
|
||||
ls -la backups/
|
||||
|
||||
# Check latest backup size
|
||||
du -h backups/sqlite-$(date +%Y%m%d)*.db | tail -1
|
||||
```
|
||||
|
||||
## Production Features Included:
|
||||
|
||||
- ✅ Automated daily backups (30-day retention)
|
||||
- ✅ Health monitoring endpoint
|
||||
- ✅ Log rotation and management
|
||||
- ✅ Multi-stage Docker optimization
|
||||
- ✅ Security hardening
|
||||
- ✅ Rate limiting configured
|
||||
- ✅ SSL-ready with Cloudflare integration
|
||||
|
||||
Your LCC Table Tennis Booking System is ready for production! 🏓
|
||||
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { settings } from '@/lib/db/schema';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Test database connectivity
|
||||
const startTime = Date.now();
|
||||
await db.select().from(settings).limit(1);
|
||||
const dbLatency = Date.now() - startTime;
|
||||
|
||||
// Check memory usage
|
||||
const memoryUsage = process.memoryUsage();
|
||||
|
||||
return NextResponse.json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: Math.floor(process.uptime()),
|
||||
environment: process.env.NODE_ENV,
|
||||
database: {
|
||||
status: 'connected',
|
||||
latency: `${dbLatency}ms`,
|
||||
},
|
||||
memory: {
|
||||
rss: `${Math.round(memoryUsage.rss / 1024 / 1024)}MB`,
|
||||
heapUsed: `${Math.round(memoryUsage.heapUsed / 1024 / 1024)}MB`,
|
||||
heapTotal: `${Math.round(memoryUsage.heapTotal / 1024 / 1024)}MB`,
|
||||
},
|
||||
version: process.env.npm_package_version || '1.0.0',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Health check failed:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'unhealthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
error: 'Database connection failed',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Cloudflare Tunnel Configuration for LCC Table Tennis Booking
|
||||
# Domain: lcc-tt-booking.mikicvi.com
|
||||
|
||||
# Save this as ~/.cloudflared/config.yml after setting up your tunnel
|
||||
|
||||
tunnel: <your-tunnel-id>
|
||||
credentials-file: /home/pi/.cloudflared/<your-tunnel-id>.json
|
||||
|
||||
# Ingress rules
|
||||
ingress:
|
||||
# Main application
|
||||
- hostname: lcc-tt-booking.mikicvi.com
|
||||
service: http://localhost:3000
|
||||
originRequest:
|
||||
# Enable HTTP/2
|
||||
httpHostHeader: lcc-tt-booking.mikicvi.com
|
||||
# Connection settings
|
||||
connectTimeout: 30s
|
||||
tlsTimeout: 10s
|
||||
# Health checks
|
||||
proxyType: http
|
||||
# Disable chunked encoding for better compatibility
|
||||
disableChunkedEncoding: true
|
||||
|
||||
# Health check endpoint (optional, for monitoring)
|
||||
- hostname: health.lcc-tt-booking.mikicvi.com
|
||||
service: http://localhost:3000/api/health
|
||||
|
||||
# Catch-all rule (must be last)
|
||||
- service: http_status:404
|
||||
|
||||
# Optional: Logging configuration
|
||||
loglevel: info
|
||||
transport-loglevel: warn
|
||||
|
||||
# Optional: Metrics
|
||||
metrics: 0.0.0.0:2000
|
||||
|
||||
# Optional: Enable compression
|
||||
compression: gzip
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
|
||||
# LCC Table Tennis Booking - Production Deployment Script
|
||||
# Domain: lcc-tt-booking.mikicvi.com
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting production deployment for LCC Table Tennis Booking..."
|
||||
|
||||
# Check if .env.production exists
|
||||
if [ ! -f .env.production ]; then
|
||||
echo "❌ .env.production file not found!"
|
||||
echo "Please create .env.production with your production environment variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create necessary directories
|
||||
echo "📁 Creating necessary directories..."
|
||||
mkdir -p data backups logs
|
||||
|
||||
# Set proper permissions
|
||||
echo "🔒 Setting directory permissions..."
|
||||
chmod 755 data backups logs
|
||||
|
||||
# Pull latest changes (if using git)
|
||||
if [ -d ".git" ]; then
|
||||
echo "📦 Pulling latest changes..."
|
||||
git pull origin main || echo "⚠️ Git pull failed or not needed"
|
||||
fi
|
||||
|
||||
# Setup database
|
||||
echo "🛠️ Setting up the database..."
|
||||
npx tsx scripts/setup-database.ts
|
||||
|
||||
# Build and deploy with Docker Compose
|
||||
echo "🐳 Building and starting Docker containers..."
|
||||
|
||||
# Stop existing containers
|
||||
docker-compose -f docker-compose.production.yml down || echo "No existing containers to stop"
|
||||
|
||||
# Build and start containers
|
||||
docker-compose -f docker-compose.production.yml up -d --build
|
||||
|
||||
# Wait for containers to be healthy
|
||||
echo "⏳ Waiting for containers to be healthy..."
|
||||
sleep 30
|
||||
|
||||
# Check health
|
||||
echo "🔍 Checking application health..."
|
||||
for i in {1..10}; do
|
||||
if curl -f http://localhost:3000/api/health >/dev/null 2>&1; then
|
||||
echo "✅ Application is healthy!"
|
||||
break
|
||||
elif [ $i -eq 10 ]; then
|
||||
echo "❌ Application health check failed after 10 attempts"
|
||||
echo "📋 Container logs:"
|
||||
docker-compose -f docker-compose.production.yml logs tt-booking
|
||||
exit 1
|
||||
else
|
||||
echo "⏳ Attempt $i/10: Application not ready yet, waiting..."
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
# Show running containers
|
||||
echo "📊 Running containers:"
|
||||
docker-compose -f docker-compose.production.yml ps
|
||||
|
||||
# Show logs
|
||||
echo "📋 Recent application logs:"
|
||||
docker-compose -f docker-compose.production.yml logs --tail=20 tt-booking
|
||||
|
||||
echo ""
|
||||
echo "🎉 Deployment completed successfully!"
|
||||
echo ""
|
||||
echo "📊 Application Status:"
|
||||
echo " • URL: https://lcc-tt-booking.mikicvi.com"
|
||||
echo " • Health Check: http://localhost:3000/api/health"
|
||||
echo " • Container Status: $(docker-compose -f docker-compose.production.yml ps -q tt-booking | xargs docker inspect -f '{{.State.Status}}')"
|
||||
echo ""
|
||||
echo "🔧 Useful commands:"
|
||||
echo " • View logs: docker-compose -f docker-compose.production.yml logs -f tt-booking"
|
||||
echo " • Restart: docker-compose -f docker-compose.production.yml restart tt-booking"
|
||||
echo " • Stop: docker-compose -f docker-compose.production.yml down"
|
||||
echo ""
|
||||
echo "⚠️ Don't forget to:"
|
||||
echo " 1. Set up Cloudflare Tunnel to expose your application"
|
||||
echo " 2. Update your .env.production with real email credentials"
|
||||
echo " 3. Change the default admin password"
|
||||
echo ""
|
||||
@@ -0,0 +1,97 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
tt-booking:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: lcc-tt-booking
|
||||
ports:
|
||||
- '3000:3000'
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=/app/data/sqlite.db
|
||||
- NEXTAUTH_URL=https://lcc-tt-booking.mikicvi.com
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
- EMAIL_USER=${EMAIL_USER}
|
||||
- EMAIL_PASSWORD=${EMAIL_PASSWORD}
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
- PORT=3000
|
||||
- RATE_LIMIT_MAX=${RATE_LIMIT_MAX:-100}
|
||||
- RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-900000}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./backups:/app/backups
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/api/health']
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
networks:
|
||||
- lcc-network
|
||||
|
||||
# Automated backup service
|
||||
backup:
|
||||
image: alpine:latest
|
||||
container_name: lcc-backup
|
||||
volumes:
|
||||
- ./data:/data:ro
|
||||
- ./backups:/backups
|
||||
environment:
|
||||
- TZ=Europe/Dublin
|
||||
command: >
|
||||
sh -c "
|
||||
apk add --no-cache tzdata &&
|
||||
while true; do
|
||||
timestamp=$$(date +%Y%m%d-%H%M%S)
|
||||
echo \"Creating backup at $$timestamp\"
|
||||
cp /data/sqlite.db \"/backups/sqlite-$$timestamp.db\" 2>/dev/null || echo 'No database file found yet'
|
||||
# Keep backups for 30 days
|
||||
find /backups -name 'sqlite-*.db' -mtime +30 -delete 2>/dev/null || true
|
||||
echo \"Backup completed, sleeping for 24 hours\"
|
||||
sleep 86400
|
||||
done"
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- tt-booking
|
||||
networks:
|
||||
- lcc-network
|
||||
|
||||
# Log rotation service
|
||||
logrotate:
|
||||
image: alpine:latest
|
||||
container_name: lcc-logrotate
|
||||
volumes:
|
||||
- ./logs:/logs
|
||||
command: >
|
||||
sh -c "
|
||||
apk add --no-cache logrotate &&
|
||||
echo '/logs/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 30
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
}' > /etc/logrotate.d/app &&
|
||||
while true; do
|
||||
logrotate /etc/logrotate.d/app
|
||||
sleep 86400
|
||||
done"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- lcc-network
|
||||
|
||||
networks:
|
||||
lcc-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
lcc-data:
|
||||
driver: local
|
||||
lcc-backups:
|
||||
driver: local
|
||||
+1
-1
@@ -5,6 +5,6 @@ export default {
|
||||
out: './lib/db/migrations',
|
||||
driver: 'better-sqlite',
|
||||
dbCredentials: {
|
||||
url: './sqlite.db',
|
||||
url: process.env.DATABASE_URL || './data/sqlite.db',
|
||||
},
|
||||
} satisfies Config;
|
||||
|
||||
+3
-1
@@ -3,7 +3,9 @@ import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
||||
import * as schema from './schema';
|
||||
|
||||
const sqlite = new Database('./sqlite.db');
|
||||
// Get database path from environment variable or use default
|
||||
const dbPath = process.env.DATABASE_URL || './data/sqlite.db';
|
||||
const sqlite = new Database(dbPath);
|
||||
export const db = drizzle(sqlite, { schema });
|
||||
|
||||
// Only run migrations if explicitly requested
|
||||
|
||||
@@ -8,6 +8,10 @@ const nextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Enable standalone output for Docker production builds
|
||||
output: process.env.NODE_ENV === 'production' ? 'standalone' : undefined,
|
||||
// External packages for better SQLite3 compatibility (Next.js 15+ syntax)
|
||||
serverExternalPackages: ['better-sqlite3'],
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
@@ -5,7 +5,8 @@ import { sql, eq } from 'drizzle-orm';
|
||||
import { randomUUID } from 'crypto';
|
||||
import bcrypt from 'bcryptjs';
|
||||
|
||||
const sqlite = new Database('./sqlite.db');
|
||||
const dbPath = process.env.DATABASE_URL || './data/sqlite.db';
|
||||
const sqlite = new Database(dbPath);
|
||||
const db = drizzle(sqlite, { schema });
|
||||
|
||||
interface SetupOptions {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Test script to verify Irish localization settings
|
||||
console.log('🇮🇪 Testing Irish Localization Settings\n');
|
||||
|
||||
console.log('1. Week starts on Monday (Irish standard)');
|
||||
console.log(' JavaScript getDay() values:');
|
||||
console.log(' Sunday = 0, Monday = 1, ..., Saturday = 6');
|
||||
console.log(' Irish display order should be: Monday, Tuesday, ..., Sunday\n');
|
||||
|
||||
// Test date formatting
|
||||
const testDate = new Date('2025-09-25'); // This is a Thursday
|
||||
console.log('2. Date Formatting Test:');
|
||||
console.log(` Test date: ${testDate.toDateString()}`);
|
||||
console.log(
|
||||
` Irish format (en-IE): ${testDate.toLocaleDateString('en-IE', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}`
|
||||
);
|
||||
console.log(` Irish short format: ${testDate.toLocaleDateString('en-IE', { weekday: 'short' })}`);
|
||||
|
||||
console.log('\n3. Day of Week Conversion:');
|
||||
const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
for (let jsDay = 0; jsDay <= 6; jsDay++) {
|
||||
const irishDisplayOrder = jsDay === 0 ? 6 : jsDay - 1; // Convert Sunday(0) to position 6, others shift down
|
||||
console.log(` JS Day ${jsDay} (${daysOfWeek[jsDay]}) -> Irish position ${irishDisplayOrder}`);
|
||||
}
|
||||
|
||||
console.log('\n4. Week Structure for Admin Panel:');
|
||||
const irishWeekOrder = [1, 2, 3, 4, 5, 6, 0]; // Monday through Sunday in JS values
|
||||
irishWeekOrder.forEach((jsDay, displayIndex) => {
|
||||
console.log(` Display position ${displayIndex}: ${daysOfWeek[jsDay]} (JS day ${jsDay})`);
|
||||
});
|
||||
|
||||
console.log('\n✅ Irish localization configuration complete!');
|
||||
console.log('📅 Calendar will now start with Monday');
|
||||
console.log('🇮🇪 All dates will use en-IE locale format');
|
||||
console.log('⏰ 24-hour time format maintained');
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Cloudflare Tunnel Setup Script for LCC Table Tennis Booking
|
||||
# Domain: lcc-tt-booking.mikicvi.com
|
||||
|
||||
set -e
|
||||
|
||||
DOMAIN="lcc-tt-booking.mikicvi.com"
|
||||
TUNNEL_NAME="lcc-tt-booking"
|
||||
|
||||
echo "🌐 Setting up Cloudflare Tunnel for $DOMAIN"
|
||||
echo ""
|
||||
|
||||
# Check if cloudflared is installed
|
||||
if ! command -v cloudflared &> /dev/null; then
|
||||
echo "📥 Installing cloudflared..."
|
||||
|
||||
# Detect architecture
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
|
||||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64"
|
||||
elif [ "$ARCH" = "armv7l" ]; then
|
||||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm"
|
||||
else
|
||||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64"
|
||||
fi
|
||||
|
||||
# Download and install
|
||||
curl -L --output cloudflared "$CLOUDFLARED_URL"
|
||||
sudo mv cloudflared /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/cloudflared
|
||||
|
||||
echo "✅ cloudflared installed successfully!"
|
||||
else
|
||||
echo "✅ cloudflared is already installed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🔐 Please follow these steps:"
|
||||
echo ""
|
||||
echo "1. Authenticate with Cloudflare:"
|
||||
echo " cloudflared tunnel login"
|
||||
echo ""
|
||||
echo "2. Create the tunnel:"
|
||||
echo " cloudflared tunnel create $TUNNEL_NAME"
|
||||
echo ""
|
||||
echo "3. Copy the tunnel ID from the output and update cloudflare-tunnel-config.yml"
|
||||
echo ""
|
||||
echo "4. Create DNS record:"
|
||||
echo " cloudflared tunnel route dns $TUNNEL_NAME $DOMAIN"
|
||||
echo ""
|
||||
echo "5. Copy the config to cloudflared directory:"
|
||||
echo " mkdir -p ~/.cloudflared"
|
||||
echo " cp cloudflare-tunnel-config.yml ~/.cloudflared/config.yml"
|
||||
echo " # Update <your-tunnel-id> in the config file with your actual tunnel ID"
|
||||
echo ""
|
||||
echo "6. Test the tunnel:"
|
||||
echo " cloudflared tunnel run $TUNNEL_NAME"
|
||||
echo ""
|
||||
echo "7. Install as a service (optional):"
|
||||
echo " sudo cloudflared service install"
|
||||
echo " sudo systemctl enable cloudflared"
|
||||
echo " sudo systemctl start cloudflared"
|
||||
echo ""
|
||||
echo "📋 Tunnel configuration template is available in: cloudflare-tunnel-config.yml"
|
||||
echo "🚀 After setup, your app will be available at: https://$DOMAIN"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user