Compare commits
9 Commits
fix/dev-wh
...
fix/prod-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9dab3e93b
|
||
|
|
349f1db721
|
||
| de56b564c5 | |||
|
|
833ff378ea
|
||
|
|
754b0ca5f6
|
||
| 03fcbdb5ad | |||
|
|
0cbac68ec1
|
||
| 4c27cfe1dd | |||
|
|
a598727888
|
@@ -2,6 +2,9 @@ name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
@@ -188,3 +191,15 @@ jobs:
|
||||
- name: Remove CI image
|
||||
if: always()
|
||||
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
|
||||
|
||||
@@ -4,10 +4,10 @@ DEBUG = True
|
||||
|
||||
INTERNAL_IPS = ["127.0.0.1"]
|
||||
|
||||
# In dev, drop WhiteNoise from middleware and use plain static file storage.
|
||||
# WhiteNoise serves from STATIC_ROOT which is empty without collectstatic,
|
||||
# so it intercepts every /static/ request and returns nothing.
|
||||
# Django's runserver handles static files natively when DEBUG=True.
|
||||
# Drop WhiteNoise in dev — it serves from STATIC_ROOT which is empty without
|
||||
# collectstatic, so it 404s every asset. Django's runserver serves static and
|
||||
# media files natively when DEBUG=True (via django.contrib.staticfiles + the
|
||||
# media URL pattern in urls.py).
|
||||
MIDDLEWARE = [m for m in MIDDLEWARE if m != "whitenoise.middleware.WhiteNoiseMiddleware"]
|
||||
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
|
||||
|
||||
|
||||
@@ -2,8 +2,16 @@ from .base import * # noqa
|
||||
|
||||
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")
|
||||
USE_X_FORWARDED_HOST = True
|
||||
SECURE_SSL_REDIRECT = True
|
||||
SECURE_SSL_REDIRECT = False
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
"https://nohypeai.net",
|
||||
"https://www.nohypeai.net",
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.generic import RedirectView
|
||||
@@ -21,3 +23,6 @@ urlpatterns = [
|
||||
path("admin/", RedirectView.as_view(url="/cms/", permanent=False)),
|
||||
path("", include(wagtail_urls)),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
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 -H "Host: nohypeai.net" 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
|
||||
22
deploy/entrypoint.prod.sh
Executable file
22
deploy/entrypoint.prod.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
python manage.py tailwind install --no-input
|
||||
python manage.py tailwind build
|
||||
python manage.py migrate --noinput
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
# Set Wagtail site hostname from first entry in ALLOWED_HOSTS
|
||||
python manage.py shell -c "
|
||||
from wagtail.models import Site
|
||||
import os
|
||||
hostname = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',')[0].strip()
|
||||
Site.objects.update(hostname=hostname, port=443, site_name='No Hype AI')
|
||||
"
|
||||
|
||||
exec gunicorn config.wsgi:application \
|
||||
--workers 3 \
|
||||
--bind 0.0.0.0:8000 \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
--capture-output
|
||||
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
|
||||
36
docker-compose.prod.yml
Normal file
36
docker-compose.prod.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
services:
|
||||
web:
|
||||
build: app
|
||||
working_dir: /app
|
||||
command: /app/deploy/entrypoint.prod.sh
|
||||
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: .
|
||||
working_dir: /app
|
||||
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 runserver 0.0.0.0:8000"
|
||||
volumes:
|
||||
|
||||
Reference in New Issue
Block a user