Skip to content

Releases: MotherofallVPNs/MoaV

v1.9.0

Choose a tag to compare

@github-actions github-actions released this 14 Jul 01:16
2deec11

Changed

  • Repository moved to the MotherofallVPNs orgshayanb/MoaV is now MotherofallVPNs/moav, joining moav-client and moav-site under one org. GitHub 301-redirects the old paths (clones, issues, PRs, releases) so existing installs and links keep working. Swept the hardcoded shayanb/MoaV references across the repo (install one-liner REPO_URL, README badges, docs, issue templates, CHANGELOG links, the dns-router Go module path) to the new home; the personal GitHub Sponsors link is intentionally unchanged.

Added

  • moav uninstall non-interactive flags--yes/-y skips the confirmation prompts, and --remove-images also deletes the Docker images (otherwise --yes keeps the image cache). Makes uninstall scriptable for automation/CI; interactive behavior is unchanged when no flag is given.
  • moav bootstrap --yes — skips the confirmation prompts, including the "already bootstrapped, re-run?" one. Re-bootstrap is already idempotent (keeps keys/users, regenerates configs), but without a TTY the prompt defaulted to no and silently cancelled — so bootstrap was unscriptable. Makes it usable from automation/CI; interactive behavior is unchanged.
  • moav user base64 <user> — emits base64 of a text-only bundle (the config files + subscription.txt; excludes the QR PNGs and README.html, which are the bulk). Paste it into the moav-client e2e's bundle_b64 dispatch input to validate a deployed server against the current client, or use it for a quick client import (moav user base64 alice | pbcopy).
  • Single-source protocol roster (data/protocols.json + scripts/gen-protocol-docs.py) — the protocol list had drifted across 6+ surfaces (README, docs/protocols.md, site/index.html meta/JSON-LD, site/llms.txt), most visibly AnyTLS and Shadowsocks-2022 missing from some. There is now one canonical roster: the docs/protocols.md overview table is generated from it (marker-delimited, idempotent), and site/index.html + README.md + site/llms.txt are drift-checked against it — gen-protocol-docs.py --check fails if any protocol is missing from a surface (run in CI). Fixed the current drift: added the missing AnyTLS to the site's meta/OG/JSON-LD/keywords strings and named wstunnel in llms.txt.
  • CI pipeline (.github/workflows/ci.yml) — runs on every PR to dev/main: shellcheck --severity=error across all 54 shell scripts, bash -n parse checks, go vet + go test -race for dns-router, docker compose config validation, the share-link golden test, and the protocol-roster drift gate. First automated regression gate for the repo (previously only release/site-deploy workflows existed). Fixed the one error-severity shellcheck finding it surfaced — an unquoted array expansion (SC2068) in moav.sh's build path that could re-split service names.
  • Manual/per-release end-to-end test (.github/workflows/e2e.yml) — stands up the full Compose stack on a self-hosted runner (a test VPS with a real test domain), provisions a user, and runs moav test (client-test.sh) against the live server across every enabled protocol, plus the CLI smoke test. Triggered manually (workflow_dispatch) and on published releases (nightly is available but disabled by default). Inputs: verbose, full (see below), and domainless — a domain-less run that skips Let's Encrypt entirely, so it validates the IP-only protocols and the client-image build without touching the LE rate limit (and needs no test domain). Standard domain runs reuse the moav_certs volume across runs (issue once, reuse — LE allows only 5 certs/week per domain); domainless/full runs start from a clean slate. Not on the per-PR path because it builds ~25 images and needs TLS certs. Setup + manual-run instructions in docs/devdocs/E2E-TESTING.md.

Testing

  • moav test now covers Shadowsocks-2022 and XDNS — the two protocols that are on by default but previously had no connectivity test. test_ss decodes the bundle's ss:// SIP002 URI (base64url method:server_psk:user_psk) and drives a sing-box shadowsocks outbound over a local SOCKS port; test_xdns runs the bundle's own Xray client config (xdns-direct-config.json, falling back to the via-DNS config) and checks its SOCKS inbound, with a longer timeout and warn-on-failure since DNS tunnels are slow. Both appear in the human + JSON result summaries.
  • moav test now covers the remaining protocolstest_cdn drives a real sing-box VLESS+WebSocket outbound through the Cloudflare-fronted endpoint (warn-on-fail, since it depends on the operator's Cloudflare proxied-record + Origin-Rule + SSL setup); test_wstunnel validates the WireGuard-over-wstunnel config and verifies the tunnel port actually speaks wss:// TLS (the 1.9.0 hardening); test_telemt was upgraded from a bare nc -z to a Fake-TLS handshake probe (it decodes the fronted domain from the tg:// secret and confirms telemt answers a TLS hello, not just an open socket). test_masterdns and test_gooserelay skip honestly with what to check manually — MasterDNS has no standalone client in the harness (MahsaNG v16), and GooseRelay needs a user-deployed Google Apps Script forwarder to reach its exit. Also fixed a latent gap: xhttp was being tested but omitted from the result summaries — it (and cdn/wstunnel/masterdns/gooserelay) now appear in both the human and JSON output. This completes the protocol-test matrix; the e2e runner validates it end-to-end.
  • moav CLI smoke test (tests/cli-smoke-test.sh) — beyond the protocol tunnels, this exercises the tool itself against the live stack: help/version/status/users/profiles/cert status/logs/check/doctor/net status/conduit-offsets status, a user addrevoke lifecycle plus user add --batch, admin password (non-interactive), restart, an exportimport round-trip, update --help, donate status (display only — never actually donates), install.sh --help, and a non-interactive TUI-menu launch — each with a hang-guard timeout, asserting read/report commands exit clean and diagnostics don't crash. Wired into the e2e workflow so a broken CLI command fails the run.
  • e2e FULL mode — a full workflow input that, on top of the standard domain-mode run, also runs the domainless-mode lifecycle (reconfigure without a domain, bootstrap → start → provision → test the domainless protocols), moav build --local (monitoring images from source), and exercises the image-removal path of moav uninstall. The standard (default) run stays fast; FULL is opt-in for release/thorough validation.
  • moav test now validates the generated bundle README.html — a test_readme_bundle check asserts the client guide is fully rendered: no template placeholder ({{TOKEN}}) survives substitution, and no protocol whose config file is present in the bundle (i.e. enabled) still shows its "…not enabled/available" sentinel. This catches template↔generator drift — a new {{CONFIG_X}} wired into one generation path but not the other (exactly the Shadowsocks/XHTTP gap fixed above) — that connectivity tests can't see. Appears in the human + JSON summaries under readme and fails the e2e run.

Internal

  • Consolidated the test scripts into tests/client-test.sh (protocol harness), cli-smoke-test.sh (CLI smoke), and the share-link golden test (tests/singbox-links-test.sh) now live in one tests/ directory with a README describing the three layers, instead of scattered across scripts/ + scripts/lib/. Updated the references (Dockerfile.client COPY, ci.yml/e2e.yml, CONTRIBUTING, docs). Go unit tests stay next to their package (dns-router/main_test.go) per Go convention. No behavior change.
  • Deduplicated sing-box share-link construction into lib/sing-box.sh — the Reality/Trojan/AnyTLS/Hysteria2/CDN/Shadowsocks-2022 link templates (plus their IPv6 variants, and the SS SIP002 base64url(method:server_psk:user_psk) userinfo encoding) were built identically in two places, scripts/singbox-user-add.sh (host add-user path) and scripts/generate-user.sh (bundle generator), and had to be kept in sync by hand. They're now single-source builder functions (singbox_reality_link, singbox_trojan_link, singbox_anytls_link, singbox_hysteria2_link, singbox_cdn_link, singbox_ss_userinfo, singbox_ss_link) that both scripts call. A pure-function golden test (tests/singbox-links-test.sh, 12 cases) locks the exact output, so the extraction is provably byte-identical — no change to any generated link. First step of the v2 provisioning-code consolidation (the server-config jq mutations and the generate-single-user.sh merge follow next).

Documentation

  • CONTRIBUTING.md — top-level contributor entry point: dev setup, the PRs-target-dev rule, comment/CHANGELOG/test-domain conventions, the local CI commands (shellcheck, go test, the share-link golden test, the roster drift gate), how the single-source protocol roster works, and how to add a protocol. Cross-links the devdocs checklists.
  • v2 docs & compatibility sweep — reconciled the docs with the shipped 1.8.5 feature set: added a Certificates section to docs/CLI.md (moav cert status/renew/install/uninstall, CERT_AUTORENEW); added the missing Shadowsocks-2022 section + overview-table row and an XDNS_METHOD (txt/aaaa) row to docs/protocols.md, and updated the wstunnel entry to describe wss:// + the path secret; added AnyTLS to docs/architecture.md (plus a new Security & isolation / Service lifecycle section) and to the human-visible/structured surfaces of site/index.html, and fixed a stale "version 1.8.2" string there; documented the admin empty-password fail-closed (503) behavior in docs/OPSEC.md; refreshed site/llms.txt

Changed

  • **wstunnel now wraps WireGua...
Read more

v1.8.6

Choose a tag to compare

@github-actions github-actions released this 13 Jul 21:13
6189d57

Hotfix: Reality/XHTTP outage after update + bootstrap

A silent, total outage of Reality and XHTTP could occur after moav update followed by moav bootstrapsing-box logs REALITY: processed invalid connection, clients see received real certificate — even though keys, bundles, services, and the TLS cert are all intact and unchanged.

Root cause: the sing-box/xray config templates interpolate ${REALITY_SHORT_ID}, which docker-compose passes in from .env. But the short_id's source of truth is the state (state/keys/reality.env), not .env. .env.example ships REALITY_SHORT_ID= empty and an update can reset it, so at render time the empty .env value shadowed the real state value → "short_id": [""] → the server rejects every real client. (The private key was never affected — it isn't passed through .env.)

Fix: bootstrap now re-sources state/keys/reality.env immediately before each config render, so the authoritative state value always wins.

If you're already hit (recover without updating)

# real short_id is in state/keys/reality.env
sed -i 's|^REALITY_SHORT_ID=.*|REALITY_SHORT_ID=<the-real-sid>|' .env
rm -f configs/sing-box/config.json configs/xray/config.json
moav bootstrap --yes && moav restart sing-box xray

Existing user bundles keep working — nothing to re-issue.

Upgrading

After updating, also run moav cert install if moav doctor reports "Cert auto-renewal is NOT scheduled" — the auto-renewal shipped in 1.8.5 but is installed once (systemd timer / cron.d). Without it the Let's Encrypt cert silently expires at ~90 days and the TLS protocols go down.

Full changelog: see CHANGELOG.md[1.8.6].


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.8.5

Choose a tag to compare

@github-actions github-actions released this 11 Jul 16:43
654b934

v1.8.5

🔐 TLS certificates now auto-renew

Fixes the 90-day certificate expiry that silently broke Trojan / Hysteria2 / CDN VLESS / admin TLS on long-running deployments. Renewal was never scheduled, and the renew script never actually invoked certbot (the compose entrypoint override swallowed it). Now:

  • New moav cert {status,renew,install,uninstall} command
  • Daily renewal via systemd timer (moav-cert-renew.timer; cron.d fallback on non-systemd hosts), auto-installed on moav start when DOMAIN is set — opt out with CERT_AUTORENEW=false
  • Cert-consuming services restart automatically only when the certificate actually changed

Existing deployments: moav update then moav cert renew — this recovers an already-expired cert in place; the timer installs itself on the next moav start.

🕵️ AnyTLS protocol (opt-in)

sing-box-native protocol that defeats TLS-in-TLS fingerprinting for higher stealth than Trojan. Reuses the Trojan TLS cert/domain on TCP 8445, full user-bundle/revoke/client-test wiring, EN/FA docs. Off by default (ENABLE_ANYTLS=false) — client support is narrower (Hiddify, sing-box SFA/SFI, NekoBox, mihomo, Shadowrocket 2.2.65+). Thanks @ibeezhan (#132).

📦 Component updates (each reviewed against our exact usage)

  • wstunnel 10.5.5 → 10.6.1 — fixes a process-abort panic (erebe/wstunnel#523) that a single port-scan probe against the public ws:// port could trigger, dropping all active WireGuard-over-wstunnel sessions
  • telemt 3.4.11 → 3.4.23 — Fake-TLS realism hardening (ServerHello fidelity, ALPN no longer a plaintext marker, synthetic key shares) applies automatically to our tls_emulation mode
  • Xray-core v26.5.9 → v26.6.27 — XHTTP inbounds now ignore spoofed X-Forwarded-For (XTLS/Xray-core#6309); finalmask UDP fix improves XDNS reliability (#6331)

⚙️ Network tuning followups

  • net.ipv4.tcp_max_syn_backlog = 8192 added to the sysctl bundle (SYN-flood/reconnect-burst resilience)
  • moav net apply prints a verification summary after applying
  • moav doctor net gains packet-drop / PMTU / CGNAT / MTU checks

Also

  • Hysteria2 inbound sets ignore_client_bandwidth: true — keeps clients on BBR instead of Brutal (#131, thanks @ibeezhan)
  • Monitoring opt-in prompt defaults to No on <2 GB RAM hosts
  • Admin dashboard fails closed on empty/default ADMIN_PASSWORD (#126, thanks @raminrez)

Full changelog: https://github.com/shayanb/MoaV/blob/main/CHANGELOG.md#185---2026-07-11
Compare: v1.8.4...v1.8.5


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.8.4

Choose a tag to compare

@shayanb shayanb released this 08 Jun 22:57
d73c068

MoaV v1.8.4 — Release Notes

Released 2026-06-08 · Changelog


A performance + observability release. BBR + kernel network tuning ships behind an install-time prompt and consistently delivers 2–5× TCP throughput on the long-RTT, lossy paths proxy traffic actually traverses. Two operator-reported bugs are fixed end-to-end: VLESS Reality :443 no longer dies silently when REALITY_TARGET is set to a hostname that doesn't resolve, and the admin dashboard finally shows correct status for every container (xray, snowflake, and three previously-invisible DNS-tunnel services). Non-interactive installs (setsid / </dev/null) work cleanly on stock Ubuntu 24.04.

What's new

BBR + kernel network tuning, opt-in once

Real-world testing (~400 ms RTT, burst loss) showed BBR ~3× single-flow TCP vs CUBIC, and post-loss recovery jumped from "stuck at 2 Mbps for seconds" to "back to 43 Mbps in the next second". UDP buffer bumps also help Hysteria2, WireGuard, and quic-go (which needs ≥7.5 MiB per UDP socket to avoid drops).

Single dedicated file /etc/sysctl.d/99-moav-net.conf so revert is clean. Buffer max auto-capped at 16 MiB on hosts with <2 GB RAM, 32 MiB otherwise. Skips silently on kernels <4.9 or OpenVZ guests. Deliberately excludes tcp_fastopen — actively hostile in heavily-censored networks (China Mobile and ~5% of paths drop SYN+data, so TFO server-side adds latency).

moav net apply     # opt in to the bundle
moav net status    # show current vs recommended
moav net revert    # remove + reload sysctl
moav doctor net    # part of the regular doctor sweep

Full per-knob rationale in docs/OPSEC.md → "Network tuning".

Reality :443 no longer RSTs when REALITY_TARGET is unreachable

Issue #115 reported the inbound returning RST/0 bytes for every TLS hello. Diagnosis: the operator had set REALITY_TARGET to a hostname that returns NXDOMAIN. Reality's fallback dial failed → connection dropped → clients saw RST and the server's :443 looked visibly dead to passive scanners. The same trap exists for XHTTP_REALITY_TARGET on Xray's :2096.

Wiring is now:

  • Bootstrap-time validation — both targets must resolve via getent hosts before the bootstrap container starts; NXDOMAIN re-prompts (up to 3 attempts) with a vetted list of alternatives, rewrites .env in place.
  • moav doctor reality — exec's into sing-box and xray containers, validates DNS resolution + TCP reachability from inside each. Surfaces the problem at moav doctor time instead of after issuing broken bundles.
  • docs/OPSEC.md "Reality fallback target" — vetted global lists (www.cloudflare.com, www.apple.com, cdn.kernel.org) and Iran-friendly SNI cover (www.aparat.com, digikala.com, taghche.com).

Also removed tcp_fast_open: true from the sing-box Reality and Trojan inbounds for the same TFO-hostile-to-censored-networks reasoning as the BBR bundle.

Admin dashboard container status — real fix this time

Issue #117 reported xray showing as "unknown" even with clients connecting fine. The first attempt added a Docker healthcheck — that helps docker ps show real Health.Status, but the admin UI never read it. Actual root cause: check_service_status decided state by DNS-resolving the container hostname on moav_net, and xray was simply missing from the lookup dict.

The fix refactored the entire status path to use the docker-socket-proxy's /containers/json endpoint uniformly — one round-trip per refresh lists every running container, no per-service special cases. Same change surfaced three previously-invisible cards:

  • masterdns — 4th DNS tunnel, default-on since 1.8.0
  • dns-router — without it, all four DNS tunnels silently break (they all route through it on :53)
  • gooserelay — opt-in SOCKS5-over-Apps-Script exit

Dashboard goes from 13 cards to 16, and snowflake (host networking) now reports real running state instead of always "unknown".

telemt log spam silenced

Same issue #117. telemt v3.4.x writes a runtime observability snapshot (beobachten.txt) relative to its working directory. The container is read_only: true with tmpfs at /app/tlsfront, /app/cache, /tmp — but the entrypoint was cd /app, the read-only image root. Every flush hit ENOSYS, logging once per ~15 s. Non-fatal but loud.

Fix: entrypoint now cd /app/cache (existing tmpfs), tls_front_dir flipped to absolute /app/tlsfront so the cwd change doesn't break TLS fronting.

Non-interactive install robustness (thanks @ibeezhan, #120)

Six fixes caught while deploying via setsid bash -c "... install.sh ... </dev/null" on fresh Ubuntu 24.04:

  • BBR detection now modprobe tcp_bbr first and persists via /etc/modules-load.d/moav-bbr.conf — most distro kernels ship BBR as a loadable module, absent from /proc/sys/net/ipv4/tcp_available_congestion_control until something loads it. Without this, the network-tuning offer was silently skipping on every fresh Ubuntu 24.04 install.
  • install.sh under setsid no longer leaks bash: /dev/tty: No such device or address — the previous [[ -e /dev/tty ]] guard was true under setsid even though opening the device returns ENXIO. Detection now actually opens the device.
  • moav no longer aborts at clear when TERM is unsetclear now only runs when stdout is a TTY.
  • moav no longer aborts in ensure_admin_password on closed stdinread -r now tolerates EOF and the auto-generate fallback fires.
  • moav install no longer exits 1 sourcing completions/moav.bash under nounset${ZSH_VERSION:-} instead of $ZSH_VERSION.
  • xray healthcheck uses xray run -test (the correct subcommand) instead of xray test (which doesn't exist). Caught before any operator hit the broken check.

Upgrade

moav update
moav net apply                                          # opt in to BBR/sysctl bundle
moav restart xray                                       # picks up new healthcheck
moav build telemt --no-cache && moav restart telemt    # picks up entrypoint fix
moav restart admin                                      # picks up the new status detection + 3 new cards

No bootstrap, no user regeneration, no DB migration required. Existing installs with a NXDOMAIN REALITY_TARGET will see the issue surfaced on next moav doctor; fix .env and run moav regenerate-users to re-issue bundles with the new SNI.

Verification

  • 15 files changed vs v1.8.3 (+689 / −17)
  • bash -n clean on every touched shell script
  • docker compose config parses YAML clean
  • Reality validator + moav doctor reality tested against the actual NXDOMAIN hostnames from issue #115
  • BBR helpers tested standalone on a non-BBR host (correctly skip)
  • check_service_status regression-tested across all 16 dashboard cards plus host-networked snowflake + a non-mapped name
  • Live-tested by @ibeezhan on fresh Ubuntu 24.04 via setsid bash -c "... install.sh ... </dev/null" — install completes, bootstrap runs, all 4 domainless protocols (Reality, XHTTP, SS-2022, WireGuard) proxy end-to-end with the server's IP as egress

Thanks

@ibeezhan — issue #115 (Reality :443 RST root-cause), all six fixes in PR #120 including catching two issues with the initial v1.8.4 work (the broken xray test subcommand and the BBR module-load gap), and live-verifying the release on Ubuntu 24.04 before merge.

@sacredx72 — issue #117 (xray status "unknown" + telemt log spam). Both were caught running real users on real servers; the diagnosis went two layers deeper than the first attempt — the admin UI doesn't read Docker healthchecks at all, so the real fix had to be in admin/main.py.


🤖 Generated with Claude Code

New Contributors

Full Changelog: v1.8.3...v1.8.4


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.8.3

Choose a tag to compare

@shayanb shayanb released this 03 Jun 13:49
284a78e

This is a quality-of-life and reliability release. The TUI surfaces more of the day-to-day commands, the operator docs catch up to where the code actually is, and a class of silent user-management bugs that affected the dashboard create / CLI revoke flow is cleaned up. No re-bootstrap or user regeneration required.

What's new

A more useful interactive menu

The moav TUI menu picks up four high-value commands that used to be CLI-only:

7) Donate configs (MahsaNet, Psiphon, Snowflake)
8) Doctor — diagnose problems
9) Admin password reset
10) Update MoaV

Slots 1–6 are unchanged for muscle memory. The "Add User" preview now lists every service the bundle actually provisions (Reality / Trojan / Hysteria2 / Shadowsocks-2022 / XHTTP / CDN / WireGuard / AmneziaWG / TrustTunnel / DNS tunnels / Telegram MTProxy / GooseRelay) instead of the stale 1.7-era "sing-box + WireGuard" line.

moav users is now complete

The listing was only showing sing-box + WireGuard + bundles, hiding four services that user-add provisions: AmneziaWG, Xray (XHTTP + XDNS), TrustTunnel, and Telegram MTProxy. Each is now its own section with totals. If you've been running MoaV for a while and didn't know who had access to your Telegram proxy, this is the section to look at.

WebSocket is the new default CDN transport

CDN_TRANSPORT flipped from httpupgrade to ws in .env.example and the three shell fallback paths. WebSocket is more battle-tested in heavily-censored networks, universally supported across V2Ray-family clients, and isn't subject to the deprecation warnings newer Xray clients emit for httpupgrade. Existing installs keep their current setting — only fresh installs and re-bootstraps pick up the new default. If you specifically need httpupgrade (some CloudFront edge cases), just set CDN_TRANSPORT=httpupgrade in .env.

Cloudflare CDN setup is now hard to miss

The bootstrap UX surfaces the two required Cloudflare settings up-front (Origin Rule for port 2082 and SSL/TLS mode = Flexible) instead of only the Origin Rule. The docs/DNS.md Cloudflare section is restructured the same way, with an inline response-code decoder so a 521 or 525 doesn't leave you guessing.

Documentation catches up

The docs had drifted across 1.7.x and 1.8.x. This release brings them back to where the code is:

  • New docs/architecture.md — container topology, dns-router fan-out, bundle generation flow, monitoring stack.
  • New site/llms.txtllmstxt.org-spec discovery file at the site root for AI assistants.
  • docs/CLI.md — rewrote the profiles table (was missing 6 of 13 actual Compose profiles); new "Disabled profiles" subsection under moav start; new moav conduit-offsets section (1.7.9+ feature was 100% undocumented).
  • docs/DNS.md — four NS-delegation Steps consolidated into one table; Cloudflare records table gains the missing m and x NS rows.
  • docs/MONITORING.md — new "Conduit lifetime bandwidth" section explaining the offset watcher.
  • docs/philosophy.md — rewritten to integrate the "this is ours to build" narrative ("What Infrastructure Actually Means", "The Internet Is Closing", "A Global Pattern", "The Arms Race Gets Creative", "You Are Donating Bandwidth", "The Window Is Open") with the existing rights-based sections. Cite-ready sources appendix.
  • Client guide HTML — Telegram MTProxy section has a clickable "Open in Telegram" button on top of the copy-pastable link; Tor + Psiphon + WireGuard-over-WebSocket sections collapse like the V2Ray-compatible cards. EN + FA mirrors.

Critical fixes

Dashboard create-user silently broke after CLI revoke

The most operationally painful bug we fixed. Five user-management scripts used mv -f tmp orig to write back rewritten config files. mv -f swaps inodes — so when you ran sudo moav user revoke X, the resulting wg0.conf / awg0.conf / xray/config.json / sing-box/config.json / trusttunnel/credentials.toml ended up -rw------- root:root. The admin container (running as moav:moav) then couldn't write the same configs on the next dashboard user-add attempt → permission denied.

Replaced every mv -f tmp orig with cat tmp > orig; rm -f tmp so the original file's inode (and thus its mode and owner) survives. Both user-add.sh and user-revoke.sh also run a chmod a+rw sweep over the six config files at the top of every invocation — so historically broken installs self-heal on the next CLI op without needing to restart the admin container.

moav user revoke reported "User not found in sing-box" for users that did exist

The check used grep -q "\"name\":\"$USERNAME\"" which requires no whitespace between : and ". But jq -S (which renders the config) always emits "name": "X" with a space. Grep returned false-negative every time, and the revoke skipped the sing-box cleanup chain entirely — bundles got deleted but users stayed valid in every service config. Three checks replaced with the same jq query user-list.sh uses. Whitespace-insensitive, source-of-truth consistent.

Xray (XHTTP + XDNS) revoke was completely missing

singbox-user-revoke.sh cleaned sing-box, TrustTunnel, and telemt — but never touched configs/xray/config.json. Revoked users kept their XHTTP and XDNS access via their saved credentials. Now correctly jq-deletes from both .settings.clients[] and .settings.users[] (Xray's v26.5.9 schema rename made them aliases) and restarts xray.

Xray users / clients alias split-brain

Related: moav users could show "(no users)" for Xray on a working install, because bootstrap-added users live in .settings.users (the template path) and the listing only checked .settings.clients. Fixed in three places — listing reads both, exists-check scans both, new users go to the canonical .settings.users.

Smaller fixes

  • Admin URL in main menu was rendering as https://DOMAIN:9443 # Admin dashboard because the URL helper stripped quotes but not trailing .env comments. All four URL helpers now use the comment-aware reader.
  • user-add.sh GOPROXY auto-heal failed silently on the admin container's read-only .env mount. Now heals into a tempfile, sources the tempfile, only writes back to .env if it's writable.
  • Bootstrap domain prompt re-asks on invalid input (up to 3 attempts) instead of saving garbage with a warning.
  • Bootstrap container build message says "may take a few minutes" instead of "may take a minute" (the first-time build is genuinely 2–5 min).

Upgrade

moav update
moav start

That's it. No bootstrap, no user-regeneration, no DB migration. Behavior changes that affect existing installs self-heal:

  • Stale config-file perms get repaired on the next CLI op (no admin container restart needed).
  • Legacy users in Xray's .settings.clients array stay reachable (alias of .settings.users).
  • The CDN transport default change only affects fresh installs and re-bootstraps — your existing CDN_TRANSPORT value (if any) is preserved.

If you want to pick up the new CDN transport on an existing install, set CDN_TRANSPORT=ws in .env and either re-bootstrap or run moav regenerate-users to refresh client bundles.

What's coming in 1.8.4

A focused PR adding BBR + sysctl tuning to the installer, behind a prompt with default = yes. Real-world testing showed BBR roughly 3× TCP throughput on a 400 ms RTT path with burst loss (5.45 → 14.8 Mbps in our test, and post-loss recovery from 2 Mbps to 43 Mbps in the next second). The bundle also bumps UDP buffers, which directly helps Hysteria2 and WireGuard even though they don't use BBR. New moav doctor net subcommand for visibility and --apply / --revert. Deliberately excludes tcp_fastopen because it's actively harmful in heavily-censored networks (China Mobile firewalls drop TFO).

Verification

  • 10 touched shell scripts pass bash -n
  • mkdocs build clean (only pre-existing CHANGELOG link warnings unrelated to this release)
  • 26 files changed: 811 insertions, 263 deletions
  • Two merged PRs: #110 (docs + architecture + llms.txt) and #113 (TUI + WebSocket + user-mgmt fixes)
  • Live-tested against a real install: perm-heal + jq-consistent revoke confirmed end-to-end across multiple users with mixed sing-box / WG / AmneziaWG state

Thanks

Operators who reported issues that surfaced these fixes — the perm bug and the sing-box / Xray / telemt revoke gaps were all caught by running real users on real servers, not by tests.

Full Changelog: v1.8.2...v1.8.3


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.8.2

Choose a tag to compare

@shayanb shayanb released this 29 May 16:56
bf025f0

v1.8.2 — bugfix rollup

Rollup of stability and UX fixes surfaced by fresh-VPS testing since v1.8.0 .

Behavior change

  • Shadowsocks-2022 is now on by default (ENABLE_SS=true in .env.example). Matches the other on-by-default proxies. Existing .env files with an explicit value win; only fresh installs / .env files that removed the line see the change.

Fixed

moav start and profile handling

  • Disabled services started anyway via DEFAULT_PROFILES / --profile all (issue #106). Six code paths hard-coded … conduit snowflake into the literal profile list, so ENABLE_SNOWFLAKE=false / ENABLE_CONDUIT=false were silently overridden — the Snowflake relay actively relayed Tor traffic on installs where the operator had explicitly disabled it. New profile_enabled / derive_enabled_profiles / filter_disabled_profiles helpers are now the single source of truth. moav start filters stale DEFAULT_PROFILES entries (self-heal with a one-line note); moav start all expands to the derived enabled set; moav start with ENABLE_*=false prompts: (1) enable in .env and start, (2) skip, (3) start once without persisting. snowflake-exporter profile moved from [monitoring, all] to [snowflake, all] so Grafana panels don't show stale data when the relay is off.

  • moav start prompt option 1 was unclear, especially for monitoring (single-flag but labeled "n/a — multiple flags"). Added
    monitoring → ENABLE_MONITORING to the case map. Multi-flag profiles (proxy, dnstunnel) now name their underlying flags. All three option labels reworded action-first.

Domainless mode

  • Bootstrap errored with DOMAIN is required immediately after the operator chose domainless. scripts/bootstrap.sh checks 6 vars; the moav.sh disable loops only flipped 5 — ENABLE_MASTERDNS (added in 1.8.0 default-on) was missing. Fixed both loops. The bootstrap error message also listed only 4 of the 6 vars to disable; corrected. The "Running in domainless mode" notice is now derived from ENABLE_* flags so the listed protocols stay accurate.
  • Domainless mode triggered a port-53 / systemd-resolved conflict prompt for nothing. XDNS doesn't need TLS, but the dnstunnel profile pulls in dns-router which is useless without a domain to delegate. Disable loop now also flips ENABLE_XDNS=false; direct-mode XDNS (port 5356)re-enabled manually.
  • Bundle for fresh users was missing MasterDNS instructions. The fast host-side moav user add only generated dnstt/Slipstream/XDNS instructions; MasterDNS (default-on since 1.8.0) was missed. Fixed.

Interactive bootstrap

  • https://AAA.DDD/ accepted as DOMAIN verbatim — broke Let's Encrypt / DNS / Reality / CDN. New sanitize_domain strips scheme / user@ / path/ port / whitespace and lowercases; is_valid_domain validates the result. Prints Cleaned input: 'X' → 'Y' so the operator sees what landed in .env. Same auto-clean runs against existing malformed DOMAIN= on later bootstraps.
  • Arrow keys printed raw ^[[D in the domain / email prompts — read -r doesn't enable readline. Switched to read -r -e.
  • Aborted bootstrap left .env half-configured; next run skipped everything. Ctrl-C after typing the domain (before email) left DOMAIN set, ACME_EMAIL empty, ADMIN_PASSWORD at the insecure default. The existing-.env branch now re-prompts for missing ACME_EMAIL and always runs ensure_admin_password.
  • moav bootstrap exited silently after ✓ .env file exists on the second run. ensure_admin_password returned 1 for "already set", which set -euo pipefail turned into a silent abort. Changed to return 0; no caller inspected the return code.
  • moav bootstrap itself failed with /usr/local/bin/sing-box: cannot execute: required file not found. Dockerfile.bootstrap was on
    alpine:3.21 (musl) but the prebuilt sing-box 1.13.x binary is glibc-linked (its PT_INTERP is /lib64/ld-linux-x86-64.so.2, missing on
    Alpine). Switched to debian:bookworm-slim.
  • DNS records prompt was missing the MasterDNS NS row (m) — fresh installs with all 4 DNS tunnels default-on wouldn't know to delegate MasterDNS. Added the missing row.

User lifecycle

  • moav user add silently produced incomplete bundles when ENABLE_SS=true was set post-bootstrap. Without the server PSK + shadowsocks-in inbound (only created during bootstrap), the user-add path silently skipped SS generation via two code paths. Added a loud precondition check with exact remediation.
  • moav user revoke reported failure after a successful files-only cleanup. The REVOKED flag only flipped when the user was found in a service entry; pure file cleanup didn't update it, so revoke would print file successes then error "not found." Added a CLEANED_FILES tracker; summary distinguishes service-revoke / files-only / not-found.

Helpers

  • update_env_var appended duplicate lines instead of uncommenting #X=. The helper recognized # X= (space after #) but not #X= — and .env.example:73 ships #ENABLE_MONITORING= with no space. Regex relaxed to ^#[[:space:]]*X=. Routed 4 other ad-hoc grep+sed-or-append patterns through the helper.

Upgrade

moav update
moav build bootstrap --no-cache    # only needed if pulling in the alpine→debian Dockerfile.bootstrap change
moav start                          # recreates containers

No bootstrap, user-regeneration, or DB migration required. Behavior changes self-heal on next start:

  • Stale DEFAULT_PROFILES entries naming disabled services drop on next moav start
  • Malformed DOMAIN= values auto-clean on next moav bootstrap
  • Missing ACME_EMAIL with DOMAIN set re-prompts on next bootstrap

Full Changelog: v1.8.0...v1.8.2


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.8.0

Choose a tag to compare

@shayanb shayanb released this 28 May 12:16
40b6c19

MoaV v1.8.0

Full MahsaNG v16 support, four DNS tunnels running in parallel on one port, a one-paste V2Ray subscription in every bundle, and a build path that finally runs on a 1–2 GB VPS.

This started as a DNS-tunnels release and grew into a tightening of the whole stack — new protocols, a critical XDNS crash fix, a sing-box 1.13 config migration, and the build/update reliability work that comes with running MoaV on small censored-region servers for real.


✨ Highlights

Full MahsaNG v16 + V2Ray subscription

  • MasterDNS — DNS tunnel native to MahsaNG v16, fronted on m.<domain>. Markedly more reliable than dnstt/Slipstream during heavy shutdowns; keeps a session alive when almost nothing but DNS is getting through. Enabled by default; prebuilt binary checksum-verified.
  • GooseRelay — SOCKS5 over a Google Apps Script you deploy in your own Google account; fronted as google.com, AES-256-GCM end-to-end. Opt-in. Each bundle ships a ready-to-paste Code.gs + client config.
  • One-paste V2Ray subscription in every bundlesubscription.txt + a click-to-copy "Import everything at once" card at the top of the README (EN + FA). One paste imports all proxy protocols into MahsaNG, v2rayNG, Hiddify, Streisand, or any V2Ray app — it isn't MahsaNG-specific.

Four DNS tunnels, one port, all at once

dnstt, Slipstream, MasterDNS, and XDNS now run simultaneously, fanned out by subdomain (t./s./m./x.) through a dns-router that now self-heals after a backend restart/rebuild. Pick whichever tunnel survives in your environment — no server reconfig needed.

🛟 Critical config fixes

  • XDNS no longer crashes Xray on v26.5.9 — v26.x removed the finalmask domain field; MoaV now emits domains / resolvers per the new schema. Without this, anyone with XDNS enabled on 1.7.8/1.7.9 was crash-looping the whole xray container — Reality, Trojan, XHTTP went down with it. If you turned on XDNS, this is the upgrade you want.
  • sing-box migrated to the 1.13 rule-action model — bumped to 1.13.12; the legacy sniff inbound field and block outbound (removed in 1.13) are gone.

🧱 Builds that survive a 1 GB box

From live testing on a 1–2 GB VPS:

  • RAM-aware builds auto-tier concurrency (≤3 GB serial, 3–6 GB ×2, more = parallel), ending the context deadline exceeded failures from Docker building every image at once.
  • Snowflake ships prebuilt (Tor's official image, source-compile fallback). The heaviest compile is gone.
  • The installer offers swap on low-RAM hosts; moav doctor flags missing swap.
  • moav update rebuilds source-changed services like dns-router — a code change actually reaches the running container.
  • dns-router self-heals after a backend restart/rebuild.

Robustness & polish

  • moav user add produces a complete bundle in one step (MasterDNS/GooseRelay included).
  • moav update no longer trips on generated/runtime files or the pipe-separated GOPROXY.
  • moav doctor no longer reports a lower release as an "available update."
  • The redundant moav user mahsanet CLI and the per-user "MahsaNG" admin download are gone — the bundled subscription replaces both.
  • New TROUBLESHOOTING coverage for DNS-tunnel client MTU and SSH-over-DNS app compatibility.

⬆️ Upgrade

Breaking — ENABLE_XDNS and ENABLE_MASTERDNS now default to true, and the XDNS + sing-box config fixes need a re-bootstrap.

moav update
moav build masterdns gooserelay xray   # builds auto-serialize on low-RAM; no --no-cache needed
moav bootstrap                          # regenerates configs (XDNS v26 + sing-box 1.13)
moav regenerate-users                   # refresh user bundles (V2Ray subscription, etc.)
moav start                              # recreate containers on the new images
  • Set ENABLE_XDNS=false / ENABLE_MASTERDNS=false in .env before updating to opt out.
  • XDNS direct-mode moved off host port 53 → PORT_XDNS (5356); dns-router owns UDP 53.
  • On a ≤2 GB host, accept the installer / moav doctor swap suggestion.

📋 Full changelog

See CHANGELOG.md.

Full Diff: v1.7.8...v1.8.0


MoaV is free, open-source infrastructure for circumventing internet censorship. If it helps someone stay connected, that's the whole point. #FreeIran


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.7.8

Choose a tag to compare

@shayanb shayanb released this 25 May 21:28
fc0a4ed

Component refresh (Xray, telemt, wstunnel) with a new XDNS multi-resolver fallback for hostile networks, much faster xray builds, and hardened build resilience for restricted regions.

Highlights

🌐 XDNS multi-resolver fan-out (XDNS_RESOLVERS) — XDNS client bundles now round-robin DNS queries across multiple public resolvers in a single mKCP session (default 1.1.1.1,8.8.8.8). When one resolver gets throttled or null-routed — the daily reality during Iran shutdowns — the tunnel keeps moving on the others. Configurable in .env; direct-mode bundles correctly skip it.

⚡ xray builds in seconds, not minutes — the server image now installs the official prebuilt Xray binary for the pinned tag (the same one the client already used, FinalMask/XDNS included) and only compiles from source as a fallback. The from-source compile was a leftover from the pre-1.7.4 main-branch era.

🛡️ Build resilience for restricted networks — all Go-from-source images now use GOPROXY=…|goproxy.cn|direct (pipe = fall through on any error, including the 403 Google's module CDN throws under rate-limiting) with GOSUMDB=off. Fixes hard build failures like klauspost/compress@v1.17.4: 403 Forbidden. Overridable in .env.

🩺 moav doctor logs — new check that finds oversized container logs (*-json.log > 100 MB) and offers to truncate them in place — useful for clearing accumulated logs from pre-1.7.6 containers created before log rotation existed.

🔄 Config-aware moav update — after pulling, it now detects changed server config templates and prints the full ordered apply sequence (build → bootstrap → regenerate-users → start) instead of a bare build hint, including the reminder that moav start (not restart) is what recreates containers on rebuilt images.

Component updates

┌───────────┬──────────┬─────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Component │   From   │   To    │                                                Notes                                                 │
├───────────┼──────────┼─────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Xray-core │ v26.3.27 │ v26.5.9 │ mKCP unaggressive congestion default (XDNS stability win), XHTTP memory-leak fix, XDNS               │
│           │          │         │ multi-resolver support, single-UDP-socket multi-resolver                                             │
├───────────┼──────────┼─────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ telemt    │ 3.4.10   │ 3.4.11  │ Security hardening (constant-time API auth, fail-closed deny-lists, PROXY-protocol rejection),       │
│           │          │         │ persistent quota state, REST quota-reset, new TLS/connection metrics                                 │
├───────────┼──────────┼─────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ wstunnel  │ 10.5.3   │ 10.5.5  │ Bugfixes: HTTP-proxy password log leak, TLS/DNS resolution, Android compat                           │
└───────────┴──────────┴─────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘

⚠️ Upgrade note — no action required

Xray v26.5.9 renamed the inbound clients/accounts key to users, but kept the old keys as backward-compatible aliases. Your existing configs/xray/config.json keeps working as-is — no crash, no re-bootstrap needed. MoaV's templates emit users for fresh installs. Optional cleanup: moav bootstrap migrates the on-disk config to the new key (idempotent, preserves keys + user UUIDs). To get the new XDNS_RESOLVERS default into existing user bundles, run moav regenerate-users.

To update:

  moav update                       # pull + sync .env versions
  moav build xray telemt wstunnel --no-cache
  moav start                        # recreate containers on the new images
  • DNS-tunnel docs: new Reachable DNS resolvers guidance (docs/protocols.md) with findns/dns-mns scanner links, cross-referenced from DNS.md, CLIENTS.md, and the bundled xdns.txt.
  • TROUBLESHOOTING.md "Disk space full": in-place log truncation steps + docker builder prune -af.
  • Dockerfile.client xray pin bumped to match the server; stale moav.sh fallback versions corrected.

Full Changelog: v1.7.7...v1.7.8


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.7.7

Choose a tag to compare

@shayanb shayanb released this 01 May 14:06
e68e931

Release v1.7.7

Highlights

  • Shadowsocks-2022 protocol — adds an SS-2022 (AEAD-2022) inbound to the existing sing-box service behind a new
    ENABLE_SS=true toggle. Compatible with NekoBox / Hiddify / Streisand / sing-box / Outline mobile clients via standard ss:// URI. Default cipher 2022-blake3-aes-128-gcm for multi-user support. Off by default. (Refs #93)
  • Container log rotation — every long-running service now caps json-file logs at 10 MB × 3 files via a shared x-logging anchor. Stops chatty services from filling /var/lib/docker/containers on long-running VPS deployments
  • dnstt update with critical bugfix — pinned to upstream v1.20260501.0, fixing a silent-stall bug where dnstt-server kept
    accepting queries but stopped sending responses if sendLoop died on a transient sendto: operation not permitted (commonly triggered when conntrack overflows under load)

Upgrade notes

  • No breaking changes for existing setups. Run moav update to upgrade.
  • If you want Shadowsocks: set ENABLE_SS=true in .env, run moav bootstrap (generates server + per-user PSKs and adds the inbound), then moav restart sing-box. Open firewall port 8388/tcp+udp. Existing user bundles will need moav user regenerate to pick up shadowsocks.txt / shadowsocks-qr.png / shadowsocks-singbox.json
  • If you previously had a chacha20 SS_METHOD (from any pre-release dev pull): bootstrap auto-detects PSK length mismatch and regenerates SS keys cleanly — no manual cleanup needed

What's New

Added

  • Shadowsocks-2022 protocol — New ENABLE_SS=true toggle adds a Shadowsocks-2022 inbound to the existing sing-box service (no new container, no new daemon). Defaults to 2022-blake3-aes-128-gcm for multi-user support (sing-box's SS-2022 multi-user mode only supports the AES variants; chacha20-poly1305 is single-user only). Per-user PSKs are generated at bootstrap and persisted under state/users/<id>/shadowsocks.env; the server PSK lives at state/keys/shadowsocks-server.psk. Bootstrap auto-regenerates the SS state if SS_METHOD changes and the saved PSK no longer matches the cipher key length. User bundles emit a standardss:// URI + QR + sing-box JSON, compatible with NekoBox / Hiddify / Streisand / sing-box clients and the Outline mobile app. Off by default; enable via ENABLE_SS=true in .env and re-bootstrap. Refs #93
  • Container log rotation — All long-running services in docker-compose.yml now use a shared x-logging anchor that caps
    each container's json-file log at max-size: 10m × max-file: 3 (~30 MB per container). Previously containers used the Docker
    default with no size cap, and chatty services (xray, sing-box, telemt, prometheus) could fill /var/lib/docker/containers over time on long-running VPS deployments
  • DNSTT_VERSION env knob — dnstt builds (server + client) are now pinned to a configurable git tag (v1.20260501.0 default) and tracked alongside the other component versions in moav update and moav versions. Was previously cloned at HEAD with no version visibility

Fixed

  • dnstt-server silent stalls under load — Updated dnstt to upstream v1.20260501.0, which fixes a bug where the server could
    keep accepting DNS queries but stop sending responses if sendLoop died on a transient sendto: operation not permitted
    (commonly triggered when the host's Netfilter conntrack table overflows under many concurrent users). Process now exits if either
    recvLoop or sendLoop returns, and sendLoop only logs (no longer dies on) most send errors. See net4people/bbs#609
  • moav user add failing on wireguard/server.pub permission deniedwg-user-add.sh defensively re-synced the server
    public key file on every run, but bootstrap chowns configs/ to 0:1000 with chmod -R g+r (read-only for the admin container's
    uid 1000). The write failed and set -euo pipefail aborted the whole user-add flow. The sync is now best-effort: failed writes log a warning and the script proceeds with the in-memory key from wg show (which is authoritative)
  • dnstt build fails over bamsoftware.com dumb HTTPDockerfile.dnstt and the dnstt-client builder stage in Dockerfile.client used git clone --depth 1 --branch ${DNSTT_VERSION}, which requires smart-HTTP upload-pack. bamsoftware.com serves git as a static Apache directory and rejected the request with exit 128. Switched to a full clone + git checkout ${DNSTT_VERSION} (tag pinning preserved)

Upgrade

moav update

No breaking changes for existing setups. To enable the new Shadowsocks-2022 protocol:

# In .env                                                                                                                         
ENABLE_SS=true                                         
                                                                                                                                  
# Then re-run bootstrap to splice in the SS inbound + generate PSKs                                                               
moav bootstrap                                                                                                                    
moav restart sing-box                                                                                                             
                                                       
# Open the firewall (host + cloud security group)                                                                                 
ufw allow 8388/tcp
ufw allow 8388/udp                                                                                                                
                                                       
# Existing users need a bundle refresh to get the new ss:// URI + QR                                                              
moav user regenerate

Full Changelog: v1.7.6...v1.7.7


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation

v1.7.6

Choose a tag to compare

@shayanb shayanb released this 01 May 10:44
1bdf622

What's New

Added

  • telemt container healthcheck — Wired up the telemt healthcheck … --mode liveness subcommand (added upstream in 3.4.3) as a Docker healthcheck on the telemt service. docker compose ps and moav doctor now reflect actual daemon liveness instead of just process-up state

Changed

  • telemt — Updated 3.3.39 → 3.4.10. Highlights since 3.3.39: configurable mask timeouts and unlimitedmask_relay_max_bytes (3.4.0/3.4.5), traffic-control + weighted fairness + 3-leveled pressure model (3.4.1/3.4.4), TLS 1.2/1.3 correctness in fronting + full ServerHello + ALPN in TLS Fetcher (3.4.6), unknown-SNI reject-handshake option (3.4.4), bounded relay queues by bytes (3.4.7), restored active-IP observability for users without unique-IP limits (3.4.8), TimeWindow IP-limit fix and atomic config Includes (3.4.9), and TLS full-cert budget bookkeeping + IP-tracker refactor (3.4.10). No config-file changes required — MoaV's existing [censorship] and [server.api] sections remain compatible
  • wstunnel — Updated 10.5.2 → 10.5.3 (proxy-protocol IP-family-mismatch workaround, log-noise reduction, deps bump)
  • Grafana Conduit dashboard — Region panels now show CODE: Country Name (full ISO 3166-1 set, 249 mappings) instead of bare 2-letter codes; Connected Clients by Region enlarged and joined by a sortable Mean/Max/Last stats table (PR #91, thanks @jSFBay)

Upgrade

moav update

No breaking changes — .env and existing user configs remain compatible. The telemt and wstunnel containers will rebuild on next moav update.

Full Changelog: v1.7.5...v1.7.6


Quick Install

curl -fsSL moav.sh/install.sh | bash

This will install MoaV to /opt/moav and guide you through setup.

Documentation

moav.sh/docs — Full documentation