Skip to content

Architecture

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

Architecture

Bull is a modular Bash application. bull.sh is the entry point; the real work lives in sourced library files under lib/.

Layout

bull.sh                 Entry point: TUI + CLI dispatch
install.sh              Unattended host setup (Vagrant + hypervisor)
lib/
  core.sh               Colors, logging, provider/WSL detection, deps, GPG
  inventory.sh          VM inventory CRUD (JSON via jq)
  vagrant.sh            VM lifecycle (create, start, stop, destroy, snapshots)
  vpn.sh                VPN configuration + iptables kill switch
  toolkits.sh           Toolkit installation + persistent registry
configs/
  Vagrantfile.template  Provider-specific Vagrant template
  kali-provision.sh     Kali provisioning (runs inside the VM as root)
  parrot-provision.sh   Parrot provisioning
  ansible-playbook.yml  Kali Ansible playbook
  parrot-playbook.yml   Parrot Ansible playbook
docs/                   Architecture, contributing, security, tooling guides

Boot sequence

bull.sh
  ├── require root
  ├── source lib/core.sh        (colors, logging, detection, GPG)
  ├── source lib/inventory.sh   (JSON inventory)
  ├── source lib/vagrant.sh     (VM lifecycle)
  ├── source lib/vpn.sh         (VPN + kill switch)
  ├── source lib/toolkits.sh    (toolkit registry)
  ├── parse_arguments "$@"
  └── no command → interactive_loop()
      else        → execute_command()

Each library guards against double-sourcing with a _BULL_<MOD>_LOADED sentinel, and core.sh must load first because the others use its logging and helpers.

Key design decisions

Provider abstraction

Bull checks for /dev/kvm and sets BULL_PROVIDER to libvirt or virtualbox (overridable via the environment). Provider-specific logic is confined to lib/vagrant.sh and configs/Vagrantfile.template; the rest of the code is provider-agnostic. VAGRANT_DEFAULT_PROVIDER is exported so Vagrant never iterates over all providers — this avoids a Hyper-V detection crash in Vagrant 2.4.x on WSL2.

Command indirection

All Vagrant calls go through ${VAGRANT_CMD}, which resolves to vagrant or, on WSL2 with VirtualBox, vagrant.exe. This keeps a single code path working across native Linux and both WSL2 configurations.

Credential security

Passwords are encrypted with GPG (AES256 cipher, SHA512 digest, 65M S2K iterations) and stored in .credentials.gpg per VM. The plaintext exists only in memory during provisioning and is wiped from environment variables immediately after use. The Vagrantfile, which briefly holds the password for provisioning, is chmod 600 and sanitized afterward. See the Security Model.

Inventory

VM metadata lives in a JSON inventory under BULL_HOME. Every mutation goes through inventory_* functions that write atomically (mktemp + mv) to avoid corruption, and bull sync reconciles it with the hypervisor's actual state.

Toolkit registry

Saved toolkits live in ${BULL_HOME}/toolkits.json. Tools are installed inside VMs via git clone over vagrant ssh -c, with URL and name validation to prevent shell injection. See the Toolkit Manager.

WSL2 adaptation

core.sh detects WSL2 and adjusts VAGRANT_CMD, VAGRANT_HOME, BULL_HOME, and the PATH, and installs cmd.exe / powershell.exe shims so Vagrant's Windows checks pass on the libvirt path. See Installation.

Dependencies

Dependency Purpose
Vagrant 2.3+ VM provisioning
libvirt/KVM or VirtualBox Hypervisor
jq JSON inventory manipulation
gpg Credential encryption
ssh / sshpass VM connections
curl or wget Box and key downloads
openssl Password generation

The in-repo ARCHITECTURE.md tracks the same design at source level.

Clone this wiki locally