infra: cutover to Hugo blog + Forgejo Actions runner
- Add blog-static (nginx:alpine) service backed by bind-mount from /root/richardnixon.dev-hugo/public. Hugo blog now owns richardnixon.dev/* via Traefik dynamic.yml (priority 100). - Narrow platform-api (Django legacy) to /api, /admin, /static, /media only; remove platform-frontend (Next.js) from routing. - Add forgejo-runner service joining infrastructure_forgejo-internal network, with bind-mount config + entrypoint. Allowed valid_volume: /root/richardnixon.dev-hugo/public for CI deploys. - Enable [actions] in Forgejo (FORGEJO__actions__ENABLED=true, DEFAULT_ACTIONS_URL=https://code.forgejo.org). - Add export_hugo management command (HTML+frontmatter dump for the one-time content migration; left in tree for future re-runs). - Update README and docs/deployment.md to reflect new public surface, CI flow, and Forgejo clone URL.
This commit is contained in:
parent
9f90aca614
commit
1e8c7ccf6f
12 changed files with 340 additions and 35 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -73,3 +73,6 @@ celerybeat.pid
|
|||
# Secrets
|
||||
*.pem
|
||||
*.key
|
||||
|
||||
# Local Traefik backups (manual rollback safety nets)
|
||||
infrastructure/traefik/*.bak-*
|
||||
|
|
|
|||
50
README.md
50
README.md
|
|
@ -1,6 +1,8 @@
|
|||
# richardnixon.dev
|
||||
|
||||
Personal portfolio and blog platform built with Django, deployed with Docker.
|
||||
Self-hosted infrastructure (Traefik + Docker Compose) running every service on the `richardnixon.dev` apex and subdomains.
|
||||
|
||||
The public blog at `richardnixon.dev` is now a **static Hugo site** — its source lives in a separate repo: [`Richard/richardnixon.dev-hugo`](https://git.richardnixon.dev/Richard/richardnixon.dev-hugo). The Django app in `apps/` here is kept around as **legacy**: it still answers `/admin/`, `/api/`, `/media/`, `/static/` while the database is preserved, but its blog/portfolio/contact views are no longer the public surface.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
@ -9,7 +11,8 @@ graph TD
|
|||
DNS[Cloudflare DNS] --> Traefik[Traefik + SSL/TLS]
|
||||
Traefik --> CrowdSec[CrowdSec IPS]
|
||||
|
||||
Traefik --> Platform[Django Platform<br>richardnixon.dev]
|
||||
Traefik --> Blog[Hugo blog-static<br>richardnixon.dev]
|
||||
Traefik --> Platform[Django legacy<br>richardnixon.dev/admin·/api]
|
||||
Traefik --> WP[WordPress<br>richardemanu.com]
|
||||
Traefik --> LocFlow[LocFlow API<br>locflow.richardnixon.dev]
|
||||
Traefik --> EireScope[EireScope<br>eirescope.richardnixon.dev]
|
||||
|
|
@ -32,11 +35,13 @@ graph TD
|
|||
|
||||
| Service | Domain | Description |
|
||||
|---------|--------|-------------|
|
||||
| Django Platform | richardnixon.dev | Blog, portfolio, contact |
|
||||
| Hugo blog (blog-static) | richardnixon.dev | Static blog (Hugo + PaperMod) — content in [`Richard/richardnixon.dev-hugo`](https://git.richardnixon.dev/Richard/richardnixon.dev-hugo) |
|
||||
| Django legacy (platform-web) | richardnixon.dev/admin · /api | Admin + API for migration window; will be retired |
|
||||
| Forgejo Actions runner | (internal) | CI runner for Hugo blog deploy (joins `forgejo-internal` network) |
|
||||
| WordPress | richardemanu.com | Personal site |
|
||||
| LocFlow | locflow.richardnixon.dev | Localization automation platform (REST API) |
|
||||
| EireScope | eirescope.richardnixon.dev | OSINT dashboard |
|
||||
| Forgejo | git.richardnixon.dev | Self-hosted Git forge (public repos, SSO via Authentik) |
|
||||
| Forgejo | git.richardnixon.dev | Self-hosted Git forge (public repos, SSO via Authentik, Actions enabled) |
|
||||
| Umami | analytics.richardnixon.dev | Privacy-focused analytics |
|
||||
| Grafana | status.richardnixon.dev | Observability dashboards |
|
||||
| Portainer | portainer.richardnixon.dev | Docker management |
|
||||
|
|
@ -45,11 +50,18 @@ graph TD
|
|||
|
||||
## Technology Stack
|
||||
|
||||
### Application
|
||||
### Public blog (`richardnixon.dev`)
|
||||
- **Generator**: Hugo 0.123.7 extended + PaperMod theme (pinned `v8.0`)
|
||||
- **Server**: nginx:alpine (`blog-static` service), volume bind-mounted from `/root/richardnixon.dev-hugo/public/`
|
||||
- **CI**: Forgejo Actions on push to `main` of [`Richard/richardnixon.dev-hugo`](https://git.richardnixon.dev/Richard/richardnixon.dev-hugo)
|
||||
- **i18n**: PT-BR (default) + EN, native Hugo language config
|
||||
|
||||
### Legacy Django app (kept for `/admin` and `/api` only)
|
||||
- **Backend**: Django 5.x, Celery, PostgreSQL 16, Redis 7
|
||||
- **Frontend**: Django Templates, HTMX, TailwindCSS
|
||||
- **Auth**: django-allauth (Google/GitHub OAuth), django-two-factor-auth (2FA)
|
||||
- **i18n**: English + Portuguese (django-modeltranslation)
|
||||
- **Routes**: limited to `/api`, `/admin`, `/static`, `/media` via Traefik dynamic.yml (priority 110); blog/portfolio/contact views are dormant.
|
||||
|
||||
### Infrastructure
|
||||
- **Reverse Proxy**: Traefik v3 with Let's Encrypt SSL
|
||||
|
|
@ -137,10 +149,15 @@ richardnixon.dev/
|
|||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/richardnixondev/richardnixon.dev.git
|
||||
git clone https://git.richardnixon.dev/Richard/richardnixon.dev.git
|
||||
cd richardnixon.dev/infrastructure
|
||||
```
|
||||
|
||||
For the public blog content (Hugo source), clone the companion repo:
|
||||
```bash
|
||||
git clone --recurse-submodules https://git.richardnixon.dev/Richard/richardnixon.dev-hugo.git
|
||||
```
|
||||
|
||||
2. Create environment file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
|
|
@ -376,17 +393,18 @@ docker exec valheim backup
|
|||
docker logs valheim 2>&1 | grep "Got connection"
|
||||
```
|
||||
|
||||
## URL Routes
|
||||
## URL Routes (post-Hugo cutover)
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `/` | Blog home |
|
||||
| `/admin/` | Django admin (2FA enabled) |
|
||||
| `/accounts/` | Authentication (OAuth) |
|
||||
| `/portfolio/` | Projects showcase |
|
||||
| `/contact/` | Contact form (reCAPTCHA) |
|
||||
| `/sitemap.xml` | XML sitemap |
|
||||
| `/feed/` | RSS feed |
|
||||
Routing is configured in `infrastructure/traefik/dynamic.yml`.
|
||||
|
||||
| Path | Backend | Notes |
|
||||
|------|---------|-------|
|
||||
| `/` | blog-static (Hugo) | Redirects to `/pt-br/` (default content language) |
|
||||
| `/pt-br/...`, `/en/...` | blog-static (Hugo) | Bilingual blog content |
|
||||
| `/feed.xml`, `/sitemap.xml` | blog-static (Hugo) | Per-language RSS and sitemap index |
|
||||
| `/admin/` | platform-web (Django) | Admin interface (2FA) — priority 110 |
|
||||
| `/api/` | platform-web (Django) | REST stub for legacy clients |
|
||||
| `/static/`, `/media/` | platform-web (Django) | Legacy static/media uploads |
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
0
apps/blog/management/__init__.py
Normal file
0
apps/blog/management/__init__.py
Normal file
0
apps/blog/management/commands/__init__.py
Normal file
0
apps/blog/management/commands/__init__.py
Normal file
131
apps/blog/management/commands/export_hugo.py
Normal file
131
apps/blog/management/commands/export_hugo.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
"""Export BlogPost rows to Hugo-ready HTML files with YAML frontmatter.
|
||||
|
||||
Output tree:
|
||||
<out>/pt-br/posts/<slug>.html
|
||||
<out>/en/posts/<slug>.html
|
||||
|
||||
Posts with empty content for a given language are skipped silently.
|
||||
Frontmatter contains: title, date, slug, tags, description, draft.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from apps.blog.models import BlogPost
|
||||
|
||||
|
||||
LANGS = ("pt-br", "en")
|
||||
|
||||
|
||||
def lang_attr(post, field, lang):
|
||||
"""Read django-modeltranslation field, e.g. title_en / content_pt_br."""
|
||||
suffix = lang.replace("-", "_")
|
||||
return getattr(post, f"{field}_{suffix}", None)
|
||||
|
||||
|
||||
def safe_title(value, fallback):
|
||||
cleaned = (value or "").strip()
|
||||
return cleaned or fallback
|
||||
|
||||
|
||||
def build_frontmatter(post, lang):
|
||||
title = safe_title(lang_attr(post, "title", lang), post.title)
|
||||
description = (lang_attr(post, "meta_description", lang) or post.meta_description or "").strip()
|
||||
if not description:
|
||||
excerpt = (lang_attr(post, "excerpt", lang) or post.excerpt or "").strip()
|
||||
description = excerpt[:200]
|
||||
|
||||
date = post.published_at or post.created_at
|
||||
tags = sorted(post.tags.values_list("name", flat=True))
|
||||
|
||||
return {
|
||||
"title": title,
|
||||
"date": date.isoformat() if date else None,
|
||||
"slug": post.slug,
|
||||
"tags": list(tags),
|
||||
"description": description,
|
||||
"draft": post.status != BlogPost.Status.PUBLISHED,
|
||||
}
|
||||
|
||||
|
||||
def _yaml_scalar(value):
|
||||
"""Quote scalars safely; bare-emit None as empty string."""
|
||||
if value is None:
|
||||
return '""'
|
||||
s = str(value)
|
||||
return json.dumps(s, ensure_ascii=False)
|
||||
|
||||
|
||||
def render_file(frontmatter, html_body):
|
||||
lines = ["---"]
|
||||
for key, value in frontmatter.items():
|
||||
if isinstance(value, list):
|
||||
if not value:
|
||||
lines.append(f"{key}: []")
|
||||
else:
|
||||
lines.append(f"{key}:")
|
||||
for item in value:
|
||||
lines.append(f" - {_yaml_scalar(item)}")
|
||||
elif isinstance(value, bool):
|
||||
lines.append(f"{key}: {'true' if value else 'false'}")
|
||||
else:
|
||||
lines.append(f"{key}: {_yaml_scalar(value)}")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
return "\n".join(lines) + "\n" + html_body + "\n"
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Export published blog posts to Hugo-compatible HTML files."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
default="/tmp/hugo-export",
|
||||
help="Output directory (default: /tmp/hugo-export)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-private",
|
||||
action="store_true",
|
||||
help="Include private posts (default: only public).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-drafts",
|
||||
action="store_true",
|
||||
help="Include drafts (marks them draft:true in frontmatter).",
|
||||
)
|
||||
|
||||
def handle(self, *args, **opts):
|
||||
out_root = Path(opts["out"])
|
||||
if opts["include_drafts"]:
|
||||
qs = BlogPost.objects.all()
|
||||
else:
|
||||
qs = BlogPost.objects.filter(status=BlogPost.Status.PUBLISHED)
|
||||
if not opts["include_private"]:
|
||||
qs = qs.filter(is_private=False)
|
||||
|
||||
written = {lang: 0 for lang in LANGS}
|
||||
skipped = {lang: 0 for lang in LANGS}
|
||||
|
||||
for post in qs.iterator():
|
||||
for lang in LANGS:
|
||||
content = lang_attr(post, "content", lang) or ""
|
||||
if not content.strip():
|
||||
skipped[lang] += 1
|
||||
continue
|
||||
|
||||
fm = build_frontmatter(post, lang)
|
||||
rendered = render_file(fm, content)
|
||||
|
||||
target = out_root / lang / "posts" / f"{post.slug}.html"
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(rendered, encoding="utf-8")
|
||||
written[lang] += 1
|
||||
|
||||
for lang in LANGS:
|
||||
self.stdout.write(
|
||||
f"[{lang}] wrote {written[lang]} files, skipped {skipped[lang]} (empty)"
|
||||
)
|
||||
self.stdout.write(self.style.SUCCESS(f"Done -> {out_root}"))
|
||||
|
|
@ -21,6 +21,7 @@ ALLOWED_HOSTS = [
|
|||
'richardnixon.dev',
|
||||
'www.richardnixon.dev',
|
||||
'localhost',
|
||||
'platform-web',
|
||||
]
|
||||
|
||||
# CSRF trusted origins for Traefik proxy
|
||||
|
|
|
|||
|
|
@ -18,10 +18,17 @@ curl -fsSL https://get.docker.com | sh
|
|||
|
||||
```bash
|
||||
cd /root
|
||||
git clone git@github.com:richardnixondev/richardnixon.dev.git
|
||||
git clone https://git.richardnixon.dev/Richard/richardnixon.dev.git
|
||||
cd richardnixon.dev
|
||||
```
|
||||
|
||||
For the public blog content (Hugo + PaperMod):
|
||||
```bash
|
||||
cd /root
|
||||
git clone --recurse-submodules https://git.richardnixon.dev/Richard/richardnixon.dev-hugo.git
|
||||
```
|
||||
The `blog-static` service in the infra compose expects this checkout at `/root/richardnixon.dev-hugo/` (bind-mounts `public/` into the nginx container).
|
||||
|
||||
## 3. Configure environment
|
||||
|
||||
```bash
|
||||
|
|
@ -48,7 +55,7 @@ cd infrastructure
|
|||
docker compose up -d
|
||||
```
|
||||
|
||||
This starts all services: Traefik, PostgreSQL, Redis, Django, Celery, WordPress, Umami, Grafana, Prometheus, LocFlow, EireScope, Authentik, Forgejo.
|
||||
This starts all services: Traefik, PostgreSQL, Redis, Django (legacy), Celery (legacy), Hugo blog-static, Forgejo + runner, WordPress, Umami, Grafana, Prometheus, LocFlow, EireScope, Authentik.
|
||||
|
||||
Traefik automatically provisions Let's Encrypt SSL certificates.
|
||||
|
||||
|
|
@ -89,22 +96,32 @@ curl https://richardnixon.dev/health/
|
|||
2. Under **Social applications**, add Google and/or GitHub OAuth providers
|
||||
3. Get credentials from Google Cloud Console / GitHub Developer Settings
|
||||
|
||||
## 9. Set up GitHub Actions CI/CD (optional)
|
||||
## 9. Set up Forgejo Actions CI/CD for the Hugo blog
|
||||
|
||||
Add the following secrets to the GitHub repository (Settings > Secrets > Actions):
|
||||
A `forgejo-runner` container is included in the compose and runs jobs on the host's Docker socket. After the first `docker compose up -d`:
|
||||
|
||||
| Secret | Value |
|
||||
|--------|-------|
|
||||
| `VPS_HOST` | VPS IP address |
|
||||
| `VPS_USER` | `root` |
|
||||
| `VPS_SSH_KEY` | SSH private key |
|
||||
| `DJANGO_SECRET_KEY` | Same as in `.env` |
|
||||
1. Generate a registration token inside the Forgejo container:
|
||||
```bash
|
||||
docker exec -u 1000 forgejo forgejo forgejo-cli actions generate-runner-token
|
||||
```
|
||||
2. Save it to `infrastructure/.env` as `FORGEJO_RUNNER_REGISTRATION_TOKEN`.
|
||||
3. Recreate the runner: `docker compose up -d --force-recreate forgejo-runner`. It auto-registers via `forgejo-runner/entrypoint.sh`.
|
||||
4. Verify the runner appears as **online** at `https://git.richardnixon.dev/-/admin/actions/runners`.
|
||||
|
||||
After this, every push to `main` will automatically deploy.
|
||||
The deploy workflow lives in the **Hugo repo** (`richardnixon.dev-hugo/.forgejo/workflows/deploy.yml`) — it builds Hugo in a `catthehacker/ubuntu:act-22.04` container and copies the output into the `blog-static` bind-mount on the host. The runner config (`infrastructure/forgejo-runner/config.yml`) authorises only one host volume: `/root/richardnixon.dev-hugo/public`.
|
||||
|
||||
Key non-obvious config bits (see `infrastructure/forgejo-runner/config.yml`):
|
||||
- `container.network: infrastructure_forgejo-internal` — required so job containers can resolve the internal `forgejo` hostname for checkout.
|
||||
- `container.docker_host: unix:///var/run/docker.sock` — `"automatic"` is not accepted by recent runner versions.
|
||||
- `valid_volumes: ["/root/richardnixon.dev-hugo/public"]` — the only mount the workflow is allowed to write to.
|
||||
|
||||
After a push to `main` on `richardnixon.dev-hugo`, the runner builds and the site is live within ~30–60s.
|
||||
|
||||
## Updating
|
||||
|
||||
Manual update:
|
||||
**Blog content** (`richardnixon.dev`): edit Markdown in the Hugo repo, `git push origin main`, Forgejo Actions deploys automatically. See `Richard/richardnixon.dev-hugo/.forgejo/workflows/deploy.yml`.
|
||||
|
||||
**Infrastructure + legacy Django**:
|
||||
|
||||
```bash
|
||||
cd /root/richardnixon.dev
|
||||
|
|
@ -121,8 +138,8 @@ All services are behind Traefik (ports 80/443). No service exposes ports directl
|
|||
|
||||
| Service | Internal port | URL |
|
||||
|---------|--------------|-----|
|
||||
| Django platform | 8000 | richardnixon.dev |
|
||||
| Next.js frontend | 3000 | richardnixon.dev (via Traefik routing) |
|
||||
| Hugo blog-static (nginx) | 80 | richardnixon.dev (catch-all, priority 100) |
|
||||
| Django platform (legacy) | 8000 | richardnixon.dev/{admin,api,static,media} (priority 110) |
|
||||
| LocFlow API | 8000 | locflow.richardnixon.dev |
|
||||
| EireScope | 5000 | osint.richardnixon.dev |
|
||||
| WordPress | 80 | richardemanu.com |
|
||||
|
|
|
|||
38
infrastructure/blog-static/nginx.conf
Normal file
38
infrastructure/blog-static/nginx.conf
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
charset utf-8;
|
||||
gzip on;
|
||||
gzip_types text/plain text/css text/xml application/xml application/rss+xml application/atom+xml application/json application/javascript;
|
||||
gzip_min_length 256;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ $uri/index.html =404;
|
||||
}
|
||||
|
||||
location ~* \.(css|js|woff2?|ttf|otf|svg|ico|png|jpe?g|gif|webp|avif)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location ~* \.(xml|rss)$ {
|
||||
expires 1h;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
}
|
||||
|
||||
location ~* \.html$ {
|
||||
expires 5m;
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
}
|
||||
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
server_tokens off;
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin";
|
||||
}
|
||||
|
|
@ -215,6 +215,49 @@ services:
|
|||
networks:
|
||||
- platform-internal
|
||||
|
||||
# ===========================================
|
||||
# FORGEJO RUNNER - CI for self-hosted git
|
||||
# ===========================================
|
||||
forgejo-runner:
|
||||
image: code.forgejo.org/forgejo/runner:6.2.2
|
||||
container_name: forgejo-runner
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
forgejo:
|
||||
condition: service_healthy
|
||||
user: root
|
||||
environment:
|
||||
FORGEJO_INSTANCE_URL: http://forgejo:3000
|
||||
FORGEJO_RUNNER_REGISTRATION_TOKEN: ${FORGEJO_RUNNER_REGISTRATION_TOKEN}
|
||||
FORGEJO_RUNNER_NAME: vps-runner
|
||||
volumes:
|
||||
- forgejo-runner-data:/data
|
||||
- ./forgejo-runner/config.yml:/data/config.yml:ro
|
||||
- ./forgejo-runner/entrypoint.sh:/entrypoint.sh:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- forgejo-internal
|
||||
- web
|
||||
entrypoint: ["/bin/sh", "/entrypoint.sh"]
|
||||
|
||||
# ===========================================
|
||||
# BLOG STATIC (Hugo) - richardnixon.dev (cutover-ready, no Traefik labels yet)
|
||||
# ===========================================
|
||||
blog-static:
|
||||
image: nginx:alpine
|
||||
container_name: blog-static
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /root/richardnixon.dev-hugo/public:/usr/share/nginx/html:ro
|
||||
- ./blog-static/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- web
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost/"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
# ===========================================
|
||||
# UMAMI ANALYTICS
|
||||
# ===========================================
|
||||
|
|
@ -743,6 +786,8 @@ services:
|
|||
FORGEJO__log__LEVEL: info
|
||||
FORGEJO__openid__ENABLE_OPENID_SIGNIN: "false"
|
||||
FORGEJO__openid__ENABLE_OPENID_SIGNUP: "false"
|
||||
FORGEJO__actions__ENABLED: "true"
|
||||
FORGEJO__actions__DEFAULT_ACTIONS_URL: "https://code.forgejo.org"
|
||||
volumes:
|
||||
- forgejo-data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
|
|
@ -816,3 +861,4 @@ volumes:
|
|||
authentik-templates:
|
||||
forgejo-data:
|
||||
forgejo-db-data:
|
||||
forgejo-runner-data:
|
||||
|
|
|
|||
30
infrastructure/forgejo-runner/config.yml
Normal file
30
infrastructure/forgejo-runner/config.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: /data/.runner
|
||||
capacity: 2
|
||||
timeout: 30m
|
||||
insecure: false
|
||||
fetch_timeout: 5s
|
||||
fetch_interval: 2s
|
||||
labels:
|
||||
- "docker:docker://node:20-bookworm"
|
||||
- "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
|
||||
|
||||
cache:
|
||||
enabled: true
|
||||
dir: /data/.cache
|
||||
|
||||
container:
|
||||
network: "infrastructure_forgejo-internal"
|
||||
privileged: false
|
||||
options: ""
|
||||
workdir_parent: ""
|
||||
valid_volumes:
|
||||
- "/root/richardnixon.dev-hugo/public"
|
||||
docker_host: "unix:///var/run/docker.sock"
|
||||
force_pull: false
|
||||
|
||||
host:
|
||||
workdir_parent: ""
|
||||
15
infrastructure/forgejo-runner/entrypoint.sh
Executable file
15
infrastructure/forgejo-runner/entrypoint.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ ! -f /data/.runner ]; then
|
||||
echo "registering runner..."
|
||||
forgejo-runner register \
|
||||
--no-interactive \
|
||||
--instance "${FORGEJO_INSTANCE_URL}" \
|
||||
--token "${FORGEJO_RUNNER_REGISTRATION_TOKEN}" \
|
||||
--name "${FORGEJO_RUNNER_NAME}" \
|
||||
--labels "docker:docker://node:20-bookworm,ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
|
||||
echo "registered."
|
||||
fi
|
||||
|
||||
exec forgejo-runner daemon -c /data/config.yml
|
||||
|
|
@ -74,9 +74,9 @@ http:
|
|||
|
||||
# === PUBLIC ROUTERS (crowdsec only) ===
|
||||
|
||||
# Django API + admin + media + feed + sitemap
|
||||
# Django admin/API only (narrowed after Hugo cutover; Hugo owns blog content)
|
||||
platform-api:
|
||||
rule: "(Host(`richardnixon.dev`) || Host(`www.richardnixon.dev`)) && (PathPrefix(`/api`) || PathPrefix(`/admin`) || PathPrefix(`/media`) || PathPrefix(`/feed`) || PathPrefix(`/sitemap.xml`) || PathPrefix(`/ckeditor5`) || PathPrefix(`/i18n`) || PathPrefix(`/en/`) || PathPrefix(`/pt-br/`) || PathPrefix(`/static`))"
|
||||
rule: "(Host(`richardnixon.dev`) || Host(`www.richardnixon.dev`)) && (PathPrefix(`/api`) || PathPrefix(`/admin`) || PathPrefix(`/static`) || PathPrefix(`/media`))"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: platform-api
|
||||
|
|
@ -86,14 +86,15 @@ http:
|
|||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
# Next.js frontend (everything else)
|
||||
platform-frontend:
|
||||
# Hugo static blog (catch-all for richardnixon.dev / www.richardnixon.dev)
|
||||
blog-static:
|
||||
rule: "Host(`richardnixon.dev`) || Host(`www.richardnixon.dev`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: platform-frontend
|
||||
service: blog-static
|
||||
middlewares:
|
||||
- crowdsec-bouncer
|
||||
priority: 100
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
|
|
@ -206,6 +207,11 @@ http:
|
|||
servers:
|
||||
- url: "http://platform-frontend:3000"
|
||||
|
||||
blog-static:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://blog-static:80"
|
||||
|
||||
wordpress:
|
||||
loadBalancer:
|
||||
servers:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue