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, "~> 1.0"}
  ]
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}

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

  • Backend: a single ETS table (:botica_flags) with :set, :public, and read_concurrency: true. Reads are O(1) and lock-free.
  • Writes: serialised through Botica.Flags.Store (a GenServer) so concurrent defines / enables / disables never race.
  • Rollout: :erlang.phash2/2 with range 100 — same for: always gets the same answer, even across VM restarts.
  • Zero external deps — no Redis, no Postgres, no Mnesia.

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
  • Botica.Doctor is the main entry point. run/2 validates the config and hands it to Botica.Runner.Executor, which sorts the checks via Botica.Runner.Sequencer and runs them in parallel with per-check timeouts and a concurrency cap. fix/1 delegates to Botica.Repair.Fixer to apply fix functions on checks that errored.

  • Botica.Runner.Executor is the execution engine. It uses Task.async_stream with max_concurrency capped at 8 (configurable), handles per-check timeouts, and supports stop_on_first_error / continue_on_error flags. execute_sequential/1 is exposed for debugging or ordered runs.

  • Botica.Runner.Sequencer sorts checks by (priority, id) for deterministic execution, and provides filter_by_tags/2, filter_fixable/1, and group_by_tags/1 helpers.

  • Botica.Repair.Fixer runs fix functions for failed checks and returns a structured report: %{applied: [...], failed: [...], skipped: [...]}. Supports fix_one/2 for ad-hoc single-check repair.

  • Botica.Flags is the feature-flag subsystem, backed by a single ETS table (:botica_flags) owned by Botica.Flags.Store (GenServer). Writes go through the GenServer; reads are lock-free O(1).

Documentation

  • README.md — this file (English)
  • docs/README.es.md — Spanish version

License

MIT — see LICENSE.md.