From 61d8656da52c9252f2ec72e7e80b7151aa855213 Mon Sep 17 00:00:00 2001 From: thecyberlearn Date: Sat, 30 Aug 2025 09:24:15 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20Restore=20multi-project=20deploymen?= =?UTF-8?q?t=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restored MULTI_PROJECT_SETUP.md with research-based approach - Restored deploy-project.sh with GitHub repo name extraction - Restored webhook-router.py for centralized webhook handling - Cleaned up confusing documentation files - Multi-project system supports unlimited Django projects - Auto-extracts repo names: my-app.git โ†’ /var/www/my-app/ - Path-based routing: yourip.com/project1/, yourip.com/project2/ - Follows 2024 Django deployment best practices --- DEPLOYMENT_GUIDE.md | 146 ------------- HOSTINGER_DEPLOYMENT.md | 89 -------- MULTI_PROJECT_SETUP.md | 238 ++++++++++++++++++++ PRODUCTION_DEPLOYMENT.md | 337 ---------------------------- QUICK_START.md | 223 ------------------- SIMPLE_DEPLOY.md | 34 +++ TROUBLESHOOTING.md | 398 ---------------------------------- deploy/deploy-project.sh | 300 +++++++++++++++++++++++++ deploy/setup-multi-webhook.sh | 168 ++++++++++++++ deploy/webhook-receiver.py | 248 +++++++++++++++++++++ deploy/webhook-router.py | 350 ++++++++++++++++++++++++++++++ deploy/webhook-service.sh | 162 ++++++++++++++ setup-auto-deploy.sh | 27 +++ setup-django-user.sh | 86 ++++++++ 14 files changed, 1613 insertions(+), 1193 deletions(-) delete mode 100644 DEPLOYMENT_GUIDE.md delete mode 100644 HOSTINGER_DEPLOYMENT.md create mode 100644 MULTI_PROJECT_SETUP.md delete mode 100644 PRODUCTION_DEPLOYMENT.md delete mode 100644 QUICK_START.md create mode 100644 SIMPLE_DEPLOY.md delete mode 100644 TROUBLESHOOTING.md create mode 100755 deploy/deploy-project.sh create mode 100755 deploy/setup-multi-webhook.sh create mode 100644 deploy/webhook-receiver.py create mode 100755 deploy/webhook-router.py create mode 100644 deploy/webhook-service.sh create mode 100755 setup-auto-deploy.sh create mode 100755 setup-django-user.sh diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md deleted file mode 100644 index 9a8cebf..0000000 --- a/DEPLOYMENT_GUIDE.md +++ /dev/null @@ -1,146 +0,0 @@ -# VPS Deployment Guide - -## Prerequisites - -Before starting, make sure you have: -- [ ] Ubuntu 20.04+ VPS with root/sudo access -- [ ] Your VPS IP address -- [ ] SSH key or password for VPS access -- [ ] Domain name (optional, can use IP address) - -## Step 1: Prepare Local Files - -Clean up your local project: -```bash -rm -rf venv/ -rm -f db.sqlite3 -rm -rf staticfiles/ -rm -f requirements_full.txt -``` - -## Step 2: Upload to VPS - -### Option A: Using SCP (if you have the files locally) -```bash -# Replace YOUR_VPS_IP with your actual IP -scp -r . root@YOUR_VPS_IP:/home/ubuntu/django-demo -``` - -### Option B: Using Git (recommended) -```bash -# On your VPS, clone the repository -ssh root@YOUR_VPS_IP -cd /home/ubuntu -git clone YOUR_REPO_URL django-demo -``` - -## Step 3: Run Deployment Script - -SSH into your VPS and run: -```bash -ssh root@YOUR_VPS_IP -cd /home/ubuntu/django-demo -sudo bash deploy/deploy.sh -``` - -## Step 4: Configure Environment - -Edit the `.env` file: -```bash -sudo nano /home/ubuntu/django-demo/.env -``` - -Set these values: -```env -SECRET_KEY=your-super-secret-key-here-make-it-long-and-random -DEBUG=False -ALLOWED_HOSTS=your-domain.com,www.your-domain.com,YOUR_VPS_IP -DATABASE_URL=postgresql://demo_user:your_password@localhost:5432/demo_db -SECURE_SSL_REDIRECT=False # Set to True after SSL setup -``` - -## Step 5: Update Domain in Nginx - -Edit nginx configuration: -```bash -sudo nano /etc/nginx/sites-available/django-demo -``` - -Replace `yourdomain.com` with your actual domain or IP. - -## Step 6: Restart Services - -```bash -sudo systemctl restart django-demo -sudo systemctl restart nginx -``` - -## Step 7: Test Deployment - -Check if everything is working: -```bash -# Check service status -sudo systemctl status django-demo -sudo systemctl status nginx - -# View logs if there are issues -sudo journalctl -u django-demo -f -``` - -Visit your website: `http://YOUR_VPS_IP` or `http://your-domain.com` - -## Troubleshooting - -### Common Issues: - -1. **Service won't start**: - ```bash - sudo journalctl -u django-demo -f - ``` - -2. **Static files not loading**: - ```bash - cd /home/ubuntu/django-demo - source venv/bin/activate - python manage.py collectstatic --noinput - sudo systemctl restart django-demo - ``` - -3. **Database errors**: - - Check PostgreSQL is running: `sudo systemctl status postgresql` - - Verify database settings in `.env` - -4. **Permission errors**: - ```bash - sudo chown -R www-data:www-data /home/ubuntu/django-demo - ``` - -## Optional: SSL Certificate (Let's Encrypt) - -```bash -sudo apt install certbot python3-certbot-nginx -sudo certbot --nginx -d your-domain.com -``` - -After SSL setup, update `.env`: -```env -SECURE_SSL_REDIRECT=True -``` - -## Management Commands - -```bash -# Restart Django -sudo systemctl restart django-demo - -# View logs -sudo journalctl -u django-demo -f - -# Update application -sudo bash /home/ubuntu/django-demo/deploy/update.sh - -# Access Django shell -cd /home/ubuntu/django-demo -source venv/bin/activate -python manage.py shell -``` \ No newline at end of file diff --git a/HOSTINGER_DEPLOYMENT.md b/HOSTINGER_DEPLOYMENT.md deleted file mode 100644 index 01506d0..0000000 --- a/HOSTINGER_DEPLOYMENT.md +++ /dev/null @@ -1,89 +0,0 @@ -# Hostinger VPS Deployment Commands - -## VPS Details -- **IP**: 69.62.81.168 -- **SSH**: ssh root@69.62.81.168 -- **Provider**: Hostinger - -## Step-by-Step Deployment - -### 1. Connect to VPS -```bash -ssh root@69.62.81.168 -``` - -### 2. Clone Repository -```bash -cd /home/ubuntu || cd /root -mkdir -p /home/ubuntu -cd /home/ubuntu -git clone https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git django-demo -cd django-demo -``` - -### 3. Run Deployment Script -```bash -sudo bash deploy/deploy.sh -``` - -### 4. Configure Environment -```bash -sudo nano .env -``` - -**Set these values:** -```env -SECRET_KEY=your-super-secret-key-change-this-to-something-long-and-random -DEBUG=False -ALLOWED_HOSTS=69.62.81.168,localhost,127.0.0.1 -DATABASE_URL=postgresql://demo_user:your_secure_password@localhost:5432/demo_db -SECURE_SSL_REDIRECT=False -``` - -### 5. Update Nginx Configuration -```bash -sudo nano /etc/nginx/sites-available/django-demo -``` - -Replace `yourdomain.com` with `69.62.81.168` in the server_name line. - -### 6. Restart Services -```bash -sudo systemctl restart django-demo -sudo systemctl restart nginx -``` - -### 7. Check Status -```bash -sudo systemctl status django-demo -sudo systemctl status nginx -``` - -### 8. Test Application -Visit: http://69.62.81.168 - -## Troubleshooting Commands - -```bash -# View Django logs -sudo journalctl -u django-demo -f - -# View Nginx logs -sudo tail -f /var/log/nginx/error.log - -# Restart services -sudo systemctl restart django-demo nginx - -# Check if ports are open -sudo netstat -tlnp | grep :80 -sudo netstat -tlnp | grep :8000 -``` - -## Creating Admin User -```bash -cd /home/ubuntu/django-demo -source venv/bin/activate -python manage.py createsuperuser -``` - -Access admin at: http://69.62.81.168/admin \ No newline at end of file diff --git a/MULTI_PROJECT_SETUP.md b/MULTI_PROJECT_SETUP.md new file mode 100644 index 0000000..6005a1a --- /dev/null +++ b/MULTI_PROJECT_SETUP.md @@ -0,0 +1,238 @@ +# Multi-Project Django VPS Setup Guide + +## ๐Ÿš€ Research-Based Multi-Project System + +This system follows **2024 Django deployment best practices** and supports unlimited Django projects on a single VPS with automatic GitHub repo name extraction. + +## โœจ Features + +- โœ… **Auto-Extract GitHub Repo Names** โ†’ No manual project naming +- โœ… **Unlimited Django Projects** โ†’ `/var/www/project1/`, `/var/www/project2/`, etc. +- โœ… **Path-Based Routing** โ†’ `yourip.com/project1/`, `yourip.com/project2/` +- โœ… **Centralized Webhook Router** โ†’ One webhook URL handles all projects +- โœ… **Individual Services** โ†’ Separate `gunicorn-project.service` per project +- โœ… **Auto-Discovery** โ†’ Automatically detects existing Django projects + +## ๐Ÿ—๏ธ Architecture + +``` +VPS Structure: +/var/www/ +โ”œโ”€โ”€ hostinger-django-demo/ โ† From: github.com/user/hostinger-django-demo +โ”œโ”€โ”€ my-blog-site/ โ† From: github.com/user/my-blog-site +โ”œโ”€โ”€ ecommerce-app/ โ† From: github.com/user/ecommerce-app +โ””โ”€โ”€ webhook-manager/ โ† Centralized webhook router + +URLs: +http://YOUR_IP/ โ†’ Project list & health check +http://YOUR_IP/hostinger-django-demo/ โ†’ First project +http://YOUR_IP/my-blog-site/ โ†’ Second project +http://YOUR_IP/ecommerce-app/ โ†’ Third project +http://YOUR_IP/webhook โ†’ GitHub webhooks (all projects) +http://YOUR_IP/deploy-status โ†’ Deployment status (all projects) +``` + +## ๐Ÿš€ Quick Setup (3 Commands) + +### Step 1: Upload & Setup Multi-Project System +```bash +# Upload this project to your VPS (django user home) +scp -r . akvps:/home/django/hostinger-django-demo + +# Setup multi-project webhook system +ssh akvps +cd /home/django/hostinger-django-demo +sudo bash deploy/setup-multi-webhook.sh +``` + +### Step 2: Deploy Your First Project +```bash +# Deploy current project (auto-extracts name: hostinger-django-demo) +sudo bash deploy/deploy-project.sh https://github.com/thecyberlearn/hostinger-django-demo.git + +# Your project is now live at: http://YOUR_IP/hostinger-django-demo/ +``` + +### Step 3: Configure GitHub Webhook +```bash +# Use the webhook secret from Step 1 +# Go to: https://github.com/thecyberlearn/hostinger-django-demo/settings/hooks +# Add webhook: +# - URL: http://YOUR_IP/webhook +# - Secret: [from setup script] +# - Content-type: application/json +# - Events: Just push event +``` + +## ๐Ÿ“ฆ Add More Projects + +### Deploy Additional Projects +```bash +# Each new project gets deployed automatically with correct naming +sudo bash deploy/deploy-project.sh https://github.com/yourusername/my-blog-app.git +sudo bash deploy/deploy-project.sh https://github.com/yourusername/portfolio-site.git +sudo bash deploy/deploy-project.sh https://github.com/yourusername/api-backend.git + +# Projects auto-deploy to: +# http://YOUR_IP/my-blog-app/ +# http://YOUR_IP/portfolio-site/ +# http://YOUR_IP/api-backend/ +``` + +### GitHub Webhook Configuration +**Single webhook handles ALL projects!** +- Each repo needs the SAME webhook URL: `http://YOUR_IP/webhook` +- Same secret for all repositories +- Router automatically detects which project to deploy based on repo name + +## ๐Ÿ”ง Management Commands + +### Check All Projects Status +```bash +curl http://YOUR_IP/deploy-status +# Shows all projects, services, and commit hashes +``` + +### Individual Project Management +```bash +# Check specific project service +systemctl status gunicorn-hostinger-django-demo.service +systemctl status gunicorn-my-blog-app.service + +# View logs for specific project +journalctl -u gunicorn-hostinger-django-demo.service -f +journalctl -u gunicorn-my-blog-app.service -f + +# Restart specific project +systemctl restart gunicorn-hostinger-django-demo.service +``` + +### Webhook Router Management +```bash +# Check webhook router status +systemctl status django-webhook-router.service + +# View webhook router logs +journalctl -u django-webhook-router.service -f + +# Restart webhook router +systemctl restart django-webhook-router.service +``` + +## ๐Ÿงช Testing Auto-Deploy + +### Test Project Deployment +```bash +# Make a change in any project repository +echo "# Multi-project test" >> README.md +git add . +git commit -m "Test multi-project auto-deploy" +git push origin main + +# Watch deployment happen +journalctl -u django-webhook-router.service -f +``` + +### Verify Deployment +```bash +# Check if all services are running +curl http://YOUR_IP/deploy-status + +# Test each project URL +curl http://YOUR_IP/hostinger-django-demo/ +curl http://YOUR_IP/my-blog-app/ +curl http://YOUR_IP/portfolio-site/ +``` + +## ๐Ÿ” Monitoring & Logs + +### Centralized Monitoring +```bash +# All projects status +curl http://YOUR_IP/deploy-status | jq + +# Webhook router health +curl http://YOUR_IP/health | jq + +# Project discovery +curl http://YOUR_IP/health +``` + +### Log Locations +```bash +# Webhook router logs +tail -f /var/log/django/webhook-router.log + +# Individual project logs +journalctl -u gunicorn-PROJECT_NAME.service -f + +# Nginx logs +tail -f /var/log/nginx/access.log +tail -f /var/log/nginx/error.log +``` + +## ๐ŸŽฏ Benefits + +### For Developers +- **No Manual Configuration** โ†’ Just provide GitHub URL +- **Unlimited Projects** โ†’ Add as many Django apps as you want +- **Consistent Naming** โ†’ Uses actual repository names +- **Single Webhook** โ†’ One URL handles all your repositories + +### For Operations +- **Industry Standard** โ†’ Follows 2024 Django deployment best practices +- **Resource Efficient** โ†’ Shared nginx, individual gunicorn processes +- **Easy Monitoring** โ†’ Centralized status and logging +- **Auto-Discovery** โ†’ Automatically detects existing projects + +## ๐Ÿšจ Troubleshooting + +### Project Not Deploying +```bash +# Check if project was discovered +curl http://YOUR_IP/health + +# Check project service +systemctl status gunicorn-PROJECT_NAME.service + +# Check deployment logs +journalctl -u django-webhook-router.service -f +``` + +### Webhook Not Triggering +```bash +# Verify webhook secret in GitHub matches +cat /var/www/webhook-manager/.env + +# Check webhook router logs +journalctl -u django-webhook-router.service -f + +# Test webhook manually +curl -X POST http://YOUR_IP/webhook \ + -H "Content-Type: application/json" \ + -d '{"test": "webhook"}' +``` + +### Service Issues +```bash +# Reload systemd if services don't start +systemctl daemon-reload + +# Check nginx configuration +nginx -t + +# Restart all services +systemctl restart django-webhook-router.service +systemctl restart nginx +``` + +## ๐ŸŽ‰ Success! + +You now have a **production-ready multi-project Django VPS** that: +- Auto-extracts GitHub repository names +- Supports unlimited Django projects +- Auto-deploys on git push (like Render/Vercel) +- Follows industry best practices for 2024 +- Scales effortlessly as you add more projects + +**Just push to GitHub and watch your projects deploy automatically!** ๐Ÿš€ \ No newline at end of file diff --git a/PRODUCTION_DEPLOYMENT.md b/PRODUCTION_DEPLOYMENT.md deleted file mode 100644 index 4b6e5f3..0000000 --- a/PRODUCTION_DEPLOYMENT.md +++ /dev/null @@ -1,337 +0,0 @@ -# Production Django Deployment Guide - -A comprehensive, battle-tested guide for deploying Django applications on VPS with proper security and performance. - -## ๐ŸŽฏ **Overview** - -This guide follows Django best practices and avoids common pitfalls we encountered: - -- โŒ **Avoid**: Running as root, wrong directories, TCP sockets, hardcoded paths -- โœ… **Use**: Non-root user, `/var/www/`, Unix sockets, proper systemd configuration - -## ๐Ÿ“‹ **Prerequisites** - -- Ubuntu 20.04+ VPS with root access -- Domain name (optional, can use IP address) -- Git repository with Django project - -## ๐Ÿš€ **Step 1: VPS Initial Setup** - -### Connect and Update System -```bash -ssh root@YOUR_VPS_IP -apt update && apt upgrade -y -``` - -### Install Required Packages -```bash -apt install -y python3 python3-pip python3-venv python3-dev \ - nginx postgresql postgresql-contrib libpq-dev \ - build-essential curl git ufw -``` - -### Create Non-Root User -```bash -adduser django --disabled-password --gecos '' -usermod -aG sudo django -``` - -### Configure SSH for New User (Optional) -```bash -mkdir -p /home/django/.ssh -cp /root/.ssh/authorized_keys /home/django/.ssh/ -chown -R django:django /home/django/.ssh -chmod 700 /home/django/.ssh -chmod 600 /home/django/.ssh/authorized_keys -``` - -## ๐Ÿ—‚๏ธ **Step 2: Project Setup** - -### Create Project Directory -```bash -mkdir -p /var/www -chown django:www-data /var/www -``` - -### Clone Project (as django user) -```bash -sudo -u django bash -c " -cd /var/www -git clone YOUR_REPO_URL django-app -cd django-app -" -``` - -### Create Virtual Environment -```bash -sudo -u django bash -c " -cd /var/www/django-app -python3 -m venv venv -source venv/bin/activate -pip install --upgrade pip -pip install -r requirements.txt -" -``` - -## โš™๏ธ **Step 3: Django Configuration** - -### Environment Variables -```bash -sudo -u django bash -c " -cd /var/www/django-app -cp .env.example .env -# Edit .env with production settings -" -``` - -**Required .env settings:** -```env -SECRET_KEY=your-super-secret-key-generate-new-one -DEBUG=False -ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com,YOUR_VPS_IP -DATABASE_URL=postgresql://dbuser:dbpassword@localhost/dbname -SECURE_SSL_REDIRECT=False # Set True after SSL setup -``` - -### Run Django Setup -```bash -sudo -u django bash -c " -cd /var/www/django-app -source venv/bin/activate -python manage.py collectstatic --noinput -python manage.py migrate -python manage.py createsuperuser -" -``` - -## ๐Ÿ”ง **Step 4: Gunicorn with systemd** - -### Create Gunicorn Socket -```bash -cat > /etc/systemd/system/gunicorn.socket << 'EOF' -[Unit] -Description=gunicorn socket - -[Socket] -ListenStream=/run/gunicorn.sock - -[Install] -WantedBy=sockets.target -EOF -``` - -### Create Gunicorn Service -```bash -cat > /etc/systemd/system/gunicorn.service << 'EOF' -[Unit] -Description=Gunicorn daemon for Django app -Requires=gunicorn.socket -After=network.target - -[Service] -User=django -Group=www-data -WorkingDirectory=/var/www/django-app -Environment=DJANGO_SETTINGS_MODULE=PROJECT_NAME.settings -EnvironmentFile=/var/www/django-app/.env -ExecStart=/var/www/django-app/venv/bin/gunicorn \ - --workers 3 \ - --bind unix:/run/gunicorn.sock \ - PROJECT_NAME.wsgi:application -Restart=always - -[Install] -WantedBy=multi-user.target -EOF -``` - -**โš ๏ธ Replace `PROJECT_NAME` with your actual Django project name!** - -### Start Services -```bash -systemctl daemon-reload -systemctl start gunicorn.socket -systemctl enable gunicorn.socket -systemctl status gunicorn.socket -``` - -## ๐ŸŒ **Step 5: Nginx Configuration** - -### Create Nginx Site Config -```bash -cat > /etc/nginx/sites-available/django-app << 'EOF' -server { - listen 80; - server_name YOUR_DOMAIN.com www.YOUR_DOMAIN.com YOUR_VPS_IP; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - - location / { - include proxy_params; - proxy_pass http://unix:/run/gunicorn.sock; - } - - location /static/ { - alias /var/www/django-app/staticfiles/; - expires 1y; - add_header Cache-Control "public, immutable"; - } - - location /media/ { - alias /var/www/django-app/media/; - expires 1y; - add_header Cache-Control "public"; - } - - # Block access to sensitive files - location ~* /\.(?!well-known\/) { - deny all; - } - - location ~* /(requirements\.txt|\.env|deploy/|\.git/) { - deny all; - } -} -EOF -``` - -### Enable Site -```bash -ln -s /etc/nginx/sites-available/django-app /etc/nginx/sites-enabled/ -rm -f /etc/nginx/sites-enabled/default -nginx -t -systemctl restart nginx -``` - -## ๐Ÿ”’ **Step 6: Security & Firewall** - -### Configure UFW Firewall -```bash -ufw default deny incoming -ufw default allow outgoing -ufw allow ssh -ufw allow 'Nginx Full' -ufw --force enable -ufw status -``` - -## ๐Ÿ” **Step 7: SSL Certificate (Optional)** - -### Install Certbot -```bash -apt install certbot python3-certbot-nginx -y -``` - -### Get SSL Certificate -```bash -certbot --nginx -d yourdomain.com -d www.yourdomain.com -``` - -### Update Environment for HTTPS -```bash -# In /var/www/django-app/.env -SECURE_SSL_REDIRECT=True -``` - -## โœ… **Step 8: Verification** - -### Test Application -```bash -curl -I http://YOUR_VPS_IP # Should return 200 OK -``` - -### Check Services -```bash -systemctl status gunicorn.socket -systemctl status gunicorn.service -systemctl status nginx -``` - -### View Logs -```bash -journalctl -u gunicorn.service -f # Django logs -tail -f /var/log/nginx/error.log # Nginx logs -``` - -## ๐Ÿ”„ **Deployment Updates** - -### Update Code -```bash -sudo -u django bash -c " -cd /var/www/django-app -git pull origin main -source venv/bin/activate -pip install -r requirements.txt -python manage.py migrate -python manage.py collectstatic --noinput -" -systemctl restart gunicorn.service -``` - -## ๐Ÿšจ **Common Issues & Solutions** - -### Issue: 502 Bad Gateway -**Cause**: Gunicorn not running or socket issues -**Solution**: -```bash -systemctl status gunicorn.service -systemctl restart gunicorn.socket -``` - -### Issue: Static files not loading -**Cause**: Nginx can't access staticfiles directory -**Solution**: -```bash -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py collectstatic --noinput" -``` - -### Issue: Permission denied -**Cause**: Wrong file ownership -**Solution**: -```bash -chown -R django:www-data /var/www/django-app -``` - -### Issue: ModuleNotFoundError -**Cause**: Virtual environment not recreated after moving files -**Solution**: -```bash -sudo -u django bash -c "cd /var/www/django-app && rm -rf venv && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt" -``` - -## ๐Ÿ“ **Quick Commands Reference** - -```bash -# Service management -systemctl restart gunicorn.service -systemctl restart nginx -systemctl status gunicorn.service - -# View logs -journalctl -u gunicorn.service -f -tail -f /var/log/nginx/access.log - -# Django management -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py COMMAND" -``` - -## ๐ŸŽฏ **Key Differences from Our Initial Approach** - -1. **User**: Use `django` user instead of `root` -2. **Location**: Use `/var/www/django-app` instead of `/root/django-demo` -3. **Socket**: Use Unix socket instead of TCP -4. **Virtual Environment**: Always recreate in target location -5. **Systemd**: Use socket activation with proper service file -6. **Security**: Proper file permissions and firewall rules - -This configuration is production-ready, secure, and follows Django best practices. \ No newline at end of file diff --git a/QUICK_START.md b/QUICK_START.md deleted file mode 100644 index 74d807b..0000000 --- a/QUICK_START.md +++ /dev/null @@ -1,223 +0,0 @@ -# Django VPS Quick Start Guide - -๐Ÿš€ **One-command deployment for Django on VPS!** - -## โšก **Super Quick Start (5 minutes)** - -1. **SSH into your VPS:** - ```bash - ssh root@YOUR_VPS_IP - ``` - -2. **Run the automated deployment:** - ```bash - curl -sSL https://raw.githubusercontent.com/thecyberlearn/hostinger-django-demo/main/deploy/production-deploy.sh | sudo bash - ``` - -3. **Follow the prompts:** - - Enter your Git repository URL - - Enter your domain name (optional) - - Enter your VPS IP address - -4. **Done!** Your Django app will be live at `http://YOUR_VPS_IP` - -## ๐Ÿ“‹ **Manual Quick Start (10 minutes)** - -If you prefer step-by-step control: - -### **Step 1: System Setup (2 minutes)** -```bash -# Update system -apt update && apt upgrade -y - -# Install packages -apt install -y python3 python3-pip python3-venv python3-dev \ - nginx postgresql postgresql-contrib libpq-dev \ - build-essential curl git ufw - -# Create user -adduser django --disabled-password --gecos '' -usermod -aG sudo django -``` - -### **Step 2: Clone Project (1 minute)** -```bash -# Setup directory -mkdir -p /var/www && chown django:www-data /var/www - -# Clone your project -sudo -u django bash -c "cd /var/www && git clone YOUR_REPO_URL django-app" -``` - -### **Step 3: Python Setup (2 minutes)** -```bash -sudo -u django bash -c " -cd /var/www/django-app -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -" -``` - -### **Step 4: Django Configuration (2 minutes)** -```bash -# Copy environment file -sudo -u django bash -c "cd /var/www/django-app && cp .env.example .env" - -# Edit .env file with your settings -nano /var/www/django-app/.env - -# Run Django setup -sudo -u django bash -c " -cd /var/www/django-app -source venv/bin/activate -python manage.py migrate -python manage.py collectstatic --noinput -" -``` - -### **Step 5: Services Setup (3 minutes)** -```bash -# Create Gunicorn socket -cat > /etc/systemd/system/gunicorn.socket << 'EOF' -[Unit] -Description=gunicorn socket - -[Socket] -ListenStream=/run/gunicorn.sock - -[Install] -WantedBy=sockets.target -EOF - -# Create Gunicorn service (replace demo_project with your project name) -cat > /etc/systemd/system/gunicorn.service << 'EOF' -[Unit] -Description=Gunicorn daemon for Django app -Requires=gunicorn.socket -After=network.target - -[Service] -User=django -Group=www-data -WorkingDirectory=/var/www/django-app -Environment=DJANGO_SETTINGS_MODULE=demo_project.settings -EnvironmentFile=/var/www/django-app/.env -ExecStart=/var/www/django-app/venv/bin/gunicorn \ - --workers 3 \ - --bind unix:/run/gunicorn.sock \ - demo_project.wsgi:application -Restart=always - -[Install] -WantedBy=multi-user.target -EOF - -# Start services -systemctl daemon-reload -systemctl start gunicorn.socket -systemctl enable gunicorn.socket - -# Configure Nginx (replace YOUR_VPS_IP) -cat > /etc/nginx/sites-available/django-app << 'EOF' -server { - listen 80; - server_name YOUR_VPS_IP; - - location / { - include proxy_params; - proxy_pass http://unix:/run/gunicorn.sock; - } - - location /static/ { - alias /var/www/django-app/staticfiles/; - } -} -EOF - -# Enable Nginx -ln -sf /etc/nginx/sites-available/django-app /etc/nginx/sites-enabled/ -rm -f /etc/nginx/sites-enabled/default -nginx -t && systemctl restart nginx - -# Configure firewall -ufw allow ssh && ufw allow 'Nginx Full' && ufw --force enable -``` - -## โœ… **Verification** - -Your Django app should now be live! Test with: -```bash -curl -I http://YOUR_VPS_IP -``` - -You should see `HTTP/1.1 200 OK` - -## ๐Ÿ”ง **Post-Deployment** - -1. **Create superuser:** - ```bash - sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py createsuperuser" - ``` - -2. **Access admin:** `http://YOUR_VPS_IP/admin` - -3. **Add SSL certificate (optional):** - ```bash - apt install certbot python3-certbot-nginx - certbot --nginx -d yourdomain.com - ``` - -## ๐Ÿšจ **If Something Goes Wrong** - -1. **Check service status:** - ```bash - systemctl status gunicorn.service nginx - ``` - -2. **View logs:** - ```bash - journalctl -u gunicorn.service -f - tail -f /var/log/nginx/error.log - ``` - -3. **Common fixes:** - ```bash - # Restart services - systemctl restart gunicorn.service nginx - - # Fix permissions - chown -R django:www-data /var/www/django-app - - # Recreate virtual environment - sudo -u django bash -c "cd /var/www/django-app && rm -rf venv && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt" - ``` - -## ๐Ÿ“š **What's Different from Before** - -Our **old problematic approach:** -- โŒ Used root user -- โŒ Wrong directory (`/root/django-demo`) -- โŒ TCP socket instead of Unix socket -- โŒ Hardcoded paths in virtual environment - -Our **new bulletproof approach:** -- โœ… Non-root `django` user -- โœ… Standard `/var/www/django-app` directory -- โœ… Unix socket for better performance -- โœ… Proper file permissions and security -- โœ… Follows Django deployment best practices - -## ๐ŸŽฏ **Why This Works Better** - -1. **Security:** Non-root user with minimal privileges -2. **Performance:** Unix sockets are faster than TCP -3. **Reliability:** Socket activation prevents startup issues -4. **Maintainability:** Standard directory structure -5. **Scalability:** Proper systemd integration - -This setup is **production-ready** and follows **Django best practices**! - ---- - -**Need help?** Check `TROUBLESHOOTING.md` for detailed solutions to common issues. \ No newline at end of file diff --git a/SIMPLE_DEPLOY.md b/SIMPLE_DEPLOY.md new file mode 100644 index 0000000..69f5580 --- /dev/null +++ b/SIMPLE_DEPLOY.md @@ -0,0 +1,34 @@ +# Simple Django VPS Deployment + +## ๐ŸŽฏ What This Is +A Django demo project that deploys to any VPS with one command. + +## ๐Ÿš€ Deploy to VPS + +### After VPS Reset: +```bash +# 1. Fix SSH key +ssh-keygen -f '/home/amit/.ssh/known_hosts' -R '69.62.81.168' + +# 2. Setup django user +scp setup-django-user.sh akvps:/root/ +ssh akvps "sudo bash /root/setup-django-user.sh" + +# 3. Clone and deploy +ssh akvps "cd /home/django && git clone https://github.com/thecyberlearn/hostinger-django-demo.git" +ssh akvps "cd /home/django/hostinger-django-demo && echo -e 'https://github.com/thecyberlearn/hostinger-django-demo.git\n\n69.62.81.168' | sudo bash deploy/production-deploy.sh" +``` + +## ๐Ÿ”ง Update After Changes +```bash +# Push changes to GitHub first +git add . && git commit -m "update" && git push + +# Then update VPS +ssh akvps "cd /var/www/django-app && sudo -u django git pull && sudo systemctl restart gunicorn nginx" +``` + +## ๐Ÿ“ฑ Your Site +**URL**: http://69.62.81.168/ + +That's it! ๐ŸŽ‰ \ No newline at end of file diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md deleted file mode 100644 index 131e38d..0000000 --- a/TROUBLESHOOTING.md +++ /dev/null @@ -1,398 +0,0 @@ -# Django VPS Deployment Troubleshooting Guide - -A comprehensive guide to diagnose and fix common issues during Django VPS deployment. - -## ๐Ÿ” **Quick Diagnosis Commands** - -Before diving into specific issues, run these commands to get an overview: - -```bash -# Check all services status -systemctl status gunicorn.socket gunicorn.service nginx - -# Test HTTP response -curl -I http://YOUR_VPS_IP - -# Check disk space -df -h - -# Check memory usage -free -m - -# View recent logs -journalctl -u gunicorn.service -n 20 -tail -20 /var/log/nginx/error.log -``` - -## ๐Ÿšจ **Common Issues & Solutions** - -### **Issue 1: 502 Bad Gateway** - -**Symptoms:** -- Nginx returns 502 Bad Gateway -- Website is unreachable - -**Diagnosis:** -```bash -systemctl status gunicorn.service -curl http://unix:/run/gunicorn.sock # Test socket directly -``` - -**Common Causes & Solutions:** - -#### 1.1 Gunicorn Service Not Running -```bash -# Check status -systemctl status gunicorn.service - -# If failed, check logs -journalctl -u gunicorn.service -n 50 - -# Restart service -systemctl restart gunicorn.socket -systemctl restart gunicorn.service -``` - -#### 1.2 Socket Permission Issues -```bash -# Check socket permissions -ls -la /run/gunicorn.sock - -# Fix permissions if needed -sudo chown django:www-data /run/gunicorn.sock -``` - -#### 1.3 Virtual Environment Issues -```bash -# Recreate virtual environment -sudo -u django bash -c " -cd /var/www/django-app -rm -rf venv -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -" -systemctl restart gunicorn.service -``` - ---- - -### **Issue 2: 500 Internal Server Error** - -**Symptoms:** -- Django returns 500 error -- HTTP 500 in curl response - -**Diagnosis:** -```bash -# Check Django logs -journalctl -u gunicorn.service -f - -# Check Django settings -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py check --deploy" -``` - -**Common Causes & Solutions:** - -#### 2.1 ModuleNotFoundError -**Error:** `No module named 'your_project.urls'` - -**Solution:** -```bash -# Check project structure -ls -la /var/www/django-app/ - -# Ensure DJANGO_SETTINGS_MODULE is correct in systemd service -grep DJANGO_SETTINGS_MODULE /etc/systemd/system/gunicorn.service - -# Update if wrong project name -sed -i 's/demo_project/YOUR_ACTUAL_PROJECT_NAME/g' /etc/systemd/system/gunicorn.service -systemctl daemon-reload -systemctl restart gunicorn.service -``` - -#### 2.2 Database Connection Issues -**Solution:** -```bash -# Test database connection -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py dbshell" - -# Check .env file -cat /var/www/django-app/.env - -# Run migrations if needed -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py migrate" -``` - -#### 2.3 Missing Static Files -**Solution:** -```bash -# Collect static files -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py collectstatic --noinput" - -# Check static files directory -ls -la /var/www/django-app/staticfiles/ -``` - ---- - -### **Issue 3: Static Files Not Loading (CSS/JS Missing)** - -**Symptoms:** -- Website loads but no styling -- 404 errors for CSS/JS files - -**Diagnosis:** -```bash -# Check nginx config -nginx -t -cat /etc/nginx/sites-enabled/django-app - -# Test static file access -curl -I http://YOUR_VPS_IP/static/admin/css/base.css -``` - -**Solutions:** -```bash -# 1. Collect static files -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py collectstatic --noinput" - -# 2. Check nginx static files configuration -grep -A 5 "location /static/" /etc/nginx/sites-enabled/django-app - -# 3. Fix permissions -chown -R django:www-data /var/www/django-app/staticfiles/ -chmod -R 755 /var/www/django-app/staticfiles/ - -# 4. Restart nginx -systemctl restart nginx -``` - ---- - -### **Issue 4: Permission Denied Errors** - -**Symptoms:** -- Various permission denied errors in logs -- Services failing to start - -**Solutions:** -```bash -# Fix project ownership -chown -R django:www-data /var/www/django-app - -# Fix socket permissions -chown django:www-data /run/gunicorn.sock - -# Fix log directory permissions -mkdir -p /var/log/django -chown -R django:www-data /var/log/django - -# Restart services -systemctl restart gunicorn.service -``` - ---- - -### **Issue 5: Firewall Blocking Connections** - -**Symptoms:** -- Connection timeout from external IPs -- Works locally but not from internet - -**Diagnosis:** -```bash -# Check firewall status -ufw status - -# Test local connection -curl -I http://127.0.0.1 -``` - -**Solutions:** -```bash -# Allow HTTP and HTTPS -ufw allow 'Nginx Full' -ufw allow 80 -ufw allow 443 - -# Reload firewall -ufw reload - -# Check status -ufw status -``` - ---- - -### **Issue 6: SSL/HTTPS Issues** - -**Symptoms:** -- SSL certificate errors -- HTTPS redirects not working - -**Solutions:** -```bash -# Check SSL certificate -certbot certificates - -# Renew certificate -certbot renew - -# Test nginx config -nginx -t - -# Check SSL-related settings in .env -grep SECURE_SSL_REDIRECT /var/www/django-app/.env -``` - ---- - -## ๐Ÿ”ง **Advanced Debugging** - -### **Check System Resources** -```bash -# Check disk space -df -h - -# Check memory usage -free -m -htop - -# Check CPU usage -top - -# Check open files -lsof | grep django -``` - -### **Network Debugging** -```bash -# Check listening ports -ss -tulpn | grep :80 -ss -tulpn | grep :443 -ss -tulpn | grep gunicorn - -# Check network connections -netstat -tlnp - -# Test DNS resolution -nslookup YOUR_DOMAIN -``` - -### **Log Analysis** -```bash -# Real-time Django logs -journalctl -u gunicorn.service -f - -# Real-time Nginx logs -tail -f /var/log/nginx/access.log -tail -f /var/log/nginx/error.log - -# Search for specific errors -journalctl -u gunicorn.service | grep ERROR -grep "500" /var/log/nginx/access.log -``` - -## ๐Ÿ“Š **Health Check Script** - -Create this script to quickly check your deployment health: - -```bash -#!/bin/bash -# Save as health-check.sh - -echo "๐Ÿฅ Django Deployment Health Check" -echo "================================" - -# Service status -echo "๐Ÿ“Š Service Status:" -systemctl is-active gunicorn.socket && echo "โœ… Gunicorn Socket: Active" || echo "โŒ Gunicorn Socket: Inactive" -systemctl is-active gunicorn.service && echo "โœ… Gunicorn Service: Active" || echo "โŒ Gunicorn Service: Inactive" -systemctl is-active nginx && echo "โœ… Nginx: Active" || echo "โŒ Nginx: Inactive" - -# HTTP test -echo -e "\n๐ŸŒ HTTP Response:" -HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost) -if [ "$HTTP_CODE" = "200" ]; then - echo "โœ… HTTP Response: $HTTP_CODE (OK)" -else - echo "โŒ HTTP Response: $HTTP_CODE" -fi - -# Disk space -echo -e "\n๐Ÿ’พ Disk Usage:" -df -h | grep -E "/$|/var" - -# Memory usage -echo -e "\n๐Ÿง  Memory Usage:" -free -m | grep Mem - -# Recent errors -echo -e "\n๐Ÿšจ Recent Errors (last 10 lines):" -journalctl -u gunicorn.service -n 10 --no-pager | grep -i error || echo "No recent errors found" - -echo -e "\nโœ… Health check complete!" -``` - -## ๐Ÿ†˜ **Emergency Recovery** - -If everything is broken, try this recovery sequence: - -```bash -# 1. Stop all services -systemctl stop gunicorn.service nginx - -# 2. Check project files -ls -la /var/www/django-app/ - -# 3. Recreate virtual environment -sudo -u django bash -c "cd /var/www/django-app && rm -rf venv && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt" - -# 4. Run Django checks -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py check" - -# 5. Collect static files -sudo -u django bash -c "cd /var/www/django-app && source venv/bin/activate && python manage.py collectstatic --noinput" - -# 6. Fix permissions -chown -R django:www-data /var/www/django-app - -# 7. Restart services -systemctl daemon-reload -systemctl start gunicorn.socket nginx - -# 8. Test -curl -I http://localhost -``` - -## ๐Ÿ“ž **Getting Help** - -If you're still stuck: - -1. **Collect information:** - ```bash - # System info - uname -a - lsb_release -a - - # Service status - systemctl status gunicorn.service nginx - - # Recent logs - journalctl -u gunicorn.service -n 50 - tail -50 /var/log/nginx/error.log - ``` - -2. **Check Django docs:** https://docs.djangoproject.com/en/stable/howto/deployment/ -3. **Check Gunicorn docs:** https://docs.gunicorn.org/ -4. **Check Nginx docs:** https://nginx.org/en/docs/ - -Remember: Most deployment issues are caused by: -- File permissions -- Incorrect paths -- Missing dependencies -- Configuration typos -- Firewall rules - -Take it step by step and check each component individually! \ No newline at end of file diff --git a/deploy/deploy-project.sh b/deploy/deploy-project.sh new file mode 100755 index 0000000..7cb58f6 --- /dev/null +++ b/deploy/deploy-project.sh @@ -0,0 +1,300 @@ +#!/bin/bash +# Multi-Project Django Deployment Script +# Automatically extracts GitHub repo name for project naming +# Usage: sudo bash deploy-project.sh +# Example: sudo bash deploy-project.sh https://github.com/thecyberlearn/hostinger-django-demo.git + +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 "${BLUE}๐Ÿš€ Multi-Project Django Deployment Script${NC}" +echo -e "${BLUE}===========================================${NC}" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}โŒ Please run this script as root (use sudo)${NC}" + exit 1 +fi + +# Get repository URL from argument +REPO_URL="$1" + +if [ -z "$REPO_URL" ]; then + echo -e "${YELLOW}๐Ÿ“ Enter your GitHub repository URL:${NC}" + read -p "Repository URL: " REPO_URL +fi + +if [ -z "$REPO_URL" ]; then + echo -e "${RED}โŒ Repository URL is required${NC}" + exit 1 +fi + +# Extract project name from GitHub URL +# Examples: +# https://github.com/user/project-name.git -> project-name +# https://github.com/user/project-name -> project-name +# git@github.com:user/project-name.git -> project-name +PROJECT_NAME=$(echo "$REPO_URL" | sed -E 's|.*/([^/]+)/?$|\1|' | sed 's|\.git$||') + +if [ -z "$PROJECT_NAME" ]; then + echo -e "${RED}โŒ Could not extract project name from URL: $REPO_URL${NC}" + exit 1 +fi + +# Configuration +VPS_IP=$(curl -s ifconfig.me 2>/dev/null || echo "YOUR_VPS_IP") +PROJECT_PATH="/var/www/$PROJECT_NAME" + +echo -e "${GREEN}โœ… Configuration:${NC}" +echo -e "Repository: $REPO_URL" +echo -e "Project Name: $PROJECT_NAME" +echo -e "Deploy Path: $PROJECT_PATH" +echo -e "VPS IP: $VPS_IP" +echo + +# Confirmation +echo -e "${YELLOW}๐Ÿค” Continue with deployment? (y/n)${NC}" +read -p "Confirm: " CONFIRM +if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}โŒ Deployment cancelled${NC}" + exit 0 +fi + +# Helper functions +print_status() { + echo -e "${GREEN}โœ… $1${NC}" +} + +print_progress() { + echo -e "${YELLOW}๐Ÿ”„ $1${NC}" +} + +# Step 1: Install system packages (only if first deployment) +if [ ! -f "/var/www/.system_setup_done" ]; then + print_progress "Installing system packages (first time setup)..." + apt update + apt install -y python3 python3-pip python3-venv python3-dev \ + nginx postgresql postgresql-contrib libpq-dev \ + build-essential curl git ufw + + # Create django user if doesn't exist + if ! id "django" &>/dev/null; then + adduser django --disabled-password --gecos '' + usermod -aG sudo django + print_status "Django user created" + fi + + # Copy SSH keys if they exist + if [ -d "/root/.ssh" ] && [ -f "/root/.ssh/authorized_keys" ]; then + mkdir -p /home/django/.ssh + cp /root/.ssh/authorized_keys /home/django/.ssh/ + chown -R django:django /home/django/.ssh + chmod 700 /home/django/.ssh + chmod 600 /home/django/.ssh/authorized_keys + fi + + # Create /var/www directory + mkdir -p /var/www + chown django:www-data /var/www + + # Mark system setup as done + touch /var/www/.system_setup_done + print_status "System setup complete" +else + print_status "System already configured, skipping package installation" +fi + +# Step 2: Clone or update project +print_progress "Setting up project: $PROJECT_NAME..." +if [ -d "$PROJECT_PATH" ]; then + print_progress "Updating existing project..." + sudo -u django bash -c "cd $PROJECT_PATH && git pull origin main" + print_status "Project updated" +else + print_progress "Cloning new project..." + sudo -u django bash -c "cd /var/www && git clone $REPO_URL $PROJECT_NAME" + print_status "Project cloned" +fi + +# Step 3: Virtual environment +print_progress "Setting up virtual environment..." +sudo -u django bash -c "cd $PROJECT_PATH && python3 -m venv venv" +print_status "Virtual environment created" + +# Step 4: Install dependencies +print_progress "Installing Python dependencies..." +sudo -u django bash -c "cd $PROJECT_PATH && source venv/bin/activate && pip install --upgrade pip && pip install -r requirements.txt" +print_status "Dependencies installed" + +# Step 5: Django setup +print_progress "Setting up Django..." + +# Create environment file if it doesn't exist +if [ ! -f "$PROJECT_PATH/.env" ]; then + print_progress "Creating environment file..." + SECRET_KEY=$(python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())") + cat > "$PROJECT_PATH/.env" << EOF +SECRET_KEY=$SECRET_KEY +DEBUG=False +ALLOWED_HOSTS=$VPS_IP,localhost,127.0.0.1 +DATABASE_URL= +EOF + chown django:django "$PROJECT_PATH/.env" + chmod 600 "$PROJECT_PATH/.env" + print_status "Environment file created" +fi + +# Run Django management commands +sudo -u django bash -c "cd $PROJECT_PATH && source venv/bin/activate && python manage.py migrate" +sudo -u django bash -c "cd $PROJECT_PATH && source venv/bin/activate && python manage.py collectstatic --noinput" +print_status "Django setup complete" + +# Step 6: Gunicorn configuration +print_progress "Setting up Gunicorn for $PROJECT_NAME..." + +# Detect Django project directory name (the one with settings.py) +DJANGO_PROJECT_DIR=$(sudo -u django find "$PROJECT_PATH" -name "settings.py" -exec dirname {} \; | head -1) +DJANGO_PROJECT_NAME=$(basename "$DJANGO_PROJECT_DIR") + +# Create Gunicorn configuration +mkdir -p "$PROJECT_PATH/deploy" +cat > "$PROJECT_PATH/deploy/gunicorn.conf.py" << EOF +# Gunicorn configuration for $PROJECT_NAME +bind = "unix:/run/gunicorn-$PROJECT_NAME.sock" +workers = 3 +user = "django" +group = "www-data" +timeout = 30 +keepalive = 2 +max_requests = 1000 +max_requests_jitter = 100 +EOF + +# Create systemd socket file +cat > "/etc/systemd/system/gunicorn-$PROJECT_NAME.socket" << EOF +[Unit] +Description=gunicorn socket for $PROJECT_NAME + +[Socket] +ListenStream=/run/gunicorn-$PROJECT_NAME.sock + +[Install] +WantedBy=sockets.target +EOF + +# Create systemd service file +cat > "/etc/systemd/system/gunicorn-$PROJECT_NAME.service" << EOF +[Unit] +Description=gunicorn daemon for $PROJECT_NAME +Requires=gunicorn-$PROJECT_NAME.socket +After=network.target + +[Service] +Type=notify +User=django +Group=www-data +RuntimeDirectory=gunicorn-$PROJECT_NAME +WorkingDirectory=$PROJECT_PATH +Environment=PYTHONPATH=$PROJECT_PATH +EnvironmentFile=$PROJECT_PATH/.env +ExecStart=$PROJECT_PATH/venv/bin/gunicorn \\ + --config $PROJECT_PATH/deploy/gunicorn.conf.py \\ + $DJANGO_PROJECT_NAME.wsgi:application +ExecReload=/bin/kill -s HUP \$MAINPID +KillMode=mixed +TimeoutStopSec=5 +PrivateTmp=true + +[Install] +WantedBy=multi-user.target +EOF + +print_status "Gunicorn configuration created" + +# Step 7: Nginx configuration +print_progress "Setting up Nginx for $PROJECT_NAME..." + +# Create Nginx site configuration +cat > "/etc/nginx/sites-available/$PROJECT_NAME" << EOF +server { + listen 80; + server_name $VPS_IP; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Root location for this project + location /$PROJECT_NAME/ { + include proxy_params; + proxy_pass http://unix:/run/gunicorn-$PROJECT_NAME.sock/; + + # Remove the project name from the path when passing to Django + rewrite ^/$PROJECT_NAME/(.*) /\$1 break; + } + + # Static files for this project + location /$PROJECT_NAME/static/ { + alias $PROJECT_PATH/staticfiles/; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Media files for this project + location /$PROJECT_NAME/media/ { + alias $PROJECT_PATH/media/; + expires 1y; + add_header Cache-Control "public"; + } +} +EOF + +# Enable the site +ln -sf "/etc/nginx/sites-available/$PROJECT_NAME" "/etc/nginx/sites-enabled/$PROJECT_NAME" +print_status "Nginx configuration created" + +# Step 8: Start services +print_progress "Starting services for $PROJECT_NAME..." +systemctl daemon-reload +systemctl enable "gunicorn-$PROJECT_NAME.socket" +systemctl start "gunicorn-$PROJECT_NAME.socket" +systemctl enable "gunicorn-$PROJECT_NAME.service" + +# Test nginx configuration +nginx -t +systemctl restart nginx + +print_status "Services started" + +# Step 9: Test deployment +print_progress "Testing deployment..." +sleep 3 + +if systemctl is-active --quiet "gunicorn-$PROJECT_NAME.service"; then + print_status "โœ… $PROJECT_NAME deployed successfully!" + echo + echo -e "${GREEN}๐ŸŽ‰ Deployment Complete!${NC}" + echo -e "${YELLOW}๐Ÿ“‹ Project Details:${NC}" + echo -e "Project Name: $PROJECT_NAME" + echo -e "Project Path: $PROJECT_PATH" + echo -e "Project URL: http://$VPS_IP/$PROJECT_NAME/" + echo -e "Static Files: http://$VPS_IP/$PROJECT_NAME/static/" + echo + echo -e "${YELLOW}๐Ÿ”ง Management Commands:${NC}" + echo -e "Check status: systemctl status gunicorn-$PROJECT_NAME.service" + echo -e "View logs: journalctl -u gunicorn-$PROJECT_NAME.service -f" + echo -e "Restart: systemctl restart gunicorn-$PROJECT_NAME.service" + echo +else + echo -e "${RED}โŒ Deployment failed for $PROJECT_NAME${NC}" + echo "Check logs: journalctl -u gunicorn-$PROJECT_NAME.service -f" + exit 1 +fi \ No newline at end of file diff --git a/deploy/setup-multi-webhook.sh b/deploy/setup-multi-webhook.sh new file mode 100755 index 0000000..cdd099d --- /dev/null +++ b/deploy/setup-multi-webhook.sh @@ -0,0 +1,168 @@ +#!/bin/bash +""" +Multi-Project Webhook Router Setup Script +Sets up centralized webhook receiver for multiple Django projects +""" + +set -e + +echo "๐Ÿ”„ Setting up Multi-Project Webhook Router..." + +# Configuration +WEBHOOK_SECRET=${1:-$(openssl rand -hex 32)} +SERVICE_USER="django" +WEBHOOK_PATH="/var/www/webhook-manager" +WEBHOOK_ROUTER_PATH="$WEBHOOK_PATH/webhook-router.py" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +# Create webhook manager directory +echo -e "${YELLOW}๐Ÿ“ฆ Setting up webhook manager...${NC}" +mkdir -p "$WEBHOOK_PATH" + +# Copy webhook router +cp "$(dirname "$0")/webhook-router.py" "$WEBHOOK_ROUTER_PATH" +chown -R django:www-data "$WEBHOOK_PATH" +chmod +x "$WEBHOOK_ROUTER_PATH" + +# Install Flask if not present +echo -e "${YELLOW}๐Ÿ“ฆ Installing Flask...${NC}" +sudo -u django bash -c "cd $WEBHOOK_PATH && python3 -m venv venv && source venv/bin/activate && pip install flask" + +# Create webhook secret file +echo -e "${YELLOW}๐Ÿ” Setting up webhook secret...${NC}" +echo "WEBHOOK_SECRET=$WEBHOOK_SECRET" > "$WEBHOOK_PATH/.env" +chown django:www-data "$WEBHOOK_PATH/.env" +chmod 600 "$WEBHOOK_PATH/.env" + +echo -e "${GREEN}๐Ÿ”‘ Webhook Secret: $WEBHOOK_SECRET${NC}" +echo -e "${YELLOW}๐Ÿ“ Save this secret - you'll need it for GitHub webhook configuration!${NC}" + +# Create systemd service file +echo -e "${YELLOW}โš™๏ธ Creating systemd service...${NC}" +cat > /etc/systemd/system/django-webhook-router.service << EOF +[Unit] +Description=Django Multi-Project Webhook Router +After=network.target + +[Service] +Type=simple +User=$SERVICE_USER +Group=www-data +WorkingDirectory=$WEBHOOK_PATH +Environment=PYTHONPATH=$WEBHOOK_PATH +EnvironmentFile=$WEBHOOK_PATH/.env +ExecStart=$WEBHOOK_PATH/venv/bin/python $WEBHOOK_ROUTER_PATH +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +EOF + +# Create main nginx configuration with webhook routing +echo -e "${YELLOW}๐ŸŒ Configuring Nginx for multi-project...${NC}" +cat > /etc/nginx/sites-available/django-multi-projects << 'EOF' +server { + listen 80 default_server; + server_name _; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Webhook endpoints (centralized) + location /webhook { + proxy_pass http://127.0.0.1:8001/webhook; + 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; + } + + # Health check endpoint + location /health { + proxy_pass http://127.0.0.1:8001/health; + 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; + } + + # Deployment status endpoint + location /deploy-status { + proxy_pass http://127.0.0.1:8001/status; + 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; + } + + # Root endpoint - show project list + location = / { + proxy_pass http://127.0.0.1:8001/health; + 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; + } + + # Include project-specific configurations + include /etc/nginx/conf.d/projects/*.conf; +} +EOF + +# Create directory for project-specific nginx configs +mkdir -p /etc/nginx/conf.d/projects/ + +# Disable default nginx site and enable multi-project +rm -f /etc/nginx/sites-enabled/default +ln -sf /etc/nginx/sites-available/django-multi-projects /etc/nginx/sites-enabled/django-multi-projects + +# Start and enable services +echo -e "${YELLOW}๐Ÿš€ Starting services...${NC}" +systemctl daemon-reload +systemctl enable django-webhook-router.service +systemctl start django-webhook-router.service + +# Test nginx and restart +nginx -t && systemctl restart nginx + +# Check service status +if systemctl is-active --quiet django-webhook-router.service; then + echo -e "${GREEN}โœ… Multi-Project Webhook Router is running${NC}" +else + echo -e "${RED}โŒ Webhook router failed to start${NC}" + echo "Check logs: journalctl -u django-webhook-router.service -f" + exit 1 +fi + +echo -e "${GREEN}๐ŸŽ‰ Multi-Project Webhook Setup Complete!${NC}" +echo +echo -e "${YELLOW}๐Ÿ“‹ Configuration:${NC}" +echo -e "1. ๐Ÿ” Webhook Secret: ${GREEN}$WEBHOOK_SECRET${NC}" +echo -e "2. ๐ŸŒ Webhook URL: ${GREEN}http://YOUR_VPS_IP/webhook${NC}" +echo -e "3. ๐Ÿ“Š Status URL: ${GREEN}http://YOUR_VPS_IP/deploy-status${NC}" +echo -e "4. โค๏ธ Health Check: ${GREEN}http://YOUR_VPS_IP/health${NC}" +echo +echo -e "${YELLOW}๐Ÿ“ GitHub Webhook Setup:${NC}" +echo -e "- Go to each repository โ†’ Settings โ†’ Webhooks โ†’ Add webhook" +echo -e "- Payload URL: http://YOUR_VPS_IP/webhook" +echo -e "- Content type: application/json" +echo -e "- Secret: $WEBHOOK_SECRET" +echo -e "- Events: Just the push event" +echo +echo -e "${YELLOW}๐Ÿš€ Deploy Projects:${NC}" +echo -e "sudo bash deploy/deploy-project.sh https://github.com/user/repo.git" +echo +echo -e "${YELLOW}๐Ÿ” Monitoring:${NC}" +echo -e "- Router logs: ${GREEN}journalctl -u django-webhook-router.service -f${NC}" +echo -e "- All projects: ${GREEN}http://YOUR_VPS_IP/deploy-status${NC}" +echo +echo -e "${GREEN}๐ŸŽฏ Your VPS now supports unlimited Django projects with auto-deploy!${NC}" \ No newline at end of file diff --git a/deploy/webhook-receiver.py b/deploy/webhook-receiver.py new file mode 100644 index 0000000..b4f113a --- /dev/null +++ b/deploy/webhook-receiver.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +""" +GitHub Webhook Receiver for Auto-Deployment +Listens for GitHub push events and triggers automatic deployment +""" + +import os +import sys +import json +import hmac +import hashlib +import subprocess +import logging +from datetime import datetime +from flask import Flask, request, jsonify +from threading import Thread +import time + +# Configuration +WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'your-webhook-secret-here') +REPO_PATH = '/var/www/django-app' +ALLOWED_BRANCHES = ['main', 'master'] +LOG_FILE = '/var/log/django/webhook.log' + +# Setup logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(LOG_FILE), + logging.StreamHandler() + ] +) +logger = logging.getLogger(__name__) + +app = Flask(__name__) + +def verify_signature(payload_body, signature_header): + """Verify GitHub webhook signature""" + if not signature_header: + return False + + hash_object = hmac.new( + WEBHOOK_SECRET.encode('utf-8'), + payload_body, + hashlib.sha256 + ) + expected_signature = "sha256=" + hash_object.hexdigest() + + return hmac.compare_digest(expected_signature, signature_header) + +def run_deployment(): + """Execute deployment in background thread""" + try: + logger.info("๐Ÿš€ Starting deployment...") + + # Change to app directory + os.chdir(REPO_PATH) + + # Run deployment script + result = subprocess.run([ + 'sudo', '-u', 'django', 'bash', '-c', + f''' + cd {REPO_PATH} + + # Store current commit for rollback + echo "$(git rev-parse HEAD)" > /tmp/last_working_commit.txt + + # Pull latest changes + git fetch origin + git reset --hard origin/main + + # Activate virtual environment and update + source venv/bin/activate + + # Install/update dependencies + pip install -r requirements.txt + + # Run Django management commands + python manage.py migrate + python manage.py collectstatic --noinput + + # Test if Django can start (quick check) + python manage.py check --deploy + ''' + ], capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + # Restart services + subprocess.run(['systemctl', 'restart', 'gunicorn.service'], check=True) + + # Wait a moment and check if service is running + time.sleep(3) + service_check = subprocess.run(['systemctl', 'is-active', 'gunicorn.service'], + capture_output=True, text=True) + + if service_check.stdout.strip() == 'active': + logger.info("โœ… Deployment successful!") + + # Send success notification (optional) + send_notification("โœ… Deployment successful!", "success") + + else: + logger.error("โŒ Service failed to start after deployment") + rollback() + else: + logger.error(f"โŒ Deployment failed: {result.stderr}") + rollback() + + except subprocess.TimeoutExpired: + logger.error("โŒ Deployment timed out") + rollback() + except Exception as e: + logger.error(f"โŒ Deployment error: {str(e)}") + rollback() + +def rollback(): + """Rollback to previous working commit""" + try: + logger.info("๐Ÿ”„ Rolling back to previous commit...") + + if os.path.exists('/tmp/last_working_commit.txt'): + with open('/tmp/last_working_commit.txt', 'r') as f: + last_commit = f.read().strip() + + subprocess.run([ + 'sudo', '-u', 'django', 'bash', '-c', + f'cd {REPO_PATH} && git reset --hard {last_commit}' + ], check=True) + + subprocess.run(['systemctl', 'restart', 'gunicorn.service'], check=True) + logger.info("โœ… Rollback completed") + send_notification("๐Ÿ”„ Rolled back due to deployment failure", "warning") + else: + logger.error("โŒ No previous commit found for rollback") + + except Exception as e: + logger.error(f"โŒ Rollback failed: {str(e)}") + +def send_notification(message, status="info"): + """Send deployment notification (extend this for Slack/Discord/Email)""" + logger.info(f"๐Ÿ“ข Notification: {message}") + + # You can extend this to send notifications to: + # - Slack webhook + # - Discord webhook + # - Email + # - SMS + + # Example Slack notification (uncomment and configure): + # import requests + # slack_webhook = "YOUR_SLACK_WEBHOOK_URL" + # requests.post(slack_webhook, json={"text": f"๐Ÿš€ Django App: {message}"}) + +@app.route('/webhook', methods=['POST']) +def handle_webhook(): + """Handle GitHub webhook""" + + # Verify signature + signature = request.headers.get('X-Hub-Signature-256') + if not verify_signature(request.data, signature): + logger.warning("โŒ Invalid webhook signature") + return jsonify({"error": "Invalid signature"}), 403 + + # Parse payload + try: + payload = request.json + except: + logger.warning("โŒ Invalid JSON payload") + return jsonify({"error": "Invalid JSON"}), 400 + + # Check if it's a push event + if request.headers.get('X-GitHub-Event') != 'push': + logger.info(f"โ„น๏ธ Ignoring non-push event: {request.headers.get('X-GitHub-Event')}") + return jsonify({"message": "Not a push event"}), 200 + + # Extract branch name + ref = payload.get('ref', '') + branch = ref.replace('refs/heads/', '') + + # Check if it's a branch we care about + if branch not in ALLOWED_BRANCHES: + logger.info(f"โ„น๏ธ Ignoring push to branch: {branch}") + return jsonify({"message": f"Ignoring branch {branch}"}), 200 + + # Log the deployment request + commit_hash = payload.get('after', 'unknown') + commit_message = "" + if payload.get('head_commit'): + commit_message = payload['head_commit'].get('message', '') + + logger.info(f"๐Ÿ”” Deployment triggered by push to {branch}") + logger.info(f"๐Ÿ“ Commit: {commit_hash[:8]} - {commit_message[:100]}") + + # Start deployment in background thread + deployment_thread = Thread(target=run_deployment) + deployment_thread.start() + + return jsonify({ + "message": "Deployment started", + "branch": branch, + "commit": commit_hash[:8] + }), 200 + +@app.route('/health', methods=['GET']) +def health_check(): + """Health check endpoint""" + return jsonify({ + "status": "healthy", + "timestamp": datetime.now().isoformat(), + "repo_path": REPO_PATH + }) + +@app.route('/status', methods=['GET']) +def deployment_status(): + """Get current deployment status""" + try: + # Check if services are running + gunicorn_status = subprocess.run(['systemctl', 'is-active', 'gunicorn.service'], + capture_output=True, text=True) + nginx_status = subprocess.run(['systemctl', 'is-active', 'nginx'], + capture_output=True, text=True) + + # Get current commit + os.chdir(REPO_PATH) + commit_result = subprocess.run(['git', 'rev-parse', 'HEAD'], + capture_output=True, text=True) + current_commit = commit_result.stdout.strip()[:8] if commit_result.returncode == 0 else "unknown" + + return jsonify({ + "gunicorn": gunicorn_status.stdout.strip(), + "nginx": nginx_status.stdout.strip(), + "current_commit": current_commit, + "timestamp": datetime.now().isoformat() + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == '__main__': + # Create log directory if it doesn't exist + os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) + + logger.info("๐Ÿš€ Starting GitHub webhook receiver...") + logger.info(f"๐Ÿ“ Monitoring repository: {REPO_PATH}") + logger.info(f"๐ŸŒฟ Allowed branches: {ALLOWED_BRANCHES}") + + # Run Flask app + app.run(host='127.0.0.1', port=8001, debug=False) \ No newline at end of file diff --git a/deploy/webhook-router.py b/deploy/webhook-router.py new file mode 100755 index 0000000..ad402be --- /dev/null +++ b/deploy/webhook-router.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 +""" +Multi-Project GitHub Webhook Router +Routes webhook requests to appropriate Django projects based on repository name +Supports unlimited Django projects on single VPS +""" + +import os +import sys +import json +import hmac +import hashlib +import subprocess +import logging +import glob +from datetime import datetime +from flask import Flask, request, jsonify +from threading import Thread +import time + +# Configuration +WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'your-webhook-secret-here') +PROJECTS_BASE_PATH = '/var/www' +ALLOWED_BRANCHES = ['main', 'master'] +LOG_FILE = '/var/log/django/webhook-router.log' + +# Setup logging +os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(LOG_FILE), + logging.StreamHandler() + ] +) +logger = logging.getLogger(__name__) + +app = Flask(__name__) + +def discover_projects(): + """Discover all Django projects in /var/www""" + projects = {} + + # Look for directories with manage.py (Django projects) + for project_dir in glob.glob(f"{PROJECTS_BASE_PATH}/*/"): + project_name = os.path.basename(project_dir.rstrip('/')) + + # Skip hidden directories and system files + if project_name.startswith('.') or project_name == 'html': + continue + + manage_py_path = os.path.join(project_dir, 'manage.py') + if os.path.exists(manage_py_path): + projects[project_name] = { + 'path': project_dir.rstrip('/'), + 'service': f'gunicorn-{project_name}.service' + } + + logger.info(f"๐Ÿ” Discovered {len(projects)} Django projects: {list(projects.keys())}") + return projects + +def extract_repo_name_from_url(repo_url): + """Extract repository name from GitHub URL""" + # Examples: + # https://github.com/user/project-name.git -> project-name + # https://github.com/user/project-name -> project-name + # git@github.com:user/project-name.git -> project-name + + import re + # Remove .git suffix and extract last part + repo_name = re.sub(r'\.git$', '', repo_url) + repo_name = repo_name.split('/')[-1] + return repo_name + +def verify_signature(payload_body, signature_header): + """Verify GitHub webhook signature""" + if not signature_header: + return False + + hash_object = hmac.new( + WEBHOOK_SECRET.encode('utf-8'), + payload_body, + hashlib.sha256 + ) + expected_signature = "sha256=" + hash_object.hexdigest() + + return hmac.compare_digest(expected_signature, signature_header) + +def run_deployment(project_name, project_path, commit_info): + """Execute deployment for specific project in background thread""" + try: + logger.info(f"๐Ÿš€ Starting deployment for {project_name}...") + + # Change to project directory + os.chdir(project_path) + + # Store current commit for rollback + subprocess.run(['bash', '-c', f'echo "$(git rev-parse HEAD)" > /tmp/last_working_commit_{project_name}.txt']) + + # Run deployment script + result = subprocess.run([ + 'sudo', '-u', 'django', 'bash', '-c', + f''' + cd {project_path} + + # Pull latest changes + git fetch origin + git reset --hard origin/main + + # Activate virtual environment and update + source venv/bin/activate + + # Install/update dependencies + pip install -r requirements.txt + + # Run Django management commands + python manage.py migrate + python manage.py collectstatic --noinput + + # Test if Django can start (quick check) + python manage.py check --deploy + ''' + ], capture_output=True, text=True, timeout=300) + + if result.returncode == 0: + # Restart project-specific service + service_name = f'gunicorn-{project_name}.service' + subprocess.run(['systemctl', 'restart', service_name], check=True) + + # Wait and check if service is running + time.sleep(3) + service_check = subprocess.run(['systemctl', 'is-active', service_name], + capture_output=True, text=True) + + if service_check.stdout.strip() == 'active': + logger.info(f"โœ… Deployment successful for {project_name}!") + send_notification(f"โœ… {project_name}: Deployment successful!", "success", commit_info) + else: + logger.error(f"โŒ Service failed to start for {project_name}") + rollback(project_name, project_path) + else: + logger.error(f"โŒ Deployment failed for {project_name}: {result.stderr}") + rollback(project_name, project_path) + + except subprocess.TimeoutExpired: + logger.error(f"โŒ Deployment timed out for {project_name}") + rollback(project_name, project_path) + except Exception as e: + logger.error(f"โŒ Deployment error for {project_name}: {str(e)}") + rollback(project_name, project_path) + +def rollback(project_name, project_path): + """Rollback to previous working commit for specific project""" + try: + logger.info(f"๐Ÿ”„ Rolling back {project_name} to previous commit...") + + rollback_file = f'/tmp/last_working_commit_{project_name}.txt' + if os.path.exists(rollback_file): + with open(rollback_file, 'r') as f: + last_commit = f.read().strip() + + subprocess.run([ + 'sudo', '-u', 'django', 'bash', '-c', + f'cd {project_path} && git reset --hard {last_commit}' + ], check=True) + + service_name = f'gunicorn-{project_name}.service' + subprocess.run(['systemctl', 'restart', service_name], check=True) + logger.info(f"โœ… Rollback completed for {project_name}") + send_notification(f"๐Ÿ”„ {project_name}: Rolled back due to deployment failure", "warning", {}) + else: + logger.error(f"โŒ No previous commit found for rollback: {project_name}") + + except Exception as e: + logger.error(f"โŒ Rollback failed for {project_name}: {str(e)}") + +def send_notification(message, status="info", commit_info={}): + """Send deployment notification""" + logger.info(f"๐Ÿ“ข Notification: {message}") + + # You can extend this to send notifications to: + # - Slack webhook + # - Discord webhook + # - Email + # - SMS + +@app.route('/webhook/', methods=['POST']) +def handle_project_webhook(project_name): + """Handle GitHub webhook for specific project""" + + # Verify signature + signature = request.headers.get('X-Hub-Signature-256') + if not verify_signature(request.data, signature): + logger.warning(f"โŒ Invalid webhook signature for {project_name}") + return jsonify({"error": "Invalid signature"}), 403 + + # Parse payload + try: + payload = request.json + except: + logger.warning(f"โŒ Invalid JSON payload for {project_name}") + return jsonify({"error": "Invalid JSON"}), 400 + + # Check if it's a push event + if request.headers.get('X-GitHub-Event') != 'push': + logger.info(f"โ„น๏ธ Ignoring non-push event for {project_name}: {request.headers.get('X-GitHub-Event')}") + return jsonify({"message": "Not a push event"}), 200 + + # Extract branch name + ref = payload.get('ref', '') + branch = ref.replace('refs/heads/', '') + + # Check if it's a branch we care about + if branch not in ALLOWED_BRANCHES: + logger.info(f"โ„น๏ธ Ignoring push to branch {branch} for {project_name}") + return jsonify({"message": f"Ignoring branch {branch}"}), 200 + + # Discover current projects + projects = discover_projects() + + # Check if project exists + if project_name not in projects: + logger.warning(f"โŒ Project not found: {project_name}") + return jsonify({"error": f"Project {project_name} not found"}), 404 + + # Get project details + project_path = projects[project_name]['path'] + + # Extract commit information + commit_hash = payload.get('after', 'unknown') + commit_message = "" + if payload.get('head_commit'): + commit_message = payload['head_commit'].get('message', '') + + commit_info = { + 'hash': commit_hash[:8], + 'message': commit_message[:100], + 'branch': branch + } + + logger.info(f"๐Ÿ”” Deployment triggered for {project_name}") + logger.info(f"๐Ÿ“ Branch: {branch}, Commit: {commit_hash[:8]} - {commit_message[:100]}") + + # Start deployment in background thread + deployment_thread = Thread(target=run_deployment, args=(project_name, project_path, commit_info)) + deployment_thread.start() + + return jsonify({ + "message": "Deployment started", + "project": project_name, + "branch": branch, + "commit": commit_hash[:8] + }), 200 + +@app.route('/webhook', methods=['POST']) +def handle_generic_webhook(): + """Handle generic webhook - try to determine project from repository URL""" + + # Verify signature + signature = request.headers.get('X-Hub-Signature-256') + if not verify_signature(request.data, signature): + logger.warning("โŒ Invalid webhook signature") + return jsonify({"error": "Invalid signature"}), 403 + + # Parse payload + try: + payload = request.json + except: + logger.warning("โŒ Invalid JSON payload") + return jsonify({"error": "Invalid JSON"}), 400 + + # Extract repository URL + repo_url = payload.get('repository', {}).get('clone_url', '') + if not repo_url: + repo_url = payload.get('repository', {}).get('html_url', '') + + if not repo_url: + logger.warning("โŒ No repository URL found in payload") + return jsonify({"error": "No repository URL found"}), 400 + + # Extract project name from repo URL + project_name = extract_repo_name_from_url(repo_url) + logger.info(f"๐Ÿ” Extracted project name: {project_name} from URL: {repo_url}") + + # Redirect to project-specific webhook handler + return handle_project_webhook(project_name) + +@app.route('/health', methods=['GET']) +def health_check(): + """Health check endpoint""" + projects = discover_projects() + return jsonify({ + "status": "healthy", + "timestamp": datetime.now().isoformat(), + "projects_count": len(projects), + "projects": list(projects.keys()) + }) + +@app.route('/status', methods=['GET']) +def deployment_status(): + """Get current deployment status for all projects""" + try: + projects = discover_projects() + status_data = { + "timestamp": datetime.now().isoformat(), + "projects": {} + } + + for project_name, project_info in projects.items(): + # Check service status + service_name = project_info['service'] + service_check = subprocess.run(['systemctl', 'is-active', service_name], + capture_output=True, text=True) + + # Get current commit + try: + os.chdir(project_info['path']) + commit_result = subprocess.run(['git', 'rev-parse', 'HEAD'], + capture_output=True, text=True) + current_commit = commit_result.stdout.strip()[:8] if commit_result.returncode == 0 else "unknown" + except: + current_commit = "unknown" + + status_data["projects"][project_name] = { + "service_status": service_check.stdout.strip(), + "current_commit": current_commit, + "path": project_info['path'], + "service": service_name + } + + return jsonify(status_data) + + except Exception as e: + return jsonify({"error": str(e)}), 500 + +if __name__ == '__main__': + # Create log directory if it doesn't exist + os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) + + logger.info("๐Ÿš€ Starting Multi-Project GitHub Webhook Router...") + + # Discover existing projects + projects = discover_projects() + logger.info(f"๐Ÿ“ Managing {len(projects)} Django projects") + for name, info in projects.items(): + logger.info(f" - {name}: {info['path']}") + + # Run Flask app + app.run(host='127.0.0.1', port=8001, debug=False) \ No newline at end of file diff --git a/deploy/webhook-service.sh b/deploy/webhook-service.sh new file mode 100644 index 0000000..a24ffdd --- /dev/null +++ b/deploy/webhook-service.sh @@ -0,0 +1,162 @@ +#!/bin/bash +""" +Auto-Deployment Service Setup Script +Sets up the webhook receiver as a systemd service +""" + +set -e + +echo "๐Ÿ”„ Setting up Auto-Deployment Service..." + +# Configuration +WEBHOOK_SECRET=${1:-$(openssl rand -hex 32)} +SERVICE_USER="django" +APP_PATH="/var/www/django-app" +WEBHOOK_PATH="$APP_PATH/deploy/webhook-receiver.py" + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +# Install Flask if not present +echo -e "${YELLOW}๐Ÿ“ฆ Installing Flask...${NC}" +sudo -u django bash -c "cd $APP_PATH && source venv/bin/activate && pip install flask" + +# Create webhook secret file +echo -e "${YELLOW}๐Ÿ” Setting up webhook secret...${NC}" +echo "WEBHOOK_SECRET=$WEBHOOK_SECRET" > /var/www/django-app/.env.webhook +chown django:www-data /var/www/django-app/.env.webhook +chmod 600 /var/www/django-app/.env.webhook + +echo -e "${GREEN}๐Ÿ”‘ Webhook Secret: $WEBHOOK_SECRET${NC}" +echo -e "${YELLOW}๐Ÿ“ Save this secret - you'll need it for GitHub webhook configuration!${NC}" + +# Create systemd service file +echo -e "${YELLOW}โš™๏ธ Creating systemd service...${NC}" +cat > /etc/systemd/system/django-webhook.service << EOF +[Unit] +Description=Django Auto-Deployment Webhook Receiver +After=network.target + +[Service] +Type=simple +User=$SERVICE_USER +Group=www-data +WorkingDirectory=$APP_PATH +Environment=PYTHONPATH=$APP_PATH +EnvironmentFile=$APP_PATH/.env.webhook +ExecStart=$APP_PATH/venv/bin/python $WEBHOOK_PATH +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +EOF + +# Create nginx configuration for webhook +echo -e "${YELLOW}๐ŸŒ Configuring Nginx proxy...${NC}" +cat > /etc/nginx/sites-available/django-webhook << 'EOF' +server { + listen 80; + server_name webhook.YOUR_DOMAIN.com; # Replace with your subdomain + + location /webhook { + proxy_pass http://127.0.0.1:8001/webhook; + 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; + } + + location /health { + proxy_pass http://127.0.0.1:8001/health; + 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; + } + + location /status { + proxy_pass http://127.0.0.1:8001/status; + 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; + + # Optional: Add basic auth for status endpoint + # auth_basic "Deployment Status"; + # auth_basic_user_file /etc/nginx/.htpasswd; + } +} +EOF + +# Enable webhook nginx site (optional - you might want to use main domain with /webhook path) +echo -e "${YELLOW}โ„น๏ธ Webhook Nginx config created at /etc/nginx/sites-available/django-webhook${NC}" +echo -e "${YELLOW}โ„น๏ธ You can enable it with: ln -s /etc/nginx/sites-available/django-webhook /etc/nginx/sites-enabled/${NC}" + +# Or add webhook endpoint to existing site +echo -e "${YELLOW}๐Ÿ”ง Adding webhook endpoint to main site...${NC}" +MAIN_NGINX_CONFIG="/etc/nginx/sites-available/django-app" +if [ -f "$MAIN_NGINX_CONFIG" ]; then + # Add webhook location block before the last closing brace + sed -i '/^}/i\ + # GitHub Webhook endpoint\ + location /webhook {\ + proxy_pass http://127.0.0.1:8001/webhook;\ + 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;\ + }\ +\ + # Deployment status endpoint\ + location /deploy-status {\ + proxy_pass http://127.0.0.1:8001/status;\ + 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;\ + }' "$MAIN_NGINX_CONFIG" +fi + +# Make webhook receiver executable +chmod +x $WEBHOOK_PATH + +# Start and enable services +echo -e "${YELLOW}๐Ÿš€ Starting services...${NC}" +systemctl daemon-reload +systemctl enable django-webhook.service +systemctl start django-webhook.service + +# Restart nginx +nginx -t && systemctl restart nginx + +# Check service status +if systemctl is-active --quiet django-webhook.service; then + echo -e "${GREEN}โœ… Webhook service is running${NC}" +else + echo -e "${RED}โŒ Webhook service failed to start${NC}" + echo "Check logs: journalctl -u django-webhook.service -f" + exit 1 +fi + +echo -e "${GREEN}๐ŸŽ‰ Auto-Deployment Setup Complete!${NC}" +echo +echo -e "${YELLOW}๐Ÿ“‹ Next Steps:${NC}" +echo -e "1. ๐Ÿ” Webhook Secret: ${GREEN}$WEBHOOK_SECRET${NC}" +echo -e "2. ๐ŸŒ Webhook URL: ${GREEN}http://YOUR_VPS_IP/webhook${NC}" +echo -e "3. ๐Ÿ“ Go to GitHub โ†’ Settings โ†’ Webhooks โ†’ Add webhook" +echo -e "4. ๐Ÿ”ง Configure webhook:" +echo -e " - Payload URL: http://YOUR_VPS_IP/webhook" +echo -e " - Content type: application/json" +echo -e " - Secret: $WEBHOOK_SECRET" +echo -e " - Events: Just the push event" +echo +echo -e "${YELLOW}๐Ÿ” Monitoring:${NC}" +echo -e "- Service logs: ${GREEN}journalctl -u django-webhook.service -f${NC}" +echo -e "- Deployment status: ${GREEN}http://YOUR_VPS_IP/deploy-status${NC}" +echo -e "- Health check: ${GREEN}http://YOUR_VPS_IP/health${NC}" +echo +echo -e "${GREEN}๐Ÿš€ Your VPS now works like Render - just push to GitHub!${NC}" \ No newline at end of file diff --git a/setup-auto-deploy.sh b/setup-auto-deploy.sh new file mode 100755 index 0000000..f2e6dc6 --- /dev/null +++ b/setup-auto-deploy.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Simple Auto-Deploy Setup Script +# Run this on your VPS to enable GitHub auto-deployment + +echo "๐Ÿš€ Setting up simple auto-deploy webhook..." +echo + +# Check if we're on the VPS +if [ ! -d "/var/www/django-app" ]; then + echo "โŒ This script should be run on your VPS" + echo "Please run: ssh akvps" + echo "Then: sudo bash setup-auto-deploy.sh" + exit 1 +fi + +# Run the webhook service setup +echo "๐Ÿ“ฆ Installing webhook service..." +sudo bash /var/www/django-app/deploy/webhook-service.sh + +echo +echo "โœ… Auto-deploy setup complete!" +echo +echo "Next steps:" +echo "1. Go to: https://github.com/thecyberlearn/hostinger-django-demo/settings/hooks" +echo "2. Click 'Add webhook'" +echo "3. Use the webhook secret shown above" +echo "4. Test by pushing a commit!" \ No newline at end of file diff --git a/setup-django-user.sh b/setup-django-user.sh new file mode 100755 index 0000000..5c5468c --- /dev/null +++ b/setup-django-user.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Setup django user and SSH access after VPS reset +# Run this FIRST after VPS reset before uploading projects + +set -e + +# Colors +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${YELLOW}๐Ÿ”ง Setting up django user and SSH access...${NC}" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}โŒ Please run this script as root (use sudo)${NC}" + exit 1 +fi + +# Create django user if doesn't exist +if ! id "django" &>/dev/null; then + echo -e "${YELLOW}๐Ÿ‘ค Creating django user...${NC}" + adduser django --disabled-password --gecos '' + usermod -aG sudo django + echo -e "${GREEN}โœ… Django user created${NC}" +else + echo -e "${GREEN}โœ… Django user already exists${NC}" +fi + +# Setup SSH access for django user +echo -e "${YELLOW}๐Ÿ”‘ Setting up SSH access for django user...${NC}" + +# Create .ssh directory for django user +mkdir -p /home/django/.ssh +chmod 700 /home/django/.ssh + +# Copy SSH keys from root if they exist +if [ -d "/root/.ssh" ] && [ -f "/root/.ssh/authorized_keys" ]; then + cp /root/.ssh/authorized_keys /home/django/.ssh/ + echo -e "${GREEN}โœ… SSH keys copied from root to django user${NC}" +else + echo -e "${YELLOW}โš ๏ธ No SSH keys found in /root/.ssh/${NC}" + echo -e "${YELLOW}๐Ÿ’ก You'll need to copy your public key to /home/django/.ssh/authorized_keys${NC}" +fi + +# Set proper ownership and permissions +chown -R django:django /home/django/.ssh +chmod 600 /home/django/.ssh/authorized_keys 2>/dev/null || true + +# Update SSH config to allow django user +echo -e "${YELLOW}๐Ÿ”ง Updating SSH configuration...${NC}" + +# Ensure django user can sudo without password for specific commands +cat > /etc/sudoers.d/django-deploy << 'EOF' +# Django user deployment permissions +django ALL=(ALL) NOPASSWD: /bin/systemctl restart gunicorn*.service +django ALL=(ALL) NOPASSWD: /bin/systemctl start gunicorn*.service +django ALL=(ALL) NOPASSWD: /bin/systemctl stop gunicorn*.service +django ALL=(ALL) NOPASSWD: /bin/systemctl enable gunicorn*.service +django ALL=(ALL) NOPASSWD: /bin/systemctl status gunicorn*.service +django ALL=(ALL) NOPASSWD: /bin/systemctl daemon-reload +django ALL=(ALL) NOPASSWD: /usr/sbin/nginx -t +django ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx +django ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx +EOF + +echo -e "${GREEN}โœ… Django user sudo permissions configured${NC}" + +# Create project directory in django user home +mkdir -p /home/django +chown django:django /home/django + +echo -e "${GREEN}๐ŸŽ‰ Django user setup complete!${NC}" +echo +echo -e "${YELLOW}๐Ÿ“ Next steps:${NC}" +echo -e "1. Test SSH access: ${GREEN}ssh akvps 'sudo -u django whoami'${NC}" +echo -e "2. Upload project: ${GREEN}scp -r . akvps:/home/django/project-name${NC}" +echo -e "3. Deploy project: ${GREEN}cd /home/django/project-name && sudo bash deploy/...${NC}" +echo +echo -e "${YELLOW}๐Ÿ’ก SSH alias for django user:${NC}" +echo -e "Add to ~/.ssh/config:" +echo -e "${GREEN}Host akvps-django${NC}" +echo -e "${GREEN} HostName $(curl -s ifconfig.me 2>/dev/null || echo 'YOUR_VPS_IP')${NC}" +echo -e "${GREEN} User django${NC}" +echo -e "${GREEN} IdentityFile ~/.ssh/id_rsa${NC}" \ No newline at end of file