Skip to content

Updating the Ansible Code

The YAML editor is where the plugin actually does its work. Plugin YAML in Rogue Arena is standard Ansible with a few load-bearing rules that diverge from how you’d write a free-standing playbook. Read this page carefully — the differences are small but unforgiving, 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 single biggest difference between Rogue Arena plugins and a normal Ansible playbook. Your YAML is a flat task list. The platform wraps it in a play at runtime (sets hosts, vars, and the tasks: collection). You do not 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
...

No --- at the top. No - hosts: line. No outer tasks: key. If you paste a snippet from the public Ansible docs that includes a playbook header, strip it before saving.

Inline execution only: you cannot use import_tasks, include_tasks, or reference separate YAML files. Everything lives in 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 — uploaded once during development, then pulled onto the target VM at runtime. The vault is managed via 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 — mirroring a config to a second path, moving a staged file into place, duplicating something machine-internally — use a shell-level copy instead:

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

For movement between two VMs, use a shell command on the source VM (robocopy, scp, 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 wired up. On Linux machines, just apt install whatever you need — no repo-adding boilerplate:

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

The vault is for things the mirror doesn’t have. Public-internet downloads (wget, curl, Invoke-WebRequest to the open internet) at runtime are a failure state — fetch them into the vault during develop instead.

Rule 3 — Write Text Files Inline Via content:

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

Scripts, configs, and other text content don’t need to live in the vault. Use win_copy / copy with a content: block to write them straight 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'

Reserve the vault for binaries — keep text in YAML.

Rule 4 — Use the Right Module Collection

Section titled “Rule 4 — Use the Right Module Collection”

Windows modules live in two collections, and 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). 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, single backslashes are escape sequences (\t = tab, \n = newline, \V = parse error). 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”

Plugins run as SYSTEM on Windows and root on Linux out of the box. Do not use become, ansible_become, or runas unless you specifically need to drop into a user’s context.

You do NOT need become for (this list is huge — internalize it):

  • 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 — 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 — 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 — 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 carry a BOM and Windows carriage returns. Strip them up front:

- 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 let PowerShell Import-Csv chew on it.

A green build doesn’t mean the install worked. After every install / configuration step, drop in checks:

- 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, retry-until-condition — same idea. A plugin that finishes silently while leaving the box half-configured is the worst kind of bug.

Track whether operations actually changed anything. PowerShell scripts emit JSON with a changed flag; Ansible reads it via 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 your second run reports changes, you have a bug.

The canonical pattern stitched together — stage from vault, install, cleanup, validate. (Some plugins also drop an opening set_fact block to centralize version numbers and staging paths — 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

That’s the shape — every plugin worth shipping follows it.

The Plugin Browser exposes 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. Three or four reference plugins and you’ll have the patterns internalized.

If hand-authoring YAML isn’t your favorite use of time, the Claude MCP plugin ships two skills tailored to plugin work:

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

Both skills know every rule on this page and produce YAML that drops straight into the Plugin Dev editor.

The real magic shows up on the hard plugins — multi-machine server/client stacks like Elastic (Stack server on one box, Agent on every endpoint), Splunk (indexer + forwarders), AD forest builds with trust relationships, Wireguard mesh deployments, anything where a half-dozen plays have to land in the right order across the right boxes with the right parameters. Claude handles those end-to-end — researches install flows, splits the work across plugins, wires up the parent/child relationships, drops vault files where they need to go, validates the offline install, and iterates against live deploy logs when something breaks. The kind of plugin that takes a weekend of hand-authoring gets stitched together in a single afternoon.