# 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 ```bash 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 `' " \` \ $`) · `G` grant sudo · `g` revoke sudo · `L` lock · `U` unlock · `D` delete (confirm with `y`). **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** — `/home` and 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 ` · `avni revoke `. - **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 `. - **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 ]`. 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) ```bash 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 | unban avni blacklist [add ] avni geo | ssh | users avni adduser [password] # create account (-m home, bash); root-safe avni passwd # set a user's Linux login / SSH password (root-safe) avni sudo | unsudo # grant / revoke sudo (refuses root) avni deluser # 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 ```bash 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) ```