Add devigo deployment role for mini-vps production environment

- Created comprehensive devigo Ansible role with Podman Quadlet support
- Deployed devigo-site container (Hugo + nginx) via systemd
- Deployed devigo-decap-oauth OAuth2 proxy for Decap CMS
- Integrated with Caddy reverse proxy for HTTPS

Services deployed:
- devigo.no (apex domain, primary)
- www.devigo.no (redirects to apex)
- decap.jnss.me (OAuth proxy)

Key features:
- REGISTRY_AUTH_FILE environment for Podman GHCR authentication
- TRUSTED_ORIGINS (plural) for decapcms-oauth2 multi-origin support
- JavaScript-based Decap CMS initialization (eliminates YAML MIME dependency)
- nginx location block for YAML MIME type (text/yaml)
- Automated deployment via GitHub Actions CI/CD
- Comprehensive documentation with troubleshooting guide
- Architecture decision records

Fixes applied during deployment:
- OAuth origin trust validation (TRUSTED_ORIGINS vs TRUSTED_ORIGIN)
- MIME type handling strategy (location-specific vs server-level types block)
- Decap CMS initialization method (JavaScript vs link tag)
- Podman authentication for systemd services (REGISTRY_AUTH_FILE)

Testing status:
-  MIME types verified (HTML, CSS, YAML all correct)
-  OAuth authentication working
-  Container image pulls from private GHCR
-  Automated deployments functional
-  Site fully operational at devigo.no
This commit is contained in:
2025-12-16 00:53:33 +01:00
parent ecbeb07ba2
commit 1350d10a7c
12 changed files with 968 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
# Decap CMS OAuth2 Proxy
# Generated by Ansible - DO NOT EDIT MANUALLY
{{ devigo_oauth_domain }} {
reverse_proxy 127.0.0.1:{{ devigo_oauth_container_port }}
# Security headers
header {
X-Frame-Options DENY
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
}
# Logging
log {
output file /var/log/caddy/devigo-decap-oauth.log {
roll_size 100mb
roll_keep 5
}
format json {
time_format "2006-01-02T15:04:05.000Z07:00"
}
level INFO
}
}

View File

@@ -0,0 +1,25 @@
[Unit]
Description=Decap CMS OAuth2 Proxy for Devigo
After=network-online.target
Wants=network-online.target
[Container]
Image={{ devigo_oauth_container_image }}
ContainerName={{ devigo_oauth_container_name }}
AutoUpdate=registry
# Environment file with secrets
EnvironmentFile={{ devigo_oauth_home }}/decap-oauth.env
# Network
PublishPort=127.0.0.1:{{ devigo_oauth_container_port }}:12000
# Security
NoNewPrivileges=true
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,10 @@
# Decap OAuth Environment Configuration
# Generated by Ansible - DO NOT EDIT MANUALLY
OAUTH_CLIENT_ID={{ devigo_oauth_client_id }}
OAUTH_CLIENT_SECRET={{ devigo_oauth_client_secret }}
SERVER_PORT=12000
# TRUSTED_ORIGINS must be comma-separated string (not YAML array)
# Example: https://example.com,https://www.example.com
TRUSTED_ORIGINS={{ devigo_oauth_trusted_origins }}

View File

@@ -0,0 +1,31 @@
[Unit]
Description=Devigo Website - Sales Training Company
After=network-online.target caddy.service
Wants=network-online.target
Requires=caddy.service
[Container]
Image=ghcr.io/jnschaffer/rustan:prod
ContainerName=devigo-site
AutoUpdate=registry
Pull=newer
# Port mapping - publish to localhost only
PublishPort=127.0.0.1:9080:80
# Security
NoNewPrivileges=true
# Health check - check if nginx is responding
HealthCmd=/usr/bin/curl -f http://localhost:80/ || exit 1
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
Environment=REGISTRY_AUTH_FILE=/etc/containers/auth.json
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,33 @@
# Devigo Website - Reverse Proxy to Containerized Site
# Generated by Ansible - DO NOT EDIT MANUALLY
# Redirect www to apex (apex is primary per user preference)
{{ devigo_www_domain }} {
redir https://{{ devigo_domain }}{uri} permanent
}
# Primary domain (apex)
{{ devigo_domain }} {
reverse_proxy localhost:9080
# Security headers
header {
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "geolocation=(), microphone=(), camera=()"
}
# Logging
log {
output file /var/log/caddy/devigo.log {
roll_size 100mb
roll_keep 5
}
format json {
time_format "2006-01-02T15:04:05.000Z07:00"
}
level INFO
}
}