mirror of
https://github.com/thecyberlearn/hostinger-django-demo.git
synced 2026-08-18 08:52:57 +00:00
✅ Restore multi-project deployment system
- 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
This commit is contained in:
parent
f721b0a87e
commit
61d8656da5
@ -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
|
|
||||||
```
|
|
||||||
@ -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
|
|
||||||
238
MULTI_PROJECT_SETUP.md
Normal file
238
MULTI_PROJECT_SETUP.md
Normal file
@ -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!** 🚀
|
||||||
@ -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.
|
|
||||||
223
QUICK_START.md
223
QUICK_START.md
@ -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.
|
|
||||||
34
SIMPLE_DEPLOY.md
Normal file
34
SIMPLE_DEPLOY.md
Normal file
@ -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! 🎉
|
||||||
@ -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!
|
|
||||||
300
deploy/deploy-project.sh
Executable file
300
deploy/deploy-project.sh
Executable file
@ -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 <GITHUB_REPO_URL>
|
||||||
|
# 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
|
||||||
168
deploy/setup-multi-webhook.sh
Executable file
168
deploy/setup-multi-webhook.sh
Executable file
@ -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}"
|
||||||
248
deploy/webhook-receiver.py
Normal file
248
deploy/webhook-receiver.py
Normal file
@ -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)
|
||||||
350
deploy/webhook-router.py
Executable file
350
deploy/webhook-router.py
Executable file
@ -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/<project_name>', 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)
|
||||||
162
deploy/webhook-service.sh
Normal file
162
deploy/webhook-service.sh
Normal file
@ -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}"
|
||||||
27
setup-auto-deploy.sh
Executable file
27
setup-auto-deploy.sh
Executable file
@ -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!"
|
||||||
86
setup-django-user.sh
Executable file
86
setup-django-user.sh
Executable file
@ -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}"
|
||||||
Loading…
Reference in New Issue
Block a user