Files
toallab-automation/playbooks/deploy_openclaw.yml
Patrick Toal df1dd39197 docs: update claude setup
refactor: Move some things to roles
refactor: fix some linting
2026-04-12 14:02:12 -04:00

248 lines
9.2 KiB
YAML

---
# Deploy OpenClaw AI Gateway on a Proxmox VM
#
# OpenClaw: https://docs.openclaw.ai
# Ansible install docs: https://docs.openclaw.ai/install/ansible
# Signal channel docs: https://docs.openclaw.ai/channels/signal
#
# Prerequisites:
# Inventory host: openclaw.toal.ca (in group 'openclaw')
# host_vars required:
# openclaw_vm_ssh_public_key — SSH public key injected via cloud-init
# openclaw_vm_ip — static IP or 'dhcp'
# openclaw_vm_gateway — required for static IP
# openclaw_vm_vnet — Proxmox SDN VNet (e.g. lan)
#
# Vault secrets (1Password):
# vault_proxmox_token_secret — Proxmox API token
# vault_openclaw_api_key — Model provider API key (Anthropic, OpenAI, etc.)
# vault_openclaw_signal_phone — Signal account phone number (E.164, if Signal enabled)
#
# Security architecture:
# - OPNsense firewall provides perimeter security
# - UFW on VM: allow SSH (22) + gateway (18789); deny everything else inbound
# - Docker CE for agent sandbox isolation
# - Systemd hardening: NoNewPrivileges, PrivateTmp, ProtectSystem
#
# Signal channel MANUAL STEP required after deploy:
# sudo -i -u openclaw
# signal-cli link -n "OpenClaw" # scan QR with Signal app
# openclaw pairing approve signal
#
# Play order:
# Play 1: openclaw_create_vm — Create Ubuntu VM in Proxmox (cloud-init)
# Play 2: openclaw_wait — Wait for SSH to become available
# Play 3: openclaw_install — Install OpenClaw, security stack, Signal channel
#
# Usage:
# ansible-navigator run playbooks/deploy_openclaw.yml
# ansible-navigator run playbooks/deploy_openclaw.yml --tags openclaw_create_vm
# ansible-navigator run playbooks/deploy_openclaw.yml --tags openclaw_install
# ansible-navigator run playbooks/deploy_openclaw.yml --tags openclaw_install,openclaw_signal
# ---------------------------------------------------------------------------
# Play 1: Create Ubuntu VM in Proxmox using cloud-init
# ---------------------------------------------------------------------------
- name: Create OpenClaw VM in Proxmox
hosts: openclaw.toal.ca
gather_facts: false
connection: local
tags: openclaw_create_vm
vars:
# Proxmox connection — override in host_vars if needed
proxmox_node: pve1
proxmox_api_user: ansible@pam
proxmox_api_token_id: ansible
proxmox_api_token_secret: "{{ vault_proxmox_token_secret }}"
proxmox_validate_certs: false
proxmox_storage: local-lvm
proxmox_iso_dir: /var/lib/vz/template/iso
# VM spec — override in host_vars for the openclaw inventory host
openclaw_vm_name: openclaw
openclaw_vm_id: 0
openclaw_vm_cpu: 2
openclaw_vm_memory_mb: 4096
openclaw_vm_disk_gb: 40
openclaw_vm_vnet: lan
openclaw_vm_user: ubuntu
openclaw_vm_ssh_public_key: "" # required — set in host_vars
openclaw_vm_ip: dhcp # set to x.x.x.x for static
openclaw_vm_prefix: 24
openclaw_vm_gateway: ""
openclaw_vm_nameserver: ""
openclaw_vm_cloud_image_url: "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
openclaw_vm_cloud_image_filename: noble-server-cloudimg-amd64.img
# Computed
__openclaw_proxmox_api_host: "{{ hostvars['proxmox_api']['ansible_host'] }}"
__openclaw_proxmox_api_port: "{{ hostvars['proxmox_api']['ansible_port'] }}"
tasks:
- name: Download Ubuntu 24.04 cloud image to Proxmox host
ansible.builtin.get_url:
url: "{{ openclaw_vm_cloud_image_url }}"
dest: "{{ proxmox_iso_dir }}/{{ openclaw_vm_cloud_image_filename }}"
mode: "0644"
delegate_to: proxmox_host
- name: Create VM definition
community.proxmox.proxmox_kvm:
api_host: "{{ __openclaw_proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_port: "{{ __openclaw_proxmox_api_port }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_validate_certs }}"
node: "{{ proxmox_node }}"
vmid: "{{ openclaw_vm_id | default(omit, true) }}"
name: "{{ openclaw_vm_name }}"
cores: "{{ openclaw_vm_cpu }}"
memory: "{{ openclaw_vm_memory_mb }}"
cpu: host
machine: q35
bios: ovmf
efidisk0:
storage: "{{ proxmox_storage }}"
format: raw
efitype: 4m
pre_enrolled_keys: false
scsihw: virtio-scsi-single
net:
net0: "virtio,bridge={{ openclaw_vm_vnet }}"
boot: "order=scsi0"
onboot: true
state: present
- name: Retrieve VM info
community.proxmox.proxmox_vm_info:
api_host: "{{ __openclaw_proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_port: "{{ __openclaw_proxmox_api_port }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_validate_certs }}"
node: "{{ proxmox_node }}"
name: "{{ openclaw_vm_name }}"
type: qemu
config: current
register: __openclaw_vm_info
retries: 5
- name: Set VM ID fact
ansible.builtin.set_fact:
openclaw_vm_id: "{{ __openclaw_vm_info.proxmox_vms[0].vmid }}"
cacheable: true
- name: Check if disk is already imported (scsi0 present in config)
ansible.builtin.set_fact:
__openclaw_disk_imported: "{{ __openclaw_vm_info.proxmox_vms[0].config.scsi0 is defined }}"
- name: Import cloud image as primary disk
ansible.builtin.command:
cmd: >-
qm importdisk {{ openclaw_vm_id }}
{{ proxmox_iso_dir }}/{{ openclaw_vm_cloud_image_filename }}
{{ proxmox_storage }} --format raw
delegate_to: proxmox_host
changed_when: true
when: not __openclaw_disk_imported | bool
- name: Attach imported disk as scsi0
ansible.builtin.command:
cmd: "qm set {{ openclaw_vm_id }} --scsi0 {{ proxmox_storage }}:vm-{{ openclaw_vm_id }}-disk-0,iothread=1,cache=writeback"
delegate_to: proxmox_host
changed_when: true
when: not __openclaw_disk_imported | bool
- name: Resize disk to configured size
ansible.builtin.command:
cmd: "qm disk resize {{ openclaw_vm_id }} scsi0 {{ openclaw_vm_disk_gb }}G"
delegate_to: proxmox_host
changed_when: true
when: not __openclaw_disk_imported | bool
- name: Add cloud-init drive
ansible.builtin.command:
cmd: "qm set {{ openclaw_vm_id }} --ide2 {{ proxmox_storage }}:cloudinit"
delegate_to: proxmox_host
changed_when: true
when: not __openclaw_disk_imported | bool
- name: Write SSH public key to temp file on Proxmox host
ansible.builtin.copy:
content: "{{ openclaw_vm_ssh_public_key }}"
dest: "/tmp/openclaw-sshkey-{{ openclaw_vm_id }}.pub"
mode: "0600"
delegate_to: proxmox_host
no_log: false
- name: Configure cloud-init user and SSH key
ansible.builtin.command:
cmd: >-
qm set {{ openclaw_vm_id }}
--ciuser {{ openclaw_vm_user }}
--sshkeys /tmp/openclaw-sshkey-{{ openclaw_vm_id }}.pub
delegate_to: proxmox_host
changed_when: true
- name: Configure cloud-init network (static)
ansible.builtin.command:
cmd: >-
qm set {{ openclaw_vm_id }}
--ipconfig0 ip={{ openclaw_vm_ip }}/{{ openclaw_vm_prefix }},gw={{ openclaw_vm_gateway }}
--nameserver {{ openclaw_vm_nameserver }}
delegate_to: proxmox_host
changed_when: true
when: openclaw_vm_ip != 'dhcp'
- name: Configure cloud-init network (DHCP)
ansible.builtin.command:
cmd: "qm set {{ openclaw_vm_id }} --ipconfig0 ip=dhcp"
delegate_to: proxmox_host
changed_when: true
when: openclaw_vm_ip == 'dhcp'
- name: Start VM
community.proxmox.proxmox_kvm:
api_host: "{{ __openclaw_proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_port: "{{ __openclaw_proxmox_api_port }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_validate_certs }}"
node: "{{ proxmox_node }}"
name: "{{ openclaw_vm_name }}"
state: started
- name: Remove temporary SSH key file
ansible.builtin.file:
path: "/tmp/openclaw-sshkey-{{ openclaw_vm_id }}.pub"
state: absent
delegate_to: proxmox_host
# ---------------------------------------------------------------------------
# Play 2: Wait for VM to become reachable
# ---------------------------------------------------------------------------
- name: Wait for OpenClaw VM SSH
hosts: openclaw.toal.ca
gather_facts: false
tags: openclaw_create_vm
tasks:
- name: Wait for SSH port
ansible.builtin.wait_for_connection:
timeout: 300
sleep: 10
# ---------------------------------------------------------------------------
# Play 3: Install OpenClaw, security stack, and Signal channel
# ---------------------------------------------------------------------------
- name: Install and configure OpenClaw
hosts: openclaw.toal.ca
gather_facts: true
become: true
tags: openclaw_install
roles:
- role: openclaw