7.5 KiB
AVNI Cloud Control Center (avni)
A single command to see and manage the whole VPS. Rewritten in Rust/Ratatui (16-06-2026) for speed — ~2% CPU vs the old Python build's ~22%, microsecond render, incremental log parsing.
- Rust source:
src/main.rs(cargo build --releaseat the repo root) - Deploy/rebuild:
sudo bash install.sh(builds + installs the binary + sets up the access group/sudoers). The binary at/usr/local/bin/avniis a real file (root:root 0755) — NOT a symlink, so authorised non-root users can exec it. - Python fallback:
avni.py(optional; install as/usr/local/bin/avni-py) - Firewall blacklist engine:
avni-firewall.sh(installed to/usr/local/bin/avni-firewall.sh) - IP registry (persistent):
/var/lib/avni/ips.json
Who can run it (access control)
avni is restricted to root + members of the avni group. A non-root user who runs it
is auto-elevated by re-exec'ing through sudo (drop-in /etc/sudoers.d/avni, NOPASSWD for the
avni group). A user with no access just gets a "not authorised" message.
Rule: grant the tool ONLY to users who are already admins (sudo). Make a user an admin in the
Users tab (G) first, then grant the tool in the Settings tab (g). Grant refuses
non-admins. Root can never be revoked.
Open it
avni # full-screen TUI (run in a real terminal / SSH session)
Keys: 1-9 or Tab/←→ switch tabs · ↑↓ (or j/k) select · r refresh ·
q quit. Tabs: Overview · Services · Domains · Traffic · Security · SSH · Users · Settings · Apps.
Times & IST: the VPS runs on Europe/Berlin (CEST). Every displayed time has the
IST equivalent beside it (e.g. 11:25 (14:55 IST)); the Overview shows both clocks.
Per-tab actions (shown in the footer):
- Services:
sstart ·xstop ·Rrestart ·llogs - Domains:
Enter= HTTP code + TLS cert expiry - Traffic:
↑↓switch window 24h / 7d / 30d (vnstat bar chart) - Security:
[]switch sub-tabs ·a= ADD blacklist /A= ADD whitelist (type IP/CIDR/domain, Enter confirms, Esc cancels) ·btemp-ban ·Bblacklist selected ·Wwhitelist selected ·uunban / un-whitelist ·wwhois ·EnterIP intel ·glist blacklist - SSH:
kkick session ·Kkick + blacklist its IP - Users:
Enteractivity (logins, failed logins, sudo commands, processes — all system metadata, never reads anyone's home) ·Nnew user (typeusernameorusername password) ·Pset login password (Linux/SSH password — type it in the masked box, min 8 chars, no spaces or' " \\ $) ·Ggrant sudo ·grevoke sudo ·Llock ·Uunlock ·Ddelete (confirm withy). **root is protected**: its sudo can't be revoked, and it can't be locked or deleted (its password *can* be set). Delete removes the **account only** —/homeand files are kept (safe on this multi-tenant box). CLI:avni adduser [pw]·avni passwd·avni sudo·avni unsudo·avni deluser·avni activity `. - Settings: who may use
avni.ggrant the tool to the selected user ·xrevoke ·Enteractivity. A user must already be an admin (sudo) — grant refuses non-admins. Root always has access and can't be revoked. CLI:avni access·avni grant <u>·avni revoke <u>. - Apps (Applications): the user-facing apps Traefik routes — auto-discovered from each
container's
Host()label, so the list never goes stale. Shows STATE (enabled/disabled), SUBDOMAIN, internal PORT and published HOST PORTS.senable (start) ·xdisable (stop) ·Rrestart ·llogs ·Enterdetails (live HTTPS code + TLS cert expiry + compose dir). CLI:avni apps. (Appwrite itself isn't listed — it's the Traefik host / catch-all, managed via its own stack, and you wouldn't want a one-key "disable" on the thing that owns 80/443.)
Security tab — the IP registry (CrowdSec-inspired)
Every IP that has touched the box is stored in /var/lib/avni/ips.json with
country, attempts, logins, first/last seen, ban/blacklist status — and it
persists across log rotation. Switch sub-tabs with [ and ] (the active
one is highlighted in the cream bar):
Attackers · Banned · Whitelist · Unbanned · New · Logins · All.
Three kinds of decision:
- Temp ban (
b) — fail2ban, expires after the jail bantime. - Permanent blacklist (
ato type /Bon selected /Kfrom SSH) — ipset + iptables at the firewall, covers host and Docker-published ports, survives reboot, never expires. Remove:avni-firewall.sh del <ip>. - Whitelist (
Ato type /Won selected) — an IP that can never be banned or blacklisted, even under brute force. It's added to fail2banignoreipAND a firewall guard refuses to blacklist it. Whitelist your own home/office IPs first so a brute-force burst can never lock you out. Anti-lockout also refuses to blacklist your current SSH client IP. CLI:avni whitelist [add|del <ip>].
The UI loads data in a background thread, so typing in the add box and switching tabs stay instant even while it refreshes. Actions reflect instantly — grant/revoke, lock, ban/blacklist/whitelist update the on-screen row immediately (optimistic), then the background re-gather reconciles with the real system state.
CLI (scriptable / same data)
avni status # host + counts
avni services # ALL containers + status + domain
avni apps # user-facing apps only: state · subdomain · port · host ports
avni ips [attackers|banned|unbanned|new|logins|all]
avni attackers [N] # top N with country
avni ban <ip> | unban <ip>
avni blacklist [add <ip|cidr|domain>]
avni geo <ip> | ssh | users
avni adduser <user> [password] # create account (-m home, bash); root-safe
avni passwd <user> <password> # set a user's Linux login / SSH password (root-safe)
avni sudo <user> | unsudo <user> # grant / revoke sudo (refuses root)
avni deluser <user> # delete account, KEEP /home (refuses root)
Speed design (inspired by k9s / lazydocker / ctop / GoAccess / CrowdSec)
- Loading splash on startup until the first snapshot lands (no blank shell).
- Parallel gather: the independent fetches (docker, auth-log registry, sessions, users, vnstat, counts) fan out across threads, so a full refresh is ~2 s (the slowest single part) instead of the ~11 s sum. Public IP is fetched once (was a 4 s curl every refresh).
- Incremental log parsing: stores a byte-offset per log inode and reads only
newly-appended lines, not the whole 17 MB
auth.log, on each refresh. - Traffic pins the default-route interface (eth0) — the box has ~80 docker veth/bridge interfaces, so vnstat's interfaces[0] was a zero-traffic bridge (empty graph). All 3 windows (24h/7d/30d) are precomputed, so switching is instant; bars show human values.
- Cached data, refreshed every ~10 s or on
r; rendering is decoupled from data gathering. - Shells out to the system tools (docker, fail2ban-client, ipset, vnstat, geoiplookup, who/last/getent) — no daemon, no heavy deps.
- GoAccess is installed for future per-domain HTTP stats (needs Traefik access
logs;
avni-py accesslog onprints the opt-in steps).
Build / rebuild
cargo build --release # at the repo root
# (toolchain: rustup stable; a distro rustc that is too old for ratatui 0.26 will fail — use rustup)