fix(sre): remove Host188 callback sudo dependency

This commit is contained in:
ogt
2026-07-17 10:49:11 +08:00
parent a789c5c99b
commit 74d338676e
6 changed files with 181 additions and 192 deletions

View File

@@ -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,
},
{

View File

@@ -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 "

View File

@@ -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

View File

@@ -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)

View File

@@ -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": [
{

View File

@@ -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 }}",