- 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>
193 lines
6.9 KiB
YAML
193 lines
6.9 KiB
YAML
---
|
|
- name: Create openclaw system user
|
|
ansible.builtin.user:
|
|
name: "{{ openclaw_user }}"
|
|
comment: "OpenClaw Service User"
|
|
system: true
|
|
shell: /bin/bash
|
|
create_home: true
|
|
home: "{{ openclaw_home }}"
|
|
state: present
|
|
|
|
- name: Ensure openclaw home directory has correct ownership
|
|
ansible.builtin.file:
|
|
path: "{{ openclaw_home }}"
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: Configure .bashrc for openclaw user
|
|
ansible.builtin.blockinfile:
|
|
path: "{{ openclaw_home }}/.bashrc"
|
|
marker: "# {mark} ANSIBLE MANAGED BLOCK - OpenClaw config"
|
|
block: |
|
|
# Enable 256 colors
|
|
export TERM=xterm-256color
|
|
export COLORTERM=truecolor
|
|
|
|
# Add pnpm to PATH
|
|
export PNPM_HOME="{{ openclaw_home }}/.local/share/pnpm"
|
|
export PATH="{{ openclaw_home }}/.local/bin:$PNPM_HOME:$PATH"
|
|
|
|
# Color support for common tools
|
|
export CLICOLOR=1
|
|
export LS_COLORS='di=34:ln=35:so=32:pi=33:ex=31:bd=34;46:cd=34;43:su=30;41:sg=30;46:tw=30;42:ow=30;43'
|
|
|
|
# Aliases
|
|
alias ls='ls --color=auto'
|
|
alias grep='grep --color=auto'
|
|
alias ll='ls -lah'
|
|
create: true
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
mode: '0644'
|
|
|
|
- name: Add openclaw user to sudoers with scoped NOPASSWD
|
|
ansible.builtin.copy:
|
|
dest: "/etc/sudoers.d/{{ openclaw_user }}"
|
|
mode: '0440'
|
|
owner: root
|
|
group: root
|
|
content: |
|
|
# OpenClaw sudo permissions (scoped for security)
|
|
#
|
|
# SECURITY NOTE: These permissions are intentionally limited.
|
|
# If openclaw is compromised, attackers can only:
|
|
# - Manage the openclaw service
|
|
# - Run basic tailscale diagnostics
|
|
# - View openclaw logs
|
|
#
|
|
# To grant full tailscale control (e.g., for self-healing VPN):
|
|
# {{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale *
|
|
#
|
|
# To grant full sudo (NOT RECOMMENDED):
|
|
# {{ openclaw_user }} ALL=(ALL) NOPASSWD: ALL
|
|
|
|
# Service control - openclaw service only
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl start openclaw
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop openclaw
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart openclaw
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl status openclaw
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable openclaw
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable openclaw
|
|
# daemon-reload affects all units (required after service file changes)
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload
|
|
|
|
# Tailscale - diagnostics + connect/disconnect
|
|
# NOTE: 'up' allows flags like --advertise-exit-node. For tighter control,
|
|
# remove 'up' and 'down' lines - operator must then manage VPN manually.
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale status
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale up *
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale down
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale ip *
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale version
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale ping *
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/tailscale whois *
|
|
|
|
# Journal access - openclaw logs only
|
|
{{ openclaw_user }} ALL=(ALL) NOPASSWD: /usr/bin/journalctl -u openclaw *
|
|
validate: /usr/sbin/visudo -cf %s
|
|
|
|
- name: Set openclaw user as primary user for installation
|
|
ansible.builtin.set_fact:
|
|
openclaw_user: "{{ openclaw_user }}"
|
|
openclaw_home: "{{ openclaw_home }}"
|
|
|
|
- name: Create .bash_profile to source .bashrc for login shells
|
|
ansible.builtin.copy:
|
|
dest: "{{ openclaw_home }}/.bash_profile"
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
mode: '0644'
|
|
content: |
|
|
# .bash_profile - Executed for login shells
|
|
# Source .bashrc to ensure environment is loaded for login shells
|
|
if [ -f ~/.bashrc ]; then
|
|
. ~/.bashrc
|
|
fi
|
|
|
|
# Fix DBus issues for systemd user services
|
|
- name: Get openclaw user ID
|
|
ansible.builtin.command: "id -u {{ openclaw_user }}"
|
|
register: openclaw_uid
|
|
changed_when: false
|
|
when: ansible_os_family == 'Debian' and not ci_test
|
|
|
|
- name: Display openclaw user ID
|
|
ansible.builtin.debug:
|
|
msg: "OpenClaw user ID: {{ openclaw_uid.stdout }}"
|
|
when: ansible_os_family == 'Debian' and not ci_test
|
|
|
|
- name: Enable lingering for openclaw user (allows systemd user services without login)
|
|
ansible.builtin.command: "loginctl enable-linger {{ openclaw_user }}"
|
|
changed_when: false
|
|
when: ansible_os_family == 'Debian' and not ci_test
|
|
|
|
- name: Create runtime directory for openclaw user
|
|
ansible.builtin.file:
|
|
path: "/run/user/{{ openclaw_uid.stdout }}"
|
|
state: directory
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
mode: '0700'
|
|
when: ansible_os_family == 'Debian' and not ci_test
|
|
|
|
- name: Store openclaw UID as fact for later use
|
|
ansible.builtin.set_fact:
|
|
openclaw_uid_value: "{{ openclaw_uid.stdout }}"
|
|
when: ansible_os_family == 'Debian' and not ci_test
|
|
|
|
# SSH key configuration
|
|
- name: Create .ssh directory for openclaw user
|
|
ansible.builtin.file:
|
|
path: "{{ openclaw_home }}/.ssh"
|
|
state: directory
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
mode: '0700'
|
|
|
|
- name: Add SSH authorized keys for openclaw user
|
|
ansible.posix.authorized_key:
|
|
user: "{{ openclaw_user }}"
|
|
state: present
|
|
key: "{{ item }}"
|
|
loop: "{{ openclaw_ssh_keys }}"
|
|
when: openclaw_ssh_keys | length > 0
|
|
|
|
- name: Display SSH key configuration status
|
|
ansible.builtin.debug:
|
|
msg: "{{ openclaw_ssh_keys | length }} SSH key(s) configured for openclaw user"
|
|
when: openclaw_ssh_keys | length > 0
|
|
|
|
- name: Display SSH key warning if none configured
|
|
ansible.builtin.debug:
|
|
msg: "No SSH keys configured. Set 'openclaw_ssh_keys' variable to allow SSH access."
|
|
when: openclaw_ssh_keys | length == 0
|
|
|
|
- name: Set XDG_RUNTIME_DIR in .bashrc for openclaw user
|
|
ansible.builtin.lineinfile:
|
|
path: "{{ openclaw_home }}/.bashrc"
|
|
line: 'export XDG_RUNTIME_DIR=/run/user/$(id -u)'
|
|
state: present
|
|
create: true
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
mode: '0644'
|
|
when: ansible_os_family == 'Debian' and not ci_test
|
|
|
|
- name: Set DBUS_SESSION_BUS_ADDRESS in .bashrc for openclaw user
|
|
ansible.builtin.blockinfile:
|
|
path: "{{ openclaw_home }}/.bashrc"
|
|
marker: "# {mark} ANSIBLE MANAGED BLOCK - DBus config"
|
|
block: |
|
|
# DBus session bus configuration
|
|
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
|
|
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
|
|
fi
|
|
create: true
|
|
owner: "{{ openclaw_user }}"
|
|
group: "{{ openclaw_user }}"
|
|
mode: '0644'
|
|
when: ansible_os_family == 'Debian' and not ci_test
|