# avni — a single-binary control center for your VPS `avni` is a fast, dependency-light **terminal dashboard (TUI) + CLI** for operating a self-hosted Linux server: see your Docker apps, domains, bandwidth, SSH sessions and brute-force attackers at a glance — and act on them (start/stop services, ban/blacklist IPs, manage users) without leaving the terminal. It shells out to the tools already on the box (`docker`, `fail2ban-client`, `ipset`, `vnstat`, `who`/`last`, `getent`, …) — **no daemon, no database, no agent**. One Rust binary, ~2% CPU, microsecond redraws. > Built for a small multi-app VPS behind [Traefik](https://traefik.io/). It auto-discovers > the apps Traefik routes, so the dashboard never goes stale as you add or remove services. ``` ┌ AVNI CLOUD · control center ─────────────────────────────────────────────────────────┐ │ 1.Overview · 2.Services · 3.Domains · 4.Traffic · 5.Security · 6.SSH · 7.Users · 8.Set…│ └────────────────────────────────────────────────────────────────────────────────────────┘ ┌ Applications (9/9 enabled) — s enable · x disable · R restart · l logs · ↵ details ─────┐ │ APP STATE SUBDOMAIN PORT HOST PORTS │ │ » ● gitea enabled git.example.com 3000 127.0.0.1:3000->3000 │ │ ● n8n enabled n8n.example.com 5678 127.0.0.1:5678->5678 │ │ ● uptime-kuma enabled uptime.example.com 3001 127.0.0.1:3001->3001 │ │ ○ analytics disabled stats.example.com 3000 - │ └──────────────────────────────────────────────────────────────────────────────────────────┘ s enable x disable R restart l logs ↵ details 1-9/⇄ tabs ↑↓ move r refresh ``` --- ## Table of contents - [Features](#features) - [Requirements](#requirements) - [Install](#install) - [Using the TUI](#using-the-tui) - [CLI](#cli-scriptable) - [Access control — who can run `avni`](#access-control--who-can-run-avni) - [How it works](#how-it-works) - [Data & file locations](#data--file-locations) - [Security notes](#security-notes) - [Build from source](#build-from-source) - [Python fallback](#python-fallback) - [License](#license) --- ## Features A tabbed dashboard (switch with `1`–`9`, `Tab`, or `←/→`): | Tab | What it shows / does | |-----|----------------------| | **Overview** | host, IP, uptime, load/memory/disk gauges, service & attacker summary, dual clock (server + your local time) | | **Services** | every Docker container with status + the domain Traefik routes to it; `s`/`x`/`R` start/stop/restart, `l` logs | | **Domains** | every `Host()` route → backend; `Enter` checks live HTTP status + TLS cert expiry | | **Traffic** | `vnstat` bandwidth bar chart, 24h / 7d / 30d windows (pins the real uplink interface) | | **Security** | a persistent **IP registry** of everyone who has touched the box (country, attempts, logins, status). Temp-ban (fail2ban), permanent firewall **blacklist** (ipset+iptables), and a **whitelist** that can never be banned | | **SSH** | live sessions (kick / kick+blacklist) + the effective `sshd` policy | | **Users** | list/create/delete Linux users, grant/revoke sudo, lock/unlock, **set login password**, and a full per-user **activity** view (system metadata only) | | **Settings** | delegate the tool to other admins (see [access control](#access-control--who-can-run-avni)) | | **Apps** | the user-facing apps Traefik routes — **enable/disable**, ports, subdomain, live HTTPS + cert details | Highlights: - **Auto-discovery** — apps and domains come straight from container labels; nothing to configure. - **Persistent attacker registry** with **incremental log parsing** (reads only newly-appended bytes of `auth.log`, survives rotation). - **Optimistic UI** — actions reflect instantly, then reconcile with the real system state. - **Dual time** — every timestamp shows a second timezone beside it (configured for IST in the source; see [Build](#build-from-source) to change). - **Scriptable** — every view is also a one-shot CLI subcommand. ## Requirements - Linux, run as **root** (or as a delegated `avni`-group member — it self-elevates via sudo). - A recent **Rust** toolchain (rustup stable; ratatui 0.26 needs a newer rustc than some distros ship). - Expected on the box (features degrade gracefully if missing): `docker`, `fail2ban-client`, `ipset` + `iptables`, `vnstat`, `geoiplookup` (geoip-bin), `whois`, plus coreutils (`who`, `last`, `lastb`, `getent`, `passwd`, `chpasswd`). ## Install ```bash git clone https://git.example.com/you/vps-tool-avni.git cd vps-tool-avni sudo bash install.sh ``` `install.sh` builds the release binary and installs it to `/usr/local/bin/avni`, installs the firewall helper to `/usr/local/bin/avni-firewall.sh`, creates the `avni` group, and writes a sudoers drop-in so delegated admins can launch it. Re-run it after pulling changes. Then just: ```bash avni # full-screen dashboard avni status # one-shot CLI ``` ## Using the TUI Global keys: `1`–`9` / `Tab` / `←→` switch tabs · `↑↓` (or `j`/`k`) move · `r` refresh · `q` quit. Per-tab actions are always shown in the footer. The most useful: - **Services / Apps** — `s` start/enable · `x` stop/disable · `R` restart · `l` logs · `Enter` details - **Security** — `[` `]` switch sub-tab · `a` add blacklist · `A` add whitelist · `b` ban · `B` blacklist selected · `W` whitelist selected · `u` undo · `w` whois · `Enter` IP intel - **SSH** — `k` kick a session · `K` kick + blacklist its IP - **Users** — `N` new user · `P` set login/SSH password (masked) · `G`/`g` grant/revoke sudo · `L`/`U` lock/unlock · `D` delete (keeps `/home`) · `Enter` activity - **Settings** — `g` grant the tool · `x` revoke Full reference: **[docs/guide.md](docs/guide.md)**. ## CLI (scriptable) Every subcommand prints the same data the TUI shows: ```bash avni status # host + counts avni services # all containers avni apps # user-facing apps: state · subdomain · port avni ips [attackers|banned|new|all] # the IP registry avni attackers [N] # top N attackers with country avni ban | unban avni blacklist [add ] avni whitelist [add|del ] avni geo | ssh | users avni adduser [password] # create account (-m home, bash) avni passwd # set a user's Linux login / SSH password avni sudo | unsudo # grant / revoke sudo avni deluser # delete account, KEEP /home avni activity # logins, failed logins, sudo cmds, processes avni access | grant | revoke # tool delegation ``` ## Access control — who can run `avni` The tool is **root-only** by default. To let a teammate use it: 1. They must already be an **admin (sudo)** — `avni sudo ` (Users tab `G`). 2. Grant the tool — `avni grant ` (Settings tab `g`). This adds them to the `avni` group; the sudoers drop-in lets the binary **auto-elevate via sudo** when they run it. Anyone not granted gets a polite "not authorised" message. Root can never be revoked. The single binary at `/usr/local/bin/avni` is a real root-owned file (mode 0755), so a delegated user can exec it but cannot modify it. ## How it works - **Background gather** — a worker thread fans out the independent fetches (docker, auth-log registry, sessions, users, vnstat) across threads, so a full refresh ≈ the slowest single part (~2 s) instead of the sum. The UI never blocks; a loading splash shows on first start. - **Incremental log parsing** — stores a byte offset per log inode and reads only the new tail of `auth.log` on each refresh (not the whole multi-MB file). - **Auto-discovery** — services, domains and apps are parsed from `docker ps` + Traefik `Host()` labels in a single call. Nothing is hard-coded; add a container and it appears. - **Traffic** pins the **default-route interface** (a busy box can have dozens of docker veth/bridge interfaces; `vnstat`'s `interfaces[0]` is often a zero-traffic bridge). ## Data & file locations | Path | Purpose | |------|---------| | `/usr/local/bin/avni` | the binary | | `/usr/local/bin/avni-firewall.sh` | blacklist/whitelist engine (ipset + iptables) | | `/var/lib/avni/ips.json` | persistent IP registry (+ per-log byte offsets) | | `/etc/avni/whitelist.txt`, `/etc/avni/blacklist.save` | firewall state | | `/etc/fail2ban/jail.d/avni-whitelist.conf` | auto-generated fail2ban `ignoreip` | | `/etc/sudoers.d/avni` | NOPASSWD drop-in for the `avni` group | ## Security notes - **No secrets are stored in this repo or the binary.** `avni` reads live system state at runtime; it never embeds passwords, IPs or hostnames. (The examples in the code are [RFC 5737](https://datatracker.ietf.org/doc/html/rfc5737) documentation addresses.) - **Whitelist your own IPs first.** A firewall guard and fail2ban `ignoreip` then make it impossible to accidentally lock yourself out under a brute-force burst; an anti-lockout check also refuses to blacklist your current SSH client IP. - **Docker-published ports bypass host UFW** — the firewall engine adds rules to the `DOCKER-USER` chain too, so a blacklist actually blocks container-exposed ports. - The per-user **activity** view uses only system metadata (logins, sudo audit, `ps`) and **never reads anyone's home directory or shell history** — safe on a multi-tenant box. ## Build from source ```bash cargo build --release # at the repo root → target/release/avni ``` Toolchain: rustup **stable**. Dependencies are just `ratatui`, `crossterm`, `serde`, `serde_json` (see `Cargo.toml`). **Changing the second timezone:** the dual-clock offset is computed in `ist_delta_min()` in `src/main.rs` (currently targets IST, +05:30). Adjust the `330` (minutes) and the `TZ=Asia/Kolkata` strings to your zone. ## Python fallback `avni.py` is an earlier pure-Python (curses) implementation kept as a zero-build fallback. `install.sh` installs it as `/usr/local/bin/avni-py`. The Rust binary is the maintained one. ## License [MIT](LICENSE) © 2026 Jagadeesh (AVNI Cloud).