moxzi
Docs / Server / Operations

Operationsalpha

Run moxzid as a service with health checks, TLS, metrics, logs and resource limits.

For production use, run moxzid under a process supervisor, with a state directory, a token, and a TLS reverse proxy in front.

Health checks#

EndpointReturnsUse it for
GET /healthok once the process is listeningLiveness
GET /health/readyready, or 503 with what it's doing while it loads actorsReadiness. Send traffic only after this returns ready
until [ "$(curl -s http://127.0.0.1:7000/health/ready)" = ready ]; do sleep 0.2; done
echo ready

systemd#

# /etc/systemd/system/moxzid.service
[Unit]
Description=moxzid
After=network-online.target

[Service]
User=moxzi
StateDirectory=moxzi
EnvironmentFile=/etc/moxzi/env
ExecStart=/usr/local/bin/moxzid -m /etc/moxzi/actors.json -s /var/lib/moxzi -l 127.0.0.1:7000
Restart=on-failure
TimeoutStopSec=60

[Install]
WantedBy=multi-user.target

Put MOXZID_TOKEN=… in /etc/moxzi/env and make that file readable only by root and the moxzi user. Keep moxzi next to moxzid in /usr/local/bin so upgrades can be checked (see Upgrade an actor).

systemctl stop sends SIGTERM. moxzid finishes the current message, saves every actor and exits; TimeoutStopSec gives a long-running message time to finish.

Docker#

There's no published image yet. This Dockerfile builds one from a release tarball:

FROM debian:bookworm-slim
ARG MOXZI_VERSION=0.1.0-alpha.7
ARG TARGET=x86_64-unknown-linux-gnu
ADD https://moxzi.ai/releases/v${MOXZI_VERSION}/moxzi-${MOXZI_VERSION}-${TARGET}.tar.gz /tmp/moxzi.tgz
RUN tar -xzf /tmp/moxzi.tgz -C /opt && mv /opt/moxzi-* /opt/moxzi && rm /tmp/moxzi.tgz
ENV PATH=/opt/moxzi:$PATH
WORKDIR /app
EXPOSE 7000
CMD ["moxzid", "-m", "/app/actors.json", "-s", "/state", "-l", "0.0.0.0:7000"]
docker build -t moxzid .
docker run -p 7000:7000 -e MOXZID_TOKEN=change-me \
  -v moxzid-state:/state -v "$PWD/app:/app:ro" moxzid

Inside a container moxzid listens on 0.0.0.0, so it needs MOXZID_TOKEN.

TLS#

moxzid doesn't do TLS. Put a reverse proxy in front of it. With Caddy, which gets certificates automatically:

actors.example.com {
    reverse_proxy 127.0.0.1:7000
}

Configure rate limits and IP restrictions in the proxy too.

Metrics#

GET /metrics (with the token) returns Prometheus metrics:

curl -s -H "Authorization: Bearer $MOXZID_TOKEN" http://127.0.0.1:7000/metrics | grep greeter
moxzid_actor_messages_total{actor="greeter"} 2
moxzid_actor_traps_total{actor="greeter"} 0
MetricTypeLabels
moxzid_uptime_secondsgauge
moxzid_http_requests_totalcounterendpoint (a class of endpoint, not a URL)
moxzid_actor_messages_totalcounteractor, as the client named it
moxzid_actor_traps_totalcounteractor
moxzid_actor_cyclesgaugeactor (hex id)
moxzid_actor_heap_bytesgaugeactor (hex id)
moxzid_state_bytesgaugeOnly with -s

Prometheus configuration:

scrape_configs:
  - job_name: moxzid
    authorization:
      credentials: change-me
    static_configs:
      - targets: ["127.0.0.1:7000"]

Things to alert on: traps rising on one actor (a bug in that actor), moxzid_actor_cycles approaching zero (its calls will start failing with 402; add cycles with POST /cycles/ACTOR), and moxzid_actor_heap_bytes growing without bound.

Logs#

By default moxzid logs readable text to stderr. --log-json writes one JSON object per event instead:

moxzid -m actors.json -s ./state --log-json 2> moxzid.log
cat moxzid.log
{"ts":1790950963834,"event":"recovered","actor":"counter","replayed":"0"}
{"ts":1790950963834,"event":"ready","actors":"1"}
{"ts":1790950963999,"event":"job","kind":"call-json","actor":"509cb312c283d83e3ac18cc6b0a027615a1cd812ee17f664dc799c4303","method":"inc","ok":"true","ms":"20"}
{"ts":1790950964013,"event":"shutdown","actors":"1"}
eventWhen
jobA call, query, install, upgrade or other request finished
recoveredAn actor was restored at startup, with the number of log entries replayed
readyStartup finished
shutdownThe server stopped cleanly
stable-types-changedAn upgrade changed an actor's stable variables

Heartbeats and timers aren't logged. Actors' own Debug.print output is separate: see Read an actor's output.

Resource limits#

OptionDefaultLimits
--instruction-limit N40,000,000,000Instructions per message. A message that exceeds it traps and is rolled back
--cycles N100,000,000,000,000Each actor's starting cycle balance. 0 turns cycle charging off
--heartbeat-ms MS1000How often system func heartbeat runs. 0 turns heartbeats off
--timer-ms MS100How often expired timers are checked. Separate from heartbeats
--outcall-timeout-secs S30Time limit for one HTTPS outcall
--outcall-max-response-bytes BYTES16 MiBHost ceiling for one response body; actors may request less
--no-outcallsoffActors can't make HTTPS outcalls
--live-cap NoffMost actors kept loaded at once. See Persistence
--history N50Recent messages kept per actor for /history

A message costs 590,000 cycles plus 4 cycles per 10 instructions.

Troubleshooting#

SymptomCauseFix
503 starting: …Actors are still loadingWait for /health/ready
401Missing or wrong tokenSend Authorization: Bearer TOKEN
402The actor is out of cyclesPOST /cycles/ACTOR with an amount as the body
400 … is already installed/install with a name that existsUse /upgrade/NAME
413Request body too largeSend a smaller module or request
Refuses to start: refusing to bind … without authenticationPublic address without a tokenSet MOXZID_TOKEN
REJECTED NAME … at startupThat module couldn't be loadedFix the module; the other actors are running
curl localhost:7000 returns nothing on macOSAirPlay Receiver also uses port 7000Use 127.0.0.1:7000, or turn off AirPlay Receiver in System Settings
On this pageHealth checkssystemdDockerTLSMetricsLogsResource limitsTroubleshooting