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

120 lines
7.5 KiB
Markdown

# 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 <u> [pw]` · `avni passwd <u> <pw>` · `avni sudo <u>` · `avni unsudo <u>` · `avni deluser <u>` · `avni activity <u>`.
- **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)
```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 <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
```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)
```