feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================
|
||||
# XFlow Server Deployment Script
|
||||
# ==========================================
|
||||
|
||||
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
|
||||
|
||||
# Configuration
|
||||
COMPOSE_FILE="docker-compose.prod.yml"
|
||||
BACKUP_DIR="./backups"
|
||||
LOG_FILE="./logs/deploy-$(date +%Y%m%d-%H%M%S).log"
|
||||
|
||||
# ==========================================
|
||||
# Helper Functions
|
||||
# ==========================================
|
||||
|
||||
log() {
|
||||
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Pre-deployment Checks
|
||||
# ==========================================
|
||||
|
||||
check_prerequisites() {
|
||||
log "Checking prerequisites..."
|
||||
|
||||
# Check Docker
|
||||
if ! command -v docker &> /dev/null; then
|
||||
error "Docker is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check Docker Compose
|
||||
if ! docker compose version &> /dev/null; then
|
||||
error "Docker Compose is not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check .env file
|
||||
if [ ! -f .env ]; then
|
||||
error ".env file not found. Please create it from .env.example"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check compose file
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
error "Compose file $COMPOSE_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "Prerequisites check passed"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Backup
|
||||
# ==========================================
|
||||
|
||||
create_backup() {
|
||||
log "Creating backup..."
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Backup database
|
||||
if docker compose ps postgres | grep -q "running"; then
|
||||
log "Backing up PostgreSQL database..."
|
||||
docker compose exec -T postgres pg_dump -U gitea gitea > "$BACKUP_DIR/db-$(date +%Y%m%d-%H%M%S).sql" 2>/dev/null || warning "Database backup failed"
|
||||
fi
|
||||
|
||||
success "Backup completed"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Deployment
|
||||
# ==========================================
|
||||
|
||||
deploy() {
|
||||
log "Starting deployment..."
|
||||
|
||||
# Pull latest images
|
||||
log "Pulling latest images..."
|
||||
docker compose -f "$COMPOSE_FILE" pull
|
||||
|
||||
# Deploy with zero downtime
|
||||
log "Deploying services..."
|
||||
docker compose -f "$COMPOSE_FILE" up -d --remove-orphans
|
||||
|
||||
success "Deployment completed"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Health Checks
|
||||
# ==========================================
|
||||
|
||||
health_check() {
|
||||
log "Running health checks..."
|
||||
|
||||
local max_attempts=30
|
||||
local attempt=1
|
||||
|
||||
# Wait for services to start
|
||||
sleep 10
|
||||
|
||||
# Check API
|
||||
log "Checking API health..."
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if curl -sf http://localhost:8080/health > /dev/null 2>&1; then
|
||||
success "API is healthy"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
error "API health check failed after $max_attempts attempts"
|
||||
show_logs
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Attempt $attempt/$max_attempts: API not ready yet..."
|
||||
sleep 5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
# Check Gitea
|
||||
log "Checking Gitea health..."
|
||||
attempt=1
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if curl -sf http://localhost:3000/api/healthz > /dev/null 2>&1; then
|
||||
success "Gitea is healthy"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ $attempt -eq $max_attempts ]; then
|
||||
warning "Gitea health check failed (may still be initializing)"
|
||||
break
|
||||
fi
|
||||
|
||||
log "Attempt $attempt/$max_attempts: Gitea not ready yet..."
|
||||
sleep 5
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
success "All health checks passed"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Cleanup
|
||||
# ==========================================
|
||||
|
||||
cleanup() {
|
||||
log "Cleaning up..."
|
||||
|
||||
# Remove old backups (keep last 7)
|
||||
if [ -d "$BACKUP_DIR" ]; then
|
||||
ls -t "$BACKUP_DIR"/*.sql 2>/dev/null | tail -n +8 | xargs -r rm
|
||||
ls -t "$BACKUP_DIR"/*.tar.gz 2>/dev/null | tail -n +8 | xargs -r rm
|
||||
fi
|
||||
|
||||
# Clean Docker
|
||||
docker system prune -f
|
||||
docker volume prune -f
|
||||
|
||||
success "Cleanup completed"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Rollback
|
||||
# ==========================================
|
||||
|
||||
rollback() {
|
||||
error "Deployment failed! Rolling back..."
|
||||
|
||||
# Get previous image
|
||||
local previous_image=$(docker images ghcr.io/*/xflow-api --format "{{.Repository}}:{{.Tag}}" | grep -v latest | head -1)
|
||||
|
||||
if [ -n "$previous_image" ]; then
|
||||
log "Reverting to: $previous_image"
|
||||
docker compose -f "$COMPOSE_FILE" stop api
|
||||
docker compose -f "$COMPOSE_FILE" rm -f api
|
||||
docker run -d --name xflow-api-rollback \
|
||||
--network xflow-network \
|
||||
-e NODE_ENV=production \
|
||||
-e PORT=8080 \
|
||||
-p 8080:8080 \
|
||||
"$previous_image"
|
||||
success "Rollback completed"
|
||||
else
|
||||
error "No previous image found for rollback"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Show Logs
|
||||
# ==========================================
|
||||
|
||||
show_logs() {
|
||||
log "Showing recent logs..."
|
||||
docker compose -f "$COMPOSE_FILE" logs --tail=50
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Main
|
||||
# ==========================================
|
||||
|
||||
main() {
|
||||
log "======================================"
|
||||
log "XFlow Server Deployment"
|
||||
log "======================================"
|
||||
|
||||
# Create logs directory
|
||||
mkdir -p logs
|
||||
|
||||
# Run deployment steps
|
||||
check_prerequisites
|
||||
create_backup
|
||||
|
||||
# Deploy with error handling
|
||||
if deploy; then
|
||||
if health_check; then
|
||||
cleanup
|
||||
success "======================================"
|
||||
success "Deployment completed successfully!"
|
||||
success "======================================"
|
||||
log "API: http://localhost:8080"
|
||||
log "Gitea: http://localhost:3000"
|
||||
else
|
||||
rollback
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
rollback
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
@@ -0,0 +1,281 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================
|
||||
# VPS Setup Script for XFlow Server
|
||||
# Run this on your fresh VPS
|
||||
# ==========================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${BLUE}[SETUP]${NC} $1"; }
|
||||
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# ==========================================
|
||||
# Configuration
|
||||
# ==========================================
|
||||
DEPLOY_DIR="/opt/xflow-server"
|
||||
REPO_URL="" # Will be set interactively
|
||||
DOMAIN="" # Will be set interactively
|
||||
|
||||
# ==========================================
|
||||
# System Update
|
||||
# ==========================================
|
||||
update_system() {
|
||||
log "Updating system packages..."
|
||||
apt update && apt upgrade -y
|
||||
apt install -y curl wget git nano htop ufw fail2ban
|
||||
success "System updated"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Install Docker
|
||||
# ==========================================
|
||||
install_docker() {
|
||||
log "Installing Docker..."
|
||||
|
||||
if command -v docker &> /dev/null; then
|
||||
warning "Docker already installed, skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sh get-docker.sh
|
||||
rm get-docker.sh
|
||||
|
||||
usermod -aG docker $USER
|
||||
|
||||
success "Docker installed"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Setup Firewall
|
||||
# ==========================================
|
||||
setup_firewall() {
|
||||
log "Configuring firewall..."
|
||||
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
|
||||
ufw allow ssh
|
||||
ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw allow 3000/tcp # Gitea
|
||||
ufw allow 8080/tcp # API
|
||||
ufw allow 222/tcp # Gitea SSH
|
||||
|
||||
ufw --force enable
|
||||
|
||||
success "Firewall configured"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Setup Fail2Ban
|
||||
# ==========================================
|
||||
setup_fail2ban() {
|
||||
log "Configuring Fail2Ban..."
|
||||
|
||||
cat > /etc/fail2ban/jail.local << 'EOF'
|
||||
[DEFAULT]
|
||||
bantime = 3600
|
||||
findtime = 600
|
||||
maxretry = 3
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
logpath = /var/log/auth.log
|
||||
maxretry = 3
|
||||
EOF
|
||||
|
||||
systemctl restart fail2ban
|
||||
success "Fail2Ban configured"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Setup Deployment Directory
|
||||
# ==========================================
|
||||
setup_deploy_dir() {
|
||||
log "Setting up deployment directory..."
|
||||
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
cd "$DEPLOY_DIR"
|
||||
|
||||
# Clone repository
|
||||
if [ -n "$REPO_URL" ]; then
|
||||
git clone "$REPO_URL" .
|
||||
fi
|
||||
|
||||
# Create necessary directories
|
||||
mkdir -p backups logs
|
||||
|
||||
success "Deployment directory ready"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Setup SSL with Let's Encrypt
|
||||
# ==========================================
|
||||
setup_ssl() {
|
||||
log "Setting up SSL certificates..."
|
||||
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
warning "No domain specified, skipping SSL setup"
|
||||
return
|
||||
fi
|
||||
|
||||
apt install -y certbot
|
||||
|
||||
# Obtain certificates
|
||||
certbot certonly --standalone -d "$DOMAIN" --agree-tos --non-interactive --email admin@$DOMAIN || {
|
||||
warning "SSL setup failed, continuing without SSL"
|
||||
return
|
||||
}
|
||||
|
||||
# Auto-renewal cron job
|
||||
(crontab -l 2>/dev/null; echo "0 12 * * * /usr/bin/certbot renew --quiet") | crontab -
|
||||
|
||||
success "SSL certificates configured"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Create Environment File
|
||||
# ==========================================
|
||||
setup_env() {
|
||||
log "Creating environment configuration..."
|
||||
|
||||
cd "$DEPLOY_DIR/server"
|
||||
|
||||
# Generate secrets
|
||||
JWT_SECRET=$(openssl rand -base64 32)
|
||||
POSTGRES_PASSWORD=$(openssl rand -base64 24)
|
||||
GITEA_SECRET_KEY=$(openssl rand -base64 32)
|
||||
WEBDAV_PASSWORD=$(openssl rand -base64 16)
|
||||
|
||||
cat > .env << EOF
|
||||
# Domain configuration
|
||||
GITEA_DOMAIN=${DOMAIN:-localhost}
|
||||
|
||||
# Gitea Configuration
|
||||
GITEA_TOKEN=
|
||||
GITEA_SECRET_KEY=$GITEA_SECRET_KEY
|
||||
|
||||
# Security
|
||||
JWT_SECRET=$JWT_SECRET
|
||||
|
||||
# WebDAV
|
||||
WEBDAV_USER=admin
|
||||
WEBDAV_PASSWORD=$WEBDAV_PASSWORD
|
||||
|
||||
# Database
|
||||
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
|
||||
# API Configuration
|
||||
API_PORT=8080
|
||||
EOF
|
||||
|
||||
success "Environment file created"
|
||||
warning "Please edit .env file and add your GITEA_TOKEN after first Gitea setup"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Create Systemd Service
|
||||
# ==========================================
|
||||
setup_systemd() {
|
||||
log "Creating systemd service..."
|
||||
|
||||
cat > /etc/systemd/system/xflow-server.service << EOF
|
||||
[Unit]
|
||||
Description=XFlow Server
|
||||
Requires=docker.service
|
||||
After=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
WorkingDirectory=$DEPLOY_DIR/server
|
||||
ExecStart=/usr/bin/docker compose -f docker-compose.prod.yml up -d
|
||||
ExecStop=/usr/bin/docker compose -f docker-compose.prod.yml down
|
||||
TimeoutStartSec=0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable xflow-server.service
|
||||
|
||||
success "Systemd service created"
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Interactive Setup
|
||||
# ==========================================
|
||||
interactive_setup() {
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo "XFlow Server VPS Setup"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
read -p "Enter your GitHub repository URL (or leave empty to skip clone): " REPO_URL
|
||||
read -p "Enter your domain name (or leave empty for local setup): " DOMAIN
|
||||
|
||||
echo ""
|
||||
log "Starting setup with:"
|
||||
log " Repository: ${REPO_URL:-None}"
|
||||
log " Domain: ${DOMAIN:-localhost}"
|
||||
echo ""
|
||||
read -p "Continue? (y/n): " confirm
|
||||
|
||||
if [ "$confirm" != "y" ]; then
|
||||
error "Setup cancelled"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# Main
|
||||
# ==========================================
|
||||
main() {
|
||||
interactive_setup
|
||||
|
||||
log "Starting VPS setup..."
|
||||
|
||||
update_system
|
||||
install_docker
|
||||
setup_firewall
|
||||
setup_fail2ban
|
||||
setup_deploy_dir
|
||||
setup_ssl
|
||||
setup_env
|
||||
setup_systemd
|
||||
|
||||
echo ""
|
||||
success "======================================"
|
||||
success "VPS Setup Complete!"
|
||||
success "======================================"
|
||||
echo ""
|
||||
log "Next steps:"
|
||||
echo " 1. Edit $DEPLOY_DIR/server/.env with your settings"
|
||||
echo " 2. Run: cd $DEPLOY_DIR/server && docker compose -f docker-compose.prod.yml up -d"
|
||||
echo " 3. Open Gitea at http://${DOMAIN:-your-server-ip}:3000"
|
||||
echo " 4. Create admin account and API token"
|
||||
echo " 5. Add GITEA_TOKEN to .env file"
|
||||
echo " 6. Restart: systemctl restart xflow-server"
|
||||
echo ""
|
||||
log "Useful commands:"
|
||||
echo " - View logs: docker compose -f docker-compose.prod.yml logs -f"
|
||||
echo " - Restart: systemctl restart xflow-server"
|
||||
echo " - Update: cd $DEPLOY_DIR && git pull && systemctl restart xflow-server"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user