Skip to content
skywardpixelPublic

About

Yet Another Mihomo Interface — a minimal Windows task tray client for mihomo.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Yami for Windows

A minimal mihomo front-end. Three jobs: keep one subscription up to date, run the core, flip the system proxy. Everything a user would touch once a year stays in mihomo's config.

A Windows port of Yami, rewritten in C# — same product, same non-goals, same dozen failure modes the macOS version already paid for.

┌───────────────────────────────────────┐
│  CONNECTION                           │
│  Mihomo                        [ ●──] │
│  ● Running · port 7890                │
│  System proxy                  [ ●──] │
│  Routing          [ Loyalsoldier  ▾ ] │
│                                       │
│  SUBSCRIPTION                         │
│  ┌─────────────────────────────────┐  │
│  │ https://example.com/sub…     ↻  │  │
│  └─────────────────────────────────┘  │
│  Updated 2 hours ago                  │
│                                       │
│  APP                                  │
│  Start with Windows            [ ●──] │
│  View config                          │
│  Open log folder                      │
│  Exit Yami                            │
│  Yami 0.3.0 · mihomo 1.19.30          │
└───────────────────────────────────────┘

No administrator rights. Ever.

Not to install, not to run, not to proxy.

Changing the system proxy on Windows is a per-user, unelevated operation — a WinINET call and a few values under HKCU — so the entire privileged-helper apparatus the macOS version needs (a root daemon, XPC, mutual code-signing requirements, a hardcoded team ID) collapses into one class. The installer is per-user into %LOCALAPPDATA%\Programs\Yami. Nothing in this app ever raises a UAC prompt.

This is the strongest thing about the port, and it is why TUN mode stays a non-goal: mihomo's TUN mode needs wintun.dll and administrator rights, which would mean a service, an elevated broker, or a UAC prompt per launch.

What it does not do

The non-goals are the product. No node list, no rule editor, no traffic graph, no multiple profiles, no port setting, no themes, no TUN mode, no LAN sharing.

What the proxy switch actually covers

Yami writes the WinINET proxy settings, which reach Edge, Chrome, most Chromium apps, .NET's default handler, and anything else that asks Windows for proxy configuration.

They do not reach apps that open raw sockets or ship their own network stack: Firefox by default, many games, many Electron apps with explicit proxy settings, most CLI tools. This is a real limitation and it is narrower than what macOS's SCPreferences gets you. It is stated here rather than left for you to discover.

If Yami is killed while the proxy is on

Four defences run, in order of coverage: a graceful exit turns the proxy off before stopping the core; SessionEnding and ProcessExit cover sign-out, shutdown and most crashes; a sentinel file makes the next launch notice a dirty shutdown and undo it; and RegisterApplicationRestart asks Windows to relaunch Yami after a crash so that happens in seconds rather than at your next sign-in.

The residual gap is real: a hard kill followed by never relaunching leaves the proxy pointed at a core that is gone, and every WinINET-aware app loses its network. The fix is to relaunch Yami, or turn the proxy off in Settings ▸ Network & internet ▸ Proxy.

There is deliberately no watchdog process. It would reintroduce most of the complexity that deleting the privileged helper bought, to close a window RegisterApplicationRestart already covers.

Routing

Subscription The provider's rules and rule-providers, untouched
Loyalsoldier clash-rules — proxy by default, mainland China and LAN direct, ads rejected
Global A single MATCH sending everything through the provider's group

Loyalsoldier targets a group literally named PROXY, which no subscription is obliged to define, so Yami substitutes whatever group the provider's own MATCH rule points at.

"Global" is not mihomo's mode: global — that routes through the GLOBAL selector, whose selection defaults to DIRECT and does not persist without store-selected.

Changing routing re-renders from the cached subscription on disk, so it costs no round-trip and works offline.

What Yami overrides in your subscription

The provider's YAML is trusted for proxies, proxy-groups and rules. Everything that decides how Yami talks to the core is overridden:

mixed-port: 7890
allow-lan: false
mode: rule
log-level: warning
external-controller: ""
external-controller-pipe: \\.\pipe\dev.yami.<per-user>
tun:
  enable: false

And these are removed outright, because a provider that sets them opens listeners you were never told about: port, socks-port, redir-port, tproxy-port, external-controller-tls, external-controller-unix, secret, external-ui.

Every rendered config is validated with mihomo -t against a staging file before it is allowed to replace the one the core loads. A bad subscription can never take a working core down.

Install

Download the installer from Releases and run it. No UAC prompt.

SmartScreen will warn you — "Windows protected your PC". SmartScreen works on accumulated reputation rather than a check, and these builds are unsigned, so it warns harder than usual and names an unknown publisher. There is no way to pre-clear this. Choose More info ▸ Run anyway, and only from this repository's releases.

Antivirus false positives happen. A process that spawns a network daemon and rewrites proxy settings matches several heuristics, and an unsigned one matches them harder. If a scanner objects, the detection gets submitted to the vendor. See On signing for why nothing is signed yet and what would change that.

Bootstrapping

On a first run there is no config, so the core cannot start, so the proxy cannot be on, so the subscription fetch must go direct. If your provider is unreachable from that network without a proxy, there is no path from inside the app. This is a known, unsolved gap — the same one the macOS version has.

Where things live

%LOCALAPPDATA%\Programs\Yami\      the app and the bundled core
%LOCALAPPDATA%\Yami\
  config.yaml                      generated — subscription + Yami's overrides
  subscription.yaml                the provider's YAML as downloaded
  settings.json                    url, lastUpdated, routing, saved PAC url
  proxy.sentinel                   present while the proxy is believed on
  cache.db, geoip.metadb, ruleset\ mihomo's own
  logs\core.log

The subscription URL is stored in the clear. It embeds a token, so encrypting it is tempting — but config.yaml next to it holds every node's password in plaintext, so protecting the URL alone would buy nothing real.

config.yaml is generated and read-only. View config shows exactly what the core loaded; edits made to the file are lost on the next update.

Building

Needs the .NET 10 SDK. Windows only.

pwsh scripts/fetch-mihomo.ps1 -Pin

Downloads the pinned mihomo release into vendor\. Upstream publishes no checksums and no signatures, so the first pin is trust-on-first-use: -Pin records the artefact's SHA-256 into scripts/mihomo.sha256, and every run after that verifies against it. Commit that file.

pwsh scripts/build.ps1 -Installer

Runs the tests, publishes self-contained into dist\, copies the core and its licence in, and builds the Inno Setup installer. The version comes from git describe, so it cannot drift from the tag.

dotnet test --project tests/Yami.Core.Tests/Yami.Core.Tests.csproj

Yami.Core has no UI reference at all, which is what makes config rendering, routing, the subscription store, log condensing and the refresh policy testable without a UI thread.

dotnet test --project tests/Yami.Integration.Tests/Yami.Integration.Tests.csproj

The integration suite needs the real machine: it starts a real core, runs everything Yami renders past mihomo -t, and exercises the port guard and the job object. It skips rather than fails when vendor\mihomo.exe is missing or port 7890 is busy.

The system-proxy tests inside it are opt-in, because they write to HKCU\...\Internet Settings and briefly point this PC at 127.0.0.1:7890. A fixture saves every value they touch and restores it, including deleting values that did not exist before, so a failing test cannot leave your machine proxied:

YAMI_ALLOW_PROXY_TESTS=1 dotnet test --project tests/Yami.Integration.Tests/Yami.Integration.Tests.csproj

Automated releases

.github/workflows/ci.yml builds and tests every push and pull request. On main it publishes a rolling prerelease tagged canary, replaced by each new commit, with the installer renamed to a stable filename so its download URL never changes. Pushing a v* tag publishes a normal immutable release with a changelog since the previous v* tag.

Builds are currently unsigned, and the workflow publishes them anyway rather than shipping nothing. Each release says so in its own notes: an unsigned installer draws a harder SmartScreen warning and is likelier to trip antivirus heuristics, which is worth knowing before you run it and worth stating plainly rather than burying.

Signing is wired up but switched off, because it costs real money for a project with no install base yet — see the note below. Supply these repository secrets and the workflow signs Yami.exe, mihomo.exe and the installer, in that order, so the installer packages already-signed binaries:

AZURE_CLIENT_ID
AZURE_TENANT_ID              # presence of this one is what turns signing on
AZURE_SUBSCRIPTION_ID
AZURE_SIGNING_ENDPOINT
AZURE_SIGNING_ACCOUNT_NAME
AZURE_SIGNING_CERT_PROFILE

Login is federated (OIDC), so no client secret is stored. The Entra application needs a federated credential matching this repository and the Trusted Signing Certificate Profile Signer role on the profile.

On signing

Azure Trusted Signing is roughly $120/year, which buys convenience rather than trust: signing does not skip the SmartScreen warning — only an EV certificate does that, and only sort of. What it genuinely reduces is antivirus false positives, which a tool that spawns a network daemon and rewrites proxy settings will eventually attract.

Cheaper routes exist and are worth preferring: SignPath Foundation provides free signing for open-source projects, and Certum's open-source certificate runs about $35/year. The plan is to sign when false positives actually show up, not before.

Licence

Yami is licensed under the GNU General Public License v3.0 — see LICENSE.

It bundles mihomo v1.19.30, which is also GPL-3.0. Yami runs the core as a separate process rather than linking it, so the two are independent works, but bundling the binary is redistribution either way: the core's licence ships next to the executable as mihomo-LICENSE.txt and the exact tag is recorded in mihomo-VERSION.txt. Both are kept in step with any version bump.

(Not legal advice — just the thing not to discover after shipping.)

About

Yet Another Mihomo Interface — a minimal Windows task tray client for mihomo.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages