Initial commit — OpenClaw VM infrastructure
- ansible/: VM provisioning playbooks and roles - provision-vm.yml: create KVM VM from Ubuntu cloud image - install.yml: install OpenClaw on guest (upstream) - customize.yml: swappiness, virtiofs fstab, linger - roles/vm/: libvirt domain XML, cloud-init templates - inventory.yml + host_vars/zap.yml: zap instance config - backup-openclaw-vm.sh: daily rsync + MinIO upload - restore-openclaw-vm.sh: full redeploy from scratch - README.md: full operational documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
---
|
||||
# Provision a KVM/libvirt VM from an Ubuntu cloud image.
|
||||
# Runs on the hypervisor host (localhost).
|
||||
|
||||
- name: Validate required variables
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- vm_domain | length > 0
|
||||
- vm_hostname | length > 0
|
||||
- vm_disk_path | length > 0
|
||||
- vm_mac | length > 0
|
||||
- vm_ip | length > 0
|
||||
fail_msg: "vm_domain, vm_hostname, vm_disk_path, vm_mac, and vm_ip must all be set in host_vars"
|
||||
|
||||
- name: Install host dependencies
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- qemu-img
|
||||
- genisoimage
|
||||
- libvirt-utils
|
||||
state: present
|
||||
|
||||
# ── Cloud image ────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Check if cloud image cache exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ vm_cloud_image_cache }}"
|
||||
register: cloud_image_stat
|
||||
|
||||
- name: Download Ubuntu cloud image
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ vm_cloud_image_url }}"
|
||||
dest: "{{ vm_cloud_image_cache }}"
|
||||
mode: "0644"
|
||||
timeout: 300
|
||||
when: not cloud_image_stat.stat.exists
|
||||
|
||||
# ── Disk image ─────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Check if VM disk already exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ vm_disk_path }}"
|
||||
register: vm_disk_stat
|
||||
|
||||
- name: Create VM disk from cloud image
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
qemu-img create -f qcow2 -F qcow2
|
||||
-b {{ vm_cloud_image_cache }}
|
||||
{{ vm_disk_path }} {{ vm_disk_size }}
|
||||
creates: "{{ vm_disk_path }}"
|
||||
when: not vm_disk_stat.stat.exists
|
||||
|
||||
# ── Cloud-init seed ISO ────────────────────────────────────────────────────
|
||||
|
||||
- name: Create cloud-init temp directory
|
||||
ansible.builtin.tempfile:
|
||||
state: directory
|
||||
suffix: cloud-init
|
||||
register: cloud_init_dir
|
||||
|
||||
- name: Write cloud-init user-data
|
||||
ansible.builtin.template:
|
||||
src: cloud-init-user-data.j2
|
||||
dest: "{{ cloud_init_dir.path }}/user-data"
|
||||
mode: "0644"
|
||||
vars:
|
||||
vm_ssh_keys: "{{ openclaw_ssh_keys | default([]) }}"
|
||||
|
||||
- name: Write cloud-init meta-data
|
||||
ansible.builtin.template:
|
||||
src: cloud-init-meta-data.j2
|
||||
dest: "{{ cloud_init_dir.path }}/meta-data"
|
||||
mode: "0644"
|
||||
|
||||
- name: Set seed ISO path fact
|
||||
ansible.builtin.set_fact:
|
||||
vm_seed_iso: "/var/lib/libvirt/images/{{ vm_hostname }}-seed.iso"
|
||||
|
||||
- name: Create cloud-init seed ISO
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
genisoimage -output {{ vm_seed_iso }}
|
||||
-volid cidata -joliet -rock
|
||||
{{ cloud_init_dir.path }}/user-data
|
||||
{{ cloud_init_dir.path }}/meta-data
|
||||
changed_when: true
|
||||
|
||||
- name: Clean up cloud-init temp directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ cloud_init_dir.path }}"
|
||||
state: absent
|
||||
|
||||
# ── VM definition ──────────────────────────────────────────────────────────
|
||||
|
||||
- name: Check if VM domain already exists
|
||||
community.libvirt.virt:
|
||||
command: list_vms
|
||||
uri: "{{ vm_libvirt_uri }}"
|
||||
register: existing_vms
|
||||
|
||||
- name: Define VM from XML template
|
||||
community.libvirt.virt:
|
||||
command: define
|
||||
xml: "{{ lookup('template', 'domain.xml.j2') }}"
|
||||
uri: "{{ vm_libvirt_uri }}"
|
||||
when: vm_domain not in existing_vms.list_vms
|
||||
|
||||
# ── Network ────────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Add static DHCP reservation
|
||||
ansible.builtin.command:
|
||||
cmd: >
|
||||
virsh -c {{ vm_libvirt_uri }} net-update {{ vm_network }}
|
||||
add ip-dhcp-host
|
||||
'<host mac="{{ vm_mac }}" name="{{ vm_hostname }}" ip="{{ vm_ip }}"/>'
|
||||
--live --config
|
||||
register: dhcp_result
|
||||
failed_when:
|
||||
- dhcp_result.rc != 0
|
||||
- "'already exists' not in dhcp_result.stderr"
|
||||
changed_when: dhcp_result.rc == 0
|
||||
|
||||
# ── Autostart & boot ───────────────────────────────────────────────────────
|
||||
|
||||
- name: Enable autostart
|
||||
community.libvirt.virt:
|
||||
name: "{{ vm_domain }}"
|
||||
autostart: true
|
||||
uri: "{{ vm_libvirt_uri }}"
|
||||
|
||||
- name: Start VM
|
||||
community.libvirt.virt:
|
||||
name: "{{ vm_domain }}"
|
||||
state: running
|
||||
uri: "{{ vm_libvirt_uri }}"
|
||||
|
||||
- name: Wait for SSH to become available
|
||||
ansible.builtin.wait_for:
|
||||
host: "{{ vm_ip }}"
|
||||
port: 22
|
||||
delay: 10
|
||||
timeout: 180
|
||||
state: started
|
||||
delegate_to: localhost
|
||||
|
||||
- name: VM is ready
|
||||
ansible.builtin.debug:
|
||||
msg: "VM '{{ vm_domain }}' is up at {{ vm_ip }}. Run install.yml + customize.yml to provision the guest."
|
||||
Reference in New Issue
Block a user