Merge pull request 'feat: production deploy pipeline' (#14) from feat/deploy-pipeline into main
Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
@@ -2,6 +2,9 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 2 * * *"
|
- cron: "0 2 * * *"
|
||||||
|
|
||||||
@@ -188,3 +191,15 @@ jobs:
|
|||||||
- name: Remove CI image
|
- name: Remove CI image
|
||||||
if: always()
|
if: always()
|
||||||
run: docker image rm -f "$CI_IMAGE" || true
|
run: docker image rm -f "$CI_IMAGE" || true
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to lintel-prod-01
|
||||||
|
uses: appleboy/ssh-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.PROD_SSH_HOST }}
|
||||||
|
username: deploy
|
||||||
|
key: ${{ secrets.PROD_SSH_KEY }}
|
||||||
|
script: bash /srv/sum/nohype/app/deploy/deploy.sh
|
||||||
|
|||||||
@@ -2,8 +2,16 @@ from .base import * # noqa
|
|||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
|
# Behind Caddy: trust the forwarded proto header so Django knows it's HTTPS.
|
||||||
|
# SECURE_SSL_REDIRECT is intentionally off — Caddy handles HTTPS redirects
|
||||||
|
# before the request reaches Django; enabling it here causes redirect loops.
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||||
USE_X_FORWARDED_HOST = True
|
USE_X_FORWARDED_HOST = True
|
||||||
SECURE_SSL_REDIRECT = True
|
SECURE_SSL_REDIRECT = False
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
CSRF_COOKIE_SECURE = True
|
CSRF_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
|
"https://nohypeai.net",
|
||||||
|
"https://www.nohypeai.net",
|
||||||
|
]
|
||||||
|
|||||||
23
deploy/caddy/nohype.caddy
Normal file
23
deploy/caddy/nohype.caddy
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
nohypeai.net, www.nohypeai.net {
|
||||||
|
encode gzip zstd
|
||||||
|
|
||||||
|
header {
|
||||||
|
X-Content-Type-Options nosniff
|
||||||
|
X-Frame-Options DENY
|
||||||
|
Referrer-Policy strict-origin-when-cross-origin
|
||||||
|
Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||||
|
X-Forwarded-Proto https
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_path /static/* {
|
||||||
|
root * /srv/sum/nohype/static
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_path /media/* {
|
||||||
|
root * /srv/sum/nohype/media
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_proxy localhost:8001
|
||||||
|
}
|
||||||
34
deploy/deploy.sh
Executable file
34
deploy/deploy.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Deploy script for No Hype AI — runs on lintel-prod-01 as deploy user.
|
||||||
|
# Called by CI after a successful push to main.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SITE_DIR=/srv/sum/nohype
|
||||||
|
APP_DIR=${SITE_DIR}/app
|
||||||
|
|
||||||
|
echo "==> Pulling latest code"
|
||||||
|
git -C "${APP_DIR}" pull origin main
|
||||||
|
|
||||||
|
echo "==> Updating compose file"
|
||||||
|
cp "${APP_DIR}/docker-compose.prod.yml" "${SITE_DIR}/docker-compose.prod.yml"
|
||||||
|
|
||||||
|
echo "==> Ensuring static/media directories exist"
|
||||||
|
mkdir -p "${SITE_DIR}/static" "${SITE_DIR}/media"
|
||||||
|
|
||||||
|
echo "==> Building image"
|
||||||
|
docker compose -f "${SITE_DIR}/docker-compose.prod.yml" build --no-cache
|
||||||
|
|
||||||
|
echo "==> Restarting service"
|
||||||
|
sudo systemctl restart sum-nohype
|
||||||
|
|
||||||
|
echo "==> Waiting for health check"
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if curl -fsS http://localhost:8001/ >/dev/null 2>&1; then
|
||||||
|
echo "==> Site is up"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
echo "ERROR: site did not come up after 90s" >&2
|
||||||
|
sudo journalctl -u sum-nohype --no-pager -n 50
|
||||||
|
exit 1
|
||||||
26
deploy/sum-nohype.service
Normal file
26
deploy/sum-nohype.service
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=No Hype AI (Docker Compose)
|
||||||
|
Requires=docker.service
|
||||||
|
After=docker.service network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=deploy
|
||||||
|
Group=www-data
|
||||||
|
WorkingDirectory=/srv/sum/nohype
|
||||||
|
|
||||||
|
ExecStartPre=docker compose -f docker-compose.prod.yml pull --ignore-pull-failures
|
||||||
|
ExecStart=docker compose -f docker-compose.prod.yml up --build
|
||||||
|
ExecStop=docker compose -f docker-compose.prod.yml down
|
||||||
|
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
TimeoutStartSec=300
|
||||||
|
TimeoutStopSec=30
|
||||||
|
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=sum-nohype
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
46
docker-compose.prod.yml
Normal file
46
docker-compose.prod.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
working_dir: /app
|
||||||
|
command: >
|
||||||
|
sh -c "python manage.py tailwind install --no-input &&
|
||||||
|
python manage.py tailwind build &&
|
||||||
|
python manage.py migrate --noinput &&
|
||||||
|
python manage.py collectstatic --noinput &&
|
||||||
|
gunicorn config.wsgi:application
|
||||||
|
--workers 3
|
||||||
|
--bind 0.0.0.0:8000
|
||||||
|
--access-logfile -
|
||||||
|
--error-logfile -
|
||||||
|
--capture-output"
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
DJANGO_SETTINGS_MODULE: config.settings.production
|
||||||
|
volumes:
|
||||||
|
- /srv/sum/nohype/static:/app/staticfiles
|
||||||
|
- /srv/sum/nohype/media:/app/media
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8001:8000"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: nohype
|
||||||
|
POSTGRES_USER: nohype
|
||||||
|
volumes:
|
||||||
|
- nohype_pg:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U nohype -d nohype"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
nohype_pg:
|
||||||
@@ -3,7 +3,9 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
command: >
|
command: >
|
||||||
sh -c "python manage.py migrate --noinput &&
|
sh -c "python manage.py tailwind install --no-input &&
|
||||||
|
python manage.py tailwind build &&
|
||||||
|
python manage.py migrate --noinput &&
|
||||||
python manage.py seed_e2e_content &&
|
python manage.py seed_e2e_content &&
|
||||||
python manage.py runserver 0.0.0.0:8000"
|
python manage.py runserver 0.0.0.0:8000"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Reference in New Issue
Block a user