What a Line means in m-ui
Every proxy panel has to answer "what do I edit?". Most answer with the core's own vocabulary: inbounds, outbounds, routing rules. m-ui answers with one record — a line — and renders the rest.
Definition
A line is: inbound protocol + port → where traffic exits, plus the options that protocol needs (TLS mode, transport, protocol-specific fields) and the list of servers it is deployed to. That is the whole record. Users are attached to lines through an assignment table; upstreams are referenced by id as the exit.
| Field | Meaning |
|---|---|
| protocol, port | The listener sing-box will open — Hysteria2 on UDP 24443, VLESS on TCP 443, and so on. |
| tls | none, certificate (the server's own), or Reality with its keys and handshake target. |
| transport | tcp, WebSocket, gRPC, HTTPUpgrade, HTTP, with their paths and hosts. |
| options | Protocol extras: Hysteria2 bandwidth hints, obfuscation and port hopping; Shadowsocks method; VLESS Vision flow. |
| upstream | Direct, or an upstream record (another proxy, WARP). This becomes the outbound and the routing rule. |
| servers | Empty = every server; otherwise the subset of nodes that should run this listener. |
What rendering does
When m-ui needs a configuration for a server — on save, on restart, or when pushing a snapshot to a node — it walks the lines deployed to that server and produces, for each: one sing-box inbound with the users assigned to it (credentials from the user records), the server's own certificate paths if TLS is "certificate", and a routing rule that sends traffic entering that inbound to the line's exit. Exits are rendered once each as outbounds; direct and block are built in. The result is a complete sing-box JSON that the embedded core loads, or that its parser checks before a save is committed.
Because rendering is a pure function of the database, two servers with the same lines produce the same inbounds, and a line changed once changes everywhere on the next 5-second snapshot.
What is deliberately not in a line
- Per-user routing. Users get lines; they do not get their own rules. Splitting traffic by destination is done at the exit (an upstream) or on the client, not per user.
- Arbitrary routing tables. The panel owns the routing section; there is no editor for it. This is what keeps multi-server rendering deterministic.
- Every sing-box option. The forms cover what the supported protocols need. A per-line "advanced" JSON field merges extra inbound options for the rest.
Why not just expose the JSON?
Three reasons, in order of how often they bite people running panels:
- Consistency across servers. Hand-maintained inbounds drift; a rendered line cannot. Adding a node means "deploy this line there", not "copy this block and fix the certificate paths".
- Validation. A form with a fixed shape can be checked before it reaches the core; m-ui additionally hands the full render to sing-box's parser inside the save transaction, so what the core would reject at startup is rejected at save time.
- Reload tiers. Knowing that a change touched only users, only an upstream, or the line itself is what lets m-ui pick a hot swap over a restart. A free-form JSON edit gives no such information.
Practical consequences
- Ports are unique per line, checked against the panel and other processes on the box; a new line gets a free five-digit port suggested.
- The same line on N servers appears N times in each assigned user's subscription, named line – server.
- Deleting a server disables lines that were deployed only to it rather than silently spreading them to all servers.
- Switching a line's exit from direct to an upstream is a hot outbound swap; changing its port is a restart with rollback.