Botica
Environment diagnostics, health checks, and feature flags for Elixir.
Botica gives you two complementary tools:
Botica.Doctor— define health checks, run them in parallel, summarise results, and optionally auto-fix failures.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, andread_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/2with range 100 — samefor: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.Doctoris the main entry point.run/2validates the config and hands it toBotica.Runner.Executor, which sorts the checks viaBotica.Runner.Sequencerand runs them in parallel with per-check timeouts and a concurrency cap.fix/1delegates toBotica.Repair.Fixerto apply fix functions on checks that errored.Botica.Runner.Executoris the execution engine. It usesTask.async_streamwithmax_concurrencycapped at 8 (configurable), handles per-check timeouts, and supportsstop_on_first_error/continue_on_errorflags.execute_sequential/1is exposed for debugging or ordered runs.Botica.Runner.Sequencersorts checks by(priority, id)for deterministic execution, and providesfilter_by_tags/2,filter_fixable/1, andgroup_by_tags/1helpers.Botica.Repair.Fixerruns fix functions for failed checks and returns a structured report:%{applied: [...], failed: [...], skipped: [...]}. Supportsfix_one/2for ad-hoc single-check repair.Botica.Flagsis the feature-flag subsystem, backed by a single ETS table (:botica_flags) owned byBotica.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.