feat(mtproto): enforce per-client quota & expiry via mtg-multi limits

Map each mtproto client's totalGB and expiryTime onto mtg-multi's new
[secret-limits] (quota/expires): emit them into the generated config and
hot-apply through PUT /secrets so live connections survive. Quota is
written as an exact "<n>B" byte count that round-trips through both the
config and API parsers without the precision loss of a base-2 unit.

The sidecar's quota counter is not pruned when a secret is dropped, so a
panel-side traffic reset re-pushes the client's secret and then calls
POST /secrets/{name}/reset-quota (wired into every reset path) so a
renewed client is not immediately re-blocked.

Resolve the mtg-multi binary from the fork's latest release tag in
DockerInit.sh and release.yml instead of a hardcoded version pin, so the
panel no longer needs a manual bump per fork release.
This commit is contained in:
MHSanaei
2026-07-08 15:30:56 +02:00
parent 61e12e4c29
commit 328d920e98
8 changed files with 251 additions and 28 deletions
+52
View File
@@ -103,3 +103,55 @@ func (s *InboundService) applyLocalMtproto(inboundId int) {
logger.Debug("mtproto: immediate client apply failed for inbound", inboundId, ":", err)
}
}
func (s *InboundService) resetMtprotoClientQuota(email string) {
mgr := mtproto.GetManager()
if !mgr.HasRunning() {
return
}
id, ok := s.localMtprotoInboundIdForEmail(email)
if !ok {
return
}
s.applyLocalMtproto(id)
mgr.ResetQuota(email)
}
func (s *InboundService) resetAllMtprotoQuotas() {
mgr := mtproto.GetManager()
if !mgr.HasRunning() {
return
}
desired, err := s.DesiredMtprotoInstances()
if err != nil {
return
}
mgr.Reconcile(desired)
for _, inst := range desired {
for _, sec := range inst.Secrets {
mgr.ResetQuota(sec.Name)
}
}
}
func (s *InboundService) localMtprotoInboundIdForEmail(email string) (int, bool) {
db := database.GetDB()
var inbounds []*model.Inbound
if err := db.Model(model.Inbound{}).
Where("protocol = ? AND node_id IS NULL", model.MTProto).
Find(&inbounds).Error; err != nil {
return 0, false
}
for _, ib := range inbounds {
inst, ok := mtproto.InstanceFromInbound(ib)
if !ok {
continue
}
for _, sec := range inst.Secrets {
if sec.Name == email {
return ib.Id, true
}
}
}
return 0, false
}