Ultradesu 3b4899f785
Build and Publish / Build and Publish Docker Image (push) Successful in 3m12s
Fixed migrations
2026-07-06 10:46:50 +03:00
2026-06-29 15:50:25 +03:00
2026-07-06 10:46:50 +03:00
2026-07-01 13:53:03 +03:00
2026-06-29 15:50:25 +03:00
2026-06-29 15:50:25 +03:00
2026-06-29 15:50:25 +03:00
2026-07-06 10:46:50 +03:00
2026-07-06 10:46:50 +03:00
2026-06-29 16:50:03 +03:00
2026-07-01 13:53:03 +03:00

amnezia-fellow

Amnezia VPN client manager written in Rust on top of the public cot 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

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:

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:

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:

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

cargo fmt
cargo check

The project depends on public crates only; cot = "0.6.0" is pulled from crates.io.

S
Description
No description provided
Readme
846 KiB
Languages
Rust 59.8%
HTML 40%
Dockerfile 0.2%