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#
| Endpoint | Returns | Use it for |
|---|---|---|
GET /health | ok once the process is listening | Liveness |
GET /health/ready | ready, or 503 with what it's doing while it loads actors | Readiness. 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
| Metric | Type | Labels |
|---|---|---|
moxzid_uptime_seconds | gauge | |
moxzid_http_requests_total | counter | endpoint (a class of endpoint, not a URL) |
moxzid_actor_messages_total | counter | actor, as the client named it |
moxzid_actor_traps_total | counter | actor |
moxzid_actor_cycles | gauge | actor (hex id) |
moxzid_actor_heap_bytes | gauge | actor (hex id) |
moxzid_state_bytes | gauge | Only 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"}
event | When |
|---|---|
job | A call, query, install, upgrade or other request finished |
recovered | An actor was restored at startup, with the number of log entries replayed |
ready | Startup finished |
shutdown | The server stopped cleanly |
stable-types-changed | An 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#
| Option | Default | Limits |
|---|---|---|
--instruction-limit N | 40,000,000,000 | Instructions per message. A message that exceeds it traps and is rolled back |
--cycles N | 100,000,000,000,000 | Each actor's starting cycle balance. 0 turns cycle charging off |
--heartbeat-ms MS | 1000 | How often system func heartbeat runs. 0 turns heartbeats off |
--timer-ms MS | 100 | How often expired timers are checked. Separate from heartbeats |
--outcall-timeout-secs S | 30 | Time limit for one HTTPS outcall |
--outcall-max-response-bytes BYTES | 16 MiB | Host ceiling for one response body; actors may request less |
--no-outcalls | off | Actors can't make HTTPS outcalls |
--live-cap N | off | Most actors kept loaded at once. See Persistence |
--history N | 50 | Recent messages kept per actor for /history |
A message costs 590,000 cycles plus 4 cycles per 10 instructions.
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
503 starting: … | Actors are still loading | Wait for /health/ready |
401 | Missing or wrong token | Send Authorization: Bearer TOKEN |
402 | The actor is out of cycles | POST /cycles/ACTOR with an amount as the body |
400 … is already installed | /install with a name that exists | Use /upgrade/NAME |
413 | Request body too large | Send a smaller module or request |
Refuses to start: refusing to bind … without authentication | Public address without a token | Set MOXZID_TOKEN |
REJECTED NAME … at startup | That module couldn't be loaded | Fix the module; the other actors are running |
curl localhost:7000 returns nothing on macOS | AirPlay Receiver also uses port 7000 | Use 127.0.0.1:7000, or turn off AirPlay Receiver in System Settings |