unrhodecode/update_server.py
2026-01-27 15:54:43 +01:00

142 lines
4.5 KiB
Python

#!/usr/bin/env python
import json
import os
import subprocess
import time
import threading
try:
# Py3
from http.server import BaseHTTPRequestHandler, HTTPServer
except ImportError:
# Py2
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
try:
from shlex import quote as shell_quote # Py3
except ImportError:
from pipes import quote as shell_quote # Py2 (Unix)
RCSTACK_PATH_NAME = "RCSTACK_PATH"
RCSTACK_PATH = os.environ.get(RCSTACK_PATH_NAME)
RCSTACK_LOG_NAME = os.environ.get("RCSTACK_LOG_NAME", "./rcstack_status.log")
STATUS = {"state": "ready"} # "ready" or "processing"
def _log_line(text, log_path):
# compatible with python 2
out = open(log_path, "ab")
try:
if not isinstance(text, bytes):
text = text.encode("utf-8")
out.write(text + b"\n")
out.flush()
finally:
out.close()
def _watch_process(popen_obj, log_path):
try:
rc = popen_obj.wait()
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
_log_line("[" + stamp + "] finished rc=" + str(rc), log_path=log_path)
except Exception as e:
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
_log_line("[" + stamp + "] watcher error: " + str(e), log_path=log_path)
finally:
STATUS["state"] = "ready"
class RcstackUpdater(BaseHTTPRequestHandler):
def __init__(self, request, client_address, server):
BaseHTTPRequestHandler.__init__(self, request, client_address, server)
def _send_json(self, obj, code=200):
body = json.dumps(obj).encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/state":
return self._send_json({"state": STATUS["state"]})
elif self.path == "/rcstack/update":
# If already processing, return ok and do not spawn a new process.
if STATUS["state"] != "ready":
return self._send_json({"status": "ok", "state": STATUS["state"]})
ok, err_msg = self.spawn_rcstack_status()
# If spawn failed for real reasons (bad env/path/etc.), return 500
if not ok:
return self._send_http_error(err_msg.encode("utf-8"), status_code=500)
return self._send_json({"status": "ok", "state": STATUS["state"]})
return self._send_http_error(b"not found", status_code=404)
def spawn_rcstack_status(self):
if not RCSTACK_PATH:
return False, "%s env variable not set" % (RCSTACK_PATH_NAME,)
if not os.path.exists(RCSTACK_PATH) or not os.path.isfile(RCSTACK_PATH):
return False, "%s does not exist" % (RCSTACK_PATH,)
rcstack = os.path.expanduser(RCSTACK_PATH)
rcstack_dir = os.path.dirname(rcstack)
log_path = os.path.join(rcstack_dir, RCSTACK_LOG_NAME)
STATUS["state"] = "processing"
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
_log_line("[%s] spawn: %s status" % (stamp, rcstack), log_path=log_path)
out = open(log_path, "ab")
try:
cmd = "%s self-update && %s stack-upgrade all" % (shell_quote(rcstack), shell_quote(rcstack))
p = subprocess.Popen(
["/bin/sh", "-c", cmd],
cwd=rcstack_dir,
stdout=out,
stderr=out,
close_fds=True,
preexec_fn=os.setsid, # Unix only
)
except Exception as e:
STATUS["state"] = "ready"
_log_line("[%s] spawn error: %s" % (stamp, str(e)), log_path=log_path)
return False, "spawn failed: %s" % (str(e),)
finally:
try:
out.close()
except Exception:
pass
t = threading.Thread(target=_watch_process, args=(p, log_path))
try:
t.daemon = True
except Exception:
t.setDaemon(True) # Py2
t.start()
return True, None
def _send_http_error(self, msg, status_code):
self.send_response(status_code)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(msg)
def main(host="127.0.0.1", port=10025):
httpd = HTTPServer((host, port), RcstackUpdater)
stamp = time.strftime("%Y-%m-%d %H:%M:%S")
print("[%s] listening on http://%s:%d" % (stamp, host, port))
httpd.serve_forever()
if __name__ == "__main__":
main()