chore(devops): env sync to server + SERVICE_ENV secret for CD (parked, not merged)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Merge service variables into the server env file (runs on the server, stdlib only).
|
||||
|
||||
env_merge.py TARGET SOURCE
|
||||
|
||||
Keys from SOURCE overwrite / add to TARGET; keys only in TARGET stay (server-generated secrets: DB passwords,
|
||||
PSEUDO_KEY, PUBLIC_URL). Protected keys are never taken from SOURCE. Prints changed key NAMES only (no values).
|
||||
Exit code: 0 = changed, 3 = nothing changed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
PROTECTED = {
|
||||
"POSTGRES_PASSWORD",
|
||||
"IDENTITY_DB_PASSWORD",
|
||||
"CLINICAL_DB_PASSWORD",
|
||||
"PSEUDO_KEY",
|
||||
"PUBLIC_URL",
|
||||
"IDENTITY_DB_URL",
|
||||
"CLINICAL_DB_URL",
|
||||
"API_BASE_URL",
|
||||
}
|
||||
|
||||
|
||||
def parse(path: str) -> list[tuple[str | None, str]]:
|
||||
"""Lines as (key, raw_line); comments/blank lines have key None. Keeps order and comments."""
|
||||
out: list[tuple[str | None, str]] = []
|
||||
with open(path, encoding="utf-8") as f:
|
||||
for raw in f.read().splitlines():
|
||||
line = raw.strip()
|
||||
key = line.split("=", 1)[0].strip() if line and not line.startswith("#") and "=" in line else None
|
||||
out.append((key, raw))
|
||||
return out
|
||||
|
||||
|
||||
def merge(target: str, source: str) -> list[str]:
|
||||
lines = parse(target)
|
||||
incoming = {
|
||||
k: raw
|
||||
for k, raw in parse(source)
|
||||
if k and k not in PROTECTED and not k.startswith("GITEA_") and raw.split("=", 1)[1].strip()
|
||||
}
|
||||
changed: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for i, (k, raw) in enumerate(lines):
|
||||
if k in incoming:
|
||||
seen.add(k)
|
||||
if raw != incoming[k]:
|
||||
lines[i] = (k, incoming[k])
|
||||
changed.append(k)
|
||||
for k, raw in incoming.items():
|
||||
if k not in seen:
|
||||
lines.append((k, raw))
|
||||
changed.append(k)
|
||||
if changed:
|
||||
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(os.path.abspath(target)))
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(raw for _, raw in lines) + "\n")
|
||||
st = os.stat(target)
|
||||
os.chmod(tmp, 0o600)
|
||||
if os.geteuid() == 0:
|
||||
os.chown(tmp, st.st_uid, st.st_gid)
|
||||
os.replace(tmp, target)
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> int:
|
||||
changed = merge(sys.argv[1], sys.argv[2])
|
||||
if not changed:
|
||||
print("env: no changes")
|
||||
return 3
|
||||
print("env: updated " + ", ".join(sorted(changed)))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user