202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
# 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 <ip> | unban <ip>
|
||
avni blacklist [add <ip|cidr|domain>]
|
||
avni whitelist [add|del <ip>]
|
||
avni geo <ip> | ssh | users
|
||
avni adduser <user> [password] # create account (-m home, bash)
|
||
avni passwd <user> <password> # set a user's Linux login / SSH password
|
||
avni sudo <user> | unsudo <user> # grant / revoke sudo
|
||
avni deluser <user> # delete account, KEEP /home
|
||
avni activity <user> # logins, failed logins, sudo cmds, processes
|
||
avni access | grant <user> | revoke <user> # 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 <user>` (Users tab `G`).
|
||
2. Grant the tool — `avni grant <user>` (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).
|