Compare commits

...

10 Commits

Author SHA1 Message Date
sijie.sun cca105e91d add chinese help message 2024-08-08 08:05:42 +08:00
sijie.sun 3e52490d1b update dep and bump version 2024-08-07 23:48:03 +08:00
sijie.sun d1293276ce use tun2
meh/rust-tun has bug on windows and may stuck on large traffic.
2024-08-07 15:58:23 +08:00
sijie.sun 4a5e426730 fix android route and peer conn disconn bug
1. android correctly add route
2. use mock nic ctx to consume packets
2024-08-06 22:19:30 +08:00
sijie.sun fdc2755291 fix set dev name not work on windows 2024-08-05 11:47:43 +08:00
sijie.sun b4fbcd8d80 use random ip from dns record instead of only first one 2024-08-04 23:42:33 +08:00
sijie.sun 2415cb211e run on v7 need rustc < 1.78 2024-08-04 11:07:24 +08:00
sijie.sun 5e51784803 support unicode hostname 2024-08-04 11:07:24 +08:00
Sijie.Sun 5f0d71b0fe check trigger user for docker action (#219) 2024-08-04 00:37:58 +08:00
sijie.sun 71d41f0a70 introduce docker image updater 2024-08-04 00:28:34 +08:00
28 changed files with 3072 additions and 2254 deletions
+35
View File
@@ -0,0 +1,35 @@
FROM alpine:latest AS builder
ARG TARGETPLATFORM
COPY . /tmp/artifacts
RUN mkdir -p /tmp/output; \
cd /tmp/artifacts; \
ARTIFACT_ARCH=""; \
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
ARTIFACT_ARCH="x86_64"; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
ARTIFACT_ARCH="aarch64"; \
else \
echo "Unsupported architecture: $TARGETARCH"; \
exit 1; \
fi; \
cp /tmp/artifacts/easytier-linux-${ARTIFACT_ARCH}/* /tmp/output;
FROM alpine:latest
WORKDIR /app
COPY --from=builder --chmod=755 /tmp/output/* /usr/local/bin
# tcp
EXPOSE 11010/tcp
# udp
EXPOSE 11010/udp
# wg
EXPOSE 11011/udp
# ws
EXPOSE 11011/tcp
# wss
EXPOSE 11012/tcp
ENTRYPOINT ["easytier-core"]
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
# All of these options are optional, so you can remove them if you are happy with the defaults # All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never' concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true' skip_after_successful_duplicate: 'true'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/core.yml"]' paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/core.yml", ".github/workflows/install_rust.sh"]'
build: build:
strategy: strategy:
fail-fast: false fail-fast: false
+61
View File
@@ -0,0 +1,61 @@
name: EasyTier Docker
on:
workflow_dispatch:
inputs:
run_id:
description: 'The run id of EasyTier-Core Action in EasyTier repo'
type: number
default: 10228239965
required: true
image_tag:
description: 'Tag for this image build'
type: string
default: 'v1.2.0'
required: true
mark_latest:
description: 'Mark this image as latest'
type: boolean
default: false
required: true
jobs:
docker:
if: contains('["KKRainbow"]', github.actor)
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Download artifact
id: download-artifact
uses: dawidd6/action-download-artifact@v6
with:
github_token: ${{secrets.GITHUB_TOKEN}}
run_id: ${{ inputs.run_id }}
repo: EasyTier/EasyTier
path: docker_context
- name: List files
run: |
ls -l -R .
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: ./docker_context
platforms: linux/amd64,linux/arm64
push: true
file: .github/workflows/Dockerfile
tags: easytier/easytier:${{ inputs.image_tag }}${{ inputs.mark_latest && ',easytier/easytier:latest' || '' }},
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
# All of these options are optional, so you can remove them if you are happy with the defaults # All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never' concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true' skip_after_successful_duplicate: 'true'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", ".github/workflows/gui.yml"]' paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", ".github/workflows/gui.yml", ".github/workflows/install_rust.sh"]'
build-gui: build-gui:
strategy: strategy:
fail-fast: false fail-fast: false
+2 -3
View File
@@ -57,8 +57,8 @@ fi
# see https://github.com/rust-lang/rustup/issues/3709 # see https://github.com/rust-lang/rustup/issues/3709
rustup set auto-self-update disable rustup set auto-self-update disable
rustup install 1.79 rustup install 1.77
rustup default 1.79 rustup default 1.77
# mips/mipsel cannot add target from rustup, need compile by ourselves # mips/mipsel cannot add target from rustup, need compile by ourselves
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
@@ -72,7 +72,6 @@ if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
rustup toolchain install nightly-x86_64-unknown-linux-gnu rustup toolchain install nightly-x86_64-unknown-linux-gnu
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
cd -
else else
rustup target add $TARGET rustup target add $TARGET
if [[ $GUI_TARGET != '' ]]; then if [[ $GUI_TARGET != '' ]]; then
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
# All of these options are optional, so you can remove them if you are happy with the defaults # All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never' concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true' skip_after_successful_duplicate: 'true'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", "tauri-plugin-vpnservice/**", ".github/workflows/mobile.yml"]' paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", "tauri-plugin-vpnservice/**", ".github/workflows/mobile.yml", ".github/workflows/install_rust.sh"]'
build-mobile: build-mobile:
strategy: strategy:
fail-fast: false fail-fast: false
Generated
+694 -526
View File
File diff suppressed because it is too large Load Diff
+28 -28
View File
@@ -1,7 +1,7 @@
{ {
"name": "easytier-gui", "name": "easytier-gui",
"type": "module", "type": "module",
"version": "1.2.0", "version": "1.2.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -12,47 +12,47 @@
"lint:fix": "eslint . --ignore-pattern src-tauri --fix" "lint:fix": "eslint . --ignore-pattern src-tauri --fix"
}, },
"dependencies": { "dependencies": {
"@primevue/themes": "^4.0.0", "@primevue/themes": "^4.0.4",
"@tauri-apps/plugin-clipboard-manager": "2.1.0-beta.4", "@tauri-apps/plugin-clipboard-manager": "2.0.0-rc.0",
"@tauri-apps/plugin-os": "2.0.0-beta.6", "@tauri-apps/plugin-os": "2.0.0-rc.0",
"@tauri-apps/plugin-process": "2.0.0-beta.6", "@tauri-apps/plugin-process": "2.0.0-rc.0",
"@tauri-apps/plugin-shell": "2.0.0-beta.7", "@tauri-apps/plugin-shell": "2.0.0-rc.0",
"aura": "link:@primevue/themes/aura", "aura": "link:@primevue/themes/aura",
"pinia": "^2.1.7", "pinia": "^2.2.1",
"primeflex": "^3.3.1", "primeflex": "^3.3.1",
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primevue": "^4.0.0", "primevue": "^4.0.4",
"tauri-plugin-vpnservice-api": "link:../tauri-plugin-vpnservice/", "tauri-plugin-vpnservice-api": "link:../tauri-plugin-vpnservice",
"vue": "^3.4.31", "vue": "^3.4.36",
"vue-i18n": "^9.13.1", "vue-i18n": "^9.13.1",
"vue-router": "^4.4.0" "vue-router": "^4.4.3"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.21.3", "@antfu/eslint-config": "^2.24.1",
"@intlify/unplugin-vue-i18n": "^4.0.0", "@intlify/unplugin-vue-i18n": "^4.0.0",
"@primevue/auto-import-resolver": "^4.0.0", "@primevue/auto-import-resolver": "^4.0.4",
"@tauri-apps/api": "2.0.0-beta.14", "@tauri-apps/api": "2.0.0-rc.0",
"@tauri-apps/cli": "2.0.0-beta.21", "@tauri-apps/cli": "2.0.0-rc.1",
"@types/node": "^20.14.10", "@types/node": "^20.14.14",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@vitejs/plugin-vue": "^5.0.5", "@vitejs/plugin-vue": "^5.1.2",
"@vue-macros/volar": "^0.19.1", "@vue-macros/volar": "^0.19.1",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"eslint": "^9.6.0", "eslint": "^9.8.0",
"eslint-plugin-format": "^0.1.2", "eslint-plugin-format": "^0.1.2",
"postcss": "^8.4.39", "postcss": "^8.4.41",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.7",
"typescript": "^5.5.3", "typescript": "^5.5.4",
"unplugin-auto-import": "^0.17.6", "unplugin-auto-import": "^0.17.8",
"unplugin-vue-components": "^0.27.2", "unplugin-vue-components": "^0.27.3",
"unplugin-vue-macros": "^2.9.5", "unplugin-vue-macros": "^2.11.4",
"unplugin-vue-markdown": "^0.26.2", "unplugin-vue-markdown": "^0.26.2",
"unplugin-vue-router": "^0.8.8", "unplugin-vue-router": "^0.8.8",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vite": "^5.3.3", "vite": "^5.3.5",
"vite-plugin-vue-devtools": "^7.3.5", "vite-plugin-vue-devtools": "^7.3.7",
"vite-plugin-vue-layouts": "^0.11.0", "vite-plugin-vue-layouts": "^0.11.0",
"vue-i18n": "^9.13.1", "vue-i18n": "^9.13.1",
"vue-tsc": "^2.0.26" "vue-tsc": "^2.0.29"
} }
} }
+1789 -1316
View File
File diff suppressed because it is too large Load Diff
+14 -10
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "easytier-gui" name = "easytier-gui"
version = "1.2.0" version = "1.2.1"
description = "EasyTier GUI" description = "EasyTier GUI"
authors = ["you"] authors = ["you"]
edition = "2021" edition = "2021"
@@ -12,10 +12,14 @@ name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] } tauri-build = { version = "2.0.0-rc", features = [] }
[dependencies] [dependencies]
tauri = { version = "2.0.0-beta", features = [ "tray-icon", "image-png", "image-ico"] } tauri = { version = "2.0.0-rc", features = [
"tray-icon",
"image-png",
"image-ico",
] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
@@ -26,20 +30,20 @@ anyhow = "1.0"
chrono = { version = "0.4.37", features = ["serde"] } chrono = { version = "0.4.37", features = ["serde"] }
once_cell = "1.18.0" once_cell = "1.18.0"
dashmap = "5.5.3" dashmap = "6.0"
privilege = "0.3" privilege = "0.3"
gethostname = "0.4.3" gethostname = "0.5"
auto-launch = "0.5.0" auto-launch = "0.5.0"
dunce = "1.0.4" dunce = "1.0.4"
tauri-plugin-shell = "2.0.0-beta.8" tauri-plugin-shell = "2.0.0-rc"
tauri-plugin-process = "2.0.0-beta.7" tauri-plugin-process = "2.0.0-rc"
tauri-plugin-clipboard-manager = "2.1.0-beta.5" tauri-plugin-clipboard-manager = "2.0.0-rc"
tauri-plugin-positioner = { version = "2.0.0-beta", features = ["tray-icon"] } tauri-plugin-positioner = { version = "2.0.0-rc", features = ["tray-icon"] }
tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" } tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" }
tauri-plugin-os = "2.0.0-beta.7" tauri-plugin-os = "2.0.0-rc"
[features] [features]
@@ -6,17 +6,17 @@
"main" "main"
], ],
"permissions": [ "permissions": [
"path:default", "core:path:default",
"event:default", "core:event:default",
"window:default", "core:window:default",
"window:allow-is-visible", "core:window:allow-is-visible",
"window:allow-show", "core:window:allow-show",
"window:allow-hide", "core:window:allow-hide",
"window:allow-set-focus", "core:window:allow-set-focus",
"app:default", "core:app:default",
"resources:default", "core:resources:default",
"menu:default", "core:menu:default",
"tray:default", "core:tray:default",
"shell:allow-open", "shell:allow-open",
"process:allow-exit", "process:allow-exit",
"clipboard-manager:allow-read-text", "clipboard-manager:allow-read-text",
@@ -24,16 +24,16 @@
"shell:default", "shell:default",
"process:default", "process:default",
"clipboard-manager:default", "clipboard-manager:default",
"tray:default", "core:tray:default",
"tray:allow-new", "core:tray:allow-new",
"tray:allow-set-menu", "core:tray:allow-set-menu",
"tray:allow-set-title", "core:tray:allow-set-title",
"tray:allow-remove-by-id", "core:tray:allow-remove-by-id",
"tray:allow-get-by-id", "core:tray:allow-get-by-id",
"tray:allow-set-icon", "core:tray:allow-set-icon",
"tray:allow-set-icon-as-template", "core:tray:allow-set-icon-as-template",
"tray:allow-set-show-menu-on-left-click", "core:tray:allow-set-show-menu-on-left-click",
"tray:allow-set-tooltip", "core:tray:allow-set-tooltip",
"vpnservice:allow-ping", "vpnservice:allow-ping",
"vpnservice:allow-prepare-vpn", "vpnservice:allow-prepare-vpn",
"vpnservice:allow-start-vpn", "vpnservice:allow-start-vpn",
+1 -1
View File
@@ -17,7 +17,7 @@
"createUpdaterArtifacts": false "createUpdaterArtifacts": false
}, },
"productName": "easytier-gui", "productName": "easytier-gui",
"version": "1.2.0", "version": "1.2.1",
"identifier": "com.kkrainbow.easytier", "identifier": "com.kkrainbow.easytier",
"plugins": {}, "plugins": {},
"app": { "app": {
-95
View File
@@ -201,98 +201,3 @@ declare module 'vue' {
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']> readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
} }
} }
declare module '@vue/runtime-core' {
interface GlobalComponents {}
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly MenuItemExit: UnwrapRef<typeof import('./composables/tray')['MenuItemExit']>
readonly MenuItemShow: UnwrapRef<typeof import('./composables/tray')['MenuItemShow']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly collectNetworkInfos: UnwrapRef<typeof import('./composables/network')['collectNetworkInfos']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly definePage: UnwrapRef<typeof import('unplugin-vue-router/runtime')['definePage']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly generateMenuItem: UnwrapRef<typeof import('./composables/tray')['generateMenuItem']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getOsHostname: UnwrapRef<typeof import('./composables/network')['getOsHostname']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly initMobileVpnService: UnwrapRef<typeof import('./composables/mobile_vpn')['initMobileVpnService']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly loadRunningInstanceIdsFromLocalStorage: UnwrapRef<typeof import('./stores/network')['loadRunningInstanceIdsFromLocalStorage']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router/auto')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router/auto')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly parseNetworkConfig: UnwrapRef<typeof import('./composables/network')['parseNetworkConfig']>
readonly prepareVpnService: UnwrapRef<typeof import('./composables/mobile_vpn')['prepareVpnService']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setAutoLaunchStatus: UnwrapRef<typeof import('./composables/network')['setAutoLaunchStatus']>
readonly setLoggingLevel: UnwrapRef<typeof import('./composables/network')['setLoggingLevel']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly setTrayMenu: UnwrapRef<typeof import('./composables/tray')['setTrayMenu']>
readonly setTrayRunState: UnwrapRef<typeof import('./composables/tray')['setTrayRunState']>
readonly setTrayTooltip: UnwrapRef<typeof import('./composables/tray')['setTrayTooltip']>
readonly setTunFd: UnwrapRef<typeof import('./composables/network')['setTunFd']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
readonly useNetworkStore: UnwrapRef<typeof import('./stores/network')['useNetworkStore']>
readonly useRoute: UnwrapRef<typeof import('vue-router/auto')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router/auto')['useRouter']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useTray: UnwrapRef<typeof import('./composables/tray')['useTray']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
}
}
@@ -159,14 +159,11 @@ async function watchNetworkInstance() {
if (subscribe_running) { if (subscribe_running) {
return return
} }
console.log('network instance change')
subscribe_running = true subscribe_running = true
try { try {
await onNetworkInstanceChange() await onNetworkInstanceChange()
} catch (_) { } catch (_) {
} }
console.log('network instance change done')
subscribe_running = false subscribe_running = false
}) })
} }
+6 -6
View File
@@ -1,4 +1,4 @@
import { getCurrent } from '@tauri-apps/api/window' import { getCurrentWindow } from '@tauri-apps/api/window'
import { Menu, MenuItem, PredefinedMenuItem } from '@tauri-apps/api/menu' import { Menu, MenuItem, PredefinedMenuItem } from '@tauri-apps/api/menu'
import { TrayIcon } from '@tauri-apps/api/tray' import { TrayIcon } from '@tauri-apps/api/tray'
import pkg from '~/../package.json' import pkg from '~/../package.json'
@@ -6,11 +6,11 @@ import pkg from '~/../package.json'
const DEFAULT_TRAY_NAME = 'main' const DEFAULT_TRAY_NAME = 'main'
async function toggleVisibility() { async function toggleVisibility() {
if (await getCurrent().isVisible()) { if (await getCurrentWindow().isVisible()) {
await getCurrent().hide() await getCurrentWindow().hide()
} else { } else {
await getCurrent().show() await getCurrentWindow().show()
await getCurrent().setFocus() await getCurrentWindow().setFocus()
} }
} }
@@ -54,7 +54,7 @@ export async function generateMenuItem() {
await MenuItemExit('Exit'), await MenuItemExit('Exit'),
await PredefinedMenuItem.new({ item: 'Separator' }), await PredefinedMenuItem.new({ item: 'Separator' }),
await MenuItemShow('Show / Hide'), await MenuItemShow('Show / Hide'),
] || [] ]
} }
export async function MenuItemExit(text: string) { export async function MenuItemExit(text: string) {
+26 -15
View File
@@ -3,7 +3,7 @@ name = "easytier"
description = "A full meshed p2p VPN, connecting all your devices in one network with one command." description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
homepage = "https://github.com/EasyTier/EasyTier" homepage = "https://github.com/EasyTier/EasyTier"
repository = "https://github.com/EasyTier/EasyTier" repository = "https://github.com/EasyTier/EasyTier"
version = "1.2.0" version = "1.2.1"
edition = "2021" edition = "2021"
authors = ["kkrainbow"] authors = ["kkrainbow"]
keywords = ["vpn", "p2p", "network", "easytier"] keywords = ["vpn", "p2p", "network", "easytier"]
@@ -43,7 +43,7 @@ time = "0.3"
toml = "0.8.12" toml = "0.8.12"
chrono = { version = "0.4.37", features = ["serde"] } chrono = { version = "0.4.37", features = ["serde"] }
gethostname = "0.4.3" gethostname = "0.5.0"
futures = { version = "0.3", features = ["bilock", "unstable"] } futures = { version = "0.3", features = ["bilock", "unstable"] }
@@ -54,7 +54,7 @@ tokio-util = { version = "0.7.9", features = ["codec", "net"] }
async-stream = "0.3.5" async-stream = "0.3.5"
async-trait = "0.1.74" async-trait = "0.1.74"
dashmap = "5.5.3" dashmap = "6.0"
timedmap = "=1.0.1" timedmap = "=1.0.1"
# for full-path zero-copy # for full-path zero-copy
@@ -62,7 +62,7 @@ zerocopy = { version = "0.7.32", features = ["derive", "simd"] }
bytes = "1.5.0" bytes = "1.5.0"
pin-project-lite = "0.2.13" pin-project-lite = "0.2.13"
atomicbox = "0.4.0" atomicbox = "0.4.0"
tachyonix = "0.2.1" tachyonix = "0.3.0"
quinn = { version = "0.11.0", optional = true, features = ["ring"] } quinn = { version = "0.11.0", optional = true, features = ["ring"] }
rustls = { version = "0.23.0", features = [ rustls = { version = "0.23.0", features = [
@@ -71,7 +71,7 @@ rustls = { version = "0.23.0", features = [
rcgen = { version = "0.11.1", optional = true } rcgen = { version = "0.11.1", optional = true }
# for websocket # for websocket
tokio-websockets = { version = "0.8.2", optional = true, features = [ tokio-websockets = { version = "0.8", optional = true, features = [
"rustls-webpki-roots", "rustls-webpki-roots",
"client", "client",
"server", "server",
@@ -84,7 +84,7 @@ http = { version = "1", default-features = false, features = [
tokio-rustls = { version = "0.26", default-features = false, optional = true } tokio-rustls = { version = "0.26", default-features = false, optional = true }
# for tap device # for tap device
tun = { package = "tun-easytier", version = "0.7.1", features = [ tun = { package = "tun-easytier", version = "1.1.1", features = [
"async", "async",
], optional = true } ], optional = true }
# for net ns # for net ns
@@ -105,8 +105,8 @@ once_cell = "1.18.0"
postcard = { "version" = "1.0.8", features = ["alloc"] } postcard = { "version" = "1.0.8", features = ["alloc"] }
# for rpc # for rpc
tonic = "0.10" tonic = "0.12"
prost = "0.12" prost = "0.13"
anyhow = "1.0" anyhow = "1.0"
tarpc = { version = "0.32", features = ["tokio1", "serde1"] } tarpc = { version = "0.32", features = ["tokio1", "serde1"] }
@@ -126,13 +126,18 @@ bytecodec = "0.4.15"
rand = "0.8.5" rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
pnet = { version = "0.34.0", features = ["serde"] } pnet = { version = "0.35.0", features = ["serde"] }
clap = { version = "4.4.8", features = ["unicode", "derive", "wrap_help"] } clap = { version = "4.4.8", features = [
"string",
"unicode",
"derive",
"wrap_help",
] }
async-recursion = "1.0.5" async-recursion = "1.0.5"
network-interface = "1.1.1" network-interface = "2.0"
# for ospf route # for ospf route
petgraph = "0.6.5" petgraph = "0.6.5"
@@ -143,15 +148,16 @@ bitflags = "2.5"
aes-gcm = { version = "0.10.3", optional = true } aes-gcm = { version = "0.10.3", optional = true }
# for cli # for cli
tabled = "0.15.*" tabled = "0.16"
humansize = "2.1.3" humansize = "2.1.3"
base64 = "0.21.7" base64 = "0.22"
derivative = "2.2.0" derivative = "2.2.0"
mimalloc-rust = { version = "0.2.1", optional = true } mimalloc-rust = { version = "0.2.1", optional = true }
# for mips
indexmap = { version = "~1.9.3", optional = false, features = ["std"] } indexmap = { version = "~1.9.3", optional = false, features = ["std"] }
atomic-shim = "0.2.0" atomic-shim = "0.2.0"
@@ -168,6 +174,9 @@ parking_lot = { version = "0.12.0", optional = true }
wildmatch = "2.3.4" wildmatch = "2.3.4"
rust-i18n = "3"
sys-locale = "0.3"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = [ windows-sys = { version = "0.52", features = [
"Win32_Networking_WinSock", "Win32_Networking_WinSock",
@@ -176,10 +185,12 @@ windows-sys = { version = "0.52", features = [
"Win32_System_IO", "Win32_System_IO",
] } ] }
encoding = "0.2" encoding = "0.2"
winreg = "0.11" winreg = "0.52"
[build-dependencies] [build-dependencies]
tonic-build = "0.10" tonic-build = "0.12"
globwalk = "0.8.1"
regex = "1"
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]
reqwest = { version = "0.11", features = ["blocking"] } reqwest = { version = "0.11", features = ["blocking"] }
+40
View File
@@ -86,6 +86,45 @@ impl WindowsBuild {
} }
} }
fn workdir() -> Option<String> {
if let Ok(cargo_manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
return Some(cargo_manifest_dir);
}
let dest = std::env::var("OUT_DIR");
if dest.is_err() {
return None;
}
let dest = dest.unwrap();
let seperator = regex::Regex::new(r"(/target/(.+?)/build/)|(\\target\\(.+?)\\build\\)")
.expect("Invalid regex");
let parts = seperator.split(dest.as_str()).collect::<Vec<_>>();
if parts.len() >= 2 {
return Some(parts[0].to_string());
}
None
}
fn check_locale() {
let workdir = workdir().unwrap_or("./".to_string());
let locale_path = format!("{workdir}/**/locales/**/*");
if let Ok(globs) = globwalk::glob(locale_path) {
for entry in globs {
if let Err(e) = entry {
println!("cargo:i18n-error={}", e);
continue;
}
let entry = entry.unwrap().into_path();
println!("cargo:rerun-if-changed={}", entry.display());
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
WindowsBuild::check_for_win(); WindowsBuild::check_for_win();
@@ -98,5 +137,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.compile(&["proto/cli.proto"], &["proto/"]) .compile(&["proto/cli.proto"], &["proto/"])
.unwrap(); .unwrap();
// tonic_build::compile_protos("proto/cli.proto")?; // tonic_build::compile_protos("proto/cli.proto")?;
check_locale();
Ok(()) Ok(())
} }
+101
View File
@@ -0,0 +1,101 @@
_version: 2
core_clap:
config_file:
en: "path to the config file, NOTE: if this is set, all other options will be ignored"
zh-CN: "配置文件路径,注意:如果设置了这个选项,其他所有选项都将被忽略"
network_name:
en: "network name to identify this vpn network"
zh-CN: "用于标识此VPN网络的网络名称"
network_secret:
en: "network secret to verify this node belongs to the vpn network"
zh-CN: "网络密钥,用于验证此节点属于VPN网络"
ipv4:
en: "ipv4 address of this vpn node, if empty, this node will only forward packets and no TUN device will be created"
zh-CN: "此VPN节点的IPv4地址,如果为空,则此节点将仅转发数据包,不会创建TUN设备"
dhcp:
en: "automatically determine and set IP address by Easytier, and the IP address starts from 10.0.0.1 by default. Warning, if there is an IP conflict in the network when using DHCP, the IP will be automatically changed."
zh-CN: "由Easytier自动确定并设置IP地址,默认从10.0.0.1开始。警告:在使用DHCP时,如果网络中出现IP冲突,IP将自动更改。"
peers:
en: "peers to connect initially"
zh-CN: "最初要连接的对等节点"
external_node:
en: "use a public shared node to discover peers"
zh-CN: "使用公共共享节点来发现对等节点"
proxy_networks:
en: "export local networks to other peers in the vpn"
zh-CN: "将本地网络导出到VPN中的其他对等节点"
rpc_portal:
en: "rpc portal address to listen for management. 0 means random port, 12345 means listen on 12345 of localhost, 0.0.0.0:12345 means listen on 12345 of all interfaces. default is 0 and will try 15888 first"
zh-CN: "用于管理的RPC门户地址。0表示随机端口,12345表示在localhost的12345上监听,0.0.0.0:12345表示在所有接口的12345上监听。默认是0,首先尝试15888"
listeners:
en: |+
listeners to accept connections, allow format:
port number: <11010>. means tcp/udp will listen on 11010, ws/wss will listen on 11010 and 11011, wg will listen on 11011
url: <tcp://0.0.0.0:11010>. tcp can be tcp, udp, ring, wg, ws, wss\n
proto & port pair: <proto:port>. wg:11011, means listen on 11011 with wireguard protocol url and proto:port can occur multiple times.
zh-CN: |+
监听器用于接受连接,允许以下格式:
端口号:<11010>,意味着tcp/udp将在11010端口监听,ws/wss将在11010和11011端口监听,wg将在11011端口监听。
url<tcp://0.0.0.0:11010>,其中tcp可以是tcp、udp、ring、wg、ws、wss协议。
协议和端口对:<proto:port>,例如wg:11011,表示使用WireGuard协议在11011端口监听。URL 和 协议端口对 可以多次出现。
no_listener:
en: "do not listen on any port, only connect to peers"
zh-CN: "不监听任何端口,只连接到对等节点"
console_log_level:
en: "console log level"
zh-CN: "控制台日志级别"
file_log_level:
en: "file log level"
zh-CN: "文件日志级别"
file_log_dir:
en: "directory to store log files"
zh-CN: "存储日志文件的目录"
hostname:
en: "host name to identify this device"
zh-CN: "用于标识此设备的主机名"
instance_name:
en: "instance name to identify this vpn node in same machine"
zh-CN: "实例名称,用于在同一台机器上标识此VPN节点"
vpn_portal:
en: "url that defines the vpn portal, allow other vpn clients to connect. example: wg://0.0.0.0:11010/10.14.14.0/24, means the vpn portal is a wireguard server listening on vpn.example.com:11010, and the vpn client is in network of 10.14.14.0/24"
zh-CN: "定义VPN门户的URL,允许其他VPN客户端连接。示例:wg://0.0.0.0:11010/10.14.14.0/24,表示VPN门户是监听在vpn.example.com:11010的wireguard服务器,VPN客户端在10.14.14.0/24网络中"
default_protocol:
en: "default protocol to use when connecting to peers"
zh-CN: "连接到对等节点时使用的默认协议"
disable_encryption:
en: "disable encryption for peers communication, default is false, must be same with peers"
zh-CN: "禁用对等节点通信的加密,默认为false,必须与对等节点相同"
multi_thread:
en: "use multi-thread runtime, default is single-thread"
zh-CN: "使用多线程运行时,默认为单线程"
disable_ipv6:
en: "do not use ipv6"
zh-CN: "不使用IPv6"
dev_name:
en: "optional tun interface name"
zh-CN: "可选的TUN接口名称"
mtu:
en: "mtu of the TUN device, default is 1420 for non-encryption, 1400 for encryption"
zh-CN: "TUN设备的MTU,默认为非加密时为1420,加密时为1400"
latency_first:
en: "latency first mode, will try to relay traffic with lowest latency path, default is using shortest path"
zh-CN: "延迟优先模式,将尝试使用最低延迟路径转发流量,默认使用最短路径"
exit_nodes:
en: "exit nodes to forward all traffic to, a virtual ipv4 address, priority is determined by the order of the list"
zh-CN: "转发所有流量的出口节点,虚拟IPv4地址,优先级由列表顺序决定"
enable_exit_node:
en: "allow this node to be an exit node"
zh-CN: "允许此节点成为出口节点"
no_tun:
en: "do not create TUN device, can use subnet proxy to access node"
zh-CN: "不创建TUN设备,可以使用子网代理访问节点"
use_smoltcp:
en: "enable smoltcp stack for subnet proxy"
zh-CN: "为子网代理启用smoltcp堆栈"
manual_routes:
en: "assign routes cidr manually, will disable subnet proxy and wireguard routes propagated from peers. e.g.: 192.168.0.0/16"
zh-CN: "手动分配路由CIDR,将禁用子网代理和从对等节点传播的wireguard路由。例如:192.168.0.0/16"
relay_network_whitelist:
en: "only relay traffic of whitelisted networks, input is a wildcard string, e.g.: '*' (all networks), 'def*' (network prefixed with def), can specify multiple networks disable relay if arg is empty. default is allowing all networks"
zh-CN: "仅转发白名单网络的流量,输入是通配符字符串,例如:'*'(所有网络),'def*'(以def为前缀的网络),可以指定多个网络。如果参数为空,则禁用转发。默认允许所有网络"
+5 -11
View File
@@ -258,21 +258,15 @@ impl ConfigLoader for TomlConfigLoader {
match hostname { match hostname {
Some(hostname) => { Some(hostname) => {
if !hostname.is_empty() { let hostname = hostname
let mut name = hostname
.chars() .chars()
.filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_') .filter(|c| !c.is_control())
.take(32) .take(32)
.collect::<String>(); .collect::<String>();
if name.len() > 32 { if !hostname.is_empty() {
name = name.chars().take(32).collect::<String>(); self.set_hostname(Some(hostname.clone()));
} hostname
if hostname != name {
self.set_hostname(Some(name.clone()));
}
name
} else { } else {
self.set_hostname(None); self.set_hostname(None);
gethostname::gethostname().to_string_lossy().to_string() gethostname::gethostname().to_string_lossy().to_string()
+1
View File
@@ -316,6 +316,7 @@ impl ManualConnectorManager {
ip_versions.push(IpVersion::Both); ip_versions.push(IpVersion::Both);
} else { } else {
let addrs = u.socket_addrs(|| Some(1000))?; let addrs = u.socket_addrs(|| Some(1000))?;
tracing::info!(?addrs, ?dead_url, "get ip from url done");
let mut has_ipv4 = false; let mut has_ipv4 = false;
let mut has_ipv6 = false; let mut has_ipv6 = false;
for addr in addrs { for addr in addrs {
+80 -51
View File
@@ -10,6 +10,9 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
#[macro_use]
extern crate rust_i18n;
use anyhow::Context; use anyhow::Context;
use clap::Parser; use clap::Parser;
@@ -52,19 +55,20 @@ struct Cli {
#[arg( #[arg(
short, short,
long, long,
help = "path to the config file, NOTE: if this is set, all other options will be ignored" help = t!("core_clap.config_file").to_string()
)] )]
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
#[arg( #[arg(
long, long,
help = "network name to identify this vpn network", help = t!("core_clap.network_name").to_string(),
default_value = "default" default_value = "default"
)] )]
network_name: String, network_name: String,
#[arg( #[arg(
long, long,
help = "network secret to verify this node belongs to the vpn network", help = t!("core_clap.network_secret").to_string(),
default_value = "" default_value = ""
)] )]
network_secret: String, network_secret: String,
@@ -72,171 +76,193 @@ struct Cli {
#[arg( #[arg(
short, short,
long, long,
help = "ipv4 address of this vpn node, if empty, this node will only forward packets and no TUN device will be created" help = t!("core_clap.ipv4").to_string()
)] )]
ipv4: Option<String>, ipv4: Option<String>,
#[arg( #[arg(
short, short,
long, long,
help = "automatically determine and set IP address by Easytier, and the help = t!("core_clap.dhcp").to_string()
IP address starts from 10.0.0.1 by default. Warning, if there is an IP
conflict in the network when using DHCP, the IP will be automatically
changed."
)] )]
dhcp: bool, dhcp: bool,
#[arg(short, long, help = "peers to connect initially", num_args = 0..)] #[arg(
short,
long,
help = t!("core_clap.peers").to_string(),
num_args = 0..
)]
peers: Vec<String>, peers: Vec<String>,
#[arg(short, long, help = "use a public shared node to discover peers")] #[arg(
short,
long,
help = t!("core_clap.external_node").to_string()
)]
external_node: Option<String>, external_node: Option<String>,
#[arg( #[arg(
short = 'n', short = 'n',
long, long,
help = "export local networks to other peers in the vpn" help = t!("core_clap.proxy_networks").to_string()
)] )]
proxy_networks: Vec<String>, proxy_networks: Vec<String>,
#[arg( #[arg(
short, short,
long, long,
default_value = "0", help = t!("core_clap.rpc_portal").to_string(),
help = "rpc portal address to listen for management. 0 means random default_value = "0"
port, 12345 means listen on 12345 of localhost, 0.0.0.0:12345 means
listen on 12345 of all interfaces. default is 0 and will try 15888 first"
)] )]
rpc_portal: String, rpc_portal: String,
#[arg(short, long, help = "listeners to accept connections, allow format: #[arg(
a port number: 11010, means tcp/udp will listen on 11010, ws/wss will listen on 11010 and 11011, wg will listen on 11011 short,
url: tcp://0.0.0.0:11010, tcp can be tcp, udp, ring, wg, ws, wss, long,
proto:port: wg:11011, means listen on 11011 with wireguard protocol help = t!("core_clap.listeners").to_string(),
url and proto:port can occur multiple times. default_values_t = ["11010".to_string()],
", default_values_t = ["11010".to_string()], num_args = 0..
num_args = 0..)] )]
listeners: Vec<String>, listeners: Vec<String>,
#[arg( #[arg(
long, long,
help = "do not listen on any port, only connect to peers", help = t!("core_clap.no_listener").to_string(),
default_value = "false" default_value = "false"
)] )]
no_listener: bool, no_listener: bool,
#[arg(long, help = "console log level", #[arg(
value_parser = clap::builder::PossibleValuesParser::new(["trace", "debug", "info", "warn", "error", "off"]))] long,
help = t!("core_clap.console_log_level").to_string()
)]
console_log_level: Option<String>, console_log_level: Option<String>,
#[arg(long, help = "file log level", #[arg(
value_parser = clap::builder::PossibleValuesParser::new(["trace", "debug", "info", "warn", "error", "off"]))] long,
help = t!("core_clap.file_log_level").to_string()
)]
file_log_level: Option<String>, file_log_level: Option<String>,
#[arg(long, help = "directory to store log files")]
#[arg(
long,
help = t!("core_clap.file_log_dir").to_string()
)]
file_log_dir: Option<String>, file_log_dir: Option<String>,
#[arg(long, help = "host name to identify this device")] #[arg(
long,
help = t!("core_clap.hostname").to_string()
)]
hostname: Option<String>, hostname: Option<String>,
#[arg( #[arg(
short = 'm', short = 'm',
long, long,
default_value = "default", help = t!("core_clap.instance_name").to_string(),
help = "instance name to identify this vpn node in same machine" default_value = "default"
)] )]
instance_name: String, instance_name: String,
#[arg( #[arg(
long, long,
help = "url that defines the vpn portal, allow other vpn clients to connect. help = t!("core_clap.vpn_portal").to_string()
example: wg://0.0.0.0:11010/10.14.14.0/24, means the vpn portal is a wireguard server listening on vpn.example.com:11010,
and the vpn client is in network of 10.14.14.0/24"
)] )]
vpn_portal: Option<String>, vpn_portal: Option<String>,
#[arg(long, help = "default protocol to use when connecting to peers")] #[arg(
long,
help = t!("core_clap.default_protocol").to_string()
)]
default_protocol: Option<String>, default_protocol: Option<String>,
#[arg( #[arg(
short = 'u', short = 'u',
long, long,
help = "disable encryption for peers communication, default is false, must be same with peers", help = t!("core_clap.disable_encryption").to_string(),
default_value = "false" default_value = "false"
)] )]
disable_encryption: bool, disable_encryption: bool,
#[arg( #[arg(
long, long,
help = "use multi-thread runtime, default is single-thread", help = t!("core_clap.multi_thread").to_string(),
default_value = "false" default_value = "false"
)] )]
multi_thread: bool, multi_thread: bool,
#[arg(long, help = "do not use ipv6", default_value = "false")] #[arg(
long,
help = t!("core_clap.disable_ipv6").to_string(),
default_value = "false"
)]
disable_ipv6: bool, disable_ipv6: bool,
#[arg(long, help = "optional tun interface name")] #[arg(
long,
help = t!("core_clap.dev_name").to_string()
)]
dev_name: Option<String>, dev_name: Option<String>,
#[arg( #[arg(
long, long,
help = "mtu of the TUN device, default is 1420 for non-encryption, 1400 for encryption" help = t!("core_clap.mtu").to_string()
)] )]
mtu: Option<u16>, mtu: Option<u16>,
#[arg( #[arg(
long, long,
help = "latency first mode, will try to relay traffic with lowest latency path, default is using shortest path", help = t!("core_clap.latency_first").to_string(),
default_value = "false" default_value = "false"
)] )]
latency_first: bool, latency_first: bool,
#[arg( #[arg(
long, long,
help = "exit nodes to forward all traffic to, a virtual ipv4 address, priority is determined by the order of the list", help = t!("core_clap.exit_nodes").to_string(),
num_args = 0.. num_args = 0..
)] )]
exit_nodes: Vec<Ipv4Addr>, exit_nodes: Vec<Ipv4Addr>,
#[arg( #[arg(
long, long,
help = "allow this node to be an exit node, default is false", help = t!("core_clap.enable_exit_node").to_string(),
default_value = "false" default_value = "false"
)] )]
enable_exit_node: bool, enable_exit_node: bool,
#[arg( #[arg(
long, long,
help = "do not create TUN device, can use subnet proxy to access node", help = t!("core_clap.no_tun").to_string(),
default_value = "false" default_value = "false"
)] )]
no_tun: bool, no_tun: bool,
#[arg( #[arg(
long, long,
help = "enable smoltcp stack for subnet proxy", help = t!("core_clap.use_smoltcp").to_string(),
default_value = "false" default_value = "false"
)] )]
use_smoltcp: bool, use_smoltcp: bool,
#[arg( #[arg(
long, long,
help = "assign routes cidr manually, will disable subnet proxy and help = t!("core_clap.manual_routes").to_string(),
wireguard routes propogated from peers. e.g.: 192.168.0.0/16",
num_args = 0.. num_args = 0..
)] )]
manual_routes: Option<Vec<String>>, manual_routes: Option<Vec<String>>,
#[arg( #[arg(
long, long,
help = "only relay traffic of whitelisted networks, input is a wildcard help = t!("core_clap.relay_network_whitelist").to_string(),
string, e.g.: '*' (all networks), 'def*' (network prefixed with def), can specify multiple networks num_args = 0..
disable relay if arg is empty. default is allowing all networks",
num_args = 0..,
)] )]
relay_network_whitelist: Option<Vec<String>>, relay_network_whitelist: Option<Vec<String>>,
} }
rust_i18n::i18n!("locales");
impl Cli { impl Cli {
fn parse_listeners(&self) -> Vec<String> { fn parse_listeners(&self) -> Vec<String> {
println!("parsing listeners: {:?}", self.listeners); println!("parsing listeners: {:?}", self.listeners);
@@ -628,6 +654,9 @@ pub async fn async_main(cli: Cli) {
fn main() { fn main() {
setup_panic_handler(); setup_panic_handler();
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
rust_i18n::set_locale(&locale);
let cli = Cli::parse(); let cli = Cli::parse();
tracing::info!(cli = ?cli, "cli args parsed"); tracing::info!(cli = ?cli, "cli args parsed");
+23 -8
View File
@@ -1,3 +1,4 @@
use std::any::Any;
use std::collections::HashSet; use std::collections::HashSet;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@@ -91,7 +92,7 @@ impl NicCtx {
} }
} }
type ArcNicCtx = Arc<Mutex<Option<NicCtx>>>; type ArcNicCtx = Arc<Mutex<Option<Box<dyn Any + 'static + Send>>>>;
pub struct Instance { pub struct Instance {
inst_name: String, inst_name: String,
@@ -197,14 +198,28 @@ impl Instance {
Ok(()) Ok(())
} }
async fn clear_nic_ctx(arc_nic_ctx: ArcNicCtx) { // use a mock nic ctx to consume packets.
async fn clear_nic_ctx(
arc_nic_ctx: ArcNicCtx,
packet_recv: Arc<Mutex<PacketRecvChanReceiver>>,
) {
let _ = arc_nic_ctx.lock().await.take(); let _ = arc_nic_ctx.lock().await.take();
let mut tasks = JoinSet::new();
tasks.spawn(async move {
let mut packet_recv = packet_recv.lock().await;
while let Some(packet) = packet_recv.recv().await {
tracing::trace!("packet consumed by mock nic ctx: {:?}", packet);
}
});
arc_nic_ctx.lock().await.replace(Box::new(tasks));
tracing::debug!("nic ctx cleared."); tracing::debug!("nic ctx cleared.");
} }
async fn use_new_nic_ctx(arc_nic_ctx: ArcNicCtx, nic_ctx: NicCtx) { async fn use_new_nic_ctx(arc_nic_ctx: ArcNicCtx, nic_ctx: NicCtx) {
let mut g = arc_nic_ctx.lock().await; let mut g = arc_nic_ctx.lock().await;
*g = Some(nic_ctx); *g = Some(Box::new(nic_ctx));
tracing::debug!("nic ctx updated."); tracing::debug!("nic ctx updated.");
} }
@@ -274,7 +289,7 @@ impl Instance {
"dhcp start changing ip" "dhcp start changing ip"
); );
Self::clear_nic_ctx(nic_ctx.clone()).await; Self::clear_nic_ctx(nic_ctx.clone(), _peer_packet_receiver.clone()).await;
if let Some(ip) = candidate_ipv4_addr { if let Some(ip) = candidate_ipv4_addr {
if global_ctx_c.no_tun() { if global_ctx_c.no_tun() {
@@ -329,9 +344,9 @@ impl Instance {
self.listener_manager.lock().await.run().await?; self.listener_manager.lock().await.run().await?;
self.peer_manager.run().await?; self.peer_manager.run().await?;
if self.global_ctx.config.get_flags().no_tun { Self::clear_nic_ctx(self.nic_ctx.clone(), self.peer_packet_receiver.clone()).await;
self.peer_packet_receiver.lock().await.close();
} else { if !self.global_ctx.config.get_flags().no_tun {
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
if let Some(ipv4_addr) = self.global_ctx.get_ipv4() { if let Some(ipv4_addr) = self.global_ctx.get_ipv4() {
let mut new_nic_ctx = NicCtx::new( let mut new_nic_ctx = NicCtx::new(
@@ -532,7 +547,7 @@ impl Instance {
fd: i32, fd: i32,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
println!("setup_nic_ctx_for_android, fd: {}", fd); println!("setup_nic_ctx_for_android, fd: {}", fd);
Self::clear_nic_ctx(nic_ctx.clone()).await; Self::clear_nic_ctx(nic_ctx.clone(), peer_packet_receiver.clone()).await;
if fd <= 0 { if fd <= 0 {
return Ok(()); return Ok(());
} }
+27 -48
View File
@@ -31,7 +31,7 @@ use tokio::{
task::JoinSet, task::JoinSet,
}; };
use tokio_util::bytes::Bytes; use tokio_util::bytes::Bytes;
use tun::{create_as_async, AsyncDevice, Configuration, Device as _, Layer}; use tun::{AbstractDevice, AsyncDevice, Configuration, Layer};
use zerocopy::{NativeEndian, NetworkEndian}; use zerocopy::{NativeEndian, NetworkEndian};
pin_project! { pin_project! {
@@ -237,9 +237,6 @@ impl AsyncWrite for TunAsyncWrite {
} }
pub struct VirtualNic { pub struct VirtualNic {
dev_name: String,
queue_num: usize,
global_ctx: ArcGlobalCtx, global_ctx: ArcGlobalCtx,
ifname: Option<String>, ifname: Option<String>,
@@ -247,10 +244,8 @@ pub struct VirtualNic {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn checkreg() -> io::Result<()> { pub fn checkreg() -> io::Result<()> {
use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey,enums::KEY_ALL_ACCESS}; use winreg::{enums::HKEY_LOCAL_MACHINE, enums::KEY_ALL_ACCESS, RegKey};
// 打开根键
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
// 打开指定的子键
let profiles_key = hklm.open_subkey_with_flags( let profiles_key = hklm.open_subkey_with_flags(
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles",
KEY_ALL_ACCESS, KEY_ALL_ACCESS,
@@ -259,21 +254,19 @@ pub fn checkreg() -> io::Result<()> {
"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\\Unmanaged", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\\Unmanaged",
KEY_ALL_ACCESS, KEY_ALL_ACCESS,
)?; )?;
// 收集要删除的子键名称 // collect subkeys to delete
let mut keys_to_delete = Vec::new(); let mut keys_to_delete = Vec::new();
let mut keys_to_delete_unmanaged = Vec::new(); let mut keys_to_delete_unmanaged = Vec::new();
for subkey_name in profiles_key.enum_keys().filter_map(Result::ok) { for subkey_name in profiles_key.enum_keys().filter_map(Result::ok) {
let subkey = profiles_key.open_subkey(&subkey_name)?; let subkey = profiles_key.open_subkey(&subkey_name)?;
// 尝试读取 ProfileName // check if ProfileName contains "et"
match subkey.get_value::<String, _>("ProfileName") { match subkey.get_value::<String, _>("ProfileName") {
Ok(profile_name) => { Ok(profile_name) => {
// 检查 ProfileName 是否包含 "et"
if profile_name.contains("et_") { if profile_name.contains("et_") {
keys_to_delete.push(subkey_name); keys_to_delete.push(subkey_name);
} }
} }
Err(e) => { Err(e) => {
// 打印错误信息
tracing::error!( tracing::error!(
"Failed to read ProfileName for subkey {}: {}", "Failed to read ProfileName for subkey {}: {}",
subkey_name, subkey_name,
@@ -284,16 +277,14 @@ pub fn checkreg() -> io::Result<()> {
} }
for subkey_name in unmanaged_key.enum_keys().filter_map(Result::ok) { for subkey_name in unmanaged_key.enum_keys().filter_map(Result::ok) {
let subkey = unmanaged_key.open_subkey(&subkey_name)?; let subkey = unmanaged_key.open_subkey(&subkey_name)?;
// 尝试读取 ProfileName // check if ProfileName contains "et"
match subkey.get_value::<String, _>("Description") { match subkey.get_value::<String, _>("Description") {
Ok(profile_name) => { Ok(profile_name) => {
// 检查 ProfileName 是否包含 "et"
if profile_name.contains("et_") { if profile_name.contains("et_") {
keys_to_delete_unmanaged.push(subkey_name); keys_to_delete_unmanaged.push(subkey_name);
} }
} }
Err(e) => { Err(e) => {
// 打印错误信息
tracing::error!( tracing::error!(
"Failed to read ProfileName for subkey {}: {}", "Failed to read ProfileName for subkey {}: {}",
subkey_name, subkey_name,
@@ -302,7 +293,7 @@ pub fn checkreg() -> io::Result<()> {
} }
} }
} }
//删除收集到的子键 // delete collected subkeys
if !keys_to_delete.is_empty() { if !keys_to_delete.is_empty() {
for subkey_name in keys_to_delete { for subkey_name in keys_to_delete {
match profiles_key.delete_subkey_all(&subkey_name) { match profiles_key.delete_subkey_all(&subkey_name) {
@@ -325,25 +316,13 @@ pub fn checkreg() -> io::Result<()> {
impl VirtualNic { impl VirtualNic {
pub fn new(global_ctx: ArcGlobalCtx) -> Self { pub fn new(global_ctx: ArcGlobalCtx) -> Self {
Self { Self {
dev_name: "".to_owned(),
queue_num: 1,
global_ctx, global_ctx,
ifname: None, ifname: None,
ifcfg: Box::new(IfConfiger {}), ifcfg: Box::new(IfConfiger {}),
} }
} }
pub fn set_dev_name(mut self, dev_name: &str) -> Result<Self, Error> { async fn create_tun(&mut self) -> Result<tun::platform::Device, Error> {
self.dev_name = dev_name.to_owned();
Ok(self)
}
pub fn set_queue_num(mut self, queue_num: usize) -> Result<Self, Error> {
self.queue_num = queue_num;
Ok(self)
}
async fn create_tun(&mut self) -> Result<AsyncDevice, Error> {
let mut config = Configuration::default(); let mut config = Configuration::default();
config.layer(Layer::L3); config.layer(Layer::L3);
@@ -351,13 +330,15 @@ impl VirtualNic {
{ {
let dev_name = self.global_ctx.get_flags().dev_name; let dev_name = self.global_ctx.get_flags().dev_name;
if !dev_name.is_empty() { if !dev_name.is_empty() {
config.name(format!("{}", dev_name)); config.tun_name(format!("{}", dev_name));
} }
config.platform(|config| { }
// detect protocol by ourselves for cross platform
#[cfg(target_os = "macos")]
config.platform_config(|config| {
// disable packet information so we can process the header by ourselves, see tun2 impl for more details
config.packet_information(false); config.packet_information(false);
}); });
}
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
@@ -366,7 +347,6 @@ impl VirtualNic {
Err(e) => tracing::error!("An error occurred: {}", e), Err(e) => tracing::error!("An error occurred: {}", e),
} }
use rand::distributions::Distribution as _; use rand::distributions::Distribution as _;
use std::net::IpAddr;
let c = crate::arch::windows::interface_count()?; let c = crate::arch::windows::interface_count()?;
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let s: String = rand::distributions::Alphanumeric let s: String = rand::distributions::Alphanumeric
@@ -376,13 +356,15 @@ impl VirtualNic {
.collect::<String>() .collect::<String>()
.to_lowercase(); .to_lowercase();
config.name(format!("et{}_{}_{}", self.dev_name, c, s)); let dev_name = self.global_ctx.get_flags().dev_name;
// set a temporary address if !dev_name.is_empty() {
config.address(format!("172.0.{}.3", c).parse::<IpAddr>().unwrap()); config.tun_name(format!("{}", dev_name));
} else {
config.tun_name(format!("et{}_{}", c, s));
}
config.platform(|config| { config.platform_config(|config| {
config.skip_config(true); config.skip_config(true);
config.guid(None);
config.ring_cap(Some(std::cmp::min( config.ring_cap(Some(std::cmp::min(
config.min_ring_cap() * 32, config.min_ring_cap() * 32,
config.max_ring_cap(), config.max_ring_cap(),
@@ -390,14 +372,10 @@ impl VirtualNic {
}); });
} }
if self.queue_num != 1 {
todo!("queue_num != 1")
}
config.queues(self.queue_num);
config.up(); config.up();
let _g = self.global_ctx.net_ns.guard(); let _g = self.global_ctx.net_ns.guard();
Ok(create_as_async(&config)?) Ok(tun::create(&config)?)
} }
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@@ -409,12 +387,11 @@ impl VirtualNic {
let mut config = Configuration::default(); let mut config = Configuration::default();
config.layer(Layer::L3); config.layer(Layer::L3);
config.raw_fd(tun_fd); config.raw_fd(tun_fd);
config.platform(|config| { config.close_fd_on_drop(false);
config.no_close_fd_on_drop(true);
});
config.up(); config.up();
let dev = create_as_async(&config)?; let dev = tun::create(&config)?;
let dev = AsyncDevice::new(dev)?;
let (a, b) = BiLock::new(dev); let (a, b) = BiLock::new(dev);
let ft = TunnelWrapper::new( let ft = TunnelWrapper::new(
TunStream::new(a, false), TunStream::new(a, false),
@@ -432,9 +409,11 @@ impl VirtualNic {
pub async fn create_dev(&mut self) -> Result<Box<dyn Tunnel>, Error> { pub async fn create_dev(&mut self) -> Result<Box<dyn Tunnel>, Error> {
let dev = self.create_tun().await?; let dev = self.create_tun().await?;
let ifname = dev.get_ref().name()?; let ifname = dev.tun_name()?;
self.ifcfg.wait_interface_show(ifname.as_str()).await?; self.ifcfg.wait_interface_show(ifname.as_str()).await?;
let dev = AsyncDevice::new(dev)?;
let flags = self.global_ctx.config.get_flags(); let flags = self.global_ctx.config.get_flags();
let mut mtu_in_config = flags.mtu; let mut mtu_in_config = flags.mtu;
if flags.enable_encryption { if flags.enable_encryption {
+8 -2
View File
@@ -205,7 +205,7 @@ impl FromUrl for SocketAddr {
fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> { fn from_url(url: url::Url, ip_version: IpVersion) -> Result<Self, TunnelError> {
let addrs = url.socket_addrs(|| None)?; let addrs = url.socket_addrs(|| None)?;
tracing::debug!(?addrs, ?ip_version, ?url, "convert url to socket addrs"); tracing::debug!(?addrs, ?ip_version, ?url, "convert url to socket addrs");
let mut addrs = addrs let addrs = addrs
.into_iter() .into_iter()
.filter(|addr| match ip_version { .filter(|addr| match ip_version {
IpVersion::V4 => addr.is_ipv4(), IpVersion::V4 => addr.is_ipv4(),
@@ -213,7 +213,13 @@ impl FromUrl for SocketAddr {
IpVersion::Both => true, IpVersion::Both => true,
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
addrs.pop().ok_or(TunnelError::NoDnsRecordFound(ip_version))
use rand::seq::SliceRandom;
// randomly select one address
addrs
.choose(&mut rand::thread_rng())
.copied()
.ok_or(TunnelError::NoDnsRecordFound(ip_version))
} }
} }
+2 -2
View File
@@ -9,9 +9,9 @@ exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"]
links = "tauri-plugin-vpnservice" links = "tauri-plugin-vpnservice"
[dependencies] [dependencies]
tauri = { version = "2.0.0-beta.23" } tauri = { version = "2.0.0-rc" }
serde = "1.0" serde = "1.0"
thiserror = "1.0" thiserror = "1.0"
[build-dependencies] [build-dependencies]
tauri-plugin = { version = "2.0.0-beta.18", features = ["build"] } tauri-plugin = { version = "2.0.0-rc", features = ["build"] }
@@ -88,9 +88,9 @@ class TauriVpnService : VpnService() {
builder.addDnsServer(dns) builder.addDnsServer(dns)
for (route in routes) { for (route in routes) {
val ipParts = ipv4Addr.split("/") val ipParts = route.split("/")
if (ipParts.size != 2) throw IllegalArgumentException("Invalid IP addr string") if (ipParts.size != 2) throw IllegalArgumentException("Invalid IP addr string")
builder.addAddress(ipParts[0], ipParts[1].toInt()) builder.addRoute(ipParts[0], ipParts[1].toInt())
} }
for (app in disallowedApplications) { for (app in disallowedApplications) {
+4 -4
View File
@@ -22,12 +22,12 @@
"pretest": "yarn build" "pretest": "yarn build"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": ">=2.0.0-beta.6" "@tauri-apps/api": "2.0.0-rc.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^11.1.6", "@rollup/plugin-typescript": "^11.1.6",
"rollup": "^4.9.6", "rollup": "^4.20.0",
"typescript": "^5.3.3", "tslib": "^2.6.3",
"tslib": "^2.6.2" "typescript": "^5.5.4"
} }
} }
+91 -91
View File
@@ -9,21 +9,21 @@ importers:
.: .:
dependencies: dependencies:
'@tauri-apps/api': '@tauri-apps/api':
specifier: '>=2.0.0-beta.6' specifier: 2.0.0-rc.0
version: 2.0.0-beta.14 version: 2.0.0-rc.0
devDependencies: devDependencies:
'@rollup/plugin-typescript': '@rollup/plugin-typescript':
specifier: ^11.1.6 specifier: ^11.1.6
version: 11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.3) version: 11.1.6(rollup@4.20.0)(tslib@2.6.3)(typescript@5.5.4)
rollup: rollup:
specifier: ^4.9.6 specifier: ^4.20.0
version: 4.18.1 version: 4.20.0
tslib: tslib:
specifier: ^2.6.2 specifier: ^2.6.3
version: 2.6.3 version: 2.6.3
typescript: typescript:
specifier: ^5.3.3 specifier: ^5.5.4
version: 5.5.3 version: 5.5.4
packages: packages:
@@ -49,88 +49,88 @@ packages:
rollup: rollup:
optional: true optional: true
'@rollup/rollup-android-arm-eabi@4.18.1': '@rollup/rollup-android-arm-eabi@4.20.0':
resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} resolution: {integrity: sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@rollup/rollup-android-arm64@4.18.1': '@rollup/rollup-android-arm64@4.20.0':
resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} resolution: {integrity: sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@rollup/rollup-darwin-arm64@4.18.1': '@rollup/rollup-darwin-arm64@4.20.0':
resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} resolution: {integrity: sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@rollup/rollup-darwin-x64@4.18.1': '@rollup/rollup-darwin-x64@4.20.0':
resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} resolution: {integrity: sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.18.1': '@rollup/rollup-linux-arm-gnueabihf@4.20.0':
resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.18.1': '@rollup/rollup-linux-arm-musleabihf@4.20.0':
resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.18.1': '@rollup/rollup-linux-arm64-gnu@4.20.0':
resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@rollup/rollup-linux-arm64-musl@4.18.1': '@rollup/rollup-linux-arm64-musl@4.20.0':
resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1': '@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.18.1': '@rollup/rollup-linux-riscv64-gnu@4.20.0':
resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.18.1': '@rollup/rollup-linux-s390x-gnu@4.20.0':
resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@rollup/rollup-linux-x64-gnu@4.18.1': '@rollup/rollup-linux-x64-gnu@4.20.0':
resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@rollup/rollup-linux-x64-musl@4.18.1': '@rollup/rollup-linux-x64-musl@4.20.0':
resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.18.1': '@rollup/rollup-win32-arm64-msvc@4.20.0':
resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.18.1': '@rollup/rollup-win32-ia32-msvc@4.20.0':
resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} resolution: {integrity: sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-msvc@4.18.1': '@rollup/rollup-win32-x64-msvc@4.20.0':
resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} resolution: {integrity: sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@tauri-apps/api@2.0.0-beta.14': '@tauri-apps/api@2.0.0-rc.0':
resolution: {integrity: sha512-YLYgHqdwWswr4Y70+hRzaLD6kLIUgHhE3shLXNquPiTaQ9+cX3Q2dB0AFfqsua6NXYFNe7LfkmMzaqEzqv3yQg==} resolution: {integrity: sha512-v454Qs3REHc3Za59U+/eSmBsdmF+3NE5+76+lFDaitVqN4ZglDHENDaMARYKGJVZuxiSkzyqG0SeG7lLQjVkPA==}
engines: {node: '>= 18.18', npm: '>= 6.6.0', yarn: '>= 1.19.1'} engines: {node: '>= 18.18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
'@types/estree@1.0.5': '@types/estree@1.0.5':
@@ -151,8 +151,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
is-core-module@2.14.0: is-core-module@2.15.0:
resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
path-parse@1.0.7: path-parse@1.0.7:
@@ -166,8 +166,8 @@ packages:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true hasBin: true
rollup@4.18.1: rollup@4.20.0:
resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} resolution: {integrity: sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
@@ -178,79 +178,79 @@ packages:
tslib@2.6.3: tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
typescript@5.5.3: typescript@5.5.4:
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
snapshots: snapshots:
'@rollup/plugin-typescript@11.1.6(rollup@4.18.1)(tslib@2.6.3)(typescript@5.5.3)': '@rollup/plugin-typescript@11.1.6(rollup@4.20.0)(tslib@2.6.3)(typescript@5.5.4)':
dependencies: dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.18.1) '@rollup/pluginutils': 5.1.0(rollup@4.20.0)
resolve: 1.22.8 resolve: 1.22.8
typescript: 5.5.3 typescript: 5.5.4
optionalDependencies: optionalDependencies:
rollup: 4.18.1 rollup: 4.20.0
tslib: 2.6.3 tslib: 2.6.3
'@rollup/pluginutils@5.1.0(rollup@4.18.1)': '@rollup/pluginutils@5.1.0(rollup@4.20.0)':
dependencies: dependencies:
'@types/estree': 1.0.5 '@types/estree': 1.0.5
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 2.3.1 picomatch: 2.3.1
optionalDependencies: optionalDependencies:
rollup: 4.18.1 rollup: 4.20.0
'@rollup/rollup-android-arm-eabi@4.18.1': '@rollup/rollup-android-arm-eabi@4.20.0':
optional: true optional: true
'@rollup/rollup-android-arm64@4.18.1': '@rollup/rollup-android-arm64@4.20.0':
optional: true optional: true
'@rollup/rollup-darwin-arm64@4.18.1': '@rollup/rollup-darwin-arm64@4.20.0':
optional: true optional: true
'@rollup/rollup-darwin-x64@4.18.1': '@rollup/rollup-darwin-x64@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.18.1': '@rollup/rollup-linux-arm-gnueabihf@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-arm-musleabihf@4.18.1': '@rollup/rollup-linux-arm-musleabihf@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-arm64-gnu@4.18.1': '@rollup/rollup-linux-arm64-gnu@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-arm64-musl@4.18.1': '@rollup/rollup-linux-arm64-musl@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1': '@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-riscv64-gnu@4.18.1': '@rollup/rollup-linux-riscv64-gnu@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-s390x-gnu@4.18.1': '@rollup/rollup-linux-s390x-gnu@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-x64-gnu@4.18.1': '@rollup/rollup-linux-x64-gnu@4.20.0':
optional: true optional: true
'@rollup/rollup-linux-x64-musl@4.18.1': '@rollup/rollup-linux-x64-musl@4.20.0':
optional: true optional: true
'@rollup/rollup-win32-arm64-msvc@4.18.1': '@rollup/rollup-win32-arm64-msvc@4.20.0':
optional: true optional: true
'@rollup/rollup-win32-ia32-msvc@4.18.1': '@rollup/rollup-win32-ia32-msvc@4.20.0':
optional: true optional: true
'@rollup/rollup-win32-x64-msvc@4.18.1': '@rollup/rollup-win32-x64-msvc@4.20.0':
optional: true optional: true
'@tauri-apps/api@2.0.0-beta.14': {} '@tauri-apps/api@2.0.0-rc.0': {}
'@types/estree@1.0.5': {} '@types/estree@1.0.5': {}
@@ -265,7 +265,7 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
is-core-module@2.14.0: is-core-module@2.15.0:
dependencies: dependencies:
hasown: 2.0.2 hasown: 2.0.2
@@ -275,34 +275,34 @@ snapshots:
resolve@1.22.8: resolve@1.22.8:
dependencies: dependencies:
is-core-module: 2.14.0 is-core-module: 2.15.0
path-parse: 1.0.7 path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0 supports-preserve-symlinks-flag: 1.0.0
rollup@4.18.1: rollup@4.20.0:
dependencies: dependencies:
'@types/estree': 1.0.5 '@types/estree': 1.0.5
optionalDependencies: optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.18.1 '@rollup/rollup-android-arm-eabi': 4.20.0
'@rollup/rollup-android-arm64': 4.18.1 '@rollup/rollup-android-arm64': 4.20.0
'@rollup/rollup-darwin-arm64': 4.18.1 '@rollup/rollup-darwin-arm64': 4.20.0
'@rollup/rollup-darwin-x64': 4.18.1 '@rollup/rollup-darwin-x64': 4.20.0
'@rollup/rollup-linux-arm-gnueabihf': 4.18.1 '@rollup/rollup-linux-arm-gnueabihf': 4.20.0
'@rollup/rollup-linux-arm-musleabihf': 4.18.1 '@rollup/rollup-linux-arm-musleabihf': 4.20.0
'@rollup/rollup-linux-arm64-gnu': 4.18.1 '@rollup/rollup-linux-arm64-gnu': 4.20.0
'@rollup/rollup-linux-arm64-musl': 4.18.1 '@rollup/rollup-linux-arm64-musl': 4.20.0
'@rollup/rollup-linux-powerpc64le-gnu': 4.18.1 '@rollup/rollup-linux-powerpc64le-gnu': 4.20.0
'@rollup/rollup-linux-riscv64-gnu': 4.18.1 '@rollup/rollup-linux-riscv64-gnu': 4.20.0
'@rollup/rollup-linux-s390x-gnu': 4.18.1 '@rollup/rollup-linux-s390x-gnu': 4.20.0
'@rollup/rollup-linux-x64-gnu': 4.18.1 '@rollup/rollup-linux-x64-gnu': 4.20.0
'@rollup/rollup-linux-x64-musl': 4.18.1 '@rollup/rollup-linux-x64-musl': 4.20.0
'@rollup/rollup-win32-arm64-msvc': 4.18.1 '@rollup/rollup-win32-arm64-msvc': 4.20.0
'@rollup/rollup-win32-ia32-msvc': 4.18.1 '@rollup/rollup-win32-ia32-msvc': 4.20.0
'@rollup/rollup-win32-x64-msvc': 4.18.1 '@rollup/rollup-win32-x64-msvc': 4.20.0
fsevents: 2.3.3 fsevents: 2.3.3
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
tslib@2.6.3: {} tslib@2.6.3: {}
typescript@5.5.3: {} typescript@5.5.4: {}