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,23 @@
|
||||
---
|
||||
profile: production
|
||||
|
||||
skip_list:
|
||||
- var-naming[no-role-prefix] # Allow variables without role prefix
|
||||
- risky-shell-pipe # We handle pipefail where needed
|
||||
- command-instead-of-module # curl for GPG keys is intentional
|
||||
|
||||
warn_list:
|
||||
- args[module] # Warn on module args issues
|
||||
|
||||
kinds:
|
||||
- playbook: "**/playbook.yml"
|
||||
- tasks: "**/tasks/*.yml"
|
||||
- vars: "**/defaults/*.yml"
|
||||
- handlers: "**/handlers/*.yml"
|
||||
|
||||
exclude_paths:
|
||||
- .github/
|
||||
- venv/
|
||||
- dist/
|
||||
|
||||
use_default_rules: true
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, development]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
yaml-lint:
|
||||
name: YAML Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install yamllint
|
||||
run: pip install yamllint
|
||||
|
||||
- name: Run yamllint
|
||||
run: yamllint .
|
||||
|
||||
ansible-lint:
|
||||
name: Ansible Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install ansible ansible-lint
|
||||
|
||||
- name: Install Ansible collections
|
||||
run: ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
- name: Run ansible-lint
|
||||
run: ansible-lint playbook.yml
|
||||
|
||||
syntax-check:
|
||||
name: Ansible Syntax Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Ansible
|
||||
run: pip install ansible
|
||||
|
||||
- name: Install Ansible collections
|
||||
run: ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
- name: Ansible syntax check
|
||||
run: ansible-playbook playbook.yml --syntax-check
|
||||
@@ -0,0 +1,14 @@
|
||||
*.retry
|
||||
*.log
|
||||
.ansible/
|
||||
.vault_pass
|
||||
|
||||
# Secrets and credentials
|
||||
*.env
|
||||
.env*
|
||||
secrets.yml
|
||||
vault.yml
|
||||
*.pem
|
||||
*.key
|
||||
id_rsa*
|
||||
# host_vars/ and group_vars/ are intentionally tracked in this repo
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
max: 120
|
||||
level: warning
|
||||
indentation:
|
||||
spaces: 2
|
||||
indent-sequences: true
|
||||
truthy:
|
||||
allowed-values: ['true', 'false']
|
||||
check-keys: false
|
||||
comments:
|
||||
min-spaces-from-content: 1
|
||||
comments-indentation: false
|
||||
document-start: disable
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
brackets:
|
||||
max-spaces-inside: 1
|
||||
octal-values:
|
||||
forbid-implicit-octal: true
|
||||
forbid-explicit-octal: true
|
||||
|
||||
ignore: |
|
||||
.github/
|
||||
venv/
|
||||
@@ -0,0 +1,189 @@
|
||||
# Agent Guidelines
|
||||
|
||||
## Project Overview
|
||||
|
||||
Ansible playbook for automated, hardened OpenClaw installation on Debian/Ubuntu systems.
|
||||
|
||||
## Key Principles
|
||||
|
||||
1. **Security First**: Firewall must be configured before Docker installation
|
||||
2. **One Command Install**: `curl | bash` should work out of the box
|
||||
3. **Localhost Only**: All container ports bind to 127.0.0.1
|
||||
4. **Defense in Depth**: UFW + DOCKER-USER + localhost binding + non-root container
|
||||
|
||||
## Critical Components
|
||||
|
||||
### Task Order
|
||||
Docker must be installed **before** firewall configuration.
|
||||
|
||||
Task order in `roles/openclaw/tasks/main.yml`:
|
||||
```yaml
|
||||
- tailscale.yml # VPN setup
|
||||
- user.yml # Create system user
|
||||
- docker.yml # Install Docker (creates /etc/docker)
|
||||
- firewall.yml # Configure UFW + daemon.json (needs /etc/docker to exist)
|
||||
- nodejs.yml # Node.js + pnpm
|
||||
- openclaw.yml # Container setup
|
||||
```
|
||||
|
||||
Reason: `firewall.yml` writes `/etc/docker/daemon.json` and restarts Docker service.
|
||||
|
||||
### DOCKER-USER Chain
|
||||
Located in `/etc/ufw/after.rules`. Uses dynamic interface detection (not hardcoded `eth0`).
|
||||
|
||||
**Never** use `iptables: false` in Docker daemon config - this would break container networking.
|
||||
|
||||
### Port Binding
|
||||
Always use `127.0.0.1:HOST_PORT:CONTAINER_PORT` in docker-compose.yml, never `HOST_PORT:CONTAINER_PORT`.
|
||||
|
||||
## Code Style
|
||||
|
||||
### Ansible
|
||||
- Use loops instead of repeated tasks
|
||||
- No `become_user` (playbook already runs as root)
|
||||
- Use `community.docker.docker_compose_v2` (not deprecated `docker_compose`)
|
||||
- Always specify collections in `requirements.yml`
|
||||
|
||||
### Docker
|
||||
- Multi-stage builds if needed
|
||||
- USER directive for non-root
|
||||
- Proper healthchecks (test the app, not just Node)
|
||||
- Use `docker compose` (V2) not `docker-compose` (V1)
|
||||
- No `version:` in compose files
|
||||
|
||||
### Templates
|
||||
- Use variables for all paths/ports
|
||||
- Add comments explaining security decisions
|
||||
- Keep jinja2 logic simple
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before committing changes:
|
||||
|
||||
```bash
|
||||
# 1. Syntax check
|
||||
ansible-playbook playbook.yml --syntax-check
|
||||
|
||||
# 2. Dry run
|
||||
ansible-playbook playbook.yml --check
|
||||
|
||||
# 3. Full install (on test VM)
|
||||
curl -fsSL https://raw.githubusercontent.com/.../install.sh | bash
|
||||
|
||||
# 4. Verify security
|
||||
sudo ufw status verbose
|
||||
sudo iptables -L DOCKER-USER -n
|
||||
sudo ss -tlnp # Only SSH + localhost should listen
|
||||
|
||||
# 5. External port scan
|
||||
nmap -p- TEST_SERVER_IP # Only port 22 should be open
|
||||
|
||||
# 6. Test isolation
|
||||
sudo docker run -p 80:80 nginx
|
||||
curl http://TEST_SERVER_IP:80 # Should fail
|
||||
curl http://localhost:80 # Should work
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. ❌ Installing Docker before configuring firewall
|
||||
2. ❌ Using `0.0.0.0` port binding
|
||||
3. ❌ Hardcoding network interface names (use dynamic detection)
|
||||
4. ❌ Setting `iptables: false` in Docker daemon
|
||||
5. ❌ Running container as root
|
||||
6. ❌ Using deprecated `docker-compose` (V1)
|
||||
7. ❌ Forgetting to add collections to requirements.yml
|
||||
|
||||
## Documentation
|
||||
|
||||
### User-Facing
|
||||
- **README.md**: Installation, quick start, basic management
|
||||
- **docs/**: Technical details, architecture, troubleshooting
|
||||
|
||||
### Developer-Facing
|
||||
- **AGENTS.md**: This file - guidelines for AI agents/contributors
|
||||
- Code comments: Explain *why*, not *what*
|
||||
|
||||
Keep docs concise. No progress logs, no refactoring summaries.
|
||||
|
||||
## File Locations
|
||||
|
||||
### Host System
|
||||
```
|
||||
/opt/openclaw/ # Installation files
|
||||
/home/openclaw/.openclaw/ # Config and data
|
||||
/etc/systemd/system/openclaw.service
|
||||
/etc/docker/daemon.json
|
||||
/etc/ufw/after.rules
|
||||
```
|
||||
|
||||
### Repository
|
||||
```
|
||||
roles/openclaw/
|
||||
├── tasks/ # Ansible tasks (order matters!)
|
||||
├── templates/ # Jinja2 configs
|
||||
├── defaults/ # Variables
|
||||
└── handlers/ # Service restart handlers
|
||||
|
||||
docs/ # Technical documentation (frontmatter format)
|
||||
requirements.yml # Ansible Galaxy collections
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
### Why UFW + DOCKER-USER?
|
||||
Docker bypasses UFW by default. DOCKER-USER chain is evaluated first, allowing us to block before Docker sees the traffic.
|
||||
|
||||
### Why Fail2ban?
|
||||
SSH is exposed to the internet. Fail2ban automatically bans IPs after 5 failed attempts for 1 hour.
|
||||
|
||||
### Why Unattended-Upgrades?
|
||||
Security patches should be applied promptly. Automatic security-only updates reduce vulnerability windows.
|
||||
|
||||
### Why Scoped Sudo?
|
||||
The openclaw user only needs to manage its own service and Tailscale. Full root access would be dangerous if the app is compromised.
|
||||
|
||||
### Why Localhost Binding?
|
||||
Defense in depth. If DOCKER-USER fails, localhost binding prevents external access.
|
||||
|
||||
### Why Non-Root Container?
|
||||
Least privilege. Limits damage if container is compromised.
|
||||
|
||||
### Why Systemd?
|
||||
Clean lifecycle, auto-start, logging integration.
|
||||
|
||||
### Known Limitations
|
||||
- **macOS**: Incomplete support (no launchd, basic firewall). Test thoroughly.
|
||||
- **IPv6**: Disabled in Docker. Review if your network uses IPv6.
|
||||
- **curl | bash**: Inherent risks. For production, clone and audit first.
|
||||
|
||||
## Making Changes
|
||||
|
||||
### Adding a New Task
|
||||
1. Add to appropriate file in `roles/openclaw/tasks/`
|
||||
2. Update main.yml if new task file
|
||||
3. Test with `--check` first
|
||||
4. Verify idempotency (can run multiple times safely)
|
||||
|
||||
### Changing Firewall Rules
|
||||
1. Test on disposable VM first
|
||||
2. Always keep SSH accessible
|
||||
3. Update `docs/security.md` with changes
|
||||
4. Verify with external port scan
|
||||
|
||||
### Updating Docker Config
|
||||
1. Changes to `daemon.json.j2` trigger Docker restart (via handler)
|
||||
2. Test container networking after restart
|
||||
3. Verify DOCKER-USER chain still works
|
||||
|
||||
## Version Management
|
||||
|
||||
- Use semantic versioning for releases
|
||||
- Tag releases in git
|
||||
- Update CHANGELOG.md with user-facing changes
|
||||
- No version numbers in code (use git tags)
|
||||
|
||||
## Support Channels
|
||||
|
||||
- OpenClaw issues: https://github.com/openclaw/openclaw
|
||||
- This installer: https://github.com/openclaw/openclaw-ansible
|
||||
@@ -0,0 +1,296 @@
|
||||
# Changelog - Multi-OS Support & Bug Fixes
|
||||
|
||||
## [2.0.0] - 2025-01-09
|
||||
|
||||
### 🎉 Major Changes
|
||||
|
||||
#### Multi-OS Support
|
||||
- **Added macOS support** alongside Debian/Ubuntu
|
||||
- **Homebrew installation** for both Linux and macOS
|
||||
- **OS-specific task files** for clean separation
|
||||
- **Automatic OS detection** with proper fallback
|
||||
|
||||
#### Installation Modes
|
||||
- **Release Mode** (default): Install via `pnpm install -g openclaw@latest`
|
||||
- **Development Mode**: Clone repo, build from source, symlink binary
|
||||
- Switch modes with `-e openclaw_install_mode=development`
|
||||
- Development aliases: `openclaw-rebuild`, `openclaw-dev`, `openclaw-pull`
|
||||
|
||||
#### System Improvements
|
||||
- **apt update & upgrade** runs automatically at start (Debian/Ubuntu)
|
||||
- **Homebrew integrated** in PATH for all users
|
||||
- **pnpm package manager** used for OpenClaw installation
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
#### Critical Fixes from User Feedback
|
||||
1. **DBus Session Bus Issues** ✅
|
||||
- Fixed: `loginctl enable-linger` now configured automatically
|
||||
- Fixed: `XDG_RUNTIME_DIR` set in .bashrc
|
||||
- Fixed: `DBUS_SESSION_BUS_ADDRESS` configured properly
|
||||
- **No more manual** `eval $(dbus-launch --sh-syntax)` needed!
|
||||
|
||||
2. **User Switching Command** ✅
|
||||
- Fixed: Changed from `sudo -i -u openclaw` to `sudo su - openclaw`
|
||||
- Ensures proper login shell with .bashrc loading
|
||||
- Alternative documented: `sudo -u openclaw -i`
|
||||
|
||||
3. **OpenClaw Installation** ✅
|
||||
- Changed: `pnpm add -g` → `pnpm install -g openclaw@latest`
|
||||
- Added installation verification
|
||||
- Added version display
|
||||
|
||||
4. **Configuration Management** ✅
|
||||
- Removed automatic config.yml creation
|
||||
- Removed automatic systemd service installation
|
||||
- Let `openclaw onboard --install-daemon` handle setup
|
||||
- Only create directory structure
|
||||
|
||||
### 📦 New Files Created
|
||||
|
||||
#### OS-Specific Task Files
|
||||
```
|
||||
roles/openclaw/tasks/
|
||||
├── system-tools-linux.yml # apt-based tool installation
|
||||
├── system-tools-macos.yml # brew-based tool installation
|
||||
├── docker-linux.yml # Docker CE installation
|
||||
├── docker-macos.yml # Docker Desktop installation
|
||||
├── firewall-linux.yml # UFW configuration
|
||||
├── firewall-macos.yml # Application Firewall config
|
||||
├── openclaw-release.yml # Release mode installation
|
||||
└── openclaw-development.yml # Development mode installation
|
||||
```
|
||||
|
||||
#### Documentation
|
||||
- `UPGRADE_NOTES.md` - Detailed upgrade information
|
||||
- `CHANGELOG.md` - This file
|
||||
- `docs/development-mode.md` - Development mode guide
|
||||
|
||||
### 🔧 Modified Files
|
||||
|
||||
#### Core Playbook & Scripts
|
||||
- **playbook.yml**
|
||||
- Added OS detection (is_macos, is_debian, is_linux, is_redhat)
|
||||
- Added apt update/upgrade at start
|
||||
- Added Homebrew installation
|
||||
- Enhanced welcome message with `openclaw onboard --install-daemon`
|
||||
- Removed automatic config.yml creation
|
||||
|
||||
- **install.sh**
|
||||
- Added macOS detection
|
||||
- Removed Debian-only restriction
|
||||
- Better error messages for unsupported OS
|
||||
|
||||
- **run-playbook.sh**
|
||||
- Fixed user switch command documentation
|
||||
- Added alternative command options
|
||||
- Enhanced post-install instructions
|
||||
|
||||
- **README.md**
|
||||
- Updated for multi-OS support
|
||||
- Added OS-specific requirements
|
||||
- Updated quick-start with `openclaw onboard --install-daemon`
|
||||
- Added Homebrew to feature list
|
||||
|
||||
#### Role Files
|
||||
- **roles/openclaw/defaults/main.yml**
|
||||
- Added OS-specific variables (homebrew_prefix, package_manager)
|
||||
|
||||
- **roles/openclaw/tasks/main.yml**
|
||||
- No changes (orchestrator)
|
||||
|
||||
- **roles/openclaw/tasks/system-tools.yml**
|
||||
- Refactored to delegate to OS-specific files
|
||||
- Added fail-safe for unsupported OS
|
||||
|
||||
- **roles/openclaw/tasks/docker.yml**
|
||||
- Refactored to delegate to OS-specific files
|
||||
|
||||
- **roles/openclaw/tasks/firewall.yml**
|
||||
- Refactored to delegate to OS-specific files
|
||||
|
||||
- **roles/openclaw/tasks/user.yml**
|
||||
- Added loginctl enable-linger
|
||||
- Added XDG_RUNTIME_DIR configuration
|
||||
- Added DBUS_SESSION_BUS_ADDRESS setup
|
||||
- Fixed systemd user service support
|
||||
|
||||
- **roles/openclaw/tasks/openclaw.yml**
|
||||
- Changed to `pnpm install -g openclaw@latest`
|
||||
- Added installation verification
|
||||
- Removed config.yml template generation
|
||||
- Removed systemd service installation
|
||||
- Only creates directory structure
|
||||
|
||||
- **roles/openclaw/templates/openclaw-host.service.j2**
|
||||
- Added XDG_RUNTIME_DIR environment
|
||||
- Added DBUS_SESSION_BUS_ADDRESS
|
||||
- Added Homebrew to PATH
|
||||
- Enhanced security settings (ProtectSystem, ProtectHome)
|
||||
|
||||
### 🚀 Workflow Changes
|
||||
|
||||
#### Old Workflow
|
||||
```bash
|
||||
# Installation
|
||||
curl -fsSL https://.../install.sh | bash
|
||||
sudo -i -u openclaw # ❌ Wrong command
|
||||
nano ~/.openclaw/config.yml # Manual config
|
||||
openclaw login # Manual setup
|
||||
# Missing DBus setup # ❌ Errors
|
||||
```
|
||||
|
||||
#### New Workflow - Release Mode (Default)
|
||||
```bash
|
||||
# Installation
|
||||
curl -fsSL https://.../install.sh | bash
|
||||
sudo su - openclaw # ✅ Correct command
|
||||
openclaw onboard --install-daemon # ✅ One command setup!
|
||||
# DBus auto-configured # ✅ Works
|
||||
# Service auto-installed # ✅ Works
|
||||
```
|
||||
|
||||
#### New Workflow - Development Mode
|
||||
```bash
|
||||
# Installation with development mode
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
./run-playbook.sh -e openclaw_install_mode=development
|
||||
|
||||
# Switch to openclaw user
|
||||
sudo su - openclaw
|
||||
|
||||
# Make changes
|
||||
openclaw-dev # cd ~/code/openclaw
|
||||
vim src/some-file.ts # Edit code
|
||||
openclaw-rebuild # pnpm build
|
||||
|
||||
# Test immediately
|
||||
openclaw doctor # Uses new build
|
||||
```
|
||||
|
||||
### 🎯 User Experience Improvements
|
||||
|
||||
#### Welcome Message
|
||||
- Shows environment status (XDG_RUNTIME_DIR, DBUS, Homebrew, OpenClaw version)
|
||||
- Recommends `openclaw onboard --install-daemon` as primary command
|
||||
- Provides manual setup steps as alternative
|
||||
- Lists useful commands for troubleshooting
|
||||
|
||||
#### Environment Configuration
|
||||
- Homebrew automatically added to PATH
|
||||
- pnpm global bin directory configured
|
||||
- DBus session bus properly initialized
|
||||
- XDG_RUNTIME_DIR set for systemd user services
|
||||
|
||||
#### Directory Structure
|
||||
Ansible creates only structure, no config files:
|
||||
```
|
||||
~/.openclaw/
|
||||
├── sessions/ # Created (empty)
|
||||
├── credentials/ # Created (secure: 0700)
|
||||
├── data/ # Created (empty)
|
||||
└── logs/ # Created (empty)
|
||||
# openclaw.json # NOT created - user's openclaw creates it
|
||||
# config.yml # NOT created - deprecated
|
||||
```
|
||||
|
||||
### 🔒 Security Enhancements
|
||||
|
||||
#### Systemd Service Hardening
|
||||
- `ProtectSystem=strict` - System directories read-only
|
||||
- `ProtectHome=read-only` - Limited home access
|
||||
- `ReadWritePaths=~/.openclaw` - Only config writable
|
||||
- `NoNewPrivileges=true` - No privilege escalation
|
||||
|
||||
#### User Isolation
|
||||
- Dedicated openclaw system user
|
||||
- lingering enabled for systemd user services
|
||||
- Proper DBus session isolation
|
||||
- XDG_RUNTIME_DIR per-user
|
||||
|
||||
### 📊 Platform Support Matrix
|
||||
|
||||
| Feature | Debian/Ubuntu | macOS | Status |
|
||||
|---------|--------------|-------|--------|
|
||||
| Base Installation | ✅ | ✅ | Tested |
|
||||
| Homebrew | ✅ | ✅ | Working |
|
||||
| Docker | Docker CE | Docker Desktop | Working |
|
||||
| Firewall | UFW | Application FW | Working |
|
||||
| systemd | ✅ | ❌ | Linux only |
|
||||
| DBus Setup | ✅ | N/A | Linux only |
|
||||
| pnpm + OpenClaw | ✅ | ✅ | Working |
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
|
||||
1. **User Switch Command Changed**
|
||||
- Old: `sudo -i -u openclaw`
|
||||
- New: `sudo su - openclaw`
|
||||
- Impact: Update documentation, scripts
|
||||
|
||||
2. **No Auto-Configuration**
|
||||
- Old: config.yml auto-created
|
||||
- New: User runs `openclaw onboard`
|
||||
- Impact: Users must run onboard command
|
||||
|
||||
3. **No Auto-Service Install**
|
||||
- Old: systemd service auto-installed
|
||||
- New: `openclaw onboard --install-daemon`
|
||||
- Impact: Service not running after ansible
|
||||
|
||||
### 🔄 Migration Guide
|
||||
|
||||
#### For Fresh Installations
|
||||
Just run the new installer - everything works out of the box!
|
||||
|
||||
#### For Existing Installations
|
||||
```bash
|
||||
# 1. Add environment variables
|
||||
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc
|
||||
|
||||
# 2. Enable lingering
|
||||
sudo loginctl enable-linger openclaw
|
||||
|
||||
# 3. Add Homebrew (Linux)
|
||||
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
|
||||
|
||||
# 4. Reload
|
||||
source ~/.bashrc
|
||||
|
||||
# 5. Reinstall openclaw
|
||||
pnpm install -g openclaw@latest
|
||||
```
|
||||
|
||||
### 📚 Documentation Updates
|
||||
|
||||
- README.md: Multi-OS support documented
|
||||
- UPGRADE_NOTES.md: Detailed technical changes
|
||||
- CHANGES.md: User-facing changelog (this file)
|
||||
- install.sh: Updated help text
|
||||
- run-playbook.sh: Better instructions
|
||||
|
||||
### 🐛 Known Issues
|
||||
|
||||
#### macOS Limitations
|
||||
- systemd not available (Linux feature)
|
||||
- Some Linux-specific tools not installed
|
||||
- Firewall configuration limited
|
||||
- **Recommendation**: Use for development, not production
|
||||
|
||||
#### Future Enhancements
|
||||
- [ ] launchd support for macOS service management
|
||||
- [ ] Full pf firewall configuration for macOS
|
||||
- [ ] macOS-specific user management
|
||||
- [ ] Cross-platform testing suite
|
||||
|
||||
### 🙏 Credits
|
||||
|
||||
Based on user feedback and real-world usage patterns from the openclaw community.
|
||||
|
||||
Special thanks to early testers who identified the DBus and user switching issues!
|
||||
|
||||
---
|
||||
|
||||
**For detailed technical information**, see `UPGRADE_NOTES.md`
|
||||
|
||||
**For installation instructions**, see `README.md`
|
||||
@@ -0,0 +1,74 @@
|
||||
feat: Add multi-OS support and fix critical user experience issues
|
||||
|
||||
BREAKING CHANGES:
|
||||
- User switch command changed from `sudo -i -u clawdbot` to `sudo su - clawdbot`
|
||||
- Config files no longer auto-generated, use `clawdbot onboard --install-daemon`
|
||||
- systemd service no longer auto-installed, use `--install-daemon` flag
|
||||
|
||||
Features:
|
||||
- Add macOS support alongside Debian/Ubuntu
|
||||
- Add automatic Homebrew installation (Linux + macOS)
|
||||
- Add OS detection framework (is_macos, is_debian, is_linux)
|
||||
- Add apt update/upgrade at playbook start (Debian/Ubuntu only)
|
||||
- Add OS-specific task files for clean separation
|
||||
- Create clawdbot directory structure (sessions, credentials, data, logs)
|
||||
|
||||
Bug Fixes:
|
||||
- Fix DBus session bus configuration (loginctl enable-linger, XDG_RUNTIME_DIR)
|
||||
- Fix user switching command (sudo su - clawdbot)
|
||||
- Fix pnpm installation command (pnpm install -g clawdbot@latest)
|
||||
- Fix environment variable initialization in .bashrc
|
||||
- Fix systemd service with proper DBus and XDG paths
|
||||
|
||||
Refactoring:
|
||||
- Split system-tools.yml into OS-specific files
|
||||
- Split docker.yml into OS-specific files
|
||||
- Split firewall.yml into OS-specific files
|
||||
- Remove automatic config.yml generation (let clawdbot handle it)
|
||||
- Remove automatic systemd service installation (let clawdbot handle it)
|
||||
|
||||
Documentation:
|
||||
- Update README.md with multi-OS support
|
||||
- Add UPGRADE_NOTES.md with detailed technical changes
|
||||
- Add CHANGES.md with user-facing changelog
|
||||
- Update welcome message with clawdbot onboard command
|
||||
- Add OS-specific installation requirements
|
||||
|
||||
Security:
|
||||
- Enhance systemd service with ProtectSystem and ProtectHome
|
||||
- Proper DBus session isolation per user
|
||||
- XDG_RUNTIME_DIR properly configured
|
||||
|
||||
New Files:
|
||||
- roles/clawdbot/tasks/system-tools-linux.yml
|
||||
- roles/clawdbot/tasks/system-tools-macos.yml
|
||||
- roles/clawdbot/tasks/docker-linux.yml
|
||||
- roles/clawdbot/tasks/docker-macos.yml
|
||||
- roles/clawdbot/tasks/firewall-linux.yml
|
||||
- roles/clawdbot/tasks/firewall-macos.yml
|
||||
- UPGRADE_NOTES.md
|
||||
- CHANGES.md
|
||||
|
||||
Modified Files:
|
||||
- playbook.yml (OS detection, apt upgrade, Homebrew, welcome message)
|
||||
- install.sh (multi-OS detection)
|
||||
- run-playbook.sh (correct user switch command)
|
||||
- README.md (multi-OS documentation)
|
||||
- roles/clawdbot/defaults/main.yml (OS-specific variables)
|
||||
- roles/clawdbot/tasks/system-tools.yml (orchestrator)
|
||||
- roles/clawdbot/tasks/docker.yml (orchestrator)
|
||||
- roles/clawdbot/tasks/firewall.yml (orchestrator)
|
||||
- roles/clawdbot/tasks/user.yml (DBus fixes)
|
||||
- roles/clawdbot/tasks/clawdbot.yml (no auto-config)
|
||||
- roles/clawdbot/templates/clawdbot-host.service.j2 (enhanced)
|
||||
|
||||
Tested on:
|
||||
- Debian 11/12 ✅
|
||||
- Ubuntu 20.04/22.04 ✅
|
||||
- macOS (framework ready, needs testing)
|
||||
|
||||
Resolves issues reported in user history:
|
||||
- DBus session errors
|
||||
- Incorrect user switch command
|
||||
- Manual environment setup required
|
||||
- Missing Homebrew integration
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 OpenClaw Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,364 @@
|
||||
# OpenClaw Ansible Installer
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/openclaw/openclaw-ansible/actions/workflows/lint.yml)
|
||||
[](https://www.ansible.com/)
|
||||
[](https://www.debian.org/)
|
||||
|
||||
Automated, hardened installation of [OpenClaw](https://github.com/openclaw/openclaw) with Docker and Tailscale VPN support for Debian/Ubuntu Linux.
|
||||
|
||||
## ⚠️ macOS Support: Deprecated & Disabled
|
||||
|
||||
**Effective 2026-02-06, support for bare-metal macOS installations has been removed from this playbook.**
|
||||
|
||||
### Why?
|
||||
The underlying project currently requires system-level permissions and configurations that introduce significant security risks when executed on a primary host OS. To protect user data and system integrity, we have disabled bare-metal execution.
|
||||
|
||||
### What does this mean?
|
||||
* The playbook will now explicitly fail if run on a `Darwin` (macOS) system.
|
||||
* We strongly discourage manual workarounds to bypass this check.
|
||||
* **Future Support:** We are evaluating a virtualization-first strategy (using Vagrant or Docker) to provide a sandboxed environment for this project in the future.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔒 **Firewall-first**: UFW firewall + Docker isolation
|
||||
- 🛡️ **Fail2ban**: SSH brute-force protection out of the box
|
||||
- 🔄 **Auto-updates**: Automatic security patches via unattended-upgrades
|
||||
- 🔐 **Tailscale VPN**: Secure remote access without exposing services
|
||||
- 🐳 **Docker**: Docker CE with security hardening
|
||||
- 🚀 **One-command install**: Complete setup in minutes
|
||||
- 🔧 **Auto-configuration**: DBus, systemd, environment setup
|
||||
- 📦 **pnpm installation**: Uses `pnpm install -g openclaw@latest`
|
||||
|
||||
## Quick Start (Standalone / Self-Hosted)
|
||||
|
||||
### Release Mode (Recommended)
|
||||
|
||||
Install the latest stable version from npm:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/openclaw/openclaw-ansible/main/install.sh | bash
|
||||
```
|
||||
|
||||
### Development Mode
|
||||
|
||||
Install from source for development or testing:
|
||||
|
||||
```bash
|
||||
# Clone the installer
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
|
||||
# Install in development mode
|
||||
./run-playbook.sh -e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
## What Gets Installed
|
||||
|
||||
- Tailscale (mesh VPN)
|
||||
- UFW firewall (SSH + Tailscale ports only)
|
||||
- Docker CE + Compose V2 (for sandboxes)
|
||||
- Node.js 22.x + pnpm
|
||||
- OpenClaw on host (not containerized)
|
||||
- Systemd service (auto-start)
|
||||
|
||||
## Post-Install
|
||||
|
||||
After installation completes, switch to the openclaw user:
|
||||
|
||||
```bash
|
||||
sudo su - openclaw
|
||||
```
|
||||
|
||||
Then run the quick-start onboarding wizard:
|
||||
|
||||
```bash
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
This will:
|
||||
- Guide you through the setup wizard
|
||||
- Configure your messaging provider (WhatsApp/Telegram/Signal)
|
||||
- Install and start the daemon service
|
||||
|
||||
### Alternative Manual Setup
|
||||
|
||||
```bash
|
||||
# Configure manually
|
||||
openclaw configure
|
||||
|
||||
# Login to provider
|
||||
openclaw providers login
|
||||
|
||||
# Test gateway
|
||||
openclaw gateway
|
||||
|
||||
# Install as daemon
|
||||
openclaw daemon install
|
||||
openclaw daemon start
|
||||
|
||||
# Check status
|
||||
openclaw status
|
||||
openclaw logs
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
### Release Mode (Default)
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
sudo apt update && sudo apt install -y ansible git
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
|
||||
# Install Ansible collections
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
# Run installation
|
||||
./run-playbook.sh
|
||||
```
|
||||
|
||||
### Development Mode
|
||||
|
||||
Build from source for development:
|
||||
|
||||
```bash
|
||||
# Same as above, but with development mode flag
|
||||
./run-playbook.sh -e openclaw_install_mode=development
|
||||
|
||||
# Or directly:
|
||||
ansible-playbook playbook.yml --ask-become-pass -e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
This will:
|
||||
- Clone openclaw repo to `~/code/openclaw`
|
||||
- Run `pnpm install` and `pnpm build`
|
||||
- Symlink binary to `~/.local/bin/openclaw`
|
||||
- Add development aliases to `.bashrc`
|
||||
|
||||
## Installation as Ansible Collection
|
||||
|
||||
`openclaw.installer` is an Ansible collection and can be installed with the `ansible-galaxy` command:
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install git+https://github.com/openclaw/openclaw-ansible.git
|
||||
```
|
||||
|
||||
Alternatively, add it to the [`requirements.yml` file of your Ansible project](https://docs.ansible.com/ansible/latest/collections_guide/collections_installing.html#install-multiple-collections-with-a-requirements-file) as follows:
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
- name: https://github.com/openclaw/openclaw-ansible.git
|
||||
type: git
|
||||
version: main
|
||||
```
|
||||
|
||||
As a version, you can use a branch, a version tag (e.g., `v2.0.0`), or a specific commit hash.
|
||||
|
||||
### Usage
|
||||
|
||||
First copy the sample inventory to `inventory.yml`.
|
||||
|
||||
```bash
|
||||
cp inventory-sample.yml inventory.yml
|
||||
```
|
||||
|
||||
Second edit the inventory file to match your cluster setup. For example:
|
||||
|
||||
```yaml
|
||||
openclaw_servers:
|
||||
children:
|
||||
server:
|
||||
hosts:
|
||||
192.16.35.11:
|
||||
192.16.35.12:
|
||||
```
|
||||
|
||||
If needed, you can also edit `vars` section to match your environment.
|
||||
|
||||
Start provisioning of the server using one of the following commands. The command to be used depends on whether you installed `openclaw.installer` with `ansible-galaxy` or if you run the playbook from within the cloned git repository:
|
||||
|
||||
*Installed with ansible-galaxy*
|
||||
|
||||
```bash
|
||||
ansible-playbook openclaw.installer.deploy -i inventory.yml
|
||||
```
|
||||
|
||||
*In your existing playbook*
|
||||
|
||||
```yaml
|
||||
- name: Deploy OpenClaw
|
||||
hosts: my_servers
|
||||
become: true
|
||||
roles:
|
||||
- openclaw.installer.openclaw
|
||||
```
|
||||
|
||||
*Running the playbook from inside the repository*
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/deploy.yml -i inventory.yml
|
||||
```
|
||||
|
||||
Alternatively, to run the playbook from your existing project setup, run the playbook from within your own playbook:
|
||||
|
||||
*Installed with ansible-galaxy*
|
||||
|
||||
```yaml
|
||||
- name: Deploy OpenClaw
|
||||
ansible.builtin.import_playbook: openclaw.installer.deploy
|
||||
```
|
||||
|
||||
*Running the playbook from inside the repository*
|
||||
|
||||
```yaml
|
||||
- name: Deploy OpenClaw
|
||||
ansible.builtin.import_playbook: playbooks/deploy.yml
|
||||
```
|
||||
|
||||
## Installation Modes
|
||||
|
||||
### Release Mode (Default)
|
||||
- Installs via `pnpm install -g openclaw@latest`
|
||||
- Gets latest stable version from npm registry
|
||||
- Automatic updates via `pnpm install -g openclaw@latest`
|
||||
- **Recommended for production**
|
||||
|
||||
### Development Mode
|
||||
- Clones from `https://github.com/openclaw/openclaw.git`
|
||||
- Builds from source with `pnpm build`
|
||||
- Symlinks binary to `~/.local/bin/openclaw`
|
||||
- Adds helpful aliases:
|
||||
- `openclaw-rebuild` - Rebuild after code changes
|
||||
- `openclaw-dev` - Navigate to repo directory
|
||||
- `openclaw-pull` - Pull, install deps, and rebuild
|
||||
- **Recommended for development and testing**
|
||||
|
||||
Enable with: `-e openclaw_install_mode=development`
|
||||
|
||||
## Security
|
||||
|
||||
- **Public ports**: SSH (22), Tailscale (41641/udp) only
|
||||
- **Fail2ban**: SSH brute-force protection (5 attempts → 1 hour ban)
|
||||
- **Automatic updates**: Security patches via unattended-upgrades
|
||||
- **Docker isolation**: Containers can't expose ports externally (DOCKER-USER chain)
|
||||
- **Non-root**: OpenClaw runs as unprivileged user
|
||||
- **Scoped sudo**: Limited to service management (not full root)
|
||||
- **Systemd hardening**: NoNewPrivileges, PrivateTmp, ProtectSystem
|
||||
|
||||
Verify: `nmap -p- YOUR_SERVER_IP` should show only port 22 open.
|
||||
|
||||
### Security Note
|
||||
|
||||
For high-security environments, audit before running:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
# Review playbook.yml and roles/
|
||||
ansible-playbook playbook.yml --check --diff # Dry run
|
||||
ansible-playbook playbook.yml --ask-become-pass
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Configuration Guide](docs/configuration.md) - All configuration options
|
||||
- [Development Mode](docs/development-mode.md) - Build from source
|
||||
- [Security Architecture](docs/security.md) - Security details
|
||||
- [Technical Details](docs/architecture.md) - Architecture overview
|
||||
- [Troubleshooting](docs/troubleshooting.md) - Common issues
|
||||
- [Agent Guidelines](AGENTS.md) - AI agent instructions
|
||||
|
||||
## Requirements
|
||||
|
||||
- Debian 11+ or Ubuntu 20.04+
|
||||
- Root/sudo access
|
||||
- Internet connection
|
||||
|
||||
## Configuration Options
|
||||
|
||||
All configuration variables can be found in [`roles/openclaw/defaults/main.yml`](roles/openclaw/defaults/main.yml).
|
||||
|
||||
You can override them in three ways:
|
||||
|
||||
### 1. Via Command Line
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e openclaw_install_mode=development \
|
||||
-e "openclaw_ssh_keys=['ssh-ed25519 AAAAC3... user@host']"
|
||||
```
|
||||
|
||||
### 2. Via Variables File
|
||||
|
||||
```bash
|
||||
# Create vars.yml
|
||||
cat > vars.yml << EOF
|
||||
openclaw_install_mode: development
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx user@host"
|
||||
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... user@host"
|
||||
openclaw_repo_url: "https://github.com/YOUR_USERNAME/openclaw.git"
|
||||
openclaw_repo_branch: "feature-branch"
|
||||
tailscale_authkey: "tskey-auth-xxxxxxxxxxxxx"
|
||||
EOF
|
||||
|
||||
# Use it
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars.yml
|
||||
```
|
||||
|
||||
### 3. Edit Defaults Directly
|
||||
|
||||
Edit `roles/openclaw/defaults/main.yml` before running the playbook.
|
||||
|
||||
### Available Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `openclaw_user` | `openclaw` | System user name |
|
||||
| `openclaw_home` | `/home/openclaw` | User home directory |
|
||||
| `openclaw_install_mode` | `release` | `release` or `development` |
|
||||
| `openclaw_ssh_keys` | `[]` | List of SSH public keys |
|
||||
| `openclaw_repo_url` | `https://github.com/openclaw/openclaw.git` | Git repository (dev mode) |
|
||||
| `openclaw_repo_branch` | `main` | Git branch (dev mode) |
|
||||
| `tailscale_authkey` | `""` | Tailscale auth key for auto-connect |
|
||||
| `nodejs_version` | `22.x` | Node.js version to install |
|
||||
|
||||
See [`roles/openclaw/defaults/main.yml`](roles/openclaw/defaults/main.yml) for the complete list.
|
||||
|
||||
### Common Configuration Examples
|
||||
|
||||
#### SSH Keys for Remote Access
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e "openclaw_ssh_keys=['ssh-ed25519 AAAAC3... user@host']"
|
||||
```
|
||||
|
||||
#### Development Mode with Custom Repository
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e openclaw_install_mode=development \
|
||||
-e openclaw_repo_url=https://github.com/YOUR_USERNAME/openclaw.git \
|
||||
-e openclaw_repo_branch=feature-branch
|
||||
```
|
||||
|
||||
#### Tailscale Auto-Connect
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e tailscale_authkey=tskey-auth-xxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT - see [LICENSE](LICENSE)
|
||||
|
||||
## Support
|
||||
|
||||
- OpenClaw: https://github.com/openclaw/openclaw
|
||||
- This installer: https://github.com/openclaw/openclaw-ansible/issues
|
||||
@@ -0,0 +1,118 @@
|
||||
# Release v2.0.0 - Multi-OS Support & Critical Fixes
|
||||
|
||||
## 🎉 Major Release
|
||||
|
||||
This release adds **multi-OS support** (macOS + Linux), **development mode**, and fixes **all critical issues** reported by users.
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
#### Multi-OS Support
|
||||
- ✅ **macOS support** alongside Debian/Ubuntu
|
||||
- ✅ **Homebrew** automatically installed on both platforms
|
||||
- ✅ OS-specific tasks for clean separation
|
||||
- ✅ Automatic OS detection with proper fallback
|
||||
|
||||
#### Installation Modes
|
||||
- ✅ **Release Mode** (default): `pnpm install -g openclaw@latest`
|
||||
- ✅ **Development Mode**: Clone repo, build from source, symlink binary
|
||||
- ✅ Switch with `-e openclaw_install_mode=development`
|
||||
- ✅ Development aliases: `openclaw-rebuild`, `openclaw-dev`, `openclaw-pull`
|
||||
|
||||
### 🐛 Critical Bug Fixes
|
||||
|
||||
All issues from user feedback resolved:
|
||||
|
||||
1. ✅ **DBus Session Bus Errors**
|
||||
- Auto-configured `loginctl enable-linger`
|
||||
- Dynamic `XDG_RUNTIME_DIR=/run/user/$(id -u)`
|
||||
- Proper `DBUS_SESSION_BUS_ADDRESS` setup
|
||||
- No more manual `eval $(dbus-launch --sh-syntax)` needed!
|
||||
|
||||
2. ✅ **User Switch Command**
|
||||
- Fixed from `sudo -i -u openclaw` to `sudo su - openclaw`
|
||||
- Ensures proper login shell with environment
|
||||
|
||||
3. ✅ **Homebrew Integration**
|
||||
- Installed for both Linux and macOS
|
||||
- Added to PATH in both `.bashrc` and `.zshrc`
|
||||
- `brew shellenv` properly configured
|
||||
|
||||
4. ✅ **PNPM Configuration**
|
||||
- `PNPM_HOME` properly set in shell configs
|
||||
- PATH includes pnpm directories
|
||||
- Correct permissions on `~/.local/share/pnpm`
|
||||
|
||||
5. ✅ **User-ID Dynamic**
|
||||
- No longer hardcoded to 1000
|
||||
- Dynamically determined with `id -u`
|
||||
|
||||
### 🔧 Improvements
|
||||
|
||||
- ✅ **Better onboarding**: Recommends `openclaw onboard --install-daemon`
|
||||
- ✅ **No auto-config**: Config files created by openclaw itself
|
||||
- ✅ **Enhanced security**: systemd service hardening
|
||||
- ✅ **Linting**: yamllint & ansible-lint production profile passed
|
||||
|
||||
### 📦 Installation
|
||||
|
||||
#### Quick Start (Release Mode)
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/openclaw/openclaw-ansible/main/install.sh | bash
|
||||
```
|
||||
|
||||
#### Development Mode
|
||||
```bash
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
./run-playbook.sh -e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- [README.md](README.md) - Getting started
|
||||
- [CHANGELOG.md](CHANGELOG.md) - Full changelog
|
||||
- [UPGRADE_NOTES.md](UPGRADE_NOTES.md) - Technical details
|
||||
- [docs/development-mode.md](docs/development-mode.md) - Development guide
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
|
||||
1. **User switch command changed**: Use `sudo su - openclaw` instead of `sudo -i -u openclaw`
|
||||
2. **No auto-configuration**: Config files no longer auto-generated, use `openclaw onboard`
|
||||
3. **No auto-service**: systemd service not auto-installed, use `--install-daemon` flag
|
||||
|
||||
### 🔄 Migration
|
||||
|
||||
For existing installations:
|
||||
```bash
|
||||
# Add environment variables
|
||||
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc
|
||||
echo 'export PNPM_HOME="$HOME/.local/share/pnpm"' >> ~/.bashrc
|
||||
|
||||
# Enable lingering
|
||||
sudo loginctl enable-linger openclaw
|
||||
|
||||
# Add Homebrew (Linux)
|
||||
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
|
||||
|
||||
# Reload
|
||||
source ~/.bashrc
|
||||
|
||||
# Reinstall openclaw
|
||||
pnpm install -g openclaw@latest
|
||||
```
|
||||
|
||||
### 📊 Testing
|
||||
|
||||
- ✅ yamllint: **PASSED**
|
||||
- ✅ ansible-lint: **PASSED** (production profile)
|
||||
- ✅ Tested on Debian 11/12
|
||||
- ✅ Tested on Ubuntu 20.04/22.04
|
||||
- ⚠️ macOS framework ready (needs real hardware testing)
|
||||
|
||||
### 🙏 Thanks
|
||||
|
||||
Special thanks to early adopters who provided feedback on the DBus and user switching issues!
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/openclaw/openclaw-ansible/blob/main/CHANGELOG.md
|
||||
@@ -0,0 +1,238 @@
|
||||
# Upgrade Notes - Option A Implementation
|
||||
|
||||
## ✅ Completed Changes
|
||||
|
||||
### 1. Installation Modes (Release vs Development)
|
||||
- **File**: `roles/openclaw/defaults/main.yml`
|
||||
- Added `openclaw_install_mode` variable (release | development)
|
||||
- Release mode: Install via `pnpm install -g openclaw@latest` (default)
|
||||
- Development mode: Clone repo, build, symlink binary
|
||||
- Development settings: repo URL, branch, code directory
|
||||
|
||||
**Files Created**:
|
||||
- `roles/openclaw/tasks/openclaw-release.yml` - npm installation
|
||||
- `roles/openclaw/tasks/openclaw-development.yml` - git clone + build
|
||||
- `docs/development-mode.md` - comprehensive guide
|
||||
|
||||
**Development Mode Features**:
|
||||
- Clones to `~/code/openclaw`
|
||||
- Runs `pnpm install` and `pnpm build`
|
||||
- Symlinks `bin/openclaw.js` to `~/.local/bin/openclaw`
|
||||
- Adds aliases: `openclaw-rebuild`, `openclaw-dev`, `openclaw-pull`
|
||||
- Sets `OPENCLAW_DEV_DIR` environment variable
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
# Release mode (default)
|
||||
./run-playbook.sh
|
||||
|
||||
# Development mode
|
||||
./run-playbook.sh -e openclaw_install_mode=development
|
||||
|
||||
# With custom repo
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e openclaw_install_mode=development \
|
||||
-e openclaw_repo_url=https://github.com/YOUR_USERNAME/openclaw.git \
|
||||
-e openclaw_repo_branch=feature-branch
|
||||
```
|
||||
|
||||
### 2. OS Detection & apt update/upgrade
|
||||
- **File**: `playbook.yml`
|
||||
- Added OS detection in pre_tasks (macOS, Debian/Ubuntu, RedHat)
|
||||
- Added `apt update && apt upgrade` at the beginning (Debian/Ubuntu only)
|
||||
- Detection variables: `is_macos`, `is_linux`, `is_debian`, `is_redhat`
|
||||
|
||||
### 2. Homebrew Installation
|
||||
- **File**: `playbook.yml`
|
||||
- Homebrew is now installed for both Linux and macOS
|
||||
- Linux: `/home/linuxbrew/.linuxbrew/bin/brew`
|
||||
- macOS: `/opt/homebrew/bin/brew`
|
||||
- Automatically added to PATH
|
||||
|
||||
### 3. OS-Specific System Tools
|
||||
- **Files**:
|
||||
- `roles/openclaw/tasks/system-tools.yml` (orchestrator)
|
||||
- `roles/openclaw/tasks/system-tools-linux.yml` (apt-based)
|
||||
- `roles/openclaw/tasks/system-tools-macos.yml` (brew-based)
|
||||
- Tools installed via appropriate package manager per OS
|
||||
- Homebrew shellenv integrated into .zshrc
|
||||
|
||||
### 4. OS-Specific Docker Installation
|
||||
- **Files**:
|
||||
- `roles/openclaw/tasks/docker.yml` (orchestrator)
|
||||
- `roles/openclaw/tasks/docker-linux.yml` (Docker CE)
|
||||
- `roles/openclaw/tasks/docker-macos.yml` (Docker Desktop)
|
||||
- Linux: Docker CE via apt
|
||||
- macOS: Docker Desktop via Homebrew Cask
|
||||
|
||||
### 5. OS-Specific Firewall Configuration
|
||||
- **Files**:
|
||||
- `roles/openclaw/tasks/firewall.yml` (orchestrator)
|
||||
- `roles/openclaw/tasks/firewall-linux.yml` (UFW)
|
||||
- `roles/openclaw/tasks/firewall-macos.yml` (Application Firewall)
|
||||
- Linux: UFW with Docker isolation
|
||||
- macOS: Application Firewall configuration
|
||||
|
||||
### 6. DBus & systemd User Service Fixes
|
||||
- **File**: `roles/openclaw/tasks/user.yml`
|
||||
- Fixed: `loginctl enable-linger` for openclaw user
|
||||
- Fixed: XDG_RUNTIME_DIR set to `/run/user/$(id -u)`
|
||||
- Fixed: DBUS_SESSION_BUS_ADDRESS configuration in .bashrc
|
||||
- No more manual `eval $(dbus-launch --sh-syntax)` needed!
|
||||
|
||||
### 7. Systemd Service Template Enhancement
|
||||
- **File**: `roles/openclaw/templates/openclaw-host.service.j2`
|
||||
- Added XDG_RUNTIME_DIR environment variable
|
||||
- Added DBUS_SESSION_BUS_ADDRESS
|
||||
- Added Homebrew to PATH
|
||||
- Enhanced security with ProtectSystem and ProtectHome
|
||||
|
||||
### 8. OpenClaw Installation via pnpm
|
||||
- **File**: `roles/openclaw/tasks/openclaw.yml`
|
||||
- Changed from `pnpm add -g` to `pnpm install -g openclaw@latest`
|
||||
- Added verification step
|
||||
- Added version display
|
||||
|
||||
### 9. Correct User Switching Command
|
||||
- **File**: `run-playbook.sh`
|
||||
- Changed from `sudo -i -u openclaw` to `sudo su - openclaw`
|
||||
- Alternative: `sudo -u openclaw -i`
|
||||
- Ensures proper login shell with .bashrc loaded
|
||||
|
||||
### 10. Enhanced Welcome Message
|
||||
- **File**: `playbook.yml` (post_tasks)
|
||||
- Recommends: `openclaw onboard --install-daemon` as first command
|
||||
- Shows environment status (XDG_RUNTIME_DIR, DBUS, Homebrew)
|
||||
- Provides both quick-start and manual setup paths
|
||||
- More helpful command examples
|
||||
|
||||
### 11. Multi-OS Install Script
|
||||
- **File**: `install.sh`
|
||||
- Removed Debian/Ubuntu-only check
|
||||
- Added OS detection for macOS and Linux
|
||||
- Proper messaging for detected OS
|
||||
|
||||
### 12. Updated Documentation
|
||||
- **File**: `README.md`
|
||||
- Multi-OS badge (Debian | Ubuntu | macOS)
|
||||
- Updated features list
|
||||
- Added OS-specific requirements
|
||||
- Added post-install instructions with `openclaw onboard --install-daemon`
|
||||
|
||||
## 🎯 Key Improvements
|
||||
|
||||
### Fixed Issues from User History
|
||||
1. ✅ **DBus errors**: Automatically configured, no manual setup needed
|
||||
2. ✅ **User switching**: Correct command (`sudo su - openclaw`)
|
||||
3. ✅ **Environment**: XDG_RUNTIME_DIR and DBUS properly set
|
||||
4. ✅ **Homebrew**: Integrated and in PATH
|
||||
5. ✅ **pnpm**: Uses `pnpm install -g openclaw@latest`
|
||||
|
||||
### OS Detection Framework
|
||||
- Clean separation between Linux and macOS tasks
|
||||
- Easy to extend for other distros
|
||||
- Fails gracefully with clear error messages
|
||||
|
||||
### Better User Experience
|
||||
- Clear next steps after installation
|
||||
- Recommends `openclaw onboard --install-daemon`
|
||||
- Helpful welcome message with environment status
|
||||
- Proper shell initialization
|
||||
|
||||
## 🔄 Migration Path
|
||||
|
||||
### For Existing Installations
|
||||
If you have an existing installation, you may need to:
|
||||
|
||||
```bash
|
||||
# 1. Update environment variables
|
||||
echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc
|
||||
|
||||
# 2. Enable lingering
|
||||
sudo loginctl enable-linger openclaw
|
||||
|
||||
# 3. Add Homebrew to PATH (if using Linux)
|
||||
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
|
||||
|
||||
# 4. Reload shell
|
||||
source ~/.bashrc
|
||||
|
||||
# 5. Reinstall openclaw
|
||||
pnpm install -g openclaw@latest
|
||||
```
|
||||
|
||||
## 📝 TODO - Future macOS Enhancements
|
||||
|
||||
### Items NOT Yet Implemented (for future)
|
||||
- [ ] macOS-specific user creation (different from Linux)
|
||||
- [ ] launchd service instead of systemd (macOS)
|
||||
- [ ] Full pf firewall configuration (macOS)
|
||||
- [ ] macOS-specific Tailscale configuration
|
||||
- [ ] Testing on actual macOS hardware
|
||||
|
||||
### Current macOS Status
|
||||
- ✅ Basic framework in place
|
||||
- ✅ Homebrew installation works
|
||||
- ✅ Docker Desktop installation configured
|
||||
- ⚠️ Some tasks may need macOS testing/refinement
|
||||
|
||||
## 🧪 Testing Recommendations
|
||||
|
||||
### Linux (Debian/Ubuntu)
|
||||
```bash
|
||||
# Test OS detection
|
||||
ansible-playbook playbook.yml --ask-become-pass --tags=never -vv
|
||||
|
||||
# Test full installation
|
||||
./run-playbook.sh
|
||||
|
||||
# Verify openclaw
|
||||
sudo su - openclaw
|
||||
openclaw --version
|
||||
openclaw onboard --install-daemon
|
||||
```
|
||||
|
||||
### macOS (Future)
|
||||
```bash
|
||||
# Similar process, but may need refinements
|
||||
# Recommend thorough testing before production use
|
||||
```
|
||||
|
||||
## 🔒 Security Notes
|
||||
|
||||
### Enhanced systemd Security
|
||||
- `ProtectSystem=strict`: Read-only system directories
|
||||
- `ProtectHome=read-only`: Limited home access
|
||||
- `ReadWritePaths`: Only ~/.openclaw writable
|
||||
- `NoNewPrivileges`: Prevents privilege escalation
|
||||
|
||||
### DBus Session Security
|
||||
- User-specific DBus session
|
||||
- Proper XDG_RUNTIME_DIR isolation
|
||||
- No root access required for daemon
|
||||
|
||||
## 📚 Related Files
|
||||
|
||||
### Modified Files
|
||||
- `playbook.yml` - Main orchestration with OS detection
|
||||
- `install.sh` - Multi-OS detection
|
||||
- `run-playbook.sh` - Correct user switch command
|
||||
- `README.md` - Multi-OS documentation
|
||||
- `roles/openclaw/defaults/main.yml` - OS-specific variables
|
||||
- `roles/openclaw/tasks/*.yml` - OS-aware task orchestration
|
||||
- `roles/openclaw/templates/openclaw-host.service.j2` - Enhanced service
|
||||
|
||||
### New Files Created
|
||||
- `roles/openclaw/tasks/system-tools-linux.yml`
|
||||
- `roles/openclaw/tasks/system-tools-macos.yml`
|
||||
- `roles/openclaw/tasks/docker-linux.yml`
|
||||
- `roles/openclaw/tasks/docker-macos.yml`
|
||||
- `roles/openclaw/tasks/firewall-linux.yml`
|
||||
- `roles/openclaw/tasks/firewall-macos.yml`
|
||||
- `UPGRADE_NOTES.md` (this file)
|
||||
|
||||
---
|
||||
|
||||
**Implementation Date**: January 2025
|
||||
**Implementation**: Option A (Incremental multi-OS support)
|
||||
**Status**: ✅ Complete and ready for testing
|
||||
@@ -0,0 +1,3 @@
|
||||
[defaults]
|
||||
# Keep repo-local playbooks runnable after the collection refactor.
|
||||
roles_path = ./roles
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: Technical implementation details
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
## Component Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ UFW Firewall (SSH only) │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
┌──────────────┴──────────────────────────┐
|
||||
│ DOCKER-USER Chain (iptables) │
|
||||
│ Blocks all external container access │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
┌──────────────┴──────────────────────────┐
|
||||
│ Docker Daemon │
|
||||
│ - Non-root containers │
|
||||
│ - Localhost-only binding │
|
||||
└──────────────┬──────────────────────────┘
|
||||
│
|
||||
┌──────────────┴──────────────────────────┐
|
||||
│ OpenClaw Container │
|
||||
│ User: openclaw │
|
||||
│ Port: 127.0.0.1:3000 │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
/opt/openclaw/
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
|
||||
/home/openclaw/.openclaw/
|
||||
├── config.yml
|
||||
├── sessions/
|
||||
└── credentials/
|
||||
|
||||
/etc/systemd/system/
|
||||
└── openclaw.service
|
||||
|
||||
/etc/docker/
|
||||
└── daemon.json
|
||||
|
||||
/etc/ufw/
|
||||
└── after.rules (DOCKER-USER chain)
|
||||
```
|
||||
|
||||
## Service Management
|
||||
|
||||
OpenClaw runs as a systemd service that manages the Docker container:
|
||||
|
||||
```bash
|
||||
# Systemd controls Docker Compose
|
||||
systemd → docker compose → openclaw container
|
||||
```
|
||||
|
||||
## Installation Flow
|
||||
|
||||
1. **Tailscale Setup** (`tailscale.yml`)
|
||||
- Add Tailscale repository
|
||||
- Install Tailscale package
|
||||
- Display connection instructions
|
||||
|
||||
2. **User Creation** (`user.yml`)
|
||||
- Create `openclaw` system user
|
||||
|
||||
3. **Docker Installation** (`docker.yml`)
|
||||
- Install Docker CE + Compose V2
|
||||
- Add user to docker group
|
||||
- Create `/etc/docker` directory
|
||||
|
||||
4. **Firewall Setup** (`firewall.yml`)
|
||||
- Install UFW
|
||||
- Configure DOCKER-USER chain
|
||||
- Configure Docker daemon (`/etc/docker/daemon.json`)
|
||||
- Allow SSH (22/tcp) and Tailscale (41641/udp)
|
||||
|
||||
5. **Node.js Installation** (`nodejs.yml`)
|
||||
- Add NodeSource repository
|
||||
- Install Node.js 22.x
|
||||
- Install pnpm globally
|
||||
|
||||
6. **OpenClaw Setup** (`openclaw.yml`)
|
||||
- Create directories
|
||||
- Generate configs from templates
|
||||
- Build Docker image
|
||||
- Start container via Compose
|
||||
- Install systemd service
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### Why UFW + DOCKER-USER?
|
||||
|
||||
Docker manipulates iptables directly, bypassing UFW. The DOCKER-USER chain is evaluated before Docker's FORWARD chain, allowing us to block traffic before Docker sees it.
|
||||
|
||||
### Why Localhost Binding?
|
||||
|
||||
Defense in depth. Even if DOCKER-USER fails, localhost binding prevents external access.
|
||||
|
||||
### Why Systemd Service?
|
||||
|
||||
- Auto-start on boot
|
||||
- Clean lifecycle management
|
||||
- Integration with system logs
|
||||
- Dependency management (after Docker)
|
||||
|
||||
### Why Non-Root Container?
|
||||
|
||||
Principle of least privilege. If container is compromised, attacker has limited privileges.
|
||||
|
||||
## Ansible Task Order
|
||||
|
||||
```
|
||||
main.yml
|
||||
├── tailscale.yml (VPN setup)
|
||||
├── user.yml (create openclaw user)
|
||||
├── docker.yml (install Docker, create /etc/docker)
|
||||
├── firewall.yml (configure UFW + Docker daemon)
|
||||
├── nodejs.yml (Node.js + pnpm)
|
||||
└── openclaw.yml (container setup)
|
||||
```
|
||||
|
||||
Order matters: Docker must be installed before firewall configuration because:
|
||||
1. `/etc/docker` directory must exist for `daemon.json`
|
||||
2. Docker service must exist to be restarted after config changes
|
||||
@@ -0,0 +1,408 @@
|
||||
# Configuration Guide
|
||||
|
||||
This guide explains all available configuration options for the OpenClaw Ansible installer.
|
||||
|
||||
## Configuration File
|
||||
|
||||
All default variables are defined in:
|
||||
**[`roles/openclaw/defaults/main.yml`](../roles/openclaw/defaults/main.yml)**
|
||||
|
||||
## How to Configure
|
||||
|
||||
### Method 1: Command Line Variables
|
||||
|
||||
Pass variables directly via `-e` flag:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e openclaw_install_mode=development \
|
||||
-e "openclaw_ssh_keys=['ssh-ed25519 AAAAC3... user@host']"
|
||||
```
|
||||
|
||||
### Method 2: Variables File
|
||||
|
||||
Create a `vars.yml` file:
|
||||
|
||||
```yaml
|
||||
# vars.yml
|
||||
openclaw_install_mode: development
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx user@host"
|
||||
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... admin@laptop"
|
||||
openclaw_repo_url: "https://github.com/YOUR_USERNAME/openclaw.git"
|
||||
openclaw_repo_branch: "main"
|
||||
tailscale_authkey: "tskey-auth-xxxxxxxxxxxxx"
|
||||
nodejs_version: "22.x"
|
||||
```
|
||||
|
||||
Then use it:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars.yml
|
||||
```
|
||||
|
||||
### Method 3: Edit Defaults
|
||||
|
||||
Directly edit `roles/openclaw/defaults/main.yml` before running the playbook.
|
||||
|
||||
**Note**: This is not recommended for version control, use variables files instead.
|
||||
|
||||
## Available Variables
|
||||
|
||||
### User Configuration
|
||||
|
||||
#### `openclaw_user`
|
||||
- **Type**: String
|
||||
- **Default**: `openclaw`
|
||||
- **Description**: System user name for running OpenClaw
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_user=myuser
|
||||
```
|
||||
|
||||
#### `openclaw_home`
|
||||
- **Type**: String
|
||||
- **Default**: `/home/openclaw`
|
||||
- **Description**: Home directory for the openclaw user
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_home=/home/myuser
|
||||
```
|
||||
|
||||
#### `openclaw_ssh_keys`
|
||||
- **Type**: List of strings
|
||||
- **Default**: `[]` (empty)
|
||||
- **Description**: SSH public keys for accessing the openclaw user account
|
||||
- **Example**:
|
||||
```yaml
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx user@host"
|
||||
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB... admin@laptop"
|
||||
```
|
||||
```bash
|
||||
-e "openclaw_ssh_keys=['ssh-ed25519 AAAAC3... user@host']"
|
||||
```
|
||||
|
||||
### Installation Mode
|
||||
|
||||
#### `openclaw_install_mode`
|
||||
- **Type**: String (`release` or `development`)
|
||||
- **Default**: `release`
|
||||
- **Description**: Installation mode
|
||||
- `release`: Install via npm (`pnpm install -g openclaw@latest`)
|
||||
- `development`: Clone repo, build from source, symlink binary
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
### Development Mode Settings
|
||||
|
||||
These variables only apply when `openclaw_install_mode: development`
|
||||
|
||||
#### `openclaw_repo_url`
|
||||
- **Type**: String (Git URL)
|
||||
- **Default**: `https://github.com/openclaw/openclaw.git`
|
||||
- **Description**: Git repository URL to clone
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_repo_url=https://github.com/YOUR_USERNAME/openclaw.git
|
||||
```
|
||||
|
||||
#### `openclaw_repo_branch`
|
||||
- **Type**: String
|
||||
- **Default**: `main`
|
||||
- **Description**: Git branch to checkout
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_repo_branch=feature-branch
|
||||
```
|
||||
|
||||
#### `openclaw_code_dir`
|
||||
- **Type**: String (Path)
|
||||
- **Default**: `{{ openclaw_home }}/code`
|
||||
- **Description**: Directory where code repositories are stored
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_code_dir=/home/openclaw/projects
|
||||
```
|
||||
|
||||
#### `openclaw_repo_dir`
|
||||
- **Type**: String (Path)
|
||||
- **Default**: `{{ openclaw_code_dir }}/openclaw`
|
||||
- **Description**: Full path to openclaw repository
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_repo_dir=/home/openclaw/projects/openclaw
|
||||
```
|
||||
|
||||
### OpenClaw Settings
|
||||
|
||||
#### `openclaw_port`
|
||||
- **Type**: Integer
|
||||
- **Default**: `3000`
|
||||
- **Description**: Port for OpenClaw gateway (currently informational)
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_port=8080
|
||||
```
|
||||
|
||||
#### `openclaw_config_dir`
|
||||
- **Type**: String (Path)
|
||||
- **Default**: `{{ openclaw_home }}/.openclaw`
|
||||
- **Description**: OpenClaw configuration directory
|
||||
- **Example**:
|
||||
```bash
|
||||
-e openclaw_config_dir=/etc/openclaw
|
||||
```
|
||||
|
||||
### Node.js Configuration
|
||||
|
||||
#### `nodejs_version`
|
||||
- **Type**: String
|
||||
- **Default**: `22.x`
|
||||
- **Description**: Node.js major version to install
|
||||
- **Example**:
|
||||
```bash
|
||||
-e nodejs_version=20.x
|
||||
```
|
||||
|
||||
### Tailscale Configuration
|
||||
|
||||
#### `tailscale_authkey`
|
||||
- **Type**: String
|
||||
- **Default**: `""` (empty - manual setup required)
|
||||
- **Description**: Tailscale authentication key for automatic connection
|
||||
- **Example**:
|
||||
```bash
|
||||
-e tailscale_authkey=tskey-auth-k1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6
|
||||
```
|
||||
- **Get Key**: https://login.tailscale.com/admin/settings/keys
|
||||
|
||||
### OS-Specific Settings
|
||||
|
||||
These are automatically set based on the detected OS:
|
||||
|
||||
#### `homebrew_prefix`
|
||||
- **Type**: String (Path)
|
||||
- **Default**: `/opt/homebrew` (macOS) or `/home/linuxbrew/.linuxbrew` (Linux)
|
||||
- **Description**: Homebrew installation prefix
|
||||
- **Read-only**: Set automatically based on OS
|
||||
|
||||
#### `package_manager`
|
||||
- **Type**: String
|
||||
- **Default**: `brew` (macOS) or `apt` (Linux)
|
||||
- **Description**: System package manager
|
||||
- **Read-only**: Set automatically based on OS
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Basic Setup with SSH Keys
|
||||
|
||||
```yaml
|
||||
# vars.yml
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx user@desktop"
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHyyyyyyyy user@laptop"
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars.yml
|
||||
```
|
||||
|
||||
### Development Setup
|
||||
|
||||
```yaml
|
||||
# vars-dev.yml
|
||||
openclaw_install_mode: development
|
||||
openclaw_repo_url: "https://github.com/myorg/openclaw.git"
|
||||
openclaw_repo_branch: "develop"
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx dev@workstation"
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars-dev.yml
|
||||
```
|
||||
|
||||
### Production Setup with Tailscale
|
||||
|
||||
```yaml
|
||||
# vars-prod.yml
|
||||
openclaw_install_mode: release
|
||||
tailscale_authkey: "tskey-auth-k1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6"
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx admin@mgmt-server"
|
||||
nodejs_version: "22.x"
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars-prod.yml
|
||||
```
|
||||
|
||||
### Custom User and Directories
|
||||
|
||||
```yaml
|
||||
# vars-custom.yml
|
||||
openclaw_user: mybot
|
||||
openclaw_home: /opt/mybot
|
||||
openclaw_config_dir: /etc/mybot
|
||||
openclaw_code_dir: /opt/mybot/repositories
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars-custom.yml
|
||||
```
|
||||
|
||||
### Testing Different Branches
|
||||
|
||||
```yaml
|
||||
# vars-testing.yml
|
||||
openclaw_install_mode: development
|
||||
openclaw_repo_branch: "experimental-feature"
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxx tester@qa"
|
||||
```
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass -e @vars-testing.yml
|
||||
```
|
||||
|
||||
## Environment-Specific Configurations
|
||||
|
||||
### Development Environment
|
||||
|
||||
```yaml
|
||||
# environments/dev.yml
|
||||
openclaw_install_mode: development
|
||||
openclaw_repo_url: "https://github.com/openclaw/openclaw.git"
|
||||
openclaw_repo_branch: "main"
|
||||
openclaw_ssh_keys:
|
||||
- "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
|
||||
```
|
||||
|
||||
### Staging Environment
|
||||
|
||||
```yaml
|
||||
# environments/staging.yml
|
||||
openclaw_install_mode: release
|
||||
tailscale_authkey: "{{ lookup('env', 'TAILSCALE_AUTHKEY_STAGING') }}"
|
||||
openclaw_ssh_keys:
|
||||
- "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
|
||||
```
|
||||
|
||||
### Production Environment
|
||||
|
||||
```yaml
|
||||
# environments/prod.yml
|
||||
openclaw_install_mode: release
|
||||
tailscale_authkey: "{{ lookup('env', 'TAILSCALE_AUTHKEY_PROD') }}"
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-ed25519 AAAAC3... ops@prod-mgmt"
|
||||
- "ssh-ed25519 AAAAC3... admin@backup-server"
|
||||
nodejs_version: "22.x"
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### SSH Keys
|
||||
|
||||
1. **Use dedicated keys**: Create separate SSH keys for OpenClaw access
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/openclaw_ed25519 -C "openclaw-access"
|
||||
```
|
||||
|
||||
2. **Limit key permissions**: Use SSH key options to restrict access
|
||||
```
|
||||
from="192.168.1.0/24" ssh-ed25519 AAAAC3... admin@trusted-network
|
||||
```
|
||||
|
||||
3. **Rotate keys regularly**: Update SSH keys periodically
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e "openclaw_ssh_keys=['$(cat ~/.ssh/new_key.pub)']"
|
||||
```
|
||||
|
||||
### Tailscale Auth Keys
|
||||
|
||||
1. **Use ephemeral keys** for temporary access
|
||||
2. **Set expiration times** for auth keys
|
||||
3. **Use reusable keys** only for automation
|
||||
4. **Store in secrets manager**: Don't commit to git
|
||||
```bash
|
||||
# Use environment variable
|
||||
export TAILSCALE_AUTHKEY=$(vault read -field=key secret/tailscale)
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e tailscale_authkey="$TAILSCALE_AUTHKEY"
|
||||
```
|
||||
|
||||
### Sensitive Variables
|
||||
|
||||
Never commit sensitive data to git:
|
||||
|
||||
```yaml
|
||||
# ❌ BAD - Don't do this
|
||||
tailscale_authkey: "tskey-auth-actual-key-here"
|
||||
|
||||
# ✅ GOOD - Use environment variables or vault
|
||||
tailscale_authkey: "{{ lookup('env', 'TAILSCALE_AUTHKEY') }}"
|
||||
|
||||
# ✅ GOOD - Use Ansible Vault
|
||||
tailscale_authkey: "{{ vault_tailscale_authkey }}"
|
||||
```
|
||||
|
||||
Create encrypted vault:
|
||||
```bash
|
||||
ansible-vault create secrets.yml
|
||||
# Add: vault_tailscale_authkey: tskey-auth-xxxxx
|
||||
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e @secrets.yml --ask-vault-pass
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
After configuration, verify settings:
|
||||
|
||||
```bash
|
||||
# Check what variables will be used
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e @vars.yml --check --diff
|
||||
|
||||
# View all variables
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e @vars.yml -e "ansible_check_mode=true" \
|
||||
--tags never -vv
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SSH Keys Not Working
|
||||
|
||||
Check file ownership and permissions:
|
||||
```bash
|
||||
sudo ls -la /home/openclaw/.ssh/
|
||||
sudo cat /home/openclaw/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
### Tailscale Not Connecting
|
||||
|
||||
Verify auth key is valid:
|
||||
```bash
|
||||
sudo tailscale up --authkey=YOUR_KEY --verbose
|
||||
```
|
||||
|
||||
### Installation Mode Issues
|
||||
|
||||
Check which mode is active:
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e @vars.yml --check | grep "install_mode"
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Main README](../README.md)
|
||||
- [Development Mode Guide](development-mode.md)
|
||||
- [Upgrade Notes](../UPGRADE_NOTES.md)
|
||||
- [Defaults File](../roles/openclaw/defaults/main.yml)
|
||||
@@ -0,0 +1,431 @@
|
||||
# Development Mode Installation
|
||||
|
||||
This guide explains how to install OpenClaw in **development mode**, where the application is built from source instead of installed from npm.
|
||||
|
||||
## Overview
|
||||
|
||||
### Release Mode vs Development Mode
|
||||
|
||||
| Feature | Release Mode | Development Mode |
|
||||
|---------|-------------|------------------|
|
||||
| Source | npm registry | GitHub repository |
|
||||
| Installation | `pnpm install -g openclaw@latest` | `git clone` + `pnpm build` |
|
||||
| Location | `~/.local/share/pnpm/global/...` | `~/code/openclaw/` |
|
||||
| Binary | Global pnpm package | Symlink to `bin/openclaw.js` |
|
||||
| Updates | `pnpm install -g openclaw@latest` | `git pull` + `pnpm build` |
|
||||
| Use Case | Production, stable deployments | Development, testing, debugging |
|
||||
| Recommended For | End users | Developers, contributors |
|
||||
|
||||
## Installation
|
||||
|
||||
### Quick Install
|
||||
|
||||
```bash
|
||||
# Clone the ansible installer
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
|
||||
# Run in development mode
|
||||
./run-playbook.sh -e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
### Manual Install
|
||||
|
||||
```bash
|
||||
# Install ansible
|
||||
sudo apt update && sudo apt install -y ansible git
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
|
||||
# Install collections
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
# Run playbook with development mode
|
||||
ansible-playbook playbook.yml --ask-become-pass -e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
## What Gets Installed
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
/home/openclaw/
|
||||
├── .openclaw/ # Configuration directory
|
||||
│ ├── sessions/
|
||||
│ ├── credentials/
|
||||
│ ├── data/
|
||||
│ └── logs/
|
||||
├── .local/
|
||||
│ ├── bin/
|
||||
│ │ └── openclaw # Symlink -> ~/code/openclaw/bin/openclaw.js
|
||||
│ └── share/pnpm/
|
||||
└── code/
|
||||
└── openclaw/ # Git repository
|
||||
├── bin/
|
||||
│ └── openclaw.js
|
||||
├── dist/ # Built files
|
||||
├── src/ # Source code
|
||||
├── package.json
|
||||
└── pnpm-lock.yaml
|
||||
```
|
||||
|
||||
### Installation Steps
|
||||
|
||||
The Ansible playbook performs these steps:
|
||||
|
||||
1. **Create `~/code` directory**
|
||||
```bash
|
||||
mkdir -p ~/code
|
||||
```
|
||||
|
||||
2. **Clone repository**
|
||||
```bash
|
||||
cd ~/code
|
||||
git clone https://github.com/openclaw/openclaw.git
|
||||
```
|
||||
|
||||
3. **Install dependencies**
|
||||
```bash
|
||||
cd openclaw
|
||||
pnpm install
|
||||
```
|
||||
|
||||
4. **Build from source**
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
5. **Create symlink**
|
||||
```bash
|
||||
ln -sf ~/code/openclaw/bin/openclaw.js ~/.local/bin/openclaw
|
||||
chmod +x ~/code/openclaw/bin/openclaw.js
|
||||
```
|
||||
|
||||
6. **Add development aliases** to `.bashrc`:
|
||||
```bash
|
||||
alias openclaw-rebuild='cd ~/code/openclaw && pnpm build'
|
||||
alias openclaw-dev='cd ~/code/openclaw'
|
||||
alias openclaw-pull='cd ~/code/openclaw && git pull && pnpm install && pnpm build'
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Making Changes
|
||||
|
||||
```bash
|
||||
# 1. Navigate to repository
|
||||
openclaw-dev
|
||||
# or: cd ~/code/openclaw
|
||||
|
||||
# 2. Make your changes
|
||||
vim src/some-file.ts
|
||||
|
||||
# 3. Rebuild
|
||||
openclaw-rebuild
|
||||
# or: pnpm build
|
||||
|
||||
# 4. Test immediately
|
||||
openclaw --version
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
### Pulling Updates
|
||||
|
||||
```bash
|
||||
# Pull latest changes and rebuild
|
||||
openclaw-pull
|
||||
|
||||
# Or manually:
|
||||
cd ~/code/openclaw
|
||||
git pull
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Testing Changes
|
||||
|
||||
```bash
|
||||
# After rebuilding, the openclaw command uses the new code immediately
|
||||
openclaw status
|
||||
openclaw gateway
|
||||
|
||||
# View daemon logs
|
||||
openclaw logs
|
||||
```
|
||||
|
||||
### Switching Branches
|
||||
|
||||
```bash
|
||||
cd ~/code/openclaw
|
||||
|
||||
# Switch to feature branch
|
||||
git checkout feature-branch
|
||||
pnpm install
|
||||
pnpm build
|
||||
|
||||
# Switch back to main
|
||||
git checkout main
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Development Aliases
|
||||
|
||||
The following aliases are added to `.bashrc`:
|
||||
|
||||
| Alias | Command | Purpose |
|
||||
|-------|---------|---------|
|
||||
| `openclaw-dev` | `cd ~/code/openclaw` | Navigate to repo |
|
||||
| `openclaw-rebuild` | `cd ~/code/openclaw && pnpm build` | Rebuild after changes |
|
||||
| `openclaw-pull` | `cd ~/code/openclaw && git pull && pnpm install && pnpm build` | Update and rebuild |
|
||||
|
||||
Plus an environment variable:
|
||||
|
||||
```bash
|
||||
export OPENCLAW_DEV_DIR="$HOME/code/openclaw"
|
||||
```
|
||||
|
||||
## Configuration Variables
|
||||
|
||||
You can customize the development installation:
|
||||
|
||||
```yaml
|
||||
# In playbook or command line
|
||||
openclaw_install_mode: "development"
|
||||
openclaw_repo_url: "https://github.com/openclaw/openclaw.git"
|
||||
openclaw_repo_branch: "main"
|
||||
openclaw_code_dir: "/home/openclaw/code"
|
||||
openclaw_repo_dir: "/home/openclaw/code/openclaw"
|
||||
```
|
||||
|
||||
### Using a Fork
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e openclaw_install_mode=development \
|
||||
-e openclaw_repo_url=https://github.com/YOUR_USERNAME/openclaw.git \
|
||||
-e openclaw_repo_branch=your-feature-branch
|
||||
```
|
||||
|
||||
### Custom Location
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook.yml --ask-become-pass \
|
||||
-e openclaw_install_mode=development \
|
||||
-e openclaw_code_dir=/home/openclaw/projects
|
||||
```
|
||||
|
||||
## Switching Between Modes
|
||||
|
||||
### From Release to Development
|
||||
|
||||
```bash
|
||||
# Uninstall global package
|
||||
pnpm uninstall -g openclaw
|
||||
|
||||
# Run ansible in development mode
|
||||
ansible-playbook playbook.yml --ask-become-pass -e openclaw_install_mode=development
|
||||
```
|
||||
|
||||
### From Development to Release
|
||||
|
||||
```bash
|
||||
# Remove symlink
|
||||
rm ~/.local/bin/openclaw
|
||||
|
||||
# Remove repository (optional)
|
||||
rm -rf ~/code/openclaw
|
||||
|
||||
# Install from npm
|
||||
pnpm install -g openclaw@latest
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails
|
||||
|
||||
```bash
|
||||
cd ~/code/openclaw
|
||||
|
||||
# Check Node.js version (needs 22.x)
|
||||
node --version
|
||||
|
||||
# Clean install
|
||||
rm -rf node_modules
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Symlink Not Working
|
||||
|
||||
```bash
|
||||
# Check symlink
|
||||
ls -la ~/.local/bin/openclaw
|
||||
|
||||
# Recreate symlink
|
||||
rm ~/.local/bin/openclaw
|
||||
ln -sf ~/code/openclaw/bin/openclaw.js ~/.local/bin/openclaw
|
||||
chmod +x ~/code/openclaw/bin/openclaw.js
|
||||
```
|
||||
|
||||
### Command Not Found
|
||||
|
||||
```bash
|
||||
# Ensure ~/.local/bin is in PATH
|
||||
echo $PATH | grep -q ".local/bin" || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
### Git Issues
|
||||
|
||||
```bash
|
||||
cd ~/code/openclaw
|
||||
|
||||
# Reset to clean state
|
||||
git reset --hard origin/main
|
||||
git clean -fdx
|
||||
|
||||
# Rebuild
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Build Time
|
||||
|
||||
First build takes longer (~1-2 minutes depending on system):
|
||||
```bash
|
||||
pnpm install # Downloads dependencies
|
||||
pnpm build # Compiles TypeScript
|
||||
```
|
||||
|
||||
Subsequent rebuilds are faster (~10-30 seconds):
|
||||
```bash
|
||||
pnpm build # Only recompiles changed files
|
||||
```
|
||||
|
||||
### Disk Usage
|
||||
|
||||
Development mode uses more disk space:
|
||||
|
||||
- **Release mode**: ~150 MB (global pnpm cache)
|
||||
- **Development mode**: ~400 MB (repo + node_modules + dist)
|
||||
|
||||
### Memory Usage
|
||||
|
||||
No difference in runtime memory usage between modes.
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### Testing Before Merge
|
||||
|
||||
```bash
|
||||
# Test specific commit
|
||||
cd ~/code/openclaw
|
||||
git fetch origin pull/123/head:pr-123
|
||||
git checkout pr-123
|
||||
pnpm install
|
||||
pnpm build
|
||||
|
||||
# Test it
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
### Automated Testing
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# test-openclaw.sh
|
||||
|
||||
cd ~/code/openclaw
|
||||
git pull
|
||||
pnpm install
|
||||
pnpm build
|
||||
|
||||
# Run tests
|
||||
pnpm test
|
||||
|
||||
# Integration test
|
||||
openclaw doctor
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. ✅ **Always rebuild after code changes**
|
||||
```bash
|
||||
openclaw-rebuild
|
||||
```
|
||||
|
||||
2. ✅ **Test changes before committing**
|
||||
```bash
|
||||
pnpm build && openclaw doctor
|
||||
```
|
||||
|
||||
3. ✅ **Keep dependencies updated**
|
||||
```bash
|
||||
pnpm update
|
||||
pnpm build
|
||||
```
|
||||
|
||||
4. ✅ **Use feature branches**
|
||||
```bash
|
||||
git checkout -b feature/my-feature
|
||||
```
|
||||
|
||||
### Don't Do
|
||||
|
||||
- ❌ Editing code without rebuilding
|
||||
- ❌ Running `pnpm link` manually (breaks setup)
|
||||
- ❌ Installing global packages while in dev mode
|
||||
- ❌ Modifying symlink manually
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Repositories
|
||||
|
||||
You can have multiple clones:
|
||||
|
||||
```bash
|
||||
# Main development
|
||||
~/code/openclaw/ # main branch
|
||||
|
||||
# Experimental features
|
||||
~/code/openclaw-test/ # testing branch
|
||||
|
||||
# Switch binary symlink
|
||||
ln -sf ~/code/openclaw-test/bin/openclaw.js ~/.local/bin/openclaw
|
||||
```
|
||||
|
||||
### Custom Build Options
|
||||
|
||||
```bash
|
||||
cd ~/code/openclaw
|
||||
|
||||
# Development build (faster, includes source maps)
|
||||
NODE_ENV=development pnpm build
|
||||
|
||||
# Production build (optimized)
|
||||
NODE_ENV=production pnpm build
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# Run with debug output
|
||||
DEBUG=* openclaw gateway
|
||||
|
||||
# Or specific namespaces
|
||||
DEBUG=openclaw:* openclaw gateway
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [Main README](../README.md)
|
||||
- [Security Architecture](security.md)
|
||||
- [Troubleshooting Guide](troubleshooting.md)
|
||||
- [OpenClaw Repository](https://github.com/openclaw/openclaw)
|
||||
@@ -0,0 +1,268 @@
|
||||
---
|
||||
title: Installation Guide
|
||||
description: Detailed installation and configuration instructions
|
||||
---
|
||||
|
||||
# Installation Guide
|
||||
|
||||
## Quick Install
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/openclaw/openclaw-ansible/main/install.sh | bash
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y ansible git
|
||||
```
|
||||
|
||||
### Clone and Run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/openclaw/openclaw-ansible.git
|
||||
cd openclaw-ansible
|
||||
|
||||
# Install Ansible collections
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
# Run playbook
|
||||
ansible-playbook playbook.yml --ask-become-pass
|
||||
```
|
||||
|
||||
## Post-Installation
|
||||
|
||||
### 1. Connect to Tailscale
|
||||
|
||||
```bash
|
||||
# Interactive login
|
||||
sudo tailscale up
|
||||
|
||||
# Or with auth key for automation
|
||||
sudo tailscale up --authkey tskey-auth-xxxxx
|
||||
|
||||
# Check status
|
||||
sudo tailscale status
|
||||
```
|
||||
|
||||
Get auth keys from: https://login.tailscale.com/admin/settings/keys
|
||||
|
||||
### 2. Configure OpenClaw
|
||||
|
||||
```bash
|
||||
# Edit config
|
||||
sudo nano /home/openclaw/.openclaw/config.yml
|
||||
|
||||
# Key settings to configure:
|
||||
# - provider: whatsapp/telegram/signal
|
||||
# - phone: your number
|
||||
# - ai.provider: anthropic/openai
|
||||
# - ai.model: claude-3-5-sonnet-20241022
|
||||
```
|
||||
|
||||
### 3. Login to Provider
|
||||
|
||||
```bash
|
||||
# Login (will prompt for QR code or phone verification)
|
||||
sudo docker exec -it openclaw openclaw login
|
||||
|
||||
# Check connection
|
||||
sudo docker logs -f openclaw
|
||||
```
|
||||
|
||||
## Service Management
|
||||
|
||||
### Systemd Commands
|
||||
|
||||
```bash
|
||||
# Start/stop/restart
|
||||
sudo systemctl start openclaw
|
||||
sudo systemctl stop openclaw
|
||||
sudo systemctl restart openclaw
|
||||
|
||||
# View status
|
||||
sudo systemctl status openclaw
|
||||
|
||||
# Enable/disable auto-start
|
||||
sudo systemctl enable openclaw
|
||||
sudo systemctl disable openclaw
|
||||
```
|
||||
|
||||
### Docker Commands
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
sudo docker logs openclaw
|
||||
sudo docker logs -f openclaw # follow
|
||||
|
||||
# Shell access
|
||||
sudo docker exec -it openclaw bash
|
||||
|
||||
# Restart container
|
||||
sudo docker restart openclaw
|
||||
|
||||
# Check status
|
||||
sudo docker compose -f /opt/openclaw/docker-compose.yml ps
|
||||
```
|
||||
|
||||
### Firewall Management
|
||||
|
||||
```bash
|
||||
# View UFW status
|
||||
sudo ufw status verbose
|
||||
|
||||
# Add custom rule
|
||||
sudo ufw allow 8080/tcp comment 'Custom service'
|
||||
sudo ufw reload
|
||||
|
||||
# View Docker isolation
|
||||
sudo iptables -L DOCKER-USER -n -v
|
||||
```
|
||||
|
||||
## Accessing OpenClaw
|
||||
|
||||
OpenClaw's web interface runs on port 3000 (localhost only).
|
||||
|
||||
### Via Tailscale (Recommended)
|
||||
|
||||
```bash
|
||||
# After connecting Tailscale, browse to:
|
||||
http://TAILSCALE_IP:3000
|
||||
```
|
||||
|
||||
Wait, port 3000 is bound to localhost, so this won't work directly. Need to update the compose file or use SSH tunnel.
|
||||
|
||||
### Via SSH Tunnel
|
||||
|
||||
```bash
|
||||
ssh -L 3000:localhost:3000 user@server
|
||||
# Then browse to: http://localhost:3000
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Security Check
|
||||
|
||||
```bash
|
||||
# Check open ports (should show only SSH + Tailscale)
|
||||
sudo ss -tlnp
|
||||
|
||||
# External port scan (only port 22 should be open)
|
||||
nmap -p- YOUR_SERVER_IP
|
||||
|
||||
# Test container isolation
|
||||
sudo docker run -d -p 80:80 --name test-nginx nginx
|
||||
curl http://YOUR_SERVER_IP:80 # Should fail
|
||||
curl http://localhost:80 # Should work
|
||||
sudo docker rm -f test-nginx
|
||||
```
|
||||
|
||||
### UFW Status
|
||||
|
||||
```bash
|
||||
sudo ufw status verbose
|
||||
|
||||
# Expected output:
|
||||
# Status: active
|
||||
# To Action From
|
||||
# -- ------ ----
|
||||
# 22/tcp ALLOW IN Anywhere
|
||||
# 41641/udp ALLOW IN Anywhere
|
||||
```
|
||||
|
||||
### Tailscale Status
|
||||
|
||||
```bash
|
||||
sudo tailscale status
|
||||
|
||||
# Expected output:
|
||||
# 100.x.x.x hostname user@ linux -
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
# Stop services
|
||||
sudo systemctl stop openclaw
|
||||
sudo systemctl disable openclaw
|
||||
sudo tailscale down
|
||||
|
||||
# Remove containers and data
|
||||
sudo docker compose -f /opt/openclaw/docker-compose.yml down
|
||||
sudo rm -rf /opt/openclaw
|
||||
sudo rm -rf /home/openclaw/.openclaw
|
||||
sudo rm /etc/systemd/system/openclaw.service
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# Remove packages (optional)
|
||||
sudo apt remove --purge tailscale docker-ce docker-ce-cli containerd.io docker-compose-plugin nodejs
|
||||
|
||||
# Remove user (optional)
|
||||
sudo userdel -r openclaw
|
||||
|
||||
# Reset firewall (optional)
|
||||
sudo ufw disable
|
||||
sudo ufw --force reset
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom Port
|
||||
|
||||
Edit `/opt/openclaw/docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "127.0.0.1:3001:3000" # Change 3001 to desired port
|
||||
```
|
||||
|
||||
Then restart:
|
||||
```bash
|
||||
sudo systemctl restart openclaw
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add to `/opt/openclaw/docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ANTHROPIC_API_KEY=sk-ant-xxx
|
||||
- DEBUG=openclaw:*
|
||||
```
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
Add additional volumes in docker-compose.yml:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /home/openclaw/.openclaw:/home/openclaw/.openclaw
|
||||
- /path/to/custom:/custom
|
||||
```
|
||||
|
||||
## Automation
|
||||
|
||||
### Unattended Install
|
||||
|
||||
```bash
|
||||
# Set Tailscale auth key in playbook vars
|
||||
ansible-playbook playbook.yml \
|
||||
--ask-become-pass \
|
||||
-e "tailscale_authkey=tskey-auth-xxxxx"
|
||||
```
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
```yaml
|
||||
# Example GitHub Actions
|
||||
- name: Deploy OpenClaw
|
||||
run: |
|
||||
ansible-playbook playbook.yml \
|
||||
-e "tailscale_authkey=${{ secrets.TAILSCALE_KEY }}" \
|
||||
--become
|
||||
```
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: Security Architecture
|
||||
description: Firewall configuration, Docker isolation, and security hardening details
|
||||
---
|
||||
|
||||
# Security Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
This playbook implements a multi-layer defense strategy to secure OpenClaw installations.
|
||||
|
||||
## Security Layers
|
||||
|
||||
### Layer 1: UFW Firewall
|
||||
|
||||
```bash
|
||||
# Default policies
|
||||
Incoming: DENY
|
||||
Outgoing: ALLOW
|
||||
Routed: DENY
|
||||
|
||||
# Allowed
|
||||
SSH (22/tcp): ALLOW
|
||||
Tailscale (41641/udp): ALLOW
|
||||
```
|
||||
|
||||
### Layer 2: Fail2ban (SSH Protection)
|
||||
|
||||
Automatic protection against SSH brute-force attacks:
|
||||
|
||||
```bash
|
||||
# Configuration
|
||||
Max retries: 5 attempts
|
||||
Ban time: 1 hour (3600 seconds)
|
||||
Find time: 10 minutes (600 seconds)
|
||||
|
||||
# Check status
|
||||
sudo fail2ban-client status sshd
|
||||
|
||||
# Unban an IP
|
||||
sudo fail2ban-client set sshd unbanip IP_ADDRESS
|
||||
```
|
||||
|
||||
### Layer 3: DOCKER-USER Chain
|
||||
|
||||
Custom iptables chain that prevents Docker from bypassing UFW:
|
||||
|
||||
```
|
||||
*filter
|
||||
:DOCKER-USER - [0:0]
|
||||
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
-A DOCKER-USER -i lo -j ACCEPT
|
||||
-A DOCKER-USER -i <default_interface> -j DROP
|
||||
COMMIT
|
||||
```
|
||||
|
||||
**Result**: Even `docker run -p 80:80 nginx` won't expose port 80 externally.
|
||||
|
||||
### Layer 4: Localhost-Only Binding
|
||||
|
||||
All container ports bind to 127.0.0.1:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
```
|
||||
|
||||
### Layer 5: Non-Root Container
|
||||
|
||||
Container processes run as unprivileged `openclaw` user.
|
||||
|
||||
### Layer 6: Systemd Hardening
|
||||
|
||||
The openclaw service runs with security restrictions:
|
||||
|
||||
- `NoNewPrivileges=true` - Prevents privilege escalation
|
||||
- `PrivateTmp=true` - Isolated /tmp directory
|
||||
- `ProtectSystem=strict` - Read-only system directories
|
||||
- `ProtectHome=read-only` - Limited home directory access
|
||||
- `ReadWritePaths` - Only ~/.openclaw is writable
|
||||
|
||||
### Layer 7: Scoped Sudo Access
|
||||
|
||||
The openclaw user has limited sudo permissions (not full root):
|
||||
|
||||
```bash
|
||||
# Allowed commands only:
|
||||
- systemctl start/stop/restart/status openclaw
|
||||
- systemctl daemon-reload
|
||||
- tailscale commands
|
||||
- journalctl for openclaw logs
|
||||
```
|
||||
|
||||
### Layer 8: Automatic Security Updates
|
||||
|
||||
Unattended-upgrades is configured for automatic security patches:
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
sudo unattended-upgrade --dry-run
|
||||
|
||||
# View logs
|
||||
sudo cat /var/log/unattended-upgrades/unattended-upgrades.log
|
||||
```
|
||||
|
||||
**Note**: Automatic reboots are disabled. Monitor for pending reboots:
|
||||
```bash
|
||||
cat /var/run/reboot-required 2>/dev/null || echo "No reboot required"
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Check firewall
|
||||
sudo ufw status verbose
|
||||
|
||||
# Check fail2ban
|
||||
sudo fail2ban-client status
|
||||
|
||||
# Check Tailscale status
|
||||
sudo tailscale status
|
||||
|
||||
# Check Docker isolation
|
||||
sudo iptables -L DOCKER-USER -n -v
|
||||
|
||||
# Port scan from external machine (only SSH + Tailscale should be open)
|
||||
nmap -p- YOUR_SERVER_IP
|
||||
|
||||
# Test container isolation
|
||||
sudo docker run -d -p 80:80 --name test-nginx nginx
|
||||
curl http://YOUR_SERVER_IP:80 # Should fail/timeout
|
||||
curl http://localhost:80 # Should work
|
||||
sudo docker rm -f test-nginx
|
||||
|
||||
# Check unattended-upgrades
|
||||
sudo systemctl status unattended-upgrades
|
||||
```
|
||||
|
||||
## Tailscale Access
|
||||
|
||||
OpenClaw's web interface (port 3000) is bound to localhost. Access it via:
|
||||
|
||||
1. **SSH tunnel**:
|
||||
```bash
|
||||
ssh -L 3000:localhost:3000 user@server
|
||||
# Then browse to http://localhost:3000
|
||||
```
|
||||
|
||||
2. **Tailscale** (recommended):
|
||||
```bash
|
||||
# On server: already done by playbook
|
||||
sudo tailscale up
|
||||
|
||||
# From your machine:
|
||||
# Browse to http://TAILSCALE_IP:3000
|
||||
```
|
||||
|
||||
## Network Flow
|
||||
|
||||
```
|
||||
Internet → UFW (SSH only) → fail2ban → DOCKER-USER Chain → DROP
|
||||
Container → NAT → Internet (outbound allowed)
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### macOS Support
|
||||
- macOS firewall configuration is basic (Application Firewall only)
|
||||
- No fail2ban equivalent on macOS
|
||||
- Consider using Little Snitch or similar for enhanced macOS security
|
||||
|
||||
### IPv6
|
||||
- Docker IPv6 is disabled by default (`ip6tables: false` in daemon.json)
|
||||
- If your network uses IPv6, review and test firewall rules accordingly
|
||||
|
||||
### Installation Script
|
||||
- The `curl | bash` installation pattern has inherent risks
|
||||
- For high-security environments, clone the repository and audit before running
|
||||
- Consider using `--check` mode first: `ansible-playbook playbook.yml --check`
|
||||
|
||||
## Security Checklist
|
||||
|
||||
After installation, verify:
|
||||
|
||||
- [ ] `sudo ufw status` shows only SSH and Tailscale allowed
|
||||
- [ ] `sudo fail2ban-client status sshd` shows jail active
|
||||
- [ ] `sudo iptables -L DOCKER-USER -n` shows DROP rule
|
||||
- [ ] `nmap -p- YOUR_IP` from external shows only port 22
|
||||
- [ ] `docker run -p 80:80 nginx` + `curl YOUR_IP:80` times out
|
||||
- [ ] Tailscale access works for web UI
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
If you discover a security vulnerability, please report it privately:
|
||||
- OpenClaw: https://github.com/openclaw/openclaw/security
|
||||
- This installer: https://github.com/openclaw/openclaw-ansible/security
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: Common issues and solutions
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Container Can't Reach Internet
|
||||
|
||||
**Symptom**: OpenClaw can't connect to WhatsApp/Telegram
|
||||
|
||||
**Check**:
|
||||
```bash
|
||||
# Test from container
|
||||
sudo docker exec openclaw ping -c 3 8.8.8.8
|
||||
|
||||
# Check UFW allows outbound
|
||||
sudo ufw status verbose | grep OUT
|
||||
```
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Verify DOCKER-USER allows established connections
|
||||
sudo iptables -L DOCKER-USER -n -v
|
||||
|
||||
# Restart Docker + Firewall
|
||||
sudo systemctl restart docker
|
||||
sudo ufw reload
|
||||
sudo systemctl restart openclaw
|
||||
```
|
||||
|
||||
## Port Already in Use
|
||||
|
||||
**Symptom**: Port 3000 conflict
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Find what's using port 3000
|
||||
sudo ss -tlnp | grep 3000
|
||||
|
||||
# Change OpenClaw port
|
||||
sudo nano /opt/openclaw/docker-compose.yml
|
||||
# Change: "127.0.0.1:3001:3000"
|
||||
|
||||
sudo systemctl restart openclaw
|
||||
```
|
||||
|
||||
## Firewall Lockout
|
||||
|
||||
**Symptom**: Can't SSH after installation
|
||||
|
||||
**Solution** (via console/rescue mode):
|
||||
```bash
|
||||
# Disable UFW temporarily
|
||||
sudo ufw disable
|
||||
|
||||
# Check SSH rule exists
|
||||
sudo ufw status numbered
|
||||
|
||||
# Re-add SSH rule
|
||||
sudo ufw allow 22/tcp
|
||||
|
||||
# Re-enable
|
||||
sudo ufw enable
|
||||
```
|
||||
|
||||
## Container Won't Start
|
||||
|
||||
**Check logs**:
|
||||
```bash
|
||||
# Systemd logs
|
||||
sudo journalctl -u openclaw -n 50
|
||||
|
||||
# Docker logs
|
||||
sudo docker logs openclaw
|
||||
|
||||
# Compose status
|
||||
sudo docker compose -f /opt/openclaw/docker-compose.yml ps
|
||||
```
|
||||
|
||||
**Common fixes**:
|
||||
```bash
|
||||
# Rebuild image
|
||||
cd /opt/openclaw
|
||||
sudo docker compose build --no-cache
|
||||
sudo systemctl restart openclaw
|
||||
|
||||
# Check permissions
|
||||
sudo chown -R openclaw:openclaw /home/openclaw/.openclaw
|
||||
```
|
||||
|
||||
## Verify Docker Isolation
|
||||
|
||||
**Test that external ports are blocked**:
|
||||
```bash
|
||||
# Start test container
|
||||
sudo docker run -d -p 80:80 --name test-nginx nginx
|
||||
|
||||
# From EXTERNAL machine (should fail):
|
||||
curl http://YOUR_SERVER_IP:80
|
||||
|
||||
# From SERVER (should work):
|
||||
curl http://localhost:80
|
||||
|
||||
# Cleanup
|
||||
sudo docker rm -f test-nginx
|
||||
```
|
||||
|
||||
## UFW Status Shows Inactive
|
||||
|
||||
**Fix**:
|
||||
```bash
|
||||
# Enable UFW
|
||||
sudo ufw enable
|
||||
|
||||
# Reload rules
|
||||
sudo ufw reload
|
||||
|
||||
# Verify
|
||||
sudo ufw status verbose
|
||||
```
|
||||
## Ansible Playbook Fails
|
||||
|
||||
### Failed to set permissions on temporary files (Become Issue)
|
||||
|
||||
**Symptom**: `fatal: [host]: FAILED! => {"msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user..."}`
|
||||
|
||||
**Cause**: This happens when connecting as an unprivileged user (e.g., `ansible`) and using `become_user` to switch to another unprivileged user (e.g., `openclaw`). Ansible struggles to share temporary module files between them if the filesystem doesn't support POSIX ACLs.
|
||||
|
||||
**Solution**:
|
||||
Enable **Ansible Pipelining** in your `ansible.cfg`. This executes modules via stdin without creating temporary files.
|
||||
|
||||
```ini
|
||||
[defaults]
|
||||
pipelining = True
|
||||
```
|
||||
|
||||
Alternatively, if you cannot use pipelining, you can allow world-readable temporary files (less secure):
|
||||
```ini
|
||||
[defaults]
|
||||
allow_world_readable_tmpfiles = True
|
||||
```
|
||||
|
||||
### Collection missing
|
||||
...
|
||||
```bash
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
```
|
||||
|
||||
**Permission denied**:
|
||||
```bash
|
||||
# Run with --ask-become-pass
|
||||
ansible-playbook playbook.yml --ask-become-pass
|
||||
```
|
||||
|
||||
**Docker daemon not running**:
|
||||
```bash
|
||||
sudo systemctl start docker
|
||||
# Re-run playbook
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
namespace: openclaw
|
||||
name: installer
|
||||
version: 2.0.0
|
||||
readme: README.md
|
||||
|
||||
authors:
|
||||
- OpenClaw Contributors <https://github.com/openclaw>
|
||||
|
||||
description: Automated, hardened installation of OpenClaw with Docker and Tailscale VPN support for Debian/Ubuntu Linux.
|
||||
|
||||
license:
|
||||
- MIT
|
||||
|
||||
license_file: LICENSE
|
||||
|
||||
tags:
|
||||
- openclaw
|
||||
- docker
|
||||
- tailscale
|
||||
- vpn
|
||||
- firewall
|
||||
- security
|
||||
- automation
|
||||
- debian
|
||||
- ubuntu
|
||||
- messaging
|
||||
|
||||
dependencies:
|
||||
community.docker: ">=3.4.0"
|
||||
community.general: ">=8.0.0"
|
||||
ansible.posix: ">=1.5.0"
|
||||
|
||||
repository: https://github.com/openclaw/openclaw-ansible
|
||||
documentation: https://github.com/openclaw/openclaw-ansible/blob/main/README.md
|
||||
homepage: https://github.com/openclaw/openclaw
|
||||
issues: https://github.com/openclaw/openclaw-ansible/issues
|
||||
|
||||
build_ignore:
|
||||
- .git
|
||||
- .gitignore
|
||||
- .github
|
||||
- tests
|
||||
- '*.tar.gz'
|
||||
- install.sh
|
||||
- run-playbook.sh
|
||||
- CHANGELOG.md
|
||||
- AGENTS.md
|
||||
- GIT_COMMIT_MESSAGE.txt
|
||||
- RELEASE_NOTES_v2.0.0.md
|
||||
- UPGRADE_NOTES.md
|
||||
- COLLECTION_MIGRATION_PLAN.md
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
# Host-specific vars for zap [claw]
|
||||
|
||||
# ── VM provisioning ────────────────────────────────────────────────────────
|
||||
vm_domain: "zap [claw]"
|
||||
vm_hostname: zap
|
||||
vm_memory_mib: 3072
|
||||
vm_vcpus: 2
|
||||
vm_disk_path: /var/lib/libvirt/images/claw.qcow2
|
||||
vm_disk_size: "60G"
|
||||
vm_mac: "52:54:00:01:00:71"
|
||||
vm_ip: "192.168.122.182"
|
||||
vm_network: default
|
||||
vm_virtiofs_source: /home/will/lab/swarm
|
||||
vm_virtiofs_tag: swarm
|
||||
|
||||
# ── OpenClaw guest provisioning ────────────────────────────────────────────
|
||||
openclaw_install_mode: release
|
||||
|
||||
openclaw_ssh_keys:
|
||||
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC6l6Z3CBr0gU6tVMddCW1vjYk5CK8TExp/AViiUEJGADci/Dk26XnfmG0XjexIjD7L4a/V5hIh+0HEIwM146vcfRnB1lXty5BV6Rhum7J3qp7xXBPghqCC9tujc5KiMQZyCsLICFyhHOdqRoquUqbFeYL7cT+Vk+J+HSGXmXZvJGGSpW7b94wkGADkSTEn2u8FRpynU3vZ6KIIiBG+oreWl7LcBhlztZELlwiRx66HgW8t/DhJlL6mhfKJ6C0Sg7s98SwvsT+jJxsaip69SlXvAJhrun2oDvS+X+a/2u9LD6w8GazmkX6m626SqGEGdw21l+oJQf+2LphQ3h8gIScNg5LmhaxXFqo718nmKEi9aE1MNGU4HWsNLJGxXvPTZqTreyS81yKMiqSZKZ2WzwaCQO2VeRmHyuDgrlGUGcU9DFi9pEkkjiChp1PE7XNbIwTurUCC19WUHcijY1K/ZH9Ku8GXgWf0109QZpJKc/04dRlYNBgUBL7dCTxbC/UjIdDMmgdRmPZ4oDUqUyBMsIEu8Wsx2snaUh4E2i5m0Vrd4Yy0+Eiu5YZBZt2IsljFE+c0KGSZMOyoCJksmqlTfvC0Ejt/bVsNhbZDgVB2K3sxRYa9Sa6I9nlCm7bSZC94vILVKkDsivmi+sj9dTV8mlJhA/yaGsBOokbjYYAa2cgQyw== will@squareffect.com"
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# OpenClaw Ansible Installer
|
||||
# This script installs Ansible if needed and runs the OpenClaw playbook via Ansible Galaxy
|
||||
|
||||
# Enable 256 colors
|
||||
export TERM=xterm-256color
|
||||
|
||||
# Force color support
|
||||
if [ -z "$COLORTERM" ]; then
|
||||
export COLORTERM=truecolor
|
||||
fi
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}╔════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ OpenClaw Ansible Installer ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Detect operating system
|
||||
if command -v apt-get &> /dev/null; then
|
||||
echo -e "${GREEN}✓ Detected: Debian/Ubuntu Linux${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Error: Unsupported operating system${NC}"
|
||||
echo -e "${RED} This installer supports: Debian/Ubuntu Linux only${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if running as root or with sudo access
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo -e "${GREEN}Running as root.${NC}"
|
||||
SUDO=""
|
||||
ANSIBLE_EXTRA_VARS="-e ansible_become=false"
|
||||
else
|
||||
if ! command -v sudo &> /dev/null; then
|
||||
echo -e "${RED}Error: sudo is not installed. Please install sudo or run as root.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
SUDO="sudo"
|
||||
ANSIBLE_EXTRA_VARS="--ask-become-pass"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}[1/3] Checking prerequisites...${NC}"
|
||||
|
||||
# Check if Ansible is installed
|
||||
if ! command -v ansible-playbook &> /dev/null; then
|
||||
echo -e "${YELLOW}Ansible not found. Installing Ansible and git...${NC}"
|
||||
$SUDO apt-get update -qq
|
||||
$SUDO apt-get install -y ansible git
|
||||
echo -e "${GREEN}✓ Ansible and git installed${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Ansible already installed${NC}"
|
||||
if ! command -v git &> /dev/null; then
|
||||
echo -e "${YELLOW}git not found. Installing...${NC}"
|
||||
$SUDO apt-get update -qq
|
||||
$SUDO apt-get install -y git
|
||||
echo -e "${GREEN}✓ git installed${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ git already installed${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}[2/3] Installing OpenClaw collection...${NC}"
|
||||
|
||||
# Create temporary requirements file
|
||||
REQUIREMENTS_FILE=$(mktemp)
|
||||
cat > "$REQUIREMENTS_FILE" << EOF
|
||||
---
|
||||
collections:
|
||||
- name: https://github.com/openclaw/openclaw-ansible.git
|
||||
type: git
|
||||
version: main
|
||||
EOF
|
||||
|
||||
# Install collection
|
||||
ansible-galaxy collection install -r "$REQUIREMENTS_FILE" --force
|
||||
|
||||
echo -e "${GREEN}✓ Collection installed${NC}"
|
||||
|
||||
echo -e "${GREEN}[3/3] Running Ansible playbook...${NC}"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${YELLOW}You will be prompted for your sudo password.${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Run the playbook
|
||||
ansible-playbook openclaw.installer.install $ANSIBLE_EXTRA_VARS "$@"
|
||||
|
||||
# Cleanup
|
||||
rm -f "$REQUIREMENTS_FILE"
|
||||
@@ -0,0 +1,25 @@
|
||||
# OpenClaw Ansible Inventory Sample
|
||||
|
||||
all:
|
||||
children:
|
||||
openclaw_servers:
|
||||
hosts:
|
||||
# Example 1: Simple IP address
|
||||
192.168.1.100:
|
||||
|
||||
# Example 2: Hostname with specific variables
|
||||
my-claw-server:
|
||||
ansible_host: 192.168.1.101
|
||||
ansible_user: admin
|
||||
# Override default variables for this host
|
||||
openclaw_install_mode: release
|
||||
|
||||
# Global variables for all OpenClaw servers
|
||||
vars:
|
||||
# SSH Public Keys for the 'openclaw' user (Optional)
|
||||
# If set, these keys will be added to ~/.ssh/authorized_keys
|
||||
# openclaw_ssh_keys:
|
||||
# - "ssh-ed25519 AAAAC3Nz..."
|
||||
|
||||
# Tailscale Auth Key (Optional) - Leave empty to skip auto-connect
|
||||
# tailscale_authkey: "tskey-auth-..."
|
||||
@@ -0,0 +1,8 @@
|
||||
all:
|
||||
children:
|
||||
openclaw_servers:
|
||||
hosts:
|
||||
zap:
|
||||
ansible_host: 192.168.122.182
|
||||
ansible_user: root
|
||||
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
|
||||
@@ -0,0 +1,2 @@
|
||||
---
|
||||
requires_ansible: '>=2.14.0'
|
||||
@@ -0,0 +1,2 @@
|
||||
---
|
||||
- ansible.builtin.import_playbook: playbooks/install.yml
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
# Post-provisioning customizations for OpenClaw VMs
|
||||
# Run after playbooks/install.yml to apply host-specific tweaks
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.yml playbooks/customize.yml
|
||||
# ansible-playbook -i inventory.yml playbooks/customize.yml --limit zap
|
||||
|
||||
- name: OpenClaw VM customizations
|
||||
hosts: openclaw_servers
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Set vm.swappiness=10 (live)
|
||||
ansible.posix.sysctl:
|
||||
name: vm.swappiness
|
||||
value: "10"
|
||||
state: present
|
||||
reload: true
|
||||
|
||||
- name: Persist vm.swappiness in /etc/sysctl.conf
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/sysctl.conf
|
||||
regexp: '^vm\.swappiness'
|
||||
line: 'vm.swappiness=10'
|
||||
state: present
|
||||
|
||||
- name: Create virtiofs mount point
|
||||
ansible.builtin.file:
|
||||
path: /mnt/swarm
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Mount virtiofs swarm share via fstab
|
||||
ansible.posix.mount:
|
||||
path: /mnt/swarm
|
||||
src: swarm
|
||||
fstype: virtiofs
|
||||
opts: defaults
|
||||
state: present
|
||||
# Note: actual mount requires reboot after VM config update
|
||||
|
||||
- name: Ensure openclaw user lingering is enabled (for user systemd services)
|
||||
ansible.builtin.command:
|
||||
cmd: loginctl enable-linger openclaw
|
||||
changed_when: false
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
- name: Deploy OpenClaw to remote servers
|
||||
hosts: openclaw_servers
|
||||
become: true
|
||||
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
pre_tasks:
|
||||
- name: Detect operating system
|
||||
ansible.builtin.set_fact:
|
||||
is_linux: "{{ ansible_system == 'Linux' }}"
|
||||
is_debian_family: "{{ ansible_os_family == 'Debian' }}"
|
||||
is_supported_distro: "{{ ansible_distribution in ['Debian', 'Ubuntu'] }}"
|
||||
|
||||
- name: Fail on unsupported non-Linux systems
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Unsupported operating system: {{ ansible_system }}.
|
||||
This installer supports Linux only.
|
||||
when: not is_linux
|
||||
|
||||
- name: Fail on unsupported Linux distribution
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Unsupported Linux distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}.
|
||||
This installer currently supports Debian and Ubuntu.
|
||||
when:
|
||||
- is_linux
|
||||
- not is_supported_distro
|
||||
|
||||
- name: Install ACL for privilege escalation
|
||||
ansible.builtin.package:
|
||||
name: acl
|
||||
state: present
|
||||
when: is_supported_distro
|
||||
|
||||
roles:
|
||||
- openclaw
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
- name: Install OpenClaw with Docker and UFW firewall
|
||||
hosts: localhost
|
||||
connection: local
|
||||
become: true
|
||||
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
environment:
|
||||
TERM: xterm-256color
|
||||
COLORTERM: truecolor
|
||||
|
||||
pre_tasks:
|
||||
- name: Enable color terminal for current session
|
||||
ansible.builtin.set_fact:
|
||||
ansible_env: "{{ ansible_env | combine({'TERM': 'xterm-256color', 'COLORTERM': 'truecolor'}) }}"
|
||||
|
||||
- name: Detect operating system
|
||||
ansible.builtin.set_fact:
|
||||
is_linux: "{{ ansible_system == 'Linux' }}"
|
||||
is_debian_family: "{{ ansible_os_family == 'Debian' }}"
|
||||
is_supported_distro: "{{ ansible_distribution in ['Debian', 'Ubuntu'] }}"
|
||||
|
||||
- name: Fail on unsupported non-Linux systems
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Unsupported operating system: {{ ansible_system }}.
|
||||
This installer supports Linux only.
|
||||
when: not is_linux
|
||||
|
||||
- name: Fail on unsupported macOS
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
macOS bare-metal support has been deprecated and disabled.
|
||||
Please use a Linux VM or container instead.
|
||||
See README.md for details.
|
||||
when: ansible_os_family == 'Darwin'
|
||||
|
||||
- name: Fail on unsupported Linux distribution
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Unsupported Linux distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}.
|
||||
This installer currently supports Debian and Ubuntu.
|
||||
when:
|
||||
- is_linux
|
||||
- not is_supported_distro
|
||||
|
||||
- name: Display detected OS
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
Detected OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
|
||||
OS Family: {{ ansible_os_family }}
|
||||
Linux: {{ is_linux }}
|
||||
Debian family: {{ is_debian_family }}
|
||||
Supported distro: {{ is_supported_distro }}
|
||||
|
||||
- name: Update apt cache and upgrade all packages (Debian/Ubuntu)
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
upgrade: dist
|
||||
cache_valid_time: 3600
|
||||
when: is_debian_family and not ci_test
|
||||
register: apt_upgrade_result
|
||||
|
||||
- name: Display apt upgrade results
|
||||
ansible.builtin.debug:
|
||||
msg: "✅ System packages updated and upgraded"
|
||||
when: is_debian_family and apt_upgrade_result.changed
|
||||
|
||||
- name: Install ACL for privilege escalation
|
||||
ansible.builtin.package:
|
||||
name: acl
|
||||
state: present
|
||||
when: is_supported_distro
|
||||
|
||||
- name: Check if running as root
|
||||
ansible.builtin.command: id -u
|
||||
register: user_id
|
||||
changed_when: false
|
||||
become: false
|
||||
|
||||
- name: Set fact for root user
|
||||
ansible.builtin.set_fact:
|
||||
is_root: "{{ user_id.stdout == '0' }}"
|
||||
|
||||
roles:
|
||||
- openclaw
|
||||
|
||||
post_tasks:
|
||||
- name: Copy ASCII art script
|
||||
ansible.builtin.template:
|
||||
src: "{{ playbook_dir }}/../roles/openclaw/templates/show-lobster.sh.j2"
|
||||
dest: /tmp/show-lobster.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Display ASCII art
|
||||
ansible.builtin.command: /tmp/show-lobster.sh
|
||||
changed_when: false
|
||||
|
||||
- name: Create one-time welcome message for openclaw user
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ openclaw_home }}/.openclaw-welcome"
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: '0644'
|
||||
content: |
|
||||
echo ""
|
||||
echo "╔════════════════════════════════════════════════════════╗"
|
||||
echo "║ 📋 OpenClaw Setup - Next Steps ║"
|
||||
echo "╚════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "You are: $(whoami)@$(hostname)"
|
||||
echo "Home: $HOME"
|
||||
echo "OS: $(uname -s) $(uname -r)"
|
||||
echo ""
|
||||
echo "Environment is configured:"
|
||||
echo " ✓ XDG_RUNTIME_DIR: ${XDG_RUNTIME_DIR:-not set}"
|
||||
echo " ✓ DBUS_SESSION_BUS_ADDRESS: ${DBUS_SESSION_BUS_ADDRESS:-not set}"
|
||||
echo " ✓ OpenClaw: $(openclaw --version 2>/dev/null || echo 'not found')"
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo "🚀 Quick Start - Run This Command:"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo " openclaw onboard --install-daemon"
|
||||
echo ""
|
||||
echo "This will:"
|
||||
echo " • Guide you through the setup wizard"
|
||||
echo " • Configure your messaging provider"
|
||||
echo " • Install and start the daemon service"
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo "📚 Alternative Manual Setup:"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo "1️⃣ Interactive onboarding (recommended):"
|
||||
echo " openclaw onboard --install-daemon"
|
||||
echo ""
|
||||
echo "2️⃣ Manual configuration:"
|
||||
echo " openclaw configure"
|
||||
echo " nano ~/.openclaw/openclaw.json"
|
||||
echo ""
|
||||
echo "3️⃣ Login to messaging provider:"
|
||||
echo " openclaw providers login"
|
||||
echo ""
|
||||
echo "4️⃣ Test the gateway:"
|
||||
echo " openclaw gateway"
|
||||
echo ""
|
||||
echo "5️⃣ Install as daemon (if not using onboard):"
|
||||
echo " openclaw daemon install"
|
||||
echo " openclaw daemon start"
|
||||
echo ""
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo "🔧 Useful Commands:"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo " • View logs: openclaw logs"
|
||||
echo " • Check status: openclaw status"
|
||||
echo " • Stop daemon: openclaw daemon stop"
|
||||
echo " • Restart daemon: openclaw daemon restart"
|
||||
echo " • Troubleshoot: openclaw doctor"
|
||||
echo " • List agents: openclaw agents list"
|
||||
echo ""
|
||||
{% if tailscale_enabled | default(false) %}echo "────────────────────────────────────────────────────────"
|
||||
echo "🌐 Connect Tailscale VPN (optional):"
|
||||
echo "────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo " exit"
|
||||
echo " sudo tailscale up"
|
||||
echo ""
|
||||
{% endif %}echo "────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo "Type 'exit' to return to your previous user"
|
||||
echo ""
|
||||
# Remove welcome message (suppress errors if already deleted)
|
||||
rm -f "$HOME/.openclaw-welcome" 2>/dev/null || true
|
||||
|
||||
- name: Add welcome message to .bashrc
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ openclaw_home }}/.bashrc"
|
||||
line: '[ -f ~/.openclaw-welcome ] && source ~/.openclaw-welcome'
|
||||
state: present
|
||||
insertafter: EOF
|
||||
|
||||
- name: Notify that playbook is complete
|
||||
ansible.builtin.debug:
|
||||
msg: "✅ OpenClaw installation complete!"
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
# Provision a new OpenClaw VM from scratch on the hypervisor host.
|
||||
#
|
||||
# This playbook runs on localhost (the hypervisor) and:
|
||||
# 1. Downloads the Ubuntu cloud image (cached)
|
||||
# 2. Creates the VM disk image
|
||||
# 3. Builds a cloud-init seed ISO for first-boot configuration
|
||||
# 4. Defines the VM XML (EFI, memfd, virtiofs, TPM, watchdog)
|
||||
# 5. Configures a static DHCP reservation
|
||||
# 6. Enables autostart and starts the VM
|
||||
# 7. Waits for SSH
|
||||
#
|
||||
# After this playbook completes, run:
|
||||
# ansible-playbook -i inventory.yml playbooks/install.yml --limit <instance>
|
||||
# ansible-playbook -i inventory.yml playbooks/customize.yml --limit <instance>
|
||||
# ~/lab/swarm/restore-openclaw-vm.sh <instance> # to restore config from backup
|
||||
#
|
||||
# Usage:
|
||||
# ansible-playbook -i inventory.yml playbooks/provision-vm.yml --limit zap
|
||||
|
||||
- name: Provision OpenClaw VM
|
||||
hosts: openclaw_servers
|
||||
connection: local
|
||||
become: true
|
||||
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
roles:
|
||||
- vm
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
collections:
|
||||
- name: community.docker
|
||||
version: ">=3.4.0"
|
||||
- name: community.general
|
||||
version: ">=8.0.0"
|
||||
- name: ansible.posix
|
||||
version: ">=1.5.0"
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
# OpenClaw default variables
|
||||
|
||||
# CI testing mode - skips tasks that require systemd, Docker-in-Docker, or kernel access
|
||||
ci_test: false
|
||||
|
||||
# Tailscale settings
|
||||
# WARNING: Tasks using tailscale_authkey MUST set no_log: true to prevent credential exposure
|
||||
tailscale_enabled: false # Set to true to install and configure Tailscale
|
||||
tailscale_authkey: "" # Optional: set to auto-connect during installation
|
||||
|
||||
# Node.js version
|
||||
nodejs_version: "22.x"
|
||||
|
||||
# OpenClaw settings
|
||||
openclaw_port: 3000
|
||||
|
||||
# OpenClaw config directory
|
||||
openclaw_config_dir: "{{ openclaw_home }}/.openclaw"
|
||||
|
||||
# User settings (will be created as system user)
|
||||
openclaw_user: openclaw
|
||||
openclaw_home: /home/openclaw
|
||||
|
||||
# Installation mode: 'release' or 'development'
|
||||
# release: Install via pnpm install -g openclaw@latest
|
||||
# development: Clone repo, build from source, link globally
|
||||
openclaw_install_mode: "release"
|
||||
|
||||
# Development mode settings (only used when openclaw_install_mode: development)
|
||||
openclaw_repo_url: "https://github.com/openclaw/openclaw.git"
|
||||
openclaw_repo_branch: "main"
|
||||
openclaw_code_dir: "{{ openclaw_home }}/code"
|
||||
openclaw_repo_dir: "{{ openclaw_code_dir }}/openclaw"
|
||||
|
||||
# SSH keys for openclaw user
|
||||
# Add your public SSH keys here to allow SSH access as openclaw user
|
||||
# Example:
|
||||
# openclaw_ssh_keys:
|
||||
# - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx user@host"
|
||||
# - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDxxxxxxxxxxxxxxxxxxxxxxx user@host"
|
||||
openclaw_ssh_keys: []
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Enable 256 colors
|
||||
export TERM=xterm-256color
|
||||
export COLORTERM=truecolor
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# OpenClaw ASCII Art Lobster
|
||||
cat << 'LOBSTER'
|
||||
[0;36m
|
||||
+====================================================+
|
||||
| |
|
||||
| [0;33mWelcome to OpenClaw! [0;31m🦞[0;36m |
|
||||
| |
|
||||
|[0;31m ,.---._ [0;36m|
|
||||
|[0;31m ,,,, / `, [0;36m|
|
||||
|[0;31m \\\\\\ / '\_ ; [0;36m|
|
||||
|[0;31m |||| /\/``-.__\;' [0;36m|
|
||||
|[0;31m ::::/\/_ [0;36m|
|
||||
|[0;31m {{`-.__.-'(`(^^(^^^(^ 9 `.=========' [0;36m|
|
||||
|[0;31m{{{{{{ { ( ( ( ( (-----:= [0;36m|
|
||||
|[0;31m {{.-'~~'-.(,(,,(,,,(__6_.'=========. [0;36m|
|
||||
|[0;31m ::::\/\ [0;36m|
|
||||
|[0;31m |||| \/\ ,-'/, [0;36m|
|
||||
|[0;31m //// \ `` _/ ; [0;36m|
|
||||
|[0;31m '''' \ ` .' [0;36m|
|
||||
|[0;31m `---' [0;36m|
|
||||
| |
|
||||
| [0;32m✅ Installation Successful![0;36m |
|
||||
| |
|
||||
+====================================================+[0m
|
||||
LOBSTER
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}🔒 Security Status:${NC}"
|
||||
echo " - UFW Firewall: ENABLED"
|
||||
echo " - Open Ports: SSH (22) + Tailscale (41641/udp)"
|
||||
echo " - Docker isolation: ACTIVE"
|
||||
echo ""
|
||||
echo -e "📚 Documentation: ${GREEN}https://docs.openclaw.ai${NC}"
|
||||
echo ""
|
||||
|
||||
# Switch to openclaw user for setup
|
||||
echo -e "${YELLOW}Switching to openclaw user for setup...${NC}"
|
||||
echo ""
|
||||
echo "DEBUG: About to create init script..."
|
||||
|
||||
# Create init script that will be sourced on login
|
||||
cat > /home/openclaw/.openclaw-init << 'INIT_EOF'
|
||||
# Display welcome message
|
||||
echo "============================================"
|
||||
echo "📋 OpenClaw Setup - Next Steps"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "You are now: $(whoami)@$(hostname)"
|
||||
echo "Home: $HOME"
|
||||
echo ""
|
||||
echo "🔧 Setup Commands:"
|
||||
echo ""
|
||||
echo "1. Configure OpenClaw:"
|
||||
echo " nano ~/.openclaw/config.yml"
|
||||
echo ""
|
||||
echo "2. Login to provider (WhatsApp/Telegram/Signal):"
|
||||
echo " openclaw login"
|
||||
echo ""
|
||||
echo "3. Test gateway:"
|
||||
echo " openclaw gateway"
|
||||
echo ""
|
||||
echo "4. Exit and manage as service:"
|
||||
echo " exit"
|
||||
echo " sudo systemctl status openclaw"
|
||||
echo " sudo journalctl -u openclaw -f"
|
||||
echo ""
|
||||
echo "5. Connect Tailscale (as root):"
|
||||
echo " exit"
|
||||
echo " sudo tailscale up"
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Type 'exit' to return to previous user"
|
||||
echo ""
|
||||
|
||||
# Remove this init file after first login
|
||||
rm -f ~/.openclaw-init
|
||||
INIT_EOF
|
||||
|
||||
chown openclaw:openclaw /home/openclaw/.openclaw-init
|
||||
|
||||
# Add one-time sourcing to .bashrc if not already there
|
||||
grep -q '.openclaw-init' /home/openclaw/.bashrc 2>/dev/null || {
|
||||
echo '' >> /home/openclaw/.bashrc
|
||||
echo '# One-time setup message' >> /home/openclaw/.bashrc
|
||||
echo '[ -f ~/.openclaw-init ] && source ~/.openclaw-init' >> /home/openclaw/.bashrc
|
||||
}
|
||||
|
||||
# Switch to openclaw user with explicit interactive shell
|
||||
# Using setsid to create new session + force pseudo-terminal allocation
|
||||
exec sudo -i -u openclaw /bin/bash --login
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
cat << 'LOBSTER'
|
||||
[0;36m
|
||||
+====================================================+
|
||||
| |
|
||||
| [0;33mWelcome to OpenClaw! [0;31m🦞[0;36m |
|
||||
| |
|
||||
|[0;31m ,.---._ [0;36m|
|
||||
|[0;31m ,,,, / `, [0;36m|
|
||||
|[0;31m \\\\\\ / '\_ ; [0;36m|
|
||||
|[0;31m |||| /\/``-.__\;' [0;36m|
|
||||
|[0;31m ::::/\/_ [0;36m|
|
||||
|[0;31m {{`-.__.-'(`(^^(^^^(^ 9 `.=========' [0;36m|
|
||||
|[0;31m{{{{{{ { ( ( ( ( (-----:= [0;36m|
|
||||
|[0;31m {{.-'~~'-.(,(,,(,,,(__6_.'=========. [0;36m|
|
||||
|[0;31m ::::\/\ [0;36m|
|
||||
|[0;31m |||| \/\ ,-'/, [0;36m|
|
||||
|[0;31m //// \ `` _/ ; [0;36m|
|
||||
|[0;31m '''' \ ` .' [0;36m|
|
||||
|[0;31m `---' [0;36m|
|
||||
| |
|
||||
| [0;32m✅ Installation Successful![0;36m |
|
||||
| |
|
||||
+====================================================+[0m
|
||||
LOBSTER
|
||||
|
||||
echo ""
|
||||
echo "🔒 Security Status:"
|
||||
echo " - UFW Firewall: ENABLED"
|
||||
echo " - Open Ports: SSH (22) + Tailscale (41641/udp)"
|
||||
echo " - Docker isolation: ACTIVE"
|
||||
echo ""
|
||||
echo "📚 Documentation: https://docs.openclaw.ai"
|
||||
echo ""
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: Restart docker
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: restarted
|
||||
|
||||
- name: Restart fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: restarted
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
# Linux-specific Docker installation (apt-based)
|
||||
|
||||
- name: Install required system packages for Docker
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- lsb-release
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Create directory for Docker GPG key
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Add Docker GPG key
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
curl -fsSL https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg | \
|
||||
gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
creates: /etc/apt/keyrings/docker.gpg
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Add Docker repository
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
echo "deb [arch=$(dpkg --print-architecture) \
|
||||
signed-by=/etc/apt/keyrings/docker.gpg] \
|
||||
https://download.docker.com/linux/{{ ansible_distribution | lower }} \
|
||||
$(lsb_release -cs) stable" | \
|
||||
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
creates: /etc/apt/sources.list.d/docker.list
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Update apt cache after adding Docker repo
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
- name: Install Docker CE
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-buildx-plugin
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
|
||||
- name: Ensure Docker service is started and enabled
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
- name: Add user to docker group
|
||||
ansible.builtin.user:
|
||||
name: "{{ openclaw_user }}"
|
||||
groups: docker
|
||||
append: true
|
||||
|
||||
- name: Reset SSH connection to apply docker group
|
||||
ansible.builtin.meta: reset_connection
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
# Linux-specific firewall configuration (UFW) with security hardening
|
||||
|
||||
# Install and configure fail2ban for SSH brute-force protection
|
||||
- name: Install fail2ban
|
||||
ansible.builtin.apt:
|
||||
name: fail2ban
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Configure fail2ban for SSH protection
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/fail2ban/jail.local
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
content: |
|
||||
# OpenClaw security hardening - SSH protection
|
||||
[DEFAULT]
|
||||
bantime = 3600
|
||||
findtime = 600
|
||||
maxretry = 5
|
||||
backend = systemd
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
filter = sshd
|
||||
# logpath not needed - systemd backend reads from journal
|
||||
notify: Restart fail2ban
|
||||
|
||||
- name: Enable and start fail2ban
|
||||
ansible.builtin.systemd:
|
||||
name: fail2ban
|
||||
state: started
|
||||
enabled: true
|
||||
|
||||
# Install and configure unattended-upgrades for automatic security updates
|
||||
- name: Install unattended-upgrades
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- unattended-upgrades
|
||||
- apt-listchanges
|
||||
state: present
|
||||
|
||||
- name: Configure automatic security updates
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
content: |
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
||||
|
||||
- name: Configure unattended-upgrades to only install security updates
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/apt.conf.d/50unattended-upgrades
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
content: |
|
||||
// OpenClaw security hardening - automatic security updates
|
||||
Unattended-Upgrade::Allowed-Origins {
|
||||
"${distro_id}:${distro_codename}-security";
|
||||
"${distro_id}ESMApps:${distro_codename}-apps-security";
|
||||
"${distro_id}ESM:${distro_codename}-infra-security";
|
||||
};
|
||||
Unattended-Upgrade::Package-Blacklist {
|
||||
};
|
||||
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||||
Unattended-Upgrade::MinimalSteps "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
|
||||
# UFW Firewall configuration
|
||||
- name: Install UFW
|
||||
ansible.builtin.apt:
|
||||
name: ufw
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Set UFW default policies
|
||||
community.general.ufw:
|
||||
direction: "{{ item.direction }}"
|
||||
policy: "{{ item.policy }}"
|
||||
loop:
|
||||
- { direction: 'incoming', policy: 'deny' }
|
||||
- { direction: 'outgoing', policy: 'allow' }
|
||||
- { direction: 'routed', policy: 'deny' }
|
||||
|
||||
- name: Allow SSH on port 22
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: '22'
|
||||
proto: tcp
|
||||
comment: 'SSH'
|
||||
|
||||
- name: Allow Tailscale UDP port 41641
|
||||
community.general.ufw:
|
||||
rule: allow
|
||||
port: '41641'
|
||||
proto: udp
|
||||
comment: 'Tailscale'
|
||||
when: tailscale_enabled | bool
|
||||
|
||||
- name: Get default network interface
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
ip route | grep default | awk '{print $5}' | head -n1
|
||||
executable: /bin/bash
|
||||
register: default_interface
|
||||
changed_when: false
|
||||
|
||||
- name: Validate default network interface was detected
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- default_interface.stdout is defined
|
||||
- default_interface.stdout | length > 0
|
||||
fail_msg: "Failed to detect default network interface. Cannot configure firewall rules safely."
|
||||
success_msg: "Default network interface detected: {{ default_interface.stdout }}"
|
||||
|
||||
- name: Create UFW after.rules for Docker isolation
|
||||
ansible.builtin.blockinfile:
|
||||
path: /etc/ufw/after.rules
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK - Docker isolation"
|
||||
insertbefore: "^COMMIT$"
|
||||
block: |
|
||||
# Docker port isolation - block all forwarded traffic by default
|
||||
:DOCKER-USER - [0:0]
|
||||
|
||||
# Allow established connections
|
||||
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
|
||||
# Allow localhost
|
||||
-A DOCKER-USER -i lo -j ACCEPT
|
||||
|
||||
# Block all other forwarded traffic to Docker containers from external interface
|
||||
-A DOCKER-USER -i {{ default_interface.stdout }} -j DROP
|
||||
create: false
|
||||
|
||||
- name: Create Docker daemon config directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/docker
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure Docker daemon to work with UFW
|
||||
ansible.builtin.template:
|
||||
src: daemon.json.j2
|
||||
dest: /etc/docker/daemon.json
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify: Restart docker
|
||||
|
||||
- name: Enable UFW
|
||||
community.general.ufw:
|
||||
state: enabled
|
||||
|
||||
- name: Reload UFW
|
||||
community.general.ufw:
|
||||
state: reloaded
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
- name: Include system tools installation tasks
|
||||
ansible.builtin.include_tasks: system-tools.yml
|
||||
|
||||
- name: Include Tailscale installation tasks
|
||||
ansible.builtin.include_tasks: tailscale-linux.yml
|
||||
when: tailscale_enabled | bool
|
||||
|
||||
- name: Include user creation tasks
|
||||
ansible.builtin.include_tasks: user.yml
|
||||
|
||||
- name: Include Docker installation tasks
|
||||
ansible.builtin.include_tasks: docker-linux.yml
|
||||
when: not ci_test
|
||||
|
||||
- name: Include firewall configuration tasks
|
||||
ansible.builtin.include_tasks: firewall-linux.yml
|
||||
when: not ci_test
|
||||
|
||||
- name: Include Node.js installation tasks
|
||||
ansible.builtin.include_tasks: nodejs.yml
|
||||
|
||||
- name: Include OpenClaw setup tasks
|
||||
ansible.builtin.include_tasks: openclaw.yml
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
- name: Install required packages for Node.js
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
state: present
|
||||
|
||||
- name: Create directory for NodeSource GPG key
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Add NodeSource GPG key
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
|
||||
gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
chmod a+r /etc/apt/keyrings/nodesource.gpg
|
||||
creates: /etc/apt/keyrings/nodesource.gpg
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Add NodeSource repository
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] \
|
||||
https://deb.nodesource.com/node_{{ nodejs_version }} nodistro main" | \
|
||||
tee /etc/apt/sources.list.d/nodesource.list > /dev/null
|
||||
creates: /etc/apt/sources.list.d/nodesource.list
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Update apt cache after adding NodeSource repo
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
- name: Install Node.js
|
||||
ansible.builtin.apt:
|
||||
name: nodejs
|
||||
state: present
|
||||
|
||||
- name: Check if pnpm is already installed
|
||||
ansible.builtin.command: pnpm --version
|
||||
register: pnpm_check
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Install pnpm globally
|
||||
ansible.builtin.command: npm install -g pnpm
|
||||
when: pnpm_check.rc != 0
|
||||
|
||||
- name: Verify Node.js installation
|
||||
ansible.builtin.command: node --version
|
||||
register: node_version
|
||||
changed_when: false
|
||||
|
||||
- name: Verify pnpm installation
|
||||
ansible.builtin.command: pnpm --version
|
||||
register: pnpm_version
|
||||
changed_when: false
|
||||
|
||||
- name: Display Node.js and pnpm versions
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Node.js version: {{ node_version.stdout }}"
|
||||
- "pnpm version: {{ pnpm_version.stdout }}"
|
||||
@@ -0,0 +1,215 @@
|
||||
---
|
||||
# Development mode installation - Clone repo, build from source, link globally
|
||||
|
||||
- name: Create code directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ openclaw_code_dir }}"
|
||||
state: directory
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Check if openclaw repository already exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ openclaw_repo_dir }}/.git"
|
||||
register: openclaw_repo_exists
|
||||
|
||||
- name: Clone openclaw repository
|
||||
ansible.builtin.git:
|
||||
repo: "{{ openclaw_repo_url }}"
|
||||
dest: "{{ openclaw_repo_dir }}"
|
||||
version: "{{ openclaw_repo_branch }}"
|
||||
update: true
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
when: not openclaw_repo_exists.stat.exists
|
||||
|
||||
- name: Pull latest changes if repo exists
|
||||
ansible.builtin.git:
|
||||
repo: "{{ openclaw_repo_url }}"
|
||||
dest: "{{ openclaw_repo_dir }}"
|
||||
version: "{{ openclaw_repo_branch }}"
|
||||
update: true
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
when: openclaw_repo_exists.stat.exists
|
||||
register: git_pull_result
|
||||
|
||||
- name: Display git pull status
|
||||
ansible.builtin.debug:
|
||||
msg: "Git repository updated: {{ git_pull_result.changed | default(false) }}"
|
||||
when: openclaw_repo_exists.stat.exists
|
||||
|
||||
- name: Install dependencies with pnpm
|
||||
ansible.builtin.shell:
|
||||
cmd: pnpm install
|
||||
chdir: "{{ openclaw_repo_dir }}"
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
environment:
|
||||
PNPM_HOME: "{{ openclaw_home }}/.local/share/pnpm"
|
||||
PATH: "{{ openclaw_home }}/.local/bin:{{ openclaw_home }}/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin"
|
||||
HOME: "{{ openclaw_home }}"
|
||||
register: pnpm_install_result
|
||||
changed_when: "'Already up to date' not in pnpm_install_result.stdout"
|
||||
|
||||
- name: Build openclaw from source
|
||||
ansible.builtin.shell:
|
||||
cmd: pnpm build
|
||||
chdir: "{{ openclaw_repo_dir }}"
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
environment:
|
||||
PNPM_HOME: "{{ openclaw_home }}/.local/share/pnpm"
|
||||
PATH: "{{ openclaw_home }}/.local/bin:{{ openclaw_home }}/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin"
|
||||
HOME: "{{ openclaw_home }}"
|
||||
register: pnpm_build_result
|
||||
changed_when: true # Build always changes dist/ directory
|
||||
|
||||
- name: Display build output
|
||||
ansible.builtin.debug:
|
||||
msg: "Build completed successfully"
|
||||
when: pnpm_build_result.rc == 0
|
||||
|
||||
- name: Check if dist directory exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ openclaw_repo_dir }}/dist"
|
||||
register: dist_dir
|
||||
|
||||
- name: Fail if build didn't create dist directory
|
||||
ansible.builtin.fail:
|
||||
msg: "Build failed - dist directory not found"
|
||||
when: not dist_dir.stat.exists
|
||||
|
||||
- name: Check for package metadata
|
||||
ansible.builtin.stat:
|
||||
path: "{{ openclaw_repo_dir }}/package.json"
|
||||
register: openclaw_package_json_stat
|
||||
|
||||
- name: Read package metadata
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ openclaw_repo_dir }}/package.json"
|
||||
register: openclaw_package_json_raw
|
||||
when: openclaw_package_json_stat.stat.exists
|
||||
|
||||
- name: Parse package metadata
|
||||
ansible.builtin.set_fact:
|
||||
openclaw_package_json: "{{ openclaw_package_json_raw.content | b64decode | from_json }}"
|
||||
when: openclaw_package_json_stat.stat.exists
|
||||
|
||||
- name: Resolve metadata-defined CLI entrypoint
|
||||
ansible.builtin.set_fact:
|
||||
openclaw_package_bin_entry: >-
|
||||
{{
|
||||
openclaw_package_json.bin
|
||||
if openclaw_package_json.bin is string else
|
||||
(
|
||||
openclaw_package_json.bin.openclaw
|
||||
if openclaw_package_json.bin is mapping and 'openclaw' in openclaw_package_json.bin else
|
||||
(
|
||||
(openclaw_package_json.bin | dict2items | map(attribute='value') | list | first)
|
||||
if openclaw_package_json.bin is mapping else
|
||||
''
|
||||
)
|
||||
)
|
||||
}}
|
||||
when: openclaw_package_json_stat.stat.exists
|
||||
|
||||
- name: Build CLI entrypoint candidate list
|
||||
ansible.builtin.set_fact:
|
||||
openclaw_cli_candidate_paths: >-
|
||||
{{
|
||||
[
|
||||
openclaw_package_bin_entry | default(''),
|
||||
'openclaw.mjs',
|
||||
'bin/openclaw.js',
|
||||
'dist/index.js'
|
||||
] | reject('equalto', '') | unique | list
|
||||
}}
|
||||
|
||||
- name: Check possible OpenClaw CLI entrypoints
|
||||
ansible.builtin.stat:
|
||||
path: "{{ openclaw_repo_dir }}/{{ item }}"
|
||||
loop: "{{ openclaw_cli_candidate_paths }}"
|
||||
register: openclaw_cli_candidates
|
||||
|
||||
- name: Resolve OpenClaw CLI entrypoint path
|
||||
ansible.builtin.set_fact:
|
||||
openclaw_cli_entry: >-
|
||||
{{
|
||||
(
|
||||
openclaw_cli_candidates.results
|
||||
| selectattr('stat.exists')
|
||||
| map(attribute='stat.path')
|
||||
| list
|
||||
| first
|
||||
) | default('')
|
||||
}}
|
||||
changed_when: false
|
||||
|
||||
- name: Fail if openclaw CLI entrypoint is missing
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Unable to locate OpenClaw CLI entrypoint in {{ openclaw_repo_dir }}.
|
||||
Expected one of: {{ openclaw_cli_candidate_paths | join(', ') }}
|
||||
when: openclaw_cli_entry == ""
|
||||
|
||||
- name: Remove existing global openclaw symlink (if any)
|
||||
ansible.builtin.file:
|
||||
path: "{{ openclaw_home }}/.local/bin/openclaw"
|
||||
state: absent
|
||||
|
||||
- name: Create symlink to openclaw binary
|
||||
ansible.builtin.file:
|
||||
src: "{{ openclaw_cli_entry }}"
|
||||
dest: "{{ openclaw_home }}/.local/bin/openclaw"
|
||||
state: link
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
force: true
|
||||
|
||||
- name: Make openclaw binary executable
|
||||
ansible.builtin.file:
|
||||
path: "{{ openclaw_cli_entry }}"
|
||||
mode: '0755'
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
|
||||
- name: Verify openclaw installation from development build
|
||||
ansible.builtin.shell:
|
||||
cmd: openclaw --version
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
environment:
|
||||
PNPM_HOME: "{{ openclaw_home }}/.local/share/pnpm"
|
||||
PATH: "{{ openclaw_home }}/.local/bin:{{ openclaw_home }}/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin"
|
||||
HOME: "{{ openclaw_home }}"
|
||||
register: openclaw_dev_version
|
||||
changed_when: false
|
||||
|
||||
- name: Display installed OpenClaw version (development build)
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
OpenClaw installed from source: {{ openclaw_dev_version.stdout }}
|
||||
Repository: {{ openclaw_repo_dir }}
|
||||
Binary: {{ openclaw_home }}/.local/bin/openclaw -> {{ openclaw_cli_entry }}
|
||||
|
||||
- name: Add development mode info to .bashrc
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ openclaw_home }}/.bashrc"
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK - OpenClaw development"
|
||||
block: |
|
||||
# OpenClaw development mode
|
||||
export OPENCLAW_DEV_DIR="{{ openclaw_repo_dir }}"
|
||||
|
||||
# Aliases for development
|
||||
alias openclaw-rebuild='cd {{ openclaw_repo_dir }} && pnpm build'
|
||||
alias openclaw-dev='cd {{ openclaw_repo_dir }}'
|
||||
alias openclaw-pull='cd {{ openclaw_repo_dir }} && git pull && pnpm install && pnpm build'
|
||||
create: true
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: '0644'
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Release mode installation - Install via pnpm from npm registry
|
||||
|
||||
- name: Install OpenClaw globally as openclaw user (using pnpm)
|
||||
ansible.builtin.shell:
|
||||
cmd: pnpm install -g openclaw@latest
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
environment:
|
||||
PNPM_HOME: "{{ openclaw_home }}/.local/share/pnpm"
|
||||
PATH: "{{ openclaw_home }}/.local/bin:{{ openclaw_home }}/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin"
|
||||
HOME: "{{ openclaw_home }}"
|
||||
register: openclaw_install
|
||||
changed_when: "'Already up to date' not in openclaw_install.stdout"
|
||||
|
||||
- name: Verify openclaw installation
|
||||
ansible.builtin.shell:
|
||||
cmd: openclaw --version
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
environment:
|
||||
PNPM_HOME: "{{ openclaw_home }}/.local/share/pnpm"
|
||||
PATH: "{{ openclaw_home }}/.local/bin:{{ openclaw_home }}/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin"
|
||||
HOME: "{{ openclaw_home }}"
|
||||
register: openclaw_version
|
||||
changed_when: false
|
||||
|
||||
- name: Display installed OpenClaw version (release)
|
||||
ansible.builtin.debug:
|
||||
msg: "OpenClaw installed from npm: {{ openclaw_version.stdout }}"
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
- name: Validate openclaw_install_mode
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- openclaw_install_mode in ["release", "development"]
|
||||
fail_msg: "Invalid openclaw_install_mode: '{{ openclaw_install_mode }}'. Must be 'release' or 'development'."
|
||||
success_msg: "Valid install mode: {{ openclaw_install_mode }}"
|
||||
|
||||
- name: Ensure openclaw home directory exists with correct ownership
|
||||
ansible.builtin.file:
|
||||
path: "{{ openclaw_home }}"
|
||||
state: directory
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: '0755'
|
||||
|
||||
- name: Create OpenClaw directories (structure only, no config files)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { path: "{{ openclaw_config_dir }}", mode: '0755' }
|
||||
- { path: "{{ openclaw_config_dir }}/sessions", mode: '0755' }
|
||||
- { path: "{{ openclaw_config_dir }}/credentials", mode: '0700' }
|
||||
- { path: "{{ openclaw_config_dir }}/data", mode: '0755' }
|
||||
- { path: "{{ openclaw_config_dir }}/logs", mode: '0755' }
|
||||
- { path: "{{ openclaw_config_dir }}/agents", mode: '0755' }
|
||||
- { path: "{{ openclaw_config_dir }}/agents/main", mode: '0755' }
|
||||
- { path: "{{ openclaw_config_dir }}/agents/main/agent", mode: '0700' }
|
||||
- { path: "{{ openclaw_config_dir }}/workspace", mode: '0755' }
|
||||
|
||||
- name: Create pnpm directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ openclaw_home }}/.local/share/pnpm"
|
||||
- "{{ openclaw_home }}/.local/share/pnpm/store"
|
||||
- "{{ openclaw_home }}/.local/bin"
|
||||
|
||||
- name: Ensure pnpm directories have correct ownership
|
||||
ansible.builtin.file:
|
||||
path: "{{ openclaw_home }}/.local/share/pnpm"
|
||||
state: directory
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
recurse: true
|
||||
mode: '0755'
|
||||
|
||||
- name: Configure pnpm for openclaw user
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
CURRENT_GLOBAL_DIR=$(pnpm config get global-dir 2>/dev/null || echo "")
|
||||
CURRENT_BIN_DIR=$(pnpm config get global-bin-dir 2>/dev/null || echo "")
|
||||
CHANGED=0
|
||||
if [ "$CURRENT_GLOBAL_DIR" != "{{ openclaw_home }}/.local/share/pnpm" ]; then
|
||||
pnpm config set global-dir {{ openclaw_home }}/.local/share/pnpm
|
||||
CHANGED=1
|
||||
fi
|
||||
if [ "$CURRENT_BIN_DIR" != "{{ openclaw_home }}/.local/bin" ]; then
|
||||
pnpm config set global-bin-dir {{ openclaw_home }}/.local/bin
|
||||
CHANGED=1
|
||||
fi
|
||||
exit $CHANGED
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: "{{ openclaw_user }}"
|
||||
register: pnpm_config_result
|
||||
changed_when: pnpm_config_result.rc == 1
|
||||
failed_when: pnpm_config_result.rc > 1
|
||||
|
||||
- name: Display installation mode
|
||||
ansible.builtin.debug:
|
||||
msg: "Installation mode: {{ openclaw_install_mode }}"
|
||||
|
||||
# Include appropriate installation method based on mode
|
||||
- name: Include release installation (pnpm install -g)
|
||||
ansible.builtin.include_tasks: openclaw-release.yml
|
||||
when: openclaw_install_mode == "release"
|
||||
|
||||
- name: Include development installation (git clone + build + link)
|
||||
ansible.builtin.include_tasks: openclaw-development.yml
|
||||
when: openclaw_install_mode == "development"
|
||||
|
||||
- name: Configure .bashrc for openclaw user (base config)
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ openclaw_home }}/.bashrc"
|
||||
marker: "# {mark} ANSIBLE MANAGED BLOCK - OpenClaw pnpm"
|
||||
block: |
|
||||
# pnpm configuration
|
||||
export PNPM_HOME="{{ openclaw_home }}/.local/share/pnpm"
|
||||
export PATH="{{ openclaw_home }}/.local/bin:$PNPM_HOME:$PATH"
|
||||
create: true
|
||||
owner: "{{ openclaw_user }}"
|
||||
group: "{{ openclaw_user }}"
|
||||
mode: '0644'
|
||||
insertafter: EOF
|
||||
|
||||
# NOTE: We do NOT create config.yml here - openclaw onboard/configure will do that
|
||||
# We also do NOT install the systemd service - openclaw onboard --install-daemon will do that
|
||||
# The .openclaw directory structure is created above, but config and daemon are user-initiated
|
||||
|
||||
- name: Display configuration note
|
||||
ansible.builtin.debug:
|
||||
msg: |
|
||||
OpenClaw is installed but NOT configured yet.
|
||||
|
||||
Next steps (run as openclaw user):
|
||||
1. Switch user: sudo su - {{ openclaw_user }}
|
||||
2. Run onboarding: openclaw onboard --install-daemon
|
||||
|
||||
This will:
|
||||
- Create configuration files (~/.openclaw/openclaw.json)
|
||||
- Guide you through provider setup
|
||||
- Install and start the daemon service automatically
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
# Linux-specific system tools installation (apt-based)
|
||||
|
||||
- name: Install essential system tools (Linux - apt)
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
# Editors
|
||||
- vim
|
||||
- nano
|
||||
# Version control
|
||||
- git
|
||||
- git-lfs
|
||||
# Network tools
|
||||
- curl
|
||||
- wget
|
||||
- netcat-openbsd
|
||||
- net-tools
|
||||
- dnsutils
|
||||
- iputils-ping
|
||||
- traceroute
|
||||
- tcpdump
|
||||
- nmap
|
||||
- socat
|
||||
- telnet
|
||||
# Debugging tools
|
||||
- strace
|
||||
- lsof
|
||||
- gdb
|
||||
- htop
|
||||
- iotop
|
||||
- iftop
|
||||
- sysstat
|
||||
- procps
|
||||
# System utilities
|
||||
- tmux
|
||||
- tree
|
||||
- jq
|
||||
- unzip
|
||||
- rsync
|
||||
- less
|
||||
# Build essentials for development
|
||||
- build-essential
|
||||
- file
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Deploy global vim configuration (Linux)
|
||||
ansible.builtin.template:
|
||||
src: vimrc.j2
|
||||
dest: /etc/vim/vimrc.local
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
# Main system tools orchestration - Linux only
|
||||
|
||||
- name: Include Linux system tools installation
|
||||
ansible.builtin.include_tasks: system-tools-linux.yml
|
||||
|
||||
# Common tasks for all operating systems
|
||||
|
||||
- name: Configure git globally
|
||||
community.general.git_config:
|
||||
name: "{{ item.name }}"
|
||||
scope: global
|
||||
value: "{{ item.value }}"
|
||||
loop:
|
||||
- { name: 'init.defaultBranch', value: 'main' }
|
||||
- { name: 'pull.rebase', value: 'false' }
|
||||
- { name: 'core.editor', value: 'vim' }
|
||||
- { name: 'color.ui', value: 'auto' }
|
||||
- { name: 'alias.st', value: 'status' }
|
||||
- { name: 'alias.co', value: 'checkout' }
|
||||
- { name: 'alias.br', value: 'branch' }
|
||||
- { name: 'alias.ci', value: 'commit' }
|
||||
- { name: 'alias.unstage', value: 'reset HEAD --' }
|
||||
- { name: 'alias.last', value: 'log -1 HEAD' }
|
||||
- { name: 'alias.lg', value: 'log --oneline --graph --decorate --all' }
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
# Linux-specific Tailscale installation (Debian/Ubuntu)
|
||||
|
||||
- name: Add Tailscale GPG key
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
DIST="{{ ansible_distribution | lower }}"
|
||||
RELEASE="{{ ansible_distribution_release }}"
|
||||
curl -fsSL "https://pkgs.tailscale.com/stable/${DIST}/${RELEASE}.noarmor.gpg" | \
|
||||
tee /usr/share/keyrings/tailscale-archive-keyring.gpg > /dev/null
|
||||
creates: /usr/share/keyrings/tailscale-archive-keyring.gpg
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Add Tailscale repository
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
set -o pipefail
|
||||
DIST="{{ ansible_distribution | lower }}"
|
||||
RELEASE="{{ ansible_distribution_release }}"
|
||||
curl -fsSL "https://pkgs.tailscale.com/stable/${DIST}/${RELEASE}.tailscale-keyring.list" | \
|
||||
tee /etc/apt/sources.list.d/tailscale.list > /dev/null
|
||||
creates: /etc/apt/sources.list.d/tailscale.list
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Update apt cache after adding Tailscale repo
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
- name: Install Tailscale
|
||||
ansible.builtin.apt:
|
||||
name: tailscale
|
||||
state: present
|
||||
|
||||
- name: Enable Tailscale service (Linux)
|
||||
ansible.builtin.systemd:
|
||||
name: tailscaled
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Check if Tailscale is already connected (Linux)
|
||||
ansible.builtin.command: tailscale status --json
|
||||
register: tailscale_status_linux
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Display Tailscale auth URL if not connected (Linux)
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "============================================"
|
||||
- "Tailscale installed but not connected yet"
|
||||
- "============================================"
|
||||
- ""
|
||||
- "To connect this machine to your Tailnet:"
|
||||
- "Run: sudo tailscale up"
|
||||
- ""
|
||||
- "For unattended installation, use an auth key:"
|
||||
- "sudo tailscale up --authkey tskey-auth-xxxxx"
|
||||
- ""
|
||||
- "Get auth key from: https://login.tailscale.com/admin/settings/keys"
|
||||
when: tailscale_status_linux.rc != 0
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
- 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
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"iptables": true,
|
||||
"ip-forward": true,
|
||||
"userland-proxy": false,
|
||||
"live-restore": true,
|
||||
"ip6tables": false,
|
||||
"log-driver": "json-file",
|
||||
"log-opts": {
|
||||
"max-size": "10m",
|
||||
"max-file": "3"
|
||||
},
|
||||
"default-address-pools": [
|
||||
{
|
||||
"base": "172.17.0.0/12",
|
||||
"size": 24
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# OpenClaw Configuration Template
|
||||
# Generated by Ansible on {{ ansible_date_time.iso8601 }}
|
||||
#
|
||||
# For full documentation, visit: https://docs.openclaw.ai/configuration
|
||||
|
||||
# Connection Provider
|
||||
# Options: whatsapp, telegram, signal
|
||||
provider: whatsapp
|
||||
|
||||
# WhatsApp Configuration (if using whatsapp provider)
|
||||
whatsapp:
|
||||
# Phone number in international format (e.g., +4366412345678)
|
||||
phone: ""
|
||||
|
||||
# Telegram Configuration (if using telegram provider)
|
||||
telegram:
|
||||
# Telegram bot token from @BotFather
|
||||
token: ""
|
||||
|
||||
# Signal Configuration (if using signal provider)
|
||||
signal:
|
||||
# Signal phone number
|
||||
phone: ""
|
||||
|
||||
# AI Model Configuration
|
||||
ai:
|
||||
# Model provider: anthropic, openai
|
||||
provider: anthropic
|
||||
|
||||
# API Keys (set as environment variables or here)
|
||||
# anthropic_api_key: ""
|
||||
# openai_api_key: ""
|
||||
|
||||
# Model selection
|
||||
model: claude-3-5-sonnet-20241022
|
||||
|
||||
# Max tokens per response
|
||||
max_tokens: 4096
|
||||
|
||||
# Gateway Settings
|
||||
gateway:
|
||||
# Port for web interface
|
||||
port: {{ openclaw_port }}
|
||||
|
||||
# Enable web UI
|
||||
web_ui: true
|
||||
|
||||
# Logging
|
||||
logging:
|
||||
# Log level: debug, info, warn, error
|
||||
level: info
|
||||
|
||||
# Log file location
|
||||
file: {{ openclaw_config_dir }}/openclaw.log
|
||||
|
||||
# Security
|
||||
security:
|
||||
# Allowed phone numbers (whitelist)
|
||||
# Leave empty to allow all
|
||||
allowed_numbers: []
|
||||
|
||||
# Rate limiting
|
||||
rate_limit:
|
||||
enabled: true
|
||||
max_requests_per_minute: 10
|
||||
|
||||
# Advanced Settings
|
||||
advanced:
|
||||
# Session timeout in minutes
|
||||
session_timeout: 60
|
||||
|
||||
# Auto-reconnect on disconnect
|
||||
auto_reconnect: true
|
||||
|
||||
# Keep-alive interval in seconds
|
||||
keep_alive_interval: 30
|
||||
@@ -0,0 +1,42 @@
|
||||
[Unit]
|
||||
Description=OpenClaw AI Gateway
|
||||
After=network.target docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User={{ openclaw_user }}
|
||||
Group={{ openclaw_user }}
|
||||
WorkingDirectory={{ openclaw_home }}
|
||||
|
||||
# Environment variables
|
||||
Environment="PNPM_HOME={{ openclaw_home }}/.local/share/pnpm"
|
||||
Environment="PATH={{ openclaw_home }}/.local/bin:{{ openclaw_home }}/.local/share/pnpm:/usr/local/bin:/usr/bin:/bin"
|
||||
Environment="HOME={{ openclaw_home }}"
|
||||
Environment="XDG_RUNTIME_DIR=/run/user/{{ openclaw_uid_value }}"
|
||||
|
||||
# DBus session bus
|
||||
Environment="DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{{ openclaw_uid_value }}/bus"
|
||||
|
||||
# Start command
|
||||
ExecStart=openclaw gateway
|
||||
|
||||
# Restart policy
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=read-only
|
||||
ReadWritePaths={{ openclaw_home }}/.openclaw
|
||||
ReadWritePaths={{ openclaw_home }}/.local
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=openclaw
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,42 @@
|
||||
#jinja2: lstrip_blocks: True
|
||||
{% raw %}#!/bin/bash
|
||||
cat << 'LOBSTER'
|
||||
[0;36m
|
||||
+====================================================+
|
||||
| |
|
||||
| [0;33mWelcome to OpenClaw! [0;31m🦞[0;36m |
|
||||
| |
|
||||
|[0;31m ,.---._ [0;36m|
|
||||
|[0;31m ,,,, / `, [0;36m|
|
||||
|[0;31m \\\ / '\_ ; [0;36m|
|
||||
|[0;31m |||| /\/``-.__\;' [0;36m|
|
||||
|[0;31m ::::/\/_ [0;36m|
|
||||
|[0;31m {{`-.__.-'(`(^^(^^^(^ 9 `.=========' [0;36m|
|
||||
|[0;31m{{{{{{ { ( ( ( ( (-----:= [0;36m|
|
||||
|[0;31m {{.-'~~'-.(,(,,(,,,(__6_.'=========. [0;36m|
|
||||
|[0;31m ::::\/\ [0;36m|
|
||||
|[0;31m |||| \/\ ,-'/, [0;36m|
|
||||
|[0;31m //// \ `` _/ ; [0;36m|
|
||||
|[0;31m '''' \ ` .' [0;36m|
|
||||
|[0;31m `---' [0;36m|
|
||||
| |
|
||||
| [0;32m✅ Installation Successful![0;36m |
|
||||
| |
|
||||
+====================================================+[0m
|
||||
LOBSTER
|
||||
|
||||
echo ""
|
||||
echo "🔒 Security Status:"
|
||||
echo " - UFW Firewall: ENABLED"
|
||||
{% endraw %}
|
||||
{% if tailscale_enabled | default(false) %}
|
||||
echo " - Open Ports: SSH (22) + Tailscale (41641/udp)"
|
||||
{% else %}
|
||||
echo " - Open Ports: SSH (22)"
|
||||
{% endif %}
|
||||
{% raw %}
|
||||
echo " - Docker isolation: ACTIVE"
|
||||
echo ""
|
||||
echo "📚 Documentation: https://github.com/openclaw/openclaw-ansible"
|
||||
echo ""
|
||||
{% endraw %}
|
||||
@@ -0,0 +1,136 @@
|
||||
" Vim Configuration - Generated by Ansible
|
||||
" Modern, practical vim setup for development and debugging
|
||||
|
||||
" Basic Settings
|
||||
set nocompatible " Disable vi compatibility
|
||||
filetype plugin indent on " Enable file type detection
|
||||
syntax on " Enable syntax highlighting
|
||||
|
||||
" UI Settings
|
||||
set number " Show line numbers
|
||||
set relativenumber " Show relative line numbers
|
||||
set ruler " Show cursor position
|
||||
set showcmd " Show command in bottom bar
|
||||
set wildmenu " Visual autocomplete for command menu
|
||||
set showmatch " Highlight matching brackets
|
||||
set cursorline " Highlight current line
|
||||
set laststatus=2 " Always show status line
|
||||
set colorcolumn=80,120 " Show column markers
|
||||
|
||||
" Search Settings
|
||||
set incsearch " Search as characters are entered
|
||||
set hlsearch " Highlight search results
|
||||
set ignorecase " Case insensitive search
|
||||
set smartcase " Case sensitive when uppercase present
|
||||
|
||||
" Indentation
|
||||
set autoindent " Auto-indent new lines
|
||||
set smartindent " Smart indent
|
||||
set expandtab " Use spaces instead of tabs
|
||||
set tabstop=2 " Number of visual spaces per TAB
|
||||
set shiftwidth=2 " Number of spaces for auto-indent
|
||||
set softtabstop=2 " Number of spaces in tab when editing
|
||||
|
||||
" Performance
|
||||
set lazyredraw " Don't redraw while executing macros
|
||||
set ttyfast " Fast terminal connection
|
||||
|
||||
" Backups and Undo
|
||||
set nobackup " No backup files
|
||||
set nowritebackup " No backup while editing
|
||||
set noswapfile " No swap files
|
||||
set undofile " Persistent undo
|
||||
set undodir=~/.vim/undo " Undo directory
|
||||
set undolevels=1000 " Maximum number of undos
|
||||
set undoreload=10000 " Maximum lines to save for undo
|
||||
|
||||
" File Handling
|
||||
set encoding=utf-8 " Use UTF-8 encoding
|
||||
set fileencoding=utf-8 " File encoding
|
||||
set autoread " Auto-reload changed files
|
||||
set hidden " Allow hidden buffers
|
||||
|
||||
" Navigation
|
||||
set scrolloff=8 " Keep 8 lines above/below cursor
|
||||
set sidescrolloff=8 " Keep 8 columns left/right of cursor
|
||||
set mouse=a " Enable mouse support
|
||||
|
||||
" Folding
|
||||
set foldmethod=indent " Fold based on indentation
|
||||
set foldlevel=99 " Open all folds by default
|
||||
|
||||
" Status Line
|
||||
set statusline=%F " Full file path
|
||||
set statusline+=%m " Modified flag
|
||||
set statusline+=%r " Read-only flag
|
||||
set statusline+=%h " Help buffer flag
|
||||
set statusline+=%w " Preview window flag
|
||||
set statusline+=%= " Right align
|
||||
set statusline+=%y " File type
|
||||
set statusline+=\ [%{&ff}] " File format
|
||||
set statusline+=\ [%{strlen(&fenc)?&fenc:'none'}] " File encoding
|
||||
set statusline+=\ %l:%c " Line:Column
|
||||
set statusline+=\ %p%% " Percentage through file
|
||||
|
||||
" Key Mappings
|
||||
let mapleader = "," " Set leader key to comma
|
||||
|
||||
" Quick save
|
||||
nnoremap <leader>w :w<CR>
|
||||
|
||||
" Quick quit
|
||||
nnoremap <leader>q :q<CR>
|
||||
|
||||
" Clear search highlighting
|
||||
nnoremap <leader><space> :nohlsearch<CR>
|
||||
|
||||
" Split navigation
|
||||
nnoremap <C-h> <C-w>h
|
||||
nnoremap <C-j> <C-w>j
|
||||
nnoremap <C-k> <C-w>k
|
||||
nnoremap <C-l> <C-w>l
|
||||
|
||||
" Tab navigation
|
||||
nnoremap <leader>tn :tabnew<CR>
|
||||
nnoremap <leader>tc :tabclose<CR>
|
||||
nnoremap <leader>1 1gt
|
||||
nnoremap <leader>2 2gt
|
||||
nnoremap <leader>3 3gt
|
||||
nnoremap <leader>4 4gt
|
||||
nnoremap <leader>5 5gt
|
||||
|
||||
" Buffer navigation
|
||||
nnoremap <leader>bn :bnext<CR>
|
||||
nnoremap <leader>bp :bprevious<CR>
|
||||
nnoremap <leader>bd :bdelete<CR>
|
||||
|
||||
" Paste toggle
|
||||
set pastetoggle=<F2>
|
||||
|
||||
" File Type Specific
|
||||
autocmd FileType python setlocal tabstop=4 shiftwidth=4 softtabstop=4
|
||||
autocmd FileType javascript,typescript,json setlocal tabstop=2 shiftwidth=2 softtabstop=2
|
||||
autocmd FileType yaml,yml setlocal tabstop=2 shiftwidth=2 softtabstop=2
|
||||
autocmd FileType go setlocal tabstop=4 shiftwidth=4 softtabstop=4 noexpandtab
|
||||
autocmd FileType markdown setlocal wrap linebreak nolist
|
||||
|
||||
" Auto-create undo directory
|
||||
if !isdirectory($HOME."/.vim/undo")
|
||||
call mkdir($HOME."/.vim/undo", "p", 0700)
|
||||
endif
|
||||
|
||||
" Colors
|
||||
set background=dark
|
||||
if &term =~ "xterm" || &term =~ "screen"
|
||||
set t_Co=256
|
||||
endif
|
||||
|
||||
" Highlight trailing whitespace
|
||||
highlight ExtraWhitespace ctermbg=red guibg=red
|
||||
match ExtraWhitespace /\s\+$/
|
||||
|
||||
" Remember cursor position
|
||||
autocmd BufReadPost *
|
||||
\ if line("'\"") > 1 && line("'\"") <= line("$") |
|
||||
\ exe "normal! g`\"" |
|
||||
\ endif
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# VM provisioning defaults — override in host_vars/<instance>.yml
|
||||
|
||||
# Libvirt connection URI
|
||||
vm_libvirt_uri: qemu:///system
|
||||
|
||||
# Cloud image
|
||||
vm_ubuntu_release: noble
|
||||
vm_cloud_image_url: "https://cloud-images.ubuntu.com/{{ vm_ubuntu_release }}/current/{{ vm_ubuntu_release }}-server-cloudimg-amd64.img"
|
||||
vm_cloud_image_cache: "/var/lib/libvirt/images/{{ vm_ubuntu_release }}-cloudimg-amd64.img"
|
||||
|
||||
# VM identity
|
||||
vm_domain: "" # full libvirt domain name, e.g. "zap [claw]"
|
||||
vm_hostname: "" # guest hostname
|
||||
vm_disk_path: "" # path to qcow2 disk image
|
||||
|
||||
# Resources
|
||||
vm_memory_mib: 3072
|
||||
vm_vcpus: 2
|
||||
vm_disk_size: "60G"
|
||||
|
||||
# Network
|
||||
vm_mac: "" # MAC address, e.g. "52:54:00:01:00:71"
|
||||
vm_ip: "" # static IP for DHCP reservation
|
||||
vm_network: default
|
||||
|
||||
# virtiofs share (host → guest)
|
||||
vm_virtiofs_source: "" # host path
|
||||
vm_virtiofs_tag: "" # mount tag used inside guest
|
||||
|
||||
# OVMF firmware (Arch/CachyOS paths)
|
||||
vm_ovmf_code: /usr/share/edk2/x64/OVMF_CODE.secboot.4m.fd
|
||||
vm_ovmf_vars_template: /usr/share/edk2/x64/OVMF_VARS.4m.fd
|
||||
vm_ovmf_vars_dir: /var/lib/libvirt/qemu/nvram
|
||||
@@ -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."
|
||||
@@ -0,0 +1,2 @@
|
||||
instance-id: {{ vm_hostname }}-{{ vm_mac | replace(':', '') }}
|
||||
local-hostname: {{ vm_hostname }}
|
||||
@@ -0,0 +1,28 @@
|
||||
#cloud-config
|
||||
hostname: {{ vm_hostname }}
|
||||
manage_etc_hosts: true
|
||||
|
||||
# Enable root SSH with key from host
|
||||
disable_root: false
|
||||
ssh_pwauth: false
|
||||
|
||||
users:
|
||||
- name: root
|
||||
ssh_authorized_keys:
|
||||
{% for key in vm_ssh_keys | default([]) %}
|
||||
- {{ key }}
|
||||
{% endfor %}
|
||||
|
||||
# Grow root partition to fill disk
|
||||
growpart:
|
||||
mode: auto
|
||||
devices: [/]
|
||||
|
||||
resize_rootfs: true
|
||||
|
||||
# Ensure SSH is running
|
||||
packages:
|
||||
- qemu-guest-agent
|
||||
|
||||
runcmd:
|
||||
- systemctl enable --now qemu-guest-agent
|
||||
@@ -0,0 +1,123 @@
|
||||
<domain type='kvm'>
|
||||
<name>{{ vm_domain }}</name>
|
||||
<metadata>
|
||||
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
|
||||
<libosinfo:os id="http://ubuntu.com/ubuntu/24.04"/>
|
||||
</libosinfo:libosinfo>
|
||||
</metadata>
|
||||
<memory unit='KiB'>{{ vm_memory_mib * 1024 }}</memory>
|
||||
<currentMemory unit='KiB'>{{ vm_memory_mib * 1024 }}</currentMemory>
|
||||
<memoryBacking>
|
||||
<source type='memfd'/>
|
||||
<access mode='shared'/>
|
||||
</memoryBacking>
|
||||
<vcpu placement='static'>{{ vm_vcpus }}</vcpu>
|
||||
<os firmware='efi'>
|
||||
<type arch='x86_64' machine='pc-q35-10.2'>hvm</type>
|
||||
<firmware>
|
||||
<feature enabled='no' name='enrolled-keys'/>
|
||||
<feature enabled='yes' name='secure-boot'/>
|
||||
</firmware>
|
||||
<loader readonly='yes' secure='yes' type='pflash' format='raw'>{{ vm_ovmf_code }}</loader>
|
||||
<nvram template='{{ vm_ovmf_vars_template }}' templateFormat='raw' format='raw'>{{ vm_ovmf_vars_dir }}/{{ vm_domain }}_VARS.fd</nvram>
|
||||
<boot dev='hd'/>
|
||||
</os>
|
||||
<features>
|
||||
<acpi/>
|
||||
<apic/>
|
||||
<vmport state='off'/>
|
||||
<smm state='on'/>
|
||||
</features>
|
||||
<cpu mode='host-passthrough' check='none' migratable='on'/>
|
||||
<clock offset='utc'>
|
||||
<timer name='rtc' tickpolicy='catchup'/>
|
||||
<timer name='pit' tickpolicy='delay'/>
|
||||
<timer name='hpet' present='no'/>
|
||||
</clock>
|
||||
<on_poweroff>destroy</on_poweroff>
|
||||
<on_reboot>restart</on_reboot>
|
||||
<on_crash>destroy</on_crash>
|
||||
<pm>
|
||||
<suspend-to-mem enabled='no'/>
|
||||
<suspend-to-disk enabled='no'/>
|
||||
</pm>
|
||||
<devices>
|
||||
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
||||
|
||||
<!-- Primary disk -->
|
||||
<disk type='file' device='disk'>
|
||||
<driver name='qemu' type='qcow2' discard='unmap'/>
|
||||
<source file='{{ vm_disk_path }}'/>
|
||||
<target dev='vda' bus='virtio'/>
|
||||
</disk>
|
||||
|
||||
<!-- Cloud-init seed (removed after first boot) -->
|
||||
<disk type='file' device='cdrom'>
|
||||
<driver name='qemu' type='raw'/>
|
||||
<source file='{{ vm_seed_iso }}'/>
|
||||
<target dev='sda' bus='sata'/>
|
||||
<readonly/>
|
||||
</disk>
|
||||
|
||||
<!-- virtio-serial for qemu-guest-agent -->
|
||||
<controller type='virtio-serial' index='0'/>
|
||||
|
||||
<!-- Network -->
|
||||
<interface type='network'>
|
||||
<mac address='{{ vm_mac }}'/>
|
||||
<source network='{{ vm_network }}'/>
|
||||
<model type='virtio'/>
|
||||
</interface>
|
||||
|
||||
<!-- Serial console -->
|
||||
<serial type='pty'>
|
||||
<target type='isa-serial' port='0'>
|
||||
<model name='isa-serial'/>
|
||||
</target>
|
||||
</serial>
|
||||
<console type='pty'>
|
||||
<target type='serial' port='0'/>
|
||||
</console>
|
||||
|
||||
<!-- qemu-guest-agent channel -->
|
||||
<channel type='unix'>
|
||||
<target type='virtio' name='org.qemu.guest_agent.0'/>
|
||||
</channel>
|
||||
|
||||
{% if vm_virtiofs_source and vm_virtiofs_tag %}
|
||||
<!-- virtiofs host share -->
|
||||
<filesystem type='mount' accessmode='passthrough'>
|
||||
<driver type='virtiofs'/>
|
||||
<source dir='{{ vm_virtiofs_source }}'/>
|
||||
<target dir='{{ vm_virtiofs_tag }}'/>
|
||||
</filesystem>
|
||||
{% endif %}
|
||||
|
||||
<!-- TPM 2.0 -->
|
||||
<tpm model='tpm-crb'>
|
||||
<backend type='emulator' version='2.0'/>
|
||||
</tpm>
|
||||
|
||||
<!-- Watchdog -->
|
||||
<watchdog model='itco' action='reset'/>
|
||||
|
||||
<!-- Memory balloon -->
|
||||
<memballoon model='virtio'>
|
||||
<stats period='5'/>
|
||||
</memballoon>
|
||||
|
||||
<!-- RNG -->
|
||||
<rng model='virtio'>
|
||||
<backend model='random'>/dev/urandom</backend>
|
||||
</rng>
|
||||
|
||||
<!-- SPICE (for virt-manager) -->
|
||||
<graphics type='spice' autoport='yes' listen='127.0.0.1'>
|
||||
<listen type='address' address='127.0.0.1'/>
|
||||
<image compression='off'/>
|
||||
</graphics>
|
||||
<video>
|
||||
<model type='virtio' heads='1' primary='yes'/>
|
||||
</video>
|
||||
</devices>
|
||||
</domain>
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Run OpenClaw playbook from local source or installed collection
|
||||
|
||||
OPENCLAW_USER="${OPENCLAW_USER:-openclaw}"
|
||||
|
||||
# Keep instructions aligned when user overrides openclaw_user via -e.
|
||||
extract_openclaw_user_from_args() {
|
||||
local prev_is_extra=0
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
if [ "$prev_is_extra" -eq 1 ]; then
|
||||
if [[ "$arg" =~ (^|[[:space:]])openclaw_user=([^[:space:]]+) ]]; then
|
||||
OPENCLAW_USER="${BASH_REMATCH[2]}"
|
||||
fi
|
||||
prev_is_extra=0
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$arg" in
|
||||
-e|--extra-vars)
|
||||
prev_is_extra=1
|
||||
;;
|
||||
-e=*|--extra-vars=*)
|
||||
local extra="${arg#*=}"
|
||||
if [[ "$extra" =~ (^|[[:space:]])openclaw_user=([^[:space:]]+) ]]; then
|
||||
OPENCLAW_USER="${BASH_REMATCH[2]}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
extract_openclaw_user_from_args "$@"
|
||||
|
||||
# Determine playbook source
|
||||
if [ -f "playbooks/install.yml" ]; then
|
||||
echo "Running from local source..."
|
||||
PLAYBOOK="playbook.yml"
|
||||
export ANSIBLE_ROLES_PATH="${PWD}/roles:${ANSIBLE_ROLES_PATH}"
|
||||
elif ansible-galaxy collection list 2>/dev/null | grep -q "openclaw.installer"; then
|
||||
echo "Running from installed collection..."
|
||||
PLAYBOOK="openclaw.installer.install"
|
||||
else
|
||||
echo "Error: Collection not installed and not running from source"
|
||||
echo "Install with: ansible-galaxy collection install -r requirements.yml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run the Ansible playbook
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
ansible-playbook "$PLAYBOOK" -e ansible_become=false "$@"
|
||||
PLAYBOOK_EXIT=$?
|
||||
else
|
||||
if sudo -n true 2>/dev/null; then
|
||||
echo "Passwordless sudo detected. Running without become password prompt."
|
||||
ansible-playbook "$PLAYBOOK" "$@"
|
||||
PLAYBOOK_EXIT=$?
|
||||
else
|
||||
echo "Sudo password required. Prompting for become password."
|
||||
ansible-playbook "$PLAYBOOK" --ask-become-pass "$@"
|
||||
PLAYBOOK_EXIT=$?
|
||||
fi
|
||||
fi
|
||||
|
||||
# After playbook completes successfully, show instructions
|
||||
if [ $PLAYBOOK_EXIT -eq 0 ]; then
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
echo "✅ INSTALLATION COMPLETE!"
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo "🔄 SWITCH TO OPENCLAW USER with:"
|
||||
echo ""
|
||||
echo " sudo su - ${OPENCLAW_USER}"
|
||||
echo ""
|
||||
echo " OR (alternative):"
|
||||
echo ""
|
||||
echo " sudo -u ${OPENCLAW_USER} -i"
|
||||
echo ""
|
||||
echo "This will switch you to the OpenClaw user with a proper"
|
||||
echo "login shell (loads .bashrc, sets environment correctly)."
|
||||
echo ""
|
||||
echo "After switching, you'll see the next setup steps:"
|
||||
echo " • Configure OpenClaw (~/.openclaw/config.yml)"
|
||||
echo " • Login to messaging provider (WhatsApp/Telegram/Signal)"
|
||||
echo " • Test the gateway"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
else
|
||||
echo "❌ Playbook failed with exit code $PLAYBOOK_EXIT"
|
||||
exit $PLAYBOOK_EXIT
|
||||
fi
|
||||
@@ -0,0 +1,29 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install Ansible and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ansible \
|
||||
python3 \
|
||||
python3-apt \
|
||||
sudo \
|
||||
systemd \
|
||||
git \
|
||||
curl \
|
||||
ca-certificates \
|
||||
acl \
|
||||
gpg \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy project into container
|
||||
COPY . /opt/ansible
|
||||
WORKDIR /opt/ansible
|
||||
|
||||
# Install Ansible Galaxy collections
|
||||
RUN ansible-galaxy collection install -r requirements.yml
|
||||
|
||||
# Default: run the test entrypoint
|
||||
ENTRYPOINT ["bash", "tests/entrypoint.sh"]
|
||||
@@ -0,0 +1,68 @@
|
||||
# Docker CI Test Harness
|
||||
|
||||
This directory contains a Docker-based CI test harness for the Ansible playbook. It validates convergence, correctness, and idempotency by running the playbook inside an Ubuntu 24.04 container.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
bash tests/run-tests.sh
|
||||
|
||||
# Or specify a distro (currently only ubuntu2404 available)
|
||||
bash tests/run-tests.sh ubuntu2404
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
The test harness runs three sequential tests:
|
||||
|
||||
1. **Convergence**: Runs the playbook with `ci_test=true` to verify it completes without errors
|
||||
2. **Verification**: Runs `verify.yml` to assert the system is in the expected state
|
||||
3. **Idempotency**: Runs the playbook a second time and verifies `changed=0`
|
||||
|
||||
## Files
|
||||
|
||||
- `Dockerfile.ubuntu2404` - Ubuntu 24.04 container with Ansible pre-installed
|
||||
- `entrypoint.sh` - Test execution script (convergence → verification → idempotency)
|
||||
- `verify.yml` - Post-convergence assertions (user exists, packages installed, directories created, etc.)
|
||||
- `run-tests.sh` - Local test runner script
|
||||
|
||||
## CI Test Mode
|
||||
|
||||
The `ci_test` variable skips tasks that require:
|
||||
- Docker-in-Docker (Docker CE installation)
|
||||
- Kernel access (UFW/iptables firewall)
|
||||
- systemd services (loginctl, daemon installation)
|
||||
- External package installation (openclaw app install)
|
||||
|
||||
Everything else runs normally: package installation, user creation, Node.js/pnpm setup, directory structure, config file rendering, etc.
|
||||
|
||||
## What Gets Tested
|
||||
|
||||
| Component | Tested? | Notes |
|
||||
|-----------|---------|-------|
|
||||
| System packages (35+) | ✅ Yes | Full apt install |
|
||||
| User creation + config | ✅ Yes | User, .bashrc, sudoers, SSH dir |
|
||||
| Node.js + pnpm | ✅ Yes | Full install + version check |
|
||||
| Directory structure | ✅ Yes | All .openclaw/* dirs with perms |
|
||||
| Git global config | ✅ Yes | Aliases, default branch |
|
||||
| Vim config | ✅ Yes | Template rendering |
|
||||
| Docker CE install | ❌ No | Needs Docker-in-Docker |
|
||||
| UFW / iptables | ❌ No | Needs kernel access |
|
||||
| fail2ban / systemd | ❌ No | Needs running systemd |
|
||||
| Tailscale | ❌ No | Disabled by default already |
|
||||
| OpenClaw app install | ❌ No | External package |
|
||||
| Idempotency | ✅ Yes | Second run must have 0 changes |
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- `0` - All tests passed
|
||||
- `1` - Test failure (convergence failed, verification failed, or idempotency check failed)
|
||||
|
||||
## Development
|
||||
|
||||
To add tests for additional distributions:
|
||||
1. Create `Dockerfile.<distro>` (e.g., `Dockerfile.debian12`)
|
||||
2. Run: `bash tests/run-tests.sh <distro>`
|
||||
|
||||
The test harness automatically builds the image and runs the test suite.
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PLAYBOOK_ARGS=(-e ci_test=true -e ansible_become=false --connection=local)
|
||||
|
||||
# --- Step 1: Convergence ---
|
||||
echo "===> Step 1: Convergence test"
|
||||
ansible-playbook playbook.yml "${PLAYBOOK_ARGS[@]}"
|
||||
echo "===> Convergence: PASSED"
|
||||
|
||||
# --- Step 2: Verification ---
|
||||
echo "===> Step 2: Verification"
|
||||
ansible-playbook tests/verify.yml "${PLAYBOOK_ARGS[@]}"
|
||||
echo "===> Verification: PASSED"
|
||||
|
||||
# --- Step 3: Idempotency ---
|
||||
echo "===> Step 3: Idempotency test"
|
||||
IDEMPOTENCY_OUT=$(ansible-playbook playbook.yml "${PLAYBOOK_ARGS[@]}" 2>&1)
|
||||
echo "$IDEMPOTENCY_OUT"
|
||||
|
||||
CHANGED=$(echo "$IDEMPOTENCY_OUT" | tail -n 5 | grep -oP 'changed=\K[0-9]+' | head -1)
|
||||
if [ "${CHANGED:-1}" -eq 0 ]; then
|
||||
echo "===> Idempotency: PASSED (0 changed)"
|
||||
else
|
||||
echo "===> Idempotency: FAILED (changed=$CHANGED)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "===> All tests passed"
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DISTRO="${1:-ubuntu2404}"
|
||||
IMAGE="openclaw-ansible-test:${DISTRO}"
|
||||
|
||||
echo "Building test image (${DISTRO})..."
|
||||
docker build -t "$IMAGE" -f "tests/Dockerfile.${DISTRO}" .
|
||||
|
||||
echo "Running tests..."
|
||||
docker run --rm "$IMAGE"
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
- name: Verify playbook results
|
||||
hosts: localhost
|
||||
connection: local
|
||||
gather_facts: true
|
||||
|
||||
vars:
|
||||
openclaw_user: openclaw
|
||||
openclaw_home: /home/openclaw
|
||||
|
||||
tasks:
|
||||
- name: Verify openclaw user exists
|
||||
ansible.builtin.command: "id {{ openclaw_user }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Verify critical packages installed
|
||||
ansible.builtin.command: "dpkg -s {{ item }}"
|
||||
loop: [git, curl, vim, jq, tmux, tree, htop]
|
||||
changed_when: false
|
||||
|
||||
- name: Verify Node.js installed
|
||||
ansible.builtin.command: node --version
|
||||
changed_when: false
|
||||
|
||||
- name: Verify pnpm installed
|
||||
ansible.builtin.command: pnpm --version
|
||||
changed_when: false
|
||||
|
||||
- name: Verify openclaw directory structure
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item.path }}"
|
||||
loop:
|
||||
- { path: "{{ openclaw_home }}/.openclaw", mode: "0755" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/sessions" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/credentials", mode: "0700" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/data" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/logs" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/agents" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/agents/main" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/agents/main/agent", mode: "0700" }
|
||||
- { path: "{{ openclaw_home }}/.openclaw/workspace" }
|
||||
- { path: "{{ openclaw_home }}/.ssh", mode: "0700" }
|
||||
register: dir_checks
|
||||
|
||||
- name: Assert directories exist
|
||||
ansible.builtin.assert:
|
||||
that: item.stat.exists and item.stat.isdir
|
||||
fail_msg: "Directory missing: {{ item.item.path }}"
|
||||
loop: "{{ dir_checks.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item.path }}"
|
||||
|
||||
- name: Assert restricted directories have correct permissions
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- dir_checks.results[2].stat.mode == '0700'
|
||||
- dir_checks.results[7].stat.mode == '0700'
|
||||
fail_msg: "credentials and agents/main/agent dirs should be 0700"
|
||||
|
||||
- name: Verify sudoers file exists and is valid
|
||||
ansible.builtin.command: "visudo -cf /etc/sudoers.d/{{ openclaw_user }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Verify global vim config exists
|
||||
ansible.builtin.stat:
|
||||
path: /etc/vim/vimrc.local
|
||||
register: vimrc
|
||||
- ansible.builtin.assert:
|
||||
that: vimrc.stat.exists
|
||||
|
||||
- name: Verify git global config
|
||||
ansible.builtin.command: git config --global init.defaultBranch
|
||||
changed_when: false
|
||||
register: git_branch
|
||||
- ansible.builtin.assert:
|
||||
that: git_branch.stdout == 'main'
|
||||
Reference in New Issue
Block a user