Files
amnezia-fellow/README.md
T
Ultradesu 9ccab69836
Build and Publish / Build and Publish Docker Image (push) Successful in 3m11s
AWG: Added migration
2026-07-01 13:53:03 +03:00

183 lines
7.0 KiB
Markdown

# amnezia-fellow
Amnezia VPN client manager written in Rust on top of the public [`cot`](https://cot.rs) framework.
The app uses SQLite or PostgreSQL as the source of truth, authenticates users through OIDC/SSO, renders AmneziaWG client peers into a Kubernetes Secret, and avoids updating that Secret when the rendered content is byte-for-byte identical.
## Quick Start
```bash
export AMNEZIA_FELLOW_DATABASE_URL=sqlite://amnezia-fellow.sqlite3?mode=rwc
cargo run -- --listen 127.0.0.1:8000
```
Open `http://localhost:8000/admin/setup` to create the first local admin account.
## Docker
The image is built by GitHub Actions and published to Docker Hub as
`ultradesu/amnezia-fellow`.
Required repository secrets:
- `DOCKERHUB_USERNAME`
- `DOCKERHUB_TOKEN`
Publishing runs for version tags matching `v*.*.*`. A tag like `v0.1.0` pushes:
- `ultradesu/amnezia-fellow:v0.1.0`
- `ultradesu/amnezia-fellow:0.1.0`
- `ultradesu/amnezia-fellow:latest`
Local image build:
```bash
docker build -t amnezia-fellow .
docker run --rm -p 8000:8000 -v "$PWD/data:/data" amnezia-fellow
```
## Roles
There are two roles:
- `admin`: full access, sees all client configs.
- `client`: sees and manages only their own configs.
OIDC provisioning is deny-by-default:
- users in `AMNEZIA_FELLOW_OIDC_ADMIN_GROUPS` become `admin`;
- users in `AMNEZIA_FELLOW_OIDC_CLIENT_GROUPS` become `client`;
- users outside both group lists cannot sign in.
The OIDC groups claim is expected to be `groups`.
## VPN Data Model
The database stores all client data needed to restore configs:
- owner user id
- display name
- assigned IPv4 address
- public key
- private key
- enabled flag
- created/updated timestamps
The Kubernetes Secret is derived from the database. Active clients are rendered into one configured Secret key, `peers.conf` by default.
## Kubernetes Sync
The app is intended to run inside Kubernetes with a ServiceAccount and RBAC that can read and update Secrets and list Pods in the Amnezia namespace.
It reads:
- server public key from `AMNEZIA_FELLOW_K8S_SERVER_SECRET`, key `server-public-key`;
- node endpoints from `AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET`; Secret data keys are used as Amnezia server names in generated `vpn://` links;
- AmneziaWG pods selected by `app=amneziawg` for config rollout status;
- writes rendered client peers to `AMNEZIA_FELLOW_K8S_CLIENTS_SECRET`.
It does not modify the server Secret. Argo/ExternalSecrets can keep managing `amneziawg-server`, while amnezia-fellow owns only the client Secret content.
## UI
The main user interface is `/configs`. It is an Alpine.js reactive page backed by JSON API endpoints:
- clients and admins use the same page;
- the backend filters data by role;
- admins get an extra manual Secret sync action.
- clients and admins see AmneziaWG pod rollout status and uptime.
## Environment Variables
All settings use `AMNEZIA_FELLOW_` prefix. Priority is:
`environment variable > database override > compiled default`
| Variable | Description | Default |
| --- | --- | --- |
| `AMNEZIA_FELLOW_DATABASE_URL` | SQLite or PostgreSQL connection URL. PostgreSQL URLs must start with `postgresql://`. | `sqlite://amnezia-fellow.sqlite3?mode=rwc` |
| `AMNEZIA_FELLOW_MIGRATE_SQLITE` | Optional SQLite path/URL imported into PostgreSQL once on startup | empty |
| `AMNEZIA_FELLOW_LOG_LEVEL` | Tracing filter | `info` |
| `AMNEZIA_FELLOW_AUTH_PASSWORD_ENABLED` | Enable password login | `true` |
| `AMNEZIA_FELLOW_AUTH_SSO_ENABLED` | Enable OIDC login | `false` |
| `AMNEZIA_FELLOW_OIDC_ISSUER` | OIDC issuer URL | empty |
| `AMNEZIA_FELLOW_OIDC_CLIENT_ID` | OIDC client ID | empty |
| `AMNEZIA_FELLOW_OIDC_CLIENT_SECRET` | OIDC client secret | empty |
| `AMNEZIA_FELLOW_OIDC_BUTTON_TEXT` | SSO button label | `Sign in with SSO` |
| `AMNEZIA_FELLOW_OIDC_ADMIN_GROUPS` | Comma-separated admin groups | empty |
| `AMNEZIA_FELLOW_OIDC_CLIENT_GROUPS` | Comma-separated client groups | empty |
| `AMNEZIA_FELLOW_K8S_NAMESPACE` | Amnezia namespace | `amnezia` |
| `AMNEZIA_FELLOW_K8S_CLIENTS_SECRET` | Client peers Secret | `amneziawg-clients` |
| `AMNEZIA_FELLOW_K8S_CLIENTS_SECRET_KEY` | Secret data key for rendered peers | `peers.conf` |
| `AMNEZIA_FELLOW_K8S_SERVER_SECRET` | Server config Secret | `amneziawg-server` |
| `AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET` | Node endpoints Secret | `amneziawg-endpoints` |
| `AMNEZIA_FELLOW_VPN_CLIENT_CIDR` | Client address pool | `10.8.0.0/16` |
| `AMNEZIA_FELLOW_VPN_DNS` | DNS servers in generated configs | `1.1.1.1, 8.8.8.8` |
| `AMNEZIA_FELLOW_VPN_MTU` | MTU in generated configs | `1376` |
| `AMNEZIA_FELLOW_SWAGGER_ENABLED` | Serve Swagger UI at `/swagger/` | `false` |
| `AMNEZIA_FELLOW_TELEGRAM_BOT_ENABLED` | Enable Telegram bot/Web App integration | `false` |
| `AMNEZIA_FELLOW_TELEGRAM_BOT_USERNAME` | Telegram bot username, with or without `@` | empty |
| `AMNEZIA_FELLOW_TELEGRAM_BOT_TOKEN` | Telegram bot token used to verify Web App `initData` | empty |
## PostgreSQL Migration
To move from SQLite to PostgreSQL, start the app with a PostgreSQL database URL
and point `AMNEZIA_FELLOW_MIGRATE_SQLITE` at the existing SQLite file:
```bash
AMNEZIA_FELLOW_DATABASE_URL=postgresql://user:pass@postgres:5432/amnezia_fellow
AMNEZIA_FELLOW_MIGRATE_SQLITE=/data/amnezia-fellow.sqlite3
```
On startup, the app runs its migrations on PostgreSQL, opens the SQLite source
read/write, applies any missing app migrations there, copies config entries,
database sessions, users, OIDC links, Telegram link state, and VPN clients,
then writes an import marker into PostgreSQL. If the marker already exists, the
import is skipped. If PostgreSQL already contains app data but has no marker,
startup fails instead of merging two databases implicitly.
## Telegram Bot
Configure the bot through the admin Settings page or the matching environment variables:
```bash
AMNEZIA_FELLOW_TELEGRAM_BOT_ENABLED=true
AMNEZIA_FELLOW_TELEGRAM_BOT_USERNAME=<BOT_USERNAME>
AMNEZIA_FELLOW_TELEGRAM_BOT_TOKEN=<BOT_TOKEN>
```
In BotFather, create or reuse the same bot and set its Web App URL to
`https://<APP_HOST>/configs`. This app runs the bot in the same process with
`getUpdates` polling, so do not configure a separate webhook for the same bot.
For browser-based linking, users copy a one-time secret code from the portal and
send it to this bot; the app reads the Telegram sender ID from that message.
Users must open the bot and send `/start` once before notifications can be
delivered.
## API
The JSON API is session-authenticated:
- `GET /api/me`
- `GET /api/vpn-clients`
- `GET /api/vpn-status`
- `GET /api/telegram-link/status`
- `POST /api/telegram-link/webapp`
- `POST /api/telegram-link/start`
- `POST /api/telegram-link/decline`
- `POST /api/telegram-link/unlink`
- `POST /api/vpn-clients`
- `POST /api/vpn-clients/{id}/enabled`
- `DELETE /api/vpn-clients/{id}`
- `GET /api/vpn-clients/{id}/config` returns `servers[]` with one raw AWG config and one Amnezia `vpn://` import link per registered endpoint
- `POST /api/vpn-clients/sync`
## Development
```bash
cargo fmt
cargo check
```
The project depends on public crates only; `cot = "0.6.0"` is pulled from crates.io.