Aromba — Onboarding
Goal: from “copy one command” to “wow message in Telegram” in under 60 seconds, touching nothing on the server.
1. Install command
curl -fsSL https://aro.mba/install | sh
What the script does, in order:
- Checks OS: Ubuntu 22.04+ / Debian 12+ (phase 1). Anything else → prints “Aromba supports Ubuntu and Debian for now” and exits.
- Needs root or sudo. Without either → prints how to run it with sudo and exits.
- Creates user
aromba(no login shell) and folder/opt/aromba; installsbubblewrapfrom the distro repos — the cage,05-security.mdsection 3. - Downloads one tarball for the CPU (amd64 / arm64) with the
arombaandaromba-wardenbinaries, verifies its checksum and signature, puts them in/usr/local/bin. - Writes the Warden’s fixed command policy to
/etc/aromba/warden/policy.toml, root-owned (see05-security.md). No sudoers entries: the Worker runs without sudo, the Warden runs as root. - Installs two systemd services,
aromba-warden.service(root) andaromba.service(useraromba), enables and starts them. - The daemon asks aro.mba for a one-time pairing token and prints:
✓ Aromba installed on vultr-fra-01
Open on your phone to connect Telegram:
https://t.me/aroomba_bot?start=a8f3k2
[QR code]
Waiting for you… (this link expires in 15 minutes)
Into a group, with one command (PLAN D226). Added to a Telegram group with no server, Aromba’s greeting carries the command that brings one there:
curl -fsSL https://aro.mba/install | sh -s -- --join k7mn4pq2
— or aromba pair --join k7mn4pq2 where Aromba is installed already. No link is printed: the server asks that group to let it in, and the person who added Aromba there taps [Let it in], because the server becomes theirs, with their slot and their AI credits. Anybody else’s tap changes nothing. The code works once, for a day; a server that has a chat already is refused, since aromba pair is the way to move one.
- The script waits until pairing completes, prints
✓ Connected. Check Telegram.and exits. Ctrl+C is fine: the daemon keeps waiting.
Re-running the script on an installed server updates the binary and, if not paired, prints a new link. It never re-creates the user or resets config.
Leaving: aromba uninstall (root) stops and removes the services, the binaries, /opt/aromba and the aromba user, and asks whether to keep /var/lib/aromba and /etc/aromba — the journal, state, settings and the relay identity. --keep-data keeps them without asking, --yes removes everything without asking.
2. Pairing
- Token: 6 characters, single use, 15-minute expiry, bound to this server’s ID.
- Tapping the link opens the bot with
/start a8f3k2. The relay matches token → server, storeschat_idfor that server, marks it paired. - One server ↔ one chat. That is the whole model for now. The bot never mixes servers in one chat, and since
PLAND164 the relay will not let it: a unique index onchat_id, and a refusal before it that says what to do instead. - A second server is a chat of its own (PLAN D108): the pairing link opens a Telegram group, named after the server, and the bot speaks only there. Telegram has no way to make a bot per server — only a person at BotFather can — and a group is that, without one. The first server keeps the private chat. The owner of a server is one account until Teams; anyone else who opens its link is told which chat holds it, and the owner sees that they tried.
- How the second one is paired (D164): its code, pasted in the owner’s own chat, is refused there — “this chat is football’s” — without spending the code, and the refusal says to make a group, add the bot and send the same code in it. In the group that code pairs the machine and the group becomes its chat: its receipts, its alerts and its buttons live there, and it needs no second room. A group that already holds a server refuses the next one for the same reason a private chat does.
- Three servers to an account (
PLAND125): rooms are not counted — a group Aromba works in costs nothing — and the AI allowance is per person, so the third machine costs what the first did. Future (not phase 1–3): sharing the connection with a teammate. Design later; the daemon storeschat_idper server so nothing here blocks it. - To re-pair, the user runs
aromba pairon the server, which prints a new link and invalidates the old chat. - Any message to the bot from a chat that owns no server gets one reply: “This bot only talks to servers it’s installed on. Install: aro.mba” — and is then ignored.
3. First scan (phase 1)
Read-only. Runs right after pairing, takes 10–20 seconds. Checks:
| Check | Source |
|---|---|
| Disk usage, largest offenders | df, du on known junk paths: Docker, /var/log, apt cache, journal, ~/.cache, ~/.npm, node_modules in untouched projects |
| Containers | docker ps -a if Docker exists: stopped, restarting |
| Services | systemctl --failed |
| SSH | failed logins in 24h from journalctl / auth.log; is fail2ban installed |
| Updates | apt list --upgradable, count security ones |
| Memory / swap | free |
| Uptime, reboot required | uptime, /var/run/reboot-required |
| Backups | none found → say so |
Only findings are reported. A clean server gets a short “all good” report, still with a number in it (“Disk 34%, nothing to clean”). An empty server (nothing but ssh) gets “Nothing here but ssh. I’ll check in each morning.” — the “prepare it” offer in 10-voice.md needs the assistant and appears from phase 3.
4. Bot conversation — verbatim
Right after pairing:
Connected to vultr-fra-01 (Ubuntu 24.04). Give me 20 seconds to look around.
The report. Bullets are only the checks that found something. Order: disk, memory, containers/services, SSH, updates, backups. The SSH line says “in 24h” only when the journal reaches that far back; without a persistent journal it says “in 9h” — since boot.
Here’s what I found: • Disk 78% full — 41 GB is Docker leftovers and old logs • 3 containers stopped, 1 restarting in a loop (api-worker) • SSH: 412 failed logins in 24h, fail2ban not installed • 14 security updates waiting • No backups found
I haven’t touched anything. [Show details] [What can you fix?]
“Show details” — one message per bullet, plain text, the raw numbers (paths and sizes, container names, top attacking IPs, update list).
Two questions, once, after the first report (phase 3, PLAN D108). Each is one message with its buttons, and each can be left:
Where are you? I keep this server’s clock on UTC, and I’d rather tell you times in yours. [Same as the server] [Tell me] [Later]
“Tell me” takes a city or an offset in plain words; the answer is the Timezone setting, and every time in the chat is the user’s from then on.
Will you want to send me voice messages? Understanding them needs about 500 MB on this server, and I’d rather not take it if you won’t. [Yes, install it] [No] [Ask me later]
“What can you fix?” — phase 1 answer (no chores yet):
Right now I only look. Cleaning and fixing come in the next update — you’ll get a message here when it’s ready. I’ll check in each morning with anything new.
“What can you fix?” — phase 2 answer:
Green — I’ll do these tonight without asking. All of it is regenerable: • Docker cleanup (~38 GB) • Rotate logs (~3 GB)
Red — I’ll ask you each time: • Install fail2ban • Apply 14 security updates • Restart api-worker
[Turn on green] [Choose myself]
“Turn on green”:
Done. I’ll run tonight when the server is quiet and send you a receipt in the morning. Anything red, I’ll ask first.
“Choose myself” — a list of chores with on/off toggles (see 03-bot.md).
One minute later (phase 3 only):
One more thing. Want to ask me questions like “why is api-worker restarting?” That runs on open models — DeepSeek, GLM, Kimi, gpt-oss — through one OpenRouter key: a fraction of a cent a question, a few dollars in a busy month. [Add key] [Later]
“Add key”:
Run
aromba keyon the server and paste it there. Keys don’t belong in a chat.
“Later” — nothing more. The offer repeats once, after the first morning receipt, never again.
Morning receipt (phase 1: a mini-scan diff — “Since yesterday:” and only the lines that moved, with [Show details] [Ignore]; “Show details” sends today’s findings, one message per bullet, as after the first report; phase 2+: real receipt):
Night receipt · 03:14–03:41 Freed 39.2 GB (Docker 36.1, logs 3.1) Skipped: api-worker still restarting. [Take a look] [Ignore]
With the first real receipt (phase 2), once:
Share the anonymous total of freed space? One number a night, no server name. It goes on aro.mba’s counter. [Yes] [No]
5. Tone rules for the bot
- Short. Numbers first. No exclamation marks, no emoji except the check mark in the terminal.
- Says what it found, what it did, what it skipped. Never “successfully”, never “please”.
- Always ends with buttons when a decision exists, and with nothing when it doesn’t.
- Every message that reports an action links to the same action in the receipt.
6. Edge cases
| Case | Behavior |
|---|---|
| Token expired before tap | Bot: “That link expired. Run aromba pair on the server for a new one.” |
| Server offline at pairing | Bot: “Connected, but the server isn’t answering yet. I’ll message you when it’s back.” |
| Docker not installed | Docker bullets simply absent |
| Second server from the same account | Not supported yet. Bot: “This account is already connected to vultr-fra-01. One server per account for now — more coming.” |
| User blocks the bot | Daemon keeps running chores, stops sending. Receipts still land in the dashboard (phase 4) |