Перейти к содержанию

Architecture

How MoaV is wired together. For protocol-level details see protocols.md; for CLI behavior see CLI.md; for DNS-tunnel mechanics see DNS.md.

How traffic flows

What a MoaV server looks like from the outside in: clients reach it by whichever protocol still works, everything egresses through the server, and two profiles additionally relay bandwidth for other people's networks.

                                                              ┌───────────────┐  ┌───────────────┐
       ┌───────────────┐                                      │ Psiphon Users │  │   Tor Users   │
       │  Your Clients │                                      │  (worldwide)  │  │  (worldwide)  │
       │   (private)   │                                      └───────┬───────┘  └───────┬───────┘
       └───────┬───────┘                                              │                  │
               │                                                      │                  │
               ├─────────────────┐                                    │                  │
               │                 │ (when IP blocked)                  │                  │
               │          ┌──────┴───────┐                            │                  │
               │          │  CDN (VLESS) │                            │                  │
               │          └──────┬───────┘                            │                  │
┌──────────────╪─────────────────╪────────────────────────────────────╪──────────────────╪─────────┐
│              │                 │          Restricted Internet       │                  │         │
└──────────────╪─────────────────╪────────────────────────────────────╪──────────────────╪─────────┘
╔══════════════╪═════════════════╪════════════════════════════════════╪══════════════════╪═════════╗
║     ┌────────┼─────────────────┼───────┼──────┐                     │                  │         ║
║     ▼        ▼                 ▼       ▼      ▼                     ▼                  ▼         ║
║ ┌─────────┐┌─────────┐┌───────┐┌─────────┐┌────────┐          ┌───────────┐      ┌───────────┐   ║
║ │ Reality ││WireGuard││ Trust ││   DNS   ││Telegram│          │  Conduit  │      │ Snowflake │   ║
║ │ Trojan  ││AmneziaWG││Tunnel ││ tunnels ││MTProxy │          │  (donate  │      │  (donate  │   ║
║ │Hysteria2││wstunnel ││       ││ (4, on  ││        │          │ bandwidth)│      │ bandwidth)│   ║
║ │ AnyTLS  ││         ││       ││ port 53)││        │          │           │      │           │   ║
║ │  SS2022 ││         ││       ││         ││        │          │           │      │           │   ║
║ │ CDN WS  ││         ││       ││         ││        │          │           │      │           │   ║
║ ├─────────┤└────┬────┘└───┬───┘└────┬────┘└───┬────┘          └─────┬─────┘      └─────┬─────┘   ║
║ │ sing-box│     │         │         │         │  ┌────────────────┐ │                  │   MoaV  ║
║ └────┬────┘     │         │         │         │  │ Grafana  :9444 │ │                  │  server ║
║      │          │         │         │         │  │ Admin    :9443 │ │                  │         ║
╚══════╪══════════╪═════════╪═════════╪═════════╪══╪════════════════╪═╪══════════════════╪═════════╝
       │          │         │         │         │                     │                  │
       ▼          ▼         ▼         ▼         ▼                     ▼                  ▼
┌─────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                        Open Internet                                            │
└─────────────────────────────────────────────────────────────────────────────────────────────────┘

Container topology

Every protocol is one or more containers grouped into a docker-compose profile. moav start reads the ENABLE_* flags from .env and brings up only the profiles whose flag is on (see CLI → moav start).

Profile Containers Protocols it serves
proxy sing-box Reality (VLESS), Trojan, AnyTLS (opt-in), Hysteria2, Shadowsocks-2022, CDN VLESS+WS
xhttp xray VLESS + XHTTP + Reality
wireguard wireguard, wstunnel WireGuard direct UDP, plus a wss:// WebSocket fallback for UDP-blocked networks
amneziawg amneziawg AmneziaWG (obfuscated WireGuard)
dnstunnel dns-router, dnstt, slipstream, masterdns, xray All four DNS tunnels, sharing port 53
trusttunnel trusttunnel TrustTunnel (HTTP/2 + QUIC over TLS)
telegram telemt Telegram MTProxy (fake-TLS)
admin admin, docker-proxy The dashboard on :9443
monitoring grafana, prometheus, exporters, cadvisor Metrics and dashboards on :9444
conduit psiphon-conduit Bandwidth donated to Psiphon
snowflake snowflake + exporter Bandwidth donated to Tor
client client Local testing (moav test)

AnyTLS is opt-in because it resists TLS-in-TLS fingerprinting but has narrower client support. all starts every profile whose flag is on.

DNS-router fan-out

All four DNS tunnels share port 53 through a small Go service called dns-router, which inspects each query's subdomain prefix and forwards to the matching backend. Each tunnel container listens on its own internal port; only dns-router binds the public port.

              Public 53/udp
            ┌──────▼──────┐
            │ dns-router  │
            └──────┬──────┘
   subdomain routing:
       t.*  ─────►  dnstt
       s.*  ─────►  slipstream
       m.*  ─────►  masterdns
       x.*  ─────►  xray   (XDNS via FinalMask)

Delegating a tunnel only requires adding its NS record (t. / s. / m. / x.); see DNS → NS Delegations. Disabling a tunnel via ENABLE_*=false removes its container; dns-router simply has no backend to forward to.

Bundle generation flow

User credentials and per-protocol configs originate inside the bootstrap container, then get rendered into per-user bundles on the host. The split exists because container-side bundle generation can't see the host's outputs/ mount layout.

   moav user add alice
   ┌─────────────────────────────────────────────────────────┐
   │ bootstrap container (sing-box-user-add.sh)              │
   │   - generates UUID + per-protocol keys                  │
   │   - writes state/users/alice/credentials.env (volume)   │
   └────────────────────┬────────────────────────────────────┘
                        │  HOST sees state/users/ via volume
   ┌─────────────────────────────────────────────────────────┐
   │ host: generate-single-user.sh                           │
   │   - reads credentials.env + .env                        │
   │   - writes outputs/bundles/alice/{*.txt, *.json, *.png, │
   │     subscription.txt, README.html, ...}                 │
   └─────────────────────────────────────────────────────────┘

Bundles split into three groups:

  • V2Ray-compatible (Reality, Trojan, AnyTLS, Hysteria2, SS-2022, CDN, XHTTP) — share-link .txts, QR .pngs, a single base64 subscription.txt importable by MahsaNG / v2rayNG / Hiddify / Streisand.
  • L3 VPNs (WireGuard, AmneziaWG, TrustTunnel) — .conf / .toml configs + QR.
  • DNS tunnels (dnstt, Slipstream, MasterDNS, XDNS) and donations (GooseRelay) — text instruction files + protocol-specific config blobs (xdns-config.json, gooserelay-AppsScript.gs + gooserelay-client_config.json, etc.).

README.html is a bilingual (EN/FA) collapsible bundle viewer with embedded QR images and one-click subscription import.

Monitoring stack

The monitoring profile is opt-in. When enabled, it adds Prometheus + Grafana plus a set of exporters — one per protocol. Each exporter lives in the same Compose profile as its target service (not in monitoring), so disabling a protocol takes its metrics down too.

   Exporters (each in its target's profile)
     ├── clash-exporter      (sing-box Clash API)
     ├── singbox-exporter    (log parser)
     ├── xray-exporter
     ├── telemt-exporter     (REST /v1/health)
     ├── wireguard-exporter
     ├── amneziawg-exporter
     ├── snowflake-exporter  (snowflake profile)
     ├── node-exporter       (host metrics)
     └── cAdvisor            (container metrics)
                │ scraped by
         ┌──────────────┐
         │  Prometheus  │  + recording rules (e.g. Conduit lifetime)
         └──────┬───────┘
         ┌──────────────┐
         │   Grafana    │  (+ optional grafana-proxy → Cloudflare CDN)
         │  dashboards  │
         └──────────────┘

Pre-built dashboards land in configs/monitoring/grafana/dashboards/. The Conduit lifetime panels depend on a recording rule plus an offset watcher — see Monitoring → Conduit lifetime bandwidth.

Security & isolation model

Every service runs in its own container with least-privilege defaults applied in docker-compose.yml:

  • Capability drop + selective add — services start from cap_drop: ALL and add back only what they need (e.g. NET_ADMIN for WireGuard, NET_BIND_SERVICE for privileged ports). Most also set read_only: true with a small tmpfs for /tmp, no-new-privileges: true, and mem_limit/cpus caps.
  • Non-root — services that must read the root-owned Let's Encrypt cert (sing-box, wstunnel) start their entrypoint as root only long enough to copy the cert into a tmpfs, then drop to an unprivileged user via setpriv before exec'ing the daemon.
  • No direct Docker socket — the admin dashboard reads container status through a read-only docker-socket-proxy scoped to CONTAINERS/NETWORKS only, never mounting /var/run/docker.sock into the app.
  • Admin auth fails closed — the dashboard refuses to serve (HTTP 503) if ADMIN_PASSWORD is empty or one of the known-insecure defaults, and uses constant-time comparison; an optional IP allow-list narrows access further. See OPSEC.
  • Secrets live under the moav_state Docker volume (state/keys/, state/users/<user>/), generated with openssl rand / wg genkey / x25519, mounted read-only into the admin service.

Service lifecycle & health

ENABLE_* flags in .env select which profiles moav start brings up. The certbot service is a one-shot that obtains the TLS cert before the cert-consuming services start; moav cert install schedules ongoing renewal (see CLI → Certificates). restart: unless-stopped recovers services across crashes and reboots. Core engines (sing-box, xray, telemt) declare Compose healthchecks so moav doctor and dependency ordering can distinguish "running" from "actually serving"; extending healthchecks to the remaining long-running services is in progress.

See also