#!/usr/bin/env python3 from __future__ import annotations import os import sys import time import socket from server_config import load_config DOCS_ARGUMENT = "docs:enabled" def docs_enabled_from_args(): return DOCS_ARGUMENT in sys.argv[1:] def _force_project_working_directory(): os.chdir(os.path.dirname(os.path.abspath(__file__))) def _import_twisted(): try: from twisted.internet import reactor from twisted.web import server except ImportError as exc: raise SystemExit("Twisted is required. Install dependencies with: python3 -m pip install -r requirements.txt") from exc return reactor, server class Log: def __init__(self): os.makedirs("logs", exist_ok=True) self.handle = open("logs/reximemo.log", "a", encoding="utf-8") def write(self, text, silent=False): line = "[%s] %s" % (time.strftime("%H:%M:%S"), str(text).rstrip()) if not silent: print(line) self.handle.write(line + "\n") self.handle.flush() def close(self): self.handle.close() def create_site(*, docs_enabled=False): _force_project_working_directory() reactor, twisted_server = _import_twisted() import DB # initializes SQLite import hatena log = Log() hatena.ServerLog = log class ProxyCompatibleSite(twisted_server.Site): def buildProtocol(self, addr): protocol = super().buildProtocol(addr) original = protocol.dataReceived def data_received(data): for host in (b"flipnote.hatena.com", b"ugomemo.hatena.ne.jp"): data = data.replace(b"GET http://" + host, b"GET ") data = data.replace(b"POST http://" + host, b"POST ") return original(data) protocol.dataReceived = data_received return protocol return reactor, ProxyCompatibleSite(hatena.Setup(docs_enabled=docs_enabled)), log def main(): settings = load_config() try: port = int(os.environ.get("REXIMEMO_PORT", settings["port"])) if not 1024 <= port <= 65535: raise ValueError except ValueError as exc: raise SystemExit("Choose a port between 1024 and 65535 in server-settings.json or REXIMEMO_PORT.") from exc host = os.environ.get("REXIMEMO_HOST", settings["host"]) docs_enabled = docs_enabled_from_args() reactor, site, log = create_site(docs_enabled=docs_enabled) log.write("%s starting on %s:%d" % (settings["site_name"], host, port)) log.write("Web client: http://127.0.0.1:%d/" % port) try: addresses = sorted({address[4][0] for address in socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET) if not address[4][0].startswith("127.")}) except OSError: addresses = [] if host in {"0.0.0.0", "::"}: for address in addresses: log.write("DSi HTTP proxy: %s, port %d" % (address, port)) elif host not in {"127.0.0.1", "::1", "localhost"}: log.write("DSi HTTP proxy: %s, port %d" % (host, port)) else: log.write("Listening on this computer only. For a DSi on your network, choose listen address 0.0.0.0 during setup.") if not settings["setup_complete"]: log.write("First-run setup is available: python manage.py setup") log.write("DSi mode uses the historical HTTP proxy path; NAS/DNS/auth services are not included.") if docs_enabled: log.write("Built-in documentation enabled at /docs") from twisted.internet.error import CannotListenError try: reactor.listenTCP(port, site, interface=host) except CannotListenError as exc: log.close() raise SystemExit(f"Could not listen on {host}:{port}. Another server may already use that port. Stop it or choose another port with: python manage.py setup") from exc try: reactor.run() finally: log.write("Server shutdown", True) log.close() if __name__ == "__main__": main()