Release 4.0.1+1: remove account binding / centralized provisioning (no panel credentials in binary; configs via user's own subscription URL); add recommended-services block on About page; Windows 4.0.1.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+78
-87
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -38,31 +39,29 @@ type App struct {
|
||||
}
|
||||
|
||||
type UIState struct {
|
||||
Connected bool `json:"connected"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
ActiveProfile string `json:"active_profile,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
HTTPProxy string `json:"http_proxy,omitempty"`
|
||||
SOCKSProxy string `json:"socks_proxy,omitempty"`
|
||||
SystemProxy bool `json:"system_proxy"`
|
||||
Proxy string `json:"proxy"`
|
||||
CoreReady bool `json:"core_ready"`
|
||||
CorePath string `json:"core_path,omitempty"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
Profiles []config.ProfileInfo `json:"profiles"`
|
||||
Version string `json:"version"`
|
||||
Update update.Status `json:"update"`
|
||||
Pings []netcheck.Result `json:"pings"`
|
||||
Subscription string `json:"subscription_url"`
|
||||
SubInfo *config.SubscriptionInfo `json:"sub_info,omitempty"`
|
||||
Remnawave remnawave.Settings `json:"remnawave"`
|
||||
Hy2 core.Hy2Options `json:"hy2"`
|
||||
StorePackaged bool `json:"store_packaged,omitempty"`
|
||||
ProvisionReady bool `json:"provision_ready,omitempty"`
|
||||
Account *remnawave.Account `json:"account,omitempty"`
|
||||
Mode string `json:"mode"`
|
||||
VPNSupported bool `json:"vpn_mode_supported"`
|
||||
VPNActive bool `json:"vpn_active"`
|
||||
Connected bool `json:"connected"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
ActiveProfile string `json:"active_profile,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
HTTPProxy string `json:"http_proxy,omitempty"`
|
||||
SOCKSProxy string `json:"socks_proxy,omitempty"`
|
||||
SystemProxy bool `json:"system_proxy"`
|
||||
Proxy string `json:"proxy"`
|
||||
CoreReady bool `json:"core_ready"`
|
||||
CorePath string `json:"core_path,omitempty"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
Profiles []config.ProfileInfo `json:"profiles"`
|
||||
Version string `json:"version"`
|
||||
Update update.Status `json:"update"`
|
||||
Pings []netcheck.Result `json:"pings"`
|
||||
Subscription string `json:"subscription_url"`
|
||||
SubInfo *config.SubscriptionInfo `json:"sub_info,omitempty"`
|
||||
Remnawave remnawave.Settings `json:"remnawave"`
|
||||
Hy2 core.Hy2Options `json:"hy2"`
|
||||
StorePackaged bool `json:"store_packaged,omitempty"`
|
||||
Mode string `json:"mode"`
|
||||
VPNSupported bool `json:"vpn_mode_supported"`
|
||||
VPNActive bool `json:"vpn_active"`
|
||||
}
|
||||
|
||||
type PingBestResult struct {
|
||||
@@ -74,20 +73,6 @@ type PingBestResult struct {
|
||||
}
|
||||
|
||||
func New(mgr *core.Manager, cfgPath string, logBuf *bytes.Buffer) *App {
|
||||
// Monthly plan upkeep: check at start and then periodically.
|
||||
mgr.StartAutoRenew(6 * time.Hour)
|
||||
// NordVPN-style: silently issue a panel account + import configs.
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
if !mgr.ProvisionConfigured() {
|
||||
return
|
||||
}
|
||||
if out, err := mgr.EnsureAutoProvision(); err != nil {
|
||||
fmt.Fprintf(logBuf, "auto-provision: %v\n", err)
|
||||
} else if out.Created {
|
||||
fmt.Fprintf(logBuf, "auto-provision: создан %s · серверов %d\n", out.Username, out.Imported)
|
||||
}
|
||||
}()
|
||||
return &App{
|
||||
Mgr: mgr,
|
||||
CfgPath: cfgPath,
|
||||
@@ -136,31 +121,29 @@ func (a *App) GetState() (UIState, error) {
|
||||
corePath, coreReady = path, true
|
||||
}
|
||||
out := UIState{
|
||||
Connected: st.Connected,
|
||||
Profile: st.Profile,
|
||||
ActiveProfile: active,
|
||||
Protocol: string(st.Protocol),
|
||||
HTTPProxy: st.HTTPProxy,
|
||||
SOCKSProxy: st.SOCKSProxy,
|
||||
SystemProxy: cfg.SystemProxy,
|
||||
Proxy: proxy,
|
||||
CoreReady: coreReady,
|
||||
CorePath: corePath,
|
||||
ConfigPath: a.CfgPath,
|
||||
Profiles: cfg.ListProfiles(),
|
||||
Version: update.DisplayVersion(),
|
||||
Update: a.UpdateStatus,
|
||||
Pings: append([]netcheck.Result(nil), a.Pings...),
|
||||
Subscription: cfg.SubscriptionURL,
|
||||
SubInfo: a.Mgr.SubscriptionInfo(),
|
||||
Remnawave: a.Mgr.RemnawaveSettings(),
|
||||
Hy2: a.Mgr.ActiveHy2Options(),
|
||||
StorePackaged: update.IsStorePackaged(),
|
||||
ProvisionReady: a.Mgr.ProvisionConfigured(),
|
||||
Account: a.Mgr.Account(),
|
||||
Mode: a.Mgr.Mode(),
|
||||
VPNSupported: a.Mgr.VPNModeSupported(),
|
||||
VPNActive: st.VPNActive,
|
||||
Connected: st.Connected,
|
||||
Profile: st.Profile,
|
||||
ActiveProfile: active,
|
||||
Protocol: string(st.Protocol),
|
||||
HTTPProxy: st.HTTPProxy,
|
||||
SOCKSProxy: st.SOCKSProxy,
|
||||
SystemProxy: cfg.SystemProxy,
|
||||
Proxy: proxy,
|
||||
CoreReady: coreReady,
|
||||
CorePath: corePath,
|
||||
ConfigPath: a.CfgPath,
|
||||
Profiles: cfg.ListProfiles(),
|
||||
Version: update.DisplayVersion(),
|
||||
Update: a.UpdateStatus,
|
||||
Pings: append([]netcheck.Result(nil), a.Pings...),
|
||||
Subscription: cfg.SubscriptionURL,
|
||||
SubInfo: a.Mgr.SubscriptionInfo(),
|
||||
Remnawave: a.Mgr.RemnawaveSettings(),
|
||||
Hy2: a.Mgr.ActiveHy2Options(),
|
||||
StorePackaged: update.IsStorePackaged(),
|
||||
Mode: a.Mgr.Mode(),
|
||||
VPNSupported: a.Mgr.VPNModeSupported(),
|
||||
VPNActive: st.VPNActive,
|
||||
}
|
||||
if out.Protocol == "" {
|
||||
if p, err := cfg.ActiveProfile(); err == nil {
|
||||
@@ -277,14 +260,6 @@ func (a *App) ImportRemnawave(s remnawave.Settings) (core.ImportResult, error) {
|
||||
return a.Mgr.ImportRemnawave(s)
|
||||
}
|
||||
|
||||
func (a *App) ProvisionUser(username string) (core.ProvisionOutcome, error) {
|
||||
return a.Mgr.ProvisionAccess(username)
|
||||
}
|
||||
|
||||
func (a *App) RenewAccount() (core.ProvisionOutcome, error) {
|
||||
return a.Mgr.RenewAccount()
|
||||
}
|
||||
|
||||
func (a *App) SetMode(mode string) error {
|
||||
return a.Mgr.SetMode(strings.TrimSpace(mode))
|
||||
}
|
||||
@@ -443,22 +418,42 @@ func (a *App) AutoCheckUpdate() {
|
||||
_, _ = a.CheckUpdate()
|
||||
}
|
||||
|
||||
func (a *App) OpenShopURL(raw string) error {
|
||||
// allowedLinkHosts are the only hosts the UI may open in the default browser
|
||||
// (shop button + «Рекомендуемые сервисы» block in index.html).
|
||||
var allowedLinkHosts = map[string]bool{
|
||||
"evilfox.win": true,
|
||||
"t.me": true,
|
||||
"de4ima.uk": true,
|
||||
"git.de4ima.uk": true,
|
||||
}
|
||||
|
||||
// LinkAllowed validates that raw is an http(s) URL pointing at one of the
|
||||
// allowed hosts; returns the normalized URL.
|
||||
func LinkAllowed(raw string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return fmt.Errorf("пустая ссылка")
|
||||
return "", fmt.Errorf("пустая ссылка")
|
||||
}
|
||||
lower := strings.ToLower(raw)
|
||||
switch {
|
||||
case lower == "https://evilfox.win/" || lower == "https://evilfox.win" ||
|
||||
lower == "http://evilfox.win/" || lower == "http://evilfox.win":
|
||||
raw = "https://evilfox.win/"
|
||||
case strings.HasPrefix(lower, "https://evilfox.win/") || strings.HasPrefix(lower, "http://evilfox.win/"):
|
||||
default:
|
||||
return fmt.Errorf("разрешена только ссылка evilfox.win")
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("некорректная ссылка")
|
||||
}
|
||||
if u.Scheme != "https" && u.Scheme != "http" {
|
||||
return "", fmt.Errorf("разрешены только http(s) ссылки")
|
||||
}
|
||||
if !allowedLinkHosts[strings.ToLower(u.Hostname())] {
|
||||
return "", fmt.Errorf("ссылка на %s не разрешена", u.Hostname())
|
||||
}
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (a *App) OpenShopURL(raw string) error {
|
||||
normalized, err := LinkAllowed(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.OpenURL != nil {
|
||||
return a.OpenURL(raw)
|
||||
return a.OpenURL(normalized)
|
||||
}
|
||||
return fmt.Errorf("openURL не настроен")
|
||||
}
|
||||
@@ -560,10 +555,6 @@ func (a *App) dispatch(name string, args []json.RawMessage) (any, error) {
|
||||
_ = json.Unmarshal(args[0], &s)
|
||||
}
|
||||
return a.ImportRemnawave(s)
|
||||
case "provisionUser":
|
||||
return a.ProvisionUser(arg(args, 0, ""))
|
||||
case "renewAccount":
|
||||
return a.RenewAccount()
|
||||
case "setMode":
|
||||
return nil, a.SetMode(arg(args, 0, ""))
|
||||
case "getLogs":
|
||||
|
||||
Reference in New Issue
Block a user