fix(sre): remove Host188 callback sudo dependency
This commit is contained in:
@@ -692,7 +692,7 @@ _CATALOG: tuple[dict[str, Any], ...] = (
|
||||
"approval_required": False,
|
||||
"risk_level": "medium",
|
||||
"canonical_asset_id": "service:openclaw:host188",
|
||||
"catalog_revision": "2026-07-17-openclaw-callback-overlay-v2",
|
||||
"catalog_revision": "2026-07-17-openclaw-callback-user-overlay-v3",
|
||||
"cross_domain_fallback_allowed": False,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -249,63 +249,53 @@ _POSTCONDITION_REGISTRY: dict[str, tuple[AssetPostcondition, ...]] = {
|
||||
"configuration",
|
||||
"host_188",
|
||||
"set -euo pipefail; "
|
||||
"root=/var/lib/awoooi-controlled-apply/openclaw-callback-forwarder/"
|
||||
"root=/home/ollama/.local/share/awoooi-controlled-apply/"
|
||||
"openclaw-callback-forwarder/"
|
||||
"payloads/ffc45311b6403b30cb6ae6e1aa83dae1a047e9c9 && "
|
||||
"manifest=\"$root/manifest.json\" && "
|
||||
"test -d \"$root\" && test ! -L \"$root\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$root\")\" = '0:0:755' && "
|
||||
"test \"$(stat -c '%U:%G:%a' \"$root\")\" = 'ollama:ollama:755' && "
|
||||
"test -f \"$manifest\" && test ! -L \"$manifest\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$manifest\")\" = '0:0:444' && "
|
||||
"test \"$(stat -c '%U:%G:%a' \"$manifest\")\" = 'ollama:ollama:444' && "
|
||||
"test \"$(sha256sum \"$manifest\" | cut -d' ' -f1)\" = "
|
||||
"f06df6f3163e5ae7bf999624b011a3ae0bff096c9d0c8cb4c43f8b40be43ae2b",
|
||||
"dafeb467b744ee7e80dc043d51f9e5d7e6905a58103f1710d519b553b56c6e48",
|
||||
),
|
||||
AssetPostcondition(
|
||||
"host_188_openclaw_callback_overlay_enabled",
|
||||
"configuration",
|
||||
"host_188",
|
||||
"set -euo pipefail; "
|
||||
"drop=/etc/systemd/system/clawbot.service.d/"
|
||||
"20-awoooi-callback-forwarder.conf && "
|
||||
"override=/etc/awoooi/openclaw-callback-forwarder/"
|
||||
"docker-compose.callback.yml && "
|
||||
"COMPOSE_FILE_key=COMPOSE_FILE && "
|
||||
"test \"$COMPOSE_FILE_key\" = 'COMPOSE_FILE' && "
|
||||
"wd=$(systemctl show clawbot.service --property=WorkingDirectory "
|
||||
"--value) && "
|
||||
"test -f \"$drop\" && test ! -L \"$drop\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$drop\")\" = '0:0:644' && "
|
||||
"drop_sha=$(sha256sum \"$drop\" | cut -d' ' -f1) && "
|
||||
"case \"$wd:$drop_sha\" in "
|
||||
"'/home/ollama/clawbot-v5:"
|
||||
"419fb4f249f7e1370fbca87086fd725e57b1f5cf54da2f84caf9a21fea7111e3'"
|
||||
"|'/opt/openclaw:"
|
||||
"4a78d414e4b591f54232fab04529861a6042718a7627c4e17ccd5cc0cbd485f9') "
|
||||
"true ;; *) false ;; esac && "
|
||||
"test \"$wd\" = '/home/ollama/clawbot-v5' && "
|
||||
"override=\"$wd/docker-compose.override.yml\" && "
|
||||
"test -f \"$override\" && test ! -L \"$override\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$override\")\" = '0:0:444' && "
|
||||
"test \"$(stat -c '%U:%G:%a' \"$override\")\" = "
|
||||
"'ollama:ollama:444' && "
|
||||
"test \"$(sha256sum \"$override\" | cut -d' ' -f1)\" = "
|
||||
"f6fa7f6724c35f86f1c8dafac07e2680231dfa29a5d45534ed0515c58ac2de77",
|
||||
"8f6b7e8a0d6746c1331e99910656e64deebf67c9582bab3e1d36f71bb7a253c0",
|
||||
),
|
||||
AssetPostcondition(
|
||||
"host_188_openclaw_callback_payload_runtime",
|
||||
"configuration",
|
||||
"host_188",
|
||||
"set -euo pipefail; "
|
||||
"root=/var/lib/awoooi-controlled-apply/openclaw-callback-forwarder/"
|
||||
"root=/home/ollama/.local/share/awoooi-controlled-apply/"
|
||||
"openclaw-callback-forwarder/"
|
||||
"payloads/ffc45311b6403b30cb6ae6e1aa83dae1a047e9c9 && "
|
||||
"telegram=\"$root/app/bot/telegram.py\" && "
|
||||
"config=\"$root/app/core/config.py\" && "
|
||||
"forwarder=\"$root/app/services/telegram_callback_forwarder.py\" && "
|
||||
"test -f \"$telegram\" && test ! -L \"$telegram\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$telegram\")\" = '0:0:444' && "
|
||||
"test \"$(stat -c '%U:%G:%a' \"$telegram\")\" = 'ollama:ollama:444' && "
|
||||
"test \"$(sha256sum \"$telegram\" | cut -d' ' -f1)\" = "
|
||||
"af9600f32305ae73184651a8d3437063d7c8d35e3c3eb0910a2e27ac874c9302 && "
|
||||
"test -f \"$config\" && test ! -L \"$config\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$config\")\" = '0:0:444' && "
|
||||
"test \"$(stat -c '%U:%G:%a' \"$config\")\" = 'ollama:ollama:444' && "
|
||||
"test \"$(sha256sum \"$config\" | cut -d' ' -f1)\" = "
|
||||
"e1b8b633f7b4780d3078d6c42a55e49d200255d3597a34e0cd2fd649b84eaea1 && "
|
||||
"test -f \"$forwarder\" && test ! -L \"$forwarder\" && "
|
||||
"test \"$(stat -c '%u:%g:%a' \"$forwarder\")\" = '0:0:444' && "
|
||||
"test \"$(stat -c '%U:%G:%a' \"$forwarder\")\" = 'ollama:ollama:444' && "
|
||||
"test \"$(sha256sum \"$forwarder\" | cut -d' ' -f1)\" = "
|
||||
"0d7693e34f63db4491d974f9f76a3a1c793d32cda193cb55ed3d8c86297b4fe3 && "
|
||||
"ids=$(docker ps -q "
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
@@ -31,6 +32,12 @@ MONITORING_REGISTRY = ROOT / "ops" / "monitoring" / "service-registry.yaml"
|
||||
CATALOG_ID = "ansible:188-openclaw-callback-forwarder"
|
||||
CANONICAL_ASSET_ID = "service:openclaw:host188"
|
||||
EXPECTED_SHA = "ffc45311b6403b30cb6ae6e1aa83dae1a047e9c9"
|
||||
EXPECTED_MANIFEST_SHA256 = (
|
||||
"dafeb467b744ee7e80dc043d51f9e5d7e6905a58103f1710d519b553b56c6e48"
|
||||
)
|
||||
EXPECTED_OVERRIDE_SHA256 = (
|
||||
"8f6b7e8a0d6746c1331e99910656e64deebf67c9582bab3e1d36f71bb7a253c0"
|
||||
)
|
||||
PAYLOAD_ROOT = (
|
||||
ROOT
|
||||
/ "infra"
|
||||
@@ -201,7 +208,7 @@ def test_openclaw_catalog_and_independent_verifier_are_exact() -> None:
|
||||
"host_188",
|
||||
]
|
||||
assert catalog["catalog_revision"] == (
|
||||
"2026-07-17-openclaw-callback-overlay-v2"
|
||||
"2026-07-17-openclaw-callback-user-overlay-v3"
|
||||
)
|
||||
assert catalog["cross_domain_fallback_allowed"] is False
|
||||
|
||||
@@ -217,7 +224,8 @@ def test_openclaw_catalog_and_independent_verifier_are_exact() -> None:
|
||||
assert {condition.inventory_host for condition in conditions} == {"host_188"}
|
||||
probes = "\n".join(condition.probe for condition in conditions)
|
||||
assert EXPECTED_SHA in probes
|
||||
assert "COMPOSE_FILE" in probes
|
||||
assert "docker-compose.override.yml" in probes
|
||||
assert "COMPOSE_FILE" not in probes
|
||||
assert "TELEGRAM_CALLBACK_FORWARD_ENABLED is True" in probes
|
||||
assert all(digest in probes for digest in EXPECTED_PAYLOAD_SHA256.values())
|
||||
assert "http://127.0.0.1:8088/health" in probes
|
||||
@@ -234,15 +242,15 @@ def test_openclaw_playbook_is_check_first_bounded_and_rollback_capable() -> None
|
||||
|
||||
assert play["hosts"] == "host_188"
|
||||
assert play["gather_facts"] is False
|
||||
assert play["become"] is True
|
||||
assert play["become"] is False
|
||||
assert play["vars"]["catalog_id"] == CATALOG_ID
|
||||
assert play["vars"]["canonical_asset_id"] == CANONICAL_ASSET_ID
|
||||
assert play["vars"]["expected_source_sha"] == EXPECTED_SHA
|
||||
assert play["vars"]["allowed_working_directories"] == [
|
||||
"/opt/openclaw",
|
||||
"/home/ollama/clawbot-v5",
|
||||
]
|
||||
assert len(play["vars"]["payloads"]) == 3
|
||||
assert play["vars"]["expected_manifest_sha256"] == EXPECTED_MANIFEST_SHA256
|
||||
assert {
|
||||
item["relative_path"]: item["target_sha256"]
|
||||
for item in play["vars"]["payloads"]
|
||||
@@ -298,23 +306,44 @@ def test_openclaw_playbook_is_check_first_bounded_and_rollback_capable() -> None
|
||||
assert check_receipt["ansible.builtin.debug"]["msg"][
|
||||
"runtime_source_mutation_allowed"
|
||||
] is False
|
||||
assert check_receipt["ansible.builtin.debug"]["msg"][
|
||||
"runtime_compose_override_write_allowed"
|
||||
] is True
|
||||
assert check_receipt["ansible.builtin.debug"]["msg"][
|
||||
"host_privilege_escalation_required"
|
||||
] is False
|
||||
assert check_receipt["ansible.builtin.debug"]["msg"][
|
||||
"image_build_allowed"
|
||||
] is False
|
||||
|
||||
for name in (
|
||||
"Reload systemd and restart the existing owned service once",
|
||||
"Reload systemd and restore the previous container definition",
|
||||
"Activate the existing Compose project without build or pull",
|
||||
"Restore the previous Compose definition without build or pull",
|
||||
):
|
||||
assert tasks[name]["ansible.builtin.systemd"] == {
|
||||
"name": "clawbot.service",
|
||||
"state": "restarted",
|
||||
"daemon_reload": True,
|
||||
}
|
||||
assert tasks[name]["ansible.builtin.command"]["argv"] == [
|
||||
"/usr/bin/docker",
|
||||
"compose",
|
||||
"up",
|
||||
"-d",
|
||||
"--no-build",
|
||||
"--pull",
|
||||
"never",
|
||||
]
|
||||
assert tasks[name]["args"]["chdir"] == "{{ openclaw_workdir }}"
|
||||
assert tasks[name]["no_log"] is True
|
||||
assert tasks["Install COMPOSE_FILE-only systemd drop-in"][
|
||||
"ansible.builtin.copy"
|
||||
]["content"] == "{{ callback_dropin_content }}"
|
||||
assert "Install COMPOSE_FILE-only systemd drop-in" not in tasks
|
||||
rendered_override = tasks["Define exact non-secret managed overlay documents"][
|
||||
"ansible.builtin.set_fact"
|
||||
]["compose_override_content"].replace(
|
||||
"{{ payload_root }}",
|
||||
(
|
||||
"/home/ollama/.local/share/awoooi-controlled-apply/"
|
||||
f"openclaw-callback-forwarder/payloads/{EXPECTED_SHA}"
|
||||
),
|
||||
)
|
||||
assert hashlib.sha256(rendered_override.encode()).hexdigest() == (
|
||||
EXPECTED_OVERRIDE_SHA256
|
||||
)
|
||||
correlation = tasks["Require same-run controlled execution correlation"][
|
||||
"ansible.builtin.assert"
|
||||
]["that"]
|
||||
@@ -333,7 +362,7 @@ def test_openclaw_playbook_is_check_first_bounded_and_rollback_capable() -> None
|
||||
assert "target_mounts_rollback == target_mounts_before" in tasks[
|
||||
"Require bounded rollback verification"
|
||||
]["ansible.builtin.assert"]["that"]
|
||||
assert tasks["Reload systemd and restore the previous container definition"][
|
||||
assert tasks["Restore the previous Compose definition without build or pull"][
|
||||
"when"
|
||||
] == "activation_attempted | default(false) | bool"
|
||||
runtime_setting = tasks[
|
||||
@@ -346,7 +375,7 @@ def test_openclaw_playbook_is_check_first_bounded_and_rollback_capable() -> None
|
||||
]["that"] == [
|
||||
"clawbot_image_after.stdout | trim == clawbot_image_before"
|
||||
]
|
||||
assert "openclaw_callback_overlay_rollback_v2" in source
|
||||
assert "openclaw_callback_overlay_rollback_v3" in source
|
||||
assert "runtime_checkout_preserved" in source
|
||||
for forbidden in (
|
||||
"git remote",
|
||||
@@ -364,12 +393,18 @@ def test_openclaw_playbook_is_check_first_bounded_and_rollback_capable() -> None
|
||||
|
||||
|
||||
def test_openclaw_payload_manifest_is_hash_pinned_and_overlay_only() -> None:
|
||||
import hashlib
|
||||
|
||||
manifest = json.loads((PAYLOAD_ROOT / "manifest.json").read_text(encoding="utf-8"))
|
||||
manifest_path = PAYLOAD_ROOT / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
assert hashlib.sha256(manifest_path.read_bytes()).hexdigest() == (
|
||||
EXPECTED_MANIFEST_SHA256
|
||||
)
|
||||
assert manifest["source_commit"] == EXPECTED_SHA
|
||||
assert manifest["deployment_mode"] == "root_owned_read_only_overlay_no_build"
|
||||
assert manifest["deployment_mode"] == (
|
||||
"host_user_owned_read_only_compose_override_no_build_no_pull"
|
||||
)
|
||||
assert manifest["runtime_source_mutation_allowed"] is False
|
||||
assert manifest["runtime_compose_override_write_allowed"] is True
|
||||
assert manifest["host_privilege_escalation_required"] is False
|
||||
assert manifest["runtime_secret_read_allowed"] is False
|
||||
assert {row["path"]: row["sha256"] for row in manifest["payloads"]} == (
|
||||
EXPECTED_PAYLOAD_SHA256
|
||||
|
||||
@@ -69,13 +69,13 @@ def test_host188_overlay_probes_are_exact_and_secret_blind() -> None:
|
||||
assert "docker inspect --format='{{.Config.WorkingDir}}'" in joined
|
||||
assert "{{.Source}}|{{.Destination}}|{{.Type}}|{{.RW}}" in joined
|
||||
assert "|bind|false" in joined
|
||||
assert "stat -c '%u:%g:%a'" in joined
|
||||
assert "0:0:444" in joined
|
||||
assert "0:0:644" in joined
|
||||
assert "f06df6f3163e5ae7bf999624b011a3ae0bff096c9d0c8cb4c43f8b40be43ae2b" in joined
|
||||
assert "f6fa7f6724c35f86f1c8dafac07e2680231dfa29a5d45534ed0515c58ac2de77" in joined
|
||||
assert "419fb4f249f7e1370fbca87086fd725e57b1f5cf54da2f84caf9a21fea7111e3" in joined
|
||||
assert "4a78d414e4b591f54232fab04529861a6042718a7627c4e17ccd5cc0cbd485f9" in joined
|
||||
assert "stat -c '%U:%G:%a'" in joined
|
||||
assert "ollama:ollama:444" in joined
|
||||
assert "ollama:ollama:755" in joined
|
||||
assert "dafeb467b744ee7e80dc043d51f9e5d7e6905a58103f1710d519b553b56c6e48" in joined
|
||||
assert "8f6b7e8a0d6746c1331e99910656e64deebf67c9582bab3e1d36f71bb7a253c0" in joined
|
||||
assert "docker-compose.override.yml" in joined
|
||||
assert "COMPOSE_FILE" not in joined
|
||||
assert all(digest in joined for digest in EXPECTED_PAYLOAD_SHA256)
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
"schema_version": "openclaw_callback_overlay_manifest_v1",
|
||||
"source_repository": "gitea:wooo/clawbot-v5",
|
||||
"source_commit": "ffc45311b6403b30cb6ae6e1aa83dae1a047e9c9",
|
||||
"deployment_mode": "root_owned_read_only_overlay_no_build",
|
||||
"deployment_mode": "host_user_owned_read_only_compose_override_no_build_no_pull",
|
||||
"runtime_source_mutation_allowed": false,
|
||||
"runtime_compose_override_write_allowed": true,
|
||||
"host_privilege_escalation_required": false,
|
||||
"runtime_secret_read_allowed": false,
|
||||
"payloads": [
|
||||
{
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
---
|
||||
# Host188 OpenClaw callback forwarder controlled convergence.
|
||||
#
|
||||
# This lane never mutates the runtime checkout. It installs three immutable,
|
||||
# repo-owned Python payloads into a root-owned directory and exposes them to the
|
||||
# existing clawbot container as read-only file mounts. The existing image,
|
||||
# service type, command, base Compose file, and runtime secrets remain untouched.
|
||||
# This lane never mutates the three runtime source payloads. It installs three
|
||||
# immutable, repo-owned Python payloads into an ollama-owned data directory and
|
||||
# exposes them to the existing clawbot container through Compose's conventional
|
||||
# docker-compose.override.yml discovery. The existing image, systemd unit,
|
||||
# base Compose file, and runtime secrets remain untouched; no sudo is required.
|
||||
|
||||
- name: Host188 OpenClaw callback forwarder controlled overlay
|
||||
hosts: host_188
|
||||
gather_facts: false
|
||||
become: true
|
||||
become: false
|
||||
serial: 1
|
||||
any_errors_fatal: true
|
||||
vars:
|
||||
catalog_id: ansible:188-openclaw-callback-forwarder
|
||||
canonical_asset_id: service:openclaw:host188
|
||||
expected_source_sha: ffc45311b6403b30cb6ae6e1aa83dae1a047e9c9
|
||||
expected_manifest_sha256: f06df6f3163e5ae7bf999624b011a3ae0bff096c9d0c8cb4c43f8b40be43ae2b
|
||||
expected_manifest_sha256: dafeb467b744ee7e80dc043d51f9e5d7e6905a58103f1710d519b553b56c6e48
|
||||
allowed_working_directories:
|
||||
- /opt/openclaw
|
||||
- /home/ollama/clawbot-v5
|
||||
compose_project: clawbot
|
||||
compose_service: clawbot
|
||||
payload_source_root: >-
|
||||
{{ playbook_dir }}/../files/openclaw-callback-forwarder/{{ expected_source_sha }}
|
||||
payload_root: >-
|
||||
/var/lib/awoooi-controlled-apply/openclaw-callback-forwarder/payloads/{{ expected_source_sha }}
|
||||
managed_root: /etc/awoooi/openclaw-callback-forwarder
|
||||
compose_override_path: >-
|
||||
/etc/awoooi/openclaw-callback-forwarder/docker-compose.callback.yml
|
||||
callback_dropin_dir: /etc/systemd/system/clawbot.service.d
|
||||
callback_dropin_path: >-
|
||||
/etc/systemd/system/clawbot.service.d/20-awoooi-callback-forwarder.conf
|
||||
receipt_root: >-
|
||||
/var/lib/awoooi-controlled-apply/openclaw-callback-forwarder/receipts
|
||||
rollback_root: >-
|
||||
/var/lib/awoooi-controlled-apply/openclaw-callback-forwarder/rollback
|
||||
managed_base: >-
|
||||
/home/ollama/.local/share/awoooi-controlled-apply/openclaw-callback-forwarder
|
||||
payload_root: "{{ managed_base }}/payloads/{{ expected_source_sha }}"
|
||||
compose_override_path: "{{ openclaw_workdir }}/docker-compose.override.yml"
|
||||
receipt_root: "{{ managed_base }}/receipts"
|
||||
rollback_root: "{{ managed_base }}/rollback"
|
||||
trace_id: "{{ controlled_trace_id | default('') }}"
|
||||
run_id: "{{ controlled_run_id | default('') }}"
|
||||
work_item_id: "{{ controlled_work_item_id | default('') }}"
|
||||
@@ -68,6 +62,8 @@
|
||||
baseline_sha256: ""
|
||||
target_sha256: 0d7693e34f63db4491d974f9f76a3a1c793d32cda193cb55ed3d8c86297b4fe3
|
||||
managed_directories:
|
||||
- path: "{{ managed_base }}"
|
||||
mode: "0755"
|
||||
- path: "{{ payload_root }}"
|
||||
mode: "0755"
|
||||
- path: "{{ payload_root }}/app"
|
||||
@@ -78,10 +74,6 @@
|
||||
mode: "0755"
|
||||
- path: "{{ payload_root }}/app/services"
|
||||
mode: "0755"
|
||||
- path: "{{ managed_root }}"
|
||||
mode: "0755"
|
||||
- path: "{{ callback_dropin_dir }}"
|
||||
mode: "0755"
|
||||
- path: "{{ receipt_root }}"
|
||||
mode: "0755"
|
||||
- path: "{{ rollback_root }}"
|
||||
@@ -410,9 +402,9 @@
|
||||
and not (managed_directories_before.results[directory_index].stat.islnk
|
||||
| default(false))
|
||||
and (managed_directories_before.results[directory_index].stat.pw_name
|
||||
| default('')) == 'root'
|
||||
| default('')) == 'ollama'
|
||||
and (managed_directories_before.results[directory_index].stat.gr_name
|
||||
| default('')) == 'root'
|
||||
| default('')) == 'ollama'
|
||||
and (managed_directories_before.results[directory_index].stat.mode
|
||||
| default('')) == item.mode
|
||||
)
|
||||
@@ -431,13 +423,12 @@
|
||||
get_checksum: true
|
||||
loop:
|
||||
- "{{ compose_override_path }}"
|
||||
- "{{ callback_dropin_path }}"
|
||||
- "{{ payload_root }}/manifest.json"
|
||||
register: managed_artifacts_before
|
||||
check_mode: false
|
||||
no_log: true
|
||||
|
||||
- name: Inspect existing root-owned overlay payloads
|
||||
- name: Inspect existing user-owned overlay payloads
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.overlay_path }}"
|
||||
checksum_algorithm: sha256
|
||||
@@ -449,7 +440,7 @@
|
||||
check_mode: false
|
||||
no_log: true
|
||||
|
||||
- name: Fail closed on foreign root-owned overlay payloads
|
||||
- name: Fail closed on foreign user-owned overlay payloads
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
@@ -465,9 +456,9 @@
|
||||
and (overlay_payload_before.results[payload_index].stat.mode
|
||||
| default('')) == '0444'
|
||||
and (overlay_payload_before.results[payload_index].stat.pw_name
|
||||
| default('')) == 'root'
|
||||
| default('')) == 'ollama'
|
||||
and (overlay_payload_before.results[payload_index].stat.gr_name
|
||||
| default('')) == 'root'
|
||||
| default('')) == 'ollama'
|
||||
)
|
||||
fail_msg: openclaw_overlay_payload_foreign_owner
|
||||
loop: "{{ payloads }}"
|
||||
@@ -503,9 +494,6 @@
|
||||
- "{{ payload_root }}/app/bot/telegram.py:/app/app/bot/telegram.py:ro"
|
||||
- "{{ payload_root }}/app/core/config.py:/app/app/core/config.py:ro"
|
||||
- "{{ payload_root }}/app/services/telegram_callback_forwarder.py:/app/app/services/telegram_callback_forwarder.py:ro"
|
||||
callback_dropin_content: |
|
||||
[Service]
|
||||
Environment="COMPOSE_FILE={{ openclaw_workdir }}/docker-compose.yml:{{ compose_override_path }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Fail closed on foreign pre-existing managed artifacts
|
||||
@@ -527,38 +515,20 @@
|
||||
and not (managed_artifacts_before.results[0].stat.islnk
|
||||
| default(false) | bool)
|
||||
and (managed_artifacts_before.results[0].stat.mode | default('')) == '0444'
|
||||
and (managed_artifacts_before.results[0].stat.pw_name | default('')) == 'root'
|
||||
and (managed_artifacts_before.results[0].stat.gr_name | default('')) == 'root'
|
||||
and (managed_artifacts_before.results[0].stat.pw_name | default('')) == 'ollama'
|
||||
and (managed_artifacts_before.results[0].stat.gr_name | default('')) == 'ollama'
|
||||
- >-
|
||||
not (managed_artifacts_before.results[1].stat.exists | default(false))
|
||||
or (
|
||||
managed_artifact_contents_before.results
|
||||
| selectattr('item.item', 'equalto', callback_dropin_path)
|
||||
| map(attribute='content')
|
||||
| map('b64decode')
|
||||
| list
|
||||
| first
|
||||
| default('')
|
||||
) == callback_dropin_content
|
||||
and (managed_artifacts_before.results[1].stat.isreg
|
||||
| default(false) | bool)
|
||||
and not (managed_artifacts_before.results[1].stat.islnk
|
||||
| default(false) | bool)
|
||||
and (managed_artifacts_before.results[1].stat.mode | default('')) == '0644'
|
||||
and (managed_artifacts_before.results[1].stat.pw_name | default('')) == 'root'
|
||||
and (managed_artifacts_before.results[1].stat.gr_name | default('')) == 'root'
|
||||
- >-
|
||||
not (managed_artifacts_before.results[2].stat.exists | default(false))
|
||||
or (
|
||||
(managed_artifacts_before.results[2].stat.checksum | default(''))
|
||||
(managed_artifacts_before.results[1].stat.checksum | default(''))
|
||||
== expected_manifest_sha256
|
||||
and (managed_artifacts_before.results[2].stat.isreg
|
||||
and (managed_artifacts_before.results[1].stat.isreg
|
||||
| default(false) | bool)
|
||||
and not (managed_artifacts_before.results[2].stat.islnk
|
||||
and not (managed_artifacts_before.results[1].stat.islnk
|
||||
| default(false) | bool)
|
||||
and (managed_artifacts_before.results[2].stat.mode | default('')) == '0444'
|
||||
and (managed_artifacts_before.results[2].stat.pw_name | default('')) == 'root'
|
||||
and (managed_artifacts_before.results[2].stat.gr_name | default('')) == 'root'
|
||||
and (managed_artifacts_before.results[1].stat.mode | default('')) == '0444'
|
||||
and (managed_artifacts_before.results[1].stat.pw_name | default('')) == 'ollama'
|
||||
and (managed_artifacts_before.results[1].stat.gr_name | default('')) == 'ollama'
|
||||
)
|
||||
fail_msg: openclaw_managed_overlay_foreign_owner
|
||||
changed_when: false
|
||||
@@ -664,8 +634,9 @@
|
||||
{{
|
||||
managed_artifacts_before.results[0].stat.exists | default(false)
|
||||
and managed_artifacts_before.results[1].stat.exists | default(false)
|
||||
and managed_artifacts_before.results[2].stat.exists | default(false)
|
||||
}}
|
||||
runtime_checkout_delta_expected: >-
|
||||
{{ 0 if (managed_artifacts_before.results[0].stat.exists | default(false)) else 1 }}
|
||||
changed_when: false
|
||||
|
||||
- name: Calculate one bounded no-build apply decision
|
||||
@@ -682,7 +653,7 @@
|
||||
- name: Publish check-mode overlay diff and no-write receipt
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
schema_version: openclaw_callback_overlay_check_v2
|
||||
schema_version: openclaw_callback_overlay_check_v3
|
||||
catalog_id: "{{ catalog_id }}"
|
||||
canonical_asset_id: "{{ canonical_asset_id }}"
|
||||
typed_domain: docker_compose_container
|
||||
@@ -693,6 +664,9 @@
|
||||
source_artifact_commit: "{{ expected_source_sha }}"
|
||||
runtime_source_dirty_count: "{{ runtime_dirty_count_before.stdout | int }}"
|
||||
runtime_source_mutation_allowed: false
|
||||
runtime_compose_override_write_allowed: true
|
||||
host_privilege_escalation_required: false
|
||||
expected_git_dirty_delta: "{{ runtime_checkout_delta_expected | int }}"
|
||||
image_before: "{{ clawbot_image_before }}"
|
||||
image_build_allowed: false
|
||||
overlay_payload_count: 3
|
||||
@@ -708,31 +682,28 @@
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Apply root-owned callback overlay with bounded rollback
|
||||
- name: Apply user-owned callback overlay with bounded rollback
|
||||
when: not ansible_check_mode
|
||||
block:
|
||||
- name: Create durable receipt and payload directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ payload_root }}/app/bot"
|
||||
- "{{ payload_root }}/app/core"
|
||||
- "{{ payload_root }}/app/services"
|
||||
- "{{ managed_root }}"
|
||||
- "{{ callback_dropin_dir }}"
|
||||
- "{{ receipt_root }}"
|
||||
- "{{ rollback_root }}/{{ run_id }}"
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "{{ item.mode }}"
|
||||
loop: >-
|
||||
{{ managed_directories
|
||||
+ [{'path': rollback_root ~ '/' ~ run_id, 'mode': '0755'}] }}
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
- name: Install exact read-only payload files
|
||||
ansible.builtin.copy:
|
||||
src: "{{ item.source_path }}"
|
||||
dest: "{{ item.overlay_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "0444"
|
||||
loop: "{{ payloads }}"
|
||||
loop_control:
|
||||
@@ -743,8 +714,8 @@
|
||||
ansible.builtin.copy:
|
||||
src: "{{ payload_source_root }}/manifest.json"
|
||||
dest: "{{ payload_root }}/manifest.json"
|
||||
owner: root
|
||||
group: root
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "0444"
|
||||
|
||||
- name: Verify installed overlay manifest checksum
|
||||
@@ -789,33 +760,34 @@
|
||||
index_var: payload_index
|
||||
changed_when: false
|
||||
|
||||
- name: Install root-owned non-secret Compose override
|
||||
- name: Install user-owned non-secret Compose override
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ compose_override_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "0444"
|
||||
content: "{{ compose_override_content }}"
|
||||
|
||||
- name: Install COMPOSE_FILE-only systemd drop-in
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ callback_dropin_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: "{{ callback_dropin_content }}"
|
||||
|
||||
- name: Mark the bounded service activation attempt
|
||||
ansible.builtin.set_fact:
|
||||
activation_attempted: true
|
||||
changed_when: false
|
||||
when: openclaw_apply_required | bool
|
||||
|
||||
- name: Reload systemd and restart the existing owned service once
|
||||
ansible.builtin.systemd:
|
||||
name: clawbot.service
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
- name: Activate the existing Compose project without build or pull
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/docker
|
||||
- compose
|
||||
- up
|
||||
- -d
|
||||
- --no-build
|
||||
- --pull
|
||||
- never
|
||||
args:
|
||||
chdir: "{{ openclaw_workdir }}"
|
||||
register: compose_activation
|
||||
changed_when: true
|
||||
when: openclaw_apply_required | bool
|
||||
no_log: true
|
||||
|
||||
@@ -1041,11 +1013,14 @@
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Require runtime checkout preservation
|
||||
- name: Require only the bounded managed override checkout delta
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- runtime_dirty_count_after.stdout | int == runtime_dirty_count_before.stdout | int
|
||||
fail_msg: openclaw_runtime_checkout_drift_changed
|
||||
- >-
|
||||
runtime_dirty_count_after.stdout | int
|
||||
== (runtime_dirty_count_before.stdout | int)
|
||||
+ (runtime_checkout_delta_expected | int)
|
||||
fail_msg: openclaw_runtime_checkout_delta_outside_managed_override
|
||||
changed_when: false
|
||||
|
||||
- name: Re-read only the three runtime target paths after apply
|
||||
@@ -1085,12 +1060,12 @@
|
||||
- name: Save durable technical apply receipt
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ receipt_root }}/{{ run_id }}.json"
|
||||
owner: root
|
||||
group: root
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "0600"
|
||||
content: |
|
||||
{
|
||||
"schema_version": "openclaw_callback_overlay_apply_v2",
|
||||
"schema_version": "openclaw_callback_overlay_apply_v3",
|
||||
"catalog_id": "{{ catalog_id }}",
|
||||
"canonical_asset_id": "{{ canonical_asset_id }}",
|
||||
"trace_id": "{{ trace_id }}",
|
||||
@@ -1102,7 +1077,9 @@
|
||||
"payload_count": 3,
|
||||
"runtime_source_dirty_count_before": {{ runtime_dirty_count_before.stdout | int }},
|
||||
"runtime_source_dirty_count_after": {{ runtime_dirty_count_after.stdout | int }},
|
||||
"runtime_source_mutated": false,
|
||||
"runtime_payload_source_mutated": false,
|
||||
"runtime_checkout_mutated_by_managed_override": true,
|
||||
"runtime_checkout_expected_delta": {{ runtime_checkout_delta_expected | int }},
|
||||
"image_rebuilt": false,
|
||||
"technical_apply_verified": true,
|
||||
"real_callback_receipt_required_for_incident_closure": true,
|
||||
@@ -1113,7 +1090,7 @@
|
||||
- name: Publish technical apply and pending real-callback receipt
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
schema_version: openclaw_callback_overlay_apply_v2
|
||||
schema_version: openclaw_callback_overlay_apply_v3
|
||||
catalog_id: "{{ catalog_id }}"
|
||||
canonical_asset_id: "{{ canonical_asset_id }}"
|
||||
typed_domain: docker_compose_container
|
||||
@@ -1124,7 +1101,8 @@
|
||||
source_artifact_commit: "{{ expected_source_sha }}"
|
||||
apply_performed: "{{ openclaw_apply_required }}"
|
||||
image_identity_unchanged: true
|
||||
runtime_checkout_preserved: true
|
||||
runtime_payload_sources_preserved: true
|
||||
managed_compose_override_persistent: true
|
||||
technical_apply_verified: true
|
||||
runtime_closure_verified: false
|
||||
next_state: partial_waiting_real_callback_receipt
|
||||
@@ -1133,30 +1111,6 @@
|
||||
changed_when: false
|
||||
|
||||
rescue:
|
||||
- name: Remove managed callback systemd drop-in after failed activation
|
||||
ansible.builtin.file:
|
||||
path: "{{ callback_dropin_path }}"
|
||||
state: absent
|
||||
when: not (managed_artifacts_before.results[1].stat.exists | default(false))
|
||||
|
||||
- name: Restore exact pre-existing managed systemd drop-in
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ callback_dropin_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
content: >-
|
||||
{{
|
||||
managed_artifact_contents_before.results
|
||||
| selectattr('item.item', 'equalto', callback_dropin_path)
|
||||
| map(attribute='content')
|
||||
| map('b64decode')
|
||||
| list
|
||||
| first
|
||||
}}
|
||||
when: managed_artifacts_before.results[1].stat.exists | default(false)
|
||||
no_log: true
|
||||
|
||||
- name: Remove managed Compose override after failed activation
|
||||
ansible.builtin.file:
|
||||
path: "{{ compose_override_path }}"
|
||||
@@ -1166,8 +1120,8 @@
|
||||
- name: Restore exact pre-existing managed Compose override
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ compose_override_path }}"
|
||||
owner: root
|
||||
group: root
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "0444"
|
||||
content: >-
|
||||
{{
|
||||
@@ -1198,13 +1152,21 @@
|
||||
path: "{{ payload_root }}/manifest.json"
|
||||
state: absent
|
||||
when: >-
|
||||
not (managed_artifacts_before.results[2].stat.exists | default(false))
|
||||
not (managed_artifacts_before.results[1].stat.exists | default(false))
|
||||
|
||||
- name: Reload systemd and restore the previous container definition
|
||||
ansible.builtin.systemd:
|
||||
name: clawbot.service
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
- name: Restore the previous Compose definition without build or pull
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/docker
|
||||
- compose
|
||||
- up
|
||||
- -d
|
||||
- --no-build
|
||||
- --pull
|
||||
- never
|
||||
args:
|
||||
chdir: "{{ openclaw_workdir }}"
|
||||
changed_when: true
|
||||
when: activation_attempted | default(false) | bool
|
||||
no_log: true
|
||||
|
||||
@@ -1348,12 +1310,12 @@
|
||||
- name: Save durable verified rollback receipt
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ rollback_root }}/{{ run_id }}/rollback-receipt.json"
|
||||
owner: root
|
||||
group: root
|
||||
owner: ollama
|
||||
group: ollama
|
||||
mode: "0600"
|
||||
content: |
|
||||
{
|
||||
"schema_version": "openclaw_callback_overlay_rollback_v2",
|
||||
"schema_version": "openclaw_callback_overlay_rollback_v3",
|
||||
"catalog_id": "{{ catalog_id }}",
|
||||
"canonical_asset_id": "{{ canonical_asset_id }}",
|
||||
"trace_id": "{{ trace_id }}",
|
||||
|
||||
Reference in New Issue
Block a user