Botica

Environment diagnostics and health checks for Elixir. Define checks and run diagnostics with structured results.

Installation

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

Dependencies

Botica requires:

Usage

Define a health check configuration

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"
},
%{
id: :elixir_version,
name: "Elixir Version",
description: "Check Elixir version is recent enough",
priority: 2,
check: fn ->
version = System.version()
if Version.match?(version, ">= 1.15.0") do
{:ok, "Elixir #{version}"}
else
{:warning, "Elixir #{version} is old"}
end
end,
fix: fn -> :skipped end,
fix_command: nil
}
]
}

Run diagnostics

# Run all checks in parallel
{:ok, results} = Botica.Doctor.run(config)
# Check results
Enum.each(results, fn result ->
IO.puts("#{result.name}: #{result.status} - #{result.message}")
end)
# Get summary
summary = Botica.Doctor.summary(results)
IO.puts("Passed: #{summary.ok}, Failed: #{summary.error}")

Run automatic fixes

# Run fixes for all failed checks
Botica.Doctor.fix(config)

Quick health check

# Returns a simple status map
result = Botica.Doctor.health_check(config)
# => %{status: :ok, summary: %{ok: 3, warning: 1, error: 0, total: 4, passed?: true}}
case result.status do
:ok -> IO.puts("All systems healthy")
:degraded -> IO.puts("Some checks warned")
:fail -> IO.puts("Critical failures detected")
end

Common Check Examples

PostgreSQL

%{
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"
}

Redis

%{
id: :redis,
name: "Redis",
description: "Cache server is running",
priority: 2,
check: fn ->
case System.cmd("redis-cli", ["ping"], stderr_to_stdout: true) do
{"PONG\n", 0} -> {:ok, "Redis is responding"}
{output, _} -> {:error, "Redis not responding: #{output}"}
end
end,
fix: fn -> {:ok, "sudo systemctl start redis"} end,
fix_command: "sudo systemctl start redis"
}

Directory Permissions

%{
id: :data_dir,
name: "Data Directory",
description: "Application data directory is writable",
priority: 3,
check: fn ->
path = "/var/data/myapp"
if File.exists?(path) && File.stat!(path).access == :write do
{:ok, "Data directory is writable"}
else
{:error, "Data directory not writable: #{path}"}
end
end,
fix: fn -> {:ok, "sudo chown -R myapp:myapp /var/data/myapp"} end,
fix_command: "sudo chown -R myapp:myapp /var/data/myapp"
}

Result Structure

Each check result is a map with:

%{
id: :postgresql, # Check identifier
name: "PostgreSQL", # Human-readable name
status: :ok | :warning | :error, # Check status
message: "PostgreSQL is ready", # Status message
fix_command: "sudo systemctl start postgresql" # Hint for fix
}

Supervisor Integration

Integrate with an Elixir supervisor for application startup health checks:

defmodule MyApp.Application do
use Supervisor
def start_link(arg) do
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
end
@impl true
def init(_arg) do
config = Botica.config() # Your health check configuration
children = [
# ... other children
{Botica.HealthCheckWorker, config}
]
Supervisor.init(children, strategy: :one_for_all)
end
end
defmodule MyApp.HealthCheckWorker do
use GenServer
def start_link(config) do
GenServer.start_link(__MODULE__, config)
end
@impl true
def init(config) do
result = Botica.Doctor.health_check(config)
if result.status == :fail do
{:stop, {:shutdown, :health_check_failed}}
else
{:ok, config}
end
end
end

API

Check Definition

Each check in the config must have:

License

MIT