fix(render,store): frpc user 支持 + 修正 useEncryption/useCompression 渲染位置

- store.Remote 新增 user 字段(DB 列名 usr,避开保留字;migrate() 自动加列),
  渲染为 frpc 全局 user。面板型 frps(如 ChhChongGou 实例)强制要求 user 登录,
  缺失时服务端返回 panel authentication unavailable: panel HTTP status 403
- ★ 修复渲染 bug:useEncryption/useCompression/bandwidthLimit 必须内嵌在 proxy 的
  transport 对象(frpc ProxyTransport)。此前渲染在 proxy 顶层,frpc 默认
  --strict-config 直接拒载:unknown field "useEncryption"。任何启用加密/压缩
  的转发都无法启动。poolCount 无 proxy 级表达(官方 ProxyTransport 无此字段),
  保留在 store 但不再渲染
- 前端:RemoteNode 画布节点与 StatusView 表单加 user 输入(画布此前会静默
  清掉未声明的字段),带『面板型 frps 必填』提示
- 测试:user 往返(含迁移路径)、渲染含/不含 user、transport 内嵌与顶层
  非法键回归守卫
This commit is contained in:
JianFeeeee
2026-10-12 19:16:28 +08:00
parent b29cd0d186
commit d83e5d8e70
7 changed files with 214 additions and 36 deletions

View File

@ -73,6 +73,12 @@ type Remote struct {
URL string `json:"url,omitempty"`
Enabled bool `json:"enabled"`
// User is frpc's global `user` field: the proxy names reported to frps become
// spellings. Panel-managed frps installations require it for login (without
// it the server answers `panel authentication unavailable: panel HTTP status
// 403`), while plain frps ignores it. Empty = omitted from the rendered config.
User string `json:"user,omitempty"`
// Transport section (M1). Protocol: tcp | quic | kcp | websocket.
TransportProtocol string `json:"transportProtocol,omitempty"`
TransportTLS bool `json:"transportTls,omitempty"`
@ -322,6 +328,7 @@ func (s *Store) migrate() error {
"transport_tls INTEGER NOT NULL DEFAULT 0",
"transport_pool INTEGER NOT NULL DEFAULT 0",
"transport_tls_server_name TEXT NOT NULL DEFAULT ''",
"usr TEXT NOT NULL DEFAULT ''",
"vhost_http_port INTEGER NOT NULL DEFAULT 0",
"admin_addr TEXT NOT NULL DEFAULT ''",
"admin_port INTEGER NOT NULL DEFAULT 0",
@ -476,7 +483,7 @@ func (s *Store) DeleteLocal(name string) error {
// ---- Remotes ----
func (s *Store) ListRemotes() ([]Remote, error) {
rows, err := s.db.Query("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, vhost_http_port, admin_addr, admin_port, admin_user, admin_password FROM remotes ORDER BY name")
rows, err := s.db.Query("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, usr, vhost_http_port, admin_addr, admin_port, admin_user, admin_password FROM remotes ORDER BY name")
if err != nil {
return nil, err
}
@ -485,7 +492,7 @@ func (s *Store) ListRemotes() ([]Remote, error) {
for rows.Next() {
var r Remote
var en, tls int
if err := rows.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName, &r.VhostHTTPPort, &r.AdminAddr, &r.AdminPort, &r.AdminUser, &r.AdminPassword); err != nil {
if err := rows.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName, &r.User, &r.VhostHTTPPort, &r.AdminAddr, &r.AdminPort, &r.AdminUser, &r.AdminPassword); err != nil {
return nil, err
}
r.Enabled = en != 0
@ -498,8 +505,8 @@ func (s *Store) ListRemotes() ([]Remote, error) {
func (s *Store) GetRemote(name string) (Remote, bool) {
var r Remote
var en, tls int
row := s.db.QueryRow("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, vhost_http_port, admin_addr, admin_port, admin_user, admin_password FROM remotes WHERE name = ?", name)
if err := row.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName, &r.VhostHTTPPort, &r.AdminAddr, &r.AdminPort, &r.AdminUser, &r.AdminPassword); err != nil {
row := s.db.QueryRow("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, usr, vhost_http_port, admin_addr, admin_port, admin_user, admin_password FROM remotes WHERE name = ?", name)
if err := row.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName, &r.User, &r.VhostHTTPPort, &r.AdminAddr, &r.AdminPort, &r.AdminUser, &r.AdminPassword); err != nil {
return Remote{}, false
}
r.Enabled = en != 0
@ -509,9 +516,9 @@ func (s *Store) GetRemote(name string) (Remote, bool) {
func (s *Store) UpsertRemote(r Remote) error {
_, err := s.db.Exec(
"INSERT INTO remotes(name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, vhost_http_port, admin_addr, admin_port, admin_user, admin_password) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "+
"ON CONFLICT(name) DO UPDATE SET ip=excluded.ip, port=excluded.port, token=excluded.token, url=excluded.url, enabled=excluded.enabled, transport_protocol=excluded.transport_protocol, transport_tls=excluded.transport_tls, transport_pool=excluded.transport_pool, transport_tls_server_name=excluded.transport_tls_server_name, vhost_http_port=excluded.vhost_http_port, admin_addr=excluded.admin_addr, admin_port=excluded.admin_port, admin_user=excluded.admin_user, admin_password=excluded.admin_password",
r.Name, r.IP, r.Port, r.Token, r.URL, boolToInt(r.Enabled), r.TransportProtocol, boolToInt(r.TransportTLS), r.TransportPool, r.TransportTLSServerName, r.VhostHTTPPort, r.AdminAddr, r.AdminPort, r.AdminUser, r.AdminPassword,
"INSERT INTO remotes(name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, usr, vhost_http_port, admin_addr, admin_port, admin_user, admin_password) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "+
"ON CONFLICT(name) DO UPDATE SET ip=excluded.ip, port=excluded.port, token=excluded.token, url=excluded.url, enabled=excluded.enabled, transport_protocol=excluded.transport_protocol, transport_tls=excluded.transport_tls, transport_pool=excluded.transport_pool, transport_tls_server_name=excluded.transport_tls_server_name, usr=excluded.usr, vhost_http_port=excluded.vhost_http_port, admin_addr=excluded.admin_addr, admin_port=excluded.admin_port, admin_user=excluded.admin_user, admin_password=excluded.admin_password",
r.Name, r.IP, r.Port, r.Token, r.URL, boolToInt(r.Enabled), r.TransportProtocol, boolToInt(r.TransportTLS), r.TransportPool, r.TransportTLSServerName, r.User, r.VhostHTTPPort, r.AdminAddr, r.AdminPort, r.AdminUser, r.AdminPassword,
)
return err
}