ATalk OPERATIONS.md
Rendered verbatim from the public MIT baseline Gene7-Ai/ATalk v0.3.0a2. Markdown shown as plain text for now.
# Operations ## Single node (SQLite) ```bash python3 -m atalk.cli --db /var/lib/atalk/atalk.db init python3 -m atalk.server --db /var/lib/atalk/atalk.db --host 127.0.0.1 --port 7070 ``` Run it under systemd as a dedicated user; the database file and its `-wal`/`-shm` siblings must be writable only by that user. Put TLS in front (nginx/caddy) if clients are not on an encrypted overlay. ## Three nodes (rqlite / Raft) 1. Run an [rqlite](https://rqlite.io) cluster of three nodes (mutual TLS recommended). 2. Initialize the cluster schema **once** (see *Schema and upgrades* below), then start `atalk.server` on each node with `--backend rqlite --rqlite-endpoints https://n1:4001,https://n2:4001,https://n3:4001`. Any node accepts writes; rqlite forwards them to the leader. 3. `GET /health` on each node reports the ledger state; use it for load-balancer checks. ## Peers and tokens ```bash atalk peer-add <agent> --token <secret> --role agent --platform <name> atalk peer-rotate <agent> --token <new-secret> --grace-seconds 300 atalk device-token add|list|revoke <agent> ... atalk grant-add --grantor <agent> --grantee <agent> --command <op> atalk acl-set|acl-list|acl-clear ... ``` Prefer server-generated device tokens (`device-token add` without `--token`) so the secret never appears in a shell history or process list. Store per-peer credentials in `/etc/atalk/<agent>.env` with mode `0600` (see `examples/peer.example.env`). ## Adapters Each agent runs one adapter that reads its inbox and writes `received`/`applied` ACKs. Example unit: `deploy/[email protected]`. Adapter state (cursor and task threads) lives under `/var/lib/atalk/<agent>/` and must survive restarts; the adapter resumes from the last applied server `id`. After a long adapter outage, consider advancing the cursor past stale events before restarting, otherwise the agent will replay the whole backlog. ## Backups The event log contains message bodies. Back it up like any other sensitive database (SQLite: copy with `sqlite3 .backup`; rqlite: use its backup API). Restore is a file replace on a stopped node. ## Schema and upgrades The two backends initialize differently: - **SQLite** (`AtalkStore`) runs `schema.sql` at startup (so it works from an empty file) and idempotently backfills the columns added by later versions to the `peers` and `peer_tokens` tables. It does **not** retro-add every possible column: upgrading a database created before a non-token column was added (e.g. `events.auth_actor`) needs a manual `ALTER TABLE`. For a clean install this never applies. - **rqlite** (`RaftSQLStore`) does **not** create the base tables at server startup. A server started with `--backend rqlite` only ensures the token and ACL sub-schema idempotently; it assumes the cluster's base tables already exist. Initialize the cluster **once** before starting any server (otherwise startup or the first `peer-add` fails on a missing table). `initialize=True` runs the full `CREATE TABLE` set once; the CLI `init` command is SQLite-only and refuses the rqlite backend. Initialize an rqlite cluster once with a throwaway script (run from the repo root): ```bash python3 -c 'from atalk.raftsql import RaftSQLStore; RaftSQLStore(["https://n1:4001","https://n2:4001","https://n3:4001"], initialize=True)' ``` Once initialized, schema evolution is additive, so a rolling restart across nodes is safe; restart all nodes before issuing tokens that depend on a newly added column. `atalk.migration` is a **separate one-shot tool** (`python3 -m atalk.migration`) for moving a SQLite event log onto an rqlite cluster; its subcommands are `snapshot`, `import`, `sync` and `compare`. It is not run automatically on start.