Release 4.0.0+1: rebrand Navis → EvilFox; centralized Remnawave auto-provisioning (panel credentials baked into binary); remove Remnawave API UI; Windows 4.0.0.1.

Keep dist/navis-release/ path and ship Navis.exe as a same-hash alias of EvilFox.exe so 3.x clients can still update from the Windows branch feed.
This commit is contained in:
Navis
2026-08-01 20:06:25 +03:00
parent e76c0a0977
commit 1144fab54d
32 changed files with 280 additions and 243 deletions
+13 -1
View File
@@ -76,6 +76,18 @@ 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,
@@ -410,7 +422,7 @@ func (a *App) ApplyUpdate() (string, error) {
os.Exit(0)
}()
}
return "Устанавливается v" + latest + "… Navis перезапустится.", nil
return "Устанавливается v" + latest + "… EvilFox перезапустится.", nil
}
func (a *App) AutoCheckUpdate() {
+11 -139
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Navis</title>
<title>EvilFox</title>
<script>
(function () {
try {
@@ -993,8 +993,8 @@
<path d="M22.5 8.2l3.2-1.1-1.1 3.2-5.6 5.6 1.6 1.6 5.6-5.6 3.2-1.1-1.1 3.2" fill="#b8f0d8" opacity=".9"/>
</svg>
</div>
<h1 class="brand">Navis</h1>
<span class="badge-ver" id="badgeVer">3.10.0</span>
<h1 class="brand">EvilFox</h1>
<span class="badge-ver" id="badgeVer">4.0.0</span>
</div>
<button class="nav-btn active" type="button" data-page="home">
@@ -1020,7 +1020,7 @@
<div class="spacer"></div>
<div class="side-foot">
<span class="ver" id="verLabel">Navis</span>
<span class="ver" id="verLabel">EvilFox</span>
<button type="button" class="theme-toggle" id="themeToggle" title="Тема" aria-label="Переключить тему">
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M20.5 14.2A8.2 8.2 0 0 1 9.8 3.5 8.5 8.5 0 1 0 20.5 14.2z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
@@ -1150,17 +1150,6 @@
</div>
</details>
<!-- Выдача доступа: скрыта, когда аккаунт уже привязан
(создание второго конфига отключено — платная подписка появится позже) -->
<div class="remna-box" id="provisionBox" hidden>
<div class="remna-title">Выдать доступ</div>
<p class="remna-hint">Создаёт пользователя в панели Remnawave (тариф из configs/remnawave-api.json, по умолчанию 50 ГБ / 30 дней), запоминает его и сразу импортирует конфиги. Повторно создать аккаунт нельзя — только продлевать.</p>
<div>
<label class="field" for="provUser">Пользователь</label>
<input id="provUser" type="text" placeholder="username / @telegram / email" spellcheck="false" autocomplete="off" />
</div>
<button class="action primary" id="provBtn" type="button">Выдать доступ</button>
</div>
</section>
<!-- ============ Настройки ============ -->
@@ -1173,7 +1162,7 @@
<button type="button" id="modeProxyBtn">Режим прокси</button>
<button type="button" id="modeVpnBtn">Режим VPN</button>
</div>
<p class="remna-hint" id="modeHint">Прокси — трафик идёт через локальный прокси (и системный прокси Windows). VPN — весь трафик, включая игры и приложения, через TUN-адаптер; нужны права администратора и wintun.dll рядом с Navis.exe. Меняется в отключённом состоянии.</p>
<p class="remna-hint" id="modeHint">Прокси — трафик идёт через локальный прокси (и системный прокси Windows). VPN — весь трафик, включая игры и приложения, через TUN-адаптер; нужны права администратора и wintun.dll рядом с EvilFox.exe. Меняется в отключённом состоянии.</p>
</div>
<div class="row">
@@ -1260,33 +1249,6 @@
<label class="check"><input id="hy2Lazy" type="checkbox" /> Lazy connect</label>
</details>
<details class="panel" id="rwPanel">
<summary>Remnawave API</summary>
<div class="remna-box" style="margin-top:8px;border:0;padding:0;background:transparent">
<p class="remna-hint">Панель → short UUID пользователя. Токен API — опционально (для /api/subscriptions/… и /users/…).</p>
<div>
<label class="field" for="rwBase">URL панели</label>
<input id="rwBase" type="text" placeholder="https://panel.example.com" spellcheck="false" />
</div>
<div>
<label class="field" for="rwShort">Short UUID</label>
<input id="rwShort" type="text" placeholder="abc12xyz" spellcheck="false" />
</div>
<div>
<label class="field" for="rwToken">API token</label>
<input id="rwToken" type="password" placeholder="Bearer из панели → API Tokens" spellcheck="false" autocomplete="off" />
</div>
<div>
<label class="field" for="rwCaddy">Caddy X-Api-Key</label>
<input id="rwCaddy" type="password" placeholder="если стоит auth у Caddy" spellcheck="false" autocomplete="off" />
</div>
<div class="sub-bar" style="margin-top:8px">
<button class="action secondary" id="rwSaveBtn" type="button" style="flex:1">Сохранить</button>
<button class="action primary" id="rwSyncBtn" type="button" style="flex:1">Синхронизировать</button>
</div>
</div>
</details>
<details class="panel" id="toolsBox">
<summary>Сервис · cores, обновления</summary>
<div class="tools">
@@ -1317,9 +1279,9 @@
<path d="M22.5 8.2l3.2-1.1-1.1 3.2-5.6 5.6 1.6 1.6 5.6-5.6 3.2-1.1-1.1 3.2" fill="#b8f0d8" opacity=".9"/>
</svg>
</div>
<h2>Navis</h2>
<h2>EvilFox</h2>
<p id="aboutVer">версия…</p>
<p style="margin-top:6px">VPN/прокси-клиент: Remnawave · Naive · Hysteria 2 · AWG · Xray</p>
<p style="margin-top:6px">VPN/прокси-клиент: Naive · Hysteria 2 · AWG · Xray</p>
</div>
<div class="tools" style="margin-top:0">
<button class="action secondary" id="aboutUpdBtn" type="button">Проверить обновление</button>
@@ -1498,15 +1460,6 @@
}
const subUrl = $("subUrl");
const subBtn = $("subBtn");
const rwBase = $("rwBase");
const rwShort = $("rwShort");
const rwToken = $("rwToken");
const rwCaddy = $("rwCaddy");
const rwSaveBtn = $("rwSaveBtn");
const rwSyncBtn = $("rwSyncBtn");
const provisionBox = $("provisionBox");
const provUser = $("provUser");
const provBtn = $("provBtn");
const hero = $("hero");
const protoChip = $("protoChip");
const heroHint = $("heroHint");
@@ -1572,7 +1525,7 @@
function markDirty() { dirty = true; }
[
proxy, nameInput, sysproxy, subUrl, rwBase, rwShort, rwToken, rwCaddy,
proxy, nameInput, sysproxy, subUrl,
$("hy2Congestion"), $("hy2Bbr"), $("hy2Up"), $("hy2Down"),
$("hy2Obfs"), $("hy2ObfsPass"), $("hy2Sni"), $("hy2Pin"), $("hy2Hop"),
$("hy2Insecure"), $("hy2Fast"), $("hy2Lazy")
@@ -1681,7 +1634,7 @@
if (!list.length) {
const empty = document.createElement("div");
empty.className = "server-empty";
empty.textContent = "Нет серверов — добавьте профиль или подписку";
empty.textContent = "Нет серверов — подождите автовыдачу или добавьте подписку";
serverList.appendChild(empty);
return;
}
@@ -1876,7 +1829,7 @@
}
function renderUpdate(u, version) {
const label = "Navis v" + (version || "?");
const label = "EvilFox v" + (version || "?");
verLabel.textContent = label;
if (aboutVer) aboutVer.textContent = "Версия " + (version || "?");
if (badgeVer && version) {
@@ -1941,16 +1894,6 @@
updateBtn.disabled = busy;
subBtn.disabled = busy;
subUrl.disabled = busy;
if (rwBase) rwBase.disabled = busy;
if (rwShort) rwShort.disabled = busy;
if (rwToken) rwToken.disabled = busy;
if (rwCaddy) rwCaddy.disabled = busy;
if (rwSaveBtn) rwSaveBtn.disabled = busy;
if (rwSyncBtn) rwSyncBtn.disabled = busy;
// Выдать доступ — только пока аккаунт НЕ привязан (второй не создаём).
if (provisionBox) provisionBox.hidden = !state.provision_ready || !!state.account;
if (provUser) provUser.disabled = busy;
if (provBtn) provBtn.disabled = busy;
if (renewBtn) renewBtn.disabled = busy || !state.provision_ready;
if (modeProxyBtn) modeProxyBtn.disabled = busy || connected;
if (modeVpnBtn) modeVpnBtn.disabled = busy || connected;
@@ -1965,12 +1908,6 @@
if (typeof state.subscription_url === "string" && !dirty) {
subUrl.value = state.subscription_url;
}
if (state.remnawave && !dirty) {
rwBase.value = state.remnawave.base_url || "";
rwShort.value = state.remnawave.short_uuid || "";
if (typeof state.remnawave.token === "string") rwToken.value = state.remnawave.token;
if (typeof state.remnawave.caddy_token === "string") rwCaddy.value = state.remnawave.caddy_token;
}
if (syncForm || (!formHydrated && !dirty)) {
const active = (state.profiles || []).find((p) => p.name === profile.value) || {};
@@ -2038,8 +1975,7 @@
function paintButtonsLocked(locked) {
[btn, coreBtn, saveBtn, addBtn, delBtn, profile, pingBtn, bestBtn, updCheckBtn, aboutUpdBtn,
updateBtn, skipUpdateBtn, subBtn, subUrl, rwBase, rwShort, rwToken, rwCaddy, rwSaveBtn, rwSyncBtn,
provUser, provBtn, renewBtn, modeProxyBtn, modeVpnBtn, logsRefreshBtn].forEach((b) => {
updateBtn, skipUpdateBtn, subBtn, subUrl, renewBtn, modeProxyBtn, modeVpnBtn, logsRefreshBtn].forEach((b) => {
if (b) b.disabled = locked;
});
}
@@ -2166,15 +2102,6 @@
return "Импортировано: " + r;
}
function readRemnawave() {
return {
base_url: (rwBase && rwBase.value || "").trim(),
short_uuid: (rwShort && rwShort.value || "").trim(),
token: (rwToken && rwToken.value || "").trim(),
caddy_token: (rwCaddy && rwCaddy.value || "").trim()
};
}
subBtn.addEventListener("click", () => withBusy(async () => {
try {
await runImportSubscription();
@@ -2187,61 +2114,6 @@
}
});
if (rwSaveBtn) {
rwSaveBtn.addEventListener("click", () => withBusy(async () => {
try {
await saveRemnawave(readRemnawave());
setMeta("Настройки Remnawave сохранены", "ok");
} catch (e) { setMeta(String(e), "err"); }
}));
}
if (rwSyncBtn) {
rwSyncBtn.addEventListener("click", () => withBusy(async () => {
try {
const s = readRemnawave();
if (!s.base_url || !s.short_uuid) {
setMeta("Укажите URL панели и short UUID", "err");
return;
}
setMeta("Remnawave: загрузка конфигов…");
const r = await importRemnawave(s);
formHydrated = false;
dirty = false;
setMeta("Remnawave: " + importSummary(r).toLowerCase() + " · пинг…", "ok");
await runBest(!!autoBest.checked);
} catch (e) { setMeta(String(e), "err"); }
}));
}
if (provBtn) {
provBtn.addEventListener("click", () => withBusy(async () => {
try {
const u = (provUser.value || "").trim();
if (!u) {
setMeta("Укажите имя пользователя (username / @telegram / email)", "err");
return;
}
setMeta("Выдача доступа через панель…");
const r = await provisionUser(u);
formHydrated = false;
dirty = false;
let msg = (r.created ? "Создан пользователь " : "Найден пользователь ") + r.username;
if (r.traffic_limit_bytes > 0) msg += " · " + fmtTraffic(r.traffic_limit_bytes);
if (r.expire_at) {
try { msg += " · до " + new Date(r.expire_at * 1000).toLocaleDateString("ru-RU"); } catch (_) {}
}
msg += " · импортировано серверов: " + (r.imported || 0);
setMeta(msg, "ok");
} catch (e) { setMeta(String(e), "err"); }
}));
provUser.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
provBtn.click();
}
});
}
if (renewBtn) {
renewBtn.addEventListener("click", () => withBusy(async () => {
try {
+45 -6
View File
@@ -298,10 +298,10 @@ func (m *Manager) SetMode(mode string) error {
return fmt.Errorf("режим VPN пока доступен только на Windows")
}
if !vpnmode.IsElevated() {
return fmt.Errorf("Запустите Navis от имени администратора для режима VPN")
return fmt.Errorf("Запустите EvilFox от имени администратора для режима VPN")
}
if !vpnmode.WintunAvailable() {
return fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с Navis.exe")
return fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с EvilFox.exe")
}
}
m.mu.Lock()
@@ -700,9 +700,12 @@ type ProvisionOutcome struct {
Skipped int `json:"skipped"`
}
// ProvisionConfigured reports whether configs/remnawave-api.json exists —
// the admin "Выдать доступ" panel is shown only in that case.
// ProvisionConfigured reports whether the Remnawave admin API is available
// (embedded credentials or configs/remnawave-api.json). Used for renew UI.
func (m *Manager) ProvisionConfigured() bool {
if remnawave.EmbeddedAdminConfig() != nil {
return true
}
st, err := os.Stat(remnawave.AdminConfigPath(m.cfgPath))
return err == nil && !st.IsDir()
}
@@ -735,10 +738,46 @@ func (m *Manager) ProvisionAccess(username string) (ProvisionOutcome, error) {
if acc := m.Account(); acc != nil {
return ProvisionOutcome{}, fmt.Errorf("аккаунт уже привязан (%s) — новый создавать нельзя, используйте «Продлить»", acc.Username)
}
cfg, err := remnawave.LoadAdminConfig(remnawave.AdminConfigPath(m.cfgPath))
cfg, err := remnawave.ResolveAdminConfig(m.cfgPath)
if err != nil {
return ProvisionOutcome{}, err
}
return m.provisionWithConfig(cfg, username)
}
// EnsureAutoProvision silently creates a panel user (if none remembered yet)
// and imports its subscription configs. NordVPN-style: panel address / API
// token are embedded; the user never edits them. Safe to call on every start.
func (m *Manager) EnsureAutoProvision() (ProvisionOutcome, error) {
if acc := m.Account(); acc != nil {
// Already provisioned — refresh configs in the background if empty.
if len(m.Profiles()) == 0 && acc.SubscriptionURL != "" {
if imp, err := m.ImportSubscription(acc.SubscriptionURL); err == nil {
return ProvisionOutcome{
Username: acc.Username,
SubscriptionURL: acc.SubscriptionURL,
TrafficLimitBytes: acc.TrafficLimitBytes,
ExpireAt: acc.ExpireAt,
Imported: imp.Imported,
Skipped: imp.Skipped,
}, nil
}
}
return ProvisionOutcome{
Username: acc.Username,
SubscriptionURL: acc.SubscriptionURL,
TrafficLimitBytes: acc.TrafficLimitBytes,
ExpireAt: acc.ExpireAt,
}, nil
}
cfg, err := remnawave.ResolveAdminConfig(m.cfgPath)
if err != nil {
return ProvisionOutcome{}, err
}
return m.provisionWithConfig(cfg, remnawave.GenerateUsername())
}
func (m *Manager) provisionWithConfig(cfg *remnawave.AdminConfig, username string) (ProvisionOutcome, error) {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
res, err := remnawave.ProvisionUser(ctx, cfg, username)
@@ -791,7 +830,7 @@ func (m *Manager) RenewAccount() (ProvisionOutcome, error) {
if acc == nil {
return ProvisionOutcome{}, fmt.Errorf("нет привязанного аккаунта — сначала выдайте доступ")
}
cfg, err := remnawave.LoadAdminConfig(remnawave.AdminConfigPath(m.cfgPath))
cfg, err := remnawave.ResolveAdminConfig(m.cfgPath)
if err != nil {
return ProvisionOutcome{}, err
}
+1 -1
View File
@@ -183,7 +183,7 @@ func doGET(ctx context.Context, client *http.Client, s Settings, endpoint string
if err != nil {
return "", nil, err
}
req.Header.Set("User-Agent", "Navis/3.9 (Remnawave)")
req.Header.Set("User-Agent", "EvilFox/4.0 (Remnawave)")
req.Header.Set("Accept", "*/*")
if auth && s.Token != "" {
req.Header.Set("Authorization", "Bearer "+s.Token)
+51
View File
@@ -0,0 +1,51 @@
package remnawave
// Centralized (NordVPN-style) provisioning: the panel address and API token
// are baked into the binary at build time, so every user gets configs
// automatically and cannot change the panel.
//
// build.bat copies configs/remnawave-api.json into embeddedcfg/ before
// `go build`; the copy is gitignored so credentials never land in the repo.
// Without the embedded file the app falls back to the external
// configs/remnawave-api.json (developer mode).
import (
"crypto/rand"
"embed"
"encoding/hex"
"fmt"
"os"
"time"
)
//go:embed embeddedcfg
var embeddedCfgFS embed.FS
// EmbeddedAdminConfig returns the admin config baked into the binary,
// or nil when this build has no embedded credentials.
func EmbeddedAdminConfig() *AdminConfig {
data, err := embeddedCfgFS.ReadFile("embeddedcfg/remnawave-api.json")
if err != nil {
return nil
}
cfg, err := parseAdminConfig(data)
if err != nil {
return nil
}
return cfg
}
// GenerateUsername makes a unique panel username for silent auto-provisioning
// (users never type anything): "fox_" + 10 hex chars, e.g. fox_3fa9c02b71.
func GenerateUsername() string {
var b [5]byte
if _, err := rand.Read(b[:]); err != nil {
// Keep within Remnawave's 634 char username limit.
s := fmt.Sprintf("fox_%x%x", os.Getpid()&0xffff, time.Now().UnixNano()&0xffffffff)
if len(s) > 34 {
s = s[:34]
}
return s
}
return "fox_" + hex.EncodeToString(b[:])
}
@@ -0,0 +1,3 @@
{
"_comment": "build.bat overwrites remnawave-api.json here from configs/ before go build. This placeholder keeps the embed directory non-empty for go build without credentials."
}
+15 -1
View File
@@ -47,12 +47,26 @@ func AdminConfigPath(cfgPath string) string {
return filepath.Join(filepath.Dir(cfgPath), "remnawave-api.json")
}
// ResolveAdminConfig prefers the baked-in (embedded) panel credentials;
// falls back to configs/remnawave-api.json for local/developer builds.
func ResolveAdminConfig(cfgPath string) (*AdminConfig, error) {
if cfg := EmbeddedAdminConfig(); cfg != nil {
return cfg, nil
}
return LoadAdminConfig(AdminConfigPath(cfgPath))
}
// LoadAdminConfig reads and validates the admin API config file.
func LoadAdminConfig(path string) (*AdminConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("нет файла %s (скопируйте remnawave-api.example.json и вставьте ключ)", filepath.Base(path))
}
return parseAdminConfig(data)
}
// parseAdminConfig validates raw remnawave-api.json bytes (file or embedded).
func parseAdminConfig(data []byte) (*AdminConfig, error) {
var cfg AdminConfig
if err := json.Unmarshal(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF}), &cfg); err != nil {
return nil, fmt.Errorf("remnawave-api.json: некорректный JSON: %w", err)
@@ -252,7 +266,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.10 (Remnawave provision)")
req.Header.Set("User-Agent", "EvilFox/4.0 (Remnawave provision)")
req.Header.Set("Accept", "application/json")
if body != nil {
req.Header.Set("Content-Type", "application/json")
+1 -1
View File
@@ -57,7 +57,7 @@ var (
// for a generic UA, but return a Clash YAML config for Clash-like UAs — that
// YAML loses vless/trojan entries for us, so try the plain UA first.
var userAgents = []string{
"Navis/3.9 (+https://evilfox.win)",
"EvilFox/4.0 (+https://evilfox.win)",
"ClashMeta/1.18.0",
}
+4 -4
View File
@@ -18,11 +18,11 @@ import (
// CurrentVersion is the product/semver used for update eligibility (feed "version").
// Keep major.minor.patch only — no build suffix here.
const CurrentVersion = "3.10.0"
const CurrentVersion = "4.0.0"
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
const BuildNumber = 2
const BuildNumber = 1
// DefaultManifestURL is the update feed (hosted in the project git repo).
const DefaultManifestURL = "https://git.de4ima.uk/Evilfox/navi/raw/branch/Windows/dist/update.json"
@@ -287,7 +287,7 @@ func fetchManifest(ctx context.Context, manifestURL string) (Manifest, error) {
if err != nil {
return Manifest{}, err
}
req.Header.Set("User-Agent", "Navis/"+DisplayVersion())
req.Header.Set("User-Agent", "EvilFox/"+DisplayVersion())
req.Header.Set("Accept", "application/json")
client := &http.Client{Timeout: 20 * time.Second}
resp, err := client.Do(req)
@@ -310,7 +310,7 @@ func downloadFile(ctx context.Context, url, dest string) error {
if err != nil {
return err
}
req.Header.Set("User-Agent", "Navis/"+DisplayVersion())
req.Header.Set("User-Agent", "EvilFox/"+DisplayVersion())
client := &http.Client{Timeout: 10 * time.Minute}
resp, err := client.Do(req)
if err != nil {
+7 -3
View File
@@ -16,9 +16,12 @@ import (
"golang.org/x/sys/windows"
)
// finishUpdateFlag must stay "--navis-finish-update" forever: older installed
// builds (3.x) launch the freshly downloaded binary with exactly this flag,
// so renaming it would break their update handoff.
const finishUpdateFlag = "--navis-finish-update"
// Apply downloads the new build and replaces the running Navis.exe after this process exits.
// Apply downloads the new build and replaces the running EvilFox.exe after this process exits.
func Apply(ctx context.Context, manifestURL string) (string, error) {
if IsStorePackaged() {
return "", fmt.Errorf("обновления устанавливаются через Microsoft Store")
@@ -27,7 +30,7 @@ func Apply(ctx context.Context, manifestURL string) (string, error) {
if err != nil {
return "", err
}
pending := filepath.Join(filepath.Dir(exe), "Navis-pending.exe")
pending := filepath.Join(filepath.Dir(exe), "EvilFox-pending.exe")
_ = os.Remove(pending)
if err := os.Rename(tmp, pending); err != nil {
if err2 := copyFile(tmp, pending); err2 != nil {
@@ -58,7 +61,7 @@ func Apply(ctx context.Context, manifestURL string) (string, error) {
return latest, nil
}
// MaybeFinishUpdate handles: Navis-pending.exe --navis-finish-update <pid> <targetExe>
// MaybeFinishUpdate handles: EvilFox-pending.exe --navis-finish-update <pid> <targetExe>
// Replaces targetExe in-place with this binary, then relaunches it.
func MaybeFinishUpdate(args []string) bool {
if len(args) < 3 || args[0] != finishUpdateFlag {
@@ -109,6 +112,7 @@ func CleanupStaleDownloads() {
_ = os.Remove(filepath.Join(dir, base+".new"))
_ = os.Remove(filepath.Join(dir, "Navis-update.part"))
_ = os.Remove(filepath.Join(dir, ".navis-update-download"))
_ = os.Remove(filepath.Join(dir, "Navis-pending.exe"))
}
func replaceExecutable(src, dst string) error {
+2 -2
View File
@@ -86,10 +86,10 @@ func Start(cfg Config) (*Session, error) {
return nil, fmt.Errorf("режим VPN: нет локального SOCKS-прокси у текущего протокола")
}
if !IsElevated() {
return nil, fmt.Errorf("Запустите Navis от имени администратора для режима VPN")
return nil, fmt.Errorf("Запустите EvilFox от имени администратора для режима VPN")
}
if !WintunAvailable() {
return nil, fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с Navis.exe")
return nil, fmt.Errorf("не найден wintun.dll — скачайте с wintun.net и положите рядом с EvilFox.exe")
}
if len(cfg.DNSServers) == 0 {
cfg.DNSServers = []string{"1.1.1.1", "8.8.8.8"}