Skip to content

Architecture

Melvin PETIT edited this page Jun 17, 2026 · 1 revision

Architecture

A tour of how Medusa is laid out and why.

Layout

Medusa/
├── medusa.sh                   Entry point: loads lib/ in order, dispatches
│                               `menu` (default) or a CLI subcommand.
├── lib/
│   ├── core.sh                 Colors, globals, tool registry, generic Docker
│                               helpers, install primitives, prompt helpers.
│   ├── modules.sh              Menu rendering, status dashboard, start/stop all,
│                               environment selection at startup.
│   ├── run_cli.sh              Guided sub-menus for CLI tools (run_<tool>).
│   ├── deploy_soc.sh           14 SOC deployers.
│   ├── deploy_grc.sh           5 GRC deployers.
│   ├── deploy_integration.sh   11 Integration deployers.
│   └── deploy_ot.sh            5 OT deployers.
└── medusa_deployments/         Generated at runtime (git-ignored).
    └── <env_name>/
        └── <tool>/
            ├── docker-compose.yml
            ├── .env
            └── credentials.txt   (chmod 600)

Boot sequence

  1. medusa.sh resolves its own absolute path into MEDUSA_HOME, the anchor for BASE_DIR. Every path helper (tool_dir, the deployers) therefore builds absolute paths, so a cd deep inside the menu loop never breaks subsequent relative paths.
  2. The original argv is captured into MEDUSA_ARGV so a privilege-escalation re-exec (sudo) resumes the same command instead of dropping to the menu.
  3. The seven lib/ files are sourced in dependency order: coredeploy_socdeploy_grcdeploy_integrationdeploy_otrun_climodules. Each has a _LOADED guard, so double-sourcing is a no-op.
  4. main "$@" reads the first argument to choose interactive menu mode or a CLI subcommand.

The script runs under set -uo pipefail. set -e is deliberately omitted, an interactive menu must survive a failing read or a grep with no match without the whole loop dying.

Tool registry

Every tool is a single register_tool call in core.sh:

register_tool "wazuh" "soc" "docker" "SIEM/XDR - Detection, response, compliance"
#               name   cat   type     description

Three parallel associative arrays, TOOL_DESC, TOOL_CAT, TOOL_TYPE, are populated. Everything that iterates tools (menu, dashboard, start-all, list) reads from them.

type is one of:

  • docker — the deployer writes a docker-compose.yml (or clones an upstream repo containing one) and runs docker compose up -d.
  • cli — the deployer installs a binary (apt, pip/pipx, curl | sh) and marks it with mark_cli_installed.
  • vm — instructions only, no automation (Security Onion ISO, GRFICSv2 lab, GRASSMARLIN jar).

Dispatchers

dispatch_deploy() { local f="deploy_${1//-/_}"; declare -f "$f" >/dev/null && "$f" || log_message error "no deployer for $1"; }
dispatch_run()    { local f="run_${1//-/_}";    declare -f "$f" >/dev/null && "$f" || log_message error "no run menu for $1"; }

This is what makes the "add a tool in 5 lines" pattern work: define deploy_<tool> (and optionally run_<tool>), add a register_tool line, nothing else needs wiring. See Adding-a-Tool.

State model

get_tool_status returns one of:

State Meaning
not_installed No marker file, no binary on PATH
cli_installed .installed marker exists, or command -v <tool> succeeds
installed Docker tool with a compose file, no running container
running Docker tool with at least one container reporting Up
stopped Docker tool with a compose file, every container down

The state determines which actions the per-tool menu offers.

What Medusa is NOT

  • No database, no state file. The medusa_deployments/ filesystem is the source of truth.
  • No background daemon. Medusa is invoked, acts, exits.
  • No abstraction over Docker engines beyond detecting docker compose vs docker-compose.

Next: Environments · Adding-a-Tool

Clone this wiki locally