Files
vps-tool-avni/docs/guide.md
Jagadeesh 8612b38bee first commit
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:36:08 +02:00

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 --release at 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/avni is 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: s start · x stop · R restart · l logs
  • 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) · b temp-ban · B blacklist selected · W whitelist selected · u unban / un-whitelist · w whois · Enter IP intel · g list blacklist
  • SSH: k kick session · K kick + blacklist its IP
  • Users: Enter activity (logins, failed logins, sudo commands, processes — all system metadata, never reads anyone's home) · N new user (type username or username password) · P set 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. g grant the tool to the selected user · x revoke · Enter activity. 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. s enable (start) · x disable (stop) · R restart · l logs · Enter details (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 (a to type / B on selected / K from SSH) — ipset + iptables at the firewall, covers host and Docker-published ports, survives reboot, never expires. Remove: avni-firewall.sh del <ip>.
  • Whitelist (A to type / W on selected) — an IP that can never be banned or blacklisted, even under brute force. It's added to fail2ban ignoreip AND 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 on prints 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)