Files
mono/shared/dev_watcher.py
giles d6f3250a77 Fix dev_watcher sentinel path for container permissions
The sentinel was written to shared/_reload_sentinel.py but shared/ is
volume-mounted as root:root, so appuser can't create files there.
Move sentinel to /app/_reload_sentinel.py which is owned by appuser
and still under Hypercorn's --reload watch path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:37:29 +00:00

70 lines
2.0 KiB
Python

"""Watch non-Python files and trigger Hypercorn reload.
Hypercorn --reload only watches .py files. This script watches .sx,
.sx, .js, and .css files and touches a sentinel .py file when they
change, causing Hypercorn to restart.
Usage (from entrypoint.sh, before exec hypercorn):
python3 -m shared.dev_watcher &
"""
import os
import time
import sys
WATCH_EXTENSIONS = {".sx", ".sx", ".js", ".css"}
SENTINEL = "/app/_reload_sentinel.py"
POLL_INTERVAL = 1.5 # seconds
def _collect_mtimes(roots):
mtimes = {}
for root in roots:
for dirpath, _dirs, files in os.walk(root):
for fn in files:
ext = os.path.splitext(fn)[1]
if ext in WATCH_EXTENSIONS:
path = os.path.join(dirpath, fn)
try:
mtimes[path] = os.path.getmtime(path)
except OSError:
pass
return mtimes
def main():
# Watch /app/shared and /app/<service>/sx plus static dirs
roots = []
for entry in os.listdir("/app"):
full = os.path.join("/app", entry)
if os.path.isdir(full):
roots.append(full)
if not roots:
roots = ["/app"]
# Ensure sentinel exists
if not os.path.exists(SENTINEL):
with open(SENTINEL, "w") as f:
f.write("# reload sentinel\n")
prev = _collect_mtimes(roots)
while True:
time.sleep(POLL_INTERVAL)
curr = _collect_mtimes(roots)
changed = []
for path, mtime in curr.items():
if path not in prev or prev[path] != mtime:
changed.append(path)
if changed:
names = ", ".join(os.path.basename(p) for p in changed[:3])
if len(changed) > 3:
names += f" (+{len(changed) - 3} more)"
print(f"[dev_watcher] Changed: {names} — triggering reload",
flush=True)
os.utime(SENTINEL, None)
prev = curr
if __name__ == "__main__":
main()