Self-hosted service
MengMengCode/VoCat avatar
MengMengCode/VoCat

VoCat: a self-hosted control panel for Quectel EC20/EC25 cellular modems

Vocat is an open-source web control panel and engineering toolkit for Quectel EC20/EC25-class cellular modems. It combines modem discovery, live radio status, AT and USSD terminals, SMS, WiFi Calling, eSIM management, network selection, proxy routing, notifications, audit logs, and release automation in one self-contained service.

864 stars273 forksGoNOASSERTION

At a glance

What is it?
VoCat bundles modem discovery, AT and USSD terminals, SMS, WiFi Calling, eSIM profile management and proxy routing into one Go binary with an embedded React frontend. It is built for people running Quectel hardware on their own Linux host, not for phones.
Who is it for?
Adopt VoCat if you already run Quectel EC20/EC25-class hardware on a Linux box you control and you want AT, SMS, eSIM and IMS operations behind one authenticated web UI instead of a pile of scripts. Skip it if your modem is not Qualcomm-based Quectel, if you cannot give the service host network access for the SOCKS bind-to-device path, or if your kernel has no XFRM/IPsec and you need WiFi Calling.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 19, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What VoCat solves for Quectel modem owners

A Quectel EC20 or EC25 in a Linux host exposes several interfaces at once: AT serial ports, QMI, USB networking. Doing anything useful with them normally means keeping a folder of shell scripts, a serial terminal, and a separate tool for SMS. VoCat collapses that into one service. The README describes it as an "open-source web control panel and engineering toolkit for Quectel EC20/EC25-class cellular modems" and lists the scope: automatic serial/USB discovery, live radio status with RSRP/RSRQ/SINR, an interactive AT terminal, USSD start/continue/cancel flows, cellular and IMS SMS, WiFi Calling over IKEv2/ePDG, eSIM profile operations, operator scanning and manual network selection, SOCKS proxy routing, notifications, and audit logs.

The intended user is not a phone owner. It is someone with a modem wired into a router, a mini PC or an OpenWrt/Kwrt box, who wants a browser UI and an audit trail for operations that are otherwise done by hand. The supported hardware list is explicit: Quectel EC20, EC25, the EG25 family, and compatible EG600 and related modules. Everything else is out of scope, and the README adds that available features depend on module firmware, USB composition, SIM/eSIM capabilities, host drivers, radio network and carrier configuration. That caveat matters more than the feature table: two people with the same EC25 can see different capabilities.

Architecture: one Go binary, embedded frontend, SQLite state

The backend is Go, the interface is React and TypeScript, and the production frontend is compiled into the Go binary. A single executable serves the web application and keeps persistent state in SQLite. The Dockerfile shows the build in three stages: Node 20 Alpine runs npm ci and npm run build for the web assets, a golang:1.25-alpine stage cross-compiles with CGO_ENABLED=0 and overlays the built frontend so go:embed web/dist picks it up, and the runtime stage is alpine:3.20 with ca-certificates, ccid, iproute2, pcsc-lite, qmi-utils and tzdata. That package list is a fair summary of what VoCat touches: PC/SC for the eUICC reader, QMI utilities, and IP routing for the proxy path.

go.mod shows the actual integration surface. Modem access goes through github.com/iniwex5/quectel-qmi-go for QMI and go.bug.st/serial for the AT side, SMS encoding uses github.com/warthog618/sms, SQLite is modernc.org/sqlite (the pure-Go build, which is why CGO can stay off), and howett.net/plist handles Apple-style property lists, presumably for eSIM profile metadata. The web layer uses github.com/coder/websocket for live updates rather than polling. There is no external database and no message broker, so the operational footprint is one process and one file.

The design consequence is that VoCat is a single-tenant appliance service. It binds 0.0.0.0:7575 and manages its own authentication, CSRF protection and access policies. There is no documented multi-user model beyond that, and the audit log exists to record actions rather than to separate tenants.

Installing VoCat on Linux and opening the panel

The README gives a one-click installer for Linux. As root, including on OpenWrt/Kwrt where sudo is normally absent, the command is a single curl piped into bash. On a distribution with sudo, the same script is run through sudo bash. The installer detects amd64, 386, arm64, aarch64 or armv7, downloads the matching GitHub Release binary, verifies it against SHA256SUMS, installs under /opt/vocat, creates a hardened systemd service, stores runtime configuration in /etc/vocat/env, and generates a random initial administrator password on first installation.

bash
curl -fsSL https://raw.githubusercontent.com/MengMengCode/VoCat/master/scripts/install.sh | bash

Before committing to that, you can check the host without installing anything. The --check-env flag runs the prerequisite check for VoWiFi/XFRM and exits. This is the step worth doing first on OpenWrt, because the installer will otherwise try to pull matching ip-full, kmod-ipsec, kmod-ipsec4/6, kmod-crypto-authenc, AES-CBC and SHA1 packages from the firmware's own feed, and the README warns that you should never force-install kmods built for a different kernel.

bash
curl -fsSL https://raw.githubusercontent.com/MengMengCode/VoCat/master/scripts/install.sh | bash -s -- --check-env

If the kernel cannot provide XFRM/IPsec and you only want cellular SMS or data, the installer accepts --skip-vowifi-check. After installation the panel is served on port 7575, so the first real use is opening http://<server-address>:7575, logging in with the generated administrator password, and letting discovery enumerate the modem. From there the AT terminal is the fastest way to confirm the module responds as expected.

bash
curl -fsSL https://raw.githubusercontent.com/MengMengCode/VoCat/master/scripts/install.sh -o install.sh
sudo bash install.sh --skip-vowifi-check

Docker is the alternative path, and the compose file documents a different first-run sequence: pull the image, read the admin password from stdin into the bootstrap-admin subcommand, then bring the service up. The compose file sets network_mode: host deliberately, and the comment explains why: the export-proxy plugin uses SO_BINDTODEVICE to pin outbound proxy traffic to the modem interface (wwan0) so roaming data egresses only the module and never the host default route. That syscall needs the host network namespace, which network_mode: host provides. Port publishing is therefore meaningless, since the container shares the host stack and VoCat binds 0.0.0.0:7575 itself.

bash
docker compose pull
read -rsp "Admin password: " VOCAT_BOOTSTRAP_PASSWORD; echo
printf '%s\n' "$VOCAT_BOOTSTRAP_PASSWORD" | docker compose run --rm -T \
  --entrypoint /opt/vocat/bin/vocat vocat bootstrap-admin
unset VOCAT_BOOTSTRAP_PASSWORD
docker compose up -d

Where VoCat stops: kernel prerequisites and container self-update

The hardest constraint is the WiFi Calling path. IMS over WiFi needs Linux XFRM/IPsec, and if the kernel cannot supply it, the feature is simply unavailable. The README is direct about this: use a firmware that includes the matching kernel modules, or install with --skip-vowifi-check and give up VoWiFi. On OpenWrt that often means changing firmware rather than installing a package. Anyone evaluating VoCat primarily for WiFi Calling should treat the kernel as the deciding factor, not the application.

In containers, self-update is disabled by design. VOCAT_CONTAINER=docker makes the server return 409 on the apply endpoint, and the compose comments say to update by pulling a new image and recreating the container instead. That is a deliberate choice, and a reasonable one, but it means the SHA-256-verified self-update described in the distribution features applies to the static binary and systemd install, not to Docker. Two installation paths, two upgrade stories.

There is also a hardware boundary. VoCat targets Qualcomm-based Quectel modules with compatible AT, QMI, serial and USB networking interfaces. A modem from another vendor, or a Quectel module outside the listed families, is the wrong tool for this panel regardless of how similar the AT command set looks. And because features depend on firmware, USB composition, SIM/eSIM capability and carrier configuration, the README's feature table is a statement of what the software can do when the hardware cooperates, not a guarantee for a given unit.

How VoCat differs from ModemManager and manual AT tooling

The obvious comparison is ModemManager with NetworkManager on top. ModemManager is a system daemon that abstracts many modem vendors and hands connections to NetworkManager; it is the right choice when you want the operating system to bring up a data connection and you do not care about the modem itself. VoCat does the opposite. It exposes the modem as the subject: raw AT responses, USSD session control, per-message SMS delivery status, RSRP/RSRQ/SINR readouts, band and channel, eUICC profile enable/disable/switch, and IMS registration diagnostics. It is an engineering console, not a connection manager.

The second comparison is a hand-rolled stack: minicom or socat for AT, a Python script for SMS, lpac for eSIM, and strongSwan or a custom IKEv2 client for ePDG. That stack can do everything VoCat does, and each piece is independently replaceable. What it lacks is the shared state: VoCat keeps device records, ICCID-based policy for WiFi Calling and flight mode, proxy bindings, notification routing and an audit log in one SQLite file, and the Telegram bot can drive device status, profile switching, WiFi Calling controls and SMS sending, with administrator confirmation required for sensitive actions. If your workflow is one modem and one script, VoCat is more machinery than you need. If you are juggling several modems and want to know who changed what, the consolidation is the point.

Licence, release cadence and upgrade cost

The repository reports a license of NOASSERTION, which means GitHub could not map the LICENSE file to a recognised SPDX identifier. A LICENSE file, a LICENSES/ directory and a NOTICE file are all present at the top level, so the terms exist, but they are not summarised in a form a tool can classify. If you plan to redistribute VoCat, embed it in a product, or ship it inside a firmware image, read those files directly and get your own legal review. Nothing here is legal advice, and the practical risk is not the copyleft question so much as the ambiguity: you cannot rely on a one-word answer.

Upgrades are cheap on the binary path. The installer verifies releases against SHA256SUMS, and the release history shows a steady stream of small versions, with v0.2.30, v0.2.31 and v0.2.32 appearing within a week of each other. That cadence is a real maintenance consideration: frequent point releases mean frequent chances for behaviour to shift, and there is no documented long-term support branch or migration guide in the README or the release notes. On Docker the upgrade is a pull and a recreate, which is mechanical but restarts the service. VoCat stores state in SQLite, so back up that file before any upgrade, and on the systemd path check /etc/vocat/env after upgrading in case new configuration keys appear.

Editorial conclusion

Adopt VoCat if you already run Quectel EC20/EC25-class hardware on a Linux box you control and you want AT, SMS, eSIM and IMS operations behind one authenticated web UI instead of a pile of scripts. Skip it if your modem is not Qualcomm-based Quectel, if you cannot give the service host network access for the SOCKS bind-to-device path, or if your kernel has no XFRM/IPsec and you need WiFi Calling. Before installing, run the installer's --check-env mode to confirm the VoWiFi prerequisites, and read the LICENSE and LICENSES/ files, because the repository reports NOASSERTION rather than a recognised SPDX identifier.

Frequently asked questions

What does VoCat do?

VoCat is an open-source web control panel and engineering toolkit for Quectel EC20/EC25-class cellular modems. It combines modem discovery, radio status, AT and USSD terminals, SMS, WiFi Calling, eSIM management, network selection, proxy routing, notifications and audit logs in one self-contained service.

How do I install VoCat on Linux?

The README gives a one-click installer that you run as root, or through sudo bash on distributions with sudo. It detects the architecture, downloads the matching GitHub Release binary, verifies it against SHA256SUMS, installs under /opt/vocat and creates a systemd service.

How do I use VoCat after installing it?

After installation the panel is served at http://<server-address>:7575. The installer generates a random initial administrator password on first installation; log in with it, then use the AT terminal to confirm the modem responds before configuring SMS, eSIM or WiFi Calling.

Official sources

  1. Issues
  2. MengMengCode/VoCat on GitHub
  3. README
  4. Releases
Community notes

Community notes