Redmine-Gitea-Sync/DEPLOYMENT.md

1042 lines
20 KiB
Markdown

# Production Deployment Guide
This guide covers best practices and instructions for deploying the Redmine-Gitea Sync Server in production environments.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Deployment Options](#deployment-options)
- [Docker Deployment](#docker-deployment)
- [Systemd Service](#systemd-service)
- [Nginx Reverse Proxy](#nginx-reverse-proxy)
- [Security Hardening](#security-hardening)
- [Monitoring and Logging](#monitoring-and-logging)
- [Backup and Recovery](#backup-and-recovery)
- [Performance Tuning](#performance-tuning)
---
## Prerequisites
### System Requirements
**Minimum:**
- CPU: 1 core
- RAM: 512 MB
- Storage: 1 GB
- OS: Linux (Ubuntu 20.04+, CentOS 8+, Debian 11+)
**Recommended:**
- CPU: 2 cores
- RAM: 2 GB
- Storage: 5 GB
- OS: Ubuntu 22.04 LTS or later
### Software Requirements
- Node.js 18.x or later
- npm 9.x or later
- Process manager (PM2, systemd, or Docker)
- Reverse proxy (Nginx or Apache)
- SSL certificates (Let's Encrypt recommended)
---
## Deployment Options
### Option 1: PM2 (Recommended)
Node.js process manager with monitoring, auto-restart, and clustering capabilities.
### Option 2: Docker
Easiest deployment with consistent environment.
### Option 3: Systemd Service
Native Linux service for better OS integration.
### Option 4: Bare Metal
Direct execution for maximum control.
---
## PM2 Deployment (Recommended)
### Why PM2?
PM2 is a production-ready process manager for Node.js applications with:
- Automatic restarts on crashes
- Built-in load balancing
- Log management
- Monitoring dashboard
- Startup scripts for system boot
- Zero-downtime reloads
- Memory monitoring
### Installation
```bash
# Install PM2 globally
npm install -g pm2
# Verify installation
pm2 --version
```
### Initial Setup
```bash
# Navigate to application directory
cd /opt/redmine-gitea-sync
# Install dependencies
npm install --production
# Configure ecosystem file
nano ecosystem.config.cjs
```
Update the `env` section with your credentials:
```javascript
env: {
NODE_ENV: 'production',
PORT: 3002,
REDMINE_API_URL: 'https://redmine.example.com',
REDMINE_API_KEY: 'your_api_key',
GITEA_API_URL: 'https://gitea.example.com',
GITEA_TOKEN: 'your_token',
GITEA_OWNER: 'your_username',
// ... other settings
}
```
### Start the Application
```bash
# Start with PM2
pm2 start ecosystem.config.cjs
# Save the PM2 process list
pm2 save
# Generate startup script
pm2 startup
# Follow the command output, then save again
pm2 save
```
### PM2 Management Commands
```bash
# View all processes
pm2 list
# View detailed info
pm2 show redmine-gitea-sync
# View logs in real-time
pm2 logs redmine-gitea-sync
# View last 100 lines
pm2 logs redmine-gitea-sync --lines 100
# Monitor resources
pm2 monit
# Restart application
pm2 restart redmine-gitea-sync
# Reload with zero downtime (for cluster mode)
pm2 reload redmine-gitea-sync
# Stop application
pm2 stop redmine-gitea-sync
# Delete from PM2
pm2 delete redmine-gitea-sync
# Update environment variables
pm2 restart redmine-gitea-sync --update-env
```
### Log Management
```bash
# View logs
pm2 logs redmine-gitea-sync
# Flush logs
pm2 flush
# Install log rotation module
pm2 install pm2-logrotate
# Configure log rotation
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true
```
### PM2 with Nginx
PM2 runs on localhost:3002, use Nginx as reverse proxy:
```nginx
server {
listen 80;
server_name sync.example.com;
location / {
proxy_pass http://localhost:3002;
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;
}
}
```
### Development Mode
For development with verbose logging:
```bash
# Start in development mode
pm2 start ecosystem.config.cjs --env development
# Watch for file changes (optional)
pm2 start ecosystem.config.cjs --watch
```
### Monitoring and Alerts
```bash
# Install PM2 web interface (optional)
pm2 install pm2-server-monit
# Setup email notifications on errors
pm2 install pm2-auto-pull
```
### Update Application
```bash
# Pull latest changes
cd /opt/redmine-gitea-sync
git pull
# Install new dependencies
npm install --production
# Restart with zero downtime
pm2 reload redmine-gitea-sync
# Or restart normally
pm2 restart redmine-gitea-sync
```
### Clustering (Optional)
For high-traffic scenarios, enable cluster mode:
```javascript
// In ecosystem.config.cjs
instances: 'max', // or specify number
exec_mode: 'cluster',
```
Then restart:
```bash
pm2 reload redmine-gitea-sync
```
### Security Best Practices
```bash
# Run PM2 as dedicated user
sudo useradd -r -s /bin/false pm2user
sudo chown -R pm2user:pm2user /opt/redmine-gitea-sync
# Start PM2 as that user
sudo su - pm2user -s /bin/bash -c "pm2 start /opt/redmine-gitea-sync/ecosystem.config.cjs"
# Save and setup startup
sudo su - pm2user -s /bin/bash -c "pm2 save"
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u pm2user --hp /home/pm2user
```
### Troubleshooting PM2
**Issue: Process keeps restarting**
```bash
# Check error logs
pm2 logs redmine-gitea-sync --err
# Increase restart delay in ecosystem.config.cjs
restart_delay: 5000
```
**Issue: High memory usage**
```bash
# Check memory
pm2 list
# Adjust memory limit in ecosystem.config.cjs
max_memory_restart: '500M'
```
**Issue: PM2 not starting on boot**
```bash
# Regenerate startup script
pm2 unstartup
pm2 startup
pm2 save
```
---
## Docker Deployment
### Dockerfile
Create `Dockerfile`:
```dockerfile
FROM node:18-alpine
# Install dependencies for native modules
RUN apk add --no-cache python3 make g++
# Create app directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Expose port
EXPOSE 3002
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3002/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); });"
# Start application
CMD ["node", "server.js"]
```
### Docker Compose
Create `docker-compose.yml`:
```yaml
version: '3.8'
services:
sync-server:
build: .
container_name: redmine-gitea-sync
restart: unless-stopped
ports:
- "3002:3002"
environment:
- NODE_ENV=production
- PORT=3002
env_file:
- .env
volumes:
- ./logs:/app/logs
networks:
- sync-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
sync-network:
driver: bridge
```
### Build and Run
```bash
# Build image
docker build -t redmine-gitea-sync .
# Run with docker-compose
docker-compose up -d
# View logs
docker-compose logs -f
# Stop service
docker-compose down
```
### Docker Hub Deployment
```bash
# Tag image
docker tag redmine-gitea-sync:latest your-username/redmine-gitea-sync:latest
# Push to Docker Hub
docker push your-username/redmine-gitea-sync:latest
# Pull and run on production server
docker pull your-username/redmine-gitea-sync:latest
docker run -d --name sync-server \
--env-file .env \
-p 3002:3002 \
--restart unless-stopped \
your-username/redmine-gitea-sync:latest
```
---
## Systemd Service
### Create Service File
Create `/etc/systemd/system/redmine-gitea-sync.service`:
```ini
[Unit]
Description=Redmine-Gitea Sync Server
After=network.target
[Service]
Type=simple
User=sync-user
Group=sync-user
WorkingDirectory=/opt/redmine-gitea-sync
EnvironmentFile=/opt/redmine-gitea-sync/.env
ExecStart=/usr/bin/node /opt/redmine-gitea-sync/server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=redmine-gitea-sync
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/redmine-gitea-sync/logs
[Install]
WantedBy=multi-user.target
```
### Setup Instructions
```bash
# Create dedicated user
sudo useradd -r -s /bin/false sync-user
# Create application directory
sudo mkdir -p /opt/redmine-gitea-sync
sudo chown sync-user:sync-user /opt/redmine-gitea-sync
# Copy application files
sudo cp -r . /opt/redmine-gitea-sync/
sudo chown -R sync-user:sync-user /opt/redmine-gitea-sync
# Install dependencies
cd /opt/redmine-gitea-sync
sudo -u sync-user npm ci --only=production
# Configure environment
sudo cp .env.example /opt/redmine-gitea-sync/.env
sudo nano /opt/redmine-gitea-sync/.env
sudo chown sync-user:sync-user /opt/redmine-gitea-sync/.env
sudo chmod 600 /opt/redmine-gitea-sync/.env
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable redmine-gitea-sync
sudo systemctl start redmine-gitea-sync
# Check status
sudo systemctl status redmine-gitea-sync
# View logs
sudo journalctl -u redmine-gitea-sync -f
```
### Service Management
```bash
# Start service
sudo systemctl start redmine-gitea-sync
# Stop service
sudo systemctl stop redmine-gitea-sync
# Restart service
sudo systemctl restart redmine-gitea-sync
# Check status
sudo systemctl status redmine-gitea-sync
# Enable auto-start
sudo systemctl enable redmine-gitea-sync
# Disable auto-start
sudo systemctl disable redmine-gitea-sync
# View logs
sudo journalctl -u redmine-gitea-sync -n 100 -f
```
---
## Nginx Reverse Proxy
### Basic Configuration
Create `/etc/nginx/sites-available/sync-server`:
```nginx
upstream sync_server {
server 127.0.0.1:3002;
keepalive 64;
}
server {
listen 80;
server_name sync.example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name sync.example.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/sync.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sync.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logging
access_log /var/log/nginx/sync-server-access.log;
error_log /var/log/nginx/sync-server-error.log;
# Request size limits
client_max_body_size 10M;
# Proxy settings
location / {
proxy_pass http://sync_server;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
# Health check endpoint (no auth required)
location /health {
proxy_pass http://sync_server;
access_log off;
}
}
```
### Enable Configuration
```bash
# Enable site
sudo ln -s /etc/nginx/sites-available/sync-server /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
```
### SSL with Let's Encrypt
```bash
# Install Certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d sync.example.com
# Auto-renewal is configured automatically
# Test renewal
sudo certbot renew --dry-run
```
---
## Security Hardening
### Environment Variables
```bash
# Secure .env file
chmod 600 .env
chown sync-user:sync-user .env
# Never commit .env to version control
echo ".env" >> .gitignore
```
### Firewall Configuration
```bash
# Allow only necessary ports
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
### Rate Limiting (Nginx)
Add to Nginx configuration:
```nginx
# Define rate limit zone
limit_req_zone $binary_remote_addr zone=webhook:10m rate=10r/s;
# Apply to webhook endpoints
location /webhook/ {
limit_req zone=webhook burst=20 nodelay;
proxy_pass http://sync_server;
# ... other proxy settings
}
```
### Webhook Signature Verification
Implement webhook signature verification for production:
```javascript
// Add to server.js
import crypto from 'crypto';
function verifyRedmineSignature(req) {
const signature = req.headers['x-redmine-signature'];
const secret = process.env.REDMINE_WEBHOOK_SECRET;
if (!signature || !secret) return false;
const hash = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(req.body))
.digest('hex');
return signature === hash;
}
// Add verification to webhook endpoint
app.post('/webhook/redmine', (req, res, next) => {
if (!verifyRedmineSignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
}, async (req, res) => {
// ... existing handler
});
```
### IP Whitelisting
```nginx
# In Nginx configuration
geo $allowed_ip {
default 0;
192.168.1.0/24 1; # Internal network
10.0.0.0/8 1; # Private network
# Add Redmine and Gitea server IPs
}
location /webhook/ {
if ($allowed_ip = 0) {
return 403;
}
proxy_pass http://sync_server;
}
```
---
## Monitoring and Logging
### Log Rotation
Create `/etc/logrotate.d/redmine-gitea-sync`:
```
/opt/redmine-gitea-sync/logs/*.log {
daily
rotate 14
compress
delaycompress
notifempty
missingok
create 0640 sync-user sync-user
sharedscripts
postrotate
systemctl reload redmine-gitea-sync > /dev/null 2>&1 || true
endscript
}
```
### Health Monitoring
Simple monitoring script `monitor.sh`:
```bash
#!/bin/bash
HEALTH_URL="http://localhost:3002/health"
MAX_RETRIES=3
RETRY_COUNT=0
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL)
if [ "$RESPONSE" -eq 200 ]; then
echo "$(date): Health check passed"
exit 0
fi
RETRY_COUNT=$((RETRY_COUNT + 1))
sleep 5
done
echo "$(date): Health check failed after $MAX_RETRIES attempts"
# Send alert (email, Slack, etc.)
exit 1
```
Add to crontab:
```bash
# Run health check every 5 minutes
*/5 * * * * /opt/redmine-gitea-sync/monitor.sh >> /var/log/sync-health.log 2>&1
```
### Prometheus Metrics
Install prometheus client:
```bash
npm install prom-client
```
Add metrics endpoint:
```javascript
import promClient from 'prom-client';
// Create metrics
const register = new promClient.Registry();
const syncCounter = new promClient.Counter({
name: 'sync_operations_total',
help: 'Total number of sync operations',
labelNames: ['direction', 'status'],
registers: [register]
});
const syncDuration = new promClient.Histogram({
name: 'sync_duration_seconds',
help: 'Duration of sync operations',
labelNames: ['direction'],
registers: [register]
});
// Expose metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
```
---
## Backup and Recovery
### Configuration Backup
```bash
#!/bin/bash
# backup.sh
BACKUP_DIR="/backup/sync-server"
DATE=$(date +%Y%m%d-%H%M%S)
APP_DIR="/opt/redmine-gitea-sync"
mkdir -p $BACKUP_DIR
# Backup configuration
tar -czf "$BACKUP_DIR/config-$DATE.tar.gz" \
"$APP_DIR/.env" \
"$APP_DIR/package.json" \
"$APP_DIR/package-lock.json"
# Backup logs
tar -czf "$BACKUP_DIR/logs-$DATE.tar.gz" "$APP_DIR/logs"
# Remove backups older than 30 days
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
echo "Backup completed: $DATE"
```
### Disaster Recovery
```bash
#!/bin/bash
# restore.sh
BACKUP_FILE=$1
RESTORE_DIR="/opt/redmine-gitea-sync"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup-file>"
exit 1
fi
# Stop service
sudo systemctl stop redmine-gitea-sync
# Restore configuration
tar -xzf "$BACKUP_FILE" -C /
# Reinstall dependencies
cd $RESTORE_DIR
npm ci --only=production
# Start service
sudo systemctl start redmine-gitea-sync
echo "Restore completed"
```
---
## Performance Tuning
### Node.js Optimization
```bash
# Set Node.js memory limit
NODE_OPTIONS="--max-old-space-size=2048" node server.js
```
Update systemd service:
```ini
[Service]
Environment="NODE_ENV=production"
Environment="NODE_OPTIONS=--max-old-space-size=2048"
```
### Database Connection Pool
If using a database for caching (future):
```javascript
const pool = {
min: 2,
max: 10,
acquireTimeoutMillis: 30000,
idleTimeoutMillis: 30000
};
```
### HTTP Keep-Alive
Already configured in axios clients:
```javascript
const http = require('http');
const https = require('https');
const httpAgent = new http.Agent({ keepAlive: true });
const httpsAgent = new https.Agent({ keepAlive: true });
const client = axios.create({
httpAgent,
httpsAgent
});
```
### Nginx Tuning
```nginx
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Enable caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
# Compression
gzip on;
gzip_vary on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript;
}
```
---
## Troubleshooting Production Issues
### High Memory Usage
```bash
# Check memory usage
ps aux | grep node
# Restart service
sudo systemctl restart redmine-gitea-sync
# Monitor memory
watch -n 5 'ps aux | grep node'
```
### Connection Issues
```bash
# Test Redmine connection
curl -H "X-Redmine-API-Key: YOUR_KEY" https://redmine.example.com/issues.json
# Test Gitea connection
curl -H "Authorization: token YOUR_TOKEN" https://gitea.example.com/api/v1/repos
# Check DNS resolution
nslookup redmine.example.com
nslookup gitea.example.com
```
### Webhook Not Triggering
```bash
# Check Nginx logs
sudo tail -f /var/log/nginx/sync-server-access.log
sudo tail -f /var/log/nginx/sync-server-error.log
# Check application logs
sudo journalctl -u redmine-gitea-sync -f
# Test webhook manually
curl -X POST http://localhost:3002/webhook/redmine \
-H "Content-Type: application/json" \
-d @test-payload.json
```
### Performance Issues
```bash
# Check CPU usage
top -p $(pgrep -f "node.*server.js")
# Check network connections
netstat -an | grep :3002
# Analyze slow requests
tail -f /var/log/nginx/sync-server-access.log | awk '{print $4, $7}'
```
---
## Production Checklist
- [ ] SSL certificates configured and auto-renewal setup
- [ ] Firewall rules configured
- [ ] Environment variables secured (chmod 600 .env)
- [ ] Service configured to start on boot
- [ ] Log rotation configured
- [ ] Monitoring and alerting setup
- [ ] Backup strategy implemented
- [ ] Health checks configured
- [ ] Webhook signatures verified
- [ ] IP whitelisting configured
- [ ] Documentation updated with production URLs
- [ ] Disaster recovery plan documented
- [ ] Performance testing completed
- [ ] Security audit completed
---
## Support and Maintenance
### Regular Maintenance Tasks
**Weekly:**
- Check logs for errors
- Verify webhooks are functioning
- Review sync statistics
**Monthly:**
- Update dependencies: `npm update`
- Review and rotate logs
- Test disaster recovery procedures
- Check SSL certificate expiration
**Quarterly:**
- Security audit
- Performance review
- Update documentation
- Backup verification
### Getting Help
For production issues:
1. Check logs: `sudo journalctl -u redmine-gitea-sync -n 100`
2. Verify configuration: `GET /status`
3. Test connectivity to Redmine and Gitea
4. Review recent changes
5. Consult documentation
6. Open issue in repository with logs and configuration details