Getting started

PIMA is a single binary with an embedded SQLite database. There is no external service to run and no message broker to babysit.

Run it

With Docker:

docker run -d --name pima -p 8080:8080 \
  -v pima-data:/data \
  -e PIMA_BASE_URL=http://localhost:8080 \
  -e PIMA_BOOTSTRAP_EMAIL=you@example.co.ke \
  -e PIMA_BOOTSTRAP_PASSWORD='a-long-passphrase-1' \
  pima:dev serve

Or straight from source:

git clone https://github.com/siredwin/pima && cd pima
make generate && make build
PIMA_BOOTSTRAP_EMAIL=you@example.co.ke \
PIMA_BOOTSTRAP_PASSWORD='a-long-passphrase-1' \
./bin/pima serve

Open http://localhost:8080. Sign in with the bootstrap account, or register at /register if self-service signup is enabled (PIMA_REGISTRATION=open, the default; set it to off on a private install).

For a demo workspace with sample monitors, run pima seed first — it creates three tenants, nine monitors and a day of history so you can see every screen populated.

Accounts

Each signup creates its own workspace with the registrant as owner — nobody can join an existing tenant by signing up. Accounts can also be issued by the operator:

pima user add -email ops@example.co.ke -org "Acme ISP" -role admin

Three roles: owner (also deletes), admin (creates and edits), viewer (read-only).

With SMTP configured, a new signup must confirm its address before signing in. The confirmation link is single-use and expires; signing up again replaces it. Password reset is on /forgot, and its response is the same whether or not the address exists. A completed reset signs out every other browser. In development, PIMA_MAIL_DUMP=./mail writes messages to disk so these flows work without an SMTP account.

Add a monitor

  1. New monitor
  2. Give it a name and pick a type:
    • http checks a status code (and optionally a keyword in the body, and any request headers your endpoint needs — one Name: value per line, e.g. Authorization: Bearer …)
    • tls inspects the certificate
    • tcp checks that a port accepts connections — use it for Postgres, MySQL, Redis, SMTP, IMAP, or anything without an HTTP health endpoint; set the keyword to the greeting the service sends (SMTP: 220) if you want to assert the protocol too
    • dns asserts that a name resolves; suffix the target with @1.1.1.1 to ask a specific resolver, which separates "our zone is broken" from "that resolver is broken"
    • ping sends ICMP echo requests and reports packet loss; on a host where ICMP is not permitted the probe says so instead of reporting an outage
  3. Enter the target (a URL for http, host:port for tcp, a name for dns, a host for tls or ping)
  4. Set the interval and, importantly, retries before alert

Retries are the difference between a useful alert and a noisy one. With retries set to 2, a monitor must fail twice before it is considered down, so a single dropped packet never pages anyone.

Optionally add a keyword: a 200 response whose body says "database unavailable" is not healthy.

Point alerts somewhere

Create a channel under Channels — Slack, Discord, Telegram, email, SMS, WhatsApp or a generic webhook — then press Send test. The test travels through the real delivery queue, so if it arrives, production alerts will too.

Every delivery attempt is listed with its status and last error, which answers "why did I not get the alert" without reading logs.

Public status page

Status page in the top bar publishes a read-only page for customers:

  1. Choose an address, e.g. acme-isp → https://pima.example.co.ke/status/acme-isp
  2. Give it a title
  3. Publish, and tick the monitors that should appear

The page shows each service's name, status and 24-hour uptime, and nothing else: no targets, no URLs, no error text, no accounts. Unpublishing takes it offline immediately; an unknown address is indistinguishable from an unpublished one.

Outage reminders

A monitor that stays down is re-announced every 30 minutes (PIMA_REMINDER_INTERVAL, 0 disables). The reminder is not a new incident: it carries the same incident id and the elapsed downtime.

Maintenance windows

Maintenance → schedule a window (all monitors, or one) with a start and end in the server's local time. During the window, probes still run and results are still recorded — only the alert is withheld. Cancel a window to resume alerting immediately.

Intervals

Type Typical interval Notes
HTTP 30–60 s One request; keep it cheap for the target
TLS 6–24 h Certificates change weekly, not minutely
Certificate warning 14–30 days ahead Match your renewal process, not the certificate

The minimum interval is 10 seconds. Anything faster is indistinguishable from a load test against your own service.

Backups

The database is one SQLite file. Back it up with the built-in command, never with cp while the service runs:

pima backup -out /var/backups/pima -keep 14
pima verify -db /var/backups/pima/<file>.db

pima backup uses SQLite's VACUUM INTO, so the copy is consistent without stopping writers, and it runs an integrity check on the result before reporting success.

Operate it

  • GET /healthz — touches the database
  • GET /heartbeat — returns 503 if the probe loop has stalled for 30 seconds
  • GET /healthz and /heartbeat are the two endpoints to give your uptime provider

Back up by copying the SQLite file: sqlite3 pima.db ".backup backup.db".

Monitor the monitor. Point an external checker at /heartbeat. Nothing inside PIMA can tell you that PIMA has stopped running.

Bulk import

Declare your endpoints in YAML and apply them:

pima import -file monitors.yaml -dry-run   # validate
pima import -file monitors.yaml            # apply

Re-running is idempotent, so the file can live in git and become the written inventory of what you promise to keep up.