v0.25.0

sleep3r/mtproto.zigv0.25.0May 24, 2026by sleep3r

Upgrading?

AI Summary

Adds proper lifecycle management for the tunnel pool, allowing users to delete tunnels and return the proxy to normal mode when the pool is empty.

Key Highlights

  • TUI and Dashboard can now inspect, replace, and delete tunnel interfaces.
  • Delete tunnel action performs hard cleanup (removes configs, links, and runtime state).
  • Last tunnel removal returns the proxy to 'auto' mode and removes tunnel-specific systemd units.
  • Empty tunnel pools no longer auto-fallback to `awg0`.

New Features

  • Tunnel pool deletion management

Full Release Notes

<details open>
<summary>🇷🇺 Что нового (RU)</summary>

## Что решает этот релиз

`v0.25.0` — feature-релиз для операторов, которые используют AmneziaWG / WireGuard tunnel pool через `mtbuddy setup tunnel` и dashboard.

После нескольких итераций tunnel mode стал надёжнее на старте и failover, но оставался неприятный UX/ops gap: туннель можно было «удалить» руками или заменить частично, а система всё ещё показывала его как настроенный. На практике это проявлялось так:

- в `config.toml` оставался `interfaces = ["awg0"]` или legacy `interface = "awg0"`;
- на диске лежали старые `/etc/amnezia/amneziawg/awg0.conf`, `awg1.conf` или `/etc/wireguard/<iface>.conf`;
- `/run/mtproto-proxy/tunnel-pool.state` продолжал говорить `active=awg0`;
- `mtproto-tunnel-pool.timer` и table 200 могли жить дальше;
- интерактивный `mtbuddy setup tunnel` предлагал создать следующий `awgN`, хотя оператор ожидал пустой пул.

`v0.25.0` добавляет нормальное управление жизненным циклом tunnel pool: теперь в TUI и dashboard можно увидеть реальные tunnel interfaces, понять их состояние и удалить stale tunnel полностью, а не только поправить одну строку конфига.

> [!NOTE]
> Это не автоматическая миграция и не удаляет туннели само по себе. После обновления откройте `sudo mtbuddy --interactive` → `Setup tunnel` или dashboard, проверьте список и удалите ненужные интерфейсы явно.

> [!TIP]
> Если после ручной чистки dashboard всё ещё показывает `awg0` / `awg1`, обновитесь до `v0.25.0` и удалите stale entry через новый delete action. Он чистит config, `.conf`, runtime state и policy routing в одном потоке.

## Что изменено

### TUI теперь управляет tunnel pool, а не только создаёт его ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

- `mtbuddy --interactive` → `Setup tunnel` теперь показывает текущие tunnel interfaces перед выбором действия.
- Список включает не только `[upstream.tunnel].interfaces` из `config.toml`, но и stale `.conf` файлы из:
  - `/etc/amnezia/amneziawg/*.conf`;
  - `/etc/wireguard/*.conf`.
- Для каждого интерфейса показывается runtime status:
  - `active` — interface поднят и handshake был;
  - `up, no handshake` — interface есть, endpoint есть, но handshake ещё не состоялся;
  - `up, no endpoint` / `up, show failed` — interface поднят, но состояние неполное;
  - `down` — interface/config найден, но link сейчас отсутствует.
- В меню появились действия:
  - create new tunnel;
  - replace existing tunnel;
  - delete tunnel.

### Delete tunnel делает hard cleanup ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

При удалении интерфейса `mtbuddy` теперь:

- удаляет interface из `[upstream.tunnel].interfaces`;
- обновляет legacy `[upstream.tunnel].interface` на первый оставшийся member;
- очищает `pinned_interface`, если pinned interface удалён;
- останавливает tunnel через `awg-quick down` / `wg-quick down` where available;
- удаляет link через `ip link delete dev <iface>` как fallback cleanup;
- удаляет known config files:
  - `/etc/amnezia/amneziawg/<iface>.conf`;
  - `/etc/wireguard/<iface>.conf`;
  - legacy `/etc/amnezia/awg0.conf` для `awg0`.

Если в пуле остаются другие туннели, controller запускается ещё раз, чтобы table 200 переехала на следующий доступный interface.

### Last tunnel removal возвращает proxy в обычный режим ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

Если удалён последний tunnel member, `mtbuddy` теперь дополнительно:

- переключает `[upstream].type` обратно в `auto`;
- записывает `interfaces = []`, `interface = ""`, `pinned_interface = ""`;
- отключает `mtproto-tunnel-pool.timer` и останавливает pool service;
- удаляет `/usr/local/bin/setup_tunnel.sh` и `/run/mtproto-proxy/tunnel-pool.state`;
- чистит `fwmark 200 -> table 200` rules и route table 200;
- восстанавливает обычный `mtproto-proxy.service` без tunnel `ExecStartPre`;
- рестартит `mtproto-proxy` уже без tunnel policy routing.

Это закрывает класс ситуаций, где «туннелей уже нет», но systemd / route table / runtime state продолжают вести себя так, будто tunnel mode всё ещё активен.

### Dashboard получил тот же delete flow ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

- Routing card теперь видит stale tunnel config files, даже если они не входят в TOML pool.
- У tunnel row появилась кнопка `Delete` с confirmation modal.
- Новый API endpoint: `POST /api/routing/tunnel-delete` с body `{ "interface": "awg0" }`.
- API делает тот же cleanup contract, что и TUI:
  - config pool update;
  - interface shutdown;
  - config file deletion;
  - last-tunnel cleanup;
  - proxy restart;
  - routing cache invalidation.
- Dashboard различает `pool` и `config` sources, чтобы stale files были видны оператору, а не прятались за fallback `awg0`.

### Empty tunnel pool больше не превращается обратно в `awg0` ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

- Dashboard parser теперь отличает отсутствие `interfaces` от явно заданного `interfaces = []`.
- Если pool явно пустой, UI/API не подставляют fallback `awg0` как «настроенный туннель».
- Это важно после удаления последнего tunnel: пустой pool остаётся пустым, а не выглядит как новый `awg0`.

## Проверено

- `zig build test`
- `python3 -m py_compile src/ctl/dashboard_assets/server.py`
- `node --check src/ctl/dashboard_assets/static/app.js`
- GitHub Actions:
  - `Test & Build`
  - `E2E Integration`
  - `Bench (No Soak)`
  - installer e2e: `debian:12`
  - installer e2e: `ubuntu:24.04`

</details>

---

<details>
<summary>🇬🇧 Release notes (EN)</summary>

## What this release addresses

`v0.25.0` is a feature release for operators using the AmneziaWG / WireGuard tunnel pool through `mtbuddy setup tunnel` and the dashboard.

Tunnel mode had become more reliable around startup and failover, but it still had a painful UX/ops gap: an operator could partially remove or replace a tunnel, while the system continued to show it as configured. In practice this looked like:

- `config.toml` still containing `interfaces = ["awg0"]` or legacy `interface = "awg0"`;
- stale `/etc/amnezia/amneziawg/awg0.conf`, `awg1.conf`, or `/etc/wireguard/<iface>.conf` files remaining on disk;
- `/run/mtproto-proxy/tunnel-pool.state` still reporting `active=awg0`;
- `mtproto-tunnel-pool.timer` and table 200 still being present;
- interactive `mtbuddy setup tunnel` offering the next `awgN` even when the operator expected an empty pool.

`v0.25.0` adds proper tunnel pool lifecycle management: both the TUI and dashboard can now show real tunnel interfaces, explain their state, and delete stale tunnels fully instead of only editing one config key.

> [!NOTE]
> This is not an automatic migration and does not delete tunnels by itself. After updating, open `sudo mtbuddy --interactive` → `Setup tunnel` or the dashboard, review the list, and explicitly delete any stale interfaces.

> [!TIP]
> If a server still shows `awg0` / `awg1` after manual cleanup, update to `v0.25.0` and remove the stale entry through the new delete action. It cleans config, `.conf` files, runtime state, and policy routing in one flow.

## What changed

### TUI now manages the tunnel pool instead of only creating it ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

- `mtbuddy --interactive` → `Setup tunnel` now shows current tunnel interfaces before asking for an action.
- The list includes both `[upstream.tunnel].interfaces` from `config.toml` and stale `.conf` files from:
  - `/etc/amnezia/amneziawg/*.conf`;
  - `/etc/wireguard/*.conf`.
- Each interface gets a runtime status:
  - `active` — the interface is up and has completed a handshake;
  - `up, no handshake` — the interface and endpoint exist, but no handshake has happened yet;
  - `up, no endpoint` / `up, show failed` — the link is present, but state is incomplete;
  - `down` — a config or pool entry exists, but the link is currently absent.
- The menu now supports:
  - create new tunnel;
  - replace existing tunnel;
  - delete tunnel.

### Delete tunnel performs hard cleanup ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

When deleting an interface, `mtbuddy` now:

- removes the interface from `[upstream.tunnel].interfaces`;
- updates legacy `[upstream.tunnel].interface` to the first remaining member;
- clears `pinned_interface` if the pinned interface was removed;
- brings the tunnel down through `awg-quick down` / `wg-quick down` when available;
- deletes the link through `ip link delete dev <iface>` as fallback cleanup;
- removes known config files:
  - `/etc/amnezia/amneziawg/<iface>.conf`;
  - `/etc/wireguard/<iface>.conf`;
  - legacy `/etc/amnezia/awg0.conf` for `awg0`.

If other pool members remain, the pool controller is run once so table 200 moves to the next available interface.

### Last tunnel removal returns the proxy to normal mode ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

When the removed tunnel was the last pool member, `mtbuddy` also:

- switches `[upstream].type` back to `auto`;
- writes `interfaces = []`, `interface = ""`, and `pinned_interface = ""`;
- disables `mtproto-tunnel-pool.timer` and stops the pool service;
- removes `/usr/local/bin/setup_tunnel.sh` and `/run/mtproto-proxy/tunnel-pool.state`;
- clears `fwmark 200 -> table 200` rules and route table 200;
- restores the regular `mtproto-proxy.service` without the tunnel `ExecStartPre`;
- restarts `mtproto-proxy` without tunnel policy routing.

This closes the class of issues where “all tunnels are gone” but systemd, route table, or runtime state still behaves as if tunnel mode is active.

### Dashboard now has the same delete flow ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

- The Routing card now detects stale tunnel config files even when they are not in the TOML pool.
- Tunnel rows have a `Delete` button with a confirmation modal.
- New API endpoint: `POST /api/routing/tunnel-delete` with body `{ "interface": "awg0" }`.
- The API follows the same cleanup contract as the TUI:
  - config pool update;
  - interface shutdown;
  - config file deletion;
  - last-tunnel cleanup;
  - proxy restart;
  - routing cache invalidation.
- The dashboard distinguishes `pool` and `config` sources so stale files are visible to operators instead of being hidden behind the `awg0` fallback.

### Empty tunnel pools no longer become `awg0` again ([#272](https://github.com/sleep3r/mtproto.zig/pull/272))

- The dashboard parser now distinguishes a missing `interfaces` key from an explicit `interfaces = []`.
- When the pool is explicitly empty, the UI/API do not inject fallback `awg0` as a configured tunnel.
- This matters after deleting the last tunnel: an empty pool stays empty instead of looking like a fresh `awg0` setup.

## Verified

- `zig build test`
- `python3 -m py_compile src/ctl/dashboard_assets/server.py`
- `node --check src/ctl/dashboard_assets/static/app.js`
- GitHub Actions:
  - `Test & Build`
  - `E2E Integration`
  - `Bench (No Soak)`
  - installer e2e: `debian:12`
  - installer e2e: `ubuntu:24.04`

</details>

## Changelog

* feat: manage tunnel pool deletion ([#272](https://github.com/sleep3r/mtproto.zig/pull/272)) ([1cadf87](https://github.com/sleep3r/mtproto.zig/commit/1cadf8723c8316b24eef9622b252ec0554659926))
* chore(main): release 0.25.0 ([#273](https://github.com/sleep3r/mtproto.zig/pull/273)) ([f37e418](https://github.com/sleep3r/mtproto.zig/commit/f37e418))

Related Repositories