468 lines
16 KiB
YAML
468 lines
16 KiB
YAML
---
|
|
- name: Inspect existing OpenClaw gateway JSON config
|
|
ansible.builtin.stat:
|
|
path: "{{ gateway_openclaw_config_path }}"
|
|
register: gateway_openclaw_existing_config_stat
|
|
no_log: true
|
|
|
|
- name: Read existing OpenClaw gateway JSON config
|
|
ansible.builtin.slurp:
|
|
path: "{{ gateway_openclaw_config_path }}"
|
|
register: gateway_openclaw_existing_config_raw
|
|
when: gateway_openclaw_existing_config_stat.stat.exists | default(false)
|
|
no_log: true
|
|
|
|
- name: Resolve OpenClaw gateway secrets
|
|
ansible.builtin.set_fact:
|
|
gateway_openclaw_effective_gateway_token: >-
|
|
{{
|
|
gateway_openclaw_gateway_token
|
|
if (gateway_openclaw_gateway_token | trim | length > 0)
|
|
else (
|
|
(
|
|
gateway_openclaw_existing_config_raw.content | default('') | b64decode | from_json
|
|
).gateway.auth.token | default('')
|
|
)
|
|
}}
|
|
no_log: true
|
|
|
|
- name: Assert OpenClaw gateway secrets are provided
|
|
ansible.builtin.assert:
|
|
that:
|
|
- gateway_openclaw_effective_gateway_token | trim | length > 0
|
|
fail_msg: >-
|
|
gateway_openclaw_gateway_token is required. Pass it with -e or keep an
|
|
existing {{ gateway_openclaw_config_path }} with gateway.auth.token; do
|
|
not commit it into inventory/defaults.
|
|
no_log: true
|
|
|
|
- name: Ensure OpenClaw gateway config directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_config_path | dirname }}"
|
|
state: directory
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "0700"
|
|
|
|
- name: Ensure OpenClaw workspace directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_workspace_path }}"
|
|
state: directory
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "{{ gateway_openclaw_workspace_mode }}"
|
|
|
|
- name: Ensure OpenClaw compile cache directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_compile_cache_dir }}"
|
|
state: directory
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "0755"
|
|
|
|
- name: Ensure OpenClaw extension backup directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_extension_backup_dir }}"
|
|
state: directory
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "0700"
|
|
|
|
- name: Install required OpenClaw gateway package version
|
|
ansible.builtin.command:
|
|
cmd: >-
|
|
npm install --global --omit=dev --no-audit --no-fund
|
|
--prefix "{{ gateway_openclaw_home }}/.local"
|
|
"{{ gateway_openclaw_npm_package_spec }}"
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
PATH: "{{ gateway_openclaw_service_path }}"
|
|
OPENCLAW_NO_RESPAWN: "1"
|
|
NODE_COMPILE_CACHE: "{{ gateway_openclaw_compile_cache_dir }}"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
register: gateway_openclaw_package_install
|
|
changed_when: >-
|
|
'added ' in (gateway_openclaw_package_install.stdout | default('')) or
|
|
'removed ' in (gateway_openclaw_package_install.stdout | default('')) or
|
|
'changed ' in (gateway_openclaw_package_install.stdout | default(''))
|
|
when:
|
|
- not ansible_check_mode
|
|
notify: Restart openclaw gateway
|
|
|
|
- name: Move stale OpenClaw plugin backups out of extension scan path
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
moved=0
|
|
for backup_path in "{{ gateway_openclaw_home }}/.openclaw/extensions"/openclaw-multi-session-plugins.backup.*; do
|
|
backup_name="$(basename "$backup_path")"
|
|
backup_target="{{ gateway_openclaw_extension_backup_dir }}/${backup_name}"
|
|
if [ -e "$backup_target" ]; then
|
|
rm -rf "$backup_path"
|
|
else
|
|
mv "$backup_path" "$backup_target"
|
|
fi
|
|
moved=1
|
|
done
|
|
chown -R "{{ gateway_openclaw_service_user }}:{{ gateway_openclaw_service_group }}" "{{ gateway_openclaw_extension_backup_dir }}"
|
|
echo "moved=${moved}"
|
|
args:
|
|
executable: /bin/bash
|
|
register: gateway_openclaw_stale_extension_backups
|
|
changed_when: "'moved=1' in (gateway_openclaw_stale_extension_backups.stdout | default(''))"
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Inspect OpenClaw gateway JSON config attributes
|
|
ansible.builtin.command:
|
|
cmd: lsattr "{{ gateway_openclaw_config_path }}"
|
|
register: gateway_openclaw_config_attrs
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Remove immutable flag from OpenClaw gateway JSON config when present
|
|
ansible.builtin.command:
|
|
cmd: chattr -i "{{ gateway_openclaw_config_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_config_attrs.stdout | default(''))"
|
|
changed_when: true
|
|
|
|
- name: Deploy OpenClaw gateway JSON config
|
|
ansible.builtin.template:
|
|
src: openclaw.json.j2
|
|
dest: "{{ gateway_openclaw_config_path }}"
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "0600"
|
|
diff: false
|
|
notify: Restart openclaw gateway
|
|
|
|
- name: Inspect OpenClaw package manifest
|
|
ansible.builtin.stat:
|
|
path: "{{ gateway_openclaw_install_dir }}/package.json"
|
|
register: gateway_openclaw_package_manifest
|
|
|
|
- name: Ensure OpenClaw package install directory is owned by service user
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_install_dir }}"
|
|
state: directory
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
recurse: true
|
|
when:
|
|
- gateway_openclaw_package_manifest.stat.exists | default(false)
|
|
|
|
- name: Repair OpenClaw package runtime dependencies as service user
|
|
ansible.builtin.command:
|
|
cmd: npm install --omit=dev --no-audit --no-fund --prefix "{{ gateway_openclaw_install_dir }}"
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
PATH: "{{ gateway_openclaw_service_path }}"
|
|
OPENCLAW_NO_RESPAWN: "1"
|
|
NODE_COMPILE_CACHE: "{{ gateway_openclaw_compile_cache_dir }}"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
register: gateway_openclaw_package_deps
|
|
changed_when: >-
|
|
'added ' in (gateway_openclaw_package_deps.stdout | default('')) or
|
|
'removed ' in (gateway_openclaw_package_deps.stdout | default('')) or
|
|
'changed ' in (gateway_openclaw_package_deps.stdout | default(''))
|
|
when:
|
|
- gateway_openclaw_package_manifest.stat.exists | default(false)
|
|
- not ansible_check_mode
|
|
|
|
- name: Inspect OpenClaw extension package manifests
|
|
ansible.builtin.stat:
|
|
path: "{{ item }}/package.json"
|
|
register: gateway_openclaw_extension_manifests
|
|
loop: "{{ gateway_openclaw_extension_dependency_dirs }}"
|
|
|
|
- name: Repair OpenClaw extension runtime dependencies as service user
|
|
ansible.builtin.command:
|
|
cmd: npm install --omit=dev --no-audit --no-fund --prefix "{{ item.item }}"
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
PATH: "{{ gateway_openclaw_service_path }}"
|
|
OPENCLAW_NO_RESPAWN: "1"
|
|
NODE_COMPILE_CACHE: "{{ gateway_openclaw_compile_cache_dir }}"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
register: gateway_openclaw_extension_deps
|
|
changed_when: >-
|
|
'added ' in (gateway_openclaw_extension_deps.stdout | default('')) or
|
|
'removed ' in (gateway_openclaw_extension_deps.stdout | default('')) or
|
|
'changed ' in (gateway_openclaw_extension_deps.stdout | default(''))
|
|
loop: "{{ gateway_openclaw_extension_manifests.results }}"
|
|
when:
|
|
- item.stat.exists | default(false)
|
|
- not ansible_check_mode
|
|
|
|
- name: Repair OpenClaw 2026.5 route state as service user
|
|
ansible.builtin.command:
|
|
cmd: "{{ gateway_openclaw_binary_path }} doctor --fix --non-interactive"
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
PATH: "{{ gateway_openclaw_service_path }}"
|
|
OPENCLAW_NO_RESPAWN: "1"
|
|
NODE_COMPILE_CACHE: "{{ gateway_openclaw_compile_cache_dir }}"
|
|
OPENCLAW_SERVICE_REPAIR_POLICY: external
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
register: gateway_openclaw_doctor_repair
|
|
changed_when: >-
|
|
((gateway_openclaw_doctor_repair.stdout | default('')) ~ '\n' ~
|
|
(gateway_openclaw_doctor_repair.stderr | default('')))
|
|
is search('(?i)(fixed|repaired|migrated|rewrote|removed|set |moved |updated|archived)')
|
|
when:
|
|
- gateway_openclaw_doctor_repair_enabled | bool
|
|
- not ansible_check_mode
|
|
notify: Restart openclaw gateway
|
|
|
|
- name: Restore immutable flag on OpenClaw gateway JSON config
|
|
ansible.builtin.command:
|
|
cmd: chattr +i "{{ gateway_openclaw_config_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_config_attrs.stdout | default(''))"
|
|
- not ansible_check_mode
|
|
changed_when: true
|
|
|
|
- name: Inspect OpenClaw gateway binary
|
|
ansible.builtin.stat:
|
|
path: "{{ gateway_openclaw_binary_path }}"
|
|
follow: true
|
|
register: gateway_openclaw_binary
|
|
|
|
- name: Fail when OpenClaw gateway binary is missing or not executable
|
|
ansible.builtin.assert:
|
|
that:
|
|
- gateway_openclaw_binary.stat.exists | default(false)
|
|
- gateway_openclaw_binary.stat.executable | default(false)
|
|
fail_msg: "OpenClaw gateway binary is missing or not executable: {{ gateway_openclaw_binary_path }}"
|
|
|
|
- name: Check OpenClaw gateway version
|
|
ansible.builtin.command:
|
|
cmd: "{{ gateway_openclaw_binary_path }} --version"
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
PATH: "{{ gateway_openclaw_service_path }}"
|
|
OPENCLAW_NO_RESPAWN: "1"
|
|
NODE_COMPILE_CACHE: "{{ gateway_openclaw_compile_cache_dir }}"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
register: gateway_openclaw_version
|
|
changed_when: false
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Assert OpenClaw gateway version is pinned
|
|
ansible.builtin.assert:
|
|
that:
|
|
- gateway_openclaw_version.stdout is search('OpenClaw ' ~ gateway_openclaw_required_version)
|
|
fail_msg: >-
|
|
OpenClaw gateway must run {{ gateway_openclaw_required_version }} after the
|
|
package upgrade. Actual version output: {{ gateway_openclaw_version.stdout | default('') }}
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Ensure OpenClaw user systemd unit directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_user_service_unit_path | dirname }}"
|
|
state: directory
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "0755"
|
|
|
|
- name: Deploy OpenClaw user systemd unit
|
|
ansible.builtin.template:
|
|
src: openclaw-gateway.user.service.j2
|
|
dest: "{{ gateway_openclaw_user_service_unit_path }}"
|
|
owner: "{{ gateway_openclaw_service_user }}"
|
|
group: "{{ gateway_openclaw_service_group }}"
|
|
mode: "0644"
|
|
register: gateway_openclaw_user_service_unit
|
|
|
|
- name: Deploy OpenClaw user systemd shell environment
|
|
ansible.builtin.template:
|
|
src: openclaw-user-systemd.sh.j2
|
|
dest: "{{ gateway_openclaw_profile_script_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
|
|
- name: Enable OpenClaw service user linger
|
|
ansible.builtin.command:
|
|
cmd: "loginctl enable-linger {{ gateway_openclaw_service_user }}"
|
|
creates: "/var/lib/systemd/linger/{{ gateway_openclaw_service_user }}"
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Ensure OpenClaw service user manager is running
|
|
ansible.builtin.systemd:
|
|
name: "user@{{ gateway_openclaw_service_uid }}.service"
|
|
state: started
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Stop and disable stale root-managed OpenClaw gateway service
|
|
ansible.builtin.systemd:
|
|
name: "{{ gateway_openclaw_service_name }}"
|
|
enabled: false
|
|
state: stopped
|
|
failed_when: false
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Inspect stale OpenClaw gateway root systemd unit attributes
|
|
ansible.builtin.command:
|
|
cmd: lsattr "{{ gateway_openclaw_service_unit_path }}"
|
|
register: gateway_openclaw_unit_attrs
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Remove immutable flag from stale OpenClaw gateway root systemd unit when present
|
|
ansible.builtin.command:
|
|
cmd: chattr -i "{{ gateway_openclaw_service_unit_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_unit_attrs.stdout | default(''))"
|
|
changed_when: true
|
|
|
|
- name: Remove stale root-managed OpenClaw gateway systemd unit
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_service_unit_path }}"
|
|
state: absent
|
|
register: gateway_openclaw_removed_root_service_unit
|
|
|
|
- name: Reload root systemd after removing stale OpenClaw gateway unit
|
|
ansible.builtin.systemd:
|
|
daemon_reload: true
|
|
when:
|
|
- gateway_openclaw_removed_root_service_unit.changed | default(false)
|
|
- not ansible_check_mode
|
|
|
|
- name: Reload OpenClaw user systemd manager
|
|
ansible.builtin.command:
|
|
cmd: systemctl --user daemon-reload
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gateway_openclaw_service_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gateway_openclaw_service_uid }}/bus"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
changed_when: false
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Ensure OpenClaw user gateway service is enabled and running
|
|
ansible.builtin.command:
|
|
cmd: >-
|
|
systemctl --user enable
|
|
{{ '--now' if not (gateway_openclaw_user_service_unit.changed | default(false)) else '' }}
|
|
{{ gateway_openclaw_service_name }}.service
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gateway_openclaw_service_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gateway_openclaw_service_uid }}/bus"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
register: gateway_openclaw_user_service_enable
|
|
changed_when: >-
|
|
'Created symlink' in (gateway_openclaw_user_service_enable.stdout | default('')) or
|
|
'Created symlink' in (gateway_openclaw_user_service_enable.stderr | default(''))
|
|
when:
|
|
- not ansible_check_mode
|
|
|
|
- name: Restart OpenClaw user gateway service after unit changes
|
|
ansible.builtin.command:
|
|
cmd: "systemctl --user restart {{ gateway_openclaw_service_name }}.service"
|
|
environment:
|
|
HOME: "{{ gateway_openclaw_home }}"
|
|
XDG_RUNTIME_DIR: "/run/user/{{ gateway_openclaw_service_uid }}"
|
|
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ gateway_openclaw_service_uid }}/bus"
|
|
become: true
|
|
become_user: "{{ gateway_openclaw_service_user }}"
|
|
when:
|
|
- gateway_openclaw_user_service_unit.changed | default(false)
|
|
- not ansible_check_mode
|
|
|
|
- name: Ensure Caddy fragment directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ gateway_openclaw_caddy_conf_dir }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Inspect Caddy main file attributes
|
|
ansible.builtin.command:
|
|
cmd: lsattr "{{ gateway_openclaw_caddyfile_path }}"
|
|
register: gateway_openclaw_caddyfile_attrs
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Remove immutable flag from Caddy main file when present
|
|
ansible.builtin.command:
|
|
cmd: chattr -i "{{ gateway_openclaw_caddyfile_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_caddyfile_attrs.stdout | default(''))"
|
|
changed_when: true
|
|
|
|
- name: Ensure Caddy imports managed fragments
|
|
ansible.builtin.lineinfile:
|
|
path: "{{ gateway_openclaw_caddyfile_path }}"
|
|
line: "import {{ gateway_openclaw_caddy_conf_dir }}/*.caddy"
|
|
insertafter: EOF
|
|
create: true
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
state: present
|
|
notify: Reload caddy
|
|
|
|
- name: Restore immutable flag on Caddy main file
|
|
ansible.builtin.command:
|
|
cmd: chattr +i "{{ gateway_openclaw_caddyfile_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_caddyfile_attrs.stdout | default(''))"
|
|
- not ansible_check_mode
|
|
changed_when: true
|
|
|
|
- name: Inspect OpenClaw Caddy fragment attributes
|
|
ansible.builtin.command:
|
|
cmd: lsattr "{{ gateway_openclaw_caddy_fragment_path }}"
|
|
register: gateway_openclaw_caddy_fragment_attrs
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: Remove immutable flag from OpenClaw Caddy fragment when present
|
|
ansible.builtin.command:
|
|
cmd: chattr -i "{{ gateway_openclaw_caddy_fragment_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_caddy_fragment_attrs.stdout | default(''))"
|
|
changed_when: true
|
|
|
|
- name: Deploy OpenClaw public Caddy site
|
|
ansible.builtin.template:
|
|
src: openclaw.svc.plus.caddy.j2
|
|
dest: "{{ gateway_openclaw_caddy_fragment_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Reload caddy
|
|
|
|
- name: Restore immutable flag on OpenClaw Caddy fragment
|
|
ansible.builtin.command:
|
|
cmd: chattr +i "{{ gateway_openclaw_caddy_fragment_path }}"
|
|
when:
|
|
- "'i' in (gateway_openclaw_caddy_fragment_attrs.stdout | default(''))"
|
|
- not ansible_check_mode
|
|
changed_when: true
|
|
|
|
- name: Ensure Caddy is enabled and running
|
|
ansible.builtin.systemd:
|
|
name: caddy
|
|
enabled: true
|
|
state: started
|
|
when:
|
|
- not ansible_check_mode
|