Compare commits

...

4 Commits

Author SHA1 Message Date
Haitao Pan
d4f936c476 Ensure workspace user exists before provisioning 2026-06-08 18:40:39 +08:00
Haitao Pan
f241b6b8ae Install ttyd from official release binary 2026-06-08 18:21:15 +08:00
Haitao Pan
044ddd7fbc Install Chrome source before workspace runtime packages 2026-06-08 18:14:01 +08:00
Haitao Pan
25078d91b7 Allow common HTTP and HTTPS ports 2026-06-08 17:43:33 +08:00
4 changed files with 178 additions and 28 deletions

View File

@ -19,6 +19,14 @@ journald_log_rotation: # 启用 journald 日志管理
# 总开关
enable_common: true
common_firewall:
enabled: true
http_port: 80
https_port: 443
allow_comments:
http: XWorkmate HTTP
https: XWorkmate HTTPS
common_security_limits:
enabled: true
nofile_soft: 1048576

View File

@ -0,0 +1,47 @@
---
- name: Common | detect UFW binary
ansible.builtin.stat:
path: /usr/sbin/ufw
register: common_firewall_ufw_binary
- name: Common | detect firewalld binary
ansible.builtin.stat:
path: /usr/bin/firewall-cmd
register: common_firewall_firewalld_binary
- name: Common | allow HTTP/HTTPS through UFW
ansible.builtin.command: "ufw allow {{ item.port }}/{{ item.protocol }}"
register: common_firewall_ufw_allow
changed_when: "'Skipping adding existing rule' not in (common_firewall_ufw_allow.stdout | default(''))"
failed_when: common_firewall_ufw_allow.rc != 0
loop:
- { port: "{{ common_firewall.http_port | default(80) }}", protocol: tcp, comment: "{{ common_firewall.allow_comments.http | default('XWorkmate HTTP') }}" }
- { port: "{{ common_firewall.https_port | default(443) }}", protocol: tcp, comment: "{{ common_firewall.allow_comments.https | default('XWorkmate HTTPS') }}" }
when:
- common_firewall.enabled | default(true) | bool
- common_firewall_ufw_binary.stat.exists | default(false)
become: true
- name: Common | allow HTTP/HTTPS through firewalld
ansible.posix.firewalld:
port: "{{ item.port }}/{{ item.protocol }}"
permanent: true
immediate: true
state: enabled
loop:
- { port: "{{ common_firewall.http_port | default(80) }}", protocol: tcp, comment: "{{ common_firewall.allow_comments.http | default('XWorkmate HTTP') }}" }
- { port: "{{ common_firewall.https_port | default(443) }}", protocol: tcp, comment: "{{ common_firewall.allow_comments.https | default('XWorkmate HTTPS') }}" }
when:
- common_firewall.enabled | default(true) | bool
- not (common_firewall_ufw_binary.stat.exists | default(false))
- common_firewall_firewalld_binary.stat.exists | default(false)
become: true
- name: Common | explain firewall handling
ansible.builtin.debug:
msg: >-
HTTP/HTTPS firewall handling completed or skipped on {{ inventory_hostname }}.
UFW={{ common_firewall_ufw_binary.stat.exists | default(false) }},
firewalld={{ common_firewall_firewalld_binary.stat.exists | default(false) }}.
when:
- common_firewall.enabled | default(true) | bool

View File

@ -38,6 +38,12 @@
- common_security_limits.enabled | default(true) | bool
tags: [limits, baseline]
- name: Base | allow HTTP/HTTPS ports
ansible.builtin.import_tasks: firewall_ports.yml
when:
- common_firewall.enabled | default(true) | bool
tags: [firewall, baseline]
# ===== Common baseline (OS split) =====
- name: Common | Debian family baseline
ansible.builtin.import_tasks: common_debian.yml

View File

@ -1,5 +1,5 @@
---
- name: Setup XWorkspace Console runtime
- name: Setup AI Agentic Workspace runtime
hosts: "{{ xworkspace_console_hosts | default('all') }}"
become: true
gather_facts: true
@ -15,18 +15,107 @@
xworkspace_console_enable_ttyd: true
xworkspace_console_install_chrome: true
xworkspace_console_autostart_enabled: true
xworkspace_console_ttyd_binary_path: /usr/local/bin/ttyd
tasks:
- name: Install XWorkspace desktop packages
- name: Install Google Chrome apt repository prerequisites
ansible.builtin.apt:
name:
- ca-certificates
- curl
- gnupg
- xdg-utils
state: present
install_recommends: false
update_cache: true
- name: Ensure Google Chrome apt keyring directory exists
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"
- name: Install Google Linux signing key
ansible.builtin.shell: |
set -euo pipefail
tmp="$(mktemp)"
curl -fsSL "https://dl.google.com/linux/linux_signing_key.pub" -o "$tmp"
gpg --dearmor -o /etc/apt/keyrings/google-linux-signing-key.gpg "$tmp"
rm -f "$tmp"
chmod 0644 /etc/apt/keyrings/google-linux-signing-key.gpg
args:
executable: /bin/bash
creates: /etc/apt/keyrings/google-linux-signing-key.gpg
- name: Configure Google Chrome apt repository
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/google-chrome.list
owner: root
group: root
mode: "0644"
content: "deb [arch=amd64 signed-by=/etc/apt/keyrings/google-linux-signing-key.gpg] https://dl.google.com/linux/chrome/deb/ stable main\n"
- name: Refresh apt cache after Google Chrome repository changes
ansible.builtin.apt:
update_cache: true
- name: Install AI Agentic Workspace runtime packages
ansible.builtin.apt:
update_cache: true
name:
- xfce4
- python3
- ttyd
- google-chrome-stable
state: present
- name: Ensure XWorkspace directories exist
- name: Ensure ttyd binary target directory exists
ansible.builtin.file:
path: "{{ xworkspace_console_ttyd_binary_path | dirname }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Set ttyd binary download metadata
ansible.builtin.set_fact:
xworkspace_console_ttyd_arch: >-
{{ 'x86_64' if ansible_architecture in ['x86_64', 'amd64'] else
'aarch64' if ansible_architecture in ['aarch64', 'arm64'] else
'' }}
- name: Fail when ttyd binary architecture is unsupported
ansible.builtin.fail:
msg: "Unsupported architecture for ttyd binary: {{ ansible_architecture }}"
when: xworkspace_console_ttyd_arch == ''
- name: Download ttyd release binary
ansible.builtin.get_url:
url: "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.{{ xworkspace_console_ttyd_arch }}"
dest: "{{ xworkspace_console_ttyd_binary_path }}"
mode: "0755"
owner: root
group: root
force: true
- name: Verify ttyd binary
ansible.builtin.command: "{{ xworkspace_console_ttyd_binary_path }} --version"
register: xworkspace_console_ttyd_version
changed_when: false
- name: Show ttyd binary version
ansible.builtin.debug:
var: xworkspace_console_ttyd_version.stdout
- name: Ensure AI Agentic Workspace user exists
ansible.builtin.user:
name: "{{ xworkspace_console_user }}"
state: present
create_home: true
shell: /bin/bash
when: xworkspace_console_user != 'root'
- name: Ensure AI Agentic Workspace directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
@ -44,7 +133,7 @@
- "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants"
- "{{ xworkspace_console_home }}/.config/systemd/user/timers.target.wants"
- name: Deploy XWorkspace status generator
- name: Deploy AI Agentic Workspace status generator
ansible.builtin.copy:
dest: "{{ xworkspace_console_scripts_dir }}/generate-status.py"
owner: "{{ xworkspace_console_user }}"
@ -177,7 +266,7 @@
print(f"Wrote {OUTPUT}")
- name: Deploy XWorkspace status service
- name: Deploy AI Agentic Workspace status service
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-status.service"
owner: "{{ xworkspace_console_user }}"
@ -185,7 +274,7 @@
mode: "0644"
content: |
[Unit]
Description=XWorkspace status snapshot generator
Description=AI Agentic Workspace status snapshot generator
After=xworkspace-portal.service xworkspace-litellm.service
Wants=xworkspace-portal.service xworkspace-litellm.service
@ -193,7 +282,7 @@
Type=oneshot
ExecStart={{ xworkspace_console_scripts_dir }}/generate-status.py
- name: Deploy XWorkspace status timer
- name: Deploy AI Agentic Workspace status timer
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-status.timer"
owner: "{{ xworkspace_console_user }}"
@ -201,7 +290,7 @@
mode: "0644"
content: |
[Unit]
Description=Refresh XWorkspace status snapshot periodically
Description=Refresh AI Agentic Workspace status snapshot periodically
[Timer]
OnBootSec=5
@ -212,7 +301,7 @@
[Install]
WantedBy=timers.target
- name: Deploy Xinit entrypoint for XWorkspace
- name: Deploy Xinit entrypoint for AI Agentic Workspace
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.xinitrc"
owner: "{{ xworkspace_console_user }}"
@ -254,7 +343,7 @@
#!/usr/bin/env bash
exec "$HOME/.xinitrc"
- name: Deploy XWorkspace console launcher script
- name: Deploy AI Agentic Workspace console launcher script
ansible.builtin.copy:
dest: "{{ xworkspace_console_scripts_dir }}/start.sh"
owner: "{{ xworkspace_console_user }}"
@ -265,7 +354,7 @@
set -euo pipefail
exit 0
- name: Deploy XWorkspace Chrome launcher wrapper
- name: Deploy AI Agentic Workspace Chrome launcher wrapper
ansible.builtin.copy:
dest: "{{ xworkspace_console_scripts_dir }}/chrome-app.sh"
owner: "{{ xworkspace_console_user }}"
@ -284,7 +373,7 @@
--disable-sync \
--new-window
- name: Deploy XWorkspace portal service
- name: Deploy AI Agentic Workspace portal service
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-portal.service"
owner: "{{ xworkspace_console_user }}"
@ -292,7 +381,7 @@
mode: "0644"
content: |
[Unit]
Description=XWorkspace Portal
Description=AI Agentic Workspace Portal
After=graphical-session.target
Wants=graphical-session.target
@ -306,7 +395,7 @@
[Install]
WantedBy=default.target
- name: Deploy XWorkspace ttyd service
- name: Deploy AI Agentic Workspace ttyd service
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-ttyd.service"
owner: "{{ xworkspace_console_user }}"
@ -314,7 +403,7 @@
mode: "0644"
content: |
[Unit]
Description=XWorkspace ttyd
Description=AI Agentic Workspace ttyd
After=graphical-session.target
Wants=graphical-session.target
@ -327,7 +416,7 @@
[Install]
WantedBy=default.target
- name: Deploy XWorkspace Chrome service
- name: Deploy AI Agentic Workspace Chrome service
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-chrome.service"
owner: "{{ xworkspace_console_user }}"
@ -335,7 +424,7 @@
mode: "0644"
content: |
[Unit]
Description=XWorkspace Chrome App
Description=AI Agentic Workspace Chrome App
After=graphical-session.target xworkspace-portal.service
Requires=xworkspace-portal.service
Wants=graphical-session.target
@ -351,7 +440,7 @@
[Install]
WantedBy=default.target
- name: Deploy XWorkspace desktop target
- name: Deploy AI Agentic Workspace target
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-console.target"
owner: "{{ xworkspace_console_user }}"
@ -359,49 +448,49 @@
mode: "0644"
content: |
[Unit]
Description=XWorkspace Desktop Console
Description=AI Agentic Workspace Console
Wants=xworkspace-portal.service xworkspace-ttyd.service xworkspace-chrome.service xworkspace-litellm.service xworkspace-status.service xworkmate-bridge.service openclaw-gateway.service hermes-gateway.service
After=graphical-session.target
[Install]
WantedBy=default.target
- name: Enable XWorkspace desktop target
- name: Enable AI Agentic Workspace target
ansible.builtin.file:
src: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-console.target"
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-console.target"
state: link
become_user: "{{ xworkspace_console_user }}"
- name: Enable XWorkspace portal service
- name: Enable AI Agentic Workspace portal service
ansible.builtin.file:
src: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-portal.service"
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-portal.service"
state: link
become_user: "{{ xworkspace_console_user }}"
- name: Enable XWorkspace ttyd service
- name: Enable AI Agentic Workspace ttyd service
ansible.builtin.file:
src: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-ttyd.service"
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-ttyd.service"
state: link
become_user: "{{ xworkspace_console_user }}"
- name: Enable XWorkspace Chrome service
- name: Enable AI Agentic Workspace Chrome service
ansible.builtin.file:
src: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-chrome.service"
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-chrome.service"
state: link
become_user: "{{ xworkspace_console_user }}"
- name: Enable XWorkspace LiteLLM service
- name: Enable AI Agentic Workspace LiteLLM service
ansible.builtin.file:
src: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-litellm.service"
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-litellm.service"
state: link
become_user: "{{ xworkspace_console_user }}"
- name: Enable XWorkspace status timer
- name: Enable AI Agentic Workspace status timer
ansible.builtin.file:
src: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-status.timer"
dest: "{{ xworkspace_console_home }}/.config/systemd/user/timers.target.wants/xworkspace-status.timer"
@ -425,7 +514,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>XWorkspace Console</title>
<title>AI Agentic Workspace Console</title>
<style>
:root {
color-scheme: light;
@ -612,7 +701,7 @@
<body>
<div class="frame">
<aside class="nav">
<h1>XWorkspace</h1>
<h1>AI Agentic Workspace</h1>
<a class="active" href="#workspace">Workspace</a>
<a href="#openclaw">OpenClaw</a>
<a href="#litellm">LiteLLM</a>