mirror of
https://github.com/thecyberlearn/hostinger-django-demo.git
synced 2026-08-18 07:52:56 +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