CHAT_OUTPUT_BEGIN
COMMAND_ID=GITEA-READER-ROUTES-AUDIT-289
STATUS=OK
RC=0
HOST=pve01
MODE=read-only
COMPONENT=cluster-audit
REFERENCE_REGISTER_CHECK=OK
REFERENCE_SHA256=5763f2f8edc75fcf6f3951f8c95896d1cc112ceccb6cf5b64a383f540a4deb8e
ERROR_REGISTER_CHECK=OK
ERROR_REGISTER_SHA256=08b3a769b8e38eaa1acec915352fb328180d70caa7ff6cd9d1ec0da426262752
COMMAND_SHA256=ca1499ccd79985134e654f663bd829da73f07f8aa2af0a227387a97e6c32c66d
DUPLICATE_FAILED_COMMAND_BLOCKED=false
EXECUTION_STARTED=true
CHANGES_MADE=false
SANITIZED=yes
SECRETS_INCLUDED=no
PRIVATE_ADDRESSES_INCLUDED=no
RAW_EVIDENCE_SHA256=8077c2ba090c3c446ffa120fd830682af6b005d086e4263b67ad67eb043d3a9b
SANITIZED_OUTPUT_SHA256=8077c2ba090c3c446ffa120fd830682af6b005d086e4263b67ad67eb043d3a9b
OUTPUT_BEGIN
PREVIOUS_COMMAND_ID=GITEA-READER-BIND-AUDIT-288
DETAIL=     1	#!/usr/bin/python3
DETAIL=     2	import hashlib
DETAIL=     3	import http.server
DETAIL=     4	import json
DETAIL=     5	import os
DETAIL=     6	import pathlib
DETAIL=     7	import re
DETAIL=     8	import sys
DETAIL=     9	
DETAIL=    10	ROOT=pathlib.Path("/var/lib/homelab-reader-v3")
DETAIL=    11	MANIFEST=ROOT/"manifest.json"
DETAIL=    12	RESULTS=ROOT/"results"
DETAIL=    13	CAPABILITY=os.environ["HOMELAB_READER_CAPABILITY"]
DETAIL=    14	PORT=int(os.environ.get("HOMELAB_READER_PORT","18788"))
DETAIL=    15	RESULT_RE=re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}-[0-9a-f]{64}\.txt$")
DETAIL=    16	
DETAIL=    17	class Handler(http.server.BaseHTTPRequestHandler):
DETAIL=    18	    server_version="HomelabReader/3"
DETAIL=    19	
DETAIL=    20	    def log_message(self,fmt,*args):
DETAIL=    21	        sys.stderr.write("%s - %s\n"%(self.address_string(),fmt%args))
DETAIL=    22	
DETAIL=    23	    def reply(self,status,content_type,payload,etag="",include_body=True):
DETAIL=    24	        self.send_response(status)
DETAIL=    25	        self.send_header("Content-Type",content_type)
DETAIL=    26	        self.send_header("Content-Length",str(len(payload)))
DETAIL=    27	        self.send_header("Cache-Control","no-store, no-cache, must-revalidate, max-age=0")
DETAIL=    28	        self.send_header("Surrogate-Control","no-store")
DETAIL=    29	        self.send_header("Pragma","no-cache")
DETAIL=    30	        self.send_header("Expires","0")
DETAIL=    31	        self.send_header("X-Robots-Tag","noindex, nofollow, noarchive")
DETAIL=    32	        if etag:
DETAIL=    33	            self.send_header("ETag",chr(34)+etag+chr(34))
DETAIL=    34	            self.send_header("X-Homelab-Generation",etag)
DETAIL=    35	        self.end_headers()
DETAIL=    36	        if include_body:
DETAIL=    37	            self.wfile.write(payload)
DETAIL=    38	
DETAIL=    39	    def do_HEAD(self):
DETAIL=    40	        self.handle_reader(False)
DETAIL=    41	
DETAIL=    42	    def do_GET(self):
DETAIL=    43	        self.handle_reader(True)
DETAIL=    44	
DETAIL=    45	    def handle_reader(self,include_body):
DETAIL=    46	        request_path=self.path.split("?",1)[0]
DETAIL=    47	        prefix="/"+CAPABILITY+"/"
DETAIL=    48	        if not request_path.startswith(prefix):
DETAIL=    49	            self.reply(404,"text/plain; charset=utf-8",b"not found\n",include_body=include_body)
DETAIL=    50	            return
DETAIL=    51	
DETAIL=    52	        relative=request_path[len(prefix):]
DETAIL=    53	
DETAIL=    54	        if relative=="health":
DETAIL=    55	            self.reply(200,"application/json; charset=utf-8",b"{\"status\":\"ok\",\"version\":3}\n","health-v3",include_body)
DETAIL=    56	            return
DETAIL=    57	
DETAIL=    58	        if relative=="feed":
DETAIL=    59	            try:
DETAIL=    60	                payload=MANIFEST.read_bytes()
DETAIL=    61	                document=json.loads(payload.decode("utf-8"))
DETAIL=    62	                generation=str(document.get("generation","0"))
DETAIL=    63	                self.reply(200,"application/json; charset=utf-8",payload,generation,include_body)
DETAIL=    64	            except Exception:
DETAIL=    65	                self.reply(500,"text/plain; charset=utf-8",b"reader error\n",include_body=include_body)
DETAIL=    66	            return
DETAIL=    67	
DETAIL=    68	        if relative.startswith("results/"):
DETAIL=    69	            name=relative[len("results/"):]
DETAIL=    70	            target=RESULTS/name
DETAIL=    71	            if not RESULT_RE.fullmatch(name) or not target.is_file():
DETAIL=    72	                self.reply(404,"text/plain; charset=utf-8",b"not found\n",include_body=include_body)
DETAIL=    73	                return
DETAIL=    74	            try:
DETAIL=    75	                payload=target.read_bytes()
DETAIL=    76	                digest=name.rsplit("-",1)[1][:-4]
DETAIL=    77	                if hashlib.sha256(payload).hexdigest()!=digest:
DETAIL=    78	                    self.reply(500,"text/plain; charset=utf-8",b"integrity error\n",include_body=include_body)
DETAIL=    79	                    return
DETAIL=    80	                self.reply(200,"text/plain; charset=utf-8",payload,digest,include_body)
DETAIL=    81	            except Exception:
DETAIL=    82	                self.reply(500,"text/plain; charset=utf-8",b"reader error\n",include_body=include_body)
DETAIL=    83	            return
DETAIL=    84	
DETAIL=    85	        self.reply(404,"text/plain; charset=utf-8",b"not found\n",include_body=include_body)
DETAIL=    86	
DETAIL=    87	class Server(http.server.ThreadingHTTPServer):
DETAIL=    88	    allow_reuse_address=True
DETAIL=    89	
DETAIL=    90	Server(("127.0.0.1",PORT),Handler).serve_forever()
CHANGES_MADE=NO

OUTPUT_END
CHAT_OUTPUT_END
