Files
sanspie a46347e2c6
ci-platforms / darwin arm64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m19s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 23m57s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
editor: add art studio and stage 12.5 workflows
2026-11-08 18:47:57 +03:00

58 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Render a new isolated vhost configuration. Never edits installed nginx, DNS,
TLS credentials, or the Docs/Demo vhosts. Run nginx -t before installation."""
import argparse, json, re
from pathlib import Path
from urllib.parse import urlparse
p = argparse.ArgumentParser()
p.add_argument('--root', required=True, type=Path)
p.add_argument('--app-origin', required=True)
p.add_argument('--docs-origin', required=True)
p.add_argument('--listen', default='443')
p.add_argument('--certificate', type=Path)
p.add_argument('--key', type=Path)
p.add_argument('--development', action='store_true')
p.add_argument('--output', required=True, type=Path)
a = p.parse_args()
safe_path = re.compile('^/[A-Za-z0-9/._-]+$')
for path in [a.root] + ([a.certificate, a.key] if a.certificate and a.key else []):
if not safe_path.fullmatch(str(path)):
raise RuntimeError('Use an absolute path without nginx metacharacters')
if not a.root.resolve() == a.root:
raise RuntimeError('Deployment root must not be a symlink')
marker = json.loads((a.root / '.aego-asset-studio-root.json').read_text())
if not (marker['schema'] == 1 and marker['development'] == a.development):
raise RuntimeError('Deployment root mode mismatch')
origins = []
for raw in [a.app_origin, a.docs_origin]:
u = urlparse(raw)
if not (u.scheme in (['http', 'https'] if a.development else ['https']) and u.netloc):
raise RuntimeError('Required validation failed')
if not (not u.path and (not u.query) and (not u.fragment) and (not u.username) and (not u.password)):
raise RuntimeError('Required validation failed')
if not re.fullmatch('[A-Za-z0-9.:-]+', u.netloc):
raise RuntimeError('Invalid origin')
origins.append(u)
if not re.fullmatch('(?:127\\.0\\.0\\.1:)?[0-9]{1,5}', a.listen):
raise RuntimeError('Invalid listen address')
if a.development:
if not a.listen.startswith('127.0.0.1:'):
raise RuntimeError('Development nginx must be loopback-only')
tls = ''
listen = a.listen
else:
if not (a.certificate and a.key):
raise RuntimeError('Reuse provisioned TLS certificate/key paths')
if not (a.certificate.is_file() and a.key.is_file()):
raise RuntimeError('TLS files must already exist')
listen = a.listen + ' ssl'
tls = f'ssl_certificate {a.certificate};\n ssl_certificate_key {a.key};\n ssl_protocols TLSv1.2 TLSv1.3;'
source = Path(__file__).with_name('nginx-site.conf.in').read_text()
for key, value in {'ROOT': str(a.root), 'APP_ORIGIN': a.app_origin, 'DOCS_ORIGIN': a.docs_origin, 'LISTEN': listen, 'SERVER_NAME': origins[0].hostname, 'TLS_DIRECTIVES': tls}.items():
source = source.replace('@' + key + '@', value)
if not not re.search('@[A-Z_]+@', source):
raise RuntimeError('Required validation failed')
with a.output.open('x') as f:
f.write(source)
print('Rendered only:', a.output)