# See high-availability status

`GET https://api.mailmasker.org/api/v1/admin/ha/status`

The database cluster, its connection poolers, the mailbox servers and the internet edges, node by node.

- Who can use it: Site admins
- Area: Fleet and status
- Answers 200 OK when it works
- Page: https://developer.mailmasker.org/admin-ha-status

Four groups, always in this order, each listing its nodes with a `role`, a `state` and whether it is `healthy`, plus a `note` on how the group works. For the database, `leader` is the node taking writes, and each node's role, state and replication lag come live from the cluster itself; `unreachable` means that node didn't answer. For the mailbox servers, `leader` is the active server's address.

## Good to know

- For the poolers, mailbox servers and edges, `healthy` only means the node's agent checked in within the last 3 minutes, not that the service itself answered.
- The edges are not a failover group. Exactly one should be powered on at a time, and their `note` says whether that is so.
- Each call asks the database nodes directly, so one that is down can slow the answer by several seconds.

## Examples

cURL:

```bash
curl -s 'https://api.mailmasker.org/api/v1/admin/ha/status' \
  -H "Authorization: Bearer $TOKEN"
```

Perl:

```perl
use Mojo::Base -strict;
use Mojo::UserAgent;

my $ua  = Mojo::UserAgent->new;
my $res = $ua->get('https://api.mailmasker.org/api/v1/admin/ha/status',
    { Authorization => "Bearer $ENV{TOKEN}" })->result;
die $res->code, ' ', ($res->json('/error') // $res->message), "\n" unless $res->is_success;
print $res->body, "\n";
```

homelab-cli:

There is no homelab-cli command for this yet.

## Response

200 OK:

```json
{
  "components": [
    {
      "name": "PostgreSQL (Patroni)",
      "kind": "patroni",
      "vip": "192.0.2.30",
      "leader": "prod-homelab06",
      "members": [
        { "name": "prod-homelab06", "address": "192.0.2.6", "role": "leader", "state": "running",
          "lag": null, "timeline": 4, "healthy": true, "last_heartbeat": "2026-11-03 21:42:10.123456-06" },
        { "name": "prod-homelab07", "address": "192.0.2.7", "role": "replica", "state": "streaming",
          "lag": 0, "timeline": 4, "healthy": true, "last_heartbeat": "2026-11-03 21:41:52.410028-06" }
      ],
      "note": "Patroni + etcd + keepalived VIP 192.0.2.30 (follows the leader; apps reach it via pgbouncer). Role/state/lag are Patroni's own cluster view via each node's :8008/cluster REST; \"unreachable\" means that node's Patroni REST did not answer."
    },
    {
      "name": "Connection poolers (pgbouncer)",
      "kind": "pgbouncer",
      "members": [
        { "name": "prod-homelab09", "address": "192.0.2.9", "role": "pooler", "state": "up",
          "healthy": true, "last_heartbeat": "2026-11-03 21:41:58.907311-06" }
      ],
      "note": "Every pooler fronts the database VIP, so any one keeps serving across a leader failover. Liveness is the fleet agent heartbeat (:6432 manifest)."
    },
    {
      "name": "Mailbox servers (dovecot)",
      "kind": "dovecot",
      "leader": "192.0.2.62",
      "members": [
        { "name": "prod-homelabvm62", "address": "192.0.2.62", "role": "active", "state": "up",
          "healthy": true, "last_heartbeat": "2026-11-03 21:42:01.335871-06" }
      ],
      "note": "Active/passive: the active host serves ALL mail paths (IMAP/IMAPS, LMTP delivery, SASL submission auth) and they fail over together via homelab-haproxy, which marks the other two `backup`. Same source as the Dovecot tab, so the two cannot disagree."
    },
    {
      "name": "Internet-facing edges",
      "kind": "edges",
      "members": [
        { "name": "prod-homelab01", "address": "192.0.2.1", "role": "powered on", "state": "serving",
          "healthy": true, "last_heartbeat": "2026-11-03 21:42:04.661290-06" },
        { "name": "prod-homelab02", "address": "192.0.2.2", "role": "off", "state": "powered off",
          "healthy": false, "last_heartbeat": "2026-10-30 08:12:44.310925-05" }
      ],
      "note": "NOT automatic failover, by design: exactly ONE edge is powered on at a time and is swapped by hand (pct shutdown one, pct start another). Both public addresses arrive by DHCP from the AT&T reservations keyed to that node's eth7/eth6 MACs, so whichever node is up answers on them. keepalived was removed deliberately. Exactly one is up, which is correct."
    }
  ]
}
```
