Refactor user management in Ansible playbooks to standardize on keeper_user
- Updated user addition tasks across multiple playbooks (mcp_switchboard, mcpo, neo4j, neo4j_mcp, openwebui, postgresql, rabbitmq, searxng, smtp4dev) to replace references to ansible_user and remote_user with keeper_user. - Modified PostgreSQL deployment to create directories and manage files under keeper_user's home. - Enhanced documentation to clarify account taxonomy and usage of keeper_user in playbooks. - Introduced new deployment for Agent S, including environment setup, desktop environment installation, XRDP configuration, and accessibility support. - Added staging playbook for preparing release tarballs from local repositories. - Created templates for XRDP configuration and environment activation scripts. - Removed obsolete sunwait documentation.
This commit is contained in:
245
docs/agent_s.md
Normal file
245
docs/agent_s.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Ansible Deployment for Agent S
|
||||
|
||||
Agent S is a computer-use automation agent that controls a desktop environment via RDP. The deployment configures a full graphical stack on `larissa.helu.ca`: MATE desktop, XRDP, audio redirection via PulseAudio, and an AT-SPI accessibility bridge so agents can introspect the UI tree.
|
||||
|
||||
## Host
|
||||
|
||||
| Host | Group | Type |
|
||||
|------|-------|------|
|
||||
| `larissa.helu.ca` | `agent_s` | Incus container |
|
||||
|
||||
## Overview
|
||||
|
||||
The deployment installs and configures:
|
||||
|
||||
- **Principal user** (`robert`, UID 1000) — the human account the agent operates on behalf of
|
||||
- **MATE desktop** — chosen for strong AT-SPI accessibility support
|
||||
- **Firefox** from the Mozilla APT repo (bypasses the snap dependency introduced by Ubuntu)
|
||||
- **Google Chrome** for browser automation
|
||||
- **XRDP** with a custom Xorg config pinned to 1024×768 for UI-TARS / Agent-S model compatibility
|
||||
- **PulseAudio + pulseaudio-module-xrdp** — audio redirection over RDP
|
||||
- **AT-SPI accessibility stack** — allows agents to query the widget tree
|
||||
- **Python virtual environment** with the Agent-S package and dependencies
|
||||
- **Agent-S repository** extracted from a staged release tarball
|
||||
- **Environment activation script** at `~/.agent_s_env`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Control node
|
||||
|
||||
- Ansible 2.12+
|
||||
- SSH access to `larissa.helu.ca`
|
||||
- Staged release tarballs in `~/rel/` (produced by `stage.yml`):
|
||||
- `~/rel/agent_s_<agent_s_rel>.tar`
|
||||
- `~/rel/pulseaudio_module_xrdp_<pulseaudio_module_xrdp_rel>.tar`
|
||||
|
||||
### Target host
|
||||
|
||||
- Ubuntu 25.04
|
||||
- Network access to Mozilla APT, Google Chrome DL, and the Ubuntu package mirrors
|
||||
- deb-src repositories available (the playbook enables them if absent)
|
||||
|
||||
## Staging
|
||||
|
||||
Before deploying, stage release tarballs from local git checkouts:
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/agent_s/stage.yml
|
||||
```
|
||||
|
||||
`stage.yml` runs on localhost and creates two tarballs from the configured git branches:
|
||||
|
||||
| Tarball | Source repo | Branch variable |
|
||||
|---------|-------------|-----------------|
|
||||
| `agent_s_<rel>.tar` | `~/gh/Agent-S` | `agent_s_rel` |
|
||||
| `pulseaudio_module_xrdp_<rel>.tar` | `~/gh/pulseaudio-module-xrdp` | `pulseaudio_module_xrdp_rel` |
|
||||
|
||||
Both variables default to the `all` group vars (`agent_s_rel: main`, `pulseaudio_module_xrdp_rel: devel`).
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
ansible-playbook ansible/agent_s/deploy.yml
|
||||
```
|
||||
|
||||
### Phase 1 — Principal user and snap suppression
|
||||
|
||||
Creates the `robert` account (UID 1000) and pins `snapd` at priority -10 via `/etc/apt/preferences.d/nosnap.pref`. This must run before the desktop install so `ubuntu-mate-desktop` cannot pull in snap-packaged Firefox.
|
||||
|
||||
### Phase 2 — Firefox
|
||||
|
||||
Adds the Mozilla APT repo and pins all `packages.mozilla.org` packages at priority 1000, ensuring the system installs the .deb Firefox rather than the snap.
|
||||
|
||||
### Phase 3 — MATE desktop
|
||||
|
||||
Installs `ubuntu-mate-desktop`. MATE is preferred over GNOME because its AT-SPI bridge is more reliable in a headless XRDP session.
|
||||
|
||||
### Phase 4 — XRDP
|
||||
|
||||
Installs `xrdp` and `xorgxrdp`, adds the `xrdp` user to `ssl-cert`, and enables the service. The Xorg configuration is deployed separately (Phase 8).
|
||||
|
||||
### Phase 5 — AT-SPI accessibility
|
||||
|
||||
Installs the AT-SPI core libraries and adds `/etc/profile.d/atspi.sh` to set:
|
||||
|
||||
```bash
|
||||
GTK_MODULES=gail:atk-bridge
|
||||
NO_AT_BRIDGE=0
|
||||
ACCESSIBILITY_ENABLED=1
|
||||
```
|
||||
|
||||
These environment variables are picked up by MATE applications when launched from `.xsession`, making the widget tree available to AT-SPI consumers such as `pyatspi`.
|
||||
|
||||
### Phase 6 — PulseAudio and RDP audio
|
||||
|
||||
See [Sound device configuration](#sound-device-configuration) below.
|
||||
|
||||
### Phase 7 — Packages, Chrome, Python environment, Agent-S
|
||||
|
||||
- Installs OCR support (`tesseract-ocr`), Python 3, and assistive tech libraries
|
||||
- Downloads and installs Google Chrome from the official .deb
|
||||
- Creates a Python venv at `~/env/agents` with `--system-site-packages` (so `pyatspi` and `python3-gi`, installed system-wide, are available)
|
||||
- Extracts Agent-S into `~/gh/Agent-S`
|
||||
|
||||
### Phase 8 — XRDP Xorg configuration and session
|
||||
|
||||
Deploys `xorg.conf.j2` to `/etc/X11/xrdp/xorg.conf` and writes `.xsession` to start MATE:
|
||||
|
||||
```
|
||||
exec mate-session
|
||||
```
|
||||
|
||||
Any change to the Xorg config triggers the `restart xrdp` handler.
|
||||
|
||||
---
|
||||
|
||||
## X Server / RDP configuration
|
||||
|
||||
The Xorg config is templated from `ansible/agent_s/xorg.conf.j2` and deployed to `/etc/X11/xrdp/xorg.conf`.
|
||||
|
||||
### Resolution
|
||||
|
||||
The display is pinned to **1024×768**. UI-TARS and similar vision–language models are trained on screenshots at this resolution; higher resolutions degrade accuracy. Fallback modelines for 800×600 and 640×480 satisfy the `xrdpdev` driver's internal requirements but are never selected in normal operation.
|
||||
|
||||
### Module loading order
|
||||
|
||||
```
|
||||
Load "glamoregl" ← must precede xorgxrdp
|
||||
Load "xorgxrdp"
|
||||
```
|
||||
|
||||
`xorgxrdp 0.10.2` (shipped in Ubuntu 25.04) depends on the symbol `glamor_xv_init`, which lives in `libglamoregl.so`. If `glamoregl` is not loaded first, `libxorgxrdp.so` fails with:
|
||||
|
||||
```
|
||||
undefined symbol: glamor_xv_init
|
||||
```
|
||||
|
||||
This cascades — `xrdpdev`, `xrdpmouse`, and `xrdpkeyb` all fail because they depend on symbols exported by `libxorgxrdp.so`.
|
||||
|
||||
### Device section
|
||||
|
||||
The Device section uses only the `xrdpdev` virtual framebuffer driver with no DRM/GPU options:
|
||||
|
||||
```
|
||||
Section "Device"
|
||||
Identifier "Video Card (xrdpdev)"
|
||||
Driver "xrdpdev"
|
||||
EndSection
|
||||
```
|
||||
|
||||
DRM options (`DRMDevice`, `DRI3`, `DRMAllowList`) are not applicable to the virtual framebuffer and were the source of a previous misconfiguration on `larissa`.
|
||||
|
||||
### Display variable
|
||||
|
||||
The agent environment sets `DISPLAY=:10.0` (via `~/.agent_s_env`). XRDP assigns display numbers starting at `:10` by default.
|
||||
|
||||
---
|
||||
|
||||
## Sound device configuration
|
||||
|
||||
RDP audio redirection requires two components:
|
||||
|
||||
1. **PulseAudio** — the userspace sound server
|
||||
2. **pulseaudio-module-xrdp** — a PulseAudio sink/source module that forwards audio to the RDP client
|
||||
|
||||
### Build process
|
||||
|
||||
`pulseaudio-module-xrdp` must be compiled against the PulseAudio headers matching the exact version running on the target host. The playbook automates this:
|
||||
|
||||
1. Enables `deb-src` entries in `/etc/apt/sources.list.d/ubuntu.sources`
|
||||
2. Runs `apt-get source pulseaudio` into `/usr/local/src/`
|
||||
3. Generates `config.h` by running `meson setup build` in the PulseAudio source tree
|
||||
4. Extracts `pulseaudio-module-xrdp` from the staged tarball into `/usr/local/src/pulseaudio-module-xrdp/`
|
||||
5. Runs `./bootstrap && ./configure PULSE_DIR=<pulse-src> && make && make install`
|
||||
|
||||
Steps 4–5 are skipped if `module-xrdp-sink.so` is already present under `/usr/lib/pulse-*/modules/`. Re-running the playbook after a PulseAudio upgrade will trigger a rebuild because the old `.so` won't be found at the new versioned path.
|
||||
|
||||
### How audio flows
|
||||
|
||||
```
|
||||
MATE application
|
||||
→ PulseAudio (userspace daemon, per-user session)
|
||||
→ module-xrdp-sink (installed by pulseaudio-module-xrdp)
|
||||
→ XRDP audio channel
|
||||
→ RDP client
|
||||
```
|
||||
|
||||
PulseAudio is started automatically as part of the MATE session (`mate-session` launches `pulseaudio --start`). No additional service unit is required.
|
||||
|
||||
---
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `principal_user` | `robert` | Username for the human account the agent runs as |
|
||||
| `agent_s_rel` | `main` | Git branch/tag to stage from `~/gh/Agent-S` |
|
||||
| `pulseaudio_module_xrdp_rel` | `devel` | Git branch/tag to stage from `~/gh/pulseaudio-module-xrdp` |
|
||||
| `agent_s_venv` | `/home/{{ principal_user }}/env/agents` | Path to the Python virtual environment |
|
||||
| `agent_s_repo` | `/home/{{ principal_user }}/gh/Agent-S` | Extraction path for the Agent-S source |
|
||||
|
||||
All variables are set in `ansible/inventory/host_vars/larissa.helu.ca.yml` (`principal_user`) and `ansible/inventory/group_vars/all/vars.yml` (release branches).
|
||||
|
||||
---
|
||||
|
||||
## Environment activation
|
||||
|
||||
After deployment, activate the Agent-S environment on the target host:
|
||||
|
||||
```bash
|
||||
source ~/.agent_s_env
|
||||
```
|
||||
|
||||
This activates the virtual environment, sets `AGENT_S_HOME`, `DISPLAY`, and exports `HF_TOKEN` and `OPENAI_API_KEY` placeholder values that must be filled in for model inference.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### X server fails to start — `undefined symbol: glamor_xv_init`
|
||||
|
||||
`glamoregl` is missing or ordered after `xorgxrdp` in the Module section. Check `/etc/X11/xrdp/xorg.conf` and ensure `Load "glamoregl"` appears before `Load "xorgxrdp"`.
|
||||
|
||||
### Black screen on RDP connect
|
||||
|
||||
Confirm `.xsession` contains `exec mate-session` and is executable (`chmod 0755`). Check `/var/log/xrdp-sesman.log` for session startup errors.
|
||||
|
||||
### No audio in RDP session
|
||||
|
||||
Verify `module-xrdp-sink.so` is installed:
|
||||
|
||||
```bash
|
||||
find /usr/lib/pulse-*/modules/ -name 'module-xrdp-sink.so'
|
||||
```
|
||||
|
||||
If absent, re-run the playbook. If PulseAudio was upgraded, the old `.so` path will not exist and the build steps will execute automatically.
|
||||
|
||||
### Accessibility / AT-SPI not working
|
||||
|
||||
Confirm the profile script is loaded in the session:
|
||||
|
||||
```bash
|
||||
echo $GTK_MODULES # should include atk-bridge
|
||||
```
|
||||
|
||||
If empty, verify `/etc/profile.d/atspi.sh` exists and the session was started via `.xsession` (not a display manager session that bypasses `/etc/profile.d/`).
|
||||
@@ -522,6 +522,69 @@ ansible-playbook myapp/deploy.yml
|
||||
- [Ansible Vault Guide](https://docs.ansible.com/ansible/latest/vault_guide/index.html)
|
||||
- [Inventory Organization](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html)
|
||||
|
||||
## Account Taxonomy
|
||||
|
||||
Standardized account roles used across Ansible and Terraform. This taxonomy eliminates confusion between Ansible reserved connection keywords (`remote_user` in `ansible.cfg`) and infrastructure-managed account variables in playbooks.
|
||||
|
||||
| Role | Variable | Example | Home | Sudo | Purpose |
|
||||
|------|----------|---------|------|------|---------|
|
||||
| user | *(login name)* | robert:1000 | /home/robert | varies | Human user account |
|
||||
| service_user | `{service}_user` | arke:500 | /srv/arke | no | Service daemon account |
|
||||
| keeper_user | `keeper_user` | ponos:519 | /srv/ponos | yes | Ansible/Terraform management (sudo) |
|
||||
| watcher_user | `watcher_user` | poros:520 | — | no | Non-sudo observation account |
|
||||
| principal_user | `principal_user` | robert:1000 | /home/robert | varies | AI agent collaborative account |
|
||||
|
||||
### Key Rules
|
||||
|
||||
- **`keeper_user`** replaces all uses of `{{ ansible_user }}` and `{{ remote_user }}` as Jinja2 variables in playbooks
|
||||
- **`ansible.cfg`** retains `remote_user = ponos` as the SSH connection keyword (Ansible built-in) — this is not a Jinja2 variable
|
||||
- **`service_user`** accounts live in `/srv/{service}` — if currently in `/home`, they migrate on next re-provision
|
||||
- **`watcher_user`** is provisioned by Ansible playbook when needed (not via cloud-init)
|
||||
- **`principal_user`** is for AI agent hosts where the agent operates on behalf of a human user; define in `host_vars/{hostname}.yml`
|
||||
- Do **not** use `vault_` prefix for any of these — that prefix is reserved for Ansible Vault variables
|
||||
|
||||
### Variable Definitions
|
||||
|
||||
All taxonomy variables are defined in `inventory/group_vars/all/vars.yml`:
|
||||
|
||||
```yaml
|
||||
# Account Taxonomy
|
||||
keeper_user: ponos
|
||||
keeper_uid: 519
|
||||
keeper_group: ponos
|
||||
keeper_home: /srv/ponos
|
||||
watcher_user: poros
|
||||
watcher_uid: 520
|
||||
```
|
||||
|
||||
`principal_user` is host-specific and defined in the relevant `host_vars` file:
|
||||
|
||||
```yaml
|
||||
# inventory/host_vars/caliban.incus.yml
|
||||
principal_user: robert
|
||||
principal_uid: 1000
|
||||
```
|
||||
|
||||
### Bootstrap Chain
|
||||
|
||||
1. **Terraform** provisions `ponos` (keeper_user) on all containers via `cloud-init`
|
||||
- UID 519, home `/srv/ponos`, sudoers, SSH authorized keys at `/srv/ponos/.ssh/authorized_keys`
|
||||
2. **`ansible.cfg`** sets `remote_user = ponos` so all Ansible connections use the keeper account
|
||||
3. **Playbooks** reference `{{ keeper_user }}` for any task that needs the management account name
|
||||
|
||||
### Playbook Pattern
|
||||
|
||||
```yaml
|
||||
- name: Add keeper_user to service group
|
||||
become: true
|
||||
ansible.builtin.user:
|
||||
name: "{{ keeper_user }}"
|
||||
groups: "{{ service_group }}"
|
||||
append: true
|
||||
```
|
||||
|
||||
**Never use** `{{ ansible_user }}` or `{{ remote_user }}` as Jinja2 template variables in tasks — these shadow Ansible built-in connection variables and cause unpredictable behaviour.
|
||||
|
||||
## Secret Management Patterns
|
||||
|
||||
### Ansible Vault (Sandbox Environment)
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
Calculate sunrise and sunset times for the current or targetted day.
|
||||
The times can be adjusted either for twilight or fixed durations.
|
||||
|
||||
The program can either: wait for sunrise or sunset (function: wait),
|
||||
or return the time (GMT or local) the event occurs (function: list),
|
||||
or report the day length and twilight timings (function: report),
|
||||
or simply report if it is DAY or NIGHT (function: poll).
|
||||
|
||||
You should specify the latitude and longitude of your target location.
|
||||
|
||||
|
||||
Usage: sunwait [major options] [minor options] [twilight type] [rise|set] [offset] [latitude] [longitude]
|
||||
|
||||
Major options, either:
|
||||
poll Returns immediately indicating DAY or NIGHT. See 'program exit codes'. Default.
|
||||
wait Sleep until specified event occurs. Else exit immediate.
|
||||
list [X] Report twilight times for next 'X' days (inclusive). Default: 1.
|
||||
report [date] Generate a report about the days sunrise and sunset timings. Default: the current day
|
||||
|
||||
Minor options, any of:
|
||||
[no]debug Print extra info and returns in one minute. Default: nodebug.
|
||||
[no]version Print the version number. Default: noversion.
|
||||
[no]help Print this help. Default: nohelp.
|
||||
[no]gmt Print times in GMT or local-time. Default: nogmt.
|
||||
|
||||
Twilight types, either:
|
||||
daylight Top of sun just below the horizon. Default.
|
||||
civil Civil Twilight. -6 degrees below horizon.
|
||||
nautical Nautical twilight. -12 degrees below horizon.
|
||||
astronomical Astronomical twilight. -18 degrees below horizon.
|
||||
angle [X.XX] User-specified twilight-angle (degrees). Default: 0.
|
||||
|
||||
Sunrise/sunset. Only useful with major-options: 'wait' and 'list'. Any of: (default: both)
|
||||
rise Wait for the sun to rise past specified twilight & offset.
|
||||
set Wait for the sun to set past specified twilight & offset.
|
||||
|
||||
Offset:
|
||||
offset [MM|HH:MM] Time interval (+ve towards noon) to adjust twilight calculation.
|
||||
|
||||
Target date. Only useful with major-options: 'report' or 'list'. Default: today
|
||||
d [DD] Set the target Day-of-Month to calculate for. 1 to 31.
|
||||
m [MM] Set the target Month to calculate for. 1 to 12.
|
||||
y [YYYY] Set the target Year to calculate for. 2000 to 2099.
|
||||
|
||||
latitude/longitude coordinates: floating-point degrees, with [NESW] appended. Default: Bingham, England.
|
||||
|
||||
Exit (return) codes:
|
||||
0 OK: exit from 'wait' or 'list' only.
|
||||
1 Error.
|
||||
2 Exit from 'poll': it is DAY or twilight.
|
||||
3 Exit from 'poll': it is NIGHT (after twilight).
|
||||
|
||||
Example 1: sunwait wait rise offset -1:15:10 51.477932N 0.000000E
|
||||
Wait until 1 hour 15 minutes 10 secs before the sun rises in Greenwich, London.
|
||||
|
||||
Example 2: sunwait list 7 civil 55.752163N 37.617524E
|
||||
List civil sunrise and sunset times for today and next 6 days. Moscow.
|
||||
|
||||
Example 3: sunwait poll exit angle 10 54.897786N -1.517536E
|
||||
Indicate by program exit-code if is Day or Night using a custom twilight angle of 10 degrees above horizon. Washington, UK.
|
||||
|
||||
Example 4: sunwait list 7 gmt sunrise angle 3
|
||||
List next 7 days sunrise times, custom +3 degree twilight angle, default location.
|
||||
Uses GMT; as any change in daylight saving over the specified period is not considered.
|
||||
|
||||
Example 5: sunwait report y 20 m 3 d 15 10.49S 105.55E
|
||||
Produce a report of the different sunrises and sunsets on an arbitrary day (2022/03/15) for an arbitrary location (Christmas Island)
|
||||
|
||||
Note that program uses C library functions to determine time and localtime.
|
||||
Error for timings are estimated at: +/- 4 minutes.
|
||||
Reference in New Issue
Block a user