🚀 TRANSFORM: Django VPS Deployment Toolkit

🔄 Complete transformation from mixed demo project to pure deployment toolkit

REMOVED:
 Django demo project (core/, demo_project/, manage.py, requirements.txt)
 Demo-specific files and configurations
 Mixed-purpose confusion

RESTRUCTURED:
 deploy-django-project.sh - Universal Django deployment
 setup-django-user.sh - VPS user setup
 setup-multi-webhook.sh - Auto-deploy webhooks
 webhook-router.py - Multi-project webhook handler
 templates/ - Configuration templates
 Clean root-level organization

NEW PURPOSE:
🎯 Universal toolkit to deploy ANY Django project to VPS
🏷️ Auto-extracts GitHub repo names
🔄 Multi-project support with path-based routing
📡 GitHub webhook auto-deploy integration
🔒 Secure non-root deployment

BENEFITS:
- Deploy any Django project with one command
- Zero configuration required
- Professional deployment toolkit
- Reusable for unlimited projects
- Industry-standard VPS setup

Now it's a PURE deployment toolkit, not a demo project! 🎉
This commit is contained in:
thecyberlearn 2025-08-30 09:38:51 +05:30
parent c544d15e02
commit 996c50841b
38 changed files with 146 additions and 1358 deletions

186
README.md
View File

@ -1,64 +1,164 @@
# Django VPS Demo Project
# 🚀 Django VPS Deployment Toolkit
Simple Django project optimized for VPS deployment with multi-project support.
**Universal toolkit to deploy ANY Django project to VPS with zero configuration.**
## ✨ Features
- **Minimal Dependencies**: Clean, lightweight setup with only essential packages
- **Environment Configuration**: Production settings managed via environment variables
- **Database Flexible**: Works with SQLite for development and PostgreSQL for production
- **Static Files Handling**: Configured with WhiteNoise for efficient static file serving
- **Contact Form**: Functional contact form with email validation
- **Simple Blog System**: Basic blog functionality with admin interface
- **Bootstrap UI**: Responsive design using Bootstrap 5
- **Production Security**: Security headers, HTTPS support, and production optimizations
- **VPS Deployment**: Complete deployment scripts and configuration files
- 🎯 **Universal** - Deploy any Django project from GitHub
- 🏷️ **Auto-naming** - Extracts project name from repo URL
- 🔄 **Multi-project** - Unlimited Django projects per VPS
- 🔧 **Zero config** - Automatic nginx + gunicorn + systemd setup
- 🚀 **One command** - Complete deployment in minutes
- 🔒 **Secure** - Non-root deployment with proper permissions
- 📡 **Auto-deploy** - GitHub webhook integration for CI/CD
## 🛠 Tech Stack
## 🚀 Quick Deploy
- **Backend**: Django 4.2.7
- **Database**: SQLite (development) / PostgreSQL (production)
- **Web Server**: Gunicorn + Nginx
- **Frontend**: Bootstrap 5, Vanilla JavaScript
- **Static Files**: WhiteNoise
- **Configuration**: python-decouple
## 🚀 Deploy to VPS
### After VPS Reset:
### Setup VPS (Once per VPS)
```bash
# 1. Fix SSH key
ssh-keygen -f '/home/amit/.ssh/known_hosts' -R '69.62.81.168'
# 1. Fix SSH after VPS reset
ssh-keygen -f '~/.ssh/known_hosts' -R 'YOUR_VPS_IP'
# 2. Setup django user
scp setup-django-user.sh akvps:/root/
ssh akvps "sudo bash /root/setup-django-user.sh"
# 2. Upload and run user setup
scp setup-django-user.sh root@YOUR_VPS_IP:/root/
ssh root@YOUR_VPS_IP "sudo bash /root/setup-django-user.sh"
# 3. Clone and deploy
# 3. Clone this toolkit
ssh root@YOUR_VPS_IP "cd /home/django && git clone https://github.com/thecyberlearn/hostinger-django-demo.git django-vps-toolkit"
```
### Deploy Your Project
### Deploy Any Django Project
```bash
ssh akvps "cd /home/django && git clone https://github.com/thecyberlearn/hostinger-django-demo.git"
ssh akvps "cd /home/django/hostinger-django-demo && sudo bash deploy/deploy-project.sh https://github.com/thecyberlearn/hostinger-django-demo.git"
# Deploy any Django project with one command!
ssh root@YOUR_VPS_IP "cd /home/django/django-vps-toolkit && sudo bash deploy-django-project.sh https://github.com/USER/PROJECT.git"
```
**Your site**: http://69.62.81.168/
**Examples:**
```bash
# Deploy a blog
sudo bash deploy-django-project.sh https://github.com/johndoe/my-blog.git
# → Live at: http://YOUR_VPS_IP/my-blog/
## 📁 Key Files
# Deploy an e-commerce site
sudo bash deploy-django-project.sh https://github.com/company/shop-backend.git
# → Live at: http://YOUR_VPS_IP/shop-backend/
**Main Deployment Script:**
- `deploy/deploy-project.sh` - Deploys any Django project with auto repo naming
# Deploy a portfolio
sudo bash deploy-django-project.sh https://github.com/jane/portfolio-site.git
# → Live at: http://YOUR_VPS_IP/portfolio-site/
```
**Documentation:**
- `MULTI_PROJECT_SETUP.md` - Complete multi-project guide
## 🎯 What It Does
**Setup:**
- `setup-django-user.sh` - Create django user on fresh VPS
- `requirements.txt` - Python dependencies
1. **Extracts project name** from GitHub URL
2. **Clones project** to `/var/www/PROJECT_NAME/`
3. **Creates virtual environment** and installs dependencies
4. **Runs Django migrations** and collects static files
5. **Creates systemd service** `gunicorn-PROJECT_NAME.service`
6. **Configures nginx** for path-based routing
7. **Starts everything** and tests deployment
## 🎯 That's It!
## 📁 Toolkit Files
For detailed multi-project setup, see `MULTI_PROJECT_SETUP.md`
**🚀 Main Scripts:**
- `deploy-django-project.sh` - Deploy any Django project
- `setup-django-user.sh` - VPS user setup (run once)
Simple, clean, and no confusion! 🚀
**🔧 Advanced Features:**
- `setup-multi-webhook.sh` - GitHub auto-deploy webhooks
- `webhook-router.py` - Multi-project webhook handler
- `MULTI_PROJECT_SETUP.md` - Advanced webhook guide
**📝 Templates:**
- `templates/nginx.conf.template` - Nginx configuration
- `templates/gunicorn.service.template` - Systemd service
- `templates/production_settings.py` - Django production settings
## 🔄 Auto-Deploy Setup
Want GitHub auto-deploy like Render/Vercel?
```bash
# Setup webhook system
sudo bash setup-multi-webhook.sh
# Add webhook to your GitHub repos:
# URL: http://YOUR_VPS_IP/webhook
# Secret: [from setup output]
# Events: Push events
# Now push to GitHub → Auto-deploy! 🚀
```
## 💡 Examples
### Deploy Multiple Projects
```bash
# Each project gets its own URL path
sudo bash deploy-django-project.sh https://github.com/user/blog.git
sudo bash deploy-django-project.sh https://github.com/user/shop.git
sudo bash deploy-django-project.sh https://github.com/user/api.git
# Results:
# http://YOUR_VPS_IP/blog/
# http://YOUR_VPS_IP/shop/
# http://YOUR_VPS_IP/api/
```
### Project Management
```bash
# Check project status
systemctl status gunicorn-blog.service
systemctl status gunicorn-shop.service
# View project logs
journalctl -u gunicorn-blog.service -f
# Restart project
systemctl restart gunicorn-blog.service
# Update project
cd /var/www/blog && sudo -u django git pull && systemctl restart gunicorn-blog.service
```
## 🎯 Requirements
**Your Django Project Needs:**
- `requirements.txt` file
- Working `manage.py`
- Proper Django project structure
**VPS Requirements:**
- Ubuntu 20.04+ or similar
- Root/sudo access
- 1GB+ RAM recommended
## 🚨 Troubleshooting
**Deployment fails?**
```bash
# Check logs
journalctl -u gunicorn-PROJECT_NAME.service -f
# Test Django
cd /var/www/PROJECT_NAME
sudo -u django bash -c "source venv/bin/activate && python manage.py check"
# Test nginx
nginx -t
```
**Can't access site?**
```bash
# Check services
systemctl status gunicorn-PROJECT_NAME.service nginx
# Check firewall
ufw status
```
## 🎉 Success!
You now have a **universal Django deployment toolkit** that can deploy any Django project to VPS with zero configuration!
**Just provide a GitHub URL and get a working Django site!** 🚀

View File

View File

@ -1,21 +0,0 @@
from django.contrib import admin
from .models import Contact, BlogPost
@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
list_display = ['name', 'email', 'subject', 'created_at']
list_filter = ['created_at']
search_fields = ['name', 'email', 'subject']
readonly_fields = ['created_at']
ordering = ['-created_at']
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ['title', 'is_published', 'created_at', 'updated_at']
list_filter = ['is_published', 'created_at']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
readonly_fields = ['created_at', 'updated_at']
ordering = ['-created_at']

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'core'

View File

@ -1,27 +0,0 @@
from django import forms
from .models import Contact
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ['name', 'email', 'subject', 'message']
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Your Name'
}),
'email': forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'your.email@example.com'
}),
'subject': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Subject'
}),
'message': forms.Textarea(attrs={
'class': 'form-control',
'rows': 5,
'placeholder': 'Your message...'
}),
}

View File

@ -1,44 +0,0 @@
# Generated by Django 4.2.7 on 2025-08-29 16:12
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='BlogPost',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('slug', models.SlugField(unique=True)),
('content', models.TextField()),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_published', models.BooleanField(default=True)),
],
options={
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='Contact',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('email', models.EmailField(max_length=254)),
('subject', models.CharField(max_length=200)),
('message', models.TextField()),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
],
options={
'ordering': ['-created_at'],
},
),
]

View File

@ -1,31 +0,0 @@
from django.db import models
from django.utils import timezone
class Contact(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
subject = models.CharField(max_length=200)
message = models.TextField()
created_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.name} - {self.subject}"
class Meta:
ordering = ['-created_at']
class BlogPost(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
is_published = models.BooleanField(default=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-created_at']

View File

@ -1,30 +0,0 @@
.hero-section {
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
}
.card {
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.navbar-brand {
font-weight: bold;
}
footer {
margin-top: auto;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}

View File

@ -1,23 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
// Auto-hide alerts after 5 seconds
const alerts = document.querySelectorAll('.alert');
alerts.forEach(function(alert) {
setTimeout(function() {
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
}, 5000);
});
// Smooth scrolling for internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth'
});
}
});
});
});

View File

@ -1,63 +0,0 @@
{% extends 'core/base.html' %}
{% block title %}About - Django Demo Project{% endblock %}
{% block content %}
<div class="container py-5">
<div class="row">
<div class="col-md-8 mx-auto">
<h1 class="mb-4">About This Project</h1>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Purpose</h5>
<p class="card-text">This Django demo project is designed as a production-ready template for testing VPS deployment workflows. It serves as a foundation for future Django projects with all the essential configurations already in place.</p>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Features</h5>
<ul class="list-unstyled">
<li>✓ Minimal dependencies for fast deployment</li>
<li>✓ Environment-based configuration</li>
<li>✓ SQLite development / PostgreSQL production</li>
<li>✓ Static files handling with WhiteNoise</li>
<li>✓ Contact form functionality</li>
<li>✓ Simple blog system</li>
<li>✓ Bootstrap-based responsive UI</li>
<li>✓ Production security settings</li>
</ul>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Tech Stack</h5>
<div class="row">
<div class="col-md-6">
<ul class="list-unstyled">
<li><strong>Backend:</strong> Django 4.2.7</li>
<li><strong>Database:</strong> SQLite/PostgreSQL</li>
<li><strong>Server:</strong> Gunicorn</li>
</ul>
</div>
<div class="col-md-6">
<ul class="list-unstyled">
<li><strong>Frontend:</strong> Bootstrap 5</li>
<li><strong>Static Files:</strong> WhiteNoise</li>
<li><strong>Config:</strong> python-decouple</li>
</ul>
</div>
</div>
</div>
</div>
<div class="text-center">
<a href="{% url 'contact' %}" class="btn btn-primary">Get in Touch</a>
<a href="{% url 'home' %}" class="btn btn-outline-primary">Back to Home</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,63 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Django Demo Project{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
{% load static %}
<link rel="stylesheet" href="{% static 'core/css/style.css' %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">Django Demo</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'home' %}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog' %}">Blog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'about' %}">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'contact' %}">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<main>
{% if messages %}
<div class="container mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% block content %}
{% endblock %}
</main>
<footer class="bg-light text-center text-muted py-4 mt-5">
<div class="container">
<p>&copy; 2024 Django Demo Project. Built for VPS deployment testing.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
{% load static %}
<script src="{% static 'core/js/main.js' %}"></script>
</body>
</html>

View File

@ -1,31 +0,0 @@
{% extends 'core/base.html' %}
{% block title %}Blog - Django Demo Project{% endblock %}
{% block content %}
<div class="container py-5">
<h1 class="mb-4">Blog</h1>
{% if posts %}
<div class="row">
{% for post in posts %}
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">{{ post.title }}</h5>
<p class="card-text">{{ post.content|truncatewords:30 }}</p>
<small class="text-muted">Published on {{ post.created_at|date:"F d, Y" }}</small>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<h3 class="text-muted">No blog posts yet</h3>
<p>Blog posts will appear here once they're created in the admin panel.</p>
<a href="{% url 'home' %}" class="btn btn-primary">Back to Home</a>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -1,68 +0,0 @@
{% extends 'core/base.html' %}
{% block title %}Contact - Django Demo Project{% endblock %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h1 class="mb-4">Contact Us</h1>
<p class="lead">Have questions about this Django demo project? Get in touch!</p>
<div class="row">
<div class="col-md-8">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label">Name</label>
{{ form.name }}
{% if form.name.errors %}
<div class="text-danger">{{ form.name.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.email.id_for_label }}" class="form-label">Email</label>
{{ form.email }}
{% if form.email.errors %}
<div class="text-danger">{{ form.email.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.subject.id_for_label }}" class="form-label">Subject</label>
{{ form.subject }}
{% if form.subject.errors %}
<div class="text-danger">{{ form.subject.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="{{ form.message.id_for_label }}" class="form-label">Message</label>
{{ form.message }}
{% if form.message.errors %}
<div class="text-danger">{{ form.message.errors }}</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
</div>
<div class="col-md-4">
<h5>Project Info</h5>
<p><strong>Django Version:</strong> 4.2.7</p>
<p><strong>Purpose:</strong> VPS Deployment Testing</p>
<p><strong>Features:</strong></p>
<ul>
<li>Contact Form</li>
<li>Blog System</li>
<li>Environment Configuration</li>
<li>Production Ready</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,77 +0,0 @@
{% extends 'core/base.html' %}
{% block title %}Home - Django Demo Project{% endblock %}
{% block content %}
<div class="hero-section bg-primary text-white py-5">
<div class="container">
<div class="row">
<div class="col-lg-6">
<h1 class="display-4">Django Demo Project</h1>
<p class="lead">A production-ready Django application template optimized for VPS hosting deployment.</p>
<a href="{% url 'contact' %}" class="btn btn-light btn-lg">Get Started</a>
</div>
</div>
</div>
</div>
<div class="container py-5">
<div class="row">
<div class="col-md-8">
<h2>Features</h2>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Production Ready</h5>
<p class="card-text">Configured with environment variables, security settings, and optimized for deployment.</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Database Flexible</h5>
<p class="card-text">Works with SQLite for development and PostgreSQL for production.</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">VPS Optimized</h5>
<p class="card-text">Includes deployment scripts and configuration for easy VPS hosting.</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Minimal Dependencies</h5>
<p class="card-text">Clean, lightweight setup with only essential packages.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<h3>Recent Blog Posts</h3>
{% if blog_posts %}
{% for post in blog_posts %}
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">{{ post.title }}</h5>
<p class="card-text">{{ post.content|truncatewords:20 }}</p>
<small class="text-muted">{{ post.created_at|date:"M d, Y" }}</small>
</div>
</div>
{% endfor %}
<a href="{% url 'blog' %}" class="btn btn-outline-primary">View All Posts</a>
{% else %}
<p class="text-muted">No blog posts yet. Check back later!</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,9 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
path('contact/', views.contact, name='contact'),
path('blog/', views.blog, name='blog'),
path('about/', views.about, name='about'),
]

View File

@ -1,31 +0,0 @@
from django.shortcuts import render, redirect
from django.contrib import messages
from .models import BlogPost
from .forms import ContactForm
def home(request):
blog_posts = BlogPost.objects.filter(is_published=True)[:3]
return render(request, 'core/home.html', {'blog_posts': blog_posts})
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Thank you for your message! We will get back to you soon.')
return redirect('contact')
else:
form = ContactForm()
return render(request, 'core/contact.html', {'form': form})
def blog(request):
posts = BlogPost.objects.filter(is_published=True)
return render(request, 'core/blog.html', {'posts': posts})
def about(request):
return render(request, 'core/about.html')

View File

@ -1,16 +0,0 @@
"""
ASGI config for demo_project project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings')
application = get_asgi_application()

View File

@ -1,162 +0,0 @@
"""
Django settings for demo_project project.
Generated by 'django-admin startproject' using Django 4.2.7.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os
from pathlib import Path
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', default='django-insecure-%%73_#43bfj4u2^usxt&(hmx$*r!&@w6z7!r-psjm%gu_5(s35')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1', cast=lambda v: [s.strip() for s in v.split(',')])
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'core',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'demo_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'demo_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
# Database configuration
if config('DATABASE_URL', default=None):
import dj_database_url
DATABASES = {
'default': dj_database_url.parse(config('DATABASE_URL'))
}
elif config('USE_POSTGRES', default=False, cast=bool):
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME', default='demo_db'),
'USER': config('DB_USER', default='demo_user'),
'PASSWORD': config('DB_PASSWORD', default=''),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = []
# Whitenoise settings
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Production security settings
if not DEBUG:
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 31536000
SECURE_REDIRECT_EXEMPT = []
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=False, cast=bool)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

View File

@ -1,23 +0,0 @@
"""
URL configuration for demo_project project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('core.urls')),
]

View File

@ -1,16 +0,0 @@
"""
WSGI config for demo_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings')
application = get_wsgi_application()

View File

@ -163,8 +163,8 @@ DJANGO_PROJECT_DIR=$(sudo -u django find "$PROJECT_PATH" -name "settings.py" -ex
DJANGO_PROJECT_NAME=$(basename "$DJANGO_PROJECT_DIR")
# Create Gunicorn configuration
mkdir -p "$PROJECT_PATH/deploy"
cat > "$PROJECT_PATH/deploy/gunicorn.conf.py" << EOF
mkdir -p "$PROJECT_PATH/config"
cat > "$PROJECT_PATH/config/gunicorn.conf.py" << EOF
# Gunicorn configuration for $PROJECT_NAME
bind = "unix:/run/gunicorn-$PROJECT_NAME.sock"
workers = 3
@ -204,7 +204,7 @@ WorkingDirectory=$PROJECT_PATH
Environment=PYTHONPATH=$PROJECT_PATH
EnvironmentFile=$PROJECT_PATH/.env
ExecStart=$PROJECT_PATH/venv/bin/gunicorn \\
--config $PROJECT_PATH/deploy/gunicorn.conf.py \\
--config $PROJECT_PATH/config/gunicorn.conf.py \\
$DJANGO_PROJECT_NAME.wsgi:application
ExecReload=/bin/kill -s HUP \$MAINPID
KillMode=mixed

View File

@ -1,35 +0,0 @@
"""Gunicorn configuration for production deployment"""
import multiprocessing
import os
# Server socket
bind = "127.0.0.1:8000"
backlog = 2048
# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
max_requests = 1000
max_requests_jitter = 100
# Restart workers after this many requests, with up to max_requests_jitter additional requests
preload_app = True
# Logging
accesslog = "/var/log/django/access.log"
errorlog = "/var/log/django/error.log"
loglevel = "info"
# Process naming
proc_name = "django_demo_project"
# Server mechanics
daemon = False
pidfile = "/var/run/gunicorn/django_demo.pid"
user = "www-data"
group = "www-data"
tmp_upload_dir = None

View File

@ -1,66 +0,0 @@
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# 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;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Static files
location /static/ {
alias /home/ubuntu/django-demo/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Media files (if you add file uploads later)
location /media/ {
alias /home/ubuntu/django-demo/media/;
expires 1y;
add_header Cache-Control "public";
}
# Django application
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Block access to sensitive files
location ~* /\.(?!well-known\/) {
deny all;
}
location ~* /(requirements\.txt|\.env|deploy/) {
deny all;
}
}

View File

@ -1,22 +0,0 @@
[Unit]
Description=Django Demo Project Gunicorn daemon
After=network.target
[Service]
Type=notify
User=www-data
Group=www-data
RuntimeDirectory=gunicorn
WorkingDirectory=/home/ubuntu/django-demo
ExecStart=/home/ubuntu/django-demo/venv/bin/gunicorn --config /home/ubuntu/django-demo/deploy/gunicorn.conf.py demo_project.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
# Environment variables
Environment=DJANGO_SETTINGS_MODULE=demo_project.settings
EnvironmentFile=/home/ubuntu/django-demo/.env
[Install]
WantedBy=multi-user.target

View File

@ -1,248 +0,0 @@
#!/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)

View File

@ -1,162 +0,0 @@
#!/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}"

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@ -1,13 +0,0 @@
# Core Django
Django==4.2.7
# Configuration & Environment
python-decouple==3.8
dj-database-url==2.1.0
# Production Server & Static Files
gunicorn==21.2.0
whitenoise==6.6.0
# Database (PostgreSQL)
psycopg2-binary==2.9.7