Release 3.10.0+1: sidebar UI (Home / Profiles / Settings / Logs / About), remembered account with 50 GB monthly renew + auto-renew, Windows VPN mode (wintun + tun2socks).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -189,6 +189,60 @@ func ProvisionUser(ctx context.Context, cfg *AdminConfig, rawUsername string) (*
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// RenewUser extends the SAME panel user: resets used traffic
|
||||
// (POST /api/users/{uuid}/actions/reset-traffic) and pushes the plan forward
|
||||
// via PATCH /api/users — trafficLimitBytes = plan (50 GB default) and
|
||||
// expireAt = max(now, current expiry) + plan days (30 default).
|
||||
// It never creates users.
|
||||
func RenewUser(ctx context.Context, cfg *AdminConfig, userUUID string, currentExpire int64) (*ProvisionResult, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("нет конфигурации remnawave-api")
|
||||
}
|
||||
userUUID = strings.TrimSpace(userUUID)
|
||||
if userUUID == "" {
|
||||
return nil, fmt.Errorf("нет UUID пользователя для продления")
|
||||
}
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// 1) Reset used traffic. Some panels return 404 for this action route —
|
||||
// tolerate it, the PATCH below still extends the plan.
|
||||
resetURL := cfg.PanelURL + "/api/users/" + url.PathEscape(userUUID) + "/actions/reset-traffic"
|
||||
status, body, err := adminDo(ctx, client, cfg, http.MethodPost, resetURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != http.StatusOK && status != http.StatusCreated && status != http.StatusNotFound {
|
||||
return nil, fmt.Errorf("сброс трафика: %w", adminHTTPError(status, body))
|
||||
}
|
||||
|
||||
// 2) Extend expiry + (re)set the traffic limit.
|
||||
base := time.Now().UTC()
|
||||
if currentExpire > 0 {
|
||||
if cur := time.Unix(currentExpire, 0).UTC(); cur.After(base) {
|
||||
base = cur
|
||||
}
|
||||
}
|
||||
payload := map[string]any{
|
||||
"uuid": userUUID,
|
||||
"status": "ACTIVE",
|
||||
"trafficLimitBytes": cfg.Provision.TrafficGB * 1024 * 1024 * 1024,
|
||||
"expireAt": base.AddDate(0, 0, cfg.Provision.Days).Format(time.RFC3339),
|
||||
}
|
||||
raw, _ := json.Marshal(payload)
|
||||
status, body, err = adminDo(ctx, client, cfg, http.MethodPatch, cfg.PanelURL+"/api/users", raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if status != http.StatusOK && status != http.StatusCreated {
|
||||
return nil, fmt.Errorf("продление: %w", adminHTTPError(status, body))
|
||||
}
|
||||
res, err := parseProvisionUser(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func adminDo(ctx context.Context, client *http.Client, cfg *AdminConfig, method, endpoint string, body []byte) (int, string, error) {
|
||||
var rd io.Reader
|
||||
if body != nil {
|
||||
@@ -198,7 +252,7 @@ func adminDo(ctx context.Context, client *http.Client, cfg *AdminConfig, method,
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Navis/3.9 (Remnawave provision)")
|
||||
req.Header.Set("User-Agent", "Navis/3.10 (Remnawave provision)")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
Reference in New Issue
Block a user