Skip to content

calabi 2.0.0

Choose a tag to compare

@calabinet calabinet released this 23 Sep 13:41
· 11 commits to main since this release

The edge's config file changed. A server that has been running since 1.14.0
will not start on the new edge until its config catches up, so take this part
before you pull.

If you run the server bundle (deploy/server), take the new
docker-compose.yml along with the new image. That file writes the edge's
config, and the version of it shipped before this release is one the new edge
refuses: it had no public: block, which is now required of a node that serves
tunnels. Your .env needs nothing new — the value comes from
CALABI_PUBLIC_HOST, which you already set.

If you wrote your own edge config, two things can stop it: public.host is
now required on a node that serves tunnels, and a handful of settings that had
quietly done nothing for some time are now refused instead of ignored. Both are
spelled out under "Your server" below, and the edge names the exact line on
startup rather than coming up half-working.

Everything that merely moved still loads from where it was, so the rest of your
file is fine as it stands. A machine that only relays mesh traffic does not need
public.host at all. The client's own config file is renamed in this release
too, but it migrates itself and needs nothing from you — also below.

Updates

  • Fixed — A client in a container was told to reinstall itself as a system
    service.
    Running the calabinet/calabi image, the local console and
    calabi update said the daemon was not a privileged system service and to
    reinstall it with calabi daemon install --system — a command the same binary
    refuses inside a container, because a container supervises its own process and
    has no OS service to install. A container is now recognised before anything
    else is decided, and gets the answer that works: pull the new image and
    recreate the container. It still checks, so the console still says a newer
    version exists. A container running as root was also a case where the daemon
    believed it could install: it downloaded and verified an installer on every
    cycle and only then found there was no service manager to restart. It no
    longer starts.

Local console

  • Fixed — The console gave a container four more pieces of advice it
    could not follow.
    Settings listed calabi daemon install / start / status / stop / uninstall with copy buttons — every one of which this binary refuses
    in a container — and the pinned-identity notes said to reinstall or uninstall
    the service to change the key or take the machine offline. The daemon now
    tells the console it runs in a container, and those four places say what
    works there instead: recreate the container with a different
    CALABI_API_KEY, stop or remove it, manage it with your container runtime.
    The same correction reaches the log a container operator actually reads: a
    rejected API key no longer answers with calabi daemon install --api-key.
    Clients installed on a machine are unchanged, wording included.

On calabi.net

  • Fixed — A payment that settled slowly could leave your order
    unpaid.
    Paying in USD goes through Paddle, and its local payment methods
    (WeChat Pay, Alipay and similar) confirm the money some minutes after you are
    finished — long enough, occasionally, to miss the 30-minute window an order
    stayed open for. The order was marked expired, the payment then arrived with
    nothing left to apply it to, and the money had been taken. USD orders now stay
    open for two hours, and a payment that still arrives after that completes the
    order instead of being dropped. Orders you canceled yourself are not affected:
    a payment on one is reported for a human to look at, never turned into a
    subscription behind you.

  • Fixed — A payment link sent by Paddle said it was invalid. The payment
    page read the transaction id from ?txn=, but links Paddle generates itself —
    the one behind "copy payment link", and the reminder it emails about an
    unfinished payment — carry it as ?_ptxn=. Every one of those landed on
    "this payment link is invalid or has expired" with the id sitting in the
    address bar under a different name. Both spellings work now. Opening such a
    link fills the page in; it does not start a checkout on its own, so the
    agreements still have to be ticked.

Your server

  • Changed — The edge config is grouped by service, and the node has one
    address.
    Two things were wrong with the old file at once.

    Nothing in it said which half of the program would read a given setting:
    control:, http:, relay: and the rest all sat at the top level, so a
    relay-only config looked exactly like an edge's. And four of those blocks
    named an address — control: { addr: ":7443" } — whose host half was always
    empty, because every one of those listeners has to be reachable from outside
    the machine. They named ports while looking like addresses, sitting among the
    settings that really are addresses. The port was then written twice: once on
    the listener, once inside public.addr, with nothing comparing them — move
    the listener, forget the other line, and every client goes on dialing a port
    nothing is listening on.

    The layout now follows the two services. tunnel: holds what only tunnels
    read, mesh: (formerly relay:) what only the mesh relay reads, and the top
    level what both use. Where this node can be reached is one setting, and each
    service names only the port it wants:

    node_label: my-server
    role: both
    mode: standalone
    coord_pubkey_file: ./coord.pub
    state: { dir: ./state }
    
    public:
      host: server.example.com
    
    tunnel:
      base_domain: tunnels.example.com
      control_port: 7443
      http_port: 80
      https_port: 443
    
    mesh:
      derp_port: 3340
      stun_port: 3478

    A node running role: mesh can now delete its whole tunnel: block, and one
    running role: tunnel its whole mesh: block.

    public.host is required on a node that serves tunnels. It is what
    clients dial and the name its certificate is issued for. It used to be
    optional, falling back to the listener's bind address — a usable dial string
    only on the one machine that is also the client. A node that only relays mesh
    traffic still does not need it; it is named in your coordinator's relay map.
    From the environment: CALABI_EDGE_PUBLIC_HOST.

    Apart from that line, an existing file keeps working. Every setting that
    moved still loads from where it was: control: { addr: ":7443" } as
    tunnel.control_port: 7443, https: { self_signed: true } as
    tunnel.https_self_signed: true, relay: as mesh:, and public.addr as
    public.host — the last provided the port written into it matches the
    listener's. A file that spells one setting both ways, or whose two ports
    disagree, is refused rather than guessed at, naming the one to keep.

    One thing this shape cannot express: an external port different from the one
    bound. If you reach your edge through a mapping like that, bind the external
    port directly. A bind address that names a host — control.addr: "10.0.0.5:7443" — is refused rather than quietly widened to every interface.

    Some settings are now refused rather than ignored. They had not done
    anything for some time, and a file that still carries one describes a node
    that does not exist. The identity:, quota:, config_svc: and nats:
    blocks and tunnel.addr / cert.addr went when the edge stopped reaching the
    control plane directly. presence.interval_seconds and cert.refresh_seconds
    go now: both had a working default, and in the whole history of this
    repository no deployed config ever set either one. edge_class and org_id
    go for a different reason — which paying plans are routed to a node, and which
    organization it belongs to, are not decisions a node should be making from a
    file on its own disk. On calabi.net both come from the certificate that node
    authenticates with, and a server you run yourself never used either. Delete
    them from your file.

    Refusing rather than ignoring is the point. Removing the settings alone would
    have been enough to stop them working — a key the edge does not know is
    skipped — and that is the outcome worth avoiding: you would set a cadence, get
    the default, and never be told.

    One more rename, for anyone running several edges in one region: the edge
    config's mesh: block used to mean edge-to-edge forwarding of tunnel
    traffic
    , which has nothing to do with the mesh. It is now
    tunnel.peer_forward:. This is the one old spelling that is not migrated
    silently — mesh: means the relay now, and quietly reading a peer-forward
    block as relay settings would take a node out of its region's forwarding pool
    without saying so. A file that still has it is refused, and the error says
    what to rename it to.

  • Changed — A replaced control certificate is picked up without a
    restart.
    The edge read control_cert_pem / control_key_pem once at start.
    Renewing those files, or swapping a self-signed certificate for a real one,
    changed nothing until the next restart — the edge went on presenting the copy
    it had read, and kept presenting it after it expired. It now re-reads them
    when they change. A rotation caught half-done (the certificate written, the
    key not yet) keeps serving the pair that still works and tries again on the
    next connection, rather than turning a moment into a refused device.

  • Changed — The daemon's config file is now calabi.yaml, and the
    coordinator moved to a server: block.
    The file stopped being about
    tunnels a while ago: a machine that only joins your server has a complete,
    working config in which tunnels: is empty. And the coordinator — which
    server the device belongs to, and how it proves itself there — was nested
    under mesh:, which read as though a device that never turns the mesh on
    did not need it. It does: calabi http 8080 reaches its edge by asking that
    same coordinator. So coord, trust, pins, ca_file, auth_key and
    name now sit in a top-level server: block, and mesh: keeps only the
    overlay's own settings.

    server:
      coord: server.example.com:7012
      trust: pin
      pins: ["sha256:…"]
      auth_key: ck_…
    mesh:
      enabled: true          # optional — tunnels work without it
    tunnels: []

    You do not have to do anything. A file in the old shape still loads, and
    is rewritten into the new one the next time anything saves it. The file the
    console manages in your data directory is renamed from tunnels.yaml to
    calabi.yaml the first time this version runs; if that rename cannot be
    performed, it keeps using the old name. A file you pass with --config or
    CALABI_DAEMON_CONFIG is yours and is never renamed — call it whatever you
    like.

    One thing to know if you go back: an older client looks for tunnels.yaml
    and does not understand server:, so after downgrading it will believe the
    machine has not joined. Renaming the file back restores it; nothing is lost.

  • Changed — The edge's two roles are now called tunnel and mesh.
    role: edge said nothing useful inside a program called calabi-edge, and
    since the relay merged into that program the setting has not been asking
    which binary you are running — it asks which of the two services this machine
    provides. Those services are called tunnels and mesh everywhere else, so the
    roles are now tunnel, mesh and both.

    Nothing to change. edge and relay are still accepted and will stay
    accepted — including CALABI_EDGE_ROLE=relay, which is the line this guide
    has always given for running a relay on its own. An existing machine keeps
    working across the upgrade untouched.


These are the same files published at https://download.calabi.net — same
version, same bytes. This release is not a separate CI build; it is that build,
uploaded here as well.

Verify them yourself

gh release download v2.0.0 --pattern build-manifest.json
bash scripts/verify-reproducible-build.sh build-manifest.json

That clones this repository at d337a2dacfadb13f11d9146d88fbc4c8e93b84f6 — the commit these binaries were
built from — rebuilds all 21 Go artifacts and compares hashes. The
verify-release workflow runs exactly that on GitHub's runners for this
release, so you can also just read its result.

SHA256SUMS (client + desktop), SHA256SUMS.edge and SHA256SUMS.coord
answer a different question: whether your download arrived intact. Archives
themselves are not reproducible (tar/zip record mtimes) — the manifest compares
the binary inside.

Not covered by the manifest

The installers. The two desktop ones are Tauri/Rust builds with their own
toolchains and are not reproducible yet; every installer is listed in
SHA256SUMS, so its integrity is checkable.

calabi-desktop-windows-x64-setup.exe — the calabi daemon it carries is a
different matter: since v1.7.1 that is the released calabi-windows-amd64
binary itself, byte for byte, so the program you actually run is covered by the
manifest even though the installer around it is not.

calabi-desktop-macos-universal.pkg — here the daemon is NOT covered either.
It is a universal (arm64 + x86_64) binary produced by lipo, so it is not byte
identical to either released macOS archive. It is also neither code-signed nor
notarized yet: macOS will stop it on first open, and the download page says how
to proceed. If you would rather not trust a build you did not make, the client
is in this repository — build it.

calabi-android.apk — built from apps/client-android and apps/client/mobile
in this repository, but not reproducibly yet: the Go core records the absolute
directory it was built in, so the same commit built elsewhere differs. It is
signed with the release key; check the signer before installing:

apksigner verify --print-certs calabi-android.apk
# certificate SHA-256 digest: 5800bc9938dd507b1a4fb7cc497f9b3ce50e3c20655fdcb5383aac4049f97d23

Components

  • calabi — the client (tunnels + mesh + the local console on :7400)
  • calabi-edge — the data plane: role: tunnel for tunnels, role: mesh
    for the mesh relay, role: both for the two
  • calabi-coord — the coordinator, every device's identity: invites, device
    registry, addresses, ACLs; names the edge and signs the grants it accepts

calabi-coord and calabi-edge together are your server
(deploy/server).

Docker images (amd64 + arm64): calabinet/calabi, calabinet/calabi-edge,
calabinet/calabi-coord. The binary inside each is the released linux one,
byte for byte.