cronstable + etcd leadership backend
=================================

This example runs two cronstable instances that elect a single leader through an
etcd key. The holder creates a lease-bound key (`cronstable/leader`) with a
create-if-absent transaction and keeps the lease alive; if it dies, the lease
expires, etcd deletes the key, and the other instance's transaction wins. While
etcd is reachable this is a fenced, exactly-once election (unlike the
best-effort gossip backend).

The backend speaks etcd's v3 gRPC-gateway JSON/HTTP API directly over the core
aiohttp dependency -- no etcd3/grpc client and so no extra dependency, and it
runs on every architecture cronstable targets. The gateway is etcd's own
first-class HTTP interface, so there is no optional native-client extra for
etcd.

Files
-----

  cronstable.yaml      the cronstable config: `cluster.backend: etcd` plus one job
                       of each clusterPolicy (Leader / PreferLeader / EveryNode)
  docker-compose.yml   a single etcd plus two cronstable instances pointed at it

Try it
------

  # SECURITY NOTE: this demo etcd has no authentication and speaks plaintext
  # HTTP, so its client port is NOT published to the host; the nodes reach it
  # only over the compose network (etcd:2379). Never expose an unauthenticated
  # etcd beyond localhost; for real use enable auth + TLS (see below).

  docker compose -f example/etcd/docker-compose.yml up --build

  # exactly one instance is the leader; watch the cluster panel:
  #   http://localhost:8080/    http://localhost:8081/

  # fail the leader over (the other takes over within ~ttl):
  docker compose -f example/etcd/docker-compose.yml stop cronstable-a

  # see the election key value (the current holder's nodeName):
  docker compose -f example/etcd/docker-compose.yml exec etcd etcdctl get cronstable/leader

Authentication and TLS
----------------------

For an authenticated etcd cluster set `cluster.etcd.username` and resolve the
password from a value / file / env var (like `web.authToken`):

  cluster:
    backend: etcd
    etcd:
      endpoints: [https://etcd:2379]
      username: root
      password:
        fromEnvVar: ETCD_PASSWORD
      tls:
        ca: /etc/etcd/ca.pem
        cert: /etc/etcd/client.pem
        key: /etc/etcd/client.key

`ca` alone is enough to trust the server over an `https://` endpoint; add
`cert` + `key` only for mutual TLS (client-certificate auth). `cert` and `key`
are all-or-nothing: set both or neither (a client certificate needs its private
key, so config load rejects one without the other).
