Installing and deploying Haify¶
This guide takes raw storage nodes to a working DRBD-backed cluster with gateways, HA, Kubernetes CSI, the AI Copilot and optional WAN replication.
The per-node package list, with the symptom each missing piece produces, is
node-prerequisites.md. Day-to-day operation is
covered in user-guide.md.
The commands are for Ubuntu 24.04 (amd64 and arm64). Package names below are Ubuntu's; adjust them for other distributions.
0. Architecture¶
haify / web UI / haify-mcp / haify-ai
│ gRPC 3374 · REST 3375 · UI 3376 · metrics 9433
▼
haify-controller ──(dispatch over SSH)──► storage nodes
│ ├─ DRBD 9 (kernel) + drbd-utils
└─ BBolt DB /var/lib/haify/haify.db ├─ drbd-reactor (promoters, HA)
├─ LVM / ZFS pools
└─ OCF agents (gateways)
- One
haify-controllerprocess manages the whole cluster. It keeps its state in a BBolt database ([database] path, default/var/lib/haify/haify.db). It runs commands on the nodes over SSH through thedispatchlibrary, so nothing Haify-specific runs on the nodes. Commands for the controller's own address run locally, without SSH. - The controller listens on four ports: gRPC on
[server] port(default3374), the REST gateway on[server] rest_port(default3375, bound to[server] listen_address), the web UI on[ui] port(default3376), and Prometheus metrics on[metrics] port(default9433). The UI passes/v1/on to the REST gateway and/ai/on to the AI Copilot at127.0.0.1:7634, so a browser only needs the UI port. - On the nodes, DRBD 9 replicates the block volumes.
drbd-reactorpromoters move mounts, VIPs and services to another node when one fails. Gateways (NFS / SMB / iSCSI / NVMe-oF) export a resource on a floating service IP. - These parts are optional: the Kubernetes CSI driver, the
haify-aiCopilot, controller Self-HA (the controller itself fails over, reached on a VIP), WAN replication (DRBD through ahaify-proxypair for each resource), encrypted replication (kernel TLS) and backups outside the cluster (rclone).
Without Self-HA the controller can run on a storage node or on a separate host. Self-HA requires it to run on a registered node.
1. Build the binaries¶
Building needs Go 1.26 (go.mod pins toolchain go1.26.8) and Node.js for the
web UI (CI uses Node 22).
git clone https://github.com/haify-project/haify.git && cd haify
(cd web-ui && npm ci)
make build
make build builds the web UI and copies it into ui/dist, where the
controller embeds it (go:embed). It then builds bin/haify-controller,
bin/haify, bin/haify-mcp, bin/service-ip, bin/haify-csi
and bin/haify-docker (the Docker volume plugin). All of them
are built for the build host's OS and architecture, except service-ip,
which is always built for Linux (on the build host's architecture).
The AI Copilot is not built by default. make build AI=1 builds the web UI with
the Copilot and its Settings page, and also builds bin/haify-ai; without it
the UI has neither (section 10).
To build for Linux nodes from another OS or architecture, build the UI once and cross-compile:
make ui-sync
for c in controller:haify-controller cli:haify mcp:haify-mcp service-ip:service-ip; do
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/${c#*:} ./cmd/${c%%:*}
done
# arm64 nodes: GOARCH=arm64
A controller built without make ui-sync shows a placeholder page instead of
the UI. The API and haify still work. go install also builds without the
UI:
go install github.com/haify-project/haify/cmd/...@latest
go install names each binary after its directory, so you get controller,
cli and mcp. Rename them to haify-controller, haify and haify-mcp
when you install them. When cross-compiling (GOOS/GOARCH set), it
puts them in $(go env GOPATH)/bin/linux_<arch>/.
Tagged releases on GitHub carry a linux/amd64 archive with haify-controller
(UI included), haify, haify-mcp, service-ip, both unit files and
controller.toml.example. make deb builds Debian packages instead
(section 4).
Other binaries:
- haify-ai (cmd/haify-ai, its own Go module): make build AI=1, or cd cmd/haify-ai && go build .
- haify-proxy (WAN transport) is built from the separate haify-proxy repository.
- The CSI image is built from Dockerfile.csi (section 9).
2. Prepare the storage nodes¶
Do this on every node. node-prerequisites.md has the details and the failure
symptoms.
-
The DRBD 9 kernel module,
drbd-utilsanddrbd-reactor(LINBIT packages or source builds):Keep drbd-utils and drbd-reactor versions matched on all nodes. Reactor parsescat /proc/drbd # version: 9.x drbdadm --version drbd-reactor --versiondrbdsetup status --json. A drbd-utils too old for it produces JSON that reactor cannot parse, so reactor logsIGNORING resource … expected ','and never fails over (drbd-utils 9.31 with reactor 1.11 does this; drbd-utils 9.34 fixed it). drbd-reactor must be running (systemctl enable --now drbd-reactor);ha self enablerefuses otherwise. -
LVM (
lvm2) for LVM pools,thin-provisioning-toolsfor incremental backups (thin_delta), andzfsutils-linuxonly for ZFS pools. -
OCF resource agents for gateways:
apt-get install -y resource-agents-extra # Filesystem, nfsserver, exportfs, nvmet-*resource-agents-basealone lacksFilesystem, which every gateway promoter starts with.ha createand Self-HA use systemd mount units andservice-ip@.serviceinstead of OCF agents. -
Per gateway type, on the nodes of the exported resource:
- iSCSI:
apt-get install -y targetcli-fb python3-rtslib-fb - NFS:
apt-get install -y nfs-kernel-server. Per-directory quotas on an NFS export also need thequota_v2kernel module, which Ubuntu cloud kernels ship only inlinux-modules-extra. Without it, the gateway is still created, but without directory quotas. - SMB:
apt-get install -y samba, and the distribution's own smbd turned off (systemctl disable --now smbd nmbd), because it holds port 445. -
NVMe-oF:
apt-get install -y nvme-cli linux-modules-extra-$(uname -r)(Ubuntu cloud kernels shipnvmet-tcponly inlinux-modules-extra). Gateway creation loadsnvmetandnvmet-tcp(ornvmet-rdmafor--transport rdma) and adds them to/etc/modules-load.d/nvmet.conf. -
Optional features:
- backups:
rcloneon every diskful node of a backed-up resource - encrypted replication:
ktls-utils(tlshd),openssl, DRBD ≥ 9.2 with TLS -
encryption at rest (
resource create --encrypt):cryptsetup,dm-crypt -
A data disk per diskful node: a raw block device (
/dev/sdb,/dev/vdb) that is not mounted. A disk with old data on it is refused unless you pass--wipetopool create, which erases it.
Once the nodes are registered (section 5), haify health-check reports per
node whether DRBD, drbd-reactor (installed and running) and the resource agents
are present.
3. SSH trust and dispatch config¶
The controller reaches nodes over SSH. Node commands use sudo, so the SSH user
must be root or have passwordless sudo. Self-HA also requires passwordless
root SSH between every pair of nodes.
On the controller node:
sudo test -f /root/.ssh/id_ed25519 || \
sudo ssh-keygen -t ed25519 -N "" -f /root/.ssh/id_ed25519 -C root@controller
sudo cat /root/.ssh/id_ed25519.pub
# append that key to /root/.ssh/authorized_keys on EVERY node, including this one
/root/.dispatch/config.toml:
[ssh]
user = "root"
port = 22
key_path = "/root/.ssh/id_ed25519"
strict_host_key = false
timeout = "30s"
# One section per node, keyed by the node's IP address.
[hosts."<node1-ip>"]
addresses = ["<node1-ip>"]
user = "root"
key_path = "/root/.ssh/id_ed25519"
[hosts."<node2-ip>"]
addresses = ["<node2-ip>"]
user = "root"
key_path = "/root/.ssh/id_ed25519"
[hosts."<node3-ip>"]
addresses = ["<node3-ip>"]
user = "root"
key_path = "/root/.ssh/id_ed25519"
- Name each
[hosts.*]section after the node's IP address. The controller looks hosts up by address, so a section named after the node ([hosts.node1]) is never used. The settings then come from~/.ssh/configinstead: aHostentry that matches that IP (by itsHostNameor by its name pattern) supplies the user, port and key. That often ends inunable to authenticate, attempted methods [none]. dispatch looks for each setting in this order: the host section, the group section,~/.ssh/config, then the[ssh]defaults. So auserorkey_pathset in the IP-named section always wins. - Tell the controller where this file is with
[dispatch] config_path. If you leave it unset, the controller uses~/.dispatch/config.tomlof the user it runs as. If the path does not exist, the controller does not start. - dispatch keeps host keys in
known_hosts(default~/.ssh/known_hosts, which is/root/.ssh/known_hostsfor root). Withstrict_host_key = falseit adds unknown hosts by itself. A key that changed is always rejected withhost key changed: <ip>, whatever that setting says. This happens after a node is rebuilt, or with a VM that makes new host keys. Remove the old entry withssh-keygen -R <ip> -f /root/.ssh/known_hosts. dispatch rereads the file when it changes, so you do not need to restart anything.
4. Install and start the controller¶
From the Debian package (Debian, Ubuntu)¶
make deb builds dist/haify-controller_<version>_amd64.deb and _arm64.deb
(plus the Proxmox plugin package; see deploy/proxmox/README.md) with plain
dpkg-deb. The script is scripts/build-deb.sh.
- It needs Go,
dpkg-deband the web UI's dependencies (cd web-ui && npm ci). WithSKIP_UI_BUILD=1it embeds the UI that is already inui/distinstead of building it. - The version comes from
git describe --tags, or fromVERSION=.... A tree with no tag gets0.0~git<commits>.<sha>, plus+dirtyif it has uncommitted changes.
sudo apt install ./haify-controller_*_amd64.deb
| Path | What |
|---|---|
/opt/haify/bin/haify-controller, /opt/haify/bin/service-ip |
where the unit runs the controller from, and where the controller finds the service-ip it installs on HA nodes |
/usr/bin/haify (haify-cli links to it), /usr/bin/haify-mcp |
the CLI and MCP server; /opt/haify/bin/haify-mcp links to the latter for haify-mcp-http.service and haify-ai |
/lib/systemd/system/ |
haify-controller.service, service-ip@.service, haify-mcp-http.service |
/usr/share/doc/haify-controller/controller.toml.example |
the full example config |
The package differs from the manual install below in three places:
haifyis/usr/bin/haify, not/usr/local/bin/haify, because/usr/localbelongs to the administrator. Remove copies left there by a manual install (/usr/local/bin/haify,haify-cli,haify-mcp); they come first inPATH. Unit files copied to/etc/systemd/system/by hand likewise override the packaged ones.resource ship, which runshaifyon a storage node, uses either path. On a node with neither, the controller installs its own/usr/bin/haifythere as/opt/haify/bin/haify.- The packaged
service-ip@.serviceruns/opt/haify/bin/service-ip. The package does not change what the controller does on HA nodes: it still installs/usr/local/bin/service-ipand its own/etc/systemd/system/service-ip@.serviceon every HA node that lacks them, this node too. /etc/haify/controller.tomlis created from the example only when no file exists at that path yet (mode 0600, because tokens go in it). It is not a conffile, so an upgrade never touches it.
Installing the package never enables or starts the controller, and upgrading
it never restarts the controller. Only one host per cluster runs the
controller, or drbd-reactor runs it under Self-HA, so the package leaves that
choice to you. After an upgrade, restart the controller yourself: systemctl restart
haify-controller, or under Self-HA install the package on
every node and then run haify ha evict haify-meta. apt remove stops nothing.
apt purge keeps /etc/haify and /var/lib/haify (the database).
Self-HA copies the controller's unit to the standbys as
/etc/systemd/system/haify-controller.service. If /etc/systemd/system/ has
no copy, it reads the packaged one in /lib/systemd/system/, so you do not
need to copy anything first.
Then configure and start the controller as described in "Configure and start" below.
By hand¶
sudo install -d /opt/haify/bin /etc/haify
sudo install -m 755 bin/haify-controller bin/service-ip /opt/haify/bin/
sudo install -m 755 bin/service-ip /usr/local/bin/service-ip
sudo install -m 755 bin/haify /usr/local/bin/haify
sudo ln -sf haify /usr/local/bin/haify-cli # older scripts call it haify-cli
sudo cp configs/haify-controller.service configs/service-ip@.service /etc/systemd/system/
Configure and start¶
A minimal /etc/haify/controller.toml:
[server]
listen_address = "0.0.0.0"
port = 3374
[dispatch]
config_path = "/root/.dispatch/config.toml"
parallel = 10
[log]
level = "info"
format = "json"
[storage]
default_pool_type = "thin_pool"
default_snapshot_suffix = "_snap"
Every other key is in configs/controller.toml.example with its default and a
comment: [server] rest_port, [database], [wan], [tls] (gRPC/REST/UI
transport), [metrics], [ui], [resource] (tiebreaker, fault domains),
[gateway], [schedule] (switches off snapshot, backup, verify and inspection
schedules together), [audit], [self_ha], [alert], [inspect],
[self_heal], [quota], and commented [auth] / [rbac] blocks. API authentication uses either
[auth] enabled with a token (at least 16 characters) or [rbac] with
per-user tokens.
configs/haify-controller.service runs
/opt/haify/bin/haify-controller --config /etc/haify/controller.toml as root with
Environment="HOME=/root". Keep the HOME line. dispatch finds root's files
through $HOME. If the service runs without it, dispatch cannot find them (~/.dispatch,
~/.ssh/known_hosts, the default keys). Operations on other nodes then fail
with unable to authenticate, attempted methods [none], while operations on
the controller's own node still work.
sudo systemctl daemon-reload
sudo systemctl enable --now haify-controller
ss -tlnp | grep -E ':(3374|3375|3376|9433)\b'
haify talks to 127.0.0.1:3374 by default; from another host, pass
--controller <host>:3374 (or -c). With [auth] or [rbac] on, it reads the token from
--token, HAIFY_TOKEN, ~/.haify/token or /etc/haify/token; with [tls] on,
pass --tls-ca (and --tls-cert/--tls-key for mutual TLS).
5. Register nodes and create pools¶
haify node register --name node1 --address <node1-ip>
haify node register --name node2 --address <node2-ip>
haify node register --name node3 --address <node3-ip>
haify node list
--name can be any name you choose. Registration also records the node's real
hostname, which generated .res files use for their on <host> sections.
--replication-address puts DRBD traffic on a separate NIC or subnet.
Label any node that must never be picked as an automatic diskless quorum tiebreaker (typically a WAN/DR node whose public address is not configured on its own interface):
haify node label <dr-node> haify.tiebreaker=false
Without the label, a 2-node LAN resource can pick the DR node as its
tiebreaker. drbdadm up then fails with IP <addr> not found on this host.
Create a pool on each diskful node's data disk. The name gets an haify_ prefix
(vg0 becomes VG haify_vg0); without --type the controller's
storage.default_pool_type applies (thin pool by default):
haify pool create --name vg0 --nodes node1,node2,node3 --devices /dev/sdb
haify pool list
--type lvm builds a thick VG, --type zfs a zpool. A diskless tiebreaker node
needs no pool.
6. Create a replicated resource¶
haify resource create --name data --port 7000 --nodes node1,node2 --size 10G --pool vg0
haify resource status data
--port and --size are required. If you leave out --nodes, the controller
picks --replicas nodes (default 2), preferring the pools with the most free
space. New resources get quorum majority and on-no-quorum io-error. A
--wan resource then has its quorum narrowed to the primary site, so the DR
node gets no vote (section 11).
With [resource] auto_tiebreaker (default on), a 2-node resource also gets a
diskless tiebreaker on a third registered node. That way it keeps quorum when
one node is lost.
A new resource on a thin LVM or ZFS pool skips the initial sync. Its
replicas are brand new and read as zeros, so the controller simply marks them
identical. On thick LVM, or when the volume is encrypted, the controller
force-promotes the resource once and DRBD copies the whole volume to the other
replicas. A thin resource whose peers do not connect within 30 seconds gets
the full sync too. The controller also installs haify-drbd-up.service on the
resource's nodes, so the resource comes back after a reboot.
To create a filesystem and mount it (the volume id is positional):
haify resource fs data 0 ext4 --node node1
haify resource mount data 0 /mnt/data --node node1
7. Gateways¶
A gateway is a DRBD resource plus a drbd-reactor promoter. On whichever node is
Primary, the promoter mounts the resource, brings up a floating service IP
(CIDR) and starts the export. Give each gateway its own resource. Do not also
mount that resource by hand or put it under ha create.
# NFS
haify gateway nfs create --resource share --service-ip 192.168.1.200/24 --export-path /share
# iSCSI (LIO)
haify gateway iscsi create --resource lun1 \
--iqn iqn.2026-01.com.example:haify.lun1 --service-ip 192.168.1.100/24
# NVMe-oF (TCP, port 4420)
haify gateway nvme create --resource ns1 \
--nqn nqn.2026-01.com.example:haify.ns1 --service-ip 192.168.1.150/24
Gateway creation first checks the resource's diskful nodes, before it changes
anything. It looks for the OCF agents the gateway needs, plus
rpc.nfsd/exportfs for NFS and targetcli for iSCSI. Then it:
- adds the small cluster-private state volume if the resource has only one
volume (
[gateway] auto_state_volume); - formats the state volume with ext4 if it has no filesystem yet (for NFS, the export volume too; iSCSI and NVMe-oF export the data volume raw);
- writes
/etc/drbd-reactor.d/haify-{nfs,iscsi,nvmeof}-<resource>.toml; - reloads drbd-reactor.
iSCSI runs on LIO only: --implementation tgt or iet is refused.
To check a gateway, run haify gateway status --resource <r> and, on the
Primary, drbd-reactorctl status and ss -tlnp.
8. High availability¶
Per-resource HA¶
haify ha create data --mount /mnt/data --fstype ext4 [--vip 192.168.1.210/24] [--services myapp.service]
When the Primary fails, drbd-reactor moves everything to a surviving node: it
promotes the resource there, mounts it, raises the VIP and starts the
services. With --vip, the controller installs the service-ip helper and
service-ip@.service on nodes that lack them. It copies its own build, so a
node of another architecture is refused.
Controller Self-HA¶
Self-HA puts the controller database on a DRBD resource haify-meta mounted at
/var/lib/haify and makes drbd-reactor run the controller and its VIP on the
Primary.
Before enabling it, make sure that:
- the controller runs on a registered node, with /etc/haify/controller.toml and
its unit in /etc/systemd/system/ (or, from the Debian package,
/lib/systemd/system/); the unit's ExecStart names the binary by
absolute path;
- every other node has the controller's architecture, or a build for its
architecture sits beside the running binary as haify-controller-<goarch>
(e.g. /opt/haify/bin/haify-controller-arm64); otherwise enable refuses that
node before changing anything;
- passwordless root SSH works between every pair of target nodes, and the
dispatch key named in the dispatch config exists on each of them;
- drbd-reactor is active on all of them, and haify-controller is not running on
any of the other target nodes.
haify ha self enable --pool vg0 --vip 192.168.1.250/24 [--nodes a,b,c] [--port 7999] [--size 1]
haify -c 192.168.1.250:3374 ha self status
haify ha evict haify-meta # move the controller to another node
enable copies four things to the other nodes: the running controller binary
(or the node's -<goarch> build), placed at the path the unit's ExecStart
names; controller.toml; the dispatch config; and the unit itself. It then
turns off haify-controller autostart on the standbys and hands the
controller over to drbd-reactor. The command's own connection drops during the
handoff. From then on, use the VIP for everything (<vip>:3374,
http://<vip>:3376/).
To ship a new controller build:
- Install it at the unit's
ExecStartpath on every node.mvthe running file aside first; overwriting it in place fails withText file busy. - Run
haify ha evict haify-meta. The controller restarts on another node.
Restarting haify-controller on the active node also restarts the promoter
target, so the controller moves to another node the same way.
[self_ha] extra_services = ["haify-ai.service"] makes more units move with the
controller. enable reads this setting when it writes the promoter config, so
set it first.
9. Kubernetes CSI driver (optional)¶
The Kubernetes nodes must be the DRBD storage nodes (the node plugin promotes and mounts DRBD on the host). The controller runs outside Kubernetes and is reached at an IP or VIP on port 3374.
-
Build the image for the node architecture and import it on every node:
Wheredocker build -f Dockerfile.csi --platform linux/<arch> -t haify-csi:latest . docker save haify-csi:latest -o haify-csi.tar sudo k3s ctr images import haify-csi.tar # or ctr -n k8s.io images importregistry.k8s.iois unreachable, pull the sidecars from a mirror, retag them to the names indeploy/k8s/20-controller.yamland30-node.yaml, and import them the same way. -
Edit
deploy/k8s/00-haify-controller-endpoint.yaml(the controller IP/VIP in the manual EndpointSlice) and the StorageClasspoolin40-storageclass.yaml, then runkubectl apply -f deploy/k8s/.50-volumesnapshotclass.yamlneeds the snapshot CRDs (deploy/k8s/README.md). -
For a smoke test, run
scripts/csi-e2e.sh. It uses a PVC on StorageClasshaify-drbdand a pod that writes to it, and prints the node the pod landed on.
Both plugin pods use hostNetwork with dnsPolicy: ClusterFirstWithHostNet, so
they find the haify-controller Service through cluster DNS. A pod can move to
any node that holds a replica of its volume, and its data is already there.
When a node dies, the controller plugin's HA controller taints it
haify.csi/lost-quorum:NoSchedule and force-deletes the pods there whose
volumes kept quorum on the other replicas, so their Deployment or StatefulSet
recreates them elsewhere within about half a minute instead of after
Kubernetes' 300 s. How it decides, how to opt a pod out and how to tune or turn
it off: deploy/k8s/README.md, "When a node dies".
10. AI Copilot (haify-ai, optional)¶
haify-ai serves the web UI's Copilot. It runs haify-mcp as its tool backend
and needs an OpenAI-compatible LLM and embedder.
The Copilot is only in a web UI built with AI=1; the release packages and a
plain make build leave it out. ./scripts/deploy-all.sh <nodes> --ai builds
the UI with it and installs haify-ai and haify-mcp on every node (they run
from the next haify ha evict haify-meta; restarting haify-ai is a failover).
- Install the binaries on every node, since haify-ai follows the controller:
/opt/haify/bin/haify-aiand/opt/haify/bin/haify-mcp. - The configuration lives on the Self-HA mount:
/var/lib/haify/ai/haify-ai.envanddomain.toml. The environment variables are: STEWARD_LLM_API_KEY,STEWARD_LLM_BASE_URL,STEWARD_LLM_MODEL,STEWARD_EMB_*(the olderOPSPILOT_*,OPSDOCTOR_*,OSS_*names are still read)HAIFY_AI_KNOWLEDGE_DB(required),HAIFY_AI_EMB_DIM(default 768)HAIFY_AI_CONTROLLER: default127.0.0.1:3374, the controller on the same nodeHAIFY_AI_MCP_CMD=/opt/haify/bin/haify-mcp(default:haify-mcponPATH)HAIFY_AI_DOMAIN=/var/lib/haify/ai/domain.toml(defaultai/domain.toml, relative to the working directory; the repository's copy isai/domain.toml)HAIFY_AI_KUBECONFIG(optional): adds thehaify_k8s_*tools (deploy/k8s/README.md)HAIFY_AI_ADDR: default127.0.0.1:7634, where the UI proxies/ai/. On any non-loopback address haify-ai refuses to start without a token (HAIFY_AI_TOKEN,HAIFY_TOKEN,~/.haify/tokenor/etc/haify/token).HAIFY_AI_EMB_DIMmust match the dimension the index was built with. If you switch to an embedder with a different dimension, rebuild the knowledge base. If you don't, searches return nothing and report no error.GET /ai/kb/listshows the dimension stored in the index.- Shared knowledge base: every GitHub release carries one built from the
release's own code,
haify-kb.tar.zst. It holds the documentation, the runbooks, the code graph, the strings in the source, the CLI and REST API reference linked to the code that implements them, and the DRBD 9 user's guide. Its manifest says it was built with theembeddinggemma:latestembedder at 768 dimensions, so haify-ai must embed with that model too. Unpack it on every node:
mkdir -p /opt/haify/share
curl -fsSL https://github.com/haify-project/haify/releases/latest/download/haify-kb.tar.zst \
| tar --zstd -xf - -C /opt/haify/share
The release also carries the code graph alone, as
haify-code-graph.json.gz, which Understand-Anything's dashboard opens.
make kb (see ai/kb/build.sh) builds the base yourself, and adds the
LINBIT knowledge-base articles, which the published one leaves out. It writes
dist/kb/haify-kb.db and its manifest haify-kb.json; install both at
/opt/haify/share/ on every node instead. Either way, set
HAIFY_AI_SHARED_KNOWLEDGE_DB=/opt/haify/share/haify-kb.db; it is searched
read-only next to HAIFY_AI_KNOWLEDGE_DB. haify-ai refuses to start if the
manifest is missing, if the cluster's embedder dimension differs from the
manifest's, or if both name an embedder model and the two differ.
- Write a systemd unit haify-ai.service (the repository does not ship one)
with EnvironmentFile=/var/lib/haify/ai/haify-ai.env and WorkingDirectory
and HOME set to /var/lib/haify/ai. Install it on every node but leave it
disabled, and list it in [self_ha] extra_services so the promoter starts
it with the controller.
- HTTP endpoints: GET /ai/health, POST /ai/chat/stream, POST /ai/chat/approve,
/ai/config, and under /ai/kb/: list, doc, doctor, resolve,
upload, ingest, refresh, purge. Send the same session_id in each
chat request to continue a conversation.
haify-mcp serve exposes the same tools to remote MCP clients over HTTP with
token auth (default 127.0.0.1:43871); configs/haify-mcp-http.service is a
reactor-managed unit for it. See mcp.md.
11. WAN replication (optional)¶
WAN replication copies a resource to a DR site. Each resource gets its own
haify-proxy pair (protocol A, mTLS) to carry the traffic. LAN resources are
unaffected.
- The controller copies its own
/usr/local/bin/haify-proxyto the WAN nodes. For nodes of another architecture, put/usr/local/bin/haify-proxy-<amd64|arm64>next to it. If no matching binary is there, the controller assumes the node already has one installed. - The DR node's WAN port must be reachable over TCP from the primary site.
- The controller keeps the proxy CA under
[wan] pki_dir(default/var/lib/haify/wanproxy-pki). - Use haify-proxy 0.2 or later: earlier builds publish no write rates, so
haify wan sizinghas only the controller's history to go on. Since 0.2 the crate and binary are calledhaify-proxy, and run by hand it reads/etc/haify-proxy/config.tomlorHAIFY_PROXY_CONFIG; the old/etc/sds-proxy/config.tomlandSDS_PROXY_CONFIGare still read, with a warning. Proxies the controller runs are given their config path and are not affected.
haify resource create --name data --port 7000 --nodes site1 --pool vg0 --size 10G \
--wan --dr-node site2 --dr-endpoint <site2-public-ip> [--wan-port 37901]
haify resource status data
--wan-port 0 (default) picks a random port above 3000. Failover to the DR site
is manual because replication is asynchronous:
haify resource dr-failover data --yes
haify wan set-endpoint and haify wan repair change or rebuild the
tunnels. haify wan sizing <name> --bandwidth <link> says whether the link
carries the resource's writes and how big the buffer should be, and
haify wan tune <name> --buffer <size> --compression <off|zstd:N> sets them
(user guide, section 13). On a resource created before this release, run
haify wan tune <name> --buffer default once: it raises DRBD's congestion-fill
to match the proxy buffer, which otherwise goes mostly unused.
12. Verify¶
haify node list
haify health-check
haify pool list
haify resource list
haify resource status <name>
haify ha self status
haify inspect run # read-only cluster checks; [inspect] runs them daily
grpcurl -plaintext <host>:3374 list
curl -s http://<host>:3375/v1/nodes
# UI: http://<host-or-VIP>:3376/
On a node:
drbdadm status
drbd-reactorctl status
systemctl is-enabled haify-drbd-up.service
journalctl -u haify-controller -f
13. Known failure modes¶
| Symptom | Cause / fix |
|---|---|
Remote ops fail: unable to authenticate, attempted methods [none] |
Unit lacks Environment="HOME=/root", or the dispatch host section is not keyed by IP (section 3). |
Remote ops fail with host key changed: <ip> |
The node's SSH host key changed. ssh-keygen -R <ip> -f /root/.ssh/known_hosts on the controller. |
Controller exits at start: dispatch config "<path>": stat …: no such file or directory |
[dispatch] config_path points at a missing file. |
Reactor logs IGNORING resource … expected ',' and never fails over |
drbd-utils too old for drbd-reactor; match versions on all nodes. |
Promoter fails: Unit ocf.rs@….service not found |
drbd-reactor installed without its ocf.rs@.service template and wrapper (node-prerequisites.md §1). |
Reactor won't start: Could not read config file: /etc/drbd-reactor.toml |
Create it: snippets = "/etc/drbd-reactor.d" plus a [[log]] table. |
Pool create refuses a disk: <dev> on <node> is in use: mounted at <path> |
Unmount it and remove it from /etc/fstab. |
Pool create refuses a disk: <dev> on <node> holds … retry with --wipe |
The disk has old data. If you don't need it, add --wipe, which erases it. |
| One node loss stops I/O on a 2-node resource | No tiebreaker: register a third node (with auto_tiebreaker on) or add a replica. |
gateway nfs create fails with missing: rpc.nfsd exportfs |
apt-get install nfs-kernel-server (EL: nfs-utils) on the resource's diskful nodes. |
gateway nvme create fails loading nvmet/nvmet-tcp |
apt-get install linux-modules-extra-$(uname -r). |
gateway nvme create --transport rdma fails with no RDMA device |
The node has no RDMA NIC (or soft-RoCE link) under /sys/class/infiniband. |
pool add-cache refuses with "not a thin pool" |
Convert first: haify pool convert-thin --node <n> --pool <p>. |
Backup fails: rclone is required on <node> |
Install rclone on that node. |
| Copilot cites documents unrelated to the question | The knowledge base is nearly empty; check GET /ai/kb/list and ingest content. |