Files
FabledSteward/docs/core/ansible.md
T
bvandeusen 230b542015 feat: rename to FabledScryer, multi-dashboard system, plugin management, branding
- Rename package fablednetmon → fabledscryer throughout
- Multi-dashboard: ownership, per-user defaults, HTMX edit (add/remove/reorder)
- Read-only share tokens scoped to individual dashboards
- Dashboard edit is HTMX-driven (no page reloads)
- Plugin management system: remote catalog, download/install, hot-reload, in-app restart
- plugin_index.py: fetch/cache remote index.yaml; default URL → bvandeusen/fabledscryer-plugins
- plugin_manager.py: download_and_install_plugin, hot_reload_plugin, restart_app
  - ZIP extraction handles GitHub archive formats (name-v1.0.0/, name-main/)
- Settings split into tabbed sections: General, Notifications, Ansible, Plugins
- Plugins tab: catalog browser (HTMX), install/activate/update/restart actions
- UI/branding: dark palette (#07071a), crystal ball SVG logo, animated star field,
  Libertinus Serif applied to headings, nav, labels, and section titles
- Widget registry (core/widgets.py) for dashboard plugin integration
- UPS widget.html (dashboard card) and settings/_tabs.html include
- Migrations 0005–0008: dashboards, is_default, ownership, share tokens
- docs/plugins/: writing-a-plugin.md updated with publishing guide,
  index.yaml.example template for fabledscryer-plugins repo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:27:56 -04:00

4.2 KiB

Ansible Integration

Fabled Scryer can browse, trigger, and stream output from Ansible playbooks directly from the web UI. Runs execute as asyncio tasks inside the same process — no Celery, no external workers.


Playbook Sources

Sources are configured at /settings/ under Ansible. Two source types are supported:

Local Filesystem

Points to a directory on the host that already contains playbooks.

# In app_settings (configured via UI)
ansible.sources:
  - name: "homelab"
    type: local
    path: "/opt/playbooks"

Git Repository

The app clones or pulls the repo into a local cache directory on a configurable schedule. All execution uses the local cache.

ansible.sources:
  - name: "infra-repo"
    type: git
    url: "https://github.com/example/infra.git"
    branch: "main"
    pull_interval_seconds: 300
    cache_path: "/data/playbook_cache/infra-repo"

Git sources register a ScheduledTask named ansible_git_pull_<name> that runs git pull on the configured interval.


Inventory Discovery

The app discovers inventory files within the root of the playbook source directory (non-recursive). It looks for files named:

  • hosts
  • inventory
  • inventory.yml
  • inventory.ini

A manual relative path can also be entered in the UI (e.g. inventories/production/hosts) for inventories in subdirectories.


Triggering a Run

From the UI at /ansible/, you can:

  1. Browse available playbooks across all sources
  2. View playbook contents before running
  3. Select an inventory file
  4. Trigger a run (requires operator or viewer role — execution is restricted to operator/admin)

The run flow:

  1. UI submits POST /ansible/runs with playbook_path, source_name, and inventory_path
  2. An AnsibleRun row is created with status = running
  3. Playbook execution starts as asyncio.create_task()
  4. The response returns the run_id and an HTMX partial that wires the SSE subscription
  5. The browser connects to GET /ansible/runs/<run_id>/stream to receive live output

SSE Streaming

Run output is streamed to the browser via Server-Sent Events (SSE) at:

GET /ansible/runs/<run_id>/stream

Each output line is sent as:

event: output
data: <line of stdout/stderr>

Run completion:

event: done
data: success|failed|interrupted

Output is flushed to the ansible_runs.output DB column every 50 lines or every 5 seconds (whichever comes first) and always on completion. This means partial output survives a process crash.

If a client connects after the run has already completed, the endpoint immediately sends event: done with the final status and closes the stream. To view stored output, use the run history view at GET /ansible/runs/<run_id>.


Run Lifecycle

Status Description
running Execution in progress
success Playbook exited 0
failed Playbook exited non-zero
interrupted App restarted while run was in progress

On startup, the app marks any runs still in running state as interrupted (see _mark_interrupted_runs() in app.py).

Output stored in the DB is capped at 1 MB. If truncated, [output truncated] is appended to the DB column, but the live SSE stream continues unaffected.


Data Model

ansible_runs table (defined in fabledscryer/models/ansible.py):

Column Type Description
id UUID Primary key
playbook_path str Relative path to playbook
inventory_path str Inventory path used
source_name str Name of the playbook source
triggered_by FK → users
status enum running, success, failed, interrupted
started_at timestamp UTC
finished_at timestamp UTC Null if still running
output text Captured stdout/stderr (capped at 1 MB)

Runs older than data.retention_days (default 90) are pruned by the data_cleanup scheduled task.


Source Files

File Purpose
fabledscryer/ansible/sources.py Source discovery, git pull logic
fabledscryer/ansible/executor.py Subprocess execution and output streaming
fabledscryer/ansible/routes.py HTTP routes (browse, trigger, stream, history)
fabledscryer/models/ansible.py AnsibleRun model