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
63 lines
2.2 KiB
Python
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())
|