Release 3.9.0+1: Remnawave API configs, Remnawave-style UI with flags, Windows build.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-08-01 16:17:10 +03:00
co-authored by Cursor
parent 92cf707597
commit 015ded9bd5
24 changed files with 985 additions and 159 deletions
+82 -1
View File
@@ -19,6 +19,7 @@ import (
"vpnclient/internal/protocols/hysteria2"
"vpnclient/internal/protocols/naive"
"vpnclient/internal/protocols/xray"
"vpnclient/internal/remnawave"
"vpnclient/internal/subscription"
"vpnclient/internal/sysproxy"
)
@@ -417,20 +418,100 @@ func (m *Manager) SubscriptionURL() string {
return m.cfg.SubscriptionURL
}
func (m *Manager) RemnawaveSettings() remnawave.Settings {
m.mu.Lock()
defer m.mu.Unlock()
return remnawave.Settings{
BaseURL: m.cfg.RemnawaveBaseURL,
Token: m.cfg.RemnawaveToken,
ShortUUID: m.cfg.RemnawaveShortUUID,
CaddyToken: m.cfg.RemnawaveCaddyToken,
}
}
func (m *Manager) SaveRemnawaveSettings(s remnawave.Settings) error {
m.mu.Lock()
defer m.mu.Unlock()
m.cfg.RemnawaveBaseURL = remnawave.NormalizeBase(s.BaseURL)
m.cfg.RemnawaveToken = strings.TrimSpace(s.Token)
m.cfg.RemnawaveShortUUID = strings.TrimSpace(s.ShortUUID)
m.cfg.RemnawaveCaddyToken = strings.TrimSpace(s.CaddyToken)
return config.Save(m.cfgPath, *m.cfg)
}
func (m *Manager) ImportSubscription(rawURL string) (int, error) {
rawURL = strings.TrimSpace(rawURL)
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
// Remnawave-style URL → persist panel coords and fetch via API helper.
if base, short, ok := remnawave.ParseInput(rawURL); ok {
s := remnawave.Settings{
BaseURL: base,
ShortUUID: short,
Token: m.RemnawaveSettings().Token,
CaddyToken: m.RemnawaveSettings().CaddyToken,
}
items, used, err := remnawave.Fetch(ctx, s)
if err != nil {
return 0, err
}
return m.applySubscriptionItems(items, used, &s)
}
items, err := subscription.Fetch(ctx, rawURL)
if err != nil {
return 0, err
}
return m.applySubscriptionItems(items, rawURL, nil)
}
// ImportRemnawave fetches configs using saved / provided panel settings.
func (m *Manager) ImportRemnawave(s remnawave.Settings) (int, error) {
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
defer cancel()
if strings.TrimSpace(s.BaseURL) == "" || strings.TrimSpace(s.ShortUUID) == "" {
cur := m.RemnawaveSettings()
if strings.TrimSpace(s.BaseURL) == "" {
s.BaseURL = cur.BaseURL
}
if strings.TrimSpace(s.ShortUUID) == "" {
s.ShortUUID = cur.ShortUUID
}
if strings.TrimSpace(s.Token) == "" {
s.Token = cur.Token
}
if strings.TrimSpace(s.CaddyToken) == "" {
s.CaddyToken = cur.CaddyToken
}
}
items, used, err := remnawave.Fetch(ctx, s)
if err != nil {
return 0, err
}
return m.applySubscriptionItems(items, used, &s)
}
func (m *Manager) applySubscriptionItems(items []subscription.Item, usedURL string, rw *remnawave.Settings) (int, error) {
if len(items) == 0 {
return 0, fmt.Errorf("в подписке нет поддерживаемых ссылок (naive / hysteria2 / awg / vless / vmess / trojan)")
}
m.mu.Lock()
defer m.mu.Unlock()
m.cfg.SubscriptionURL = strings.TrimSpace(rawURL)
if usedURL != "" {
m.cfg.SubscriptionURL = strings.TrimSpace(usedURL)
}
if rw != nil {
m.cfg.RemnawaveBaseURL = remnawave.NormalizeBase(rw.BaseURL)
m.cfg.RemnawaveShortUUID = strings.TrimSpace(rw.ShortUUID)
if t := strings.TrimSpace(rw.Token); t != "" {
m.cfg.RemnawaveToken = t
}
if t := strings.TrimSpace(rw.CaddyToken); t != "" {
m.cfg.RemnawaveCaddyToken = t
}
}
for _, it := range items {
_ = m.cfg.UpsertProfileWithProtocol(it.Name, it.URI, it.Protocol)
for i := range m.cfg.Profiles {