Skip to content
Gianluca Rea.

Tortuga πŸ΄β€β˜ οΈ

"Make way! Make way for Tortuga!" β€” Captain Barbossa

A terminal-based HTTP endpoint monitor written in Rust. Tortuga watches your HTTP services, renders a live dashboard in the terminal, and fires instant alerts via Telegram and Slack/Discord webhooks whenever a service goes up or down.


Why I built Tortuga

I wanted a lightweight, self-hosted way to keep an eye on a handful of HTTP services β€” APIs, health endpoints, personal projects β€” without spinning up a full observability stack or relying on a paid SaaS tool.

The result is a single Rust binary: drop in a config.toml, run it, and you get a live terminal dashboard plus push notifications the moment something changes status.

Repository: github.com/gianlucarea/tortuga


Dashboard

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Tortuga ────────────────────────────────┐
β”‚ Name            URL                            Status  HTTP  Uptime  ms β”‚
β”‚ httpbin OK      https://httpbin.org/status/200 UP      200   100%    87 β”‚
β”‚ httpbin 404     https://httpbin.org/status/404 DOWN    404    0%    102 β”‚
β”‚ Example.com     https://example.com            UP      200    99%   210 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Events ──────────────────────────────────
β”‚ 16:34:01  [httpbin OK] Unknown β†’ UP (HTTP 200)                          β”‚
β”‚ 16:34:02  [httpbin 404] Unknown β†’ DOWN (HTTP 404)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Green = UP Β· Red = DOWN Β· Yellow = Unknown / timeout


Features

  • Live TUI dashboard β€” real-time ratatui table refreshed every second, showing status, HTTP code, uptime %, response time, and last-checked timestamp
  • Concurrent monitoring β€” each endpoint runs in its own async task (tokio) with an independent polling interval and timeout
  • Telegram alerts β€” instant push notifications via the Telegram Bot API to a channel or chat
  • Webhook alerts β€” Slack-compatible JSON POST to any webhook URL (Slack, Discord, etc.)
  • State persistence β€” uptime counters and last status survive restarts via a local state.json
  • Graceful shutdown β€” press q or Ctrl-C to exit cleanly; state is flushed to disk

Architecture and components

Config loader (config.rs)

  • Parses config.toml using serde + toml
  • Supports global defaults (Telegram token, webhook URL, state file path) with per-endpoint overrides
  • Each endpoint declares a name, URL, polling interval, optional expected status code, and timeout

Monitor (monitor.rs)

  • Spawns one tokio task per endpoint
  • Each task loops: fires an HTTP GET via reqwest, evaluates the response against expected_status, updates the shared state, and sleeps for interval_secs
  • Status transitions (UP β†’ DOWN, DOWN β†’ UP, Unknown β†’ *) trigger alert dispatch

Alert dispatcher (alert.rs)

  • Telegram: sends a formatted message via https://api.telegram.org/bot<token>/sendMessage
  • Webhook: POSTs a Slack-compatible JSON payload to the configured URL
  • Both channels are optional; per-endpoint overrides take precedence over global config

State (state.rs)

  • Holds a HashMap<endpoint_name, EndpointState> with total checks, up-count, last HTTP code, and last checked time
  • Serialized to / deserialized from state.json on startup and shutdown (and periodically while running)

TUI (ui.rs)

Built with ratatui and crossterm.

The interface uses a two-section layout:

SectionDescription
Dashboard tableOne row per endpoint β€” name, URL, status badge (color-coded), HTTP code, uptime %, response time in ms, last-checked time
Events logScrolling list of status-change events with timestamps

Configuration

[global]
state_file = "state.json"

telegram_bot_token = "123456:ABC-DEF..."
telegram_chat_id   = "@mychannel"

# webhook_url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

[[endpoints]]
name            = "My API"
url             = "https://api.example.com/health"
interval_secs   = 30
expected_status = 200
timeout_secs    = 10

[[endpoints]]
name          = "Example.com"
url           = "https://example.com"
interval_secs = 60

Configuration reference

KeyScopeDescription
state_fileglobalPath to the JSON file for state persistence
telegram_bot_tokenglobalTelegram bot token from @BotFather
telegram_chat_idglobal / endpointTarget channel or chat
webhook_urlglobal / endpointSlack-compatible webhook URL
nameendpointDisplay name in the dashboard
urlendpointURL to poll
interval_secsendpointPolling interval in seconds
expected_statusendpointExact HTTP status for UP; omit for any 2xx
timeout_secsendpointRequest timeout in seconds (default: 10)

Tech stack

LayerLibrary
Async runtimetokio
HTTP clientreqwest
TUIratatui + crossterm
Config / stateserde + toml + serde_json
Timechrono
CLI argsclap
Error handlinganyhow

Usage

# Clone and build
git clone https://github.com/gianlucarea/tortuga
cd tortuga
cargo build --release

# Run with default config.toml
./target/release/tortuga

# Specify a custom config
./target/release/tortuga --config /etc/tortuga/prod.toml