feat(tailscale): add opt-in Tailscale outbound nodes and bypass Inject Tailscale proxies, route rules, and fake-IP filters into the generated Clash config when enabled - #2270
Conversation
f6ed70e to
11c4838
Compare
9b2bac7 to
f39c538
Compare
Workaround: Tailscale works on FlClash 0.8.94 via direct config editConfirming that does work on FlClash 0.8.94 (Android), but only when the Tailscale proxy is defined in the main config file directly — NOT via the script override (覆写脚本). What doesn't work
What worksEdit the active config file directly (), add the Tailscale proxy alongside your existing proxies: proxies:
# ... your existing proxies ...
- name: TAILSCALE
type: tailscale
hostname: my-phone
auth-key: tskey-auth-xxxx # generate at tailscale.com admin > Keys
control-url: https://controlplane.tailscale.com
state-dir: ./tailscale
ephemeral: true
udp: true
accept-routes: true
ip-version: ipv4-prefer
proxy-groups:
- name: PROXY
type: select
proxies:
- TAILSCALE
- YourExistingProxy
- DIRECT
rules:
- IP-CIDR,100.64.0.0/10,PROXY,no-resolve
- IP-CIDR,100.100.100.100/32,PROXY,no-resolve
- DOMAIN-SUFFIX,ts.net,PROXY
- MATCH,PROXYVerified
CaveatThis workaround requires editing the main config directly, which gets overwritten on subscription sync. Looking forward to #2270 landing so it can be configured via the GUI natively! |
Workaround: Tailscale works on FlClash 0.8.94 via direct config editConfirming that What doesn't work
What worksEdit the active config file directly ( proxies:
# ... your existing proxies ...
- name: TAILSCALE
type: tailscale
hostname: my-phone
auth-key: tskey-auth-xxxx # generate at tailscale.com admin > Keys
control-url: https://controlplane.tailscale.com
state-dir: ./tailscale
ephemeral: true
udp: true
accept-routes: true
ip-version: ipv4-prefer
proxy-groups:
- name: PROXY
type: select
proxies:
- TAILSCALE
- YourExistingProxy
- DIRECT
rules:
- IP-CIDR,100.64.0.0/10,PROXY,no-resolve
- IP-CIDR,100.100.100.100/32,PROXY,no-resolve
- DOMAIN-SUFFIX,ts.net,PROXY
- MATCH,PROXYVerified
CaveatThis workaround requires editing the main config directly, which gets overwritten on subscription sync. Looking forward to #2270 landing so it can be configured via the GUI natively! |
183a213 to
cc28835
Compare
f39c538 to
f353cdf
Compare
|
Thanks for the detailed workaround and for confirming that type: tailscale works when it lands in the profile / runtime config that Mihomo actually loads. How this PR injects Tailscale (not via 覆写脚本) Load the profile (getConfig) When it should work Happy to adjust docs/defaults (e.g. suggest udp / ephemeral for Android) if maintainers want that. |
f353cdf to
f00235d
Compare
1e44a54 to
5b17883
Compare
c2df44b to
52f72ed
Compare
8179499 to
d966b18
Compare
Rebase Tailscale outbound integration onto current upstream dev: - Keep Tailscale profile injection in setup action and Tools menu entry - Adopt upstream desktop lifecycle, Android service refactor, and helper v6 - Update Clash.Meta submodule to upstream dev pointer - Regenerate Riverpod, Freezed, and l10n outputs after conflict resolution Co-authored-by: MichaelZ <joei4cm@users.noreply.github.com>
Changelog: Rebuilt the core, provider, and application layers Changelog-Type: feat
Changelog: Rebuilt the interface on material_ui and simplified the overwrite flow Changelog-Type: feat
Refines the profile, provider, and configuration interactions, virtualizes the provider lists so a large subscription no longer builds every row, syncs updates through the path the list reads, and expands the subscription details with full usage. Paged sheets are scoped locally and sized to the page in view instead of the tallest one, so a sheet no longer reserves space it never uses. Changelog: Provider lists scroll smoothly, subscriptions show full usage details, and sheets size to the page in view
…ghten the build Crash recovery decides from a boot sentinel rather than Crashlytics, so it no longer depends on a reporting SDK having initialized. The window header is built into the window manager fork instead of being reassembled by the app, and the toolchain tightens dependency pins, plugin analysis, CI, and the host-agnostic tests. Changelog: Recovery after a crash no longer waits on the crash reporter to start Changelog-Type: fix
…e when narrow The elevated Core no longer leaves root-owned files behind on macOS, and provider files stay inside the profile that owns them. Cards, form rows, and narrow layouts stay usable at small widths. Changelog: Elevated runs and profile providers stop leaving files outside their owner, and narrow windows stay usable
…d refused starts Ownership reclaim records dev+ino during the walk and refuses to chown a descriptor whose fstat does not match, closing the rename-and-symlink window O_NOFOLLOW alone left open. A half-written IPC frame that hits the write deadline now rearms and resumes instead of closing the connection, so a suspended host no longer reads as EOF and exits the Core. An overtaking Android start adopts the service that is already up rather than establishing a second interface, and the exit-reason lookup reads every record so a sub-process crash loop cannot evict the main-process one. setRunning and toggleRunning restore the state the request started from and report the failure instead of leaving the app claiming it runs. Changelog: Stalls, double starts, and refused starts no longer take the tunnel down
… tray titles Backup names its temp files explicitly and deletes the archive once the upload or save is done, so it no longer leaves a database copy in the OS temp directory. Proxy group offsets are paired with the groups they were measured from, so jumping to a group while a search is active lands on the right row instead of a RangeError. Map input rows key on their key rather than the MapEntry identity, which does not implement ==, restoring element reuse. The tray collapses titles that queued behind a blocked native call, and putProfileDisabledRule stores into the disabled scene it names. Changelog: Backups clean up their temp files, group jumps land right while searching, and the tray stops replaying stale speeds
…hat loaded The tray menu, the strings the Android service shows on its VPN notification, and the launcher shortcut label all come from Intl, and each was pushed ahead of its own translation. The tray rebuilt on TrayState.locale and sharedState watched the locale setting, both of which change before AppLocalizations.load has swapped Intl.defaultLocale, so every switch sent the previous language: the first one looked like nothing happened, and each one after it showed the language left behind. initShortcuts was worse off, since it runs once at startup and kept the language the app was launched in until the next restart. LocaleManager now publishes the locale Localizations exposes, which only advances once every delegate has loaded, into loadedLocaleProvider, and everything derived from Intl outside the widget tree hangs off that one signal: the tray listens to it, sharedState watches it, and Android re-pushes the shortcut label from it. TrayState drops the locale field that was only ever a change signal. Following the system language is covered by the same path, and a selection that resolves to the language already loaded no longer forces a rebuild. Changelog: A language switch now reaches the tray menu, VPN notification, and shortcut label
…nimation The group header shows its type beside the expand toggle, is tightened up, and the group icon gets room inside its container. Cards animate when their group expands rather than when the list builds them, and they slide in from the left with a per-card delay and a distance that grows with the index, so a wide group no longer appears as one block. Changelog: Proxy groups show their type and their cards slide in one after another when expanded
The indicator carried its own approximation of the M3E shape sequence: seven StarBorder specs lerped field by field, so a morph interpolated point counts and rounding rather than the outlines themselves, and the pill and the oval had to be faked with width and height scales. material_new_shapes ships that sequence as RoundedPolygon and morphs between two of them along their real outlines, so the shapes are the M3E ones and the transition follows the path between them. The morphs are built once per polygon list and reused across frames, since building one walks both polygons. polygons now takes RoundedPolygon. Changelog: skip
The popup faded and scaled in place from its top right corner while the target it belongs to sat unchanged underneath, so the two read as unrelated pieces of the screen. It now starts at the anchor at the target's size, winds out to where it settles, and grows to full size along the way. A copy of the target rides at the anchor and fades out as the popup takes over, while the real one is hidden behind a Visibility that keeps its size, so nothing around it shifts while the popup is up. The route reports its dispose back to the box, which restores the target after a barrier dismiss, a pick, or a pop from anywhere else. Both durations grow to leave room for the travel. Changelog: Popup menus grow out of the button that opened them
…r icon release.sh resolves the version the same way for pre and stable, bumping the patch when pubspec still names an already tagged one, prints the notes the tag would ship, and pushes only with --push. The two paths differ in more than the tag: a prerelease renders its notes in CI from build --unreleased and writes nothing back, while a stable release needs CHANGELOG.md and changelog.json regenerated and committed before the tag, because render release reads the committed file for a stable tag. The pubspec rewrite has to happen before the preview, since build --unreleased reads pubspec rather than the tag, so an EXIT trap restores it on any exit that is not a completed release. The tag is pushed by name instead of with --follow-tags, which carries annotated tags only while every release tag here is lightweight, so the branch used to land while the tag stayed local and the workflow never fired. The confirmation reads from /dev/tty and re-asks rather than treating an empty answer as No, since rendering the preview takes seconds and a Return pressed while waiting sits in the terminal buffer until the prompt consumes it. The bash that ships with macOS is 3.2, which has neither a fractional read timeout nor a way to flush stdin, so re-asking is the portable fix. The build number is refreshed on every release rather than only when the app version changes: it feeds Android's versionCode, and two builds carrying the same one cannot be told apart once installed. The app version still holds across a cycle, because build --unreleased keys its commit range off it. setup_icon_file loses its leading ..\, since the packager joins that value onto Directory.current before handing it to Inno Setup and the old path landed a level above the repository. The locale entries are copied into the generated .iss verbatim and stay relative to dist/, where that script is written. Changelog: skip
Re-apply Tailscale outbound nodes and bypass on upstream/dev (5f659cd). Resolve conflicts in setup/task injection, ARB strings, and tools entry. Adapt tailscale view to upstream dialog and delay-test APIs. Co-authored-by: MichaelZ <joei4cm@users.noreply.github.com>
c8ca65e to
5a41848
Compare
b0a2b3b to
7748c64
Compare
This pull request introduces full Tailscale outbound node support, including configuration, routing, and localization updates. The main changes add Tailscale-related strings to all supported languages, update the configuration generation logic to inject Tailscale proxy and rule settings, and enhance the English message lookup to support new UI elements.
Tailscale feature support and configuration:
_makeRealProfileTask(lib/common/task.dart) to inject Tailscale fake-IP DNS filters and prepend Tailscale rules, ensuring Tailscale traffic is handled correctly and with priority. Also merges Tailscale proxies into the final config. [1] [2]Localization and UI strings:
lib/l10n/intl/messages_en.dart) to include new Tailscale UI strings and pluralization logic for nodes and routes. [1] [2] [3] [4] [5] [6]These changes collectively enable Tailscale outbound node management in the app, with proper configuration merging and full localization support.