Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Install: binary, systemd and Docker

Rustiz is a headless, agent-first social-media scheduler: one Rust binary, one SQLite database, no web GUI, no Redis and no Postgres. The same process serves the REST API (/public/v1), the MCP endpoint (/mcp), the OAuth “connect a channel” callbacks and the scheduler.

This document is the operator reference: install paths, every environment variable the core reads, storage and ffmpeg, reverse proxies and TLS, organizations/API keys and connecting agents over stdio or remote MCP.

Reality check. The port is complete but, as of this writing, no provider has been tested against a real platform. Provider tests use mock servers. Treat the first real connection to every platform as a debugging session. See Porting status and the per-provider pages in ./providers/.


1. Quickstart

PathTLSDatabase / mediaTypical use
Local binary (§1.1)none, plain HTTP on loopback~/.rustiz/rustiz.db + ~/.rustiz configEvaluating, one agent on one machine
Production binary + systemd (§1.2)terminated by Caddy/Nginx/Cloudflare/var/lib/rustiz/rustiz.db + /var/lib/rustiz/uploads (or S3/R2)A real deployment with a public HTTPS origin
Docker (§1.3)terminated by the host proxynamed volume /dataReproducible deployment or a container platform

1.1 Local, single user (no TLS)

Requirements: a Rust toolchain (stable), and optionally ffmpeg for video thumbnails. SQLite is embedded.

cargo build --release --locked -p rustiz
./target/release/rustiz init
./target/release/rustiz providers --status
./target/release/rustiz serve

rustiz init creates:

  • ~/.rustiz/ (mode 0700 on Unix)
  • ~/.rustiz/config.env (mode 0600) containing JWT_SECRET, DATABASE_URL, FRONTEND_URL and RUSTIZ_API_KEY
  • ~/.rustiz/rustiz.db (SQLite, WAL mode)
  • the first organization

It prints one JSON document with the paths, the organization, and an mcpServers block to paste into an agent. When it creates an organization it also prints the API key; when it adopts an existing one it prints api_key_location instead and never replaces the existing secret or creates a second organization.

Then:

  • REST API: http://localhost:5000/public/v1/...
  • MCP over HTTP: http://localhost:5000/mcp (Bearer API key)
  • MCP over stdio: rustiz mcp
  • Health: http://localhost:5000/health → ok

To use another directory (for example a system service):

sudo rustiz init --dir /var/lib/rustiz --name production

init writes the config file but does not persist shell environment variables. In particular, if you run FRONTEND_URL=https://social.example.com rustiz init, the generated config.env still contains FRONTEND_URL=http://localhost:5000; the same applies to DATABASE_URL. Edit the file (or export the variable on every start) before relying on it (see §3.1).

1.2 Production server (systemd + reverse proxy)

Do not expose the plain-HTTP process directly. Put Caddy, Nginx or Cloudflare in front; TLS terminates there.

sudo install -m 0755 target/release/rustiz /usr/local/bin/rustiz
sudo useradd --system --home /var/lib/rustiz --shell /usr/sbin/nologin rustiz
sudo install -d -o rustiz -g rustiz -m 0700 /var/lib/rustiz
sudo -u rustiz /usr/local/bin/rustiz init --dir /var/lib/rustiz --name production
sudo install -d -o rustiz -g rustiz -m 0700 /var/lib/rustiz/uploads
sudoedit /var/lib/rustiz/config.env

Set at least:

FRONTEND_URL=https://social.example.com
DATABASE_URL=sqlite:///var/lib/rustiz/rustiz.db
UPLOAD_DIRECTORY=/var/lib/rustiz/uploads
JWT_SECRET=<keep the value init generated>
RUSTIZ_API_KEY=<keep the value init generated>
EMAIL_PROVIDER=resend
RESEND_API_KEY=re_...
EMAIL_FROM_ADDRESS=notifications@example.com
EMAIL_FROM_NAME=Rustiz
TRUSTED_PROXY_HOPS=1

A systemd unit:

# /etc/systemd/system/rustiz.service
[Unit]
Description=Rustiz (API + MCP + scheduler)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=rustiz
Group=rustiz
WorkingDirectory=/var/lib/rustiz
Environment=RUSTIZ_CONFIG=/var/lib/rustiz/config.env
ExecStart=/usr/local/bin/rustiz serve
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/rustiz

[Install]
WantedBy=multi-user.target

If you want a separate scheduler process (for example API replicas that must not each run jobs):

# /etc/systemd/system/rustiz-worker.service
[Unit]
Description=Rustiz scheduler
After=rustiz.service
[Service]
Type=simple
User=rustiz
Group=rustiz
Environment=RUSTIZ_CONFIG=/var/lib/rustiz/config.env
ExecStart=/usr/local/bin/rustiz worker
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

and start the HTTP tier with rustiz serve --no-worker.

1.3 Docker

The repository Dockerfile builds the binary in rust:1-slim and copies it to gcr.io/distroless/cc-debian12. The runtime image has no shell, no package manager and no ffmpeg (see §5.4).

docker build -t rustiz:local .

# One-time: create the volume, config file and database.
docker volume create rustiz-data
docker run --rm -v rustiz-data:/data rustiz:local init --dir /data

# Run the server.
docker run -d --name rustiz \
  -p 127.0.0.1:5000:5000 \
  -v rustiz-data:/data \
  -e RUSTIZ_CONFIG=/data/config.env \
  -e FRONTEND_URL=https://social.example.com \
  rustiz:local

The image already sets:

DATABASE_URL=sqlite:///data/rustiz.db
UPLOAD_DIRECTORY=/data/uploads
PORT=5000

RUSTIZ_CONFIG=/data/config.env is required if you rely on the file init wrote: without it the binary looks for ~/.rustiz/config.env and will not see the JWT_SECRET created there. Alternatively pass JWT_SECRET and RUSTIZ_API_KEY as environment variables.

A minimal Compose file:

services:
  rustiz:
    build: .
    restart: unless-stopped
    ports:
      - "127.0.0.1:5000:5000"
    environment:
      RUSTIZ_CONFIG: /data/config.env
      FRONTEND_URL: https://social.example.com
      TRUSTED_PROXY_HOPS: "1"
    volumes:
      - rustiz-data:/data

volumes:
  rustiz-data:

Run docker compose run --rm rustiz init --dir /data once before the first up. The distroless image has no shell, so docker exec ... sh will not work; use logs and the /health endpoint instead. If you run the container as a non-root user, chown the volume first.


2. What runs where

CommandHTTP APIMCPSchedulerOAuth callback receiver
rustiz serveyesyes (/mcp)yes (unless --no-worker)yes
rustiz serve --no-workeryesyesnoyes
rustiz workernonoyesno
rustiz mcpnostdio onlyyesyes, on 127.0.0.1:<FRONTEND_URL port> when the URL is plain HTTP on localhost
rustiz initnononono
rustiz org ...nononono
rustiz providersnononono
rustiz migratenononono

The HTTP server and the worker share only the SQLite database. Jobs are claimed with a five-minute lease and a heartbeat every 100 seconds, so a separate rustiz worker and rustiz serve can coexist. SQLite still has one writer at a time; for anything beyond a small deployment, run one worker and one API process, and use WORKER_CONCURRENCY to parallelize inside them.

rustiz with no subcommand is equivalent to rustiz serve.

In-process CLI commands (posts, channels, media, analytics, notifications, find-slot) run the HTTP router against the local database but do not start the scheduler. A post created with --type schedule stays queued until rustiz serve, rustiz worker or rustiz mcp is running.