Skip to content

Updating the Ansible Code

The YAML editor holds the tasks the plugin runs. Plugin YAML in Rogue Arena is standard Ansible with a few load-bearing rules that differ from how you’d write a standalone playbook. Read this page carefully. The differences are small but strict, and most first-build failures come from missing one of them.

Rule 1: Task List Only, No Playbook Header

Section titled “Rule 1: Task List Only, No Playbook Header”

This is the biggest difference between a Rogue Arena plugin and a normal Ansible playbook. Your YAML is a flat task list. At runtime the platform wraps it in a play (it sets hosts, vars, and the tasks: collection). You don’t write the wrapper.

Correct (start directly with - name: tasks):

- name: Create staging dir
ansible.windows.win_file:
path: "C:\\PluginSetup"
state: directory
- name: Stage installer from vault
ansible.windows.win_copy:
src: my-installer.exe
dest: "C:\\PluginSetup\\my-installer.exe"

Wrong (do not include playbook structure):

---
- hosts: all
tasks:
- name: Create staging dir
...

Leave out the --- at the top, the - hosts: line, and the outer tasks: key. If you paste a snippet from the public Ansible docs that includes a playbook header, remove the header before you save.

Everything runs inline. You cannot use import_tasks or include_tasks, or reference separate YAML files. The whole plugin is one continuous task list.

Rule 2: Stage From the Vault Before You Install

Section titled “Rule 2: Stage From the Vault Before You Install”

Non-apt resources (.exe, .msi, .deb, Docker images, git tarballs, ISOs, etc.) live in the plugin vault. You upload them once during development, and they’re pulled onto the target VM at runtime. You manage the vault in the Resources panel.

- name: Stage VLC installer from plugin vault
ansible.windows.win_copy:
src: vlc-3.0.20-win64.exe
dest: "C:\\PluginSetup\\vlc-3.0.20-win64.exe"

For copies on the target VM later in the play, such as mirroring a config to a second path, moving a staged file into place, or duplicating a file within the machine, use a shell-level copy:

  • Windows: Copy-Item inside ansible.windows.win_powershell or win_shell
  • Linux: cp / mv inside ansible.builtin.shell

To move files between two VMs, run a shell command on the source VM (robocopy, scp, or Copy-Item over a UNC path).

Apt is the runtime exception. Rogue Arena ships an apt mirror inside every Architect scenario, with most popular repositories already set up. On Linux machines, apt install what you need without adding repositories first:

- name: Install Docker
apt:
pkg:
- docker-ce
- docker-ce-cli
- containerd.io
state: present
update_cache: yes

Use the vault for anything the mirror doesn’t have. Downloading from the public internet at runtime (wget, curl, Invoke-WebRequest to the open internet) is a failure state. Fetch those files into the vault during development.

Rule 3: Write Text Files Inline Via content:

Section titled “Rule 3: Write Text Files Inline Via content:”

Scripts, configs, and other text files don’t need to go in the vault. Use win_copy / copy with a content: block to write them directly onto the target:

- name: Write PowerShell setup script
ansible.windows.win_copy:
content: |
$ErrorActionPreference = 'Stop'
Write-Host "Running setup"
# ...
dest: 'C:\PluginSetup\setup.ps1'
- name: Write bash setup script
ansible.builtin.copy:
content: |
#!/bin/bash
set -e
echo "Running setup"
dest: /tmp/plugin-setup/setup.sh
mode: '0755'

Keep text in the YAML, and save the vault for binaries.

Windows modules come from two collections, and using the wrong namespace fails the build:

  • ansible.windows.*: core Windows modules (bundled with Ansible)
  • community.windows.*: extended Windows modules (community-maintained)

Common mistakes:

WrongRight
ansible.windows.win_lineinfilecommunity.windows.win_lineinfile
ansible.windows.win_firewall_rulecommunity.windows.win_firewall_rule
ansible.windows.win_unzipcommunity.windows.win_unzip

Always use the fully qualified collection name (FQCN). The platform mapping at a glance:

WindowsLinux
ansible.windows.win_copyansible.builtin.copy
ansible.windows.win_fileansible.builtin.file
ansible.windows.win_statansible.builtin.stat
ansible.windows.win_shellansible.builtin.shell
ansible.windows.win_powershelln/a (use shell)
ansible.windows.win_rebootansible.builtin.reboot
ansible.windows.win_serviceansible.builtin.service

Rule 5: Windows Paths Use Doubled Backslashes

Section titled “Rule 5: Windows Paths Use Doubled Backslashes”

In YAML double-quoted strings, a single backslash starts an escape sequence (\t = tab, \n = newline, \V = parse error). The house style is double-quoted Windows paths with \\:

- name: Check file
ansible.windows.win_stat:
path: "C:\\Program Files\\MyApp\\app.exe"

Exception: inside script: | blocks, content passes raw to PowerShell. Use normal single-backslash paths:

- name: PowerShell script
ansible.windows.win_powershell:
script: |
$path = "C:\scripts\file.exe"
Copy-Item -Path $path -Destination "C:\temp\"

Rule 6: Default to No Privilege Escalation

Section titled “Rule 6: Default to No Privilege Escalation”

By default, plugins run as SYSTEM on Windows and root on Linux. Don’t use become, ansible_become, or runas unless you need to run in a specific user’s context.

You do NOT need become for (this list is long, so learn it well):

  • Writing files to system paths (C:\, C:\temp, /etc/, /opt/, etc.)
  • Modifying HKLM (HKEY_LOCAL_MACHINE) registry
  • Installing software system-wide
  • Managing services
  • Creating directories anywhere on the system
  • Active Directory operations from a domain-joined machine (SYSTEM has domain access)

You DO need become/runas for (narrow cases):

  • Modifying user-specific registry hives (HKCU, or HKEY_CURRENT_USER)
  • Touching user profile folders that SYSTEM can’t read
  • Running processes that must appear in a user’s session context
  • AD operations that require a specific user identity (Domain Admin, etc.)

Example: an HKCU registry edit (the rare case where runas is needed):

- name: Change user wallpaper
ansible.windows.win_powershell:
script: |
Set-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name Wallpaper -Value "C:\wallpaper.jpg"
RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters
vars:
ansible_become: true
ansible_become_method: runas
ansible_become_user: "{{ username }}"
ansible_become_password: "{{ user_password }}"

Example: an AD operation that needs a Domain Admin token:

- name: Run as Domain Admin
ansible.windows.win_powershell:
script: |
Import-Module ActiveDirectory
# ... AD operations
vars:
ansible_become: true
ansible_become_method: runas
ansible_become_user: "Administrator@{{ DomainNameFQDN }}"
ansible_become_password: "{{ domain_admin_password }}"

CSV parameters arrive as raw strings that often include a BOM and Windows carriage returns. Strip both first:

- name: Normalize CSV
ansible.builtin.set_fact:
my_csv_clean: "{{ my_csv_param | default('') | regex_replace('^\\ufeff','') | regex_replace('\\r','') | trim }}"
- name: Parse CSV to list of objects
ansible.builtin.set_fact:
my_records: "{{ my_csv_clean | community.general.from_csv }}"

For complex processing (bulk AD creation, etc.), write the cleaned CSV to a file and parse it with PowerShell Import-Csv.

A green build doesn’t prove the install worked. Add checks after every install or configuration step:

- name: Check application installed
ansible.windows.win_stat:
path: "C:\\Program Files\\MyApp\\app.exe"
register: app_check
- name: Fail if not found
ansible.builtin.fail:
msg: "Installation failed — application not found"
when: not app_check.stat.exists

Service checks, port polls, and retry-until-condition loops follow the same idea. A plugin that finishes silently and leaves the box half-configured is the worst kind of bug.

Track whether each operation changed anything. PowerShell scripts output JSON with a changed flag, and Ansible reads it through changed_when:

- name: Configure setting (idempotent)
ansible.windows.win_powershell:
script: |
$changed = $false
if ($currentValue -ne $desiredValue) {
Set-Something -Value $desiredValue
$changed = $true
}
[pscustomobject]@{ changed = $changed } | ConvertTo-Json
register: config_result
changed_when: config_result.output is search('"changed"\s*:\s*true')

Re-running a finished plugin should be a no-op. If the second run reports changes, you have a bug.

This example puts the standard pattern together: stage from the vault, install, clean up, and validate. (Some plugins also start with a set_fact block that keeps version numbers and staging paths in one place. It’s optional but common.)

- name: Create staging folder
ansible.windows.win_file:
path: "C:\\PluginSetup"
state: directory
- name: Stage VLC installer from plugin vault
ansible.windows.win_copy:
src: vlc-3.0.20-win64.exe
dest: "C:\\PluginSetup\\vlc-3.0.20-win64.exe"
- name: Install VLC silently
ansible.windows.win_powershell:
script: |
C:\PluginSetup\vlc-3.0.20-win64.exe /L=1033 /S
- name: Pause to let VLC setup finish
ansible.builtin.pause:
minutes: 4
- name: Remove staging folder
ansible.windows.win_file:
path: "C:\\PluginSetup"
state: absent
- name: Check for VLC executable
ansible.windows.win_stat:
path: "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe"
register: vlc_check
- name: Fail if VLC is missing
ansible.builtin.fail:
msg: "Validation failed — VLC executable not found"
when: not vlc_check.stat.exists

Every plugin worth shipping follows this shape.

The Plugin Browser shows the YAML of every plugin you can see. When you’re stuck, pick a published plugin that does something similar, clone it, and read its task list. After three or four reference plugins, you’ll know the patterns.

If you’d rather not write the YAML by hand, the Agent MCP plugin includes two skills built for plugin work:

  • /rogue-plugin-brainstorm: start a new plugin from scratch
  • /rogue-plugin-develop: iterate on an existing plugin

Both skills follow every rule on this page, and the YAML they produce goes straight into the Plugin Dev editor.

The skills help most with hard plugins: multi-machine server/client stacks like Elastic (Stack server on one box, Agent on every endpoint) and Splunk (indexer + forwarders), AD forest builds with trust relationships, Wireguard mesh deployments, and anything where half a dozen plays must run in the right order on the right boxes with the right parameters. Your agent handles these end to end. It researches install flows, splits the work across plugins, sets up the parent/child relationships, puts vault files where they belong, validates the offline install, and iterates against live deploy logs when something breaks. A plugin that takes a weekend to write by hand comes together in a single afternoon.