Files
kodin00 d13b8f505e dev: configurable bind address for dev-server (--host)
Add a --host option to both dev-server.py entry points so the dev
server can be reached from other devices on the LAN (e.g. a headless
Raspberry Pi behind a firewall). Default stays 127.0.0.1 (localhost);
pass --host 0.0.0.0 (or ::) to expose it. When bound to a wildcard,
the script also prints the machine's LAN address to open.

Both scripts move to argparse, so --help documents the options and bad
input errors cleanly instead of tracebacking. The positional [port] is
unchanged for backwards compatibility (python3 dev-server.py 8080).

web/dev-server.py previously bound "" (all interfaces) by default; it
now defaults to 127.0.0.1 to match the safe-by-default principle and
the other entry point. Use --host 0.0.0.0 for the old behavior.

stdout is flushed before serve_forever() so the startup lines (and the
LAN hint) land immediately in a redirected log.

README documents the new option.
2026-09-22 13:07:02 +07:00

70 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""
Dev server for web/. Plain static files -- the app has no build step -- but with
caching turned OFF.
`python3 -m http.server` serves Last-Modified and no Cache-Control, so browsers
apply heuristic caching to ES modules. Editing a module and reloading then runs
the OLD code, which looks exactly like a logic bug and wastes an afternoon.
python3 dev-server.py [port] # http://localhost:8731/
python3 dev-server.py --host 0.0.0.0 # reach from other devices on the LAN
"""
import argparse
import functools
import http.server
import os
import socket
import sys
class NoCacheHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self):
self.send_header('Cache-Control', 'no-store, must-revalidate')
self.send_header('Pragma', 'no-cache')
self.send_header('Expires', '0')
super().end_headers()
def log_message(self, fmt, *args): # one line per request, no noise
sys.stderr.write(f"{self.command} {self.path} -> {args[1]}\n")
def _lan_ip():
"""Best-effort LAN address for the "reachable at" hint. No packets are sent."""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80)) # pick a route; the OS resolves the local end without sending
ip = s.getsockname()[0]
s.close()
return ip
except OSError:
return None
def main():
parser = argparse.ArgumentParser(
description='Support Fins no-cache dev server.',
epilog='Bind 0.0.0.0 (or ::) to reach the server from other devices on your LAN.',
)
parser.add_argument('port', nargs='?', type=int, default=8731,
help='port to listen on (default: 8731)')
parser.add_argument('--host', default='127.0.0.1',
help='address to bind (default: 127.0.0.1; 0.0.0.0 exposes it to the LAN)')
args = parser.parse_args()
root = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web') # serve ./web from the repo root
handler = functools.partial(NoCacheHandler, directory=root)
print(f"support-fins dev server: http://localhost:{args.port}/ (serving {root})")
wildcard = args.host in ('0.0.0.0', '::', '')
if wildcard:
ip = _lan_ip()
if ip:
print(f"On other devices on your LAN, open http://{ip}:{args.port}/")
bind = '' if wildcard else args.host
sys.stdout.flush() # ensure the lines above land in a redirected log immediately (e.g. headless RPi)
http.server.ThreadingHTTPServer((bind, args.port), handler).serve_forever()
if __name__ == '__main__':
main()