Files
moments/script/render-site-conf.py
rob thijssen 3260bfb35b fix(ci): share one nginx-vhost renderer; drop unused deploy.sh
The nightly refresh.yml and deploy.yml each substituted asset/nginx/
site.conf.tmpl with their own inline python. When bb2f5b1 templated the
listen line as {{WEB_LISTEN}} (moving the vhost behind oolon's stream SNI
router), it added the substitution to the template and deploy.yml but not
to refresh.yml. The daily refresh then rsynced a literal
`listen {{WEB_LISTEN}};` into /etc/nginx/conf.d/rob.tn.conf, `nginx -t`
failed for the whole edge, and — because the file is written into the live
conf.d before it is tested — every vhost's reload (including the step@
cert renewals) stayed frozen. Internal vhosts, cichlid.internal among
them, served certs that had expired days earlier while the renewed certs
sat unused on disk.

- Replace both inline renderers with script/render-site-conf.py, shared by
  deploy.yml and refresh.yml so they cannot drift on what they substitute.
- Guard rails: the renderer fails if any {{PLACEHOLDER}} lacks an env value
  or survives substitution, so a forgotten/misnamed variable is a red build
  on the runner instead of a broken vhost on the edge.
- Add the missing WEB_LISTEN to refresh.yml's env (the immediate drift).
- Rename the template's {{DOCROOT}} to {{WEB_ROOT}} so every placeholder
  maps to the env var of the same name.
- Remove script/deploy.sh: the third, unused renderer of the same template
  (superseded by the Actions workflows) and a standing source of drift.
- Docs (readme, CLAUDE.md) updated to the Actions-only deploy path.

Known follow-up (needs a sudoers change + infra-setup re-run on oolon, so
out of scope here): the rendered vhost is still rsynced straight into the
live conf.d and only then `nginx -t`'d, so a valid-but-wrong config could
still wedge nginx. Stage-validate-swap with rollback would close that.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsH1rcWQYtRVhvaftiKm22
2026-07-26 15:52:18 +03:00

63 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""Render the nginx vhost (asset/nginx/site.conf.tmpl) from the environment.
Both .gitea/workflows/deploy.yml (deploy-web) and refresh.yml call this, so the
two pipelines can never disagree about what they substitute. They did once: the
WEB_LISTEN placeholder was added to the template and to deploy.yml but not to
refresh.yml, so the nightly refresh shipped a literal `listen {{WEB_LISTEN}};`
to oolon. `nginx -t` then failed for the whole edge, and because the file is
rsynced straight into the live conf.d before it is tested, every vhost's reload
-- including the step@ cert renewals -- stayed frozen for days while renewed
certs piled up unserved on disk.
Guard rails, so that can't recur:
* every {{PLACEHOLDER}} in the template must have a matching environment
variable, or the render fails before anything leaves the runner;
* no {{...}} may survive substitution.
A forgotten or misnamed variable is now a red build, not a broken edge.
usage: render-site-conf.py [OUTPUT] (default: rendered/site.conf)
"""
import os
import re
import sys
TEMPLATE = "asset/nginx/site.conf.tmpl"
PLACEHOLDER = re.compile(r"\{\{(\w+)\}\}")
def main() -> int:
out = sys.argv[1] if len(sys.argv) > 1 else "rendered/site.conf"
with open(TEMPLATE, encoding="utf-8") as fh:
text = fh.read()
names = sorted(set(PLACEHOLDER.findall(text)))
missing = [n for n in names if n not in os.environ]
if missing:
sys.stderr.write(
"render-site-conf: no environment value for placeholder(s): "
+ ", ".join(missing) + "\n")
return 1
for name in names:
text = text.replace("{{%s}}" % name, os.environ[name])
leftover = sorted(set(PLACEHOLDER.findall(text)))
if leftover:
sys.stderr.write(
"render-site-conf: unrendered placeholder(s) after substitution: "
+ ", ".join(leftover) + "\n")
return 1
os.makedirs(os.path.dirname(out) or ".", exist_ok=True)
with open(out, "w", encoding="utf-8") as fh:
fh.write(text)
sys.stderr.write(
"render-site-conf: wrote %s (%d substitutions: %s)\n"
% (out, len(names), ", ".join(names)))
return 0
if __name__ == "__main__":
raise SystemExit(main())