Compare commits
7 Commits
fix/dev-st
...
feat/deplo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
833ff378ea
|
||
|
|
754b0ca5f6
|
||
|
|
0cbac68ec1
|
||
| 4c27cfe1dd | |||
|
|
a598727888
|
||
| 36eb0f1dd2 | |||
|
|
f950e3cd5e
|
@@ -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
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ DEBUG = True
|
|||||||
|
|
||||||
INTERNAL_IPS = ["127.0.0.1"]
|
INTERNAL_IPS = ["127.0.0.1"]
|
||||||
|
|
||||||
# Use plain static file storage in dev — CompressedManifestStaticFilesStorage
|
# Drop WhiteNoise in dev — it serves from STATIC_ROOT which is empty without
|
||||||
# (set in base.py) requires collectstatic to have been run and will 404 on
|
# collectstatic, so it 404s every asset. Django's runserver serves static and
|
||||||
# every asset otherwise.
|
# 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"
|
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -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",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
@@ -21,3 +23,6 @@ urlpatterns = [
|
|||||||
path("admin/", RedirectView.as_view(url="/cms/", permanent=False)),
|
path("admin/", RedirectView.as_view(url="/cms/", permanent=False)),
|
||||||
path("", include(wagtail_urls)),
|
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 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
|
||||||
14
deploy/entrypoint.prod.sh
Executable file
14
deploy/entrypoint.prod.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
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: .
|
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