Build on Anahita

Services over gRPC

A service of your own, running in the same cluster as an Anahita site, can call Anahita's three gRPC services directly: the graph of profiles, posts and the links between them, the files, and mail. It may be written in any language that has gRPC, and be under any licence: the definitions it is made from are Apache 2.0 (Licensing).

Most things built on Anahita should use the API instead: an app, a bot, or a service outside the cluster. The gRPC services are the inside of a site, and trust whoever calls them (see Checking access).

What you need

What Where
The definitions anahita-libs/proto: 22 .proto files, imported as proto/graphpb/node.proto and the like, so code is generated from anahita-libs
A certificate from the site's own authority A certificate
The services' addresses, inside the cluster graph-grpc-service:50051, file-grpc-service:50051 and mail-grpc-service:50051, the *_SERVICE_ADDR settings in k8s/base/config.env
gRPC for your language Below, with the versions these examples were tried with

Every example here is in examples/grpc/ (opens in a new tab), one folder for each language, and reads the same two settings Anahita's services do: GRAPH_SERVICE_ADDR, and GRPC_TLS_DIR, the folder holding the certificate (/run/secrets/grpc unless said). They are Apache 2.0, like the definitions, so that they can be copied.

A certificate

Anahita's services accept only callers with a certificate from the site's own authority, anahita-grpc, which cert-manager keeps in the cluster (Certificates between the services). Ask it for one for your service:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: anahita-grpc-my-service
  namespace: anahita
spec:
  secretName: anahita-grpc-my-service
  commonName: my-service
  usages:
    - client auth
  duration: 2160h
  privateKey:
    algorithm: ECDSA
    size: 256
  issuerRef:
    name: anahita-grpc
    kind: Issuer

and mount its Secret where your service reads it:

      containers:
        - name: my-service
          volumeMounts:
            - name: grpc-tls
              mountPath: /run/secrets/grpc
              readOnly: true
      volumes:
        - name: grpc-tls
          secret:
            secretName: anahita-grpc-my-service

The Secret holds tls.crt, tls.key and ca.crt. cert-manager renews it before it runs out, in place; a service that keeps running for longer than the certificate's 90 days must read it again, or be restarted.

Generating the code

The code is generated from the definitions with buf (opens in a new tab), with plugins it fetches itself, so no compiler or plugin is needed on your machine. Each folder has its own buf.gen.yaml, with the plugins' versions; run, in it:

buf generate ../../../anahita-libs --path ../../../anahita-libs/proto/graphpb

The generated code goes into gen/. Node.js reads the .proto files when it starts, so nothing is generated for it. The libraries each needs:

# go.mod: google.golang.org/grpc v1.76.0, google.golang.org/protobuf v1.36.10.
# Inside a checkout of anahita-services, prefix go with GOWORK=off: its
# go.work does not list the example.
buf generate ../../../anahita-libs --path ../../../anahita-libs/proto/graphpb
go mod tidy

Reading a node

Each example reads one node by its id, with NodeService.Read, and prints its type, alias and name:

1 node.actor.person-service.person.v1 @dax Dax
cert, err := tls.LoadX509KeyPair(filepath.Join(tlsDir, "tls.crt"), filepath.Join(tlsDir, "tls.key"))
if err != nil {
	log.Fatal(err)
}
ca, err := os.ReadFile(filepath.Join(tlsDir, "ca.crt"))
if err != nil {
	log.Fatal(err)
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(ca)
creds := credentials.NewTLS(&tls.Config{
	MinVersion:   tls.VersionTLS13,
	Certificates: []tls.Certificate{cert},
	RootCAs:      pool,
})

conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(creds))
if err != nil {
	log.Fatal(err)
}
defer conn.Close()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
reply, err := graphpb.NewNodeServiceClient(conn).Read(ctx, &graphpb.NodeReadRequest{Id: id})
if err != nil {
	log.Fatal(err)
}

node := reply.GetNode()
fmt.Println(node.GetId(), node.GetType(), "@"+node.GetAlias(), node.GetName())

The whole of each, with where it reads its settings and the node's id, is in its folder in examples/grpc/. The other calls are in the .proto files: each service block lists what it answers, and each message what it takes and gives.

Checking access

The gRPC services do not check who may see what. A node read from the graph comes back whoever it belongs to and whoever may see it: a post only its author's followers may read, a profile that is private, a person who has blocked another. Anahita's own services check before they answer anybody, with the rules in anahita-libs/permissions (Permissions). A service of yours that shows what it reads to people must make the same checks itself, or ask through the API, which makes them for it, as the person who is asking.

Any holder of a certificate from the authority may call any of the three services, so keep the Secret as you would a password.

Trying it on your own machine

On the k3d cluster: ask for the certificate above, copy its three files out of the Secret, forward graph-grpc's port, and run an example in a container that knows the service by its name in the cluster, so that its certificate is accepted:

kubectl --kubeconfig ~/.config/k3d/kubeconfig-anahita.yaml -n anahita \
  port-forward --address 127.0.0.1 svc/graph-grpc-service 50051:50051

docker run --rm --add-host graph-grpc-service:host-gateway \
  -v "$PWD/certs:/run/secrets/grpc:ro" -v "$PWD:/example" -w /example \
  python:3.12-slim sh -c 'pip install -r requirements.txt && python client.py 1'

The examples were each tried this way, against a site's graph, on 2026-10-26. Where host-gateway gives an IPv6 address that does not answer, as it can for PHP's gRPC, give the host's IPv4 address instead (getent ahostsv4 host.docker.internal in a container).

From anahitasocial/anahita-services, 2026-10-27.

Written by Claude, reviewed by Rastin Mehr · About this