Improve code consistency and organization

- Move header parsing to dedicated util/http_utils.go file
- Fix indentation issues in marzneshin.go
- Remove unused getEnvOrArg function
- Make HTTP header handling consistent across all API clients
- Standardize README formatting with consistent bullet points
- Improve documentation for custom headers feature
- Fix table formatting in documentation

These changes maintain all functionality while improving code organization,
readability, and consistency throughout the codebase.
This commit is contained in:
Sergey Kutovoy 2025-05-14 15:31:55 +05:00
parent e633a7fb49
commit d459ffa8f4
No known key found for this signature in database
GPG key ID: 485DE7FCA2B08DD2
4 changed files with 61 additions and 60 deletions

View file

@ -4,8 +4,8 @@ A command-line tool for migrating users from various VPN management panels to Re
## Supported Source Panels
* Marzban
* Marzneshin
- Marzban
- Marzneshin
## Overview
@ -13,13 +13,13 @@ This tool helps you migrate user accounts from various VPN management panels to
## Key Features
* Batch processing with configurable batch size
* Migration of selected number of most recent users
* Automatic handling of existing users
* Support for environment variables
* Customizable traffic reset strategy
* Flexible status handling
* Support for custom headers in both source and destination panels
- Batch processing with configurable batch size
- Migration of selected number of most recent users
- Automatic handling of existing users
- Support for environment variables
- Customizable traffic reset strategy
- Flexible status handling
- Support for custom headers in both source and destination panels
## Migrated User Fields
@ -40,20 +40,20 @@ This tool helps you migrate user accounts from various VPN management panels to
The tool can be configured using command-line flags or environment variables.
| Flag | Env Variable | Description | Default |
| -------------------- | ------------------- | -------------------------------------------------------------- | ------- |
| --panel-type | PANEL\_TYPE | Source panel type (marzban or marzneshin) | marzban |
| --panel-url | PANEL\_URL | Source panel URL | |
| --panel-username | PANEL\_USERNAME | Source panel admin username | |
| --panel-password | PANEL\_PASSWORD | Source panel admin password | |
| --remnawave-url | REMNAWAVE\_URL | Destination panel URL | |
| --remnawave-token | REMNAWAVE\_TOKEN | Destination panel API token (used as Authorization Bearer) | |
| --dest-headers | DEST\_HEADERS | Additional headers for Remnawave (e.g., X-Api-Key) | |
| --source-headers | SOURCE\_HEADERS | Additional headers for source panel | |
| --batch-size | BATCH\_SIZE | Number of users to process in one batch | 100 |
| --last-users | LAST\_USERS | Only migrate last N users (0 means all users) | 0 |
| --preferred-strategy | PREFERRED\_STRATEGY | Preferred traffic reset strategy (NO\_RESET, DAY, WEEK, MONTH) | |
| --preserve-status | PRESERVE\_STATUS | Preserve user status from source panel | false |
| Flag | Env Variable | Description | Default |
| -------------------- | ------------------ | ------------------------------------------------------------- | ------- |
| --panel-type | PANEL_TYPE | Source panel type (marzban or marzneshin) | marzban |
| --panel-url | PANEL_URL | Source panel URL | |
| --panel-username | PANEL_USERNAME | Source panel admin username | |
| --panel-password | PANEL_PASSWORD | Source panel admin password | |
| --remnawave-url | REMNAWAVE_URL | Destination panel URL | |
| --remnawave-token | REMNAWAVE_TOKEN | Destination panel API token (used as Authorization Bearer) | |
| --dest-headers | DEST_HEADERS | Additional headers for Remnawave (e.g., X-Api-Key) | |
| --source-headers | SOURCE_HEADERS | Additional headers for source panel | |
| --batch-size | BATCH_SIZE | Number of users to process in one batch | 100 |
| --last-users | LAST_USERS | Only migrate last N users (0 means all users) | 0 |
| --preferred-strategy | PREFERRED_STRATEGY | Preferred traffic reset strategy (NO_RESET, DAY, WEEK, MONTH) | |
| --preserve-status | PRESERVE_STATUS | Preserve user status from source panel | false |
## Usage
@ -95,12 +95,12 @@ The tool can be configured using command-line flags or environment variables.
**Available strategy values:**
* NO\_RESET
* DAY
* WEEK
* MONTH
- NO_RESET
- DAY
- WEEK
- MONTH
> Note: If not specified, the original strategy from Marzban will be used. YEAR is converted to NO\_RESET.
> Note: If not specified, the original strategy from Marzban will be used. YEAR is converted to NO_RESET.
## Custom Headers

30
main.go
View file

@ -2,13 +2,13 @@ package main
import (
"log"
"os"
"strings"
"remnawave-migrate/config"
"remnawave-migrate/migrator"
"remnawave-migrate/remnawave"
"remnawave-migrate/source"
"remnawave-migrate/util"
)
var version = "unknown"
@ -16,8 +16,8 @@ var version = "unknown"
func main() {
cfg := config.Parse(version)
cfg.SourceHeaders = parseHeaderMap(cfg.SourceHeadersRaw)
cfg.DestHeaders = parseHeaderMap(cfg.DestHeadersRaw)
cfg.SourceHeaders = util.ParseHeaderMap(cfg.SourceHeadersRaw)
cfg.DestHeaders = util.ParseHeaderMap(cfg.DestHeadersRaw)
if cfg.RemnawaveToken != "" {
if _, exists := cfg.DestHeaders["Authorization"]; !exists {
@ -67,27 +67,3 @@ func main() {
log.Println("Migration completed successfully!")
}
func parseHeaderMap(raw string) map[string]string {
headers := make(map[string]string)
if raw == "" {
return headers
}
for _, pair := range strings.Split(raw, ",") {
kv := strings.SplitN(pair, ":", 2)
if len(kv) == 2 {
headers[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
}
}
return headers
}
func getEnvOrArg(envKey, flagKey string) string {
for _, arg := range os.Args {
if strings.HasPrefix(arg, flagKey+"=") {
return strings.SplitN(arg, "=", 2)[1]
}
}
return os.Getenv(envKey)
}

View file

@ -18,7 +18,7 @@ type MarzneshinPanel struct {
client *http.Client
baseURL string
authToken string
headers map[string]string
headers map[string]string
}
type MarzneshinUser struct {
@ -66,6 +66,9 @@ func (p *MarzneshinPanel) Login(username, password string) error {
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
for k, v := range p.headers {
req.Header.Set(k, v)
}
resp, err := p.client.Do(req)
if err != nil {
@ -88,10 +91,6 @@ func (p *MarzneshinPanel) Login(username, password string) error {
}
p.authToken = tokenResp.AccessToken
for k, v := range p.headers {
req.Header.Set(k, v)
}
return nil
}
@ -106,6 +105,9 @@ func (p *MarzneshinPanel) GetUsers(offset, limit int) (*models.UsersResponse, er
}
req.Header.Set("Authorization", "Bearer "+p.authToken)
for k, v := range p.headers {
req.Header.Set(k, v)
}
resp, err := p.client.Do(req)
if err != nil {
@ -179,9 +181,11 @@ func (p *MarzneshinPanel) fetchUserProxies(username, key string) (string, string
if err != nil {
return "", "", "", fmt.Errorf("creating subscription request: %w", err)
}
for k, v := range p.headers {
req.Header.Set(k, v)
}
resp, err := p.client.Do(req)
if err != nil {
return "", "", "", fmt.Errorf("fetching subscription: %w", err)

21
util/http_utils.go Normal file
View file

@ -0,0 +1,21 @@
package util
import (
"strings"
)
func ParseHeaderMap(raw string) map[string]string {
headers := make(map[string]string)
if raw == "" {
return headers
}
for _, pair := range strings.Split(raw, ",") {
kv := strings.SplitN(pair, ":", 2)
if len(kv) == 2 {
headers[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
}
}
return headers
}