first commit

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 20:36:08 +02:00
commit 8612b38bee
10 changed files with 3109 additions and 0 deletions

201
README.md Normal file
View File

@@ -0,0 +1,201 @@
# 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).