Skip to content

Latest commit

 

History

History
126 lines (88 loc) · 11.5 KB

File metadata and controls

126 lines (88 loc) · 11.5 KB

Deploy

Iva runs on one VPS as two systemd user services, two systemd watchdog timers, and five in-process eve schedules. install.sh sets all of it up (install); this page is what's actually running and how to operate it.

Transport: long polling

Telegram never connects to your server. The permanent scripts/telegram-poll.mjs entry shim starts the TypeScript bridge in scripts/poller/main.ts, which long-polls getUpdates and POSTs each update to the local eve webhook (http://127.0.0.1:8723/eve/v1/telegram) with the shared X-Telegram-Bot-Api-Secret-Token header. Telegram sees an ordinary bot; the channel code is unchanged. No public HTTPS, no domain, no reverse proxy.

The bridge also gives you:

  • 📬 Ordered delivery — advances the offset (data/telegram-offset.json) only after eve replies 2xx, retrying with backoff up to 15s while the server boots.
  • Per-chat pacing — a 1.5s pause between updates to the same chat, so a burst can't start two runs on one session.
  • 🛟 Out-of-band recovery — a handful of slash commands (/restart and friends) are handled by the bridge itself, so they work even when the agent is stuck. Which ones, and what they do: cli.md.

Webhook mode (alternative)

Polling and webhook are mutually exclusive — the bridge calls deleteWebhook on start. If you do have a public HTTPS endpoint, disable the bridge and register the webhook:

systemctl --user disable --now iva-telegram-poll
curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://<your-domain>/eve/v1/telegram",
       "secret_token":"'"$TELEGRAM_WEBHOOK_SECRET_TOKEN"'",
       "allowed_updates":["message","callback_query"]}'

Note: getUpdates — which the setup wizard uses to discover your user ID — stops working while a webhook is registered.

What you give up without the bridge: the out-of-band commands are gone (/menu, /stop, /new, /restart, /start, /model, /think, /update, /usage, /help reach the model as ordinary text instead of being handled), and so are the busy-time queue with its 👀 acknowledgement, per-chat pacing, and durable offset. Only /task, /tasks and /digest are model-routed by design. The ⏹ Stop button keeps working in both modes: the channel answers the tap itself and calls its own cancel route.

systemd units

scripts/cli/systemd.ts is the single source of truth for every unit; the permanent bin/iva.mjs entry shim delegates to the TypeScript CLI. Any restart through the iva CLI regenerates the units first, so Environment=PORT always matches IVA_PORT in .env. Don't hand-edit ~/.config/systemd/user/iva-* — edits get overwritten. If you write your own unit instead, bake the port literally (Environment=PORT=8723): systemd will not expand $IVA_PORT from an EnvironmentFile.

The unit starts eve with --host 127.0.0.1, and a hand-written one must do the same. Setting HOST in the environment is insufficient because eve start overwrites HOST/NITRO_HOST for the process it spawns. Iva also requires the generated ASSISTANT_BEARER on Eve session routes. localDev() is included only under eve dev, which sets EVE_DEV=1; production does not use the client-controlled Host header as authentication.

The two controls cover different failure modes: loopback binding removes direct network reachability, while the bearer protects against another local process, SSRF, or a reverse proxy reaching the port. iva doctor repairs a missing bearer, .env permissions, and an old process still listening beyond loopback.

For a direct smoke test, load the secret without printing it and pass the header to curl through stdin rather than the process arguments:

ASSISTANT_BEARER="$(node --env-file=.env -p 'process.env.ASSISTANT_BEARER')"
IVA_PORT="$(node --env-file=.env -p 'process.env.IVA_PORT || 8723')"
printf 'header = "Authorization: Bearer %s"\n' "$ASSISTANT_BEARER" |
  curl --fail-with-body --config - \
  -X POST "http://127.0.0.1:${IVA_PORT}/eve/v1/session" \
  -H "content-type: application/json" \
  -d '{"message":"Reply with exactly: auth ok"}'
unset ASSISTANT_BEARER IVA_PORT
Unit When Job
iva.service always the agent (eve start), Restart=always
iva-telegram-poll.service always the long-polling bridge
iva-telegram-userbot.service opt-in (iva userbot setup) Telethon userbot MCP proxy — see userbot.md
iva-brain.timer 05:00 nightly schema/health/decay/MOC checks + vault git push
iva-update-check.timer 10:00 daily check for a newer stable Iva version; notify once per version

The Brain and update-check timers stay on systemd on purpose: they're watchdogs that must keep running even if the agent process itself is wedged. iva-brain.timer embeds ASSISTANT_TIMEZONE directly, so its 05:00 schedule remains correct even when the server clock uses UTC — as do the eve schedules below (Environment=TZ in iva.service). Setting the server's own system timezone to match is therefore optional, not required for anything in this doc to work correctly:

# Optional — the generated units and the eve schedules already carry ASSISTANT_TIMEZONE
# themselves, so this only affects OTHER things that read the system clock (log
# timestamps, cron jobs you add yourself, etc.). `node --env-file` parses .env as plain
# KEY=VALUE pairs — unlike `source`, it never shell-interprets its contents, so a stray
# `$(...)` or backtick sitting in .env can't execute anything.
ASSISTANT_TIMEZONE="$(node --env-file=.env -p 'process.env.ASSISTANT_TIMEZONE || ""')"
[ -n "$ASSISTANT_TIMEZONE" ] && sudo timedatectl set-timezone "$ASSISTANT_TIMEZONE"

Memory rollups and the digest: in-process eve schedules

The four memory-rollup cadences moved off systemd and run as agent/schedules/*.ts — eve's native defineSchedule API — inside the iva.service process itself:

Schedule Cron (local time) Job
memory-daily 0 4 * * * (04:00 nightly) transcript → cards + daily summary; Telegram report off by default, enable via memoryReports.enabled in data/settings.json
memory-weekly 15 4 * * 1 (Mon 04:15) 7 dailies → weekly summary; same report switch as memory-daily
memory-monthly 20 4 1 * * (1st, 04:20) weeklies → monthly summary (silent)
memory-yearly 25 4 1 1 * (Jan 1, 04:25) monthlies → yearly summary (silent)
digest 0 8 * * * (08:00 daily) morning digest — off by default, enable via digestSchedule.enabled in data/settings.json

Each one is a thin spawner (agent/lib/schedule-runner.ts): it runs the exact same command the old timer did (flock -w 3900 .memory.lock node --env-file=.env scripts/memory/rollup.ts <period>), under a hard timeout, and records the outcome to data/rollup-status.json. iva.service sets Environment=TZ from ASSISTANT_TIMEZONE (ivaServiceBody() in scripts/cli/systemd.ts), so cron expressions above tick in the configured local time, not the host's system TZ — Nitro's schedule runner carries no timezone of its own otherwise.

Nitro's scheduled-task runner has no Persistent=true equivalent, so a period missed while the server was down does not auto-fire on its own. agent/lib/schedule-migration.ts replaces that: on every server start it compares each period's last recorded success against its most recent scheduled point and, if it's stale and still within a grace window (20h daily / 3d weekly / 7d monthly / 14d yearly), runs it once. A brand-new install seeds a baseline and runs nothing on its first boot, so installing never triggers an immediate storm of catch-up jobs. The same start-up hook also retires the old iva-memory-{daily,weekly,monthly,yearly}.{service,timer} units on any existing install, by exact name only — any unrelated timer you've set up yourself is left alone.

Manual runs and status:

npm run memory -- daily   # or weekly | monthly | yearly
npm run brain
systemctl --user list-timers                             # Brain, update-check (the only two systemd timers left)
systemctl --user status iva.service iva-telegram-poll.service  # the two always-on services
cat data/rollup-status.json                               # last run per eve schedule (or: /menu → ⏰ in Telegram)
iva logs                  # agent; `iva logs poll` for the bridge

The update check fetches the configured Git upstream without calling the model. It stays silent when the installed stable version is current, when the same version was already offered, or when Telegram is not configured. A newer MAJOR.MINOR.PATCH release produces one message in TELEGRAM_DIGEST_CHAT_ID (falling back to the first trusted user) with Update and Later buttons. Errors are journal-only and retry on the next timer run.

Full CLI reference: cli. What the rollups actually write: memory.

nginx and TLS

You need neither for Telegram - polling is outbound-only. If you expose the Telegram webhook, proxy only /eve/v1/telegram; that route verifies X-Telegram-Bot-Api-Secret-Token and the Telegram user allowlist.

Exposing the Eve HTTP channel is a separate security decision. Require HTTPS and preserve the Authorization: Bearer ... header so Iva can verify ASSISTANT_BEARER. Never remove the bearer check merely because the proxy connects to 127.0.0.1: loopback describes the proxy-to-Iva hop, not the original caller.

Moving servers

Your state is three things: the vault (its own git repo, pushed nightly by the Brain pass), .env (all keys), and data/ (tasks.json, usage.jsonl).

  1. Old box: npm run brain to push the vault, then copy .env and data/ off.
  2. New box: run the installer (install) with --skip-setup, drop in .env.
  3. Clone the vault back — gh repo clone <user>/iva-vault <vault-dir> — restore data/, then iva restart.

If all you have left is the vault repo, you lose open tasks and token history. Memory survives intact.

Vercel (advanced)

Iva is built on eve, which deploys to Vercel natively — but self-host is the intended path. If you go there anyway:

  • SchedulesdefineSchedule in agent/schedules/*.ts becomes a real Vercel Cron Job (cron times are UTC there).
  • Storage./data is ephemeral on Vercel; tasks and usage logs need a real DB or KV store.
  • Auth - Eve routes accept Vercel OIDC or ASSISTANT_BEARER. localDev() is enabled only by eve dev; configure the bearer as a Vercel secret for any non-OIDC caller.