Botica

Environment diagnostics, health checks, and feature flags for Elixir.

Hex Version Hex Docs License

Botica gives you two complementary tools:

  1. Botica.Doctor — define health checks, run them in parallel, summarise results, and optionally auto-fix failures.
  2. Botica.Flags — feature flags with an ETS backend and deterministic per-entity rollouts. No Redis. No Postgres. Just Elixir.

Installation

def deps do
  [
    {:botica, "~> 2.1"}
  ]
end

Usage — Health checks

config = %{
  app_name: "myapp",
  checks: [
    %{
      id: :postgresql,
      name: "PostgreSQL",
      description: "Database server is running",
      priority: 1,
      check: fn ->
        case System.cmd("pg_isready", [], stderr_to_stdout: true) do
          {_, 0} -> {:ok, "PostgreSQL is ready"}
          {output, _} -> {:error, "PostgreSQL not ready: #{output}"}
        end
      end,
      fix: fn -> {:ok, "sudo systemctl start postgresql"} end,
      fix_command: "sudo systemctl start postgresql"
    }
  ]
}

{:ok, results} = Botica.Doctor.run(config)
summary = Botica.Doctor.summary(results)
# => %{ok: 1, warning: 0, error: 0, total: 1, passed?: true}

Crash isolation — every check runs in an unlinked, monitored process. A check that calls exit/1 or throws an unhandled exception cannot kill the caller; it is reported as an error result instead. Likewise :DOWN messages from the monitor are always flushed so the caller's mailbox stays clean.

Validation — Botica.Doctor.run/2 rejects configurations with an empty checks: [] list and returns {:error, "config.checks must contain at least one check"}.

Botica also ships predefined batteries: PostgreSQL, Redis, Memory, Disk.

Usage — Feature flags

# Define flags (typically at boot)
Botica.Flags.define(:new_dashboard, default: false)
Botica.Flags.define(:beta_search, default: true)
Botica.Flags.define(:rate_limiting, default: false, rollout: 25)

# Query
Botica.Flags.enabled?(:new_dashboard)                       # => false
Botica.Flags.enabled?(:beta_search)                         # => true
Botica.Flags.enabled?(:rate_limiting, for: current_user.id) # => true / false deterministically

# Toggle at runtime
Botica.Flags.enable(:new_dashboard)
Botica.Flags.disable(:new_dashboard)
Botica.Flags.set(:rate_limiting, rollout: 50)

# Introspection
Botica.Flags.all()         # [%Flag{...}, ...]
Botica.Flags.get(:foo)      # {:ok, %Flag{...}} | :error
Botica.Flags.count()        # 3

Flags in the Doctor banner

The Doctor also exposes a feature-flags diagnostic — useful for botica doctor-style CLI banners:

banner = Botica.Doctor.format_flags_summary()

# Flags (3 defined):
#   ✓ beta_search     enabled            (default: true)
#   ✗ new_dashboard   disabled           (default: false)
#   ~ rate_limiting   rollout 25%        (default: false)

How the flags work

Configuration

Botica.Application starts the Botica.Flags.Store automatically when you list botica as a dependency. You can customise the supervision tree by overriding mod: in your own mix.exs.

Architecture

Botica (top-level facade — defdelegates)
├── Doctor          — entry point: run/1, fix/1, summary/1, health_check/1
│   ├── Batteries   — predefined checks (PostgreSQL, Redis, Memory, Disk)
│   ├── Check       — behaviour (Botica.Check.Behaviour) + result struct
│   ├── Runner      — orchestration layer
│   │   ├── Executor  — parallel execution with timeout + concurrency caps
│   │   └── Sequencer — priority sorting, tag/fixable filtering
│   ├── Repair      — auto-fix orchestration (Botica.Repair.Fixer)
│   └── Flags       — feature-flags diagnostic (flags_summary, format_flags_summary)
└── Flags           — feature flags (define, enable, disable, rollout, etc.)
    ├── Flag        — struct + factory with rollout clamping
    └── Store       — GenServer that owns the :botica_flags ETS table

Documentation

License

MIT — see LICENSE.md.