From f773378cfcaf11590c01cd33fc91cbf8647ab396 Mon Sep 17 00:00:00 2001 From: homelab-runtime-publisher Date: Fri, 24 Jul 2026 09:36:36 +0000 Subject: [PATCH] runtime: publish HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426 --- ...MELAB-DUPLICATE-FINGERPRINT-AUDIT-426.json | 30 ++ ...OMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.txt | 433 ++++++++++++++++++ runtime/latest.json | 24 +- runtime/latest.txt | 431 ++++++++++++++++- 4 files changed, 892 insertions(+), 26 deletions(-) create mode 100644 runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.json create mode 100644 runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.txt diff --git a/runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.json b/runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.json new file mode 100644 index 0000000..e75259e --- /dev/null +++ b/runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.json @@ -0,0 +1,30 @@ +{ + "schema_version": 1, + "channel": "homelab-runtime", + "command_id": "HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426", + "status": "OK", + "rc": 0, + "host": "pve01", + "mode": "read-only", + "component": "cluster-audit", + "started_at_utc": "2026-07-24T09:36:35Z", + "finished_at_utc": "2026-07-24T09:36:35Z", + "reference_register_checked": true, + "reference_sha256": "5763f2f8edc75fcf6f3951f8c95896d1cc112ceccb6cf5b64a383f540a4deb8e", + "error_register_checked": true, + "error_register_sha256": "08b3a769b8e38eaa1acec915352fb328180d70caa7ff6cd9d1ec0da426262752", + "command_sha256": "d95e5a9d8480b33605c127ce6ebdc688d05087f3c0afbdda7c3af1a5129a3d3c", + "duplicate_failed_command_blocked": false, + "block_reason": null, + "execution_started": true, + "changes_made": false, + "sanitized": true, + "secrets_included": false, + "private_addresses_included": false, + "raw_evidence_retained_locally": true, + "raw_evidence_sha256": "db73d9a0e67a6c8cdbb9fcb6ae3ee47c92879dc079fe115a8412abd39ef39ca4", + "sanitized_output_sha256": "b1ad62957baf7f8680f85ae10f1db94077a48c98e438dda3987b551f7a15cb13", + "output_truncated_in_json": false, + "full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt", + "output": "DETAIL=FILE=/usr/local/sbin/homelab-chat-run-linked\nDETAIL=SHA256=5e8725e129fd2055b36fe2fae13fc95fc4b3271074a0e0d4bb7347e7b2f270a1\nDETAIL=FILE=/usr/local/sbin/homelab-chat-run\nDETAIL=SHA256=2a88defef9517f89bc60934dce82097f8e316b08af9417e95167ea5d759a10d9\nDETAIL=SOURCE=51- read-only|verify|change|transport-test)\nDETAIL=SOURCE=52- ;;\nDETAIL=SOURCE=53- *)\nDETAIL=SOURCE=54- echo 'RESULT=FAIL'\nDETAIL=SOURCE=55- echo 'REASON=INVALID_MODE'\nDETAIL=SOURCE=56- exit 67\nDETAIL=SOURCE=57- ;;\nDETAIL=SOURCE=58-esac\nDETAIL=SOURCE=59-\nDETAIL=SOURCE=60-for TOOL in \\\nDETAIL=SOURCE=61- awk \\\nDETAIL=SOURCE=62- curl \\\nDETAIL=SOURCE=63- flock \\\nDETAIL=SOURCE=64- python3 \\\nDETAIL=SOURCE=65- scp \\\nDETAIL=SOURCE=66: sha256sum \\\nDETAIL=SOURCE=67- ssh\nDETAIL=SOURCE=68-do\nDETAIL=SOURCE=69- command -v \"$TOOL\" >/dev/null 2>&1 || {\nDETAIL=SOURCE=70- echo 'RESULT=FAIL'\nDETAIL=SOURCE=71- echo \"REASON=REQUIRED_TOOL_MISSING:$TOOL\"\nDETAIL=SOURCE=72- exit 68\nDETAIL=SOURCE=73- }\nDETAIL=SOURCE=74-done\nDETAIL=SOURCE=75-\nDETAIL=SOURCE=76-REFERENCE=/etc/pve/31_HOMELAB_REFERENCE.md\nDETAIL=SOURCE=77-ERROR_REGISTER=/etc/pve/HOMELAB_ASSISTANT_ERROR_REGISTER.md\nDETAIL=SOURCE=78-\nDETAIL=SOURCE=79-[ -r \"$REFERENCE\" ] || {\nDETAIL=SOURCE=80- echo 'RESULT=FAIL'\nDETAIL=SOURCE=81- echo 'REASON=REFERENCE_NOT_READABLE'\nDETAIL=SOURCE=82- exit 69\nDETAIL=SOURCE=83-}\nDETAIL=SOURCE=84-\nDETAIL=SOURCE=85-[ -r \"$ERROR_REGISTER\" ] || {\nDETAIL=SOURCE=86- echo 'RESULT=FAIL'\nDETAIL=SOURCE=87- echo 'REASON=ERROR_REGISTER_NOT_READABLE'\nDETAIL=SOURCE=88- exit 70\nDETAIL=SOURCE=89-}\nDETAIL=SOURCE=90-\nDETAIL=SOURCE=91-STATE_DIR=/var/lib/homelab-chat-run\nDETAIL=SOURCE=92-RAW_DIR=\"$STATE_DIR/raw\"\nDETAIL=SOURCE=93-PUBLIC_DIR=\"$STATE_DIR/public\"\nDETAIL=SOURCE=94-LEDGER=\"$STATE_DIR/state/command-ledger.tsv\"\nDETAIL=SOURCE=95-LOCK=\"$STATE_DIR/state/runner.lock\"\nDETAIL=SOURCE=96-\nDETAIL=SOURCE=--\nDETAIL=SOURCE=104-flock -w 60 9 || {\nDETAIL=SOURCE=105- echo 'RESULT=FAIL'\nDETAIL=SOURCE=106- echo 'REASON=RUNNER_LOCK_TIMEOUT'\nDETAIL=SOURCE=107- exit 71\nDETAIL=SOURCE=108-}\nDETAIL=SOURCE=109-\nDETAIL=SOURCE=110-STARTED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nDETAIL=SOURCE=111-STAMP=\"$(date -u +%Y%m%dT%H%M%SZ)\"\nDETAIL=SOURCE=112-HOST_SHORT=\"$(hostname -s 2>/dev/null || hostname)\"\nDETAIL=SOURCE=113-\nDETAIL=SOURCE=114-RAW_FILE=\"$RAW_DIR/${COMMAND_ID}-${STAMP}.log\"\nDETAIL=SOURCE=115-SANITIZED_FILE=\"$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.txt\"\nDETAIL=SOURCE=116-JSON_FILE=\"$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.json\"\nDETAIL=SOURCE=117-TXT_FILE=\"$PUBLIC_DIR/${COMMAND_ID}-${STAMP}-latest.txt\"\nDETAIL=SOURCE=118-\nDETAIL=SOURCE=119:REFERENCE_SHA256=\"$(sha256sum \"$REFERENCE\" | awk '{print $1}')\"\nDETAIL=SOURCE=120:ERROR_REGISTER_SHA256=\"$(sha256sum \"$ERROR_REGISTER\" | awk '{print $1}')\"\nDETAIL=SOURCE=121-\nDETAIL=SOURCE=122:COMMAND_SHA256=\"$(\nDETAIL=SOURCE=123- {\nDETAIL=SOURCE=124- printf '%s\\0' \"$MODE\"\nDETAIL=SOURCE=125- printf '%s\\0' \"$COMPONENT\"\nDETAIL=SOURCE=126- printf '%s\\0' \"$@\"\nDETAIL=SOURCE=127- } |\nDETAIL=SOURCE=128: sha256sum |\nDETAIL=SOURCE=129- awk '{print $1}'\nDETAIL=SOURCE=130-)\"\nDETAIL=SOURCE=131-\nDETAIL=SOURCE=132-PREVIOUS_FAILED_RC=\"$(\nDETAIL=SOURCE=133: awk -F '\\t' -v hash=\"$COMMAND_SHA256\" '\nDETAIL=SOURCE=134- $3 == hash && $4 != \"0\" {\nDETAIL=SOURCE=135- value=$4\nDETAIL=SOURCE=136- }\nDETAIL=SOURCE=137-\nDETAIL=SOURCE=138- END {\nDETAIL=SOURCE=139- print value\nDETAIL=SOURCE=140- }\nDETAIL=SOURCE=141- ' \"$LEDGER\"\nDETAIL=SOURCE=142-)\"\nDETAIL=SOURCE=143-\nDETAIL=SOURCE=144-RUN_STARTED=true\nDETAIL=SOURCE=145-REPEAT_BLOCKED=false\nDETAIL=SOURCE=146-BLOCK_REASON=''\nDETAIL=SOURCE=147-CHANGES_MADE=false\nDETAIL=SOURCE=148-\nDETAIL=SOURCE=149-case \"$MODE\" in\nDETAIL=SOURCE=150- change)\nDETAIL=SOURCE=151- CHANGES_MADE=true\nDETAIL=SOURCE=152- ;;\nDETAIL=SOURCE=153-esac\nDETAIL=SOURCE=154-\nDETAIL=SOURCE=155-if [ -n \"$PREVIOUS_FAILED_RC\" ] &&\nDETAIL=SOURCE=156- [ \"${HOMELAB_ALLOW_REPEAT:-0}\" != 1 ]; then\nDETAIL=SOURCE=157-\nDETAIL=SOURCE=158- RUN_STARTED=false\nDETAIL=SOURCE=159- REPEAT_BLOCKED=true\nDETAIL=SOURCE=160: BLOCK_REASON='EXACT_FAILED_COMMAND_REPEAT'\nDETAIL=SOURCE=161- RC=73\nDETAIL=SOURCE=162- STATUS='BLOCKED'\nDETAIL=SOURCE=163-\nDETAIL=SOURCE=164- {\nDETAIL=SOURCE=165- echo 'COMMAND_EXECUTION_BLOCKED=yes'\nDETAIL=SOURCE=166: echo 'REASON=EXACT_FAILED_COMMAND_REPEAT'\nDETAIL=SOURCE=167- echo \"PREVIOUS_RC=$PREVIOUS_FAILED_RC\"\nDETAIL=SOURCE=168: echo \"COMMAND_SHA256=$COMMAND_SHA256\"\nDETAIL=SOURCE=169- echo\nDETAIL=SOURCE=170- echo 'Set HOMELAB_ALLOW_REPEAT=1 only after reviewing the previous failure.'\nDETAIL=SOURCE=171- } >\"$RAW_FILE\"\nDETAIL=SOURCE=172-else\nDETAIL=SOURCE=173- set +e\nDETAIL=SOURCE=174-\nDETAIL=SOURCE=175- if [ -n \"${HOMELAB_TIMEOUT:-}\" ]; then\nDETAIL=SOURCE=176- case \"$HOMELAB_TIMEOUT\" in\nDETAIL=SOURCE=177- *[!0-9]*|'')\nDETAIL=SOURCE=178- echo 'RESULT=FAIL'\nDETAIL=SOURCE=179- echo 'REASON=INVALID_HOMELAB_TIMEOUT'\nDETAIL=SOURCE=180- exit 74\nDETAIL=SOURCE=181- ;;\nDETAIL=SOURCE=182- esac\nDETAIL=SOURCE=183-\nDETAIL=SOURCE=184- timeout \\\nDETAIL=SOURCE=185- --foreground \\\nDETAIL=SOURCE=186- --preserve-status \\\nDETAIL=SOURCE=187- \"$HOMELAB_TIMEOUT\" \\\nDETAIL=SOURCE=188- \"$@\" >\"$RAW_FILE\" 2>&1\nDETAIL=SOURCE=189- RC=$?\nDETAIL=SOURCE=190- else\nDETAIL=SOURCE=191- \"$@\" >\"$RAW_FILE\" 2>&1\nDETAIL=SOURCE=192- RC=$?\nDETAIL=SOURCE=193- fi\nDETAIL=SOURCE=194-\nDETAIL=SOURCE=195- set -e\nDETAIL=SOURCE=196-\nDETAIL=SOURCE=197- if [ \"$RC\" -eq 0 ]; then\nDETAIL=SOURCE=198- STATUS='OK'\nDETAIL=SOURCE=199- else\nDETAIL=SOURCE=200- STATUS='FAIL'\nDETAIL=SOURCE=201- fi\nDETAIL=SOURCE=202-fi\nDETAIL=SOURCE=203-\nDETAIL=SOURCE=204-FINISHED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nDETAIL=SOURCE=205:RAW_SHA256=\"$(sha256sum \"$RAW_FILE\" | awk '{print $1}')\"\nDETAIL=SOURCE=206-\nDETAIL=SOURCE=207-python3 - \"$RAW_FILE\" \"$SANITIZED_FILE\" <<'PY'\nDETAIL=SOURCE=208-import re\nDETAIL=SOURCE=209-import sys\nDETAIL=SOURCE=210-from pathlib import Path\nDETAIL=SOURCE=211-\nDETAIL=SOURCE=212:source = Path(sys.argv[1])\nDETAIL=SOURCE=213:target = Path(sys.argv[2])\nDETAIL=SOURCE=214-\nDETAIL=SOURCE=215-text = source.read_bytes().decode(\"utf-8\", errors=\"replace\")\nDETAIL=SOURCE=216-\nDETAIL=SOURCE=217-patterns = [\nDETAIL=SOURCE=218- (\nDETAIL=SOURCE=219- re.compile(\nDETAIL=SOURCE=220- r\"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----\"\nDETAIL=SOURCE=221- r\".*?\"\nDETAIL=SOURCE=222- r\"-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----\",\nDETAIL=SOURCE=223- re.IGNORECASE | re.DOTALL,\nDETAIL=SOURCE=224- ),\nDETAIL=SOURCE=225- \"[PRIVATE_KEY_REDACTED]\",\nDETAIL=SOURCE=226- ),\nDETAIL=SOURCE=227- (\nDETAIL=SOURCE=228- re.compile(\nDETAIL=SOURCE=229- r\"(?im)^(\\s*(?:password|passwd|passphrase|authorization|\"\nDETAIL=SOURCE=230- r\"api[_-]?key|access[_-]?token|refresh[_-]?token|\"\nDETAIL=SOURCE=231- r\"private[_-]?key|secret)\\s*[:=]\\s*).*$\"\nDETAIL=SOURCE=232- ),\nDETAIL=SOURCE=233- r\"\\1[REDACTED]\",\nDETAIL=SOURCE=234- ),\nDETAIL=SOURCE=235- (\nDETAIL=SOURCE=236- re.compile(r\"(?i)(Bearer\\s+)[A-Za-z0-9._~+/=-]+\"),\nDETAIL=SOURCE=237- r\"\\1[REDACTED]\",\nDETAIL=SOURCE=238- ),\nDETAIL=SOURCE=239- (\nDETAIL=SOURCE=240- re.compile(r\"\\b10(?:\\.\\d{1,3}){3}\\b\"),\nDETAIL=SOURCE=241- \"[PRIVATE_IP]\",\nDETAIL=SOURCE=242- ),\nDETAIL=SOURCE=243- (\nDETAIL=SOURCE=--\nDETAIL=SOURCE=294-if len(encoded) > maximum:\nDETAIL=SOURCE=295- first = encoded[:440_000].decode(\"utf-8\", errors=\"ignore\")\nDETAIL=SOURCE=296- last = encoded[-440_000:].decode(\"utf-8\", errors=\"ignore\")\nDETAIL=SOURCE=297-\nDETAIL=SOURCE=298- text = (\nDETAIL=SOURCE=299- first\nDETAIL=SOURCE=300- + \"\\n\\n\"\nDETAIL=SOURCE=301- + \"===== PUBLIC OUTPUT TRUNCATED; FULL RAW LOG RETAINED LOCALLY =====\"\nDETAIL=SOURCE=302- + \"\\n\\n\"\nDETAIL=SOURCE=303- + last\nDETAIL=SOURCE=304- )\nDETAIL=SOURCE=305-\nDETAIL=SOURCE=306-target.write_text(text, encoding=\"utf-8\")\nDETAIL=SOURCE=307-PY\nDETAIL=SOURCE=308-\nDETAIL=SOURCE=309:SANITIZED_SHA256=\"$(\nDETAIL=SOURCE=310: sha256sum \"$SANITIZED_FILE\" |\nDETAIL=SOURCE=311- awk '{print $1}'\nDETAIL=SOURCE=312-)\"\nDETAIL=SOURCE=313-\nDETAIL=SOURCE=314-python3 - \\\nDETAIL=SOURCE=315- \"$JSON_FILE\" \\\nDETAIL=SOURCE=316- \"$SANITIZED_FILE\" \\\nDETAIL=SOURCE=317- \"$COMMAND_ID\" \\\nDETAIL=SOURCE=318- \"$STATUS\" \\\nDETAIL=SOURCE=319- \"$RC\" \\\nDETAIL=SOURCE=320- \"$HOST_SHORT\" \\\nDETAIL=SOURCE=321- \"$MODE\" \\\nDETAIL=SOURCE=322- \"$COMPONENT\" \\\nDETAIL=SOURCE=323- \"$STARTED_AT\" \\\nDETAIL=SOURCE=324- \"$FINISHED_AT\" \\\nDETAIL=SOURCE=325: \"$REFERENCE_SHA256\" \\\nDETAIL=SOURCE=326: \"$ERROR_REGISTER_SHA256\" \\\nDETAIL=SOURCE=327: \"$COMMAND_SHA256\" \\\nDETAIL=SOURCE=328: \"$RAW_SHA256\" \\\nDETAIL=SOURCE=329: \"$SANITIZED_SHA256\" \\\nDETAIL=SOURCE=330- \"$RUN_STARTED\" \\\nDETAIL=SOURCE=331- \"$REPEAT_BLOCKED\" \\\nDETAIL=SOURCE=332- \"$BLOCK_REASON\" \\\nDETAIL=SOURCE=333- \"$CHANGES_MADE\" <<'PY'\nDETAIL=SOURCE=334-import json\nDETAIL=SOURCE=335-import sys\nDETAIL=SOURCE=336-from pathlib import Path\nDETAIL=SOURCE=337-\nDETAIL=SOURCE=338-(\nDETAIL=SOURCE=339- json_path,\nDETAIL=SOURCE=340- output_path,\nDETAIL=SOURCE=341- command_id,\nDETAIL=SOURCE=342- status,\nDETAIL=SOURCE=343- rc,\nDETAIL=SOURCE=344- host,\nDETAIL=SOURCE=345- mode,\nDETAIL=SOURCE=346- component,\nDETAIL=SOURCE=347- started,\nDETAIL=SOURCE=348- finished,\nDETAIL=SOURCE=349- reference_sha,\nDETAIL=SOURCE=350- error_sha,\nDETAIL=SOURCE=351- command_sha,\nDETAIL=SOURCE=352- raw_sha,\nDETAIL=SOURCE=353- sanitized_sha,\nDETAIL=SOURCE=354- run_started,\nDETAIL=SOURCE=355- repeat_blocked,\nDETAIL=SOURCE=356- block_reason,\nDETAIL=SOURCE=357- changes_made,\nDETAIL=SOURCE=358:) = sys.argv[1:]\nDETAIL=SOURCE=359-\nDETAIL=SOURCE=360-output = Path(output_path).read_text(\nDETAIL=SOURCE=361- encoding=\"utf-8\",\nDETAIL=SOURCE=362- errors=\"replace\",\nDETAIL=SOURCE=363-)\nDETAIL=SOURCE=364-\nDETAIL=SOURCE=365-encoded = output.encode(\"utf-8\")\nDETAIL=SOURCE=366-json_limit = 300_000\nDETAIL=SOURCE=367-output_truncated = len(encoded) > json_limit\nDETAIL=SOURCE=368-\nDETAIL=SOURCE=369-if output_truncated:\nDETAIL=SOURCE=370- output_preview = encoded[:json_limit].decode(\nDETAIL=SOURCE=371- \"utf-8\",\nDETAIL=SOURCE=372- errors=\"ignore\",\nDETAIL=SOURCE=373- )\nDETAIL=SOURCE=374-else:\nDETAIL=SOURCE=375- output_preview = output\nDETAIL=SOURCE=376-\nDETAIL=SOURCE=377-document = {\nDETAIL=SOURCE=378- \"schema_version\": 1,\nDETAIL=SOURCE=379- \"channel\": \"homelab-runtime\",\nDETAIL=SOURCE=380- \"command_id\": command_id,\nDETAIL=SOURCE=381- \"status\": status,\nDETAIL=SOURCE=382- \"rc\": int(rc),\nDETAIL=SOURCE=383- \"host\": host,\nDETAIL=SOURCE=384- \"mode\": mode,\nDETAIL=SOURCE=385- \"component\": component,\nDETAIL=SOURCE=386- \"started_at_utc\": started,\nDETAIL=SOURCE=387- \"finished_at_utc\": finished,\nDETAIL=SOURCE=388- \"reference_register_checked\": True,\nDETAIL=SOURCE=389: \"reference_sha256\": reference_sha,\nDETAIL=SOURCE=390- \"error_register_checked\": True,\nDETAIL=SOURCE=391: \"error_register_sha256\": error_sha,\nDETAIL=SOURCE=392: \"command_sha256\": command_sha,\nDETAIL=SOURCE=393: \"duplicate_failed_command_blocked\": repeat_blocked == \"true\",\nDETAIL=SOURCE=394- \"block_reason\": block_reason or None,\nDETAIL=SOURCE=395- \"execution_started\": run_started == \"true\",\nDETAIL=SOURCE=396- \"changes_made\": changes_made == \"true\",\nDETAIL=SOURCE=397- \"sanitized\": True,\nDETAIL=SOURCE=398- \"secrets_included\": False,\nDETAIL=SOURCE=399- \"private_addresses_included\": False,\nDETAIL=SOURCE=400- \"raw_evidence_retained_locally\": True,\nDETAIL=SOURCE=401: \"raw_evidence_sha256\": raw_sha,\nDETAIL=SOURCE=402: \"sanitized_output_sha256\": sanitized_sha,\nDETAIL=SOURCE=403- \"output_truncated_in_json\": output_truncated,\nDETAIL=SOURCE=404- \"full_sanitized_output_url\": (\nDETAIL=SOURCE=405- \"https://git.gram1.ru/.well-known/\"\nDETAIL=SOURCE=406- \"homelab-runtime/latest.txt\"\nDETAIL=SOURCE=407- ),\nDETAIL=SOURCE=408- \"output\": output_preview,\nDETAIL=SOURCE=409-}\nDETAIL=SOURCE=410-\nDETAIL=SOURCE=411-Path(json_path).write_text(\nDETAIL=SOURCE=412- json.dumps(\nDETAIL=SOURCE=413- document,\nDETAIL=SOURCE=414- ensure_ascii=False,\nDETAIL=SOURCE=415- indent=2,\nDETAIL=SOURCE=416- )\nDETAIL=SOURCE=417- + \"\\n\",\nDETAIL=SOURCE=418- encoding=\"utf-8\",\nDETAIL=SOURCE=419-)\nDETAIL=SOURCE=420-PY\nDETAIL=SOURCE=421-\nDETAIL=SOURCE=422-{\nDETAIL=SOURCE=423- echo 'CHAT_OUTPUT_BEGIN'\nDETAIL=SOURCE=424- echo \"COMMAND_ID=$COMMAND_ID\"\nDETAIL=SOURCE=425- echo \"STATUS=$STATUS\"\nDETAIL=SOURCE=426- echo \"RC=$RC\"\nDETAIL=SOURCE=427- echo \"HOST=$HOST_SHORT\"\nDETAIL=SOURCE=428- echo \"MODE=$MODE\"\nDETAIL=SOURCE=429- echo \"COMPONENT=$COMPONENT\"\nDETAIL=SOURCE=430- echo 'REFERENCE_REGISTER_CHECK=OK'\nDETAIL=SOURCE=431: echo \"REFERENCE_SHA256=$REFERENCE_SHA256\"\nDETAIL=SOURCE=432- echo 'ERROR_REGISTER_CHECK=OK'\nDETAIL=SOURCE=433: echo \"ERROR_REGISTER_SHA256=$ERROR_REGISTER_SHA256\"\nDETAIL=SOURCE=434: echo \"COMMAND_SHA256=$COMMAND_SHA256\"\nDETAIL=SOURCE=435: echo \"DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED\"\nDETAIL=SOURCE=436- echo \"EXECUTION_STARTED=$RUN_STARTED\"\nDETAIL=SOURCE=437- echo \"CHANGES_MADE=$CHANGES_MADE\"\nDETAIL=SOURCE=438- echo 'SANITIZED=yes'\nDETAIL=SOURCE=439- echo 'SECRETS_INCLUDED=no'\nDETAIL=SOURCE=440- echo 'PRIVATE_ADDRESSES_INCLUDED=no'\nDETAIL=SOURCE=441: echo \"RAW_EVIDENCE_SHA256=$RAW_SHA256\"\nDETAIL=SOURCE=442: echo \"SANITIZED_OUTPUT_SHA256=$SANITIZED_SHA256\"\nDETAIL=SOURCE=443- echo 'OUTPUT_BEGIN'\nDETAIL=SOURCE=444- cat \"$SANITIZED_FILE\"\nDETAIL=SOURCE=445- echo\nDETAIL=SOURCE=446- echo 'OUTPUT_END'\nDETAIL=SOURCE=447- echo 'CHAT_OUTPUT_END'\nDETAIL=SOURCE=448-} >\"$TXT_FILE\"\nDETAIL=SOURCE=449-\nDETAIL=SOURCE=450-chmod 0600 \\\nDETAIL=SOURCE=451- \"$RAW_FILE\" \\\nDETAIL=SOURCE=452- \"$SANITIZED_FILE\" \\\nDETAIL=SOURCE=453- \"$JSON_FILE\" \\\nDETAIL=SOURCE=454- \"$TXT_FILE\"\nDETAIL=SOURCE=455-\nDETAIL=SOURCE=456-TARGET=debian@[PRIVATE_IP]\nDETAIL=SOURCE=457-REMOTE_PREFIX=\"/tmp/homelab-runtime-${COMMAND_ID}-$$\"\nDETAIL=SOURCE=458-\nDETAIL=SOURCE=459-SSH_OPTS=(\nDETAIL=SOURCE=460- -o BatchMode=yes\nDETAIL=SOURCE=461- -o ConnectTimeout=10\nDETAIL=SOURCE=462- -o ConnectionAttempts=1\nDETAIL=SOURCE=463- -o StrictHostKeyChecking=yes\nDETAIL=SOURCE=464- -o UserKnownHostsFile=[SENSITIVE_PATH]\nDETAIL=SOURCE=465-)\nDETAIL=SOURCE=466-\nDETAIL=SOURCE=467-scp \\\nDETAIL=SOURCE=468- -q \\\nDETAIL=SOURCE=469- \"${SSH_OPTS[@]}\" \\\nDETAIL=SOURCE=470- \"$JSON_FILE\" \\\nDETAIL=SOURCE=471- \"${TARGET}:${REMOTE_PREFIX}.json\"\nDETAIL=SOURCE=472-\nDETAIL=SOURCE=--\nDETAIL=SOURCE=512-\nDETAIL=SOURCE=513- sleep 2\nDETAIL=SOURCE=514-done\nDETAIL=SOURCE=515-\nDETAIL=SOURCE=516-if [ \"$PUBLIC_VISIBLE\" != true ]; then\nDETAIL=SOURCE=517- echo 'RESULT=FAIL'\nDETAIL=SOURCE=518- echo 'REASON=PUBLIC_RUNTIME_RESULT_NOT_VISIBLE'\nDETAIL=SOURCE=519- echo \"COMMAND_ID=$COMMAND_ID\"\nDETAIL=SOURCE=520- echo \"LOCAL_RAW_FILE=$RAW_FILE\"\nDETAIL=SOURCE=521- exit 75\nDETAIL=SOURCE=522-fi\nDETAIL=SOURCE=523-\nDETAIL=SOURCE=524-printf '%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n' \\\nDETAIL=SOURCE=525- \"$FINISHED_AT\" \\\nDETAIL=SOURCE=526- \"$COMMAND_ID\" \\\nDETAIL=SOURCE=527: \"$COMMAND_SHA256\" \\\nDETAIL=SOURCE=528- \"$RC\" \\\nDETAIL=SOURCE=529- \"$STATUS\" \\\nDETAIL=SOURCE=530- \"$COMPONENT\" >>\"$LEDGER\"\nDETAIL=SOURCE=531-\nDETAIL=SOURCE=532-echo\nDETAIL=SOURCE=533-echo '===== HOMELAB CHAT RUN COMPLETE ====='\nDETAIL=SOURCE=534-echo \"COMMAND_ID=$COMMAND_ID\"\nDETAIL=SOURCE=535-echo \"STATUS=$STATUS\"\nDETAIL=SOURCE=536-echo \"RC=$RC\"\nDETAIL=SOURCE=537-echo 'REFERENCE_REGISTER_CHECK=OK'\nDETAIL=SOURCE=538-echo 'ERROR_REGISTER_CHECK=OK'\nDETAIL=SOURCE=539:echo \"DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED\"\nDETAIL=SOURCE=540-echo 'GITEA_PUBLICATION=OK'\nDETAIL=SOURCE=541-echo \"PUBLIC_URL=$PUBLIC_URL\"\nDETAIL=SOURCE=542-echo \"LOCAL_RAW_FILE=$RAW_FILE\"\nDETAIL=SOURCE=543-echo 'SHELL_SESSION_PRESERVED=yes'\nDETAIL=SOURCE=544-\nDETAIL=SOURCE=545-exit \"$RC\"\nDETAIL=DUPLICATE_FINGERPRINT_AUDIT=PASS\nDETAIL=SECRET_VALUES_OUTPUT=NONE\nCHANGES_MADE=NO\n" +} diff --git a/runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.txt b/runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.txt new file mode 100644 index 0000000..3f436a9 --- /dev/null +++ b/runtime/history/HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426.txt @@ -0,0 +1,433 @@ +CHAT_OUTPUT_BEGIN +COMMAND_ID=HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426 +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=d95e5a9d8480b33605c127ce6ebdc688d05087f3c0afbdda7c3af1a5129a3d3c +DUPLICATE_FAILED_COMMAND_BLOCKED=false +EXECUTION_STARTED=true +CHANGES_MADE=false +SANITIZED=yes +SECRETS_INCLUDED=no +PRIVATE_ADDRESSES_INCLUDED=no +RAW_EVIDENCE_SHA256=db73d9a0e67a6c8cdbb9fcb6ae3ee47c92879dc079fe115a8412abd39ef39ca4 +SANITIZED_OUTPUT_SHA256=b1ad62957baf7f8680f85ae10f1db94077a48c98e438dda3987b551f7a15cb13 +OUTPUT_BEGIN +DETAIL=FILE=/usr/local/sbin/homelab-chat-run-linked +DETAIL=SHA256=5e8725e129fd2055b36fe2fae13fc95fc4b3271074a0e0d4bb7347e7b2f270a1 +DETAIL=FILE=/usr/local/sbin/homelab-chat-run +DETAIL=SHA256=2a88defef9517f89bc60934dce82097f8e316b08af9417e95167ea5d759a10d9 +DETAIL=SOURCE=51- read-only|verify|change|transport-test) +DETAIL=SOURCE=52- ;; +DETAIL=SOURCE=53- *) +DETAIL=SOURCE=54- echo 'RESULT=FAIL' +DETAIL=SOURCE=55- echo 'REASON=INVALID_MODE' +DETAIL=SOURCE=56- exit 67 +DETAIL=SOURCE=57- ;; +DETAIL=SOURCE=58-esac +DETAIL=SOURCE=59- +DETAIL=SOURCE=60-for TOOL in \ +DETAIL=SOURCE=61- awk \ +DETAIL=SOURCE=62- curl \ +DETAIL=SOURCE=63- flock \ +DETAIL=SOURCE=64- python3 \ +DETAIL=SOURCE=65- scp \ +DETAIL=SOURCE=66: sha256sum \ +DETAIL=SOURCE=67- ssh +DETAIL=SOURCE=68-do +DETAIL=SOURCE=69- command -v "$TOOL" >/dev/null 2>&1 || { +DETAIL=SOURCE=70- echo 'RESULT=FAIL' +DETAIL=SOURCE=71- echo "REASON=REQUIRED_TOOL_MISSING:$TOOL" +DETAIL=SOURCE=72- exit 68 +DETAIL=SOURCE=73- } +DETAIL=SOURCE=74-done +DETAIL=SOURCE=75- +DETAIL=SOURCE=76-REFERENCE=/etc/pve/31_HOMELAB_REFERENCE.md +DETAIL=SOURCE=77-ERROR_REGISTER=/etc/pve/HOMELAB_ASSISTANT_ERROR_REGISTER.md +DETAIL=SOURCE=78- +DETAIL=SOURCE=79-[ -r "$REFERENCE" ] || { +DETAIL=SOURCE=80- echo 'RESULT=FAIL' +DETAIL=SOURCE=81- echo 'REASON=REFERENCE_NOT_READABLE' +DETAIL=SOURCE=82- exit 69 +DETAIL=SOURCE=83-} +DETAIL=SOURCE=84- +DETAIL=SOURCE=85-[ -r "$ERROR_REGISTER" ] || { +DETAIL=SOURCE=86- echo 'RESULT=FAIL' +DETAIL=SOURCE=87- echo 'REASON=ERROR_REGISTER_NOT_READABLE' +DETAIL=SOURCE=88- exit 70 +DETAIL=SOURCE=89-} +DETAIL=SOURCE=90- +DETAIL=SOURCE=91-STATE_DIR=/var/lib/homelab-chat-run +DETAIL=SOURCE=92-RAW_DIR="$STATE_DIR/raw" +DETAIL=SOURCE=93-PUBLIC_DIR="$STATE_DIR/public" +DETAIL=SOURCE=94-LEDGER="$STATE_DIR/state/command-ledger.tsv" +DETAIL=SOURCE=95-LOCK="$STATE_DIR/state/runner.lock" +DETAIL=SOURCE=96- +DETAIL=SOURCE=-- +DETAIL=SOURCE=104-flock -w 60 9 || { +DETAIL=SOURCE=105- echo 'RESULT=FAIL' +DETAIL=SOURCE=106- echo 'REASON=RUNNER_LOCK_TIMEOUT' +DETAIL=SOURCE=107- exit 71 +DETAIL=SOURCE=108-} +DETAIL=SOURCE=109- +DETAIL=SOURCE=110-STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +DETAIL=SOURCE=111-STAMP="$(date -u +%Y%m%dT%H%M%SZ)" +DETAIL=SOURCE=112-HOST_SHORT="$(hostname -s 2>/dev/null || hostname)" +DETAIL=SOURCE=113- +DETAIL=SOURCE=114-RAW_FILE="$RAW_DIR/${COMMAND_ID}-${STAMP}.log" +DETAIL=SOURCE=115-SANITIZED_FILE="$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.txt" +DETAIL=SOURCE=116-JSON_FILE="$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.json" +DETAIL=SOURCE=117-TXT_FILE="$PUBLIC_DIR/${COMMAND_ID}-${STAMP}-latest.txt" +DETAIL=SOURCE=118- +DETAIL=SOURCE=119:REFERENCE_SHA256="$(sha256sum "$REFERENCE" | awk '{print $1}')" +DETAIL=SOURCE=120:ERROR_REGISTER_SHA256="$(sha256sum "$ERROR_REGISTER" | awk '{print $1}')" +DETAIL=SOURCE=121- +DETAIL=SOURCE=122:COMMAND_SHA256="$( +DETAIL=SOURCE=123- { +DETAIL=SOURCE=124- printf '%s\0' "$MODE" +DETAIL=SOURCE=125- printf '%s\0' "$COMPONENT" +DETAIL=SOURCE=126- printf '%s\0' "$@" +DETAIL=SOURCE=127- } | +DETAIL=SOURCE=128: sha256sum | +DETAIL=SOURCE=129- awk '{print $1}' +DETAIL=SOURCE=130-)" +DETAIL=SOURCE=131- +DETAIL=SOURCE=132-PREVIOUS_FAILED_RC="$( +DETAIL=SOURCE=133: awk -F '\t' -v hash="$COMMAND_SHA256" ' +DETAIL=SOURCE=134- $3 == hash && $4 != "0" { +DETAIL=SOURCE=135- value=$4 +DETAIL=SOURCE=136- } +DETAIL=SOURCE=137- +DETAIL=SOURCE=138- END { +DETAIL=SOURCE=139- print value +DETAIL=SOURCE=140- } +DETAIL=SOURCE=141- ' "$LEDGER" +DETAIL=SOURCE=142-)" +DETAIL=SOURCE=143- +DETAIL=SOURCE=144-RUN_STARTED=true +DETAIL=SOURCE=145-REPEAT_BLOCKED=false +DETAIL=SOURCE=146-BLOCK_REASON='' +DETAIL=SOURCE=147-CHANGES_MADE=false +DETAIL=SOURCE=148- +DETAIL=SOURCE=149-case "$MODE" in +DETAIL=SOURCE=150- change) +DETAIL=SOURCE=151- CHANGES_MADE=true +DETAIL=SOURCE=152- ;; +DETAIL=SOURCE=153-esac +DETAIL=SOURCE=154- +DETAIL=SOURCE=155-if [ -n "$PREVIOUS_FAILED_RC" ] && +DETAIL=SOURCE=156- [ "${HOMELAB_ALLOW_REPEAT:-0}" != 1 ]; then +DETAIL=SOURCE=157- +DETAIL=SOURCE=158- RUN_STARTED=false +DETAIL=SOURCE=159- REPEAT_BLOCKED=true +DETAIL=SOURCE=160: BLOCK_REASON='EXACT_FAILED_COMMAND_REPEAT' +DETAIL=SOURCE=161- RC=73 +DETAIL=SOURCE=162- STATUS='BLOCKED' +DETAIL=SOURCE=163- +DETAIL=SOURCE=164- { +DETAIL=SOURCE=165- echo 'COMMAND_EXECUTION_BLOCKED=yes' +DETAIL=SOURCE=166: echo 'REASON=EXACT_FAILED_COMMAND_REPEAT' +DETAIL=SOURCE=167- echo "PREVIOUS_RC=$PREVIOUS_FAILED_RC" +DETAIL=SOURCE=168: echo "COMMAND_SHA256=$COMMAND_SHA256" +DETAIL=SOURCE=169- echo +DETAIL=SOURCE=170- echo 'Set HOMELAB_ALLOW_REPEAT=1 only after reviewing the previous failure.' +DETAIL=SOURCE=171- } >"$RAW_FILE" +DETAIL=SOURCE=172-else +DETAIL=SOURCE=173- set +e +DETAIL=SOURCE=174- +DETAIL=SOURCE=175- if [ -n "${HOMELAB_TIMEOUT:-}" ]; then +DETAIL=SOURCE=176- case "$HOMELAB_TIMEOUT" in +DETAIL=SOURCE=177- *[!0-9]*|'') +DETAIL=SOURCE=178- echo 'RESULT=FAIL' +DETAIL=SOURCE=179- echo 'REASON=INVALID_HOMELAB_TIMEOUT' +DETAIL=SOURCE=180- exit 74 +DETAIL=SOURCE=181- ;; +DETAIL=SOURCE=182- esac +DETAIL=SOURCE=183- +DETAIL=SOURCE=184- timeout \ +DETAIL=SOURCE=185- --foreground \ +DETAIL=SOURCE=186- --preserve-status \ +DETAIL=SOURCE=187- "$HOMELAB_TIMEOUT" \ +DETAIL=SOURCE=188- "$@" >"$RAW_FILE" 2>&1 +DETAIL=SOURCE=189- RC=$? +DETAIL=SOURCE=190- else +DETAIL=SOURCE=191- "$@" >"$RAW_FILE" 2>&1 +DETAIL=SOURCE=192- RC=$? +DETAIL=SOURCE=193- fi +DETAIL=SOURCE=194- +DETAIL=SOURCE=195- set -e +DETAIL=SOURCE=196- +DETAIL=SOURCE=197- if [ "$RC" -eq 0 ]; then +DETAIL=SOURCE=198- STATUS='OK' +DETAIL=SOURCE=199- else +DETAIL=SOURCE=200- STATUS='FAIL' +DETAIL=SOURCE=201- fi +DETAIL=SOURCE=202-fi +DETAIL=SOURCE=203- +DETAIL=SOURCE=204-FINISHED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +DETAIL=SOURCE=205:RAW_SHA256="$(sha256sum "$RAW_FILE" | awk '{print $1}')" +DETAIL=SOURCE=206- +DETAIL=SOURCE=207-python3 - "$RAW_FILE" "$SANITIZED_FILE" <<'PY' +DETAIL=SOURCE=208-import re +DETAIL=SOURCE=209-import sys +DETAIL=SOURCE=210-from pathlib import Path +DETAIL=SOURCE=211- +DETAIL=SOURCE=212:source = Path(sys.argv[1]) +DETAIL=SOURCE=213:target = Path(sys.argv[2]) +DETAIL=SOURCE=214- +DETAIL=SOURCE=215-text = source.read_bytes().decode("utf-8", errors="replace") +DETAIL=SOURCE=216- +DETAIL=SOURCE=217-patterns = [ +DETAIL=SOURCE=218- ( +DETAIL=SOURCE=219- re.compile( +DETAIL=SOURCE=220- r"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----" +DETAIL=SOURCE=221- r".*?" +DETAIL=SOURCE=222- r"-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----", +DETAIL=SOURCE=223- re.IGNORECASE | re.DOTALL, +DETAIL=SOURCE=224- ), +DETAIL=SOURCE=225- "[PRIVATE_KEY_REDACTED]", +DETAIL=SOURCE=226- ), +DETAIL=SOURCE=227- ( +DETAIL=SOURCE=228- re.compile( +DETAIL=SOURCE=229- r"(?im)^(\s*(?:password|passwd|passphrase|authorization|" +DETAIL=SOURCE=230- r"api[_-]?key|access[_-]?token|refresh[_-]?token|" +DETAIL=SOURCE=231- r"private[_-]?key|secret)\s*[:=]\s*).*$" +DETAIL=SOURCE=232- ), +DETAIL=SOURCE=233- r"\1[REDACTED]", +DETAIL=SOURCE=234- ), +DETAIL=SOURCE=235- ( +DETAIL=SOURCE=236- re.compile(r"(?i)(Bearer\s+)[A-Za-z0-9._~+/=-]+"), +DETAIL=SOURCE=237- r"\1[REDACTED]", +DETAIL=SOURCE=238- ), +DETAIL=SOURCE=239- ( +DETAIL=SOURCE=240- re.compile(r"\b10(?:\.\d{1,3}){3}\b"), +DETAIL=SOURCE=241- "[PRIVATE_IP]", +DETAIL=SOURCE=242- ), +DETAIL=SOURCE=243- ( +DETAIL=SOURCE=-- +DETAIL=SOURCE=294-if len(encoded) > maximum: +DETAIL=SOURCE=295- first = encoded[:440_000].decode("utf-8", errors="ignore") +DETAIL=SOURCE=296- last = encoded[-440_000:].decode("utf-8", errors="ignore") +DETAIL=SOURCE=297- +DETAIL=SOURCE=298- text = ( +DETAIL=SOURCE=299- first +DETAIL=SOURCE=300- + "\n\n" +DETAIL=SOURCE=301- + "===== PUBLIC OUTPUT TRUNCATED; FULL RAW LOG RETAINED LOCALLY =====" +DETAIL=SOURCE=302- + "\n\n" +DETAIL=SOURCE=303- + last +DETAIL=SOURCE=304- ) +DETAIL=SOURCE=305- +DETAIL=SOURCE=306-target.write_text(text, encoding="utf-8") +DETAIL=SOURCE=307-PY +DETAIL=SOURCE=308- +DETAIL=SOURCE=309:SANITIZED_SHA256="$( +DETAIL=SOURCE=310: sha256sum "$SANITIZED_FILE" | +DETAIL=SOURCE=311- awk '{print $1}' +DETAIL=SOURCE=312-)" +DETAIL=SOURCE=313- +DETAIL=SOURCE=314-python3 - \ +DETAIL=SOURCE=315- "$JSON_FILE" \ +DETAIL=SOURCE=316- "$SANITIZED_FILE" \ +DETAIL=SOURCE=317- "$COMMAND_ID" \ +DETAIL=SOURCE=318- "$STATUS" \ +DETAIL=SOURCE=319- "$RC" \ +DETAIL=SOURCE=320- "$HOST_SHORT" \ +DETAIL=SOURCE=321- "$MODE" \ +DETAIL=SOURCE=322- "$COMPONENT" \ +DETAIL=SOURCE=323- "$STARTED_AT" \ +DETAIL=SOURCE=324- "$FINISHED_AT" \ +DETAIL=SOURCE=325: "$REFERENCE_SHA256" \ +DETAIL=SOURCE=326: "$ERROR_REGISTER_SHA256" \ +DETAIL=SOURCE=327: "$COMMAND_SHA256" \ +DETAIL=SOURCE=328: "$RAW_SHA256" \ +DETAIL=SOURCE=329: "$SANITIZED_SHA256" \ +DETAIL=SOURCE=330- "$RUN_STARTED" \ +DETAIL=SOURCE=331- "$REPEAT_BLOCKED" \ +DETAIL=SOURCE=332- "$BLOCK_REASON" \ +DETAIL=SOURCE=333- "$CHANGES_MADE" <<'PY' +DETAIL=SOURCE=334-import json +DETAIL=SOURCE=335-import sys +DETAIL=SOURCE=336-from pathlib import Path +DETAIL=SOURCE=337- +DETAIL=SOURCE=338-( +DETAIL=SOURCE=339- json_path, +DETAIL=SOURCE=340- output_path, +DETAIL=SOURCE=341- command_id, +DETAIL=SOURCE=342- status, +DETAIL=SOURCE=343- rc, +DETAIL=SOURCE=344- host, +DETAIL=SOURCE=345- mode, +DETAIL=SOURCE=346- component, +DETAIL=SOURCE=347- started, +DETAIL=SOURCE=348- finished, +DETAIL=SOURCE=349- reference_sha, +DETAIL=SOURCE=350- error_sha, +DETAIL=SOURCE=351- command_sha, +DETAIL=SOURCE=352- raw_sha, +DETAIL=SOURCE=353- sanitized_sha, +DETAIL=SOURCE=354- run_started, +DETAIL=SOURCE=355- repeat_blocked, +DETAIL=SOURCE=356- block_reason, +DETAIL=SOURCE=357- changes_made, +DETAIL=SOURCE=358:) = sys.argv[1:] +DETAIL=SOURCE=359- +DETAIL=SOURCE=360-output = Path(output_path).read_text( +DETAIL=SOURCE=361- encoding="utf-8", +DETAIL=SOURCE=362- errors="replace", +DETAIL=SOURCE=363-) +DETAIL=SOURCE=364- +DETAIL=SOURCE=365-encoded = output.encode("utf-8") +DETAIL=SOURCE=366-json_limit = 300_000 +DETAIL=SOURCE=367-output_truncated = len(encoded) > json_limit +DETAIL=SOURCE=368- +DETAIL=SOURCE=369-if output_truncated: +DETAIL=SOURCE=370- output_preview = encoded[:json_limit].decode( +DETAIL=SOURCE=371- "utf-8", +DETAIL=SOURCE=372- errors="ignore", +DETAIL=SOURCE=373- ) +DETAIL=SOURCE=374-else: +DETAIL=SOURCE=375- output_preview = output +DETAIL=SOURCE=376- +DETAIL=SOURCE=377-document = { +DETAIL=SOURCE=378- "schema_version": 1, +DETAIL=SOURCE=379- "channel": "homelab-runtime", +DETAIL=SOURCE=380- "command_id": command_id, +DETAIL=SOURCE=381- "status": status, +DETAIL=SOURCE=382- "rc": int(rc), +DETAIL=SOURCE=383- "host": host, +DETAIL=SOURCE=384- "mode": mode, +DETAIL=SOURCE=385- "component": component, +DETAIL=SOURCE=386- "started_at_utc": started, +DETAIL=SOURCE=387- "finished_at_utc": finished, +DETAIL=SOURCE=388- "reference_register_checked": True, +DETAIL=SOURCE=389: "reference_sha256": reference_sha, +DETAIL=SOURCE=390- "error_register_checked": True, +DETAIL=SOURCE=391: "error_register_sha256": error_sha, +DETAIL=SOURCE=392: "command_sha256": command_sha, +DETAIL=SOURCE=393: "duplicate_failed_command_blocked": repeat_blocked == "true", +DETAIL=SOURCE=394- "block_reason": block_reason or None, +DETAIL=SOURCE=395- "execution_started": run_started == "true", +DETAIL=SOURCE=396- "changes_made": changes_made == "true", +DETAIL=SOURCE=397- "sanitized": True, +DETAIL=SOURCE=398- "secrets_included": False, +DETAIL=SOURCE=399- "private_addresses_included": False, +DETAIL=SOURCE=400- "raw_evidence_retained_locally": True, +DETAIL=SOURCE=401: "raw_evidence_sha256": raw_sha, +DETAIL=SOURCE=402: "sanitized_output_sha256": sanitized_sha, +DETAIL=SOURCE=403- "output_truncated_in_json": output_truncated, +DETAIL=SOURCE=404- "full_sanitized_output_url": ( +DETAIL=SOURCE=405- "https://git.gram1.ru/.well-known/" +DETAIL=SOURCE=406- "homelab-runtime/latest.txt" +DETAIL=SOURCE=407- ), +DETAIL=SOURCE=408- "output": output_preview, +DETAIL=SOURCE=409-} +DETAIL=SOURCE=410- +DETAIL=SOURCE=411-Path(json_path).write_text( +DETAIL=SOURCE=412- json.dumps( +DETAIL=SOURCE=413- document, +DETAIL=SOURCE=414- ensure_ascii=False, +DETAIL=SOURCE=415- indent=2, +DETAIL=SOURCE=416- ) +DETAIL=SOURCE=417- + "\n", +DETAIL=SOURCE=418- encoding="utf-8", +DETAIL=SOURCE=419-) +DETAIL=SOURCE=420-PY +DETAIL=SOURCE=421- +DETAIL=SOURCE=422-{ +DETAIL=SOURCE=423- echo 'CHAT_OUTPUT_BEGIN' +DETAIL=SOURCE=424- echo "COMMAND_ID=$COMMAND_ID" +DETAIL=SOURCE=425- echo "STATUS=$STATUS" +DETAIL=SOURCE=426- echo "RC=$RC" +DETAIL=SOURCE=427- echo "HOST=$HOST_SHORT" +DETAIL=SOURCE=428- echo "MODE=$MODE" +DETAIL=SOURCE=429- echo "COMPONENT=$COMPONENT" +DETAIL=SOURCE=430- echo 'REFERENCE_REGISTER_CHECK=OK' +DETAIL=SOURCE=431: echo "REFERENCE_SHA256=$REFERENCE_SHA256" +DETAIL=SOURCE=432- echo 'ERROR_REGISTER_CHECK=OK' +DETAIL=SOURCE=433: echo "ERROR_REGISTER_SHA256=$ERROR_REGISTER_SHA256" +DETAIL=SOURCE=434: echo "COMMAND_SHA256=$COMMAND_SHA256" +DETAIL=SOURCE=435: echo "DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED" +DETAIL=SOURCE=436- echo "EXECUTION_STARTED=$RUN_STARTED" +DETAIL=SOURCE=437- echo "CHANGES_MADE=$CHANGES_MADE" +DETAIL=SOURCE=438- echo 'SANITIZED=yes' +DETAIL=SOURCE=439- echo 'SECRETS_INCLUDED=no' +DETAIL=SOURCE=440- echo 'PRIVATE_ADDRESSES_INCLUDED=no' +DETAIL=SOURCE=441: echo "RAW_EVIDENCE_SHA256=$RAW_SHA256" +DETAIL=SOURCE=442: echo "SANITIZED_OUTPUT_SHA256=$SANITIZED_SHA256" +DETAIL=SOURCE=443- echo 'OUTPUT_BEGIN' +DETAIL=SOURCE=444- cat "$SANITIZED_FILE" +DETAIL=SOURCE=445- echo +DETAIL=SOURCE=446- echo 'OUTPUT_END' +DETAIL=SOURCE=447- echo 'CHAT_OUTPUT_END' +DETAIL=SOURCE=448-} >"$TXT_FILE" +DETAIL=SOURCE=449- +DETAIL=SOURCE=450-chmod 0600 \ +DETAIL=SOURCE=451- "$RAW_FILE" \ +DETAIL=SOURCE=452- "$SANITIZED_FILE" \ +DETAIL=SOURCE=453- "$JSON_FILE" \ +DETAIL=SOURCE=454- "$TXT_FILE" +DETAIL=SOURCE=455- +DETAIL=SOURCE=456-TARGET=debian@[PRIVATE_IP] +DETAIL=SOURCE=457-REMOTE_PREFIX="/tmp/homelab-runtime-${COMMAND_ID}-$$" +DETAIL=SOURCE=458- +DETAIL=SOURCE=459-SSH_OPTS=( +DETAIL=SOURCE=460- -o BatchMode=yes +DETAIL=SOURCE=461- -o ConnectTimeout=10 +DETAIL=SOURCE=462- -o ConnectionAttempts=1 +DETAIL=SOURCE=463- -o StrictHostKeyChecking=yes +DETAIL=SOURCE=464- -o UserKnownHostsFile=[SENSITIVE_PATH] +DETAIL=SOURCE=465-) +DETAIL=SOURCE=466- +DETAIL=SOURCE=467-scp \ +DETAIL=SOURCE=468- -q \ +DETAIL=SOURCE=469- "${SSH_OPTS[@]}" \ +DETAIL=SOURCE=470- "$JSON_FILE" \ +DETAIL=SOURCE=471- "${TARGET}:${REMOTE_PREFIX}.json" +DETAIL=SOURCE=472- +DETAIL=SOURCE=-- +DETAIL=SOURCE=512- +DETAIL=SOURCE=513- sleep 2 +DETAIL=SOURCE=514-done +DETAIL=SOURCE=515- +DETAIL=SOURCE=516-if [ "$PUBLIC_VISIBLE" != true ]; then +DETAIL=SOURCE=517- echo 'RESULT=FAIL' +DETAIL=SOURCE=518- echo 'REASON=PUBLIC_RUNTIME_RESULT_NOT_VISIBLE' +DETAIL=SOURCE=519- echo "COMMAND_ID=$COMMAND_ID" +DETAIL=SOURCE=520- echo "LOCAL_RAW_FILE=$RAW_FILE" +DETAIL=SOURCE=521- exit 75 +DETAIL=SOURCE=522-fi +DETAIL=SOURCE=523- +DETAIL=SOURCE=524-printf '%s\t%s\t%s\t%s\t%s\t%s\n' \ +DETAIL=SOURCE=525- "$FINISHED_AT" \ +DETAIL=SOURCE=526- "$COMMAND_ID" \ +DETAIL=SOURCE=527: "$COMMAND_SHA256" \ +DETAIL=SOURCE=528- "$RC" \ +DETAIL=SOURCE=529- "$STATUS" \ +DETAIL=SOURCE=530- "$COMPONENT" >>"$LEDGER" +DETAIL=SOURCE=531- +DETAIL=SOURCE=532-echo +DETAIL=SOURCE=533-echo '===== HOMELAB CHAT RUN COMPLETE =====' +DETAIL=SOURCE=534-echo "COMMAND_ID=$COMMAND_ID" +DETAIL=SOURCE=535-echo "STATUS=$STATUS" +DETAIL=SOURCE=536-echo "RC=$RC" +DETAIL=SOURCE=537-echo 'REFERENCE_REGISTER_CHECK=OK' +DETAIL=SOURCE=538-echo 'ERROR_REGISTER_CHECK=OK' +DETAIL=SOURCE=539:echo "DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED" +DETAIL=SOURCE=540-echo 'GITEA_PUBLICATION=OK' +DETAIL=SOURCE=541-echo "PUBLIC_URL=$PUBLIC_URL" +DETAIL=SOURCE=542-echo "LOCAL_RAW_FILE=$RAW_FILE" +DETAIL=SOURCE=543-echo 'SHELL_SESSION_PRESERVED=yes' +DETAIL=SOURCE=544- +DETAIL=SOURCE=545-exit "$RC" +DETAIL=DUPLICATE_FINGERPRINT_AUDIT=PASS +DETAIL=SECRET_VALUES_OUTPUT=NONE +CHANGES_MADE=NO + +OUTPUT_END +CHAT_OUTPUT_END diff --git a/runtime/latest.json b/runtime/latest.json index 111cdd9..e75259e 100644 --- a/runtime/latest.json +++ b/runtime/latest.json @@ -1,30 +1,30 @@ { "schema_version": 1, "channel": "homelab-runtime", - "command_id": "HOMELAB-CLUSTER-CORE-HEALTH-425", - "status": "BLOCKED", - "rc": 73, + "command_id": "HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426", + "status": "OK", + "rc": 0, "host": "pve01", "mode": "read-only", "component": "cluster-audit", - "started_at_utc": "2026-07-24T09:34:33Z", - "finished_at_utc": "2026-07-24T09:34:33Z", + "started_at_utc": "2026-07-24T09:36:35Z", + "finished_at_utc": "2026-07-24T09:36:35Z", "reference_register_checked": true, "reference_sha256": "5763f2f8edc75fcf6f3951f8c95896d1cc112ceccb6cf5b64a383f540a4deb8e", "error_register_checked": true, "error_register_sha256": "08b3a769b8e38eaa1acec915352fb328180d70caa7ff6cd9d1ec0da426262752", - "command_sha256": "77896618b46e2f97362240deb743fb8e625075381e57377ee0314130be2b2026", - "duplicate_failed_command_blocked": true, - "block_reason": "EXACT_FAILED_COMMAND_REPEAT", - "execution_started": false, + "command_sha256": "d95e5a9d8480b33605c127ce6ebdc688d05087f3c0afbdda7c3af1a5129a3d3c", + "duplicate_failed_command_blocked": false, + "block_reason": null, + "execution_started": true, "changes_made": false, "sanitized": true, "secrets_included": false, "private_addresses_included": false, "raw_evidence_retained_locally": true, - "raw_evidence_sha256": "1b4fb2e0791e93c374cf0af491e412f8d211a067b8652929108a257cb36472b6", - "sanitized_output_sha256": "1b4fb2e0791e93c374cf0af491e412f8d211a067b8652929108a257cb36472b6", + "raw_evidence_sha256": "db73d9a0e67a6c8cdbb9fcb6ae3ee47c92879dc079fe115a8412abd39ef39ca4", + "sanitized_output_sha256": "b1ad62957baf7f8680f85ae10f1db94077a48c98e438dda3987b551f7a15cb13", "output_truncated_in_json": false, "full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt", - "output": "COMMAND_EXECUTION_BLOCKED=yes\nREASON=EXACT_FAILED_COMMAND_REPEAT\nPREVIOUS_RC=73\nCOMMAND_SHA256=77896618b46e2f97362240deb743fb8e625075381e57377ee0314130be2b2026\n\nSet HOMELAB_ALLOW_REPEAT=1 only after reviewing the previous failure.\n" + "output": "DETAIL=FILE=/usr/local/sbin/homelab-chat-run-linked\nDETAIL=SHA256=5e8725e129fd2055b36fe2fae13fc95fc4b3271074a0e0d4bb7347e7b2f270a1\nDETAIL=FILE=/usr/local/sbin/homelab-chat-run\nDETAIL=SHA256=2a88defef9517f89bc60934dce82097f8e316b08af9417e95167ea5d759a10d9\nDETAIL=SOURCE=51- read-only|verify|change|transport-test)\nDETAIL=SOURCE=52- ;;\nDETAIL=SOURCE=53- *)\nDETAIL=SOURCE=54- echo 'RESULT=FAIL'\nDETAIL=SOURCE=55- echo 'REASON=INVALID_MODE'\nDETAIL=SOURCE=56- exit 67\nDETAIL=SOURCE=57- ;;\nDETAIL=SOURCE=58-esac\nDETAIL=SOURCE=59-\nDETAIL=SOURCE=60-for TOOL in \\\nDETAIL=SOURCE=61- awk \\\nDETAIL=SOURCE=62- curl \\\nDETAIL=SOURCE=63- flock \\\nDETAIL=SOURCE=64- python3 \\\nDETAIL=SOURCE=65- scp \\\nDETAIL=SOURCE=66: sha256sum \\\nDETAIL=SOURCE=67- ssh\nDETAIL=SOURCE=68-do\nDETAIL=SOURCE=69- command -v \"$TOOL\" >/dev/null 2>&1 || {\nDETAIL=SOURCE=70- echo 'RESULT=FAIL'\nDETAIL=SOURCE=71- echo \"REASON=REQUIRED_TOOL_MISSING:$TOOL\"\nDETAIL=SOURCE=72- exit 68\nDETAIL=SOURCE=73- }\nDETAIL=SOURCE=74-done\nDETAIL=SOURCE=75-\nDETAIL=SOURCE=76-REFERENCE=/etc/pve/31_HOMELAB_REFERENCE.md\nDETAIL=SOURCE=77-ERROR_REGISTER=/etc/pve/HOMELAB_ASSISTANT_ERROR_REGISTER.md\nDETAIL=SOURCE=78-\nDETAIL=SOURCE=79-[ -r \"$REFERENCE\" ] || {\nDETAIL=SOURCE=80- echo 'RESULT=FAIL'\nDETAIL=SOURCE=81- echo 'REASON=REFERENCE_NOT_READABLE'\nDETAIL=SOURCE=82- exit 69\nDETAIL=SOURCE=83-}\nDETAIL=SOURCE=84-\nDETAIL=SOURCE=85-[ -r \"$ERROR_REGISTER\" ] || {\nDETAIL=SOURCE=86- echo 'RESULT=FAIL'\nDETAIL=SOURCE=87- echo 'REASON=ERROR_REGISTER_NOT_READABLE'\nDETAIL=SOURCE=88- exit 70\nDETAIL=SOURCE=89-}\nDETAIL=SOURCE=90-\nDETAIL=SOURCE=91-STATE_DIR=/var/lib/homelab-chat-run\nDETAIL=SOURCE=92-RAW_DIR=\"$STATE_DIR/raw\"\nDETAIL=SOURCE=93-PUBLIC_DIR=\"$STATE_DIR/public\"\nDETAIL=SOURCE=94-LEDGER=\"$STATE_DIR/state/command-ledger.tsv\"\nDETAIL=SOURCE=95-LOCK=\"$STATE_DIR/state/runner.lock\"\nDETAIL=SOURCE=96-\nDETAIL=SOURCE=--\nDETAIL=SOURCE=104-flock -w 60 9 || {\nDETAIL=SOURCE=105- echo 'RESULT=FAIL'\nDETAIL=SOURCE=106- echo 'REASON=RUNNER_LOCK_TIMEOUT'\nDETAIL=SOURCE=107- exit 71\nDETAIL=SOURCE=108-}\nDETAIL=SOURCE=109-\nDETAIL=SOURCE=110-STARTED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nDETAIL=SOURCE=111-STAMP=\"$(date -u +%Y%m%dT%H%M%SZ)\"\nDETAIL=SOURCE=112-HOST_SHORT=\"$(hostname -s 2>/dev/null || hostname)\"\nDETAIL=SOURCE=113-\nDETAIL=SOURCE=114-RAW_FILE=\"$RAW_DIR/${COMMAND_ID}-${STAMP}.log\"\nDETAIL=SOURCE=115-SANITIZED_FILE=\"$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.txt\"\nDETAIL=SOURCE=116-JSON_FILE=\"$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.json\"\nDETAIL=SOURCE=117-TXT_FILE=\"$PUBLIC_DIR/${COMMAND_ID}-${STAMP}-latest.txt\"\nDETAIL=SOURCE=118-\nDETAIL=SOURCE=119:REFERENCE_SHA256=\"$(sha256sum \"$REFERENCE\" | awk '{print $1}')\"\nDETAIL=SOURCE=120:ERROR_REGISTER_SHA256=\"$(sha256sum \"$ERROR_REGISTER\" | awk '{print $1}')\"\nDETAIL=SOURCE=121-\nDETAIL=SOURCE=122:COMMAND_SHA256=\"$(\nDETAIL=SOURCE=123- {\nDETAIL=SOURCE=124- printf '%s\\0' \"$MODE\"\nDETAIL=SOURCE=125- printf '%s\\0' \"$COMPONENT\"\nDETAIL=SOURCE=126- printf '%s\\0' \"$@\"\nDETAIL=SOURCE=127- } |\nDETAIL=SOURCE=128: sha256sum |\nDETAIL=SOURCE=129- awk '{print $1}'\nDETAIL=SOURCE=130-)\"\nDETAIL=SOURCE=131-\nDETAIL=SOURCE=132-PREVIOUS_FAILED_RC=\"$(\nDETAIL=SOURCE=133: awk -F '\\t' -v hash=\"$COMMAND_SHA256\" '\nDETAIL=SOURCE=134- $3 == hash && $4 != \"0\" {\nDETAIL=SOURCE=135- value=$4\nDETAIL=SOURCE=136- }\nDETAIL=SOURCE=137-\nDETAIL=SOURCE=138- END {\nDETAIL=SOURCE=139- print value\nDETAIL=SOURCE=140- }\nDETAIL=SOURCE=141- ' \"$LEDGER\"\nDETAIL=SOURCE=142-)\"\nDETAIL=SOURCE=143-\nDETAIL=SOURCE=144-RUN_STARTED=true\nDETAIL=SOURCE=145-REPEAT_BLOCKED=false\nDETAIL=SOURCE=146-BLOCK_REASON=''\nDETAIL=SOURCE=147-CHANGES_MADE=false\nDETAIL=SOURCE=148-\nDETAIL=SOURCE=149-case \"$MODE\" in\nDETAIL=SOURCE=150- change)\nDETAIL=SOURCE=151- CHANGES_MADE=true\nDETAIL=SOURCE=152- ;;\nDETAIL=SOURCE=153-esac\nDETAIL=SOURCE=154-\nDETAIL=SOURCE=155-if [ -n \"$PREVIOUS_FAILED_RC\" ] &&\nDETAIL=SOURCE=156- [ \"${HOMELAB_ALLOW_REPEAT:-0}\" != 1 ]; then\nDETAIL=SOURCE=157-\nDETAIL=SOURCE=158- RUN_STARTED=false\nDETAIL=SOURCE=159- REPEAT_BLOCKED=true\nDETAIL=SOURCE=160: BLOCK_REASON='EXACT_FAILED_COMMAND_REPEAT'\nDETAIL=SOURCE=161- RC=73\nDETAIL=SOURCE=162- STATUS='BLOCKED'\nDETAIL=SOURCE=163-\nDETAIL=SOURCE=164- {\nDETAIL=SOURCE=165- echo 'COMMAND_EXECUTION_BLOCKED=yes'\nDETAIL=SOURCE=166: echo 'REASON=EXACT_FAILED_COMMAND_REPEAT'\nDETAIL=SOURCE=167- echo \"PREVIOUS_RC=$PREVIOUS_FAILED_RC\"\nDETAIL=SOURCE=168: echo \"COMMAND_SHA256=$COMMAND_SHA256\"\nDETAIL=SOURCE=169- echo\nDETAIL=SOURCE=170- echo 'Set HOMELAB_ALLOW_REPEAT=1 only after reviewing the previous failure.'\nDETAIL=SOURCE=171- } >\"$RAW_FILE\"\nDETAIL=SOURCE=172-else\nDETAIL=SOURCE=173- set +e\nDETAIL=SOURCE=174-\nDETAIL=SOURCE=175- if [ -n \"${HOMELAB_TIMEOUT:-}\" ]; then\nDETAIL=SOURCE=176- case \"$HOMELAB_TIMEOUT\" in\nDETAIL=SOURCE=177- *[!0-9]*|'')\nDETAIL=SOURCE=178- echo 'RESULT=FAIL'\nDETAIL=SOURCE=179- echo 'REASON=INVALID_HOMELAB_TIMEOUT'\nDETAIL=SOURCE=180- exit 74\nDETAIL=SOURCE=181- ;;\nDETAIL=SOURCE=182- esac\nDETAIL=SOURCE=183-\nDETAIL=SOURCE=184- timeout \\\nDETAIL=SOURCE=185- --foreground \\\nDETAIL=SOURCE=186- --preserve-status \\\nDETAIL=SOURCE=187- \"$HOMELAB_TIMEOUT\" \\\nDETAIL=SOURCE=188- \"$@\" >\"$RAW_FILE\" 2>&1\nDETAIL=SOURCE=189- RC=$?\nDETAIL=SOURCE=190- else\nDETAIL=SOURCE=191- \"$@\" >\"$RAW_FILE\" 2>&1\nDETAIL=SOURCE=192- RC=$?\nDETAIL=SOURCE=193- fi\nDETAIL=SOURCE=194-\nDETAIL=SOURCE=195- set -e\nDETAIL=SOURCE=196-\nDETAIL=SOURCE=197- if [ \"$RC\" -eq 0 ]; then\nDETAIL=SOURCE=198- STATUS='OK'\nDETAIL=SOURCE=199- else\nDETAIL=SOURCE=200- STATUS='FAIL'\nDETAIL=SOURCE=201- fi\nDETAIL=SOURCE=202-fi\nDETAIL=SOURCE=203-\nDETAIL=SOURCE=204-FINISHED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nDETAIL=SOURCE=205:RAW_SHA256=\"$(sha256sum \"$RAW_FILE\" | awk '{print $1}')\"\nDETAIL=SOURCE=206-\nDETAIL=SOURCE=207-python3 - \"$RAW_FILE\" \"$SANITIZED_FILE\" <<'PY'\nDETAIL=SOURCE=208-import re\nDETAIL=SOURCE=209-import sys\nDETAIL=SOURCE=210-from pathlib import Path\nDETAIL=SOURCE=211-\nDETAIL=SOURCE=212:source = Path(sys.argv[1])\nDETAIL=SOURCE=213:target = Path(sys.argv[2])\nDETAIL=SOURCE=214-\nDETAIL=SOURCE=215-text = source.read_bytes().decode(\"utf-8\", errors=\"replace\")\nDETAIL=SOURCE=216-\nDETAIL=SOURCE=217-patterns = [\nDETAIL=SOURCE=218- (\nDETAIL=SOURCE=219- re.compile(\nDETAIL=SOURCE=220- r\"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----\"\nDETAIL=SOURCE=221- r\".*?\"\nDETAIL=SOURCE=222- r\"-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----\",\nDETAIL=SOURCE=223- re.IGNORECASE | re.DOTALL,\nDETAIL=SOURCE=224- ),\nDETAIL=SOURCE=225- \"[PRIVATE_KEY_REDACTED]\",\nDETAIL=SOURCE=226- ),\nDETAIL=SOURCE=227- (\nDETAIL=SOURCE=228- re.compile(\nDETAIL=SOURCE=229- r\"(?im)^(\\s*(?:password|passwd|passphrase|authorization|\"\nDETAIL=SOURCE=230- r\"api[_-]?key|access[_-]?token|refresh[_-]?token|\"\nDETAIL=SOURCE=231- r\"private[_-]?key|secret)\\s*[:=]\\s*).*$\"\nDETAIL=SOURCE=232- ),\nDETAIL=SOURCE=233- r\"\\1[REDACTED]\",\nDETAIL=SOURCE=234- ),\nDETAIL=SOURCE=235- (\nDETAIL=SOURCE=236- re.compile(r\"(?i)(Bearer\\s+)[A-Za-z0-9._~+/=-]+\"),\nDETAIL=SOURCE=237- r\"\\1[REDACTED]\",\nDETAIL=SOURCE=238- ),\nDETAIL=SOURCE=239- (\nDETAIL=SOURCE=240- re.compile(r\"\\b10(?:\\.\\d{1,3}){3}\\b\"),\nDETAIL=SOURCE=241- \"[PRIVATE_IP]\",\nDETAIL=SOURCE=242- ),\nDETAIL=SOURCE=243- (\nDETAIL=SOURCE=--\nDETAIL=SOURCE=294-if len(encoded) > maximum:\nDETAIL=SOURCE=295- first = encoded[:440_000].decode(\"utf-8\", errors=\"ignore\")\nDETAIL=SOURCE=296- last = encoded[-440_000:].decode(\"utf-8\", errors=\"ignore\")\nDETAIL=SOURCE=297-\nDETAIL=SOURCE=298- text = (\nDETAIL=SOURCE=299- first\nDETAIL=SOURCE=300- + \"\\n\\n\"\nDETAIL=SOURCE=301- + \"===== PUBLIC OUTPUT TRUNCATED; FULL RAW LOG RETAINED LOCALLY =====\"\nDETAIL=SOURCE=302- + \"\\n\\n\"\nDETAIL=SOURCE=303- + last\nDETAIL=SOURCE=304- )\nDETAIL=SOURCE=305-\nDETAIL=SOURCE=306-target.write_text(text, encoding=\"utf-8\")\nDETAIL=SOURCE=307-PY\nDETAIL=SOURCE=308-\nDETAIL=SOURCE=309:SANITIZED_SHA256=\"$(\nDETAIL=SOURCE=310: sha256sum \"$SANITIZED_FILE\" |\nDETAIL=SOURCE=311- awk '{print $1}'\nDETAIL=SOURCE=312-)\"\nDETAIL=SOURCE=313-\nDETAIL=SOURCE=314-python3 - \\\nDETAIL=SOURCE=315- \"$JSON_FILE\" \\\nDETAIL=SOURCE=316- \"$SANITIZED_FILE\" \\\nDETAIL=SOURCE=317- \"$COMMAND_ID\" \\\nDETAIL=SOURCE=318- \"$STATUS\" \\\nDETAIL=SOURCE=319- \"$RC\" \\\nDETAIL=SOURCE=320- \"$HOST_SHORT\" \\\nDETAIL=SOURCE=321- \"$MODE\" \\\nDETAIL=SOURCE=322- \"$COMPONENT\" \\\nDETAIL=SOURCE=323- \"$STARTED_AT\" \\\nDETAIL=SOURCE=324- \"$FINISHED_AT\" \\\nDETAIL=SOURCE=325: \"$REFERENCE_SHA256\" \\\nDETAIL=SOURCE=326: \"$ERROR_REGISTER_SHA256\" \\\nDETAIL=SOURCE=327: \"$COMMAND_SHA256\" \\\nDETAIL=SOURCE=328: \"$RAW_SHA256\" \\\nDETAIL=SOURCE=329: \"$SANITIZED_SHA256\" \\\nDETAIL=SOURCE=330- \"$RUN_STARTED\" \\\nDETAIL=SOURCE=331- \"$REPEAT_BLOCKED\" \\\nDETAIL=SOURCE=332- \"$BLOCK_REASON\" \\\nDETAIL=SOURCE=333- \"$CHANGES_MADE\" <<'PY'\nDETAIL=SOURCE=334-import json\nDETAIL=SOURCE=335-import sys\nDETAIL=SOURCE=336-from pathlib import Path\nDETAIL=SOURCE=337-\nDETAIL=SOURCE=338-(\nDETAIL=SOURCE=339- json_path,\nDETAIL=SOURCE=340- output_path,\nDETAIL=SOURCE=341- command_id,\nDETAIL=SOURCE=342- status,\nDETAIL=SOURCE=343- rc,\nDETAIL=SOURCE=344- host,\nDETAIL=SOURCE=345- mode,\nDETAIL=SOURCE=346- component,\nDETAIL=SOURCE=347- started,\nDETAIL=SOURCE=348- finished,\nDETAIL=SOURCE=349- reference_sha,\nDETAIL=SOURCE=350- error_sha,\nDETAIL=SOURCE=351- command_sha,\nDETAIL=SOURCE=352- raw_sha,\nDETAIL=SOURCE=353- sanitized_sha,\nDETAIL=SOURCE=354- run_started,\nDETAIL=SOURCE=355- repeat_blocked,\nDETAIL=SOURCE=356- block_reason,\nDETAIL=SOURCE=357- changes_made,\nDETAIL=SOURCE=358:) = sys.argv[1:]\nDETAIL=SOURCE=359-\nDETAIL=SOURCE=360-output = Path(output_path).read_text(\nDETAIL=SOURCE=361- encoding=\"utf-8\",\nDETAIL=SOURCE=362- errors=\"replace\",\nDETAIL=SOURCE=363-)\nDETAIL=SOURCE=364-\nDETAIL=SOURCE=365-encoded = output.encode(\"utf-8\")\nDETAIL=SOURCE=366-json_limit = 300_000\nDETAIL=SOURCE=367-output_truncated = len(encoded) > json_limit\nDETAIL=SOURCE=368-\nDETAIL=SOURCE=369-if output_truncated:\nDETAIL=SOURCE=370- output_preview = encoded[:json_limit].decode(\nDETAIL=SOURCE=371- \"utf-8\",\nDETAIL=SOURCE=372- errors=\"ignore\",\nDETAIL=SOURCE=373- )\nDETAIL=SOURCE=374-else:\nDETAIL=SOURCE=375- output_preview = output\nDETAIL=SOURCE=376-\nDETAIL=SOURCE=377-document = {\nDETAIL=SOURCE=378- \"schema_version\": 1,\nDETAIL=SOURCE=379- \"channel\": \"homelab-runtime\",\nDETAIL=SOURCE=380- \"command_id\": command_id,\nDETAIL=SOURCE=381- \"status\": status,\nDETAIL=SOURCE=382- \"rc\": int(rc),\nDETAIL=SOURCE=383- \"host\": host,\nDETAIL=SOURCE=384- \"mode\": mode,\nDETAIL=SOURCE=385- \"component\": component,\nDETAIL=SOURCE=386- \"started_at_utc\": started,\nDETAIL=SOURCE=387- \"finished_at_utc\": finished,\nDETAIL=SOURCE=388- \"reference_register_checked\": True,\nDETAIL=SOURCE=389: \"reference_sha256\": reference_sha,\nDETAIL=SOURCE=390- \"error_register_checked\": True,\nDETAIL=SOURCE=391: \"error_register_sha256\": error_sha,\nDETAIL=SOURCE=392: \"command_sha256\": command_sha,\nDETAIL=SOURCE=393: \"duplicate_failed_command_blocked\": repeat_blocked == \"true\",\nDETAIL=SOURCE=394- \"block_reason\": block_reason or None,\nDETAIL=SOURCE=395- \"execution_started\": run_started == \"true\",\nDETAIL=SOURCE=396- \"changes_made\": changes_made == \"true\",\nDETAIL=SOURCE=397- \"sanitized\": True,\nDETAIL=SOURCE=398- \"secrets_included\": False,\nDETAIL=SOURCE=399- \"private_addresses_included\": False,\nDETAIL=SOURCE=400- \"raw_evidence_retained_locally\": True,\nDETAIL=SOURCE=401: \"raw_evidence_sha256\": raw_sha,\nDETAIL=SOURCE=402: \"sanitized_output_sha256\": sanitized_sha,\nDETAIL=SOURCE=403- \"output_truncated_in_json\": output_truncated,\nDETAIL=SOURCE=404- \"full_sanitized_output_url\": (\nDETAIL=SOURCE=405- \"https://git.gram1.ru/.well-known/\"\nDETAIL=SOURCE=406- \"homelab-runtime/latest.txt\"\nDETAIL=SOURCE=407- ),\nDETAIL=SOURCE=408- \"output\": output_preview,\nDETAIL=SOURCE=409-}\nDETAIL=SOURCE=410-\nDETAIL=SOURCE=411-Path(json_path).write_text(\nDETAIL=SOURCE=412- json.dumps(\nDETAIL=SOURCE=413- document,\nDETAIL=SOURCE=414- ensure_ascii=False,\nDETAIL=SOURCE=415- indent=2,\nDETAIL=SOURCE=416- )\nDETAIL=SOURCE=417- + \"\\n\",\nDETAIL=SOURCE=418- encoding=\"utf-8\",\nDETAIL=SOURCE=419-)\nDETAIL=SOURCE=420-PY\nDETAIL=SOURCE=421-\nDETAIL=SOURCE=422-{\nDETAIL=SOURCE=423- echo 'CHAT_OUTPUT_BEGIN'\nDETAIL=SOURCE=424- echo \"COMMAND_ID=$COMMAND_ID\"\nDETAIL=SOURCE=425- echo \"STATUS=$STATUS\"\nDETAIL=SOURCE=426- echo \"RC=$RC\"\nDETAIL=SOURCE=427- echo \"HOST=$HOST_SHORT\"\nDETAIL=SOURCE=428- echo \"MODE=$MODE\"\nDETAIL=SOURCE=429- echo \"COMPONENT=$COMPONENT\"\nDETAIL=SOURCE=430- echo 'REFERENCE_REGISTER_CHECK=OK'\nDETAIL=SOURCE=431: echo \"REFERENCE_SHA256=$REFERENCE_SHA256\"\nDETAIL=SOURCE=432- echo 'ERROR_REGISTER_CHECK=OK'\nDETAIL=SOURCE=433: echo \"ERROR_REGISTER_SHA256=$ERROR_REGISTER_SHA256\"\nDETAIL=SOURCE=434: echo \"COMMAND_SHA256=$COMMAND_SHA256\"\nDETAIL=SOURCE=435: echo \"DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED\"\nDETAIL=SOURCE=436- echo \"EXECUTION_STARTED=$RUN_STARTED\"\nDETAIL=SOURCE=437- echo \"CHANGES_MADE=$CHANGES_MADE\"\nDETAIL=SOURCE=438- echo 'SANITIZED=yes'\nDETAIL=SOURCE=439- echo 'SECRETS_INCLUDED=no'\nDETAIL=SOURCE=440- echo 'PRIVATE_ADDRESSES_INCLUDED=no'\nDETAIL=SOURCE=441: echo \"RAW_EVIDENCE_SHA256=$RAW_SHA256\"\nDETAIL=SOURCE=442: echo \"SANITIZED_OUTPUT_SHA256=$SANITIZED_SHA256\"\nDETAIL=SOURCE=443- echo 'OUTPUT_BEGIN'\nDETAIL=SOURCE=444- cat \"$SANITIZED_FILE\"\nDETAIL=SOURCE=445- echo\nDETAIL=SOURCE=446- echo 'OUTPUT_END'\nDETAIL=SOURCE=447- echo 'CHAT_OUTPUT_END'\nDETAIL=SOURCE=448-} >\"$TXT_FILE\"\nDETAIL=SOURCE=449-\nDETAIL=SOURCE=450-chmod 0600 \\\nDETAIL=SOURCE=451- \"$RAW_FILE\" \\\nDETAIL=SOURCE=452- \"$SANITIZED_FILE\" \\\nDETAIL=SOURCE=453- \"$JSON_FILE\" \\\nDETAIL=SOURCE=454- \"$TXT_FILE\"\nDETAIL=SOURCE=455-\nDETAIL=SOURCE=456-TARGET=debian@[PRIVATE_IP]\nDETAIL=SOURCE=457-REMOTE_PREFIX=\"/tmp/homelab-runtime-${COMMAND_ID}-$$\"\nDETAIL=SOURCE=458-\nDETAIL=SOURCE=459-SSH_OPTS=(\nDETAIL=SOURCE=460- -o BatchMode=yes\nDETAIL=SOURCE=461- -o ConnectTimeout=10\nDETAIL=SOURCE=462- -o ConnectionAttempts=1\nDETAIL=SOURCE=463- -o StrictHostKeyChecking=yes\nDETAIL=SOURCE=464- -o UserKnownHostsFile=[SENSITIVE_PATH]\nDETAIL=SOURCE=465-)\nDETAIL=SOURCE=466-\nDETAIL=SOURCE=467-scp \\\nDETAIL=SOURCE=468- -q \\\nDETAIL=SOURCE=469- \"${SSH_OPTS[@]}\" \\\nDETAIL=SOURCE=470- \"$JSON_FILE\" \\\nDETAIL=SOURCE=471- \"${TARGET}:${REMOTE_PREFIX}.json\"\nDETAIL=SOURCE=472-\nDETAIL=SOURCE=--\nDETAIL=SOURCE=512-\nDETAIL=SOURCE=513- sleep 2\nDETAIL=SOURCE=514-done\nDETAIL=SOURCE=515-\nDETAIL=SOURCE=516-if [ \"$PUBLIC_VISIBLE\" != true ]; then\nDETAIL=SOURCE=517- echo 'RESULT=FAIL'\nDETAIL=SOURCE=518- echo 'REASON=PUBLIC_RUNTIME_RESULT_NOT_VISIBLE'\nDETAIL=SOURCE=519- echo \"COMMAND_ID=$COMMAND_ID\"\nDETAIL=SOURCE=520- echo \"LOCAL_RAW_FILE=$RAW_FILE\"\nDETAIL=SOURCE=521- exit 75\nDETAIL=SOURCE=522-fi\nDETAIL=SOURCE=523-\nDETAIL=SOURCE=524-printf '%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n' \\\nDETAIL=SOURCE=525- \"$FINISHED_AT\" \\\nDETAIL=SOURCE=526- \"$COMMAND_ID\" \\\nDETAIL=SOURCE=527: \"$COMMAND_SHA256\" \\\nDETAIL=SOURCE=528- \"$RC\" \\\nDETAIL=SOURCE=529- \"$STATUS\" \\\nDETAIL=SOURCE=530- \"$COMPONENT\" >>\"$LEDGER\"\nDETAIL=SOURCE=531-\nDETAIL=SOURCE=532-echo\nDETAIL=SOURCE=533-echo '===== HOMELAB CHAT RUN COMPLETE ====='\nDETAIL=SOURCE=534-echo \"COMMAND_ID=$COMMAND_ID\"\nDETAIL=SOURCE=535-echo \"STATUS=$STATUS\"\nDETAIL=SOURCE=536-echo \"RC=$RC\"\nDETAIL=SOURCE=537-echo 'REFERENCE_REGISTER_CHECK=OK'\nDETAIL=SOURCE=538-echo 'ERROR_REGISTER_CHECK=OK'\nDETAIL=SOURCE=539:echo \"DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED\"\nDETAIL=SOURCE=540-echo 'GITEA_PUBLICATION=OK'\nDETAIL=SOURCE=541-echo \"PUBLIC_URL=$PUBLIC_URL\"\nDETAIL=SOURCE=542-echo \"LOCAL_RAW_FILE=$RAW_FILE\"\nDETAIL=SOURCE=543-echo 'SHELL_SESSION_PRESERVED=yes'\nDETAIL=SOURCE=544-\nDETAIL=SOURCE=545-exit \"$RC\"\nDETAIL=DUPLICATE_FINGERPRINT_AUDIT=PASS\nDETAIL=SECRET_VALUES_OUTPUT=NONE\nCHANGES_MADE=NO\n" } diff --git a/runtime/latest.txt b/runtime/latest.txt index 0d33c2c..3f436a9 100644 --- a/runtime/latest.txt +++ b/runtime/latest.txt @@ -1,7 +1,7 @@ CHAT_OUTPUT_BEGIN -COMMAND_ID=HOMELAB-CLUSTER-CORE-HEALTH-425 -STATUS=BLOCKED -RC=73 +COMMAND_ID=HOMELAB-DUPLICATE-FINGERPRINT-AUDIT-426 +STATUS=OK +RC=0 HOST=pve01 MODE=read-only COMPONENT=cluster-audit @@ -9,22 +9,425 @@ REFERENCE_REGISTER_CHECK=OK REFERENCE_SHA256=5763f2f8edc75fcf6f3951f8c95896d1cc112ceccb6cf5b64a383f540a4deb8e ERROR_REGISTER_CHECK=OK ERROR_REGISTER_SHA256=08b3a769b8e38eaa1acec915352fb328180d70caa7ff6cd9d1ec0da426262752 -COMMAND_SHA256=77896618b46e2f97362240deb743fb8e625075381e57377ee0314130be2b2026 -DUPLICATE_FAILED_COMMAND_BLOCKED=true -EXECUTION_STARTED=false +COMMAND_SHA256=d95e5a9d8480b33605c127ce6ebdc688d05087f3c0afbdda7c3af1a5129a3d3c +DUPLICATE_FAILED_COMMAND_BLOCKED=false +EXECUTION_STARTED=true CHANGES_MADE=false SANITIZED=yes SECRETS_INCLUDED=no PRIVATE_ADDRESSES_INCLUDED=no -RAW_EVIDENCE_SHA256=1b4fb2e0791e93c374cf0af491e412f8d211a067b8652929108a257cb36472b6 -SANITIZED_OUTPUT_SHA256=1b4fb2e0791e93c374cf0af491e412f8d211a067b8652929108a257cb36472b6 +RAW_EVIDENCE_SHA256=db73d9a0e67a6c8cdbb9fcb6ae3ee47c92879dc079fe115a8412abd39ef39ca4 +SANITIZED_OUTPUT_SHA256=b1ad62957baf7f8680f85ae10f1db94077a48c98e438dda3987b551f7a15cb13 OUTPUT_BEGIN -COMMAND_EXECUTION_BLOCKED=yes -REASON=EXACT_FAILED_COMMAND_REPEAT -PREVIOUS_RC=73 -COMMAND_SHA256=77896618b46e2f97362240deb743fb8e625075381e57377ee0314130be2b2026 - -Set HOMELAB_ALLOW_REPEAT=1 only after reviewing the previous failure. +DETAIL=FILE=/usr/local/sbin/homelab-chat-run-linked +DETAIL=SHA256=5e8725e129fd2055b36fe2fae13fc95fc4b3271074a0e0d4bb7347e7b2f270a1 +DETAIL=FILE=/usr/local/sbin/homelab-chat-run +DETAIL=SHA256=2a88defef9517f89bc60934dce82097f8e316b08af9417e95167ea5d759a10d9 +DETAIL=SOURCE=51- read-only|verify|change|transport-test) +DETAIL=SOURCE=52- ;; +DETAIL=SOURCE=53- *) +DETAIL=SOURCE=54- echo 'RESULT=FAIL' +DETAIL=SOURCE=55- echo 'REASON=INVALID_MODE' +DETAIL=SOURCE=56- exit 67 +DETAIL=SOURCE=57- ;; +DETAIL=SOURCE=58-esac +DETAIL=SOURCE=59- +DETAIL=SOURCE=60-for TOOL in \ +DETAIL=SOURCE=61- awk \ +DETAIL=SOURCE=62- curl \ +DETAIL=SOURCE=63- flock \ +DETAIL=SOURCE=64- python3 \ +DETAIL=SOURCE=65- scp \ +DETAIL=SOURCE=66: sha256sum \ +DETAIL=SOURCE=67- ssh +DETAIL=SOURCE=68-do +DETAIL=SOURCE=69- command -v "$TOOL" >/dev/null 2>&1 || { +DETAIL=SOURCE=70- echo 'RESULT=FAIL' +DETAIL=SOURCE=71- echo "REASON=REQUIRED_TOOL_MISSING:$TOOL" +DETAIL=SOURCE=72- exit 68 +DETAIL=SOURCE=73- } +DETAIL=SOURCE=74-done +DETAIL=SOURCE=75- +DETAIL=SOURCE=76-REFERENCE=/etc/pve/31_HOMELAB_REFERENCE.md +DETAIL=SOURCE=77-ERROR_REGISTER=/etc/pve/HOMELAB_ASSISTANT_ERROR_REGISTER.md +DETAIL=SOURCE=78- +DETAIL=SOURCE=79-[ -r "$REFERENCE" ] || { +DETAIL=SOURCE=80- echo 'RESULT=FAIL' +DETAIL=SOURCE=81- echo 'REASON=REFERENCE_NOT_READABLE' +DETAIL=SOURCE=82- exit 69 +DETAIL=SOURCE=83-} +DETAIL=SOURCE=84- +DETAIL=SOURCE=85-[ -r "$ERROR_REGISTER" ] || { +DETAIL=SOURCE=86- echo 'RESULT=FAIL' +DETAIL=SOURCE=87- echo 'REASON=ERROR_REGISTER_NOT_READABLE' +DETAIL=SOURCE=88- exit 70 +DETAIL=SOURCE=89-} +DETAIL=SOURCE=90- +DETAIL=SOURCE=91-STATE_DIR=/var/lib/homelab-chat-run +DETAIL=SOURCE=92-RAW_DIR="$STATE_DIR/raw" +DETAIL=SOURCE=93-PUBLIC_DIR="$STATE_DIR/public" +DETAIL=SOURCE=94-LEDGER="$STATE_DIR/state/command-ledger.tsv" +DETAIL=SOURCE=95-LOCK="$STATE_DIR/state/runner.lock" +DETAIL=SOURCE=96- +DETAIL=SOURCE=-- +DETAIL=SOURCE=104-flock -w 60 9 || { +DETAIL=SOURCE=105- echo 'RESULT=FAIL' +DETAIL=SOURCE=106- echo 'REASON=RUNNER_LOCK_TIMEOUT' +DETAIL=SOURCE=107- exit 71 +DETAIL=SOURCE=108-} +DETAIL=SOURCE=109- +DETAIL=SOURCE=110-STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +DETAIL=SOURCE=111-STAMP="$(date -u +%Y%m%dT%H%M%SZ)" +DETAIL=SOURCE=112-HOST_SHORT="$(hostname -s 2>/dev/null || hostname)" +DETAIL=SOURCE=113- +DETAIL=SOURCE=114-RAW_FILE="$RAW_DIR/${COMMAND_ID}-${STAMP}.log" +DETAIL=SOURCE=115-SANITIZED_FILE="$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.txt" +DETAIL=SOURCE=116-JSON_FILE="$PUBLIC_DIR/${COMMAND_ID}-${STAMP}.json" +DETAIL=SOURCE=117-TXT_FILE="$PUBLIC_DIR/${COMMAND_ID}-${STAMP}-latest.txt" +DETAIL=SOURCE=118- +DETAIL=SOURCE=119:REFERENCE_SHA256="$(sha256sum "$REFERENCE" | awk '{print $1}')" +DETAIL=SOURCE=120:ERROR_REGISTER_SHA256="$(sha256sum "$ERROR_REGISTER" | awk '{print $1}')" +DETAIL=SOURCE=121- +DETAIL=SOURCE=122:COMMAND_SHA256="$( +DETAIL=SOURCE=123- { +DETAIL=SOURCE=124- printf '%s\0' "$MODE" +DETAIL=SOURCE=125- printf '%s\0' "$COMPONENT" +DETAIL=SOURCE=126- printf '%s\0' "$@" +DETAIL=SOURCE=127- } | +DETAIL=SOURCE=128: sha256sum | +DETAIL=SOURCE=129- awk '{print $1}' +DETAIL=SOURCE=130-)" +DETAIL=SOURCE=131- +DETAIL=SOURCE=132-PREVIOUS_FAILED_RC="$( +DETAIL=SOURCE=133: awk -F '\t' -v hash="$COMMAND_SHA256" ' +DETAIL=SOURCE=134- $3 == hash && $4 != "0" { +DETAIL=SOURCE=135- value=$4 +DETAIL=SOURCE=136- } +DETAIL=SOURCE=137- +DETAIL=SOURCE=138- END { +DETAIL=SOURCE=139- print value +DETAIL=SOURCE=140- } +DETAIL=SOURCE=141- ' "$LEDGER" +DETAIL=SOURCE=142-)" +DETAIL=SOURCE=143- +DETAIL=SOURCE=144-RUN_STARTED=true +DETAIL=SOURCE=145-REPEAT_BLOCKED=false +DETAIL=SOURCE=146-BLOCK_REASON='' +DETAIL=SOURCE=147-CHANGES_MADE=false +DETAIL=SOURCE=148- +DETAIL=SOURCE=149-case "$MODE" in +DETAIL=SOURCE=150- change) +DETAIL=SOURCE=151- CHANGES_MADE=true +DETAIL=SOURCE=152- ;; +DETAIL=SOURCE=153-esac +DETAIL=SOURCE=154- +DETAIL=SOURCE=155-if [ -n "$PREVIOUS_FAILED_RC" ] && +DETAIL=SOURCE=156- [ "${HOMELAB_ALLOW_REPEAT:-0}" != 1 ]; then +DETAIL=SOURCE=157- +DETAIL=SOURCE=158- RUN_STARTED=false +DETAIL=SOURCE=159- REPEAT_BLOCKED=true +DETAIL=SOURCE=160: BLOCK_REASON='EXACT_FAILED_COMMAND_REPEAT' +DETAIL=SOURCE=161- RC=73 +DETAIL=SOURCE=162- STATUS='BLOCKED' +DETAIL=SOURCE=163- +DETAIL=SOURCE=164- { +DETAIL=SOURCE=165- echo 'COMMAND_EXECUTION_BLOCKED=yes' +DETAIL=SOURCE=166: echo 'REASON=EXACT_FAILED_COMMAND_REPEAT' +DETAIL=SOURCE=167- echo "PREVIOUS_RC=$PREVIOUS_FAILED_RC" +DETAIL=SOURCE=168: echo "COMMAND_SHA256=$COMMAND_SHA256" +DETAIL=SOURCE=169- echo +DETAIL=SOURCE=170- echo 'Set HOMELAB_ALLOW_REPEAT=1 only after reviewing the previous failure.' +DETAIL=SOURCE=171- } >"$RAW_FILE" +DETAIL=SOURCE=172-else +DETAIL=SOURCE=173- set +e +DETAIL=SOURCE=174- +DETAIL=SOURCE=175- if [ -n "${HOMELAB_TIMEOUT:-}" ]; then +DETAIL=SOURCE=176- case "$HOMELAB_TIMEOUT" in +DETAIL=SOURCE=177- *[!0-9]*|'') +DETAIL=SOURCE=178- echo 'RESULT=FAIL' +DETAIL=SOURCE=179- echo 'REASON=INVALID_HOMELAB_TIMEOUT' +DETAIL=SOURCE=180- exit 74 +DETAIL=SOURCE=181- ;; +DETAIL=SOURCE=182- esac +DETAIL=SOURCE=183- +DETAIL=SOURCE=184- timeout \ +DETAIL=SOURCE=185- --foreground \ +DETAIL=SOURCE=186- --preserve-status \ +DETAIL=SOURCE=187- "$HOMELAB_TIMEOUT" \ +DETAIL=SOURCE=188- "$@" >"$RAW_FILE" 2>&1 +DETAIL=SOURCE=189- RC=$? +DETAIL=SOURCE=190- else +DETAIL=SOURCE=191- "$@" >"$RAW_FILE" 2>&1 +DETAIL=SOURCE=192- RC=$? +DETAIL=SOURCE=193- fi +DETAIL=SOURCE=194- +DETAIL=SOURCE=195- set -e +DETAIL=SOURCE=196- +DETAIL=SOURCE=197- if [ "$RC" -eq 0 ]; then +DETAIL=SOURCE=198- STATUS='OK' +DETAIL=SOURCE=199- else +DETAIL=SOURCE=200- STATUS='FAIL' +DETAIL=SOURCE=201- fi +DETAIL=SOURCE=202-fi +DETAIL=SOURCE=203- +DETAIL=SOURCE=204-FINISHED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +DETAIL=SOURCE=205:RAW_SHA256="$(sha256sum "$RAW_FILE" | awk '{print $1}')" +DETAIL=SOURCE=206- +DETAIL=SOURCE=207-python3 - "$RAW_FILE" "$SANITIZED_FILE" <<'PY' +DETAIL=SOURCE=208-import re +DETAIL=SOURCE=209-import sys +DETAIL=SOURCE=210-from pathlib import Path +DETAIL=SOURCE=211- +DETAIL=SOURCE=212:source = Path(sys.argv[1]) +DETAIL=SOURCE=213:target = Path(sys.argv[2]) +DETAIL=SOURCE=214- +DETAIL=SOURCE=215-text = source.read_bytes().decode("utf-8", errors="replace") +DETAIL=SOURCE=216- +DETAIL=SOURCE=217-patterns = [ +DETAIL=SOURCE=218- ( +DETAIL=SOURCE=219- re.compile( +DETAIL=SOURCE=220- r"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----" +DETAIL=SOURCE=221- r".*?" +DETAIL=SOURCE=222- r"-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----", +DETAIL=SOURCE=223- re.IGNORECASE | re.DOTALL, +DETAIL=SOURCE=224- ), +DETAIL=SOURCE=225- "[PRIVATE_KEY_REDACTED]", +DETAIL=SOURCE=226- ), +DETAIL=SOURCE=227- ( +DETAIL=SOURCE=228- re.compile( +DETAIL=SOURCE=229- r"(?im)^(\s*(?:password|passwd|passphrase|authorization|" +DETAIL=SOURCE=230- r"api[_-]?key|access[_-]?token|refresh[_-]?token|" +DETAIL=SOURCE=231- r"private[_-]?key|secret)\s*[:=]\s*).*$" +DETAIL=SOURCE=232- ), +DETAIL=SOURCE=233- r"\1[REDACTED]", +DETAIL=SOURCE=234- ), +DETAIL=SOURCE=235- ( +DETAIL=SOURCE=236- re.compile(r"(?i)(Bearer\s+)[A-Za-z0-9._~+/=-]+"), +DETAIL=SOURCE=237- r"\1[REDACTED]", +DETAIL=SOURCE=238- ), +DETAIL=SOURCE=239- ( +DETAIL=SOURCE=240- re.compile(r"\b10(?:\.\d{1,3}){3}\b"), +DETAIL=SOURCE=241- "[PRIVATE_IP]", +DETAIL=SOURCE=242- ), +DETAIL=SOURCE=243- ( +DETAIL=SOURCE=-- +DETAIL=SOURCE=294-if len(encoded) > maximum: +DETAIL=SOURCE=295- first = encoded[:440_000].decode("utf-8", errors="ignore") +DETAIL=SOURCE=296- last = encoded[-440_000:].decode("utf-8", errors="ignore") +DETAIL=SOURCE=297- +DETAIL=SOURCE=298- text = ( +DETAIL=SOURCE=299- first +DETAIL=SOURCE=300- + "\n\n" +DETAIL=SOURCE=301- + "===== PUBLIC OUTPUT TRUNCATED; FULL RAW LOG RETAINED LOCALLY =====" +DETAIL=SOURCE=302- + "\n\n" +DETAIL=SOURCE=303- + last +DETAIL=SOURCE=304- ) +DETAIL=SOURCE=305- +DETAIL=SOURCE=306-target.write_text(text, encoding="utf-8") +DETAIL=SOURCE=307-PY +DETAIL=SOURCE=308- +DETAIL=SOURCE=309:SANITIZED_SHA256="$( +DETAIL=SOURCE=310: sha256sum "$SANITIZED_FILE" | +DETAIL=SOURCE=311- awk '{print $1}' +DETAIL=SOURCE=312-)" +DETAIL=SOURCE=313- +DETAIL=SOURCE=314-python3 - \ +DETAIL=SOURCE=315- "$JSON_FILE" \ +DETAIL=SOURCE=316- "$SANITIZED_FILE" \ +DETAIL=SOURCE=317- "$COMMAND_ID" \ +DETAIL=SOURCE=318- "$STATUS" \ +DETAIL=SOURCE=319- "$RC" \ +DETAIL=SOURCE=320- "$HOST_SHORT" \ +DETAIL=SOURCE=321- "$MODE" \ +DETAIL=SOURCE=322- "$COMPONENT" \ +DETAIL=SOURCE=323- "$STARTED_AT" \ +DETAIL=SOURCE=324- "$FINISHED_AT" \ +DETAIL=SOURCE=325: "$REFERENCE_SHA256" \ +DETAIL=SOURCE=326: "$ERROR_REGISTER_SHA256" \ +DETAIL=SOURCE=327: "$COMMAND_SHA256" \ +DETAIL=SOURCE=328: "$RAW_SHA256" \ +DETAIL=SOURCE=329: "$SANITIZED_SHA256" \ +DETAIL=SOURCE=330- "$RUN_STARTED" \ +DETAIL=SOURCE=331- "$REPEAT_BLOCKED" \ +DETAIL=SOURCE=332- "$BLOCK_REASON" \ +DETAIL=SOURCE=333- "$CHANGES_MADE" <<'PY' +DETAIL=SOURCE=334-import json +DETAIL=SOURCE=335-import sys +DETAIL=SOURCE=336-from pathlib import Path +DETAIL=SOURCE=337- +DETAIL=SOURCE=338-( +DETAIL=SOURCE=339- json_path, +DETAIL=SOURCE=340- output_path, +DETAIL=SOURCE=341- command_id, +DETAIL=SOURCE=342- status, +DETAIL=SOURCE=343- rc, +DETAIL=SOURCE=344- host, +DETAIL=SOURCE=345- mode, +DETAIL=SOURCE=346- component, +DETAIL=SOURCE=347- started, +DETAIL=SOURCE=348- finished, +DETAIL=SOURCE=349- reference_sha, +DETAIL=SOURCE=350- error_sha, +DETAIL=SOURCE=351- command_sha, +DETAIL=SOURCE=352- raw_sha, +DETAIL=SOURCE=353- sanitized_sha, +DETAIL=SOURCE=354- run_started, +DETAIL=SOURCE=355- repeat_blocked, +DETAIL=SOURCE=356- block_reason, +DETAIL=SOURCE=357- changes_made, +DETAIL=SOURCE=358:) = sys.argv[1:] +DETAIL=SOURCE=359- +DETAIL=SOURCE=360-output = Path(output_path).read_text( +DETAIL=SOURCE=361- encoding="utf-8", +DETAIL=SOURCE=362- errors="replace", +DETAIL=SOURCE=363-) +DETAIL=SOURCE=364- +DETAIL=SOURCE=365-encoded = output.encode("utf-8") +DETAIL=SOURCE=366-json_limit = 300_000 +DETAIL=SOURCE=367-output_truncated = len(encoded) > json_limit +DETAIL=SOURCE=368- +DETAIL=SOURCE=369-if output_truncated: +DETAIL=SOURCE=370- output_preview = encoded[:json_limit].decode( +DETAIL=SOURCE=371- "utf-8", +DETAIL=SOURCE=372- errors="ignore", +DETAIL=SOURCE=373- ) +DETAIL=SOURCE=374-else: +DETAIL=SOURCE=375- output_preview = output +DETAIL=SOURCE=376- +DETAIL=SOURCE=377-document = { +DETAIL=SOURCE=378- "schema_version": 1, +DETAIL=SOURCE=379- "channel": "homelab-runtime", +DETAIL=SOURCE=380- "command_id": command_id, +DETAIL=SOURCE=381- "status": status, +DETAIL=SOURCE=382- "rc": int(rc), +DETAIL=SOURCE=383- "host": host, +DETAIL=SOURCE=384- "mode": mode, +DETAIL=SOURCE=385- "component": component, +DETAIL=SOURCE=386- "started_at_utc": started, +DETAIL=SOURCE=387- "finished_at_utc": finished, +DETAIL=SOURCE=388- "reference_register_checked": True, +DETAIL=SOURCE=389: "reference_sha256": reference_sha, +DETAIL=SOURCE=390- "error_register_checked": True, +DETAIL=SOURCE=391: "error_register_sha256": error_sha, +DETAIL=SOURCE=392: "command_sha256": command_sha, +DETAIL=SOURCE=393: "duplicate_failed_command_blocked": repeat_blocked == "true", +DETAIL=SOURCE=394- "block_reason": block_reason or None, +DETAIL=SOURCE=395- "execution_started": run_started == "true", +DETAIL=SOURCE=396- "changes_made": changes_made == "true", +DETAIL=SOURCE=397- "sanitized": True, +DETAIL=SOURCE=398- "secrets_included": False, +DETAIL=SOURCE=399- "private_addresses_included": False, +DETAIL=SOURCE=400- "raw_evidence_retained_locally": True, +DETAIL=SOURCE=401: "raw_evidence_sha256": raw_sha, +DETAIL=SOURCE=402: "sanitized_output_sha256": sanitized_sha, +DETAIL=SOURCE=403- "output_truncated_in_json": output_truncated, +DETAIL=SOURCE=404- "full_sanitized_output_url": ( +DETAIL=SOURCE=405- "https://git.gram1.ru/.well-known/" +DETAIL=SOURCE=406- "homelab-runtime/latest.txt" +DETAIL=SOURCE=407- ), +DETAIL=SOURCE=408- "output": output_preview, +DETAIL=SOURCE=409-} +DETAIL=SOURCE=410- +DETAIL=SOURCE=411-Path(json_path).write_text( +DETAIL=SOURCE=412- json.dumps( +DETAIL=SOURCE=413- document, +DETAIL=SOURCE=414- ensure_ascii=False, +DETAIL=SOURCE=415- indent=2, +DETAIL=SOURCE=416- ) +DETAIL=SOURCE=417- + "\n", +DETAIL=SOURCE=418- encoding="utf-8", +DETAIL=SOURCE=419-) +DETAIL=SOURCE=420-PY +DETAIL=SOURCE=421- +DETAIL=SOURCE=422-{ +DETAIL=SOURCE=423- echo 'CHAT_OUTPUT_BEGIN' +DETAIL=SOURCE=424- echo "COMMAND_ID=$COMMAND_ID" +DETAIL=SOURCE=425- echo "STATUS=$STATUS" +DETAIL=SOURCE=426- echo "RC=$RC" +DETAIL=SOURCE=427- echo "HOST=$HOST_SHORT" +DETAIL=SOURCE=428- echo "MODE=$MODE" +DETAIL=SOURCE=429- echo "COMPONENT=$COMPONENT" +DETAIL=SOURCE=430- echo 'REFERENCE_REGISTER_CHECK=OK' +DETAIL=SOURCE=431: echo "REFERENCE_SHA256=$REFERENCE_SHA256" +DETAIL=SOURCE=432- echo 'ERROR_REGISTER_CHECK=OK' +DETAIL=SOURCE=433: echo "ERROR_REGISTER_SHA256=$ERROR_REGISTER_SHA256" +DETAIL=SOURCE=434: echo "COMMAND_SHA256=$COMMAND_SHA256" +DETAIL=SOURCE=435: echo "DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED" +DETAIL=SOURCE=436- echo "EXECUTION_STARTED=$RUN_STARTED" +DETAIL=SOURCE=437- echo "CHANGES_MADE=$CHANGES_MADE" +DETAIL=SOURCE=438- echo 'SANITIZED=yes' +DETAIL=SOURCE=439- echo 'SECRETS_INCLUDED=no' +DETAIL=SOURCE=440- echo 'PRIVATE_ADDRESSES_INCLUDED=no' +DETAIL=SOURCE=441: echo "RAW_EVIDENCE_SHA256=$RAW_SHA256" +DETAIL=SOURCE=442: echo "SANITIZED_OUTPUT_SHA256=$SANITIZED_SHA256" +DETAIL=SOURCE=443- echo 'OUTPUT_BEGIN' +DETAIL=SOURCE=444- cat "$SANITIZED_FILE" +DETAIL=SOURCE=445- echo +DETAIL=SOURCE=446- echo 'OUTPUT_END' +DETAIL=SOURCE=447- echo 'CHAT_OUTPUT_END' +DETAIL=SOURCE=448-} >"$TXT_FILE" +DETAIL=SOURCE=449- +DETAIL=SOURCE=450-chmod 0600 \ +DETAIL=SOURCE=451- "$RAW_FILE" \ +DETAIL=SOURCE=452- "$SANITIZED_FILE" \ +DETAIL=SOURCE=453- "$JSON_FILE" \ +DETAIL=SOURCE=454- "$TXT_FILE" +DETAIL=SOURCE=455- +DETAIL=SOURCE=456-TARGET=debian@[PRIVATE_IP] +DETAIL=SOURCE=457-REMOTE_PREFIX="/tmp/homelab-runtime-${COMMAND_ID}-$$" +DETAIL=SOURCE=458- +DETAIL=SOURCE=459-SSH_OPTS=( +DETAIL=SOURCE=460- -o BatchMode=yes +DETAIL=SOURCE=461- -o ConnectTimeout=10 +DETAIL=SOURCE=462- -o ConnectionAttempts=1 +DETAIL=SOURCE=463- -o StrictHostKeyChecking=yes +DETAIL=SOURCE=464- -o UserKnownHostsFile=[SENSITIVE_PATH] +DETAIL=SOURCE=465-) +DETAIL=SOURCE=466- +DETAIL=SOURCE=467-scp \ +DETAIL=SOURCE=468- -q \ +DETAIL=SOURCE=469- "${SSH_OPTS[@]}" \ +DETAIL=SOURCE=470- "$JSON_FILE" \ +DETAIL=SOURCE=471- "${TARGET}:${REMOTE_PREFIX}.json" +DETAIL=SOURCE=472- +DETAIL=SOURCE=-- +DETAIL=SOURCE=512- +DETAIL=SOURCE=513- sleep 2 +DETAIL=SOURCE=514-done +DETAIL=SOURCE=515- +DETAIL=SOURCE=516-if [ "$PUBLIC_VISIBLE" != true ]; then +DETAIL=SOURCE=517- echo 'RESULT=FAIL' +DETAIL=SOURCE=518- echo 'REASON=PUBLIC_RUNTIME_RESULT_NOT_VISIBLE' +DETAIL=SOURCE=519- echo "COMMAND_ID=$COMMAND_ID" +DETAIL=SOURCE=520- echo "LOCAL_RAW_FILE=$RAW_FILE" +DETAIL=SOURCE=521- exit 75 +DETAIL=SOURCE=522-fi +DETAIL=SOURCE=523- +DETAIL=SOURCE=524-printf '%s\t%s\t%s\t%s\t%s\t%s\n' \ +DETAIL=SOURCE=525- "$FINISHED_AT" \ +DETAIL=SOURCE=526- "$COMMAND_ID" \ +DETAIL=SOURCE=527: "$COMMAND_SHA256" \ +DETAIL=SOURCE=528- "$RC" \ +DETAIL=SOURCE=529- "$STATUS" \ +DETAIL=SOURCE=530- "$COMPONENT" >>"$LEDGER" +DETAIL=SOURCE=531- +DETAIL=SOURCE=532-echo +DETAIL=SOURCE=533-echo '===== HOMELAB CHAT RUN COMPLETE =====' +DETAIL=SOURCE=534-echo "COMMAND_ID=$COMMAND_ID" +DETAIL=SOURCE=535-echo "STATUS=$STATUS" +DETAIL=SOURCE=536-echo "RC=$RC" +DETAIL=SOURCE=537-echo 'REFERENCE_REGISTER_CHECK=OK' +DETAIL=SOURCE=538-echo 'ERROR_REGISTER_CHECK=OK' +DETAIL=SOURCE=539:echo "DUPLICATE_FAILED_COMMAND_BLOCKED=$REPEAT_BLOCKED" +DETAIL=SOURCE=540-echo 'GITEA_PUBLICATION=OK' +DETAIL=SOURCE=541-echo "PUBLIC_URL=$PUBLIC_URL" +DETAIL=SOURCE=542-echo "LOCAL_RAW_FILE=$RAW_FILE" +DETAIL=SOURCE=543-echo 'SHELL_SESSION_PRESERVED=yes' +DETAIL=SOURCE=544- +DETAIL=SOURCE=545-exit "$RC" +DETAIL=DUPLICATE_FINGERPRINT_AUDIT=PASS +DETAIL=SECRET_VALUES_OUTPUT=NONE +CHANGES_MADE=NO OUTPUT_END CHAT_OUTPUT_END