Compare commits

...

374 Commits

Author SHA1 Message Date
fad7bf8d8b Bump github.com/fatedier/frp from 0.64.0 to 0.65.0 (#267)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.64.0 to 0.65.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/Release.md)
- [Commits](https://github.com/fatedier/frp/compare/v0.64.0...v0.65.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-version: 0.65.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 10:17:07 +08:00
f628c39287 Update golangci-lint version 2025-09-29 09:54:58 +08:00
d119834272 Bump version to 1.24.0 2025-08-18 09:21:31 +08:00
a0d0c057f7 Support token string from external file (#264)
* Support token string from external file

* Validate token source before saving config file
2025-08-17 14:34:09 +08:00
1f5cf1df35 Bump github.com/fatedier/frp from 0.63.0 to 0.64.0 (#263)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.63.0 to 0.64.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.63.0...v0.64.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-version: 0.64.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 09:25:33 +08:00
033394c457 Fix invalid signtool path 2025-07-21 22:20:41 +08:00
87d5281816 Bump version to 1.23.0 2025-07-21 21:45:48 +08:00
7a8d1f59ba Remove the use of reflection to close log files 2025-07-19 18:38:50 +08:00
77eee96005 Remove unused functions 2025-07-19 17:03:33 +08:00
472df0cdfa Add server info to the properties dialog 2025-07-19 16:49:20 +08:00
4cdc9165be Support reinstallation of the same version 2025-07-16 14:30:06 +08:00
3730c0660b Support for ARM64 (#257)
* Support for ARM64

* Rename environment variable

* Switch to MinGW toolchain

* Fix invalid path

* Fix error handling

* Fix error level

* Delete installer/actions/actions.def
2025-07-12 22:08:03 +08:00
89bc3bd5ef Bump version to 1.22.0 2025-07-03 22:10:50 +08:00
8219d4b7e5 Rewrite custom actions in C (#253)
* Rewrite custom actions in C

* Add language setting file

* Improve setup

* Improve build script

* Skip directory selection on upgrade

* Fix dll symbols error on x86

* Validate install path

* Hide language selection dialog on upgrade

* Improve macros

* Add multi-language support to the setup

* Remove old files
2025-07-03 22:05:26 +08:00
a443bf41ba Use code signing for release files (#244)
* Use code signing for release files

* Use matrix strategy

* Fix incorrect directory structure

* Fix missing command option

* Fix invalid file checksum

* Fix missing imports

* Add code signing policy

* Update sponsor

* Remove SHA256 checksum

* Test release process

* Switch to release policy
2025-06-30 15:08:21 +08:00
b82b9691d6 Bump github.com/fatedier/frp from 0.62.1 to 0.63.0 (#252)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.62.1 to 0.63.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.62.1...v0.63.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-version: 0.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-26 09:41:42 +08:00
325a2e6a5e Add properties dialog (#250)
* Add properties dialog

* Resize window

* Count TCP and UDP connections

* Resize the first column

* Fix lint error
2025-06-25 10:03:22 +08:00
6a66bd3960 Use matrix strategy in build job 2025-06-19 21:28:14 +08:00
ef4362bd76 Fix stale proxy state after connection loss (#251) 2025-06-18 17:26:34 +08:00
b2c716163c Fix incorrect window size 2025-06-17 17:02:11 +08:00
32b2b7657c Update screenshots 2025-06-15 21:34:18 +08:00
d70ec0b76b Resize the startup window 2025-06-15 18:17:33 +08:00
44b7357ede Restore previous window state on launch (#247) 2025-06-13 09:58:27 +08:00
dc5516ecbd Add option to disable automatic checking for updates (#245)
* Add option to disable automatic checking for updates

* Fix test errors

* Rename json field
2025-06-12 11:21:30 +08:00
e486d9e0e8 Use rc to compile resources 2025-06-06 13:55:08 +00:00
472ee845b5 Store filename in variables 2025-06-06 12:43:48 +00:00
af9662389a Improve build script 2025-06-06 07:46:47 +00:00
5bac41a5e4 Fix missing version info 2025-06-06 03:09:29 +00:00
aa0481df98 Don't switch build environment in one file 2025-06-05 16:53:24 +00:00
5be9cca700 Decoupling build steps 2025-06-04 08:58:32 +00:00
5b48c95d91 Rename remote address to server address 2025-06-01 00:22:19 +08:00
f4fefdd53c Fix text overflow 2025-05-31 18:14:55 +08:00
cd79deaea7 Improve translation 2025-05-31 16:27:14 +08:00
bdeee903fc Remove wmic dependency in build script 2025-05-30 13:32:25 +00:00
8904aefbeb Update issue templates 2025-05-30 14:45:02 +08:00
ff073a40bc Use library instead of wmic to kill GUI processes (#242) 2025-05-27 16:18:34 +08:00
b211518500 Add wiki description 2025-05-26 14:17:45 +00:00
aa3e7bbd53 Fix typos 2025-05-25 00:22:49 +08:00
91d47bc960 Use English as default language in README 2025-05-24 15:42:51 +00:00
ecc96efe04 Bump version to 1.21.1 2025-05-08 15:24:37 +08:00
a91e34747b Remove proxy type indicator for QUIC 2025-05-08 00:55:28 +08:00
12382afb9f Remove .deps directory 2025-05-07 07:10:24 +00:00
c7fdce6cf5 New logo (#238)
* New logo

* Improve the about page

* Add files via upload

* Format svg files
2025-05-07 14:57:02 +08:00
1338bc6902 Bump github.com/fatedier/frp from 0.62.0 to 0.62.1 (#236)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.62.0 to 0.62.1.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.62.0...v0.62.1)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-version: 0.62.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-04 23:56:12 +08:00
5358c069f5 Bump version to 1.21.0 2025-04-18 15:17:24 +08:00
f371b107d5 Upgrade ui package 2025-04-18 11:15:13 +08:00
49d6555dad Bump github.com/fatedier/frp from 0.61.2 to 0.62.0 (#233)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.61.2 to 0.62.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/Release.md)
- [Commits](https://github.com/fatedier/frp/compare/v0.61.2...v0.62.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-version: 0.62.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-17 23:19:24 +08:00
82446fb0de Remove unused code 2025-04-16 15:49:19 +08:00
83449d3eb8 Fix translation of quick add dialog 2025-04-16 13:57:40 +08:00
c27ea5ef86 Display protocol and TLS status on the panel (#231)
* Display protocol and TLS status on the panel

* Improve proxy translation

* Upgrade ui package

* Update screenshots
2025-04-15 23:00:48 +08:00
54f94e829f Add list editor for array fields (#229)
* Add list editor for array fields

* Disable move button in edit mode
2025-04-13 18:58:17 +08:00
86cf25174b Add cue banner to text box 2025-04-12 16:45:48 +08:00
f0f659b2e5 Remove support for SVCB 2025-04-12 00:11:42 +08:00
67fe555d14 Fix missing icons on older Windows versions 2025-04-11 21:33:09 +08:00
889c8723c1 Show proxy status (#224)
* Show proxy status

* Fix incorrect proxy name when user is set

* Add proxy status description

* Use timer to make probe requests

* Stop tracker if the config page is not visible

* New timer logic

* Use buffered channel

* Add screenshots

* Fix incorrect remote address

* Set proxy state priority

* Increase pipe output buffer size
2025-04-11 00:17:25 +08:00
0a40d85471 Rename config state 2025-04-05 23:09:47 +08:00
f6aa8db84a Avoid unnecessary UI updates 2025-04-05 11:43:25 +08:00
00de0753c1 Bump github.com/fsnotify/fsnotify from 1.8.0 to 1.9.0 (#223)
Bumps [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/fsnotify/fsnotify/releases)
- [Changelog](https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fsnotify/fsnotify/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/fsnotify/fsnotify
  dependency-version: 1.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-05 10:13:19 +08:00
87687f1990 Improve UI operation of proxy table 2025-04-03 16:51:43 +08:00
1ee16a1b4c Use event-driven API to track service status (#222)
* Use event-driven API to track service status

* Fix cleanup order
2025-03-29 10:44:47 +08:00
7fbc420b64 Bump version to 1.20.0 2025-03-20 09:56:42 +08:00
e06f383b06 Remove service button (#221) 2025-03-19 16:49:07 +08:00
054135cf3d Allow multiple selections (#219) 2025-03-19 14:29:32 +08:00
e91c382b58 Force the use of the default log file (#218) 2025-03-16 21:07:28 +08:00
465621fb4d Bump golang.org/x/text from 0.22.0 to 0.23.0 (#213)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 16:18:50 +08:00
3a277208c4 Bump golang.org/x/sys from 0.30.0 to 0.31.0 (#215)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/sys/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 16:10:16 +08:00
32267b870a Bump github.com/fatedier/frp from 0.61.1 to 0.61.2 (#216)
* Bump github.com/fatedier/frp from 0.61.1 to 0.61.2

Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.61.1 to 0.61.2.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.61.1...v0.61.2)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update go version

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gerhard Tan <gwohau.tan@gmail.com>
2025-03-13 16:00:59 +08:00
2e7bc3546e Bump github.com/miekg/dns from 1.1.62 to 1.1.63 (#203)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.62 to 1.1.63.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.62...v1.1.63)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 15:32:15 +08:00
b3df3bf8c0 Support custom sort order in config list (#160)
* Support custom sort order in config list

* Fix data race
2025-02-10 15:34:00 +08:00
275876d204 Bump golang.org/x/text from 0.21.0 to 0.22.0 (#209)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.21.0 to 0.22.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-06 16:15:29 +08:00
5b292b88b5 Bump golang.org/x/sys from 0.29.0 to 0.30.0 (#208)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.29.0 to 0.30.0.
- [Commits](https://github.com/golang/sys/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-06 16:06:17 +08:00
bf239eed62 Bump golang.org/x/sys from 0.28.0 to 0.29.0 (#202)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.28.0 to 0.29.0.
- [Commits](https://github.com/golang/sys/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-01 22:51:55 +08:00
730f9411d8 Use event-driven instead of polling to read logs (#201)
* Use event-driven instead of polling to read logs

* Fix failed tests

* Add copy action in context menu

* Add a fixed time interval to read the log

* Scroll only after new lines are added

* Do not monitor backup logs

* Upgrade ui package version

* Reset model when visibility changes

* The open button is disabled by default

* Select all
2025-02-01 22:45:37 +08:00
a980ec29e5 Bump version to 1.19.2 2024-12-19 11:00:13 +08:00
9347641da0 Bump github.com/fatedier/frp from 0.61.0 to 0.61.1 (#199)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.61.0 to 0.61.1.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.61.0...v0.61.1)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-18 17:08:27 +08:00
6c763f33f1 Update Go version in go.mod 2024-12-18 16:57:32 +08:00
6d4cb1e2e5 Bump golang.org/x/text from 0.20.0 to 0.21.0 (#197)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 18:15:35 +08:00
90879d1922 Bump golang.org/x/sys from 0.27.0 to 0.28.0 (#196)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.27.0 to 0.28.0.
- [Commits](https://github.com/golang/sys/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 17:33:02 +08:00
2273d04d0e Bump golang.org/x/text from 0.19.0 to 0.20.0 (#191)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-17 14:30:19 +08:00
bab185f279 Bump golang.org/x/sys from 0.26.0 to 0.27.0 (#192)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/sys/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-17 11:43:08 +08:00
af38e165b5 Bump version to 1.19.1 2024-10-23 14:50:40 +08:00
d51515831b Fix linting errors 2024-10-23 14:45:58 +08:00
8a268e5ae0 Use go1.23 2024-10-23 06:18:09 +00:00
257a47405b Bump github.com/fatedier/frp from 0.60.0 to 0.61.0 (#189)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.60.0 to 0.61.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.60.0...v0.61.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-23 10:39:58 +08:00
9a007b1fd5 Bump golang.org/x/sys from 0.25.0 to 0.26.0 (#188)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/sys/compare/v0.25.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 15:55:44 +08:00
e9ae8babaf Bump golang.org/x/text from 0.18.0 to 0.19.0 (#187)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 17:08:17 +08:00
0edc0dc305 Bump golang.org/x/sys from 0.24.0 to 0.25.0 (#185)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/sys/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 20:31:29 +08:00
d8d29a95c9 Bump golang.org/x/text from 0.17.0 to 0.18.0 (#184)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.17.0 to 0.18.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-06 19:37:39 +08:00
70a48ec331 Validate the local address of tls2raw client plugin 2024-08-20 17:17:26 +08:00
e7c0ae9a8b Bump version to 1.19.0 2024-08-20 15:31:17 +08:00
20a4af6abb Show plugin local address in proxy list (#181) 2024-08-20 15:29:46 +08:00
7ac8f62338 Add tls2raw client plugin (#180) 2024-08-20 14:26:11 +08:00
d98ec5052f Bump github.com/fatedier/frp from 0.59.0 to 0.60.0 (#179)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.59.0 to 0.60.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.59.0...v0.60.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-20 08:19:13 +08:00
07bf7ac380 Bump github.com/miekg/dns from 1.1.61 to 1.1.62 (#178)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.61 to 1.1.62.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.61...v1.1.62)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 15:27:52 +08:00
c2559a0f27 Bump golang.org/x/sys from 0.22.0 to 0.24.0 (#176)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.22.0 to 0.24.0.
- [Commits](https://github.com/golang/sys/compare/v0.22.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 15:18:26 +08:00
1ab5cd4c12 Bump golang.org/x/text from 0.16.0 to 0.17.0 (#175)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.16.0 to 0.17.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-19 15:08:37 +08:00
94101a6dfe Update screenshots 2024-07-13 22:41:17 +08:00
75bc7ca36d Bump version to 1.18.0 2024-07-11 09:17:33 +08:00
dd00e173b4 Add HTTP/2 option in plugin https2http and https2https (#166) 2024-07-10 14:53:03 +08:00
2c7fe912ce Add http2http client plugin (#165)
* Add http2http client plugin

* Reorder plugin types
2024-07-10 13:55:11 +08:00
6bf7b8b206 Bump github.com/fatedier/frp from 0.58.1 to 0.59.0 (#164)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.58.1 to 0.59.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.58.1...v0.59.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 08:53:53 +08:00
c6fb2c69f9 Bump golang.org/x/sys from 0.21.0 to 0.22.0 (#163)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/sys/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 14:52:24 +08:00
50e597be97 Support custom sort order in proxy list (#158)
* Support custom sort order in proxy list

* Dispose bitmap with defer

* Fix incorrect sort order when moving multiple steps

* Change icon

* Remove icons
2024-06-25 17:58:45 +08:00
f992bb4b0a Bump github.com/miekg/dns from 1.1.59 to 1.1.61 (#156)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.59 to 1.1.61.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.59...v1.1.61)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 11:06:26 +08:00
4aa3ce04c9 Bump golang.org/x/text from 0.15.0 to 0.16.0 (#155)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 21:44:08 +08:00
58981baa42 Bump golang.org/x/sys from 0.20.0 to 0.21.0 (#154)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/sys/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-05 21:35:37 +08:00
8f79619edc Remove Windows 7 from README 2024-05-31 08:53:02 +00:00
3a533ac00f Bump github.com/fatedier/frp from 0.58.0 to 0.58.1 (#153)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.58.0 to 0.58.1.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.58.0...v0.58.1)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-31 16:07:35 +08:00
635ed0b083 The VersionNT value is 603 in Windows 10 2024-05-31 15:08:51 +08:00
69ed06542b Bump version to 1.17.0 2024-05-15 09:49:38 +08:00
629a51071d Improve translations 2024-05-15 00:34:27 +08:00
74e4af976f Drop support for Windows 7 2024-05-12 01:38:19 +08:00
812aac31b5 Remove response headers for proxy type http in legacy format 2024-05-11 01:20:42 +08:00
279f74180c Support response headers for proxy type http (#151) 2024-05-09 11:24:10 +08:00
e1109e946f Bump github.com/fatedier/frp from 0.57.0 to 0.58.0 (#150)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.57.0 to 0.58.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/Release.md)
- [Commits](https://github.com/fatedier/frp/compare/v0.57.0...v0.58.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 01:40:22 +08:00
e0e731b2be Bump golang.org/x/sys from 0.19.0 to 0.20.0 (#149)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/sys/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 01:34:47 +08:00
b2645546db Bump golang.org/x/text from 0.14.0 to 0.15.0 (#148)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 01:27:42 +08:00
f342a37801 Use real file extensions for exported config files (#145) 2024-04-22 09:48:02 +08:00
c32881f759 Bump github.com/miekg/dns from 1.1.58 to 1.1.59 (#144)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.58 to 1.1.59.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.58...v1.1.59)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-20 14:10:16 +08:00
33f7d115c4 Bump github.com/fatedier/golib from 0.4.2 to 0.4.3 (#140)
Bumps [github.com/fatedier/golib](https://github.com/fatedier/golib) from 0.4.2 to 0.4.3.
- [Commits](https://github.com/fatedier/golib/compare/v0.4.2...v0.4.3)

---
updated-dependencies:
- dependency-name: github.com/fatedier/golib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 02:15:27 +08:00
b7179bd824 Bump github.com/fatedier/frp from 0.56.0 to 0.57.0 (#139)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.56.0 to 0.57.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.56.0...v0.57.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 12:29:08 +08:00
9607978501 Bump golang.org/x/sys from 0.18.0 to 0.19.0 (#138)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/sys/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 15:50:06 +08:00
da6523413a Improve zh-TW translations (#136)
* Update messages.gotext.json

* Update messages.gotext.json

* Update messages.gotext.json
2024-03-28 15:50:17 +08:00
7088e62ab4 Support custom request headers in http health check (#135) 2024-03-25 13:39:07 +08:00
cc347d64bb Bump github.com/fatedier/frp from 0.55.1 to 0.56.0 (#133)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.55.1 to 0.56.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.55.1...v0.56.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-24 13:48:39 +08:00
4534dd1650 Support proxy annotations (#134) 2024-03-24 13:12:39 +08:00
bc4d6e61fb Bump github.com/fatedier/frp from 0.54.0 to 0.55.1 (#129)
* Bump github.com/fatedier/frp from 0.54.0 to 0.55.1

Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.54.0 to 0.55.1.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.54.0...v0.55.1)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix logger

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gerhard Tan <gwohau.tan@gmail.com>
2024-03-21 00:49:30 +08:00
b35770b029 Support go1.22 and remove legacy patch 2024-03-21 00:30:33 +08:00
dd0e3cb790 Fix missing GOPATH variable in releaser workflow (#131)
* Fix missing GOPATH variable in releaser workflow

* Downgrade setup-go to v4

* Set GOPATH manually

* Set GOPATH manually

* Upgrade setup-go to v5
2024-03-17 14:21:48 +08:00
da9cd0235b Bump version to 1.16.0 2024-03-16 18:12:59 +08:00
69a5e9a008 Fix incorrect use of default configuration 2024-03-15 03:26:10 +08:00
afea139a79 Update screenshots 2024-03-15 02:51:02 +08:00
95731e592b Reuse binding object 2024-03-15 01:06:28 +08:00
d0bbdb201b Rollback to go1.21.4 2024-03-14 15:12:30 +08:00
b66050952d Fix spaces 2024-03-14 14:59:16 +08:00
d3062942d0 Add conversion patch 2024-03-14 06:55:41 +00:00
5a0d517559 Use NumberEdit for integer fields 2024-03-14 02:16:19 +08:00
3981230629 Allow setting default configuration format (#127)
* Allow setting default configuration format

* Fix spaces
2024-03-11 20:53:36 +08:00
77453e1448 Make TOML and INI formats switchable in a single configuration (#124)
* Make TOML and INI formats switchable in a single configuration

* Fix type assertion

* Fix unit tests

* Validate proxy and support for ranged ports in TOML format

* Add greedy layout for number edit

* Fix json tag
2024-03-10 02:01:41 +08:00
702e4472a8 Bump golang.org/x/sys from 0.17.0 to 0.18.0 (#125)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/sys/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-10 00:47:16 +08:00
e70a646f78 Make icon aware of DPI changes in validation dialog 2024-02-27 06:04:06 +00:00
b548490e0f Ensure a gap between two shapes of the copy icon 2024-02-27 03:28:02 +00:00
2a8a494ff7 Use system icons instead of embedded icons 2024-02-27 02:47:28 +08:00
f54cc4f898 Support go1.22 and upgrade GitHub Actions version 2024-02-22 03:22:30 +00:00
4f2f962564 New icons for quick add menu and random name button 2024-02-22 03:00:46 +00:00
4b61e30782 Replace go-funk with lo 2024-02-22 02:34:48 +00:00
10db5b089d Add request header, oidc parameter and metadata dialog (#123) 2024-02-22 09:23:01 +08:00
b942f13fc7 Support TOML/YAML/JSON format files (#121)
* Support TOML/YAML/JSON format files

* Fix start error for non-ini files

* Rename config files after upgrade

* Fix admin server config and ui

* Update package
2024-02-20 22:31:48 +08:00
5b5939c33c Prevent random names from removing range prefix 2024-02-17 23:01:48 +08:00
e4822ac7cb Upload the build output of pull requests (#122)
* Upload the build output of pull requests

* Fix incorrect commit sha
2024-02-17 20:44:38 +08:00
26186cadbf Bump frp from 0.51.3 to 0.54.0 (#120) 2024-02-10 18:07:09 +08:00
88113a286e Bump github.com/miekg/dns from 1.1.57 to 1.1.58 (#117)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.57 to 1.1.58.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.57...v1.1.58)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-09 20:07:24 +08:00
1ae571f05c Bump golang.org/x/sys from 0.16.0 to 0.17.0 (#119)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/sys/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-09 19:57:47 +08:00
ac221a25a0 Bump golang.org/x/sys from 0.15.0 to 0.16.0 (#115)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/sys/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-06 21:41:09 +08:00
52f8e5417c Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#111)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 17:05:37 +08:00
cfe72be589 Bump golang.org/x/sys from 0.14.0 to 0.15.0 (#106)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/sys/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-29 13:39:15 +08:00
4eea47a7e3 Bump github.com/go-jose/go-jose/v3 from 3.0.0 to 3.0.1 (#105)
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/v3/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-23 09:19:37 +08:00
a3d1244824 Show disable dialog only if config is started 2023-11-20 01:47:08 +00:00
b341aa1ade Fix msgbox style 2023-11-20 01:25:00 +00:00
53fd30be75 Bump version to 1.15.1 2023-11-20 01:16:51 +08:00
06dfeff9ba Remove lang file in archive 2023-11-20 01:06:38 +08:00
9d7e291762 Add confirmation dialog for stopping configuration (#104) 2023-11-20 00:51:03 +08:00
2071fde7d0 Bump golang.org/x/sys from 0.13.0 to 0.14.0 (#101)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/sys/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 00:50:00 +08:00
f4edec67f1 Bump github.com/miekg/dns from 1.1.56 to 1.1.57 (#102)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.56 to 1.1.57.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.56...v1.1.57)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 00:44:01 +08:00
cb07d0897e Bump golang.org/x/text from 0.13.0 to 0.14.0 (#100)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 00:37:56 +08:00
a8ec3c2a72 Bump golang.org/x/net from 0.15.0 to 0.17.0 (#96)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-14 16:43:21 +08:00
bcbd378fd2 Bump golang.org/x/sys from 0.12.0 to 0.13.0 (#92)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/sys/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-07 10:30:41 +08:00
85dd6a8e1c Bump github.com/miekg/dns from 1.1.55 to 1.1.56 (#89)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.55 to 1.1.56.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.55...v1.1.56)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 12:33:46 +08:00
e622ba5b77 Bump golang.org/x/text from 0.12.0 to 0.13.0 (#87)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.12.0 to 0.13.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 08:29:02 +08:00
97ffd12dea Bump golang.org/x/sys from 0.11.0 to 0.12.0 (#86)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/sys/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-05 08:14:07 +08:00
4e8ee741ab Add portable (standalone) build (#85)
* Add portable (standalone) build

* Replace '/' with '\'
2023-08-31 22:05:58 +08:00
13e3d48336 Bump github.com/go-ole/go-ole from 1.2.6 to 1.3.0 (#83)
Bumps [github.com/go-ole/go-ole](https://github.com/go-ole/go-ole) from 1.2.6 to 1.3.0.
- [Release notes](https://github.com/go-ole/go-ole/releases)
- [Changelog](https://github.com/go-ole/go-ole/blob/master/ChangeLog.md)
- [Commits](https://github.com/go-ole/go-ole/compare/v1.2.6...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-ole/go-ole
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 09:29:16 +08:00
b38ae91853 Allow empty port in SVCB RR 2023-08-16 08:30:08 +00:00
1c60fa82bd Bump version to 1.15.0 2023-08-14 18:47:19 +08:00
5945ca3460 Workflow: build with go1.21 2023-08-14 10:07:33 +00:00
e45e02f7ca Bump github.com/fatedier/frp from 0.51.2 to 0.51.3 (#81)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.51.2 to 0.51.3.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.51.2...v0.51.3)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 17:44:26 +08:00
f863abab62 Add experimental support for SVCB RR (#80)
* Add experimental support for SVCB RR

* Resolve SVCB and improve address buffer initialization

* Add experimental features dialog to UI

* Add translation for experimental features

* Added translation for SVCB records in various locales

* Implemented exponential backoff for SVCB lookups
2023-08-12 15:05:16 +08:00
ed5a19b747 Bump golang.org/x/text from 0.11.0 to 0.12.0 (#78)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 12:15:59 +08:00
1a7a243e7f Bump golang.org/x/sys from 0.10.0 to 0.11.0 (#79)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/sys/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 11:32:42 +08:00
57755c2b12 Bump version to 1.14.2 2023-07-26 09:10:14 +08:00
11c03686c6 Bump github.com/fatedier/frp from 0.51.1 to 0.51.2 (#76)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.51.1 to 0.51.2.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.51.1...v0.51.2)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-26 01:40:11 +08:00
43b91c0754 Fix typos 2023-07-23 02:24:22 +08:00
4f4e846034 Bump version to 1.14.1 2023-07-21 14:21:33 +08:00
9c4b998658 Bump github.com/fatedier/frp from 0.51.0 to 0.51.1 (#75)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.51.0 to 0.51.1.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.51.0...v0.51.1)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 08:22:31 +08:00
c1d2dbfac8 Get FRP version by code (#73) 2023-07-12 09:40:26 +08:00
8ef6bca68c Fix typos 2023-07-07 10:53:17 +08:00
c58976a4d4 Bump version to 1.14.0 2023-07-07 02:45:18 +08:00
812e4e16ca Complete proxy config before validation 2023-07-07 02:36:00 +08:00
1b0a810ffc Support more visitor parameters in proxy dialog (#70)
* Support more visitor parameters in proxy dialog

* Fix typos

* Fix lint errors

* Update walk package

* Add translations
2023-07-07 00:46:14 +08:00
664c8d492c Support wss protocol (#72) 2023-07-06 20:33:47 +08:00
b757854ba0 Bump github.com/fatedier/frp from 0.50.0 to 0.51.0 (#69)
* Bump github.com/fatedier/frp from 0.50.0 to 0.51.0

Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.50.0 to 0.51.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/.goreleaser.yml)
- [Commits](https://github.com/fatedier/frp/compare/v0.50.0...v0.51.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix client service

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gerhard Tan <gwohau.tan@gmail.com>
2023-07-06 13:56:06 +08:00
25257c4eab Enable TLS by default (#71) 2023-07-06 13:54:43 +08:00
56d6abbfd1 Bump golang.org/x/sys from 0.9.0 to 0.10.0 (#68)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/sys/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 01:50:36 +08:00
dc8a23d53e Bump golang.org/x/text from 0.10.0 to 0.11.0 (#67)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-06 01:17:21 +08:00
a237c074a2 Bump github.com/fatedier/frp from 0.49.0 to 0.50.0 (#66)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.49.0 to 0.50.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/Release.md)
- [Commits](https://github.com/fatedier/frp/compare/v0.49.0...v0.50.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-27 11:53:52 +08:00
db21d1297e Bump golang.org/x/text from 0.9.0 to 0.10.0 (#65)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 11:54:17 +08:00
9d66447fd9 Bump golang.org/x/sys from 0.8.0 to 0.9.0 (#64)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/sys/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-13 08:23:07 +08:00
593ae3f556 Use custom default STUN server instead of the one in frp 2023-06-08 09:36:52 +08:00
be5bbffdf2 Add NAT discovery tool (#63)
* Add NAT discovery tool

* Update nat icon

* Add translations

* Add default STUN server setting

* Fix typos
2023-06-08 09:02:58 +08:00
8302e3a3fd Change service name and description (#61)
* Change service name and description

* Remove old service after upgrade
2023-05-31 00:04:39 +08:00
d369849b7f Bump github.com/fatedier/frp from 0.48.0 to 0.49.0 (#60)
Bumps [github.com/fatedier/frp](https://github.com/fatedier/frp) from 0.48.0 to 0.49.0.
- [Release notes](https://github.com/fatedier/frp/releases)
- [Changelog](https://github.com/fatedier/frp/blob/dev/Release.md)
- [Commits](https://github.com/fatedier/frp/compare/v0.48.0...v0.49.0)

---
updated-dependencies:
- dependency-name: github.com/fatedier/frp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 02:19:58 +08:00
dc6f370d3b Update dependencies 2023-05-24 01:16:37 +08:00
0c30b02171 Add prefix to custom configuration 2023-05-24 01:04:57 +08:00
6135f98e51 Bump version to 1.13.0 2023-05-21 22:39:45 +08:00
b7154f1656 Fix import ordering (#58) 2023-05-18 17:26:28 +08:00
fd224d7a70 Hot-Reloading proxy configuration (#57) 2023-05-18 16:15:32 +08:00
9fefbadd68 Bump golang.org/x/sys from 0.7.0 to 0.8.0 (#54)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/sys/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-05 12:13:21 +08:00
a51fd8fc3a Bump golang.org/x/text from 0.8.0 to 0.9.0 (#52)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-07 12:53:36 +08:00
445cc8d653 Bump golang.org/x/sys from 0.6.0 to 0.7.0 (#51)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 14:36:51 +08:00
2b0b5613c0 Resize plugin proxy dialog 2023-03-27 01:53:58 +08:00
f541c5e621 Add golangci-lint linter and go tests (#50)
* Add golangci-lint linter and go tests

* Fix linter

* Fix triggers

* Update releaser.yml

* Remove env

* Rename tests

* Add tests

* Fix timezone
2023-03-24 15:48:01 +08:00
5dd73a8388 Build 32-bit binaries (#49)
* Build 32-bit binaries

* Update README

* Update release workflow

* Update release workflow

* Update release workflow

* Update release workflow

* Update release workflow

* Update release workflow

* Update release workflow

* Add step name

* Remove workflow triggers

* Change upload asset action

* Add artifact name

* Use conditional job

* Fix workflow warnings

* Update release workflow

* Fix space

* Update releaser.yml
2023-03-22 17:51:00 +08:00
e194ec87d4 Set default value of delete method 2023-03-18 23:11:24 +08:00
b75302eedc Support automatic deletion of config after an absolute date (#46)
* Support automatic deletion of config after an absolute date

* Replace string literals with consts
2023-03-17 15:33:48 +08:00
7f1c8de1d7 Use "go generate" instead of batch script (#45) 2023-03-14 02:10:39 +08:00
52dfa4bdd9 Fix sidebar width when DPI is changed (#44)
* Fix sidebar width when DPI is changed

* Remove patches
2023-03-13 00:11:20 +08:00
2fd2bb691a Bump golang.org/x/sys from 0.5.0 to 0.6.0 (#42)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 21:49:45 +08:00
a855cc34e8 Bump github.com/thoas/go-funk from 0.9.2 to 0.9.3 (#41)
Bumps [github.com/thoas/go-funk](https://github.com/thoas/go-funk) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/thoas/go-funk/releases)
- [Changelog](https://github.com/thoas/go-funk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/thoas/go-funk/compare/v0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: github.com/thoas/go-funk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 21:46:35 +08:00
f631c188e4 Bump golang.org/x/text from 0.7.0 to 0.8.0 (#40)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 21:46:11 +08:00
5e4962b7e1 Create dependabot.yml 2023-03-08 21:42:27 +08:00
6bf1b6166f Bump version to 1.12.0 2023-03-08 20:58:58 +08:00
30099655ba Update release workflow 2023-03-08 20:56:06 +08:00
9432b31d37 Bump frp version to 0.48.0 (#39) 2023-03-08 20:51:49 +08:00
f0ab389044 Fix import ordering 2023-03-08 19:12:43 +08:00
ed96a91b9e Support authentication in tcpmux (#38) 2023-03-08 18:49:06 +08:00
55d56d3c11 Add an open port action in quick add menu (#37)
* Add an open port action in quick add menu

* Add tooltip to show error

* Update passwd.go
2023-03-08 18:20:39 +08:00
a5ef9a7a16 Bump golang.org/x/net from 0.4.0 to 0.7.0 (#36)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.4.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-07 17:34:51 +08:00
a11202bc0b Support automatic deletion of config after x days (#34)
* Support automatic deletion after x days

* Add a clock icon suffix

* Update README

* Rename config model
2023-03-06 15:02:27 +08:00
240a5f02ab Password protection, masked password and configurable default values (#33)
* Password protection, masked password and setting default value

* Update README

* Update screenshots

* Update app name reference

* Update display name of service
2023-03-01 13:56:38 +08:00
317738b8a9 Update CHANGELOG.md 2023-02-10 16:08:34 +08:00
337401938e Fix typos 2023-02-10 15:48:39 +08:00
a41f2fd895 Bump version to 1.11.0 2023-02-10 15:43:19 +08:00
a92e8667ae Support bandwidth_limit_mode option 2023-02-10 15:35:18 +08:00
2fe84fc26c Bump frp version to 0.47.0 2023-02-10 14:37:01 +08:00
d823b22c44 Support go1.20 2023-02-10 04:46:20 +08:00
801b9ce2d9 Set background color for disabled proxy 2023-02-03 14:24:41 +08:00
2141a11ec8 Show blue config name when config is set to start manually 2023-01-31 12:38:39 +08:00
2ff9a81e29 Use underscore instead of dash 2023-01-28 00:21:21 +08:00
ac1d47c6ed Show item name in dialog title 2023-01-22 13:54:23 +08:00
0d2d0377a9 View old history log files (#30) 2023-01-16 01:34:31 +08:00
b478e98af2 Fix uninstallation error caused by file removal 2023-01-12 01:48:32 +08:00
58991ba509 Bump version to 1.10.1 2023-01-10 22:08:50 +08:00
1567b0f860 Allow downgrades 2023-01-10 21:53:42 +08:00
58e0a55b64 Bump frp version to 0.46.1 2023-01-10 21:17:58 +08:00
d762fa81f5 Refactor: align grid 2023-01-09 15:17:48 +08:00
d0c7ae6e8d Remove dns patch (#29) 2023-01-01 16:23:39 +08:00
b81f7d7094 Add frp version badge 2022-12-21 17:38:38 +08:00
fd7f70ab1c Bump version to 1.10.0 2022-12-19 16:26:44 +08:00
31158ee3ec Bump frp version to 0.46.0 2022-12-19 16:07:26 +08:00
485ec3a44f Fix empty oidc scope 2022-12-19 14:38:14 +08:00
7d5a704b7d Support quic options 2022-12-19 01:43:12 +08:00
55e4a851c2 Remove workflow status badge 2022-12-16 17:28:59 +08:00
ec1acafc86 Add OIDC scope parameter 2022-12-14 23:01:39 +08:00
408d8d5ec9 Display visitor parameters in table (#28) 2022-12-11 14:09:56 +08:00
db5e1a1137 Bump version to 1.9.2 2022-10-27 16:05:11 +08:00
ac31290324 Build with go1.19 2022-10-27 15:59:58 +08:00
06baf7b66e Bump frp version to 0.45.0 2022-10-27 15:56:08 +08:00
0a4954d3fb Bump version to 1.9.1 2022-07-11 09:26:14 +08:00
d2273faf04 Bump frp version to 0.44.0 2022-07-11 09:19:56 +08:00
2939eaa70a Workflows: fix changelog reader 2022-07-01 10:45:27 +08:00
0d1b519fac Bump version to 1.9.0 2022-07-01 10:24:08 +08:00
650d56c2ac Rename dialog title 2022-06-25 20:58:47 +08:00
da76780447 Update screenshots 2022-06-23 10:34:42 +08:00
adbdb29289 Move update URL to consts 2022-06-22 01:30:17 +08:00
6652cc21e1 Resize HTTP file dialog 2022-06-21 09:36:51 +08:00
b878e69b50 Resize main window 2022-06-20 10:36:17 +08:00
c91c91e1c4 Combine subdomain and custom domains into one column 2022-06-19 00:23:32 +08:00
780045f904 Fix regexp for non-empty string 2022-06-17 09:32:46 +08:00
df5fa9261f Import config from URL (#21)
* Import config from URL

* Fix error canceling url import
2022-06-16 20:07:22 +08:00
2d24b0ff5b Feat: create a copy and share link of config 2022-06-10 17:51:26 +08:00
d46d1ad268 Fix font issue on Windows 7 2022-06-08 10:59:01 +08:00
edc2be9f67 Use MsiTran to avoid localization issues 2022-06-07 17:00:38 +08:00
64bcfc7244 Place config files in profiles directory 2022-06-06 00:39:42 +08:00
bdc847283a Fix missing translations 2022-06-05 22:37:16 +08:00
0a48f45500 Support more display languages (#20)
* Support more display languages

* Added English readme translations

* Add support for Spanish

* Fix dialog width
2022-06-05 15:59:50 +08:00
89d8ce7126 Bump version to 1.8.1 2022-05-28 22:20:27 +08:00
7a50e2a9e4 Add route_by_http_user option 2022-05-28 22:05:56 +08:00
fe08a6ab98 Bump frp version to 0.43.0 2022-05-28 21:55:51 +08:00
7aae7cb217 Provide fractional icon sizes 2022-05-16 00:43:01 +08:00
ee616b0ab2 Fix some typos 2022-05-16 00:40:33 +08:00
68f15e055a Workflow: fix version info 2022-05-15 15:40:00 +08:00
d2218a1f3f Workflow: read version info 2022-05-15 15:15:37 +08:00
f2ad65af96 Bump version to 1.8.0 2022-05-15 14:46:54 +08:00
61c2949a3f Update README.md 2022-05-15 14:06:08 +08:00
1e01152ba6 Update README.md 2022-05-15 14:01:51 +08:00
d6cd68d13c Reveal config file in explorer 2022-05-13 11:38:33 +08:00
8f7d659e30 Fix some typos 2022-05-11 15:53:45 +08:00
60a5c2b1b7 Update screenshots 2022-05-11 01:31:40 +08:00
de76d3ee25 Display message after config imported 2022-05-11 01:13:16 +08:00
2ca07ca152 Minor UI improvements 2022-05-11 00:47:08 +08:00
28ea74d70a Support the drag-and-drop method to import config 2022-05-10 11:12:48 +08:00
642eb752fa Use ShellExecute to open file 2022-05-10 01:22:50 +08:00
52a34e1b5c Adjust ini load options 2022-05-10 01:03:19 +08:00
af9dc3c1b5 Import proxy from clipboard 2022-05-09 09:30:28 +08:00
07fce91c41 Fix clipboard import error 2022-05-08 12:52:48 +08:00
43635be88b Import config from clipboard 2022-05-07 14:39:49 +08:00
eeedfb5a99 Various UI improvements 2022-05-07 11:26:00 +08:00
974a825924 Fix about-page spacing 2022-05-06 03:26:09 +08:00
1f5a774480 Fix label status line 2022-05-06 03:16:20 +08:00
615a2bd4e0 Fix panel view spacing 2022-05-06 02:15:26 +08:00
50070962d1 Add medium text font 2022-05-05 17:35:49 +08:00
6bd2fb5377 Fix UI spacing 2022-05-05 17:19:13 +08:00
4898fcf532 New update checker 2022-05-05 14:09:25 +08:00
d244012bf0 Fix some indents 2022-05-04 00:57:16 +08:00
1f4e516586 Remove license dialog 2022-05-04 00:35:48 +08:00
c8a0210b73 Refine version info 2022-05-03 18:42:49 +08:00
18eccc4e45 Reduce compiled file size 2022-05-02 13:54:26 +08:00
c959c89da0 Show default address 2022-04-23 15:17:58 +08:00
156ddf6d36 Verify config before copying file 2022-04-23 15:13:26 +08:00
2a35ae74be Bump version to v1.7.2 2022-04-22 15:52:34 +08:00
cb64ee108a Bump frp version to 0.42.0 2022-04-22 15:41:06 +08:00
77a4605259 Update release note 2022-04-13 16:41:06 +08:00
6a45d44f1b Update screenshots 2022-04-13 16:32:16 +08:00
716a0a2bb1 Bump package version 2022-04-13 15:38:05 +08:00
d5939a4039 Fix error installing service 2022-04-07 17:01:04 +08:00
298e9a61d0 Fix some typos and indents 2022-04-03 01:09:00 +08:00
7c5e34eb22 Update README 2022-04-02 21:31:13 +08:00
495ce30ba5 Use COM to display service property dialog 2022-04-02 17:03:05 +08:00
5d9bc21028 Adjust text description and some view size 2022-04-02 14:25:48 +08:00
25ccd3a2dd Add "range:" prefix when range port is being used 2022-04-01 17:11:41 +08:00
a6a9928e8b Add toggle proxy action 2022-04-01 15:47:38 +08:00
d6b39720d9 Scroll to bottom when new proxies are added 2022-03-31 09:24:19 +08:00
bd6c38144c Reformat code 2022-03-30 13:33:58 +08:00
40d7e5a7db Verify proxy 2022-03-30 13:29:22 +08:00
e15281e4f1 Verify config before starting service 2022-03-30 13:26:19 +08:00
91931e2eb3 Update FUNDING.yml 2022-03-30 11:00:17 +08:00
90e631dabe Create FUNDING.yml 2022-03-30 10:56:47 +08:00
ce3ac4158f Update screenshots 2022-03-29 15:28:25 +08:00
1c344228a4 Update screenshots 2022-03-29 15:22:20 +08:00
1dbde9dbca Add copy access address 2022-03-29 13:37:58 +08:00
9ea1882359 Adjust edit conf dialog width 2022-03-29 09:54:32 +08:00
ea6cc8bfa5 Add validators 2022-03-27 15:43:39 +08:00
ecd967c60b Add udp_packet_size setting 2022-03-27 14:58:14 +08:00
2acb215ae1 Add more auth settings 2022-03-27 14:54:52 +08:00
a0cefbd874 Add TLS setting 2022-03-27 13:31:31 +08:00
31d663699e Update filters and browse line edit 2022-03-27 13:25:22 +08:00
a585094e76 Add assets_dir setting 2022-03-27 02:51:38 +08:00
0158080593 Add heartbeat and mux settings 2022-03-27 02:31:57 +08:00
60e7914bac Use lazy dll 2022-03-27 02:22:22 +08:00
96dcdf006e Change the title of quick-add dialog 2022-03-27 01:16:17 +08:00
a42ac95bb3 Use basic dialog 2022-03-27 01:09:22 +08:00
dbfa44ef18 Add radio button group 2022-03-27 00:27:45 +08:00
22c26bc22b Change edit dialog icon 2022-03-26 17:26:17 +08:00
0625fec50d Support more quick-add actions 2022-03-26 17:20:58 +08:00
2c7fe1f48f Prevent creation of multiple GUI instances 2022-03-25 15:04:17 +08:00
638c5c0769 Update build date 2022-03-24 16:42:51 +08:00
47ea387a5c Workflow: build with go1.18 2022-03-24 15:49:12 +08:00
85587becc7 Update release note 2022-03-24 15:44:51 +08:00
56272f9277 Add dial_server_keepalive and pprof_enable 2022-03-24 15:24:27 +08:00
198dd1b03e Bump frp version to 0.41.0 2022-03-24 15:18:22 +08:00
a565e552c1 Remove redundant tidy command 2022-03-24 14:13:26 +08:00
95fc7773d9 Disable autofill on log file that changed manually 2022-03-24 14:12:02 +08:00
92fe1be9b5 Add file dialog on LineEdit 2022-03-24 14:07:41 +08:00
858d614ede Fix import config issue for multiple files 2022-03-24 13:25:12 +08:00
a8f8c9fefc Remove redundant parameters 2022-03-24 13:19:18 +08:00
59107b63a5 Add dial_server_timeout option 2022-03-23 18:12:07 +08:00
fabe216329 Bump frp version to 0.40.0 2022-03-23 18:07:30 +08:00
e31d4369b8 Code refactoring 2022-03-23 16:01:23 +08:00
681fa94e42 Add instructions 2022-03-11 13:31:36 +08:00
8629308695 Set random seed 2022-03-09 16:15:30 +08:00
d9f703340c Update release note 2022-03-07 15:17:54 +08:00
05e1ac77dc Update file info 2022-03-07 15:01:22 +08:00
6596b6bceb Change working directory of MMC 2022-03-07 10:57:00 +08:00
00413467c6 Kill gui processes during uninstall 2022-03-07 10:53:23 +08:00
e77bf5df4b Update releaser workflow 2022-03-03 15:19:26 +08:00
4b3f7b7af8 Fix build script error 2022-03-03 15:18:33 +08:00
d15f603813 Add CHANGELOG 2022-03-03 15:17:04 +08:00
5b106039e3 Add github actions 2022-03-02 17:45:37 +08:00
617af368dd Remove service on uninstall 2022-03-02 15:26:42 +08:00
54ddbd1ea2 Code refactoring 2022-03-02 11:27:29 +08:00
1d70505492 Save version info in new file 2022-03-01 19:53:53 +08:00
98489bce99 Use default version if no git info is found 2022-03-01 18:04:35 +08:00
d142e0b2ee Update copyright 2022-03-01 18:04:10 +08:00
972d5cba8e Create setup.exe for installation 2022-03-01 17:55:29 +08:00
141 changed files with 25875 additions and 4573 deletions

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE colormap [
<!ELEMENT colormap (color)+>
<!ELEMENT color (#PCDATA)>
<!ATTLIST color name CDATA "0">
<!ATTLIST color color CDATA "rgb(0,0,0)">
<!ATTLIST color compliance CDATA "SVG">
]>
<!--
Associate a color name with its red, green, blue, and alpha intensities.
A number of methods and options require a color parameter. It is often
convenient to refer to a color by name (e.g. white) rather than by hex
value (e.g. #fff). This file maps a color name to its equivalent red,
green, blue, and alpha intensities (e.g. for white, red = 255, green =
255, blue = 255, and alpha = 0).
-->
<colormap>
<!-- <color name="none" color="rgba(0,0,0,0)" compliance="SVG, XPM"/> -->
<!-- <color name="black" color="rgb(0,0,0)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="red" color="rgb(255,0,0)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="magenta" color="rgb(255,0,255)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="green" color="rgb(0,128,0)" compliance="SVG"/> -->
<!-- <color name="cyan" color="rgb(0,255,255)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="blue" color="rgb(0,0,255)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="yellow" color="rgb(255,255,0)" compliance="SVG, X11, XPM"/> -->
<!-- <color name="white" color="rgb(255,255,255)" compliance="SVG, X11"/> -->
</colormap>

Binary file not shown.

View File

@ -1,102 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE delegatemap [
<!ELEMENT delegatemap (delegate)+>
<!ELEMENT delegate (#PCDATA)>
<!ATTLIST delegate decode CDATA #IMPLIED>
<!ATTLIST delegate encode CDATA #IMPLIED>
<!ATTLIST delegate mode CDATA #IMPLIED>
<!ATTLIST delegate spawn CDATA #IMPLIED>
<!ATTLIST delegate stealth CDATA #IMPLIED>
<!ATTLIST delegate thread-support CDATA #IMPLIED>
<!ATTLIST delegate command CDATA #REQUIRED>
]>
<!--
Delegate command file.
Commands which specify
decode="in_format" encode="out_format"
specify the rules for converting from in_format to out_format These
rules may be used to translate directly between formats.
Commands which specify only
decode="in_format"
specify the rules for converting from in_format to some format that
ImageMagick will automatically recognize. These rules are used to
decode formats.
Commands which specify only
encode="out_format"
specify the rules for an "encoder" which may accept any input format.
For delegates other than ps:alpha, ps:color, ps:mono, and mpeg-encode the
substitution rules are as follows:
%i input image filename
%o output image filename
%u unique temporary filename
%# input image signature
%b image file size
%c input image comment
%g image geometry
%h image rows (height)
%k input image number colors
%l image label
%m input image format
%p page number
%q input image depth
%s scene number
%w image columns (width)
%x input image x resolution
%y input image y resolution
-->
<delegatemap>
<delegate decode="bpg" command="cmd.exe /c (&quot;bpgdec&quot; -b 16 -o &quot;%o.png&quot; &quot;%i&quot;) &amp; (move &quot;%o.png&quot; &quot;%o&quot; >nul)"/>
<delegate decode="png" encode="bpg" command="&quot;bpgenc&quot; -b 12 -q %~ -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="browse" stealth="True" spawn="True" command="cmd.exe /c start &quot;&quot; http://www.imagemagick.org/"/>
<delegate decode="dng:decode" stealth="True" command="dcraw.exe -6 -W -O &quot;%u.ppm&quot; &quot;%i&quot;"/>
<delegate decode="dot" command="dot -Tps &quot;%i&quot; -o &quot;%o&quot;"/>
<delegate decode="dvi" command="dvips -q -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="edit" stealth="True" command="notepad &quot;%o&quot;"/>
<delegate decode="eps" encode="pdf" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -sDEVICE=pdfwrite &quot;-sOutputFile=%o&quot; -- &quot;%i&quot;"/>
<delegate decode="eps" encode="ps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 -sDEVICE=ps2write &quot;-sOutputFile=%o&quot; -- &quot;%i&quot;"/>
<delegate decode="hpg" command="hp2xx -q -m eps -f &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="hpgl" command="hp2xx -q -m eps -f &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="htm" command="html2ps -U -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="html" command="html2ps -U -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="jxr" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.jxr&quot; >nul) &amp; (&quot;JXRDecApp.exe&quot; -i &quot;%i.jxr&quot; -o &quot;%o.pnm&quot;) &amp; (move &quot;%i.jxr&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.pnm&quot; &quot;%o&quot; >nul)"/>
<delegate decode="mpeg:decode" command="&quot;ffmpeg.exe&quot; -nostdin -v -1 -i &quot;%i&quot; -vframes %S -vcodec pam -an -f rawvideo -y &quot;%u.pam&quot;"/>
<delegate decode="pcl:cmyk" stealth="True" command="&quot;pcl6.exe&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pamcmyk32&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="pcl:color" stealth="True" command="&quot;pcl6.exe&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=ppmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="pcl:mono" stealth="True" command="&quot;pcl6.exe&quot; -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="pdf" encode="eps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -sDEVICE=eps2write -sPDFPassword=&quot;%a&quot; &quot;-sOutputFile=%o&quot; -- &quot;%i&quot;"/>
<delegate decode="pdf" encode="ps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 -sDEVICE=ps2write -sPDFPassword=&quot;%a&quot; &quot;-sOutputFile=%o&quot; -- &quot;%i&quot;"/>
<delegate decode="pgp" command="pgpv -fq &quot;%i&quot;"/>
<delegate decode="png" encode="launch" spawn="True" mode="encode" command="imdisplay &quot;%i&quot;" />
<delegate decode="png" encode="show" spawn="True" mode="encode" command="imdisplay &quot;%i&quot;" />
<delegate decode="png" encode="win" spawn="True" mode="encode" command="imdisplay &quot;%i&quot;" />
<delegate decode="pnm" encode="ilbm" mode="encode" command="ppmtoilbm -24if &quot;%i&quot; &gt; &quot;%o&quot;"/>
<delegate decode="pnm" encode="jxr" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.pnm&quot; >nul) &amp; (&quot;JXREncApp.exe&quot; -i &quot;%i.pnm&quot; -o &quot;%o.jxr&quot;) &amp; (move &quot;%i.pnm&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.jxr&quot; &quot;%o&quot; >nul)"/>
<delegate decode="pnm" encode="wdp" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.pnm&quot; >nul) &amp; (&quot;JXREncApp.exe&quot; -i &quot;%i.pnm&quot; -o &quot;%o.jxr&quot;) &amp; (move &quot;%i.pnm&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.jxr&quot; &quot;%o&quot; >nul)"/>
<delegate decode="ps:alpha" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pngalpha&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="ps:cmyk" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pamcmyk32&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="ps:color" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pnmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="ps" encode="eps" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 -sDEVICE=eps2write &quot;-sOutputFile=%o&quot; -- &quot;%i&quot;"/>
<delegate decode="ps" encode="pdf" mode="bi" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 -sDEVICE=pdfwrite &quot;-sOutputFile=%o&quot; -- &quot;%i&quot;"/>
<delegate decode="ps:mono" stealth="True" command="&quot;@PSDelegate@&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pnmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;-f%s&quot; &quot;-f%s&quot;"/>
<delegate decode="shtml" command="html2ps -U -o &quot;%o&quot; &quot;%i&quot;"/>
<delegate decode="svg" command="&quot;rsvg-convert&quot; -o &quot;%o&quot; &quot;%i&quot;"/>
<!-- Remove the extra space in - -export in the line below when you want to use inkscape -->
<!--<delegate decode="svg:decode" stealth="True" command="&quot;inkscape&quot; &quot;%s&quot; - -export-eps=&quot;%s&quot; - -export-dpi=&quot;%s&quot; - -export-background=&quot;%s&quot; - -export-background-opacity=&quot;%s&quot; &gt; &quot;%s&quot; 2&gt;&amp;1"/>-->
<delegate decode="wdp" command="cmd.exe /c (move &quot;%i&quot; &quot;%i.jxr&quot; >nul) &amp; (&quot;JXRDecApp.exe&quot; -i &quot;%i.jxr&quot; -o &quot;%o.pnm&quot;) &amp; (move &quot;%i.jxr&quot; &quot;%i&quot; >nul) &amp; (move &quot;%o.pnm&quot; &quot;%o&quot; >nul)"/>
<delegate decode="xps:cmyk" stealth="True" command="&quot;gxps.exe&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pamcmyk32&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="xps:color" stealth="True" command="&quot;gxps.exe&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pnmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate decode="xps:mono" stealth="True" command="&quot;gxps.exe&quot; -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 &quot;-sDEVICE=pbmraw&quot; -dTextAlphaBits=%u -dGraphicsAlphaBits=%u &quot;-r%s&quot; %s &quot;-sOutputFile=%s&quot; &quot;%s&quot;"/>
<delegate encode="mpeg:encode" stealth="True" command="&quot;ffmpeg.exe&quot; -nostdin -v -1 -i &quot;%M%%d.jpg&quot; &quot;%u.%m&quot;"/>
</delegatemap>

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: https://github.com/koho/frpmgr/blob/master/docs/donate-wechat.jpg

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Version**
Include the version in the About page.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows 10]
- Version [e.g. 24H2]

View File

@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Describe the feature request**
A clear and concise description of what you want to add.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

7
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

26
.github/workflows/golangci-lint.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: CI
on:
push:
branches:
- master
pull_request:
jobs:
golangci:
name: Lint
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go environment
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Run
uses: golangci/golangci-lint-action@v8
with:
version: v2.5

192
.github/workflows/releaser.yml vendored Normal file
View File

@ -0,0 +1,192 @@
name: Releaser
on:
release:
types: [published]
jobs:
build:
name: Build
runs-on: windows-latest
strategy:
matrix:
architecture: [x64, x86, arm64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go environment
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Setup VS environment
shell: powershell
run: |
echo "$(vswhere.exe -latest -property installationPath)\VC\Auxiliary\Build" >> $env:GITHUB_PATH
@"
@echo off
setlocal enabledelayedexpansion
set SRC_DIR=%~1
:safe_copy
shift
if "%~1"=="" exit /b 0
if "%~2"=="" exit /b 1
set SRC_FILE=%SRC_DIR%\%~1
set DST_FILE=%~2\%~1
if not "%~x1" == ".msi" (
set SRC_FILE_UNSIGNED=!SRC_FILE!.unsigned
copy /Y "!SRC_FILE!" "!SRC_FILE_UNSIGNED!"
"%SIGNTOOL%" remove /s "!SRC_FILE_UNSIGNED!" || exit /b 1
call :pe_compare "!SRC_FILE_UNSIGNED!" "!DST_FILE!" || (
echo File checksum mismatch: !SRC_FILE!
exit /b 1))
copy /Y "!SRC_FILE!" "!DST_FILE!"
shift
goto :safe_copy
:pe_compare
python -c "import os;import sys;from ctypes import *;header_sum,checksum1,checksum2=c_ulong(0),c_ulong(0),c_ulong(0);src_size,dst_size=os.path.getsize(sys.argv[1]),os.path.getsize(sys.argv[2]);f=open(sys.argv[1],'r+');f.seek(dst_size,os.SEEK_SET);f.truncate();f.close();assert windll.imagehlp.MapFileAndCheckSumW(sys.argv[1],byref(header_sum),byref(checksum1))==0;assert windll.imagehlp.MapFileAndCheckSumW(sys.argv[2],byref(header_sum),byref(checksum2))==0;assert checksum1.value==checksum2.value" %1 %2
goto :eof
"@ | Out-File -Encoding ascii -FilePath safe_copy.bat
- name: Setup toolchain
shell: cmd
run: |
curl "https://github.com/mstorsjo/llvm-mingw/releases/download/20250709/llvm-mingw-20250709-msvcrt-x86_64.zip" -o llvm-mingw-20250709-msvcrt-x86_64.zip -L
tar -xf llvm-mingw-20250709-msvcrt-x86_64.zip
echo %CD%\llvm-mingw-20250709-msvcrt-x86_64\bin>>%GITHUB_PATH%
vcvarsall x64 && set WindowsSdkVerBinPath >> %GITHUB_ENV%
- name: Build main application
shell: cmd
run: build.bat -p ${{ matrix.architecture }}
- name: Get version info
shell: powershell
run: |
$version = $((Get-Item .\bin\${{ matrix.architecture }}\frpmgr.exe).VersionInfo.ProductVersion)
echo "VERSION=$version" >> $env:GITHUB_ENV
$signtool = $(cmd /C "vcvarsall x64 && where signtool" | Select-Object -Last 1)
echo "SIGNTOOL=$signtool" >> $env:GITHUB_ENV
- name: Build custom actions
shell: cmd
run: installer\build.bat %VERSION% ${{ matrix.architecture }} actions
- name: Prepare to upload files
shell: cmd
run: copy /Y installer\build\${{ matrix.architecture }}\actions.dll bin\${{ matrix.architecture }}
- name: Upload unsigned application
id: upload-unsigned-application
uses: actions/upload-artifact@v4
with:
name: frpmgr-${{ env.VERSION }}-main-${{ matrix.architecture }}-unsigned
path: |
bin/${{ matrix.architecture }}/frpmgr.exe
bin/${{ matrix.architecture }}/actions.dll
- name: Sign
uses: signpath/github-action-submit-signing-request@v1.2
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '${{ secrets.SIGNPATH_ORGANIZATION_ID }}'
project-slug: 'frpmgr'
signing-policy-slug: 'release-signing'
github-artifact-id: '${{ steps.upload-unsigned-application.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'dist'
- name: Verify and copy signed files
shell: cmd
run: safe_copy dist frpmgr.exe bin\${{ matrix.architecture }} actions.dll installer\build\${{ matrix.architecture }}
- name: Build MSI installer
shell: cmd
run: installer\build.bat %VERSION% ${{ matrix.architecture }} msi
- name: Upload unsigned installer
id: upload-unsigned-installer
uses: actions/upload-artifact@v4
with:
name: frpmgr-${{ env.VERSION }}-installer-${{ matrix.architecture }}-unsigned
path: installer/build/${{ matrix.architecture }}/frpmgr.msi
- name: Sign
uses: signpath/github-action-submit-signing-request@v1.2
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '${{ secrets.SIGNPATH_ORGANIZATION_ID }}'
project-slug: 'frpmgr'
signing-policy-slug: 'release-signing'
github-artifact-id: '${{ steps.upload-unsigned-installer.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'dist'
- name: Verify and copy signed files
shell: cmd
run: safe_copy dist frpmgr.msi installer\build\${{ matrix.architecture }}
- name: Build EXE bootstrapper
shell: cmd
run: installer\build.bat %VERSION% ${{ matrix.architecture }} setup
- name: Upload unsigned bootstrapper
id: upload-unsigned-bootstrapper
uses: actions/upload-artifact@v4
with:
name: frpmgr-${{ env.VERSION }}-setup-${{ matrix.architecture }}-unsigned
path: installer/build/${{ matrix.architecture }}/setup.exe
- name: Sign
uses: signpath/github-action-submit-signing-request@v1.2
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '${{ secrets.SIGNPATH_ORGANIZATION_ID }}'
project-slug: 'frpmgr'
signing-policy-slug: 'release-signing'
github-artifact-id: '${{ steps.upload-unsigned-bootstrapper.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'dist'
- name: Verify and copy signed files
shell: cmd
run: safe_copy dist setup.exe installer\build\${{ matrix.architecture }}
- name: Create release files
shell: cmd
run: installer\build.bat %VERSION% ${{ matrix.architecture }} dist
- name: Upload release files
uses: actions/upload-artifact@v4
with:
name: frpmgr-${{ env.VERSION }}-dist-${{ matrix.architecture }}
path: |
bin/*.exe
bin/*.zip
release:
name: Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Get version info
run: |
tag_name="${{ github.event.release.tag_name }}"
echo "VERSION=${tag_name#v}" >> $GITHUB_ENV
- name: Collect files
uses: actions/download-artifact@v4
with:
pattern: frpmgr-${{ env.VERSION }}-dist-*
merge-multiple: true
- name: Upload release assets
uses: shogo82148/actions-upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: |
./*.exe
./*.zip

78
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Tests
on:
push:
branches:
- master
pull_request:
jobs:
build:
name: Build
runs-on: windows-latest
strategy:
matrix:
architecture: [x64, x86, arm64]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go environment
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Setup VS environment
shell: powershell
run: echo "$(vswhere.exe -latest -property installationPath)\VC\Auxiliary\Build" >> $env:GITHUB_PATH
- name: Setup toolchain
shell: cmd
run: |
curl "https://github.com/mstorsjo/llvm-mingw/releases/download/20250709/llvm-mingw-20250709-msvcrt-x86_64.zip" -o llvm-mingw-20250709-msvcrt-x86_64.zip -L
tar -xf llvm-mingw-20250709-msvcrt-x86_64.zip
echo %CD%\llvm-mingw-20250709-msvcrt-x86_64\bin>>%GITHUB_PATH%
vcvarsall x64 && set WindowsSdkVerBinPath >> %GITHUB_ENV%
- name: Add commit hash to version number
shell: powershell
if: github.event_name == 'pull_request'
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
$versionFile = ".\pkg\version\version.go"
$rev = [UInt16]("0x" + $env:HEAD_SHA.Substring(0, 4))
$version = (findstr /r "Number.*=.*[0-9.]*" $versionFile | Select-Object -First 1 | ConvertFrom-StringData).Get_Item("Number")
$newVersion = $version.Substring(0, $version.Length - 1) + ".$rev" + '"'
(Get-Content $versionFile).Replace($version, $newVersion) | Set-Content $versionFile
echo "VERSION=$($newVersion.Replace('"', ''))" >> $env:GITHUB_ENV
- name: Build
shell: cmd
run: build.bat ${{ matrix.architecture }}
- name: Upload
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request'
with:
name: frpmgr-${{ env.VERSION }}-test-${{ matrix.architecture }}
path: |
bin/*.exe
bin/*.zip
retention-days: 7
test:
name: Go
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Go environment
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Test
run: go test -v ./...

31
.golangci.yml Normal file
View File

@ -0,0 +1,31 @@
version: "2"
linters:
default: none
enable:
- govet
- ineffassign
- staticcheck
- unused
- whitespace
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- staticcheck
text: should not use dot imports
paths:
- third_party$
- builtin$
- examples$
formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

242
CHANGELOG.md Normal file
View File

@ -0,0 +1,242 @@
# Changelog[Deprecated]
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.11.0] - 2023-02-10
### What's Changed
* View old history log files by @koho in https://github.com/koho/frpmgr/pull/30
* Show item name in dialog title
* Show blue config name when config is set to start manually
* Set gray background color for disabled proxy
* Support go1.20
* Support `bandwidth_limit_mode` option
### Update
* FRP v0.47.0
**Full Changelog**: https://github.com/koho/frpmgr/compare/v1.10.1...v1.11.0
## [1.10.1] - 2023-01-10
### 新增
* 自定义 DNS 服务
* 支持降级安装
### 更新
* FRP 版本 0.46.1
## [1.10.0] - 2022-12-19
### 新增
* 认证支持 `oidc_scope` 参数
* 支持 quic 协议
### 优化
* 展示 `xtcp``stcp``sudp` 类型的访问者参数(#27
### 更新
* FRP 版本 0.46.0
## [1.9.2] - 2022-10-27
### 更新
* FRP 版本 0.45.0
## [1.9.1] - 2022-07-11
### 更新
* FRP 版本 0.44.0
## [1.9.0] - 2022-07-01
### 新增
* 多语言支持(#19
* 支持创建/导入分享链接
* 可创建某个配置的副本,避免参数的重复输入
* 从 URL 导入配置
### 优化
* 配置文件统一存放到 `profiles` 目录
## [1.8.1] - 2022-05-28
### 新增
* 新的代理参数「路由用户」(`route_by_http_user`)
### 更新
* FRP 版本 0.43.0
## [1.8.0] - 2022-05-15
### 新增
* 从剪贴板导入配置/代理
* 支持拖拽文件导入配置
* 在文件夹中显示配置
### 优化
* 减少安装包体积(-48%
* 升级时默认选择上次安装的目录
* 导入文件前验证配置文件
## [1.7.2] - 2022-04-22
### 更新
* FRP 版本 0.42.0
## [1.7.1] - 2022-04-13
### 新增
* "快速添加"支持更多类型,如 FTP、文件服务等
* 快捷启用/禁用代理条目
* 新增 TLS、心跳、复用等配置选项
* 代理条目右键菜单新增"复制访问地址"功能
### 修复
* 修复 Win7 下无法打开服务窗口
### 优化
* 防止同一用户下 GUI 窗口多开
* 启动配置前验证配置文件
* 保存代理条目前验证代理条目
* 使用范围端口时自动添加前缀
## [1.7.0] - 2022-03-24
### 新增
* 支持全部代理类型(本次新增`sudp`, `http`, `https`, `tcpmux`)的图形化配置
* 新增插件编辑
* 新增负载均衡
* 新增健康检查
* 新增带宽限制,代理协议版本配置
* 代理项目表格新增了子域名,自定义域名,插件列
* 添加连接超时时间,心跳间隔时间配置
* 添加 pprof 开关
### 修复
* 修复在中文配置名下,打开服务按钮无反应的问题
* 修复随机名称按钮会生成相同名称问题
* 修复了小概率界面崩溃问题
### 优化
* 无法添加相同名称的代理
* 无法导入相同名称的配置,当以压缩包导入时,忽略同名配置导入
* 减少了不必要的 IO 查询
* 代理项目表格各列宽调整,以充分利用空间
* 手动指定日志文件后修改配置名不再自动改变日志路径配置
* 路径配置的输入框添加浏览文件按钮
### 更新
* FRP 版本 0.41.0
## [1.6.1] - 2022-03-07
### 优化
* 安装包改用 exe 格式,避免无法关闭占用程序
* 升级完成后自动重启之前运行的服务
## [1.6.0] - 2022-02-14
### 新增
* 配置编辑支持自定义参数(#12)
* 打开配置文件入口
* 项目编辑可生成随机名称
* 复制服务器地址入口
* 添加`connect_server_local_ip``http_proxy``user`编辑入口
### 优化
* 减少不必要的视图更新
* 优化系统缩放时的界面显示
### 更新
* FRP 版本 0.39.1
## [1.5.0] - 2022-01-05
### 更新
* FRP 版本 0.38.0
## [1.4.2] - 2021-09-08
### 新增
* 可单独设定配置的服务启动方式(手动/自动)(#9)
### 修复
* 修复某些情况下无法查看服务的异常
## [1.4.1] - 2021-09-07
### 新增
* 支持配置xtcp/stcp类型(#8)
* 添加自定义选项支持
* 查看服务属性入口(#9)
### 更新
* FRP 版本 0.37.1
## [1.4.0] - 2021-07-12
### 修复
* 修复日志文件的卸载错误提示
### 更新
* FRP 版本 0.37.0
## [1.3.2] - 2020-12-16
### 新增
* 支持双击编辑
### 优化
* 小幅UI优化
## [1.3.1] - 2020-12-16
### 新增
* 添加文件版本信息
### 修复
* 修复卸载程序时的DLL错误
## [1.3.0] - 2020-12-13
### 新增
* 添加关于页面
* 支持导出配置文件
### 优化
* 日志实时显示
* 小幅UI优化
### 修复
* 修复卸载时日志文件无法删除的问题
## [1.2.5] - 2020-12-03
### 优化
* 小幅 UI 逻辑优化
* 相关日志文件重命名/删除
### 修复
* 修复 Windows 7 下的闪退问题(#2)
## [1.2.4] - 2020-08-17
### 新增
* 添加自定义DNS服务器的支持对于使用动态DNS的服务器可以减少离线时间
### 修复
* 修复了一些编译错误
## [1.2.3] - 2020-05-24
### 修复
* 解决某些情况下电脑重启后服务没有自动运行问题
* 更新软件后需打开软件,选择左侧配置项后右键编辑,然后直接确定,再启动即可
[Unreleased]: https://github.com/koho/frpmgr/compare/v1.11.0...HEAD
[1.11.0]: https://github.com/koho/frpmgr/compare/v1.10.1...v1.11.0
[1.10.1]: https://github.com/koho/frpmgr/compare/v1.10.0...v1.10.1
[1.10.0]: https://github.com/koho/frpmgr/compare/v1.9.2...v1.10.0
[1.9.2]: https://github.com/koho/frpmgr/compare/v1.9.1...v1.9.2
[1.9.1]: https://github.com/koho/frpmgr/compare/v1.9.0...v1.9.1
[1.9.0]: https://github.com/koho/frpmgr/compare/v1.8.1...v1.9.0
[1.8.1]: https://github.com/koho/frpmgr/compare/v1.8.0...v1.8.1
[1.8.0]: https://github.com/koho/frpmgr/compare/v1.7.2...v1.8.0
[1.7.2]: https://github.com/koho/frpmgr/compare/v1.7.1...v1.7.2
[1.7.1]: https://github.com/koho/frpmgr/compare/v1.7.0...v1.7.1
[1.7.0]: https://github.com/koho/frpmgr/compare/v1.6.1...v1.7.0
[1.6.1]: https://github.com/koho/frpmgr/compare/v1.6.0...v1.6.1
[1.6.0]: https://github.com/koho/frpmgr/compare/v1.5.0...v1.6.0
[1.5.0]: https://github.com/koho/frpmgr/compare/v1.4.2...v1.5.0
[1.4.2]: https://github.com/koho/frpmgr/compare/v1.4.1...v1.4.2
[1.4.1]: https://github.com/koho/frpmgr/compare/v1.4.0...v1.4.1
[1.4.0]: https://github.com/koho/frpmgr/compare/v1.3.2...v1.4.0
[1.3.2]: https://github.com/koho/frpmgr/compare/v1.3.1...v1.3.2
[1.3.1]: https://github.com/koho/frpmgr/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/koho/frpmgr/compare/v1.2.5...v1.3.0
[1.2.5]: https://github.com/koho/frpmgr/compare/v1.2.4...v1.2.5
[1.2.4]: https://github.com/koho/frpmgr/compare/v1.2.3...v1.2.4
[1.2.3]: https://github.com/koho/frpmgr/releases/tag/v1.2.3

120
README.md
View File

@ -1,47 +1,113 @@
# frpmgr
# FRP Manager
Windows 下的 [frp](https://github.com/fatedier/frp) 图形界面客户端。
[![GitHub Release](https://img.shields.io/github/tag/koho/frpmgr.svg?label=release)](https://github.com/koho/frpmgr/releases)
[![FRP Version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgo.shields.workers.dev%2Fkoho%2Ffrpmgr%2Fmaster%3Fname%3Dfrp)](https://github.com/fatedier/frp)
[![GitHub Downloads](https://img.shields.io/github/downloads/koho/frpmgr/total.svg)](https://github.com/koho/frpmgr/releases)
![frpmgr](/docs/frpmgr.jpg)
English | [简体中文](README_zh.md)
系统需求win7及以上版本
FRP Manager is a multi-node, graphical reverse proxy tool designed for [FRP](https://github.com/fatedier/frp) on Windows. It allows users to setup reverse proxy easily without writing the configuration file. FRP Manager offers a complete solution including editor, launcher, status tracking, and hot reload.
## 特征
* 简易的编辑界面
* 支持导入/导出配置文件
* 开机自启动
* 多配置文件管理
The tool was inspired by a common use case where we often need to combine multiple tools including client, configuration file, and launcher to create a stable service that exposes a local server behind a NAT or firewall to the Internet. Now, with FRP Manager, an all-in-one solution, you can avoid many tedious operations when deploying a reverse proxy.
## 编译
**安装依赖**:
- go >=1.16
- Visual Studio
- [MinGW](https://www.mingw-w64.org/)
- [WiX Toolset](https://wixtoolset.org/)
The latest release requires at least Windows 10 or Server 2016. Please visit the **[Wiki](https://github.com/koho/frpmgr/wiki)** for comprehensive guides.
**环境配置**:
![screenshot](/docs/screenshot_en.png)
`VsMSBuildCmd.bat`添加到环境变量。通常目录为:
- `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools`
## Features
**执行编译**:
- **Closable GUI:** All launched configurations will run independently as background services, so you can close the GUI after finishing all settings.
- **Auto-start:** A launched configuration is registered as an auto-start service by default and starts automatically during system boot (no login required).
- **Hot reload:** Allows users to apply proxy changes to a running configuration without restarting the service and without losing proxy state.
- **Multiple configurations:** It's easy to connect to multiple nodes by creating multiple configurations.
- **Import and export configurations:** Provides the option to import configuration file from multiple sources, including local file, clipboard, and HTTP.
- **Self-destructing configuration:** A special configuration that disappears and becomes unreachable after a certain amount of time.
- **Status tracking:** You can check the proxy status directly in the table view without looking at the logs.
```shell script
Visit the **[Wiki](https://github.com/koho/frpmgr/wiki)** for comprehensive guides, including:
- **[Installation Instructions](https://github.com/koho/frpmgr/wiki#how-to-install):** Install or upgrade FRP Manager on Windows.
- **[Quick Start Guide](https://github.com/koho/frpmgr/wiki/Quick-Start):** Learn how to connect to your node and setup a proxy in minutes.
- **[Configuration](https://github.com/koho/frpmgr/wiki/Configuration):** Explore configuration, proxy, visitor, and log.
- **[Examples](https://github.com/koho/frpmgr/wiki/Examples):** There are some common examples to help you learn FRP Manager.
## Building
To build FRP Manager from source, you need to install the following dependencies:
- Go
- [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/)
- [MinGW](https://github.com/mstorsjo/llvm-mingw)
- [WiX Toolset](https://wixtoolset.org/) v3.14
Once installed, the `WindowsSdkVerBinPath` environment variable should be set to tell build script where to find the specific version of Windows SDK, e.g., `set WindowsSdkVerBinPath=C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\`. You should also add the `bin` directory of MinGW to the `PATH` environment variable.
You can compile the project by opening the terminal:
```shell
git clone https://github.com/koho/frpmgr
cd frpmgr
build.bat
```
在 `bin` 目录找到生成的安装文件。
The generated installation files are located in the `bin` directory.
**调试**:
You can also skip building the installation package and get a portable application by passing the `-p` option to the `build` command:
编译程序需要渲染图标生成资源和对某些文件打补丁,这些操作都在`build.bat`完成。因此在第一次调试前需要运行一次`build.bat`,后续调试不需要再执行该脚本。
```shell
build.bat -p
```
## 捐助
In this case, you only need to install Go and MinGW.
如果您觉得本项目对你有帮助,欢迎给予我们支持。
### Debugging
### 微信支付捐赠
If you're building the project for the first time, you need to compile resources:
![donate-wechat](/docs/donate-wechat.jpg)
```shell
go generate
```
The command does not need to be executed again unless the project's resources change.
After that, the application can be run directly:
```shell
go run ./cmd/frpmgr
```
## Sponsors
> We are really thankful for all of our users, contributors, and sponsors that has been keeping this project alive and well. We are also giving our gratitude for these company/organization for providing their service for us.
1. SignPath Foundation for providing us free code signing!
<p align=center>
<a href="https://about.signpath.io/">
<img src="./docs/sponsor_signpath.png" alt="SignPath Logo" height=50 />
</a>
</p>
## Code Signing Policy
Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/).
Team roles:
- Committers and reviewers: [Members team](https://github.com/koho/frpmgr/graphs/contributors)
- Approvers: [Owners](https://github.com/koho)
Read our full [Privacy Policy](#privacy-policy).
## Privacy Policy
This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
FRP Manager has integrated the following services for additional functions, which can be enabled or disabled at any time in the settings:
- [api.github.com](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement) (Check for program updates)
## Donation
If this project is useful to you, consider supporting its development in one of the following ways:
- [**WeChat**](/docs/donate-wechat.jpg)

113
README_zh.md Normal file
View File

@ -0,0 +1,113 @@
# FRP 管理器
[![GitHub Release](https://img.shields.io/github/tag/koho/frpmgr.svg?label=release)](https://github.com/koho/frpmgr/releases)
[![FRP Version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgo.shields.workers.dev%2Fkoho%2Ffrpmgr%2Fmaster%3Fname%3Dfrp)](https://github.com/fatedier/frp)
[![GitHub Downloads](https://img.shields.io/github/downloads/koho/frpmgr/total.svg)](https://github.com/koho/frpmgr/releases)
[English](README.md) | 简体中文
FRP 管理器是一个多节点、图形化反向代理工具,专为 Windows 上的 [FRP](https://github.com/fatedier/frp) 设计。它允许用户轻松设置反向代理而无需编写配置文件。FRP 管理器提供了一套完整的解决方案,包括编辑器、启动器、状态跟踪和热重载。
该工具的灵感来自于一个常见的用例,我们经常需要组合使用多种工具,包括客户端、配置文件和启动器,以创建一个稳定的服务,将位于 NAT 或防火墙后的本地服务器暴露到互联网。现在,有了 FRP 管理器这个一体化解决方案,您可以在部署反向代理时省去许多繁琐的操作。
最新版本至少需要 Windows 10 或 Server 2016。请访问 **[Wiki](https://github.com/koho/frpmgr/wiki)** 获取完整指南。
![screenshot](/docs/screenshot_zh.png)
## 特征
- **界面可退出:**&#8203;所有已启动的配置都将作为后台服务独立运行,因此您可以在完成所有设置后关闭界面。
- **开机自启:**&#8203;已启动的配置默认注册为自动启动服务,并在系统启动时自动启动(无需登录)。
- **热重载:**&#8203;允许用户将代理更改应用于正在运行的配置,而无需重启服务,也不会丢失代理状态。
- **多配置文件管理:**&#8203;通过创建多个配置,可以轻松连接到多个节点。
- **导入和导出配置:**&#8203;提供从多个来源导入配置文件的选项,包括本地文件、剪贴板和 HTTP。
- **自毁配置:**&#8203;一种特殊配置,会在指定的时间后删除并无法访问。
- **状态跟踪:**&#8203;您可以直接在表格视图中查看代理状态,而无需查看日志。
访问 **[Wiki](https://github.com/koho/frpmgr/wiki)** 获取完整指南,包括:
- **[安装说明](https://github.com/koho/frpmgr/wiki#how-to-install)**&#8203;在 Windows 上安装或升级 FRP 管理器。
- **[快速入门指南](https://github.com/koho/frpmgr/wiki/Quick-Start)**&#8203;了解如何在几分钟内连接到您的节点并设置代理。
- **[配置](https://github.com/koho/frpmgr/wiki/Configuration)**&#8203;探索配置、代理、访问者和日志。
- **[示例](https://github.com/koho/frpmgr/wiki/Examples)**&#8203;这里有一些常见的示例可以帮助您学习 FRP 管理器。
## 构建
要从源代码构建 FRP 管理器,您需要安装以下依赖项:
- Go
- [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/)
- [MinGW](https://github.com/mstorsjo/llvm-mingw)
- [WiX Toolset](https://wixtoolset.org/) v3.14
安装完成后,您需要设置 `WindowsSdkVerBinPath` 环境变量,以指示构建脚本在哪里找到特定版本的 Windows SDK例如 `set WindowsSdkVerBinPath=C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\`。您还需要将 MinGW 的 `bin` 目录添加到 `PATH` 环境变量中。
您可以通过打开终端来编译项目:
```shell
git clone https://github.com/koho/frpmgr
cd frpmgr
build.bat
```
生成的安装文件位于 `bin` 目录。
如果您想跳过构建安装包,可以在 `build` 命令中添加 `-p` 选项来获得一个便携软件:
```shell
build.bat -p
```
在这种情况下,您只需安装 Go 和 MinGW 即可。
### 调试
如果您是首次构建项目,则需要编译资源:
```shell
go generate
```
除非项目资源发生变化,否则无需再次执行该命令。
之后,即可直接运行该应用程序:
```shell
go run ./cmd/frpmgr
```
## 赞助商
> 我们非常感谢所有为项目发展而努力的用户、贡献者和赞助者。同时也感谢这些公司/组织为我们提供服务。
1. SignPath Foundation 为我们提供免费的代码签名!
<p align=center>
<a href="https://about.signpath.io/">
<img src="./docs/sponsor_signpath.png" alt="SignPath Logo" height=50 />
</a>
</p>
## 代码签名政策
免费代码签名由 [SignPath.io](https://about.signpath.io/) 提供,证书由 [SignPath Foundation](https://signpath.org/) 提供。
团队角色:
- 提交者和审阅者:[团队成员](https://github.com/koho/frpmgr/graphs/contributors)
- 审批者:[所有者](https://github.com/koho)
请阅读我们的完整[隐私政策](#隐私政策)。
## 隐私政策
除非得到用户、安装或操作人员的许可,否则该程序不会将任何信息传输到其他联网系统。
FRP 管理器集成了以下服务以实现附加功能,您可以随时在设置中启用或禁用这些服务:
- [api.github.com](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement)(检查程序更新)
## 捐助
如果本项目对您有帮助,请考虑通过以下方式支持其开发:
- [**微信**](/docs/donate-wechat.jpg)

View File

@ -1,21 +1,53 @@
@echo off
setlocal enabledelayedexpansion
for /F %%i in ('git tag') do (set FRPMGR_VERSION=%%i)
set FRPMGR_VERSION=%FRPMGR_VERSION:~1%
echo Version: %FRPMGR_VERSION%
set GOARCH_x64=amd64
set GOARCH_x86=386
set BUILDDIR=%~dp0
set PATH=%BUILDDIR%.deps;%PATH%
echo [+] Rendering icons
for %%a in ("icon\*.svg") do convert -density 1000 -background none "%%~fa" -define icon:auto-resize="256,192,128,96,64,48,32,24,16" "%%~dpna.ico" || exit /b 1
echo [+] Building resources
windres -DFRPMGR_VERSION_ARRAY=%FRPMGR_VERSION:.=,% -DFRPMGR_VERSION_STR=%FRPMGR_VERSION% -i cmd/frpmgr/resources.rc -o cmd/frpmgr/rsrc.syso -O coff -c 65001 || exit /b %errorlevel%
echo [+] Patching files
go mod tidy || exit /b 1
for %%f in (patches\*.patch) do patch -N -r - -d %GOPATH% -p0 < %%f
echo [+] Compiling release version
for /F "tokens=2 delims=@" %%y in ('go mod graph ^| findstr frpmgr ^| findstr frp@') do (set FRP_VERSION=%%y)
go build -ldflags="-H windowsgui -X github.com/koho/frpmgr/config.Version=v%FRPMGR_VERSION% -X github.com/koho/frpmgr/config.FRPVersion=%FRP_VERSION%" -o bin/frpmgr.exe github.com/koho/frpmgr/cmd/frpmgr || exit /b 1
echo [+] Building installer
call installer/build.bat %FRPMGR_VERSION% || exit /b 1
echo [+] Success.
exit /b 0
cd /d %BUILDDIR% || exit /b 1
if "%~1" == "-p" (
set FRPMGR_TARGET=%~2
) else (
set FRPMGR_TARGET=%~1
)
if "%FRPMGR_TARGET%" == "" set FRPMGR_TARGET=x64 x86
:packages
echo [+] Downloading packages
go mod tidy || goto :error
:resources
echo [+] Generating resources
for /f %%a in ('go generate') do set %%a
if not defined VERSION exit /b 1
:build
echo [+] Building program
set MOD=github.com/koho/frpmgr
set GO111MODULE=on
set CGO_ENABLED=0
for %%a in (%FRPMGR_TARGET%) do (
if defined GOARCH_%%a (
set GOARCH=!GOARCH_%%a!
) else (
set GOARCH=%%a
)
go build -trimpath -ldflags="-H windowsgui -s -w -X %MOD%/pkg/version.BuildDate=%BUILD_DATE%" -o bin\%%a\frpmgr.exe .\cmd\frpmgr || goto :error
)
if "%~1" == "-p" goto :success
:installer
echo [+] Building installer
for %%a in (%FRPMGR_TARGET%) do (
call installer\build.bat %VERSION% %%a || goto :error
)
:success
echo [+] Success
exit /b 0
:error
echo [-] Failed with error %errorlevel%.
exit /b %errorlevel%

View File

@ -1,51 +1,81 @@
package main
import (
"errors"
"flag"
"fmt"
"github.com/koho/frpmgr/services"
"github.com/koho/frpmgr/ui"
"golang.org/x/sys/windows"
"os"
"strings"
"syscall"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"github.com/koho/frpmgr/i18n"
"github.com/koho/frpmgr/pkg/version"
"github.com/koho/frpmgr/services"
"github.com/koho/frpmgr/ui"
)
func fatal(v ...interface{}) {
windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprint(v...)), windows.StringToUTF16Ptr("错误"), windows.MB_ICONERROR)
windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprint(v...)), windows.StringToUTF16Ptr(ui.AppLocalName), windows.MB_ICONERROR)
os.Exit(1)
}
func info(title string, format string, v ...interface{}) {
windows.MessageBox(0, windows.StringToUTF16Ptr(fmt.Sprintf(format, v...)), windows.StringToUTF16Ptr(title), windows.MB_ICONINFORMATION)
windows.MessageBox(0, windows.StringToUTF16Ptr(i18n.Sprintf(format, v...)), windows.StringToUTF16Ptr(title), windows.MB_ICONINFORMATION)
}
func usage() {
var flags = [...]string{
fmt.Sprintf("(no argument): run the frp manager"),
"/service CONFIG_PATH",
}
builder := strings.Builder{}
for _, flag := range flags {
builder.WriteString(fmt.Sprintf(" %s\n", flag))
}
info(fmt.Sprintf("Command Line Options"), "Usage: %s [\n%s]", os.Args[0], builder.String())
os.Exit(1)
var (
confPath string
showVersion bool
showHelp bool
flagOutput strings.Builder
)
func init() {
flag.StringVar(&confPath, "c", "", "The path to config `file` (Service-only).")
flag.BoolVar(&showVersion, "v", false, "Display version information.")
flag.BoolVar(&showHelp, "h", false, "Show help information.")
flag.CommandLine.SetOutput(&flagOutput)
flag.Parse()
}
func main() {
if len(os.Args) <= 1 {
ui.RunUI()
if showHelp {
flag.Usage()
info(ui.AppLocalName, flagOutput.String())
return
}
switch os.Args[1] {
case "/service":
if len(os.Args) != 3 {
usage()
if showVersion {
info(ui.AppLocalName, strings.Join([]string{
i18n.Sprintf("Version: %s", version.Number),
i18n.Sprintf("FRP version: %s", version.FRPVersion),
i18n.Sprintf("Built on: %s", version.BuildDate),
}, "\n"))
return
}
inService, err := svc.IsWindowsService()
if err != nil {
fatal(err)
}
if inService {
if confPath == "" {
os.Exit(1)
return
}
err := services.Run(os.Args[2])
if err != nil {
if err = services.Run(confPath); err != nil {
fatal(err)
}
} else {
h, err := checkSingleton()
defer windows.CloseHandle(h)
if errors.Is(err, syscall.ERROR_ALREADY_EXISTS) {
showMainWindow()
return
}
if err = ui.RunUI(); err != nil {
fatal(err)
}
return
}
usage()
}

View File

@ -3,9 +3,9 @@
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="frpmgr" type="win32"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
@ -19,4 +19,10 @@
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
</windowsSettings>
</application>
</assembly>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

15
cmd/frpmgr/resource.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _RESOURCE_H
#define _RESOURCE_H
#define IDI_APP 7
#define IDI_DOT 21
#define IDD_VALIDATE VALDLG
#define IDC_TITLE 2000
#define IDC_STATIC1 2001
#define IDC_STATIC2 2002
#define IDC_EDIT 2003
#define IDC_ICON 2004
#endif

94
cmd/frpmgr/resource.rc Normal file
View File

@ -0,0 +1,94 @@
#include <windows.h>
#include "resource.h"
#pragma code_page(65001) // UTF-8
#define STRINGIZE(x) #x
#define EXPAND(x) STRINGIZE(x)
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
IDI_APP ICON icon/app.ico
IDI_DOT ICON icon/dot.ico
#define VERSIONINFO_TEMPLATE(block_id, lang_id, charset_id, file_desc) \
VS_VERSION_INFO VERSIONINFO \
FILEVERSION VERSION_ARRAY \
PRODUCTVERSION VERSION_ARRAY \
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK \
FILEFLAGS 0x0 \
FILEOS VOS__WINDOWS32 \
FILETYPE VFT_APP \
FILESUBTYPE VFT2_UNKNOWN \
BEGIN \
BLOCK "StringFileInfo" \
BEGIN \
BLOCK block_id \
BEGIN \
VALUE "CompanyName", "FRP Manager Project" \
VALUE "FileDescription", file_desc \
VALUE "FileVersion", EXPAND(VERSION_STR) \
VALUE "InternalName", "frpmgr" \
VALUE "LegalCopyright", "Copyright © FRP Manager Project" \
VALUE "OriginalFilename", "frpmgr.exe" \
VALUE "ProductName", file_desc \
VALUE "ProductVersion", EXPAND(VERSION_STR) \
VALUE "Comments", "https://github.com/koho/frpmgr" \
END \
END \
BLOCK "VarFileInfo" \
BEGIN \
VALUE "Translation", lang_id, charset_id \
END \
END
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
VERSIONINFO_TEMPLATE(
"040904B0", 0x0409, 1200,
"FRP Manager"
)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
VERSIONINFO_TEMPLATE(
"080404B0", 0x0804, 1200,
"FRP 管理器"
)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
VERSIONINFO_TEMPLATE(
"040404B0", 0x0404, 1200,
"FRP 管理器"
)
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
VERSIONINFO_TEMPLATE(
"041104B0", 0x0411, 1200,
"FRP マネージャ"
)
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
VERSIONINFO_TEMPLATE(
"041204B0", 0x0412, 1200,
"FRP 관리자"
)
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
VERSIONINFO_TEMPLATE(
"0C0A04B0", 0x0C0A, 1200,
"Administrador de FRP"
)
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_VALIDATE DIALOGEX 0, 0, 275, 114
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "@IDD_VALIDATE"
FONT 9, "Microsoft YaHei", 400, 0, 0x80
BEGIN
ICON "",IDC_ICON,7,5,20,18
LTEXT "@IDC_TITLE",IDC_TITLE,31,5,237,30
RTEXT "@IDC_STATIC2",IDC_STATIC2,33,59,49,11
EDITTEXT IDC_EDIT,87,58,141,11,ES_PASSWORD | ES_AUTOHSCROLL
GROUPBOX "@IDC_STATIC1",IDC_STATIC1,26,40,222,43
DEFPUSHBUTTON "@IDOK",IDOK,158,95,52,14
PUSHBUTTON "@IDCANCEL",IDCANCEL,216,95,52,14
END

View File

@ -1,43 +0,0 @@
#include <windows.h>
#pragma code_page(65001) // UTF-8
#define STRINGIZE(x) #x
#define EXPAND(x) STRINGIZE(x)
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
11 ICON icon/app.ico
21 ICON icon/dot.ico
22 ICON icon/open.ico
23 ICON icon/refresh.ico
24 ICON icon/copy.ico
25 ICON icon/copy_act.ico
VS_VERSION_INFO VERSIONINFO \
FILEVERSION FRPMGR_VERSION_ARRAY \
PRODUCTVERSION FRPMGR_VERSION_ARRAY \
FILEOS VOS_NT_WINDOWS32 \
FILETYPE VFT_APP \
FILESUBTYPE VFT2_UNKNOWN \
BEGIN \
BLOCK "StringFileInfo" \
BEGIN \
BLOCK "040904b0" \
BEGIN \
VALUE "CompanyName", "" \
VALUE "FileDescription", "FRP 管理器" \
VALUE "FileVersion", EXPAND(FRPMGR_VERSION_STR) \
VALUE "InternalName", "frpmgr" \
VALUE "LegalCopyright", "" \
VALUE "OriginalFilename", "frpmgr.exe" \
VALUE "ProductName", "frpmgr" \
VALUE "ProductVersion", EXPAND(FRPMGR_VERSION_STR) \
VALUE "Comments", "https://github.com/koho/frpmgr" \
END \
END \
BLOCK "VarFileInfo" \
BEGIN \
VALUE "Translation", 0x0804, 1200 \
END \
END

92
cmd/frpmgr/singleton.go Normal file
View File

@ -0,0 +1,92 @@
package main
import (
"crypto/md5"
"encoding/hex"
"io/fs"
"os"
"syscall"
"github.com/lxn/win"
"golang.org/x/sys/windows"
)
// checkSingleton returns an error when another program is running.
// This function should be only called in gui mode before the window is created.
func checkSingleton() (windows.Handle, error) {
path, err := os.Executable()
if err != nil {
return 0, err
}
hashName := md5.Sum([]byte(path))
name, err := syscall.UTF16PtrFromString("Local\\" + hex.EncodeToString(hashName[:]))
if err != nil {
return 0, err
}
return windows.CreateMutex(nil, false, name)
}
// showMainWindow activates and brings the window of running process to the foreground.
func showMainWindow() error {
var windowToShow win.HWND
path, err := os.Executable()
if err != nil {
return err
}
execFileInfo, err := os.Stat(path)
if err != nil {
return err
}
syscall.MustLoadDLL("user32.dll").MustFindProc("EnumWindows").Call(
syscall.NewCallback(func(hwnd syscall.Handle, lparam uintptr) uintptr {
className := make([]uint16, windows.MAX_PATH)
if _, err = win.GetClassName(win.HWND(hwnd), &className[0], len(className)); err != nil {
return 1
}
if windows.UTF16ToString(className) == "\\o/ Walk_MainWindow_Class \\o/" {
var pid uint32
var imageName string
var imageFileInfo fs.FileInfo
if _, err = windows.GetWindowThreadProcessId(windows.HWND(hwnd), &pid); err != nil {
return 1
}
imageName, err = getImageName(pid)
if err != nil {
return 1
}
imageFileInfo, err = os.Stat(imageName)
if err != nil {
return 1
}
if os.SameFile(execFileInfo, imageFileInfo) {
windowToShow = win.HWND(hwnd)
return 0
}
}
return 1
}), 0)
if windowToShow != 0 {
if win.IsIconic(windowToShow) {
win.ShowWindow(windowToShow, win.SW_RESTORE)
} else {
win.SetForegroundWindow(windowToShow)
}
}
return nil
}
// getImageName returns the full process image name of the given process id.
func getImageName(pid uint32) (string, error) {
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
if err != nil {
return "", err
}
defer windows.CloseHandle(proc)
var exeNameBuf [261]uint16
exeNameLen := uint32(len(exeNameBuf) - 1)
err = windows.QueryFullProcessImageName(proc, 0, &exeNameBuf[0], &exeNameLen)
if err != nil {
return "", err
}
return windows.UTF16ToString(exeNameBuf[:exeNameLen]), nil
}

View File

@ -1,189 +0,0 @@
package config
import (
"github.com/koho/frpmgr/utils"
"gopkg.in/ini.v1"
"os"
"path/filepath"
"strings"
"sync"
)
type AuthInfo struct {
AuthMethod string `ini:"authentication_method,omitempty"`
Token string `ini:"token,omitempty"`
OIDCClientId string `ini:"oidc_client_id,omitempty"`
OIDCClientSecret string `ini:"oidc_client_secret,omitempty"`
OIDCAudience string `ini:"oidc_audience,omitempty"`
OIDCTokenEndpoint string `ini:"oidc_token_endpoint_url,omitempty"`
}
type Common struct {
AuthInfo `ini:"common"`
ServerAddress string `ini:"server_addr"`
ServerPort string `ini:"server_port"`
ConnectServerLocalIP string `ini:"connect_server_local_ip,omitempty"`
HTTPProxy string `ini:"http_proxy,omitempty"`
LogFile string `ini:"log_file,omitempty"`
LogLevel string `ini:"log_level,omitempty"`
LogMaxDays uint `ini:"log_max_days,omitempty"`
AdminAddr string `ini:"admin_addr,omitempty"`
AdminPort string `ini:"admin_port,omitempty"`
AdminUser string `ini:"admin_user,omitempty"`
AdminPwd string `ini:"admin_pwd,omitempty"`
PoolCount uint `ini:"pool_count,omitempty"`
DNSServer string `ini:"dns_server,omitempty"`
Protocol string `ini:"protocol,omitempty"`
LoginFailExit bool `ini:"login_fail_exit"`
User string `ini:"user,omitempty"`
ManualStart bool `ini:"manual_start,omitempty"`
Custom map[string]string `ini:"-"`
}
type Section struct {
Name string `ini:"-"`
Type string `ini:"type,omitempty"`
LocalIP string `ini:"local_ip,omitempty"`
LocalPort string `ini:"local_port,omitempty"`
RemotePort string `ini:"remote_port,omitempty"`
Role string `ini:"role,omitempty"`
SK string `ini:"sk,omitempty"`
ServerName string `ini:"server_name,omitempty"`
BindAddr string `ini:"bind_addr,omitempty"`
BindPort string `ini:"bind_port,omitempty"`
UseEncryption bool `ini:"use_encryption,omitempty"`
UseCompression bool `ini:"use_compression,omitempty"`
Custom map[string]string `ini:"-"`
}
type Config struct {
Name string `ini:"-"`
Path string
Status ServiceState
Common
Items []*Section
}
var Configurations []*Config
var ConfMutex sync.Mutex
var StatusChan = make(chan bool)
func LoadConfig() ([]*Config, error) {
files, err := filepath.Glob("*.ini")
if err != nil {
return nil, err
}
confList := make([]*Config, 0)
for _, f := range files {
c := new(Config)
if err = c.Load(f); err != nil {
continue
}
confList = append(confList, c)
}
return confList, nil
}
func GetConfigNames() []string {
names := make([]string, 0)
for _, c := range Configurations {
names = append(names, c.Name)
}
return names
}
func (c *Config) GetSectionNames() []string {
names := make([]string, 0)
for _, sect := range c.Items {
names = append(names, sect.Name)
}
return names
}
func (c *Config) Load(source string) error {
c.Name = NameFromPath(source)
c.Path = source
c.Status = StateStopped
cfg, err := ini.Load(source)
if err != nil {
return err
}
common, err := cfg.GetSection("common")
if err != nil {
return err
}
err = common.MapTo(&c.Common)
if err != nil {
return err
}
c.Common.Custom = make(map[string]string)
for _, key := range common.Keys() {
if utils.GetFieldName(key.Name(), "ini", Common{}) == "" && utils.GetFieldName(key.Name(), "ini", AuthInfo{}) == "" {
c.Common.Custom[key.Name()] = key.String()
}
}
c.Items = make([]*Section, 0)
for _, section := range cfg.Sections() {
if section.Name() == "common" || section.Name() == "DEFAULT" {
continue
}
s := Section{Name: section.Name()}
section.MapTo(&s)
s.Custom = make(map[string]string)
for _, key := range section.Keys() {
if utils.GetFieldName(key.Name(), "ini", Section{}) == "" {
s.Custom[key.Name()] = key.String()
}
}
c.Items = append(c.Items, &s)
}
return nil
}
func (c *Config) SaveTo(path string) error {
cfg := ini.Empty()
common, err := cfg.NewSection("common")
if err != nil {
return err
}
common.ReflectFrom(&c.Common)
for k, v := range c.Common.Custom {
common.Key(k).SetValue(v)
}
for _, item := range c.Items {
s, err := cfg.NewSection(item.Name)
if err != nil {
return err
}
s.ReflectFrom(&item)
for k, v := range item.Custom {
s.Key(k).SetValue(v)
}
}
for _, sect := range cfg.Sections() {
if sect.Name() == "common" || sect.Name() == "DEFAULT" {
continue
}
if _, found := utils.Find(c.GetSectionNames(), sect.Name()); !found {
cfg.DeleteSection(sect.Name())
}
}
return cfg.SaveTo(path)
}
func (c *Config) Save() error {
return c.SaveTo(c.Name + ".ini")
}
func (c *Config) Delete() error {
return os.Remove(c.Name + ".ini")
}
func NameFromPath(confPath string) string {
name := filepath.Base(confPath)
return strings.TrimSuffix(name, filepath.Ext(name))
}
func PathFromName(name string) (string, error) {
return filepath.Abs(name + ".ini")
}

View File

@ -1,16 +0,0 @@
package config
type ServiceState int
const (
StateUnknown ServiceState = iota
StateStarted
StateStopped
StateStarting
StateStopping
)
var (
Version = "v0.0.0"
FRPVersion = "v0.0.0"
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/screenshot_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/screenshot_zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/sponsor_signpath.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

3
generate.go Normal file
View File

@ -0,0 +1,3 @@
package frpmgr
//go:generate go run resource.go

69
go.mod
View File

@ -1,15 +1,66 @@
module github.com/koho/frpmgr
go 1.16
go 1.24.0
require (
github.com/fatedier/frp v0.39.1
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee
github.com/Microsoft/go-winio v0.6.2
github.com/fatedier/frp v0.65.0
github.com/fatedier/golib v0.5.1
github.com/fsnotify/fsnotify v1.9.0
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/miekg/dns v1.1.45 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
gopkg.in/ini.v1 v1.63.0
github.com/lxn/win v0.0.0-20210218163916-a377121e959e
github.com/pelletier/go-toml/v2 v2.2.0
github.com/samber/lo v1.47.0
golang.org/x/sys v0.32.0
golang.org/x/text v0.24.0
gopkg.in/ini.v1 v1.67.0
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/reedsolomon v1.12.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/stun/v2 v2.0.0 // indirect
github.com/pion/transport/v2 v2.2.1 // indirect
github.com/pion/transport/v3 v3.0.1 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/quic-go v0.53.0 // indirect
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/templexxx/cpu v0.1.1 // indirect
github.com/templexxx/xorsimd v0.4.3 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vishvananda/netlink v1.3.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtaci/kcp-go/v5 v5.6.13 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.31.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apimachinery v0.28.8 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace github.com/lxn/walk => github.com/koho/frpmgr v0.0.0-20250619085234-ed99ab60add0

750
go.sum
View File

@ -1,698 +1,256 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
github.com/fatedier/frp v0.39.1 h1:t5NiZxpjKRG1Lpr0jCS9AbTPx4Y7ThZsvpJzXvJKPN8=
github.com/fatedier/frp v0.39.1/go.mod h1:FpjNieDWogfli0W8aGAfNIyA/TmIOT1f0Ixk5Zgxdew=
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee h1:iS0wlj2uZPxh3pciAf/HTzi88Kqu7DPh1jNKgJaFhtI=
github.com/fatedier/golib v0.1.1-0.20220119075718-78e5cf8c00ee/go.mod h1:fLV0TLwHqrnB/L3jbNl67Gn6PCLggDGHniX1wLrA2Qo=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/fatedier/frp v0.65.0 h1:Wn8qjXXf+G7+czZB0o7Y4U7Ohy9q/LOAqzK5AVKEel4=
github.com/fatedier/frp v0.65.0/go.mod h1:P5aPIs0cqLWDKIYkOziZQGZpb4DwzQkRyK5h+OtPp0Y=
github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c h1:nqkErwUGfpZZMqj29WZ9U/wz2OpJVDuiokLhE/3Y7IQ=
github.com/hashicorp/yamux v0.0.0-20210707203944-259a57b3608c/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/reedsolomon v1.9.15 h1:g2erWKD2M6rgnPf89fCji6jNlhMKMdXcuNHMW1SYCIo=
github.com/klauspost/reedsolomon v1.9.15/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
github.com/klauspost/reedsolomon v1.12.0/go.mod h1:EPLZJeh4l27pUGC3aXOjheaoh1I9yut7xTURiW3LQ9Y=
github.com/koho/frpmgr v0.0.0-20250619085234-ed99ab60add0 h1:45SbnvjExN+92n5O4YVWPXfF/sQG0iLvijF247q0kjs=
github.com/koho/frpmgr v0.0.0-20250619085234-ed99ab60add0/go.mod h1:BEvTAgZsEET00wLNsOhKg8fX//k6l4b5INTzX2APBb8=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk=
github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pires/go-proxyproto v0.5.0 h1:A4Jv4ZCaV3AFJeGh5mGwkz4iuWUYMlQ7IoO/GTuSuLo=
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI=
github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.63.0 h1:2t0h8NA59dpVQpa5Yh8cIcR6nHAeBIEk0zlLVqfw4N4=
gopkg.in/ini.v1 v1.63.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ=
k8s.io/apimachinery v0.28.8/go.mod h1:cBnwIM3fXoRo28SqbV/Ihxf/iviw85KyXOrzxvZQ83U=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

1330
i18n/catalog.go Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

104
i18n/text.go Normal file
View File

@ -0,0 +1,104 @@
package i18n
//go:generate go run golang.org/x/text/cmd/gotext -srclang=en-US update -out=catalog.go -lang=en-US,zh-CN,zh-TW,ja-JP,ko-KR,es-ES ../cmd/frpmgr
import (
"encoding/json"
"os"
"golang.org/x/sys/windows"
"golang.org/x/text/language"
"golang.org/x/text/message"
"github.com/koho/frpmgr/pkg/config"
)
var (
printer *message.Printer
useLang language.Tag
IDToName = map[string]string{
"zh-CN": "简体中文",
"zh-TW": "繁體中文",
"en-US": "English",
"ja-JP": "日本語",
"ko-KR": "한국어",
"es-ES": "Español",
}
)
func init() {
if preferredLang := langInConfig(); preferredLang != "" {
useLang = language.Make(preferredLang)
} else {
useLang = lang()
}
printer = message.NewPrinter(useLang)
}
// GetLanguage returns the current display language code.
func GetLanguage() string {
return useLang.String()
}
// langInConfig returns the UI language code in config file
func langInConfig() string {
b, err := os.ReadFile(config.LangFile)
if err == nil {
id := string(b)
if _, ok := IDToName[id]; ok {
return id
}
}
b, err = os.ReadFile(config.DefaultAppFile)
if err != nil {
return ""
}
var s struct {
Lang string `json:"lang"`
}
if err = json.Unmarshal(b, &s); err != nil {
return ""
}
if _, ok := IDToName[s.Lang]; ok {
return s.Lang
}
return ""
}
// lang returns the user preferred UI language.
func lang() (tag language.Tag) {
tag = language.English
languages, err := windows.GetUserPreferredUILanguages(windows.MUI_LANGUAGE_NAME)
if err != nil {
return
}
if match := message.MatchLanguage(languages...); !match.IsRoot() {
tag = match
}
return
}
// Sprintf is just a wrapper function of message printer.
func Sprintf(key message.Reference, a ...interface{}) string {
return printer.Sprintf(key, a...)
}
// SprintfColon adds a colon at the tail of a string.
func SprintfColon(key message.Reference, a ...interface{}) string {
return Sprintf(key, a...) + ":"
}
// SprintfEllipsis adds an ellipsis at the tail of a string.
func SprintfEllipsis(key message.Reference, a ...interface{}) string {
return Sprintf(key, a...) + "..."
}
// SprintfLSpace adds a space at the start of a string.
func SprintfLSpace(key message.Reference, a ...interface{}) string {
return " " + Sprintf(key, a...)
}
// SprintfRSpace adds a space at the end of a string.
func SprintfRSpace(key message.Reference, a ...interface{}) string {
return Sprintf(key, a...) + " "
}

BIN
icon/app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -1,427 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
zoomAndPan="magnify"
contentStyleType="text/css"
id="svg2"
sodipodi:docname="Crystal_Clear_app_linneighborhood.new.svg"
version="1.1"
width="407"
preserveAspectRatio="xMidYMid meet"
inkscape:version="0.48.5 r10040"
height="407">
<metadata
id="metadata146">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
objecttolerance="10"
bordercolor="#666666"
inkscape:window-height="834"
inkscape:zoom="0.55698257"
id="namedview84"
gridtolerance="10"
inkscape:current-layer="svg2"
inkscape:window-y="27"
inkscape:cy="158.05771"
inkscape:window-x="0"
inkscape:cx="6.7187214"
showgrid="false"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1440"
pagecolor="#ffffff"
borderopacity="1"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs4">
<linearGradient
id="linearGradient3936"
inkscape:collect="always">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3938" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3940" />
</linearGradient>
<linearGradient
id="linearGradient4370"
inkscape:collect="always">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4372" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4374" />
</linearGradient>
<linearGradient
id="linearGradient4279"
inkscape:collect="always">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4281" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4283" />
</linearGradient>
<linearGradient
id="linearGradient4036"
inkscape:collect="always">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4038" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4040" />
</linearGradient>
<linearGradient
id="linearGradient4024">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4026" />
<stop
offset="0.5"
style="stop-color:#ffffff;stop-opacity:0.49803922;"
id="stop4034" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4028" />
</linearGradient>
<linearGradient
id="linearGradient3685">
<stop
style="stop-color:#ffffff;stop-opacity:0.30000001"
offset="0"
id="stop3687" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop3689" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.97422123,0,0,1.0442282,-152.37144,-243.18363)"
inkscape:collect="always"
id="linearGradient3868"
gradientUnits="userSpaceOnUse"
x1="369"
y1="614.86218"
x2="369"
y2="478.36218"
xlink:href="#linearGradient3685" />
<linearGradient
gradientTransform="matrix(-0.12477578,0.62894977,-1.344634,-0.08139473,448.14031,-32.957024)"
inkscape:collect="always"
id="linearGradient4032"
gradientUnits="userSpaceOnUse"
spreadMethod="reflect"
x1="84.270332"
y1="158.15402"
y2="158.15402"
x2="398.27032"
xlink:href="#linearGradient4024" />
<linearGradient
x1="149.80469"
xlink:href="#linearGradient4036"
y1="360.66406"
x2="365.625"
gradientUnits="userSpaceOnUse"
y2="360.66406"
id="linearGradient4042"
inkscape:collect="always"
gradientTransform="matrix(-0.05429007,-0.74636342,-1.5075144,0.03084743,772.50444,499.09629)" />
<filter
x="-0.29278594"
y="-0.30742526"
width="1.5855719"
id="filter4247"
height="1.6148505"
inkscape:collect="always"
color-interpolation-filters="sRGB">
<feGaussianBlur
stdDeviation="3.2523829"
inkscape:collect="always"
id="feGaussianBlur4249" />
</filter>
<filter
x="-0.35134313"
y="-0.36891031"
width="1.7026863"
id="filter4255"
height="1.7378206"
inkscape:collect="always"
color-interpolation-filters="sRGB">
<feGaussianBlur
stdDeviation="3.9028595"
inkscape:collect="always"
id="feGaussianBlur4257" />
</filter>
<filter
x="-0.35134313"
y="-0.36891031"
width="1.7026863"
id="filter4259"
height="1.7378206"
inkscape:collect="always"
color-interpolation-filters="sRGB">
<feGaussianBlur
stdDeviation="3.9028595"
inkscape:collect="always"
id="feGaussianBlur4261" />
</filter>
<filter
x="-0.35134313"
y="-0.36891031"
width="1.7026863"
id="filter4263"
height="1.7378206"
inkscape:collect="always"
color-interpolation-filters="sRGB">
<feGaussianBlur
stdDeviation="3.9028595"
inkscape:collect="always"
id="feGaussianBlur4265" />
</filter>
<filter
x="-0.35134313"
y="-0.36891031"
width="1.7026863"
id="filter4271"
height="1.7378206"
inkscape:collect="always"
color-interpolation-filters="sRGB">
<feGaussianBlur
stdDeviation="3.9028595"
inkscape:collect="always"
id="feGaussianBlur4273" />
</filter>
<linearGradient
x1="213.15121"
xlink:href="#linearGradient4279"
y1="252.88338"
x2="242.87743"
gradientUnits="userSpaceOnUse"
y2="252.88338"
id="linearGradient4285"
inkscape:collect="always"
gradientTransform="matrix(-1.8191796,0,0,1,610.04675,-50.01213)" />
<linearGradient
x1="213.27011"
xlink:href="#linearGradient4370"
y1="252.88338"
x2="242.75851"
gradientUnits="userSpaceOnUse"
y2="252.88338"
id="linearGradient4376"
inkscape:collect="always"
gradientTransform="matrix(-4.3200016,-8.8839851,-0.82294491,0.0107314,1396.8773,2227.0713)" />
<linearGradient
x1="210.78125"
xlink:href="#linearGradient3936"
y1="257.83203"
x2="239.90234"
gradientUnits="userSpaceOnUse"
y2="257.83203"
id="linearGradient3942"
inkscape:collect="always"
gradientTransform="matrix(-0.74242242,-0.5447124,0.51896924,-0.77924984,254.64457,522.83946)" />
</defs>
<path
d="m 402.75789,203.5 a 199.25789,199.25789 0 0 1 -398.51578,0 199.25789,199.25789 0 1 1 398.51578,0 z"
id="path2830"
inkscape:connector-curvature="0"
style="fill:#183f8e;fill-opacity:1;stroke:#0b1e46;stroke-width:7.80000019;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="M 36.62747,220.83189 C 51.67425,307.12428 62.75993,396.04902 204.19353,398.35069 358.47758,374.2341 361.03402,302.45167 377.60492,235.45109 337.09511,301.76722 284.16517,350.61049 202.24509,358.67001 104.88013,353.98314 77.1934,281.10538 36.62747,220.83189 z"
id="path3663"
inkscape:connector-curvature="0"
style="fill:url(#linearGradient3868);fill-opacity:1;stroke:none" />
<path
d="m 383.06193,177.62039 c 0,0 -2.63803,13.19016 -4.8992,14.69761 -2.26117,1.50744 -13.94389,13.94388 -13.94389,13.94388 l -2.26117,7.53723 1.13059,21.85798 -10.55213,16.20506 -2.63803,15.45133 -7.53724,9.7984 -20.72739,0 -13.19016,-7.16037 -6.40665,-13.94388 -3.76862,-9.04469 1.13059,-10.92899 4.52234,-11.30585 -6.78351,-16.58191 0,-14.69761 -15.8282,-3.39176 -21.10425,0.37687 -18.84309,-18.46623 -0.37686,-27.13404 10.92899,-18.08936 16.95878,-15.07447 29.77207,-1.13059 13.19016,10.55213 31.65639,-1.13058 0,-3.76862 -11.68272,-13.56702 -5.27606,0 -2.63803,3.39175 c 0,0 -1.50745,-3.76861 -1.50745,-2.26117 0,1.50745 0.75372,4.52234 0.75372,4.52234 l -4.52234,-6.40665 0,4.89921 -3.6708,-3.44062 -6.7188,-12.353926 -3.6845,0.21674 -1.95062,2.60083 4.33471,6.718796 6.06859,5.20165 -0.43347,2.16736 -3.03429,-2.16736 -0.86695,1.95062 0.21674,2.60083 -2.38409,-0.86694 0.21673,-3.90124 -9.10289,-13.220866 -13.22087,-0.65021 -4.76818,4.55145 -2.81756,10.403296 -5.41839,1.30042 -1.08368,2.81756 -11.05351,-3.0343 0,-17.122106 2.3841,-2.38409 c 1.81765,-0.88212 5.37275,-0.89641 11.70371,-0.6502 3.94024,-0.32222 2.32657,-6.19836 0,-12.7874 l -2.81756,-1.51715 2.81756,-1.73388 4.33471,0.6502 11.27025,-3.90124 6.28533,-6.93553 6.7188,-8.66943 3.68451,0.86695 4.11797,-3.90124 4.06343,-9.24691 c 9.68955,8.27679 20.00533,16.62146 27.49814,24.79876 32.11834,32.45403 53.72083,67.452026 51.80744,95.868386 -5.32863,0.34435 -5.25327,3.70738 -8.49669,7.17955 l -12.23575,0.38802 -12.13719,-14.30455 -11.27025,-17.33884 9.7531,20.15641 9.53637,16.90537 15.38822,0 -0.59101,3.63483 z"
id="path4122"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 199.74621,215.24232 -2.11534,9.16648 -7.75626,9.8716 -0.70511,13.39717 -15.51251,14.10228 -2.82046,11.98694 -13.39717,12.69206 -15.51251,9.87159 -3.52557,15.51251 -2.82045,16.21763 3.52557,13.39717 -12.69206,-1.41023 -4.23068,-5.64091 c 0,0 -6.34603,-9.16649 -4.9358,-13.39717 1.41023,-4.23069 1.41023,-33.84548 1.41023,-33.84548 l 1.41023,-21.85854 c 0,0 -0.70754,-14.39556 -2.11535,-17.62785 -1.06966,-2.45591 -16.8481,-17.1798 -14.95322,-19.07468 l -4.08486,-14.06568 0.70512,-12.69206 7.75625,-9.8716 L 89.74841,177.16616 87.8641,166.78388 69.3001,160.24342 54.4927,146.84625 48.14668,124.98771 49.5569,97.488264 c -4.30241,7.365846 -4.17276,15.618106 -3.52557,23.973876 l -1.76808,1.5133 -1.13059,-9.79841 -1.88431,-9.7984 6.78351,-16.205046 c 0,0 20.79061,-19.85867 22.23484,-20.7274 2.50607,-1.50744 1.68473,-6.70752 1.17618,-6.70752 l 25.82263,-20.90822 12.57167,14.04872 3.76862,7.9141 c 0,0 4.1461,0.33391 5.65293,0.37686 3.53828,0.10086 8.01172,-25.27388 6.40665,-26.00346 l 12.43643,4.52234 12.8133,-1.13058 12.43644,18.46622 -7.50684,14.83284 2.9845,0.24163 7.53723,0.37686 -1.13058,8.66782 -9.42155,-4.52234 0,-4.8992 -12.43643,0 0.37686,4.8992 8.29096,4.52234 -2.63804,10.55213 -9.04468,-3.39176 -9.04468,7.53724 -7.91409,2.63803 -1.50745,8.290956 -4.14548,7.16037 -10.17527,7.53723 -1.50744,5.27607 2.26117,5.65292 -1.50745,1.88431 -3.39176,-0.37686 -2.26117,-6.02979 -2.63803,-3.01489 -10.55213,1.13058 -13.56702,5.27607 -3.39175,13.19016 2.26117,3.39175 9.42154,1.88431 2.63803,-4.52234 10.92899,-0.37686 -4.14548,6.02979 4.52234,6.78351 5.65293,2.63803 -2.63803,5.65292 6.78351,8.29096 10.17526,-4.52234 22.23485,-0.37686 6.02978,9.42154 13.19016,5.27607 9.42155,6.78351 9.04468,10.17526 13.56702,3.39176 9.63748,10.86475 z"
id="path4091"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 143.37788,156.13929 a 4.14548,3.0148947 0 0 1 -8.29096,0 4.14548,3.0148947 0 1 1 8.29096,0 z"
id="path4101"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="M 214.52594,6.5398948 C 302.46431,20.561174 355.47357,52.086404 346.87978,114.4826 322.4798,198.5275 248.02366,196.39738 179.38024,204.27431 70.00917,182.56946 66.45439,142.54809 63.50845,102.42131 70.67184,26.720464 138.88855,12.871314 214.52594,6.5398948 z"
id="path3683"
inkscape:connector-curvature="0"
style="fill:url(#linearGradient4032);fill-opacity:1;stroke:none"
inkscape:transform-center-y="56.105886" />
<path
d="m 165.23587,142.76068 a 0.94215429,1.319016 0 1 1 -0.14323,-0.69912"
id="path4106"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 126.04224,149.35576 c 2.15158,4.70346 9.09922,7.09931 -5.27606,7.9141 -6.49976,-0.83396 -7.98706,-7.97717 -18.08937,-13.19016 9.39282,-2.25218 15.76779,3.04028 23.36543,5.27606 z"
id="path4097"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 166.74331,130.51266 a 0.94215429,2.638032 0 1 1 -0.14322,-1.39824"
id="path4108"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 165.61272,181.76587 a 1.1305852,1.1305852 0 1 1 -0.17188,-0.59925"
id="path4110"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 160.71351,180.8237 a 0.37686172,0.94215429 0 1 1 -0.0573,-0.49937"
id="path4112"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 228.17177,182.89645 a 1.8843086,1.8843086 0 1 1 -0.28646,-0.99874"
id="path4114"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 223.64941,188.17252 a 0.94215429,1.8843086 0 1 1 -0.14323,-0.99875"
id="path4116"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 221.38826,193.44858 a 0.75372344,1.1305852 0 1 1 -0.11458,-0.59925"
id="path4118"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
d="m 381.93134,229.51162 -10.55213,9.42155 -6.02978,16.20505 3.76861,8.29096 9.04469,-5.27607 3.39175,-11.30585 2.63803,-8.66782 -2.26117,-8.66782 z"
id="path4120"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 279.40315,61.499814 -2.60083,1.08368 -2.60083,1.73388 -3.25103,1.73389 -2.16736,-1.51715 1.30042,-3.68451 -2.38409,-5.41838 -0.43347,-4.76819 -0.65021,-3.03429 -2.81756,-1.08368 2.38409,-2.16736 4.98492,0 0,2.60083 4.76818,6.7188 3.6845,3.90124 -0.21673,3.90124 z"
id="path4124"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 265.53207,62.800224 0.43347,-3.25103 -0.86694,-3.90124 -2.60083,-0.21673 -4.33471,2.81756 -0.43347,3.90124 c 0,0 3.25103,1.51715 4.33471,1.51715 1.08368,0 3.46777,-0.86695 3.46777,-0.86695 z"
id="path4126"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 234.99702,10.573344 c 0.001,3.50761 -13.24553,12.13081 -13.24553,12.13081 l -18.85599,5.63512 -6.50207,10.40331 c 0,0 -8.45268,1.30041 -9.31962,0.21674 -0.86695,-1.08368 -6.50207,-8.45269 -6.50207,-8.45269 0,0 -10.46552,-3.03944 -9.53636,-4.76818 1.67578,-3.11786 -19.91653,-11.10653 -20.7305,-10.45534 22.24425,-6.3867792 52.84194,-9.8068792 84.69214,-4.70977 z"
id="path4128"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
d="m 231.72133,42.860554 7.15227,0.21674 2.60083,-2.81756 0.6502,-3.90124 -5.63512,-0.86694 -6.7188,0.86694 -3.0343,3.0343 0.21674,0.43347 4.76818,3.03429 z"
id="path4130"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<path
inkscape:transform-center-x="-5.386165"
id="path3890"
d="m 208.94837,237.33272 c -61.305,1.25445 -108.37961,38.33046 -105.14407,82.81158 3.23553,44.48112 55.55597,79.52326 116.86097,78.26881 61.305,-1.25445 108.3796,-38.33046 105.14407,-82.81157 -3.23553,-44.48113 -55.55597,-79.52327 -116.86097,-78.26882 z"
style="opacity:0.46835441;fill:url(#linearGradient4042);fill-opacity:1;fill-rule:evenodd;stroke:none"
inkscape:transform-center-y="-69.571298"
inkscape:connector-curvature="0" />
<path
id="path4277"
d="m 169.11869,202.87125 c 0,99.15656 11.69838,179.53883 26.12908,179.53883 14.43068,0 26.12906,-80.38227 26.12906,-179.53883 0,-99.15656 -11.69838,-179.538837 -26.12906,-179.538837 -14.4307,0 -26.12908,80.382277 -26.12908,179.538837 z"
style="fill:none;stroke:url(#linearGradient4285);stroke-width:5;stroke-miterlimit:4;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path4277-1"
d="M 141.69738,76.507699 C 60.096991,77.571788 21.726927,135.56357 55.995469,206.03605 90.263968,276.50844 184.19426,332.77501 265.79465,331.71092 347.39504,330.64683 385.76511,272.65504 351.49661,202.18265 317.22807,131.71017 223.29776,75.44361 141.69738,76.507699 z"
style="fill:none;stroke:url(#linearGradient4376);stroke-width:5;stroke-miterlimit:4;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path4044-6-7-2"
d="m 206.93359,339.08203 c 0,7.01143 -5.96808,12.69531 -13.33007,12.69531 -7.362,0 -13.33008,-5.68388 -13.33008,-12.69531 0,-7.01143 5.96808,-12.69531 13.33008,-12.69531 7.36199,0 13.33007,5.68388 13.33007,12.69531 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;filter:url(#filter4263)"
transform="matrix(0.99132302,0,0,1.0198451,23.878498,-253.73902)"
inkscape:connector-curvature="0" />
<path
id="path4044-6-7-2-2"
d="m 206.93359,339.08203 c 0,7.01143 -5.96808,12.69531 -13.33007,12.69531 -7.362,0 -13.33008,-5.68388 -13.33008,-12.69531 0,-7.01143 5.96808,-12.69531 13.33008,-12.69531 7.36199,0 13.33007,5.68388 13.33007,12.69531 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;filter:url(#filter4271)"
transform="matrix(0.88117602,0,0,0.90652897,4.961992,-223.17678)"
inkscape:connector-curvature="0" />
<path
id="path4044"
d="m 206.93359,339.08203 c 0,7.01143 -5.96808,12.69531 -13.33007,12.69531 -7.362,0 -13.33008,-5.68388 -13.33008,-12.69531 0,-7.01143 5.96808,-12.69531 13.33008,-12.69531 7.36199,0 13.33007,5.68388 13.33007,12.69531 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;filter:url(#filter4247)"
transform="matrix(1.4193929,0,0,1.4633411,-118.05725,-194.61686)"
inkscape:connector-curvature="0" />
<path
id="path3934"
d="m 212.19864,192.60789 c 52.76137,-79.22298 99.54177,-140.504607 104.48693,-136.876359 4.94517,3.628253 -33.81752,70.792439 -86.57889,150.015419 -52.76137,79.22298 -99.54176,140.50461 -104.48693,136.87636 -4.94517,-3.62825 33.81752,-70.79244 86.57889,-150.01542 z"
style="fill:none;stroke:url(#linearGradient3942);stroke-width:4.64010096;stroke-miterlimit:4;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
id="path4044-6-7"
d="m 206.93359,339.08203 c 0,7.01143 -5.96808,12.69531 -13.33007,12.69531 -7.362,0 -13.33008,-5.68388 -13.33008,-12.69531 0,-7.01143 5.96808,-12.69531 13.33008,-12.69531 7.36199,0 13.33007,5.68388 13.33007,12.69531 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;filter:url(#filter4259)"
transform="matrix(1.10147,0,0,1.1331612,66.8255,-256.41909)"
inkscape:connector-curvature="0" />
<path
id="path4044-6"
d="m 206.93359,339.08203 c 0,7.01143 -5.96808,12.69531 -13.33007,12.69531 -7.362,0 -13.33008,-5.68388 -13.33008,-12.69531 0,-7.01143 5.96808,-12.69531 13.33008,-12.69531 7.36199,0 13.33007,5.68388 13.33007,12.69531 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:0;stroke-miterlimit:4;stroke-opacity:1;filter:url(#filter4255)"
transform="matrix(1.211617,0,0,1.2464773,-13.370076,-206.53428)"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="cscsscssscsssc"
id="path3957"
d="m 162.83483,386.89854 c 22.48747,-15.69642 11.27745,-17.27867 20.38403,-20.23002 13.91318,-4.50911 43.35225,6.76461 43.35225,6.76461 0,0 22.1813,7.16185 33.3236,9.98354 6.09329,1.54307 10.46554,-13.73761 16.74815,-13.54242 11.68148,0.36291 22.9682,5.88013 22.9682,5.88013 0,0 -15.10414,4.57739 -18.85157,7.81631 -7.53806,6.51517 -29.6514,10.5094 -42.06951,12.75363 -12.4181,2.24424 -25.53685,1.669 -38.152,1.34654 -14.4392,-0.36908 -41.29393,-4.48846 -41.29393,-4.48846 0,0 -17.43773,-0.66839 -24.23775,-5.38617 -5.86455,-4.06876 -15.59404,1.55575 -15.47862,-12.87574 0.0574,-7.1704 9.93198,3.44129 19.79228,6.32411 11.10693,3.2473 23.51487,5.65394 23.51487,5.65394 z"
inkscape:connector-curvature="0"
style="opacity:0.7;fill:#e3e6f1;fill-opacity:1;stroke:none" />
<ellipse
rx="198.83333"
ry="196.16666"
style="fill:none;stroke:#0b1e46;stroke-width:5"
cx="238.16667"
cy="241.83334"
id="ellipse144"
sodipodi:cx="238.16667"
sodipodi:cy="241.83334"
sodipodi:rx="198.83333"
sodipodi:ry="196.16666"
transform="translate(-34.65789,-38.342106)" />
<svg width="196" height="196" viewBox="0 0 196 196" xmlns="http://www.w3.org/2000/svg">
<circle cx="98" cy="98" r="98" fill="#307fec"/>
<path d="M0 0C9.14866886.037533 17.22012512 2.42754171 25.875 5.3125 25.24816224 9.20323238 24.06490291 12.41180682 22.40625 15.98046875 21.91769531 17.03814453 21.42914063 18.09582031 20.92578125 19.18554688 20.41402344 20.27931641 19.90226562 21.37308594 19.375 22.5 18.86324219 23.60537109 18.35148437 24.71074219 17.82421875 25.84960938 14.22879464 33.60491071 14.22879464 33.60491071 12.875 36.3125c6.6.0 13.2.0 20 0-2.74311757 8.2293527-5.81983443 14.5072504-10 22-6.6.0-13.2.0-20 0-.66 1.98-1.32 3.96-2 6C-.30574675 66.89295828-1.51012196 69.4485972-2.75 72-7.20837762 81.30592927-11.1713474 90.78137319-15.125 100.3125c-11.55.0-23.1.0-35 0 1.12511809-6.75070855 1.12511809-6.75070855 2.92578125-10.4609375C-46.81443359 89.04976563-46.42964844 88.24796875-46.03320312 87.421875-45.63037109 86.60203125-45.22753906 85.7821875-44.8125 84.9375-44.42126953 84.12539062-44.03003906 83.31328125-43.62695312 82.4765625c1.48804651-3.07973806 2.98287133-6.15404921 4.54638671-9.19628906C-37.35389762 69.91812423-35.79432282 66.56321047-34.42578125 63.0390625-31.06119644 54.44632958-27.11915997 46.09069224-23.3125 37.6875c1.57027085-3.47078555 3.13906321-6.94223608 4.70703125-10.4140625C-18.22574921 26.43367371-17.84602966 25.59390991-17.45480347 24.72869873c2.79794821-6.20059747 5.53869159-12.4246724 8.22062683-18.67636108C-6.76291797.33468549-6.2733019.13332521.0.0z"
fill="#fcfdfe" transform="translate(86.125,50.6875)"/>
<path d="M0 0C5.49110072-.07550699 10.98188377-.12922511 16.47338867-.16479492c1.85877765-.01486511 3.71752171-.03516313 5.57617188-.06152344C56.20891765-.69780069 56.20891765-.69780069 69 7 69.88558594 7.51433594 69.88558594 7.51433594 70.7890625 8.0390625 73.62173603 9.79773659 75.12383176 11.18574763 77 14c.265625 2.92578125.265625 2.92578125.25 6.3125C77.25773437 21.97732422 77.25773437 21.97732422 77.265625 23.67578125 77 27 77 27 75 33c-11.55.0-23.1.0-35 0C40.33 31.02 40.66 29.04 41 27 41.92939473 18.83651171 41.92939473 18.83651171 39 11.5 30.21072208 4.17560173 20.01523416 2.92347445 8.9375 1.875c-.86560547-.08636719-1.73121094-.17273437-2.62304688-.26171875C4.21011156 1.40400971 2.10511876 1.20130332.0 1 0 .67.0.34.0.0z"
fill="#f8fafe" transform="translate(83,45)"/>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="2" x2="2" y1="30" y2="2"><stop offset="0" stop-color="#197cf1"/><stop offset="1" stop-color="#20bcfa"/></linearGradient><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="23" x2="30" xlink:href="#a" y1="13" y2="20"/><linearGradient id="a"><stop offset="0" stop-color="#292c2f"/><stop offset="1" stop-opacity="0"/></linearGradient><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="6" x2="26" xlink:href="#a" y1="6" y2="26"/><linearGradient id="e" gradientUnits="userSpaceOnUse" x1="2" x2="2" y1="30" y2="2"><stop offset="0" stop-color="#54d883"/><stop offset="1" stop-color="#abf9c7"/></linearGradient><rect fill="url(#b)" height="28" rx="14" width="28" x="2" y="2"/><path d="M16 2A13.965 13.965 0 0 0 2.078 14.53l.543.07L6 17l7.188 4.133 1.47 8.799A13.97 13.97 0 0 0 30 16c0-7.756-6.244-14-14-14z" fill="url(#c)" fill-rule="evenodd" opacity=".2"/><path d="M29.73 13.27L25 18l3.758 3.758a13.984 13.984 0 0 0 .972-8.488z" fill="url(#d)" fill-rule="evenodd" opacity=".2"/><path d="M16 2a14 14 0 0 0-13.059 9H3l3-1 1 1 1 1v-2l2-1V8l2-1 2-2v1l1-1-1-1h2v1l2-1-1-1v-.95A14 14 0 0 0 16 2zm10.375 4.625L26 7v1h1.469a14 14 0 0 0-1.094-1.375zm1.316 1.684L27 9l-2 2-1 1-1 3v1l1 1 1 1 2 1 2.791-.688A14 14 0 0 0 30 16a14 14 0 0 0-2.309-7.691zM4 13l-1 1h-.838c-.05.34-.088.681-.113 1.023v.002L4 16l1 1 2 1 1 1-1 1v2l1 2 2 2v2.629A14 14 0 0 0 16 30c.17-.003.34-.009.51-.018L17 29v-1l2-1 1-1v-2l1-1v-1l-1-1-3-1-1-1-1-1h-1l-1-1-3-1-2 1H6v-2H5v-2z" fill="url(#e)"/><path d="M29.979 15.414A14 14 0 0 1 16 29 14 14 0 0 1 2.021 15.586 14 14 0 0 0 2 16a14 14 0 0 0 14 14 14 14 0 0 0 14-14 14 14 0 0 0-.021-.586z" fill="#292c2f" opacity=".2"/></svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16">
<g fill="#555">
<path d="M11 5H5v9h8V5h-2zm0 7H7V7h4v5z"/>
<path d="M10 2H2v9h2V4h6z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 207 B

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16">
<g fill="#315efb">
<path d="M11 5H5v9h8V5h-2zm0 7H7V7h4v5z"/>
<path d="M10 2H2v9h2V4h6z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 210 B

BIN
icon/dot.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,5 +0,0 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sync-alt"
class="svg-inline--fa fa-sync-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="#0000ff"
d="M370.72 133.28C339.458 104.008 298.888 87.962 255.848 88c-77.458.068-144.328 53.178-162.791 126.85-1.344 5.363-6.122 9.15-11.651 9.15H24.103c-7.498 0-13.194-6.807-11.807-14.176C33.933 94.924 134.813 8 256 8c66.448 0 126.791 26.136 171.315 68.685L463.03 40.97C478.149 25.851 504 36.559 504 57.941V192c0 13.255-10.745 24-24 24H345.941c-21.382 0-32.09-25.851-16.971-40.971l41.75-41.749zM32 296h134.059c21.382 0 32.09 25.851 16.971 40.971l-41.75 41.75c31.262 29.273 71.835 45.319 114.876 45.28 77.418-.07 144.315-53.144 162.787-126.849 1.344-5.363 6.122-9.15 11.651-9.15h57.304c7.498 0 13.194 6.807 11.807 14.176C478.067 417.076 377.187 504 256 504c-66.448 0-126.791-26.136-171.315-68.685L48.97 471.03C33.851 486.149 8 475.441 8 454.059V320c0-13.255 10.745-24 24-24z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1014 B

View File

@ -1,206 +0,0 @@
{\rtf1\ansi\ansicpg936\deff0\nouicompat\deflang1033\deflangfe2052{\fonttbl{\f0\fnil\fcharset134 \'cb\'ce\'cc\'e5;}}
{\colortbl ;\red0\green0\blue255;}
{\*\generator Riched20 10.0.18362}\viewkind4\uc1
\pard\sl240\slmult1\f0\fs22\lang2052 Apache License\par
Version 2.0, January 2004\par
{{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/ }}{\fldrslt{http://www.apache.org/licenses/\ul0\cf0}}}}\f0\fs22\par
\par
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\par
\par
1. Definitions.\par
\par
"License" shall mean the terms and conditions for use, reproduction,\par
and distribution as defined by Sections 1 through 9 of this document.\par
\par
"Licensor" shall mean the copyright owner or entity authorized by\par
the copyright owner that is granting the License.\par
\par
"Legal Entity" shall mean the union of the acting entity and all\par
other entities that control, are controlled by, or are under common\par
control with that entity. For the purposes of this definition,\par
"control" means (i) the power, direct or indirect, to cause the\par
direction or management of such entity, whether by contract or\par
otherwise, or (ii) ownership of fifty percent (50%) or more of the\par
outstanding shares, or (iii) beneficial ownership of such entity.\par
\par
"You" (or "Your") shall mean an individual or Legal Entity\par
exercising permissions granted by this License.\par
\par
"Source" form shall mean the preferred form for making modifications,\par
including but not limited to software source code, documentation\par
source, and configuration files.\par
\par
"Object" form shall mean any form resulting from mechanical\par
transformation or translation of a Source form, including but\par
not limited to compiled object code, generated documentation,\par
and conversions to other media types.\par
\par
"Work" shall mean the work of authorship, whether in Source or\par
Object form, made available under the License, as indicated by a\par
copyright notice that is included in or attached to the work\par
(an example is provided in the Appendix below).\par
\par
"Derivative Works" shall mean any work, whether in Source or Object\par
form, that is based on (or derived from) the Work and for which the\par
editorial revisions, annotations, elaborations, or other modifications\par
represent, as a whole, an original work of authorship. For the purposes\par
of this License, Derivative Works shall not include works that remain\par
separable from, or merely link (or bind by name) to the interfaces of,\par
the Work and Derivative Works thereof.\par
\par
"Contribution" shall mean any work of authorship, including\par
the original version of the Work and any modifications or additions\par
to that Work or Derivative Works thereof, that is intentionally\par
submitted to Licensor for inclusion in the Work by the copyright owner\par
or by an individual or Legal Entity authorized to submit on behalf of\par
the copyright owner. For the purposes of this definition, "submitted"\par
means any form of electronic, verbal, or written communication sent\par
to the Licensor or its representatives, including but not limited to\par
communication on electronic mailing lists, source code control systems,\par
and issue tracking systems that are managed by, or on behalf of, the\par
Licensor for the purpose of discussing and improving the Work, but\par
excluding communication that is conspicuously marked or otherwise\par
designated in writing by the copyright owner as "Not a Contribution."\par
\par
"Contributor" shall mean Licensor and any individual or Legal Entity\par
on behalf of whom a Contribution has been received by Licensor and\par
subsequently incorporated within the Work.\par
\par
2. Grant of Copyright License. Subject to the terms and conditions of\par
this License, each Contributor hereby grants to You a perpetual,\par
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\par
copyright license to reproduce, prepare Derivative Works of,\par
publicly display, publicly perform, sublicense, and distribute the\par
Work and such Derivative Works in Source or Object form.\par
\par
3. Grant of Patent License. Subject to the terms and conditions of\par
this License, each Contributor hereby grants to You a perpetual,\par
worldwide, non-exclusive, no-charge, royalty-free, irrevocable\par
(except as stated in this section) patent license to make, have made,\par
use, offer to sell, sell, import, and otherwise transfer the Work,\par
where such license applies only to those patent claims licensable\par
by such Contributor that are necessarily infringed by their\par
Contribution(s) alone or by combination of their Contribution(s)\par
with the Work to which such Contribution(s) was submitted. If You\par
institute patent litigation against any entity (including a\par
cross-claim or counterclaim in a lawsuit) alleging that the Work\par
or a Contribution incorporated within the Work constitutes direct\par
or contributory patent infringement, then any patent licenses\par
granted to You under this License for that Work shall terminate\par
as of the date such litigation is filed.\par
\par
4. Redistribution. You may reproduce and distribute copies of the\par
Work or Derivative Works thereof in any medium, with or without\par
modifications, and in Source or Object form, provided that You\par
meet the following conditions:\par
\par
(a) You must give any other recipients of the Work or\par
Derivative Works a copy of this License; and\par
\par
(b) You must cause any modified files to carry prominent notices\par
stating that You changed the files; and\par
\par
(c) You must retain, in the Source form of any Derivative Works\par
that You distribute, all copyright, patent, trademark, and\par
attribution notices from the Source form of the Work,\par
excluding those notices that do not pertain to any part of\par
the Derivative Works; and\par
\par
(d) If the Work includes a "NOTICE" text file as part of its\par
distribution, then any Derivative Works that You distribute must\par
include a readable copy of the attribution notices contained\par
within such NOTICE file, excluding those notices that do not\par
pertain to any part of the Derivative Works, in at least one\par
of the following places: within a NOTICE text file distributed\par
as part of the Derivative Works; within the Source form or\par
documentation, if provided along with the Derivative Works; or,\par
within a display generated by the Derivative Works, if and\par
wherever such third-party notices normally appear. The contents\par
of the NOTICE file are for informational purposes only and\par
do not modify the License. You may add Your own attribution\par
notices within Derivative Works that You distribute, alongside\par
or as an addendum to the NOTICE text from the Work, provided\par
that such additional attribution notices cannot be construed\par
as modifying the License.\par
\par
You may add Your own copyright statement to Your modifications and\par
may provide additional or different license terms and conditions\par
for use, reproduction, or distribution of Your modifications, or\par
for any such Derivative Works as a whole, provided Your use,\par
reproduction, and distribution of the Work otherwise complies with\par
the conditions stated in this License.\par
\par
5. Submission of Contributions. Unless You explicitly state otherwise,\par
any Contribution intentionally submitted for inclusion in the Work\par
by You to the Licensor shall be under the terms and conditions of\par
this License, without any additional terms or conditions.\par
Notwithstanding the above, nothing herein shall supersede or modify\par
the terms of any separate license agreement you may have executed\par
with Licensor regarding such Contributions.\par
\par
6. Trademarks. This License does not grant permission to use the trade\par
names, trademarks, service marks, or product names of the Licensor,\par
except as required for reasonable and customary use in describing the\par
origin of the Work and reproducing the content of the NOTICE file.\par
\par
7. Disclaimer of Warranty. Unless required by applicable law or\par
agreed to in writing, Licensor provides the Work (and each\par
Contributor provides its Contributions) on an "AS IS" BASIS,\par
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\par
implied, including, without limitation, any warranties or conditions\par
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\par
PARTICULAR PURPOSE. You are solely responsible for determining the\par
appropriateness of using or redistributing the Work and assume any\par
risks associated with Your exercise of permissions under this License.\par
\par
8. Limitation of Liability. In no event and under no legal theory,\par
whether in tort (including negligence), contract, or otherwise,\par
unless required by applicable law (such as deliberate and grossly\par
negligent acts) or agreed to in writing, shall any Contributor be\par
liable to You for damages, including any direct, indirect, special,\par
incidental, or consequential damages of any character arising as a\par
result of this License or out of the use or inability to use the\par
Work (including but not limited to damages for loss of goodwill,\par
work stoppage, computer failure or malfunction, or any and all\par
other commercial damages or losses), even if such Contributor\par
has been advised of the possibility of such damages.\par
\par
9. Accepting Warranty or Additional Liability. While redistributing\par
the Work or Derivative Works thereof, You may choose to offer,\par
and charge a fee for, acceptance of support, warranty, indemnity,\par
or other liability obligations and/or rights consistent with this\par
License. However, in accepting such obligations, You may act only\par
on Your own behalf and on Your sole responsibility, not on behalf\par
of any other Contributor, and only if You agree to indemnify,\par
defend, and hold each Contributor harmless for any liability\par
incurred by, or claims asserted against, such Contributor by reason\par
of your accepting any such warranty or additional liability.\par
\par
END OF TERMS AND CONDITIONS\par
\par
APPENDIX: How to apply the Apache License to your work.\par
\par
To apply the Apache License to your work, attach the following\par
boilerplate notice, with the fields enclosed by brackets "\{\}"\par
replaced with your own identifying information. (Don't include\par
the brackets!) The text should be enclosed in the appropriate\par
comment syntax for the file format. We also recommend that a\par
file or class name and description of purpose be included on the\par
same "printed page" as the copyright notice for easier\par
identification within third-party archives.\par
\par
Copyright \{yyyy\} \{name of copyright owner\}\par
\par
Licensed under the Apache License, Version 2.0 (the "License");\par
you may not use this file except in compliance with the License.\par
You may obtain a copy of the License at\par
\par
{{\field{\*\fldinst{HYPERLINK http://www.apache.org/licenses/LICENSE-2.0 }}{\fldrslt{http://www.apache.org/licenses/LICENSE-2.0\ul0\cf0}}}}\f0\fs22\par
\par
Unless required by applicable law or agreed to in writing, software\par
distributed under the License is distributed on an "AS IS" BASIS,\par
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\par
See the License for the specific language governing permissions and\par
limitations under the License.\par
}

View File

@ -1,3 +0,0 @@
.vs
bin
obj

398
installer/actions/actions.c Normal file
View File

@ -0,0 +1,398 @@
#include <windows.h>
#include <msi.h>
#include <msidefs.h>
#include <msiquery.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#define LEGACY_SERVICE_PREFIX L"FRPC$"
#define SERVICE_PREFIX L"frpmgr_"
static void Log(MSIHANDLE installer, INSTALLMESSAGE messageType, const WCHAR* format, ...)
{
MSIHANDLE record = MsiCreateRecord(0);
if (!record)
return;
LPWSTR pBuffer = NULL;
va_list args = NULL;
va_start(args, format);
FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, format,
0, 0, (LPWSTR)&pBuffer, 0, &args);
va_end(args);
if (pBuffer)
{
MsiRecordSetStringW(record, 0, pBuffer);
MsiProcessMessage(installer, messageType, record);
LocalFree(pBuffer);
}
MsiCloseHandle(record);
}
static BOOL GetFileInformation(const LPWSTR path, BY_HANDLE_FILE_INFORMATION* fileInfo)
{
HANDLE file = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE)
return FALSE;
BOOL ret = GetFileInformationByHandle(file, fileInfo);
CloseHandle(file);
return ret;
}
static void KillProcessesEx(LPWSTR path, BOOL uiOnly)
{
HANDLE snapshot, process;
BY_HANDLE_FILE_INFORMATION fileInfo = { 0 }, procFileInfo = { 0 };
WCHAR procPath[MAX_PATH];
PROCESSENTRY32W entry;
entry.dwSize = sizeof(PROCESSENTRY32W);
LPWSTR filename = PathFindFileNameW(path);
if (!GetFileInformation(path, &fileInfo))
return;
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE)
return;
for (BOOL ret = Process32FirstW(snapshot, &entry); ret; ret = Process32NextW(snapshot, &entry))
{
if (_wcsicmp(entry.szExeFile, filename))
continue;
process = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
if (!process)
continue;
DWORD procPathLen = _countof(procPath);
DWORD sessionId = 0;
if (!QueryFullProcessImageNameW(process, 0, procPath, &procPathLen))
goto next;
if (!GetFileInformation(procPath, &procFileInfo))
goto next;
if (fileInfo.dwVolumeSerialNumber != procFileInfo.dwVolumeSerialNumber ||
fileInfo.nFileIndexHigh != procFileInfo.nFileIndexHigh ||
fileInfo.nFileIndexLow != procFileInfo.nFileIndexLow)
goto next;
if (!ProcessIdToSessionId(entry.th32ProcessID, &sessionId))
goto next;
if (uiOnly && sessionId == 0)
goto next;
if (TerminateProcess(process, 1))
WaitForSingleObject(process, INFINITE);
next:
CloseHandle(process);
}
CloseHandle(snapshot);
return;
}
__declspec(dllexport) UINT __stdcall KillFrpProcesses(MSIHANDLE installer)
{
WCHAR path[MAX_PATH];
DWORD pathLen = _countof(path);
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
return ERROR_SUCCESS;
}
if (path[0])
KillProcessesEx(path, FALSE);
return ERROR_SUCCESS;
}
__declspec(dllexport) UINT __stdcall KillFrpGUIProcesses(MSIHANDLE installer)
{
WCHAR path[MAX_PATH];
DWORD pathLen = _countof(path);
MSIHANDLE record = MsiCreateRecord(0);
if (!record)
return ERROR_SUCCESS;
MsiRecordSetStringW(record, 0, L"[#frpmgr.exe]");
UINT ret = MsiFormatRecordW(installer, record, path, &pathLen);
MsiCloseHandle(record);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load application path");
return ERROR_SUCCESS;
}
if (path[0])
KillProcessesEx(path, TRUE);
return ERROR_SUCCESS;
}
__declspec(dllexport) UINT __stdcall EvaluateFrpServices(MSIHANDLE installer)
{
SC_HANDLE scm = NULL;
LPENUM_SERVICE_STATUS_PROCESSW services = NULL;
DWORD SERVICE_STATUS_PROCESS_SIZE = 0x10000;
DWORD resume = 0;
LPQUERY_SERVICE_CONFIGW cfg = NULL;
DWORD cfgSize = 0;
MSIHANDLE db = 0, view = 0;
WCHAR path[MAX_PATH];
DWORD pathLen = _countof(path);
BY_HANDLE_FILE_INFORMATION fileInfo = { 0 }, svcFileInfo = { 0 };
BOOL fileInfoExists = FALSE;
MSIHANDLE record = MsiCreateRecord(0);
if (!record)
goto out;
MsiRecordSetStringW(record, 0, L"[#frpmgr.exe]");
UINT ret = MsiFormatRecordW(installer, record, path, &pathLen);
MsiCloseHandle(record);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load application path");
goto out;
}
if (!path[0])
goto out;
fileInfoExists = GetFileInformation(path, &fileInfo);
db = MsiGetActiveDatabase(installer);
if (!db)
{
Log(installer, INSTALLMESSAGE_ERROR, L"MsiGetActiveDatabase failed");
goto out;
}
ret = MsiDatabaseOpenViewW(db,
L"INSERT INTO `ServiceControl` (`ServiceControl`, `Name`, `Event`, `Component_`, `Wait`) VALUES(?, ?, ?, ?, ?) TEMPORARY",
&view);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"MsiDatabaseOpenView failed (%1)", ret);
goto out;
}
scm = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASEW, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!scm)
{
Log(installer, INSTALLMESSAGE_ERROR, L"OpenSCManager failed (%1)", GetLastError());
goto out;
}
services = (LPENUM_SERVICE_STATUS_PROCESSW)LocalAlloc(LMEM_FIXED, SERVICE_STATUS_PROCESS_SIZE);
if (!services)
{
Log(installer, INSTALLMESSAGE_ERROR, L"LocalAlloc failed (%1)", GetLastError());
goto out;
}
for (BOOL more = TRUE; more;)
{
DWORD bytesNeeded = 0, count = 0;
if (EnumServicesStatusExW(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, (LPBYTE)services,
SERVICE_STATUS_PROCESS_SIZE, &bytesNeeded, &count, &resume, NULL))
more = FALSE;
else
{
ret = GetLastError();
if (ret != ERROR_MORE_DATA)
{
Log(installer, INSTALLMESSAGE_ERROR, L"EnumServicesStatusEx failed (%1)", ret);
break;
}
}
for (DWORD i = 0; i < count; ++i)
{
INT legacy;
if ((legacy = _wcsnicmp(services[i].lpServiceName, LEGACY_SERVICE_PREFIX, _countof(LEGACY_SERVICE_PREFIX) - 1)) &&
_wcsnicmp(services[i].lpServiceName, SERVICE_PREFIX, _countof(SERVICE_PREFIX) - 1))
continue;
SC_HANDLE service = OpenServiceW(scm, services[i].lpServiceName, SERVICE_QUERY_CONFIG);
if (!service)
continue;
BOOL ok = FALSE;
while (!(ok = QueryServiceConfigW(service, cfg, cfgSize, &bytesNeeded)) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
if (cfg)
LocalFree(cfg);
else
bytesNeeded += sizeof(path) + 256 * sizeof(WCHAR); // Additional size for path and display name.
cfgSize = bytesNeeded;
cfg = (LPQUERY_SERVICE_CONFIGW)LocalAlloc(LMEM_FIXED, cfgSize);
if (!cfg)
{
Log(installer, INSTALLMESSAGE_ERROR, L"LocalAlloc failed (%1)", GetLastError());
break;
}
}
CloseServiceHandle(service);
if (!ok || cfg == NULL)
continue;
INT nArgs = 0;
LPWSTR* args = CommandLineToArgvW(cfg->lpBinaryPathName, &nArgs);
if (!args)
continue;
ok = nArgs >= 1 && (fileInfoExists ?
(GetFileInformation(args[0], &svcFileInfo) &&
fileInfo.dwVolumeSerialNumber == svcFileInfo.dwVolumeSerialNumber &&
fileInfo.nFileIndexHigh == svcFileInfo.nFileIndexHigh &&
fileInfo.nFileIndexLow == svcFileInfo.nFileIndexLow) : _wcsicmp(args[0], path) == 0);
LocalFree(args);
if (!ok)
continue;
Log(installer, INSTALLMESSAGE_INFO, L"Scheduling stop on upgrade or removal on uninstall of service %1", services[i].lpServiceName);
GUID guid;
if (FAILED(CoCreateGuid(&guid)))
continue;
WCHAR identifier[40];
if (StringFromGUID2(&guid, identifier, _countof(identifier)) == 0)
continue;
record = MsiCreateRecord(5);
if (!record)
continue;
BOOL start = services[i].ServiceStatusProcess.dwWin32ExitCode == ERROR_FAIL_NOACTION_REBOOT;
MsiRecordSetStringW(record, 1, identifier);
MsiRecordSetStringW(record, 2, services[i].lpServiceName);
MsiRecordSetInteger(record, 3, (start ? msidbServiceControlEventStart : msidbServiceControlEventStop) | msidbServiceControlEventUninstallStop | (legacy == 0 ? msidbServiceControlEventDelete : 0) | msidbServiceControlEventUninstallDelete);
MsiRecordSetStringW(record, 4, L"frpmgr.exe");
MsiRecordSetInteger(record, 5, 1);
ret = MsiViewExecute(view, record);
MsiCloseHandle(record);
if (ret != ERROR_SUCCESS)
Log(installer, INSTALLMESSAGE_ERROR, L"MsiViewExecute failed for service %1 (%2)", services[i].lpServiceName, ret);
}
}
LocalFree(services);
if (cfg)
LocalFree(cfg);
out:
if (scm)
CloseServiceHandle(scm);
if (view)
MsiCloseHandle(view);
if (db)
MsiCloseHandle(db);
return ERROR_SUCCESS;
}
__declspec(dllexport) UINT __stdcall SetLangConfig(MSIHANDLE installer)
{
WCHAR path[MAX_PATH];
DWORD pathLen = _countof(path);
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
goto out;
}
if (!path[0] || !PathAppendW(path, L"lang.config"))
goto out;
WCHAR localeName[LOCALE_NAME_MAX_LENGTH];
if (LCIDToLocaleName(MsiGetLanguage(installer), localeName, _countof(localeName), 0) == 0)
goto out;
HANDLE langFile = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (langFile == INVALID_HANDLE_VALUE)
goto out;
CHAR buf[LOCALE_NAME_MAX_LENGTH];
DWORD bytesWritten = WideCharToMultiByte(CP_UTF8, 0, localeName, -1, buf, sizeof(buf), NULL, NULL);
if (bytesWritten > 0)
WriteFile(langFile, buf, bytesWritten - 1, &bytesWritten, NULL);
CloseHandle(langFile);
out:
return ERROR_SUCCESS;
}
__declspec(dllexport) UINT __stdcall MoveFrpProfiles(MSIHANDLE installer)
{
WIN32_FIND_DATAW findData;
const WCHAR* dirs[] = { NULL, L"profiles" };
WCHAR path[MAX_PATH], newPath[MAX_PATH];
DWORD pathLen = _countof(path), newPathLen = _countof(newPath);
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
goto out;
}
if (!path[0] || !PathCombineW(newPath, path, L"profiles"))
goto out;
if (CreateDirectoryW(newPath, NULL) == 0 && GetLastError() != ERROR_ALREADY_EXISTS)
goto out;
newPathLen = wcsnlen_s(newPath, _countof(newPath) - 1);
for (size_t i = 0; i < _countof(dirs); i++)
{
path[pathLen] = L'\0';
if (dirs[i] && !PathAppendW(path, dirs[i]))
continue;
pathLen = wcsnlen_s(path, _countof(path) - 1);
if (!PathAppendW(path, L"*.ini"))
continue;
HANDLE hFind = FindFirstFileExW(path, FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, 0);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY || !PathMatchSpecW(findData.cFileName, L"*.ini"))
continue;
path[pathLen] = L'\0';
newPath[newPathLen] = L'\0';
if (!PathAppendW(path, findData.cFileName) ||
!PathRenameExtensionW(findData.cFileName, L".conf") ||
!PathAppendW(newPath, findData.cFileName))
continue;
MoveFileW(path, newPath);
} while (FindNextFileW(hFind, &findData));
FindClose(hFind);
}
}
out:
return ERROR_SUCCESS;
}
__declspec(dllexport) UINT __stdcall RemoveFrpFiles(MSIHANDLE installer)
{
WCHAR path[MAX_PATH];
DWORD pathLen = _countof(path);
UINT ret = MsiGetPropertyW(installer, L"CustomActionData", path, &pathLen);
if (ret != ERROR_SUCCESS)
{
Log(installer, INSTALLMESSAGE_ERROR, L"Failed to load CustomActionData");
return ERROR_SUCCESS;
}
const WCHAR* appFiles[] = { L"app.json", L"lang.config" };
for (size_t i = 0; i < _countof(appFiles); i++)
{
path[pathLen] = L'\0';
if (!PathAppendW(path, appFiles[i]))
return ERROR_SUCCESS;
DeleteFileW(path);
}
WIN32_FIND_DATAW findData;
const WCHAR* files[][2] = { {L"profiles", L"*.conf"}, {L"logs", L"*.log"} };
for (size_t i = 0; i < _countof(files); i++)
{
path[pathLen] = L'\0';
if (!PathAppendW(path, files[i][0]))
continue;
SIZE_T dirLen = wcsnlen_s(path, _countof(path) - 1);
if (!PathAppendW(path, files[i][1]))
continue;
HANDLE hFind = FindFirstFileExW(path, FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, 0);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY || !PathMatchSpecW(findData.cFileName, files[i][1]))
continue;
path[dirLen] = L'\0';
if (!PathAppendW(path, findData.cFileName))
continue;
DeleteFileW(path);
} while (FindNextFileW(hFind, &findData));
FindClose(hFind);
}
path[dirLen] = L'\0';
RemoveDirectoryW(path);
}
return ERROR_SUCCESS;
}

View File

@ -1,31 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "actions", "actions\actions.csproj", "{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x64.ActiveCfg = Debug|x64
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x64.Build.0 = Debug|x64
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x86.ActiveCfg = Debug|x86
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Debug|x86.Build.0 = Debug|x86
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x64.ActiveCfg = Release|x64
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x64.Build.0 = Release|x64
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x86.ActiveCfg = Release|x86
{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9B84C910-F22D-4D3F-9BD6-6C2134E26EE8}
EndGlobalSection
EndGlobal

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<!--
Use supportedRuntime tags to explicitly specify the version(s) of the .NET Framework runtime that
the custom action should run on. If no versions are specified, the chosen version of the runtime
will be the "best" match to what Microsoft.Deployment.WindowsInstaller.dll was built against.
WARNING: leaving the version unspecified is dangerous as it introduces a risk of compatibility
problems with future versions of the .NET Framework runtime. It is highly recommended that you specify
only the version(s) of the .NET Framework runtime that you have tested against.
Note for .NET Framework v3.0 and v3.5, the runtime version is still v2.0.
In order to enable .NET Framework version 2.0 runtime activation policy, which is to load all assemblies
by using the latest supported runtime, @useLegacyV2RuntimeActivationPolicy="true".
For more information, see http://msdn.microsoft.com/en-us/library/bbx34a2h.aspx
-->
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727"/>
</startup>
<!--
Add additional configuration settings here. For more information on application config files,
see http://msdn.microsoft.com/en-us/library/kza1yk3a.aspx
-->
</configuration>

View File

@ -1,199 +0,0 @@
using Microsoft.Deployment.WindowsInstaller;
using System;
using System.Collections.Generic;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
namespace actions
{
public class CustomActions
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int MessageBox(int hWnd, String text, String caption, uint type);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSECURITY_ATTRIBUTES, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool GetFileInformationByHandle(IntPtr handle, ref BY_HANDLE_FILE_INFORMATION hfi);
[StructLayout(LayoutKind.Sequential)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint dwVolumeSerialNumber;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint nNumberOfLinks;
public uint nFileIndexHigh;
public uint nFileIndexLow;
};
public const int OPEN_EXISTING = 3;
public const int INVALID_HANDLE_VALUE = -1;
public const int FILE_ATTRIBUTE_NORMAL = 0x80;
public const int MB_OK = 0;
public const int MB_YESNO = 0x4;
public const int MB_RETRYCANCEL = 0x5;
public const int MB_ICONQUESTION = 0x20;
public const int MB_ICONWARNING = 0x30;
public const int IDYES = 6;
public const int IDRETRY = 4;
public static bool CalculateFileId(string path, out BY_HANDLE_FILE_INFORMATION hfi)
{
hfi = new BY_HANDLE_FILE_INFORMATION { };
IntPtr file = CreateFile(path, 0, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
if (file.ToInt32() == INVALID_HANDLE_VALUE)
return false;
bool ret = GetFileInformationByHandle(file, ref hfi);
CloseHandle(file);
if (!ret)
return false;
return true;
}
public static bool CompareFile(BY_HANDLE_FILE_INFORMATION f1, BY_HANDLE_FILE_INFORMATION f2)
{
return f1.dwVolumeSerialNumber == f2.dwVolumeSerialNumber && f1.nFileIndexHigh == f2.nFileIndexHigh && f1.nFileIndexLow == f2.nFileIndexLow;
}
[CustomAction]
public static ActionResult KillProcesses(Session session)
{
session.Log("Killing FRP processes");
string binPath = session["CustomActionData"];
if (string.IsNullOrEmpty(binPath))
{
return ActionResult.Failure;
}
if (!CalculateFileId(binPath, out BY_HANDLE_FILE_INFORMATION binInfo))
{
return ActionResult.Failure;
}
Process[] processes = Process.GetProcesses();
foreach (Process p in processes)
{
if (p.ProcessName != "frpmgr")
continue;
try
{
if (!CalculateFileId(p.MainModule.FileName, out BY_HANDLE_FILE_INFORMATION info))
continue;
if (CompareFile(binInfo, info))
{
p.Kill();
p.WaitForExit();
}
} catch (Exception)
{
continue;
}
}
return ActionResult.Success;
}
[CustomAction]
public static ActionResult RemoveFrpFiles(Session session)
{
session.Log("Removing files");
string installPath = session["CustomActionData"];
if (string.IsNullOrEmpty(installPath))
{
return ActionResult.Failure;
}
int result = MessageBox(FindWindow(null, "FRP").ToInt32(), "<22>Ƿ<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>?\n\nע<6E><EFBFBD><E2A3BA>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>´ΰ<C2B4>װʱ<D7B0><CAB1><EFBFBD>밲װ<EBB0B2><D7B0><EFBFBD><EFBFBD>Ŀ¼<C4BF><C2BC>\n\n" + installPath, <><D0B6><EFBFBD><EFBFBD>ʾ", MB_YESNO | MB_ICONQUESTION);
if (result == IDYES)
{
foreach (string file in Directory.GetFiles(installPath))
{
if (Path.GetExtension(file) == ".ini")
{
DEL_CONF:
try
{
File.Delete(file);
} catch (Exception)
{
if (MessageBox(FindWindow(null, "FRP").ToInt32(), "<22>޷<EFBFBD>ɾ<EFBFBD><C9BE><EFBFBD>ļ<EFBFBD> " + file, "<22><><EFBFBD><EFBFBD>", MB_RETRYCANCEL | MB_ICONWARNING) == IDRETRY)
goto DEL_CONF;
}
}
}
string logPath = Path.Combine(installPath, "logs");
DEL_LOG:
try
{
if (Directory.Exists(logPath))
{
Directory.Delete(logPath, true);
}
} catch (Exception)
{
if (MessageBox(FindWindow(null, "FRP").ToInt32(), "<22>޷<EFBFBD>ɾ<EFBFBD><C9BE>Ŀ¼ " + logPath, "<22><><EFBFBD><EFBFBD>", MB_RETRYCANCEL | MB_ICONWARNING) == IDRETRY)
goto DEL_LOG;
}
}
return ActionResult.Success;
}
[CustomAction]
public static ActionResult EvaluateFrpServices(Session session)
{
session.Log("Evaluate FRP Services");
string binPath = session["CustomActionData"];
if (string.IsNullOrEmpty(binPath))
{
return ActionResult.Failure;
}
ServiceController[] services = ServiceController.GetServices();
foreach (ServiceController controller in services)
{
ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + controller.ServiceName + "'");
wmiService.Get();
string pathName = wmiService.GetPropertyValue("PathName").ToString();
string path1 = pathName.Substring(0, Math.Min(binPath.Length, pathName.Length));
string path2 = pathName.Substring(1, Math.Min(binPath.Length, pathName.Length - 1));
if (binPath.ToLower().Equals(path1.ToLower()) || binPath.ToLower().Equals(path2.ToLower()))
{
try
{
controller.Stop();
controller.WaitForStatus(ServiceControllerStatus.Stopped);
}
catch (Exception) { }
ServiceInstaller installer = new ServiceInstaller
{
Context = new InstallContext(),
ServiceName = controller.ServiceName
};
try
{
installer.Uninstall(null);
} catch (Exception)
{
session.Log("Failed to uninstall " + controller.ServiceName);
}
}
}
return ActionResult.Success;
}
}
}

View File

@ -1,35 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("actions")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("actions")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("dc9743da-8782-4a7c-8b46-b2d4eea19d4e")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{DC9743DA-8782-4A7C-8B46-B2D4EEA19D4E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>actions</RootNamespace>
<AssemblyName>actions</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.Deployment.WindowsInstaller">
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="CustomAction.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Content Include="CustomAction.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(WixCATargetsPath)" Condition=" '$(WixCATargetsPath)' != '' " />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.CA.targets" Condition=" '$(WixCATargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.CA.targets') " />
<Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixCATargetsImported)' != 'true' ">
<Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
</Target>
</Project>

View File

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@ -0,0 +1,38 @@
#include <windows.h>
#pragma code_page(65001) // UTF-8
#define STRINGIZE(x) #x
#define EXPAND(x) STRINGIZE(x)
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_ARRAY
PRODUCTVERSION VERSION_ARRAY
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0x0
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "FRP Manager Project"
VALUE "FileDescription", "FRP Manager Setup Custom Actions"
VALUE "FileVersion", EXPAND(VERSION_STR)
VALUE "InternalName", "frpmgr-actions"
VALUE "LegalCopyright", "Copyright © FRP Manager Project"
VALUE "OriginalFilename", "actions.dll"
VALUE "ProductName", "FRP Manager"
VALUE "ProductVersion", EXPAND(VERSION_STR)
VALUE "Comments", "https://github.com/koho/frpmgr"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
ISOLATIONAWARE_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml

View File

@ -1,16 +1,99 @@
@echo off
set FRPMGR_VERSION=%1
setlocal enabledelayedexpansion
set VERSION=%~1
set ARCH=%~2
set STEP="%~3"
set BUILDDIR=%~dp0
cd /d %BUILDDIR% || exit /b 1
if "%WIX%"=="" (
echo ERROR: WIX was not found.
exit /b 1
set TARGET_x64=x86_64
set TARGET_x86=i686
set TARGET_arm64=aarch64
if "%VERSION%" == "" (
echo ERROR: no version provided.
exit /b 1
)
if not exist build md build
call VsMSBuildCmd.bat
msbuild actions/actions.sln /t:Rebuild /p:Configuration=Release /p:Platform="x64" || exit /b 1
copy actions\actions\bin\x64\Release\actions.CA.dll build\actions.dll /y || exit /b 1
set WIX_CANDLE_FLAGS=-nologo -dFRPMGR_VERSION=%FRPMGR_VERSION%
set WIX_LIGHT_FLAGS=-nologo -spdb -ext "%WIX%bin\\WixUtilExtension.dll" -ext "%WIX%bin\\WixUIExtension.dll" -cultures:zh-CN
"%WIX%bin\candle" %WIX_CANDLE_FLAGS% -out build\ -arch x64 frpmgr.wxs
"%WIX%bin\light" %WIX_LIGHT_FLAGS% -out "..\\bin\\frpmgr-%FRPMGR_VERSION%.msi" "build\\frpmgr.wixobj"
if "%ARCH%" == "" (
echo ERROR: no architecture provided.
exit /b 1
)
if not defined TARGET_%ARCH% (
echo ERROR: unsupported architecture.
exit /b 1
)
:build
if not exist build md build
set PLAT_DIR=build\%ARCH%
set SETUP_FILENAME=frpmgr-%VERSION%-setup-%ARCH%.exe
if %STEP% == "dist" goto :dist
set CC=!TARGET_%ARCH%!-w64-mingw32-gcc
set WINDRES=!TARGET_%ARCH%!-w64-mingw32-windres
if not exist %PLAT_DIR% md %PLAT_DIR%
set MSI_FILE=%PLAT_DIR%\frpmgr.msi
if %STEP:"actions"=""% == "" call :build_actions || goto :error
if %STEP:"msi"=""% == "" call :build_msi || goto :error
if %STEP:"setup"=""% == "" call :build_setup || goto :error
if %STEP% == "" goto :dist
:success
exit /b 0
:build_actions
%WINDRES% -DVERSION_ARRAY=%VERSION:.=,% -DVERSION_STR=%VERSION% -o %PLAT_DIR%\actions.res.obj -i actions\version.rc -O coff -c 65001 || exit /b 1
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -municode -DUNICODE -D_UNICODE -DNDEBUG
set LDFLAGS=-shared -s -Wl,--kill-at -Wl,--major-os-version=6 -Wl,--minor-os-version=2 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=2 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols
set LDLIBS=-lmsi -lole32 -lshlwapi -lshell32 -ladvapi32
%CC% %CFLAGS% %LDFLAGS% -o %PLAT_DIR%\actions.dll actions\actions.c %PLAT_DIR%\actions.res.obj %LDLIBS% || exit /b 1
goto :eof
:build_msi
if not defined WIX (
echo ERROR: WIX was not found.
exit /b 1
)
set WIX_CANDLE_FLAGS=-dVERSION=%VERSION%
set WIX_LIGHT_FLAGS=-ext "%WIX%bin\WixUtilExtension.dll" -ext "%WIX%bin\WixUIExtension.dll" -sval
set WIX_OBJ=%PLAT_DIR%\frpmgr.wixobj
"%WIX%bin\candle" %WIX_CANDLE_FLAGS% -out %WIX_OBJ% -arch %ARCH% msi\frpmgr.wxs || exit /b 1
"%WIX%bin\light" %WIX_LIGHT_FLAGS% -cultures:en-US -loc msi\en-US.wxl -out %MSI_FILE% %WIX_OBJ% || exit /b 1
for %%l in (zh-CN zh-TW ja-JP ko-KR es-ES) do (
set WIX_LANG_MSI=%MSI_FILE:~0,-4%_%%l.msi
"%WIX%bin\light" %WIX_LIGHT_FLAGS% -cultures:%%l -loc msi\%%l.wxl -out !WIX_LANG_MSI! %WIX_OBJ% || exit /b 1
for /f "tokens=3 delims=><" %%a in ('findstr /r "Id.*=.*Language" msi\%%l.wxl') do set LANG_CODE=%%a
"%WindowsSdkVerBinPath%x86\MsiTran" -g %MSI_FILE% !WIX_LANG_MSI! %PLAT_DIR%\!LANG_CODE! || exit /b 1
"%WindowsSdkVerBinPath%x86\MsiDb" -d %MSI_FILE% -r %PLAT_DIR%\!LANG_CODE! || exit /b 1
)
goto :eof
:build_setup
%WINDRES% -DFILENAME=%SETUP_FILENAME% -DVERSION_ARRAY=%VERSION:.=,% -DVERSION_STR=%VERSION% -DMSI_FILE=%MSI_FILE:\=\\% -o %PLAT_DIR%\setup.res.obj -i setup\resource.rc -O coff -c 65001 || exit /b 1
set ARCH_LINE=-1
for /f "tokens=1 delims=:" %%a in ('findstr /n /r ".*=.*\"%ARCH%\"" msi\frpmgr.wxs') do set ARCH_LINE=%%a
if %ARCH_LINE% lss 0 (
echo ERROR: unsupported architecture.
exit /b 1
)
for /f "tokens=1,5 delims=: " %%a in ('findstr /n /r "UpgradeCode.*=.*\"[0-9a-fA-F-]*\"" msi\frpmgr.wxs') do (
if %%a gtr %ARCH_LINE% if not defined UPGRADE_CODE set UPGRADE_CODE=%%b
)
if not defined UPGRADE_CODE (
echo ERROR: UpgradeCode was not found.
exit /b 1
)
set CFLAGS=-O3 -Wall -std=gnu11 -DWINVER=0x0602 -D_WIN32_WINNT=0x0602 -municode -DUNICODE -D_UNICODE -DNDEBUG -DUPGRADE_CODE=L\"{%UPGRADE_CODE%}\" -DVERSION=L\"%VERSION%\"
set LDFLAGS=-s -Wl,--major-os-version=6 -Wl,--minor-os-version=2 -Wl,--major-subsystem-version=6 -Wl,--minor-subsystem-version=2 -Wl,--tsaware -Wl,--dynamicbase -Wl,--nxcompat -mwindows
set LDLIBS=-lmsi -lole32 -lshlwapi -ladvapi32 -luser32 -lcomctl32
%CC% %CFLAGS% %LDFLAGS% -o %PLAT_DIR%\setup.exe setup\setup.c %PLAT_DIR%\setup.res.obj %LDLIBS% || exit /b 1
goto :eof
:dist
echo [+] Creating %ARCH% archives
tar -ac -C ..\bin\%ARCH% -f ..\bin\frpmgr-%VERSION%-%ARCH%.zip frpmgr.exe || goto :error
echo [+] Creating %ARCH% installer
copy %PLAT_DIR%\setup.exe ..\bin\%SETUP_FILENAME% /y
:error
exit /b %errorlevel%

View File

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="FRP 管理器" Language="2052" Version="$(var.FRPMGR_VERSION)" Manufacturer="FRP" UpgradeCode="c9f7c2b3-291a-454a-9871-150d98dc2645" Codepage="936">
<Package InstallerVersion="400" Compressed="yes" InstallScope="perMachine" Platform="x64" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes"/>
<Binary Id="actions.dll" SourceFile=".\\build\\actions.dll" />
<Feature Id="ProductFeature" Title="FRP" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<Property Id="DISABLEADVTSHORTCUTS" Value="yes" />
<Property Id="LicenseAccepted" Value="1" />
<UI>
<UIRef Id="WixUI_InstallDir" />
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">NOT Installed</Publish>
<ProgressText Action="KillProcesses">正在结束进程</ProgressText>
<ProgressText Action="EvaluateFrpServices">正在停止服务</ProgressText>
<ProgressText Action="RemoveFrpFiles">正在删除文件</ProgressText>
</UI>
<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
<Icon Id="ProductIcon" SourceFile="..\\icon\\app.ico" />
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLFOLDER" Name="FRP" />
</Directory>
<Directory Id="ProgramMenuFolder">
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Guid="{E39EABEF-A7EB-4EAF-AD3E-A1254450BBE1}" Id="MainApplication" Win64="yes">
<File Id="MainApplication" Source="..\\bin\\frpmgr.exe" KeyPath="yes">
<Shortcut Id="StartMenuShortcut" Name="FRP 管理器" Directory="ProgramMenuFolder" WorkingDirectory="INSTALLFOLDER" Advertise="yes"/>
</File>
<ServiceControl Id="DummyService.E3F2D6BE_38C7_4654_9C1B_C667A1F9040A" Name="DummyService.E3F2D6BE_38C7_4654_9C1B_C667A1F9040A" />
</Component>
</ComponentGroup>
</Fragment>
<Fragment>
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
<CustomAction Id="KillProcesses.SetProperty" Return="check" Property="KillProcesses" Value="[#MainApplication]" />
<CustomAction Id="EvaluateFrpServices.SetProperty" Return="check" Property="EvaluateFrpServices" Value="[#MainApplication]" />
<CustomAction Id="RemoveFrpFiles.SetProperty" Return="check" Property="RemoveFrpFiles" Value="[INSTALLFOLDER]" />
<Property Id="WixShellExecTarget" Value="[#MainApplication]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<CustomAction Id="EvaluateFrpServices" BinaryKey="actions.dll" DllEntry="EvaluateFrpServices" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="EvaluateFrpServices.SetProperty" After="InstallInitialize" />
<Custom Action="EvaluateFrpServices" After="EvaluateFrpServices.SetProperty">REMOVE="ALL"</Custom>
</InstallExecuteSequence>
<CustomAction Id="KillProcesses" BinaryKey="actions.dll" DllEntry="KillProcesses" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="KillProcesses.SetProperty" After="StopServices" />
<Custom Action="KillProcesses" After="KillProcesses.SetProperty">REMOVE="ALL"</Custom>
</InstallExecuteSequence>
<CustomAction Id="RemoveFrpFiles" BinaryKey="actions.dll" DllEntry="RemoveFrpFiles" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="RemoveFrpFiles.SetProperty" After="DeleteServices" />
<Custom Action="RemoveFrpFiles" After="RemoveFrpFiles.SetProperty">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
</Fragment>
</Wix>

6
installer/msi/en-US.wxl Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-US" Codepage="1252" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Language">1033</String>
<String Id="ApplicationName">FRP Manager</String>
<String Id="BlockMessage">This application is only supported on Windows 10, Windows Server 2016, or higher.</String>
</WixLocalization>

6
installer/msi/es-ES.wxl Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="es-ES" Codepage="1252" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Language">3082</String>
<String Id="ApplicationName">Administrador de FRP</String>
<String Id="BlockMessage">Esta aplicación solo es compatible con Windows 10, Windows Server 2016 o superior.</String>
</WixLocalization>

167
installer/msi/frpmgr.wxs Normal file
View File

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<?if $(sys.BUILDARCH) = "x64" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?define UpgradeCode = "C9F7C2B3-291A-454A-9871-150D98DC2645" ?>
<?elseif $(sys.BUILDARCH) = "x86" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?define UpgradeCode = "46E3AA36-10BB-4CD9-92E3-5F990AB5FC88" ?>
<?elseif $(sys.BUILDARCH) = "arm64" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?define UpgradeCode = "3C3590A6-8139-482D-81F5-2FE6D21687A6" ?>
<?else?>
<?error Unknown platform ?>
<?endif?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="!(loc.ApplicationName)" Language="!(loc.Language)" Version="$(var.VERSION)" Manufacturer="FRP Manager Project" UpgradeCode="$(var.UpgradeCode)">
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Languages="1033,1041,1042,2052,1028,3082" Description="!(loc.ApplicationName)" ReadOnly="yes" />
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<Condition Message="!(loc.BlockMessage)">
<![CDATA[Installed OR (VersionNT >= 603)]]>
</Condition>
<!--
Upgrading
-->
<MajorUpgrade AllowDowngrades="yes" />
<Icon Id="app.ico" SourceFile="..\icon\app.ico" />
<Binary Id="actions.dll" SourceFile="build\$(sys.BUILDARCH)\actions.dll" />
<Property Id="ARPPRODUCTICON" Value="app.ico" />
<Property Id="ARPURLINFOABOUT" Value="https://github.com/koho/frpmgr" />
<Property Id="ARPNOREPAIR" Value="yes" />
<Property Id="DISABLEADVTSHORTCUTS" Value="yes" />
<Property Id="REINSTALLMODE" Value="amus" />
<!--
Detect previous install folder if it's a upgrade
-->
<SetProperty Id="INSTALLFOLDER" Value="[PREVINSTALLFOLDER]" After="AppSearch" Sequence="first">
PREVINSTALLFOLDER
</SetProperty>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<Property Id="LicenseAccepted" Value="1" />
<Feature Id="CoreFeature" Title="!(loc.ApplicationName)" Level="1">
<ComponentGroupRef Id="CoreComponents" />
</Feature>
<UI>
<UIRef Id="WixUI_InstallDir" />
<UIRef Id="WixUI_ErrorProgressText" />
<!-- Skip license dialog -->
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">NOT WIX_UPGRADE_DETECTED</Publish>
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">NOT WIX_UPGRADE_DETECTED</Publish>
<!-- Skip directory selection on upgrade -->
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="2">WIX_UPGRADE_DETECTED</Publish>
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">WIX_UPGRADE_DETECTED</Publish>
<!-- Launch application after installation -->
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">NOT Installed</Publish>
</UI>
</Product>
<!--
Folders
-->
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLFOLDER" Name="FRP" />
</Directory>
<Directory Id="ProgramMenuFolder" />
</Directory>
</Fragment>
<!--
Components
-->
<Fragment>
<ComponentGroup Id="CoreComponents" Directory="INSTALLFOLDER">
<Component Id="frpmgr.exe">
<File Id="frpmgr.exe" Source="..\bin\$(sys.BUILDARCH)\frpmgr.exe" KeyPath="yes">
<Shortcut Id="StartMenuShortcut" Name="!(loc.ApplicationName)" Directory="ProgramMenuFolder" WorkingDirectory="INSTALLFOLDER" Advertise="yes"/>
</File>
<!-- A dummy to make WiX create ServiceControl table for us. -->
<ServiceControl Id="DummyService.E3F2D6BE_38C7_4654_9C1B_C667A1F9040A" Name="DummyService.E3F2D6BE_38C7_4654_9C1B_C667A1F9040A" />
</Component>
</ComponentGroup>
</Fragment>
<!--
Actions
-->
<Fragment>
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
<CustomAction Id="KillFrpProcesses.SetProperty" Return="check" Property="KillFrpProcesses" Value="[#frpmgr.exe]" />
<CustomAction Id="RemoveFrpFiles.SetProperty" Return="check" Property="RemoveFrpFiles" Value="[INSTALLFOLDER]" />
<CustomAction Id="SetLangConfig.SetProperty" Return="check" Property="SetLangConfig" Value="[INSTALLFOLDER]" />
<CustomAction Id="MoveFrpProfiles.SetProperty" Return="check" Property="MoveFrpProfiles" Value="[INSTALLFOLDER]" />
<!--
Launch application
-->
<Property Id="WixShellExecTarget" Value="[#frpmgr.exe]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<!--
Close GUI windows
-->
<CustomAction Id="KillFrpGUIProcesses" BinaryKey="actions.dll" DllEntry="KillFrpGUIProcesses" Impersonate="yes" Execute="immediate" />
<InstallExecuteSequence>
<Custom Action="KillFrpGUIProcesses" Before="InstallValidate">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
<!--
Evaluate FRP services
-->
<CustomAction Id="EvaluateFrpServices" BinaryKey="actions.dll" DllEntry="EvaluateFrpServices" />
<InstallExecuteSequence>
<Custom Action="EvaluateFrpServices" After="InstallInitialize">NOT ((UPGRADINGPRODUCTCODE OR SAVESTATE) AND (REMOVE="ALL"))</Custom>
</InstallExecuteSequence>
<!--
Kill lingering processes
-->
<CustomAction Id="KillFrpProcesses" BinaryKey="actions.dll" DllEntry="KillFrpProcesses" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="KillFrpProcesses.SetProperty" After="StopServices" />
<Custom Action="KillFrpProcesses" After="KillFrpProcesses.SetProperty">REMOVE="ALL"</Custom>
</InstallExecuteSequence>
<!--
Delete files generated by FRP
-->
<CustomAction Id="RemoveFrpFiles" BinaryKey="actions.dll" DllEntry="RemoveFrpFiles" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="RemoveFrpFiles.SetProperty" After="DeleteServices" />
<Custom Action="RemoveFrpFiles" After="RemoveFrpFiles.SetProperty">(NOT UPGRADINGPRODUCTCODE) AND (NOT SAVESTATE) AND (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
<!--
Set language of the product
-->
<CustomAction Id="SetLangConfig" BinaryKey="actions.dll" DllEntry="SetLangConfig" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="SetLangConfig.SetProperty" After="InstallFiles" />
<Custom Action="SetLangConfig" After="SetLangConfig.SetProperty">NOT (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
<!--
Move old profiles to the new folder
-->
<CustomAction Id="MoveFrpProfiles" BinaryKey="actions.dll" DllEntry="MoveFrpProfiles" Impersonate="no" Execute="deferred" />
<InstallExecuteSequence>
<Custom Action="MoveFrpProfiles.SetProperty" After="InstallFiles" />
<Custom Action="MoveFrpProfiles" After="MoveFrpProfiles.SetProperty">NOT (REMOVE="ALL")</Custom>
</InstallExecuteSequence>
</Fragment>
</Wix>

6
installer/msi/ja-JP.wxl Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="ja-JP" Codepage="932" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Language">1041</String>
<String Id="ApplicationName">FRP マネージャ</String>
<String Id="BlockMessage">このアプリケーションは、Windows 10、Windows Server 2016 以降でのみサポートされています。</String>
</WixLocalization>

6
installer/msi/ko-KR.wxl Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="ko-KR" Codepage="949" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Language">1042</String>
<String Id="ApplicationName">FRP 관리자</String>
<String Id="BlockMessage">이 애플리케이션은 Windows 10, Windows Server 2016 이상에서만 지원됩니다.</String>
</WixLocalization>

6
installer/msi/zh-CN.wxl Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="zh-CN" Codepage="936" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Language">2052</String>
<String Id="ApplicationName">FRP 管理器</String>
<String Id="BlockMessage">此应用程序仅在 Windows 10、Windows Server 2016 或更高版本上受支持。</String>
</WixLocalization>

6
installer/msi/zh-TW.wxl Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="zh-TW" Codepage="950" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="Language">1028</String>
<String Id="ApplicationName">FRP 管理器</String>
<String Id="BlockMessage">此應用程式僅在 Windows 10、Windows Server 2016 或更高版本上支援。</String>
</WixLocalization>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="frpmgr-Setup" type="win32"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@ -0,0 +1,16 @@
#ifndef _RESOURCE_H
#define _RESOURCE_H
#define IDI_ICON 10
#define IDR_MSI 11
#define IDD_LANG_DIALOG 100
#define IDC_LANG_COMBO 1000
#define IDC_STATIC -1
#define IDS_TITLE 200
#define IDS_MANAGEMENT 201
#define IDS_OPERATION 202
#define IDS_REINSTALL 203
#define IDS_UNINSTALL 204
#endif

195
installer/setup/resource.rc Normal file
View File

@ -0,0 +1,195 @@
#include <windows.h>
#include "resource.h"
#pragma code_page(65001) // UTF-8
#define STRINGIZE(x) #x
#define EXPAND(x) STRINGIZE(x)
#define TITLE_EN_US "FRP Manager Setup"
#define TITLE_ZH_CN "FRP 管理器安装程序"
#define TITLE_ZH_TW "FRP 管理器安裝程式"
#define TITLE_JA_JP "FRP マネージャーインストーラー"
#define TITLE_KO_KR "FRP 관리자 설치 프로그램"
#define TITLE_ES_ES "Instalación de Administrador de FRP"
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST manifest.xml
IDI_ICON ICON "../icon/app.ico"
IDR_MSI RCDATA EXPAND(MSI_FILE)
#define VERSIONINFO_TEMPLATE(block_id, lang_id, charset_id, file_desc, product_name) \
VS_VERSION_INFO VERSIONINFO \
FILEVERSION VERSION_ARRAY \
PRODUCTVERSION VERSION_ARRAY \
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK \
FILEFLAGS 0x0 \
FILEOS VOS__WINDOWS32 \
FILETYPE VFT_APP \
FILESUBTYPE VFT2_UNKNOWN \
BEGIN \
BLOCK "StringFileInfo" \
BEGIN \
BLOCK block_id \
BEGIN \
VALUE "CompanyName", "FRP Manager Project" \
VALUE "FileDescription", file_desc \
VALUE "FileVersion", EXPAND(VERSION_STR) \
VALUE "InternalName", "frpmgr-setup" \
VALUE "LegalCopyright", "Copyright © FRP Manager Project" \
VALUE "OriginalFilename", EXPAND(FILENAME) \
VALUE "ProductName", product_name \
VALUE "ProductVersion", EXPAND(VERSION_STR) \
VALUE "Comments", "https://github.com/koho/frpmgr" \
END \
END \
BLOCK "VarFileInfo" \
BEGIN \
VALUE "Translation", lang_id, charset_id \
END \
END
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
VERSIONINFO_TEMPLATE(
"040904B0", 0x0409, 1200,
TITLE_EN_US,
"FRP Manager"
)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
VERSIONINFO_TEMPLATE(
"080404B0", 0x0804, 1200,
TITLE_ZH_CN,
"FRP 管理器"
)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
VERSIONINFO_TEMPLATE(
"040404B0", 0x0404, 1200,
TITLE_ZH_TW,
"FRP 管理器"
)
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
VERSIONINFO_TEMPLATE(
"041104B0", 0x0411, 1200,
TITLE_JA_JP,
"FRP マネージャ"
)
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
VERSIONINFO_TEMPLATE(
"041204B0", 0x0412, 1200,
TITLE_KO_KR,
"FRP 관리자"
)
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
VERSIONINFO_TEMPLATE(
"0C0A04B0", 0x0C0A, 1200,
TITLE_ES_ES,
"Administrador de FRP"
)
#define LANG_DIALOG_TEMPLATE(title, description, ok, cancel) \
IDD_LANG_DIALOG DIALOGEX 0, 0, 252, 69 \
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU \
CAPTION title \
FONT 9, "Segoe UI" \
BEGIN \
COMBOBOX IDC_LANG_COMBO, 34, 30, 211, 374, CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP \
DEFPUSHBUTTON ok, IDOK, 141, 48, 50, 14, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP \
PUSHBUTTON cancel, IDCANCEL, 195, 48, 50, 14, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP \
LTEXT description, IDC_STATIC, 34, 8, 211, 20, SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP \
ICON IDI_ICON, IDC_STATIC, 7, 7, 21, 20, SS_ICON | WS_CHILD | WS_VISIBLE \
END
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
LANG_DIALOG_TEMPLATE(
TITLE_EN_US, "Select the language for the installation from the choices below.", "OK", "Cancel"
)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
LANG_DIALOG_TEMPLATE(
TITLE_ZH_CN, "从下列选项中选择安装语言。", "确定", "取消"
)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
LANG_DIALOG_TEMPLATE(
TITLE_ZH_TW, "從下列選項中選擇安裝語言。", "確定", "取消"
)
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
LANG_DIALOG_TEMPLATE(
TITLE_JA_JP, "以下のオプションからインストール言語を選択してください。", "OK", "キャンセル"
)
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
LANG_DIALOG_TEMPLATE(
TITLE_KO_KR, "아래 옵션에서 설치 언어를 선택하세요.", "확인", "취소"
)
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
LANG_DIALOG_TEMPLATE(
TITLE_ES_ES, "Seleccione el idioma para la instalación entre las opciones siguientes.", "Aceptar", "Cancelar"
)
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
IDS_TITLE TITLE_EN_US
IDS_MANAGEMENT "Manage Current Product"
IDS_OPERATION "Select the action you want to perform."
IDS_REINSTALL "Reinstall with ""%1""%rThis operation requires suspending the running services.\0 "
IDS_UNINSTALL "Uninstall"
END
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
STRINGTABLE
BEGIN
IDS_TITLE TITLE_ZH_CN
IDS_MANAGEMENT "管理当前产品"
IDS_OPERATION "选择希望执行的操作。"
IDS_REINSTALL "以 “%1” 重新安装%r此操作需要暂停正在运行的服务。\0 "
IDS_UNINSTALL "卸载"
END
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL
STRINGTABLE
BEGIN
IDS_TITLE TITLE_ZH_TW
IDS_MANAGEMENT "管理現行產品"
IDS_OPERATION "選取您要執行的作業。"
IDS_REINSTALL "以「%1」重新安裝%r此操作需要暫停正在運作的服務。\0 "
IDS_UNINSTALL "移除"
END
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
IDS_TITLE TITLE_JA_JP
IDS_MANAGEMENT "現在のインストールの管理"
IDS_OPERATION "実行するアクションを選択します。"
IDS_REINSTALL "「%1」で再インストールします%rこの操作では実行中のサービスを一時停止する必要があります。\0 "
IDS_UNINSTALL "アンインストール"
END
LANGUAGE LANG_KOREAN, SUBLANG_DEFAULT
STRINGTABLE
BEGIN
IDS_TITLE TITLE_KO_KR
IDS_MANAGEMENT "현재 제품 관리"
IDS_OPERATION "수행하고자 하는 작업을 선택하세요."
IDS_REINSTALL """%1""로 다시 설치%r이 작업을 수행하려면 실행 중인 서비스를 중단해야 합니다.\0 "
IDS_UNINSTALL "제거하다"
END
LANGUAGE LANG_SPANISH, SUBLANG_SPANISH_MODERN
STRINGTABLE
BEGIN
IDS_TITLE TITLE_ES_ES
IDS_MANAGEMENT "Administrar el producto actual"
IDS_OPERATION "Seleccione la acción que desea realizar."
IDS_REINSTALL "Reinstalar con ""%1""%rEsta operación requiere suspender los servicios en ejecución.\0 "
IDS_UNINSTALL "Desinstalar"
END

353
installer/setup/setup.c Normal file
View File

@ -0,0 +1,353 @@
#include <windows.h>
#include <msi.h>
#include <shlwapi.h>
#include <sddl.h>
#include <commctrl.h>
#include <stdio.h>
#include "resource.h"
static WCHAR msiPath[MAX_PATH];
static HANDLE msiFile = INVALID_HANDLE_VALUE;
typedef struct {
WCHAR id[5];
WCHAR name[16];
WCHAR code[10];
} Language;
typedef struct {
WCHAR path[MAX_PATH];
DWORD pathLen;
WCHAR lang[10];
DWORD langLen;
WCHAR version[20];
DWORD versionLen;
} Product;
static Language languages[] = {
{L"2052", L"简体中文", L"zh-CN"},
{L"1028", L"繁體中文", L"zh-TW"},
{L"1033", L"English", L"en-US"},
{L"1041", L"日本語", L"ja-JP"},
{L"1042", L"한국어", L"ko-KR"},
{L"3082", L"Español", L"es-ES"},
};
static INT MatchLanguageCode(LPWSTR langCode)
{
for (size_t i = 0; i < _countof(languages); i++)
{
if (wcscmp(languages[i].code, langCode) == 0)
return i;
}
return -1;
}
static LPWSTR FormatString(HINSTANCE hInstance, UINT uID, ...)
{
LPWSTR pBuffer = NULL, pFormat;
int n = LoadStringW(hInstance, uID, (LPWSTR)&pFormat, 0);
if (n < 2 || pFormat[n - 2] != L'\0')
return NULL;
va_list args = NULL;
va_start(args, uID);
FormatMessageW(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, pFormat,
0, 0, (LPWSTR)&pBuffer, 0, &args);
va_end(args);
return pBuffer;
}
static HANDLE CreateReinstallEvent(LPWSTR path, DWORD pathLen)
{
if (!PathAppendW(path, L"frpmgr.exe"))
return NULL;
HANDLE hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
path[pathLen] = L'\0';
if (hFile == INVALID_HANDLE_VALUE)
return NULL;
FILE_ID_INFO fileId;
BOOL ret = GetFileInformationByHandleEx(hFile, FileIdInfo, &fileId, sizeof(fileId));
CloseHandle(hFile);
if (!ret)
return NULL;
CHAR name[_countof("Global\\") + sizeof(fileId) * 2];
int n = sprintf_s(name, _countof(name), "Global\\%llx", fileId.VolumeSerialNumber);
if (n < 0)
return NULL;
for (size_t i = 0; i < sizeof(fileId.FileId); i++)
n += sprintf_s(&name[n], _countof(name) - n, "%02x", fileId.FileId.Identifier[i]);
return CreateEventA(NULL, TRUE, FALSE, name);
}
static INT GetApplicationLanguage(LPWSTR path, DWORD pathLen)
{
if (!PathAppendW(path, L"lang.config"))
return -1;
DWORD bytesRead = 0;
HANDLE hFile = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
path[pathLen] = L'\0';
if (hFile != INVALID_HANDLE_VALUE)
{
CHAR buf[LOCALE_NAME_MAX_LENGTH];
WCHAR localeName[LOCALE_NAME_MAX_LENGTH];
BOOL ok = ReadFile(hFile, buf, sizeof(buf) - 1, &bytesRead, NULL);
CloseHandle(hFile);
if (ok && bytesRead != 0)
{
buf[bytesRead] = 0;
if (MultiByteToWideChar(CP_UTF8, 0, buf, -1, localeName, _countof(localeName)) > 0)
{
INT i = MatchLanguageCode(localeName);
if (i >= 0)
return i;
}
}
}
if (!PathAppendW(path, L"app.json"))
return -1;
hFile = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
path[pathLen] = L'\0';
if (hFile == INVALID_HANDLE_VALUE)
return -1;
CHAR buf[100];
// To avoid JSON dependency, we require the first field to be the language setting.
static const CHAR* langKey = "{\"lang\":\"*\"";
WCHAR langCode[10];
DWORD langCodeLen = 0;
INT j = 0;
while (ReadFile(hFile, buf, sizeof(buf), &bytesRead, NULL) && bytesRead != 0)
{
for (DWORD i = 0; i < bytesRead; i++)
{
if (langKey[j] == '*')
{
if (buf[i] == '"')
j++;
else
{
langCode[langCodeLen++] = buf[i];
if (langCodeLen >= sizeof(langCode) - 1)
goto out;
continue;
}
}
if (buf[i] == langKey[j])
{
j++;
if (langKey[j] == 0)
goto out;
}
else if (buf[i] != '\t' && buf[i] != ' ' && buf[i] != '\r' && buf[i] != '\n')
goto out;
else if (langKey[j] != '{' && langKey[j] != ':' && j > 0 && langKey[j - 1] != '{' && langKey[j - 1] != ':')
goto out;
}
}
out:
CloseHandle(hFile);
if (langKey[j] != 0 || langCodeLen == 0)
return -1;
langCode[langCodeLen] = 0;
return MatchLanguageCode(langCode);
}
INT_PTR CALLBACK LanguageDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
INT_PTR nResult;
switch (message) {
case WM_INITDIALOG:
for (size_t i = 0; i < _countof(languages); i++)
SendDlgItemMessageW(hDlg, IDC_LANG_COMBO, CB_ADDSTRING, 0, (LPARAM)languages[i].name);
SendDlgItemMessageW(hDlg, IDC_LANG_COMBO, CB_SETCURSEL, lParam, 0);
return (INT_PTR)TRUE;
case WM_COMMAND:
nResult = LOWORD(wParam);
if (nResult == IDOK || nResult == IDCANCEL)
{
if (nResult == IDOK)
{
LRESULT i = SendDlgItemMessageW(hDlg, IDC_LANG_COMBO, CB_GETCURSEL, 0, 0);
nResult = (i >= 0 && i < _countof(languages)) ? (INT_PTR)&languages[i] : 0;
}
else
nResult = 0;
EndDialog(hDlg, nResult);
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
static int Cleanup(void)
{
if (msiFile != INVALID_HANDLE_VALUE)
{
CloseHandle(msiFile);
msiFile = INVALID_HANDLE_VALUE;
}
for (INT i = 0; i < 200 && !DeleteFileW(msiPath) && GetLastError() != ERROR_FILE_NOT_FOUND; i++)
Sleep(200);
return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
INT langIndex = -1;
BOOL installed = FALSE, reinstall = FALSE, showDlg = TRUE;
Product product = {
.pathLen = _countof(product.path),
.langLen = _countof(product.lang),
.versionLen = _countof(product.version)
};
#ifdef UPGRADE_CODE
WCHAR productCode[39];
if (MsiEnumRelatedProductsW(UPGRADE_CODE, 0, 0, productCode) == ERROR_SUCCESS)
{
MsiGetProductInfo(productCode, INSTALLPROPERTY_VERSIONSTRING, product.version, &product.versionLen);
if (MsiGetProductInfo(productCode, INSTALLPROPERTY_INSTALLLOCATION, product.path, &product.pathLen) == ERROR_SUCCESS && product.path[0])
langIndex = GetApplicationLanguage(product.path, product.pathLen);
if (MsiGetProductInfo(productCode, L"InstalledLanguage", product.lang, &product.langLen) == ERROR_SUCCESS && langIndex < 0)
{
for (size_t i = 0; i < _countof(languages); i++)
{
if (wcscmp(languages[i].id, product.lang) == 0)
{
langIndex = i;
break;
}
}
}
}
#ifdef VERSION
installed = wcscmp(VERSION, product.version) == 0;
showDlg = !product.path[0] || installed || langIndex < 0;
#endif
#endif
if (langIndex < 0)
{
PZZWSTR langList = NULL;
ULONG langNum, langLen = 0;
if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langNum, NULL, &langLen))
{
langList = (PZZWSTR)LocalAlloc(LMEM_FIXED, langLen * sizeof(WCHAR));
if (langList)
{
if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &langNum, langList, &langLen) && langNum > 0)
{
for (size_t i = 0; i < langLen && langList[i] != L'\0'; i += wcsnlen_s(&langList[i], langLen - i) + 1)
{
langIndex = MatchLanguageCode(&langList[i]);
if (langIndex >= 0)
break;
}
}
LocalFree(langList);
}
}
}
Language* lang = showDlg ? (Language*)DialogBoxParamW(
hInstance, MAKEINTRESOURCE(IDD_LANG_DIALOG),
NULL, LanguageDialog, langIndex
) : &languages[langIndex];
if (lang == NULL)
return 0;
if (installed)
{
LPWSTR pszButtonText = FormatString(hInstance, IDS_REINSTALL, lang->name);
const TASKDIALOG_BUTTON buttons[] = {
{ IDYES, pszButtonText },
{ IDNO, MAKEINTRESOURCE(IDS_UNINSTALL) }
};
TASKDIALOGCONFIG config = {
.cbSize = sizeof(config),
.hInstance = hInstance,
.dwFlags = TDF_USE_COMMAND_LINKS,
.dwCommonButtons = TDCBF_CLOSE_BUTTON,
.pszWindowTitle = MAKEINTRESOURCE(IDS_TITLE),
.pszMainIcon = MAKEINTRESOURCE(IDI_ICON),
.pszMainInstruction = MAKEINTRESOURCE(IDS_MANAGEMENT),
.pszContent = MAKEINTRESOURCE(IDS_OPERATION),
.cButtons = ARRAYSIZE(buttons),
.pButtons = buttons,
.nDefaultButton = IDYES
};
LPWSTR newLine;
if (pszButtonText && (newLine = wcschr(pszButtonText, L'\r')))
*newLine = L'\n';
int nButtonPressed = 0;
HRESULT ret = TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL);
if (pszButtonText)
LocalFree((HLOCAL)pszButtonText);
if (ret != S_OK)
return 1;
if (nButtonPressed == IDCLOSE)
return 0;
reinstall = nButtonPressed == IDYES;
}
if (!GetWindowsDirectoryW(msiPath, _countof(msiPath)) || !PathAppendW(msiPath, L"Temp"))
return 1;
GUID guid;
if (FAILED(CoCreateGuid(&guid)))
return 1;
WCHAR identifier[40];
if (StringFromGUID2(&guid, identifier, _countof(identifier)) == 0 || !PathAppendW(msiPath, identifier))
return 1;
HRSRC hRes = FindResourceW(NULL, MAKEINTRESOURCE(IDR_MSI), RT_RCDATA);
if (hRes == NULL)
return 1;
HGLOBAL hResData = LoadResource(NULL, hRes);
if (hResData == NULL)
return 1;
DWORD resSize = SizeofResource(NULL, hRes);
if (resSize == 0)
return 1;
LPVOID pResData = LockResource(hResData);
if (pResData == NULL)
return 1;
SECURITY_ATTRIBUTES sa = { .nLength = sizeof(sa) };
if (!ConvertStringSecurityDescriptorToSecurityDescriptorA("O:BAD:PAI(A;;FA;;;BA)", SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL))
return 1;
msiFile = CreateFileW(msiPath, GENERIC_WRITE, 0, &sa, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, NULL);
if (sa.lpSecurityDescriptor)
LocalFree(sa.lpSecurityDescriptor);
if (msiFile == INVALID_HANDLE_VALUE)
return 1;
_onexit(Cleanup);
DWORD bytesWritten;
BOOL ok = WriteFile(msiFile, pResData, resSize, &bytesWritten, NULL);
CloseHandle(msiFile);
msiFile = INVALID_HANDLE_VALUE;
if (!ok || bytesWritten != resSize)
return 1;
if (installed)
{
if (!reinstall)
return MsiInstallProductW(msiPath, L"REMOVE=ALL");
HANDLE hEvent = CreateReinstallEvent(product.path, product.pathLen);
UINT ret = MsiInstallProductW(msiPath, L"REMOVE=ALL MSIDISABLERMRESTART=1 SAVESTATE=1");
if (hEvent)
CloseHandle(hEvent);
if (ret != ERROR_SUCCESS)
return 1;
MsiSetInternalUI(INSTALLUILEVEL_BASIC | INSTALLUILEVEL_ENDDIALOG, NULL);
}
else
MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
#define CMD_FORMAT L"ProductLanguage=%s PREVINSTALLFOLDER=\"%s\""
WCHAR cmd[_countof(CMD_FORMAT) + _countof(product.path)];
if (swprintf_s(cmd, _countof(cmd), CMD_FORMAT, lang->id, product.path) < 0)
return 1;
return MsiInstallProductW(msiPath, cmd);
}

View File

@ -1,51 +0,0 @@
--- pkg/mod/github.com/lxn/walk@v0.0.0-20210112085537-c389da54e794/icon.go 2020-12-02 18:15:12.452532000 +0800
+++ patches/icon.go 2020-12-02 18:19:27.967292500 +0800
@@ -246,18 +246,22 @@
var hInst win.HINSTANCE
var name *uint16
+ var flags uint32
if i.filePath != "" {
absFilePath, err := filepath.Abs(i.filePath)
if err != nil {
return 0, err
}
+ flags |= win.LR_LOADFROMFILE
name = syscall.StringToUTF16Ptr(absFilePath)
} else {
if !i.isStock {
if hInst = win.GetModuleHandle(nil); hInst == 0 {
return 0, lastError("GetModuleHandle")
}
+ } else {
+ flags |= win.LR_SHARED
}
name = i.res
@@ -265,6 +269,7 @@
var size Size
if i.size96dpi.Width == 0 || i.size96dpi.Height == 0 {
+ flags |= win.LR_DEFAULTSIZE
size = SizeFrom96DPI(defaultIconSize(), dpi)
} else {
size = SizeFrom96DPI(i.size96dpi, dpi)
@@ -291,7 +296,16 @@
int32(size.Height),
&hIcon))
if hr < 0 || hIcon == 0 {
- return 0, lastError("LoadIconWithScaleDown")
+ hIcon = win.HICON(win.LoadImage(
+ hInst,
+ name,
+ win.IMAGE_ICON,
+ int32(size.Width),
+ int32(size.Height),
+ flags))
+ if hIcon == 0 {
+ return 0, lastError("LoadIconWithScaleDown & LoadImage")
+ }
}
}

View File

@ -1,32 +0,0 @@
--- pkg/mod/github.com/fatedier/frp@v0.39.1/cmd/frpc/sub/root.go 2022-02-07 09:18:00.000000000 +0800
+++ patches/root.go 2022-02-07 10:12:45.000000000 +0800
@@ -102,13 +102,13 @@
if showVersion {
fmt.Println(version.Full())
return nil
}
// Do not show command usage here.
- err := runClient(cfgFile)
+ err := RunClient(cfgFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return nil
},
@@ -161,13 +161,13 @@
err = fmt.Errorf("Parse config error: %v", err)
return
}
return
}
-func runClient(cfgFilePath string) error {
+func RunClient(cfgFilePath string) error {
cfg, pxyCfgs, visitorCfgs, err := config.ParseClientConfig(cfgFilePath)
if err != nil {
return err
}
return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
}

View File

@ -1,103 +0,0 @@
--- pkg/mod/github.com/fatedier/frp@v0.39.1/client/service.go 2022-02-07 09:18:00.000000000 +0800
+++ patches/service.go 2022-02-07 10:12:45.000000000 +0800
@@ -20,12 +20,13 @@
"errors"
"fmt"
"io"
"net"
"runtime"
"strconv"
+ "strings"
"sync"
"sync/atomic"
"time"
"github.com/fatedier/frp/assets"
"github.com/fatedier/frp/pkg/auth"
@@ -34,12 +35,13 @@
"github.com/fatedier/frp/pkg/transport"
"github.com/fatedier/frp/pkg/util/log"
frpNet "github.com/fatedier/frp/pkg/util/net"
"github.com/fatedier/frp/pkg/util/version"
"github.com/fatedier/frp/pkg/util/xlog"
libdial "github.com/fatedier/golib/net/dial"
+ "github.com/miekg/dns"
fmux "github.com/hashicorp/yamux"
)
// Service is a client service.
type Service struct {
@@ -203,12 +205,50 @@
svr.ctlMu.Unlock()
break
}
}
}
+func lookupIP(addr string, server string) (string, error) {
+ if net.ParseIP(addr) != nil {
+ return addr, nil
+ }
+ c := dns.Client{}
+ m := dns.Msg{}
+ if !strings.HasSuffix(addr, ".") {
+ addr += "."
+ }
+ if !strings.Contains(server, ":") {
+ server += ":53"
+ }
+ m.SetQuestion(addr, dns.TypeA)
+ r, _, err := c.Exchange(&m, server)
+ if err != nil {
+ return "", err
+ }
+ if len(r.Answer) == 0 {
+ m.SetQuestion(addr, dns.TypeAAAA)
+ if r, _, err = c.Exchange(&m, server); err != nil {
+ return "", err
+ }
+ if len(r.Answer) == 0 {
+ return "", errors.New(fmt.Sprintf("no record for host '%s' with '%s'", addr, server))
+ }
+ }
+ switch v := r.Answer[0].(type) {
+ case *dns.A:
+ return v.A.String(), nil
+ case *dns.AAAA:
+ return v.AAAA.String(), nil
+ case *dns.CNAME:
+ return lookupIP(v.Target, server)
+ default:
+ return "", errors.New(fmt.Sprintf("host '%s' lookup failed with '%s'", addr, server))
+ }
+}
+
// login creates a connection to frps and registers it self as a client
// conn: control connection
// session: if it's not nil, using tcp mux
func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
xl := xlog.FromContextSafe(svr.ctx)
var tlsConfig *tls.Config
@@ -249,14 +289,20 @@
libdial.WithProxyAuth(auth),
libdial.WithTLSConfig(tlsConfig),
libdial.WithAfterHook(libdial.AfterHook{
Hook: frpNet.DialHookCustomTLSHeadByte(tlsConfig != nil, svr.cfg.DisableCustomTLSFirstByte),
}),
)
+ var serverAddr = svr.cfg.ServerAddr
+ if svr.cfg.DNSServer != "" && runtime.GOOS == "windows" {
+ if serverAddr, err = lookupIP(svr.cfg.ServerAddr, svr.cfg.DNSServer); err != nil {
+ return nil, nil, err
+ }
+ }
conn, err = libdial.Dial(
- net.JoinHostPort(svr.cfg.ServerAddr, strconv.Itoa(svr.cfg.ServerPort)),
+ net.JoinHostPort(serverAddr, strconv.Itoa(svr.cfg.ServerPort)),
dialOptions...,
)
if err != nil {
return
}

76
pkg/config/app.go Normal file
View File

@ -0,0 +1,76 @@
package config
import (
"encoding/json"
"os"
"github.com/koho/frpmgr/pkg/consts"
)
const (
DefaultAppFile = "app.json"
LangFile = "lang.config"
)
type App struct {
Lang string `json:"lang,omitempty"`
Password string `json:"password,omitempty"`
CheckUpdate bool `json:"checkUpdate"`
Defaults DefaultValue `json:"defaults"`
Sort []string `json:"sort,omitempty"`
Position []int32 `json:"position,omitempty"`
}
type DefaultValue struct {
Protocol string `json:"protocol,omitempty"`
User string `json:"user,omitempty"`
LogLevel string `json:"logLevel"`
LogMaxDays int64 `json:"logMaxDays"`
DNSServer string `json:"dnsServer,omitempty"`
NatHoleSTUNServer string `json:"natHoleStunServer,omitempty"`
ConnectServerLocalIP string `json:"connectServerLocalIP,omitempty"`
TCPMux bool `json:"tcpMux"`
TLSEnable bool `json:"tls"`
ManualStart bool `json:"manualStart,omitempty"`
LegacyFormat bool `json:"legacyFormat,omitempty"`
}
func (dv *DefaultValue) AsClientConfig() ClientCommon {
return ClientCommon{
ServerPort: consts.DefaultServerPort,
Protocol: dv.Protocol,
User: dv.User,
LogLevel: dv.LogLevel,
LogMaxDays: dv.LogMaxDays,
DNSServer: dv.DNSServer,
NatHoleSTUNServer: dv.NatHoleSTUNServer,
ConnectServerLocalIP: dv.ConnectServerLocalIP,
TCPMux: dv.TCPMux,
TLSEnable: dv.TLSEnable,
ManualStart: dv.ManualStart,
LegacyFormat: dv.LegacyFormat,
DisableCustomTLSFirstByte: true,
}
}
func UnmarshalAppConf(path string, dst *App) (lang *string, err error) {
b, err := os.ReadFile(LangFile)
if err == nil {
s := string(b)
lang = &s
}
b, err = os.ReadFile(path)
if err != nil {
return
}
err = json.Unmarshal(b, dst)
return
}
func (conf *App) Save(path string) error {
b, err := json.MarshalIndent(conf, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, b, 0666)
}

53
pkg/config/app_test.go Normal file
View File

@ -0,0 +1,53 @@
package config
import (
"os"
"reflect"
"testing"
)
func TestUnmarshalAppConfFromIni(t *testing.T) {
input := `{
"password": "abcde",
"defaults": {
"logLevel": "info",
"logMaxDays": 5,
"protocol": "kcp",
"user": "user",
"tcpMux": true,
"manualStart": true,
"legacyFormat": true
}
}
`
if err := os.WriteFile(DefaultAppFile, []byte(input), 0666); err != nil {
t.Fatal(err)
}
expected := App{
Password: "abcde",
Defaults: DefaultValue{
LogLevel: "info",
LogMaxDays: 5,
Protocol: "kcp",
User: "user",
TCPMux: true,
ManualStart: true,
LegacyFormat: true,
},
}
expectedLang := "en-US"
if err := os.WriteFile(LangFile, []byte(expectedLang), 0666); err != nil {
t.Fatal(err)
}
var actual App
lang, err := UnmarshalAppConf(DefaultAppFile, &actual)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Expected: %v, got: %v", expected, actual)
}
if lang == nil || *lang != expectedLang {
t.Errorf("Expected: %v, got: %v", expectedLang, lang)
}
}

683
pkg/config/client.go Normal file
View File

@ -0,0 +1,683 @@
package config
import (
"fmt"
"os"
"slices"
"strings"
"github.com/fatedier/frp/pkg/config"
"github.com/fatedier/frp/pkg/config/v1"
frputil "github.com/fatedier/frp/pkg/util/util"
"github.com/pelletier/go-toml/v2"
"github.com/samber/lo"
"gopkg.in/ini.v1"
"github.com/koho/frpmgr/pkg/consts"
"github.com/koho/frpmgr/pkg/util"
)
type ClientAuth struct {
AuthMethod string `ini:"authentication_method,omitempty"`
AuthenticateHeartBeats bool `ini:"authenticate_heartbeats,omitempty" token:"true" oidc:"true"`
AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns,omitempty" token:"true" oidc:"true"`
Token string `ini:"token,omitempty" token:"true"`
TokenSource string `ini:"-" token:"true"`
TokenSourceFile string `ini:"-" token:"true"`
OIDCClientId string `ini:"oidc_client_id,omitempty" oidc:"true"`
OIDCClientSecret string `ini:"oidc_client_secret,omitempty" oidc:"true"`
OIDCAudience string `ini:"oidc_audience,omitempty" oidc:"true"`
OIDCScope string `ini:"oidc_scope,omitempty" oidc:"true"`
OIDCTokenEndpoint string `ini:"oidc_token_endpoint_url,omitempty" oidc:"true"`
OIDCAdditionalEndpointParams map[string]string `ini:"-" oidc:"true"`
}
func (ca ClientAuth) Complete() ClientAuth {
authMethod := ca.AuthMethod
if authMethod != "" {
if auth, err := util.PruneByTag(ca, "true", authMethod); err == nil {
ca = auth.(ClientAuth)
ca.AuthMethod = authMethod
}
if authMethod == consts.AuthToken {
if ca.TokenSource != "" {
ca.Token = ""
} else {
ca.TokenSourceFile = ""
if ca.Token == "" {
ca.AuthMethod = ""
}
}
}
} else {
ca = ClientAuth{}
}
return ca
}
type ClientCommon struct {
v1.APIMetadata `ini:"-"`
ClientAuth `ini:",extends"`
ServerAddress string `ini:"server_addr,omitempty"`
ServerPort int `ini:"server_port,omitempty"`
NatHoleSTUNServer string `ini:"nat_hole_stun_server,omitempty"`
DialServerTimeout int64 `ini:"dial_server_timeout,omitempty"`
DialServerKeepAlive int64 `ini:"dial_server_keepalive,omitempty"`
ConnectServerLocalIP string `ini:"connect_server_local_ip,omitempty"`
HTTPProxy string `ini:"http_proxy,omitempty"`
LogFile string `ini:"log_file,omitempty"`
LogLevel string `ini:"log_level,omitempty"`
LogMaxDays int64 `ini:"log_max_days,omitempty"`
AdminAddr string `ini:"admin_addr,omitempty"`
AdminPort int `ini:"admin_port,omitempty"`
AdminUser string `ini:"admin_user,omitempty"`
AdminPwd string `ini:"admin_pwd,omitempty"`
AdminTLS v1.TLSConfig `ini:"-"`
AssetsDir string `ini:"assets_dir,omitempty"`
PoolCount int `ini:"pool_count,omitempty"`
DNSServer string `ini:"dns_server,omitempty"`
Protocol string `ini:"protocol,omitempty"`
QUICKeepalivePeriod int `ini:"quic_keepalive_period,omitempty"`
QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout,omitempty"`
QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams,omitempty"`
LoginFailExit bool `ini:"login_fail_exit"`
User string `ini:"user,omitempty"`
HeartbeatInterval int64 `ini:"heartbeat_interval,omitempty"`
HeartbeatTimeout int64 `ini:"heartbeat_timeout,omitempty"`
TCPMux bool `ini:"tcp_mux"`
TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval,omitempty"`
TLSEnable bool `ini:"tls_enable"`
TLSCertFile string `ini:"tls_cert_file,omitempty"`
TLSKeyFile string `ini:"tls_key_file,omitempty"`
TLSTrustedCaFile string `ini:"tls_trusted_ca_file,omitempty"`
TLSServerName string `ini:"tls_server_name,omitempty"`
UDPPacketSize int64 `ini:"udp_packet_size,omitempty"`
Start []string `ini:"start,omitempty"`
PprofEnable bool `ini:"pprof_enable,omitempty"`
DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte"`
// Name of this config.
Name string `ini:"frpmgr_name"`
// ManualStart defines whether to start the config on system boot.
ManualStart bool `ini:"frpmgr_manual_start,omitempty"`
// AutoDelete is a mechanism for temporary use.
// The config will be stopped and deleted at some point.
AutoDelete `ini:",extends"`
// Client meta info
Metas map[string]string `ini:"-"`
// Config file format
LegacyFormat bool `ini:"-"`
}
// BaseProxyConf provides configuration info that is common to all types.
type BaseProxyConf struct {
// Name is the name of this proxy.
Name string `ini:"-"`
// Type specifies the type of this. Valid values include tcp, udp,
// xtcp, stcp, sudp, http, https, tcpmux. By default, this value is "tcp".
Type string `ini:"type,omitempty"`
// UseEncryption controls whether communication with the server will
// be encrypted. Encryption is done using the tokens supplied in the server
// and client configuration. By default, this value is false.
UseEncryption bool `ini:"use_encryption,omitempty"`
// UseCompression controls whether communication with the server
// will be compressed. By default, this value is false.
UseCompression bool `ini:"use_compression,omitempty"`
// Group specifies which group the proxy is a part of. The server will use
// this information to load balance proxies in the same group. If the value
// is "", this will not be in a group. By default, this value is "".
Group string `ini:"group,omitempty"`
// GroupKey specifies a group key, which should be the same among proxies
// of the same group. By default, this value is "".
GroupKey string `ini:"group_key,omitempty"`
// ProxyProtocolVersion specifies which protocol version to use. Valid
// values include "v1", "v2", and "". If the value is "", a protocol
// version will be automatically selected. By default, this value is "".
ProxyProtocolVersion string `ini:"proxy_protocol_version,omitempty"`
// BandwidthLimit limits the bandwidth.
// 0 means no limit.
BandwidthLimit string `ini:"bandwidth_limit,omitempty"`
BandwidthLimitMode string `ini:"bandwidth_limit_mode,omitempty"`
// LocalIP specifies the IP address or host name.
LocalIP string `ini:"local_ip,omitempty"`
// LocalPort specifies the port.
LocalPort string `ini:"local_port,omitempty"`
// Plugin specifies what plugin should be used for ng. If this value
// is set, the LocalIp and LocalPort values will be ignored. By default,
// this value is "".
Plugin string `ini:"plugin,omitempty"`
// PluginParams specify parameters to be passed to the plugin, if one is
// being used.
PluginParams `ini:",extends"`
// HealthCheckType specifies what protocol to use for health checking.
HealthCheckType string `ini:"health_check_type,omitempty"` // tcp | http
// Health checking parameters.
HealthCheckConf `ini:",extends"`
// Meta info for each proxy
Metas map[string]string `ini:"-"`
// Annotations for each proxy
Annotations map[string]string `ini:"-"`
// Disabled defines whether to start the proxy.
Disabled bool `ini:"-"`
}
type PluginParams struct {
PluginLocalAddr string `ini:"plugin_local_addr,omitempty" http2https:"true" http2http:"true" https2https:"true" https2http:"true" tls2raw:"true"`
PluginCrtPath string `ini:"plugin_crt_path,omitempty" https2https:"true" https2http:"true" tls2raw:"true"`
PluginKeyPath string `ini:"plugin_key_path,omitempty" https2https:"true" https2http:"true" tls2raw:"true"`
PluginHostHeaderRewrite string `ini:"plugin_host_header_rewrite,omitempty" http2https:"true" http2http:"true" https2https:"true" https2http:"true"`
PluginHttpUser string `ini:"plugin_http_user,omitempty" http_proxy:"true" static_file:"true"`
PluginHttpPasswd string `ini:"plugin_http_passwd,omitempty" http_proxy:"true" static_file:"true"`
PluginUser string `ini:"plugin_user,omitempty" socks5:"true"`
PluginPasswd string `ini:"plugin_passwd,omitempty" socks5:"true"`
PluginLocalPath string `ini:"plugin_local_path,omitempty" static_file:"true"`
PluginStripPrefix string `ini:"plugin_strip_prefix,omitempty" static_file:"true"`
PluginUnixPath string `ini:"plugin_unix_path,omitempty" unix_domain_socket:"true"`
PluginHeaders map[string]string `ini:"-" http2https:"true" http2http:"true" https2https:"true" https2http:"true"`
PluginEnableHTTP2 bool `ini:"-" https2https:"true" https2http:"true"`
}
// HealthCheckConf configures health checking. This can be useful for load
// balancing purposes to detect and remove proxies to failing services.
type HealthCheckConf struct {
// HealthCheckTimeoutS specifies the number of seconds to wait for a health
// check attempt to connect. If the timeout is reached, this counts as a
// health check failure. By default, this value is 3.
HealthCheckTimeoutS int `ini:"health_check_timeout_s,omitempty" tcp:"true" http:"true"`
// HealthCheckMaxFailed specifies the number of allowed failures before the
// is stopped. By default, this value is 1.
HealthCheckMaxFailed int `ini:"health_check_max_failed,omitempty" tcp:"true" http:"true"`
// HealthCheckIntervalS specifies the time in seconds between health
// checks. By default, this value is 10.
HealthCheckIntervalS int `ini:"health_check_interval_s,omitempty" tcp:"true" http:"true"`
// HealthCheckURL specifies the address to send health checks to if the
// health check type is "http".
HealthCheckURL string `ini:"health_check_url,omitempty" http:"true"`
// HealthCheckHTTPHeaders specifies the headers to send with the http request.
HealthCheckHTTPHeaders map[string]string `ini:"-" http:"true"`
}
type Proxy struct {
BaseProxyConf `ini:",extends"`
RemotePort string `ini:"remote_port,omitempty" tcp:"true" udp:"true"`
Role string `ini:"role,omitempty" stcp:"true" xtcp:"true" sudp:"true" visitor:"*"`
SK string `ini:"sk,omitempty" stcp:"true" xtcp:"true" sudp:"true" visitor:"*"`
AllowUsers string `ini:"allow_users,omitempty" stcp:"true" xtcp:"true" sudp:"true"`
ServerUser string `ini:"server_user,omitempty" visitor:"*"`
ServerName string `ini:"server_name,omitempty" visitor:"*"`
BindAddr string `ini:"bind_addr,omitempty" visitor:"*"`
BindPort int `ini:"bind_port,omitempty" visitor:"*"`
CustomDomains string `ini:"custom_domains,omitempty" http:"true" https:"true" tcpmux:"true"`
SubDomain string `ini:"subdomain,omitempty" http:"true" https:"true" tcpmux:"true"`
Locations string `ini:"locations,omitempty" http:"true"`
HTTPUser string `ini:"http_user,omitempty" http:"true" tcpmux:"true"`
HTTPPwd string `ini:"http_pwd,omitempty" http:"true" tcpmux:"true"`
HostHeaderRewrite string `ini:"host_header_rewrite,omitempty" http:"true"`
Headers map[string]string `ini:"-" http:"true"`
ResponseHeaders map[string]string `ini:"-" http:"true"`
Multiplexer string `ini:"multiplexer,omitempty" tcpmux:"true"`
RouteByHTTPUser string `ini:"route_by_http_user,omitempty" http:"true" tcpmux:"true"`
// "kcp" or "quic"
Protocol string `ini:"protocol,omitempty" visitor:"xtcp"`
KeepTunnelOpen bool `ini:"keep_tunnel_open,omitempty" visitor:"xtcp"`
MaxRetriesAnHour int `ini:"max_retries_an_hour,omitempty" visitor:"xtcp"`
MinRetryInterval int `ini:"min_retry_interval,omitempty" visitor:"xtcp"`
FallbackTo string `ini:"fallback_to,omitempty" visitor:"xtcp"`
FallbackTimeoutMs int `ini:"fallback_timeout_ms,omitempty" visitor:"xtcp"`
}
// GetAlias returns the alias of this proxy.
// It's usually equal to the proxy name, but proxies that start with "range:" differ from it.
func (p *Proxy) GetAlias() []string {
if p.IsRange() {
localPorts, err := frputil.ParseRangeNumbers(p.LocalPort)
if err != nil {
return []string{p.Name}
}
alias := make([]string, len(localPorts))
for i := range localPorts {
alias[i] = fmt.Sprintf("%s_%d", p.Name, i)
}
return alias
}
return []string{p.Name}
}
// IsVisitor returns a boolean indicating whether the proxy has a visitor role.
func (p *Proxy) IsVisitor() bool {
return (p.Type == consts.ProxyTypeXTCP ||
p.Type == consts.ProxyTypeSTCP ||
p.Type == consts.ProxyTypeSUDP) && p.Role == "visitor"
}
func (p *Proxy) IsRange() bool {
return (p.Type == consts.ProxyTypeTCP || p.Type == consts.ProxyTypeUDP) &&
lo.Some([]rune(p.LocalPort+p.RemotePort), []rune{',', '-'})
}
// Complete removes redundant parameters base on the proxy type.
func (p *Proxy) Complete() {
var base = p.BaseProxyConf
if p.IsVisitor() {
// Visitor
if vp, err := util.PruneByTag(*p, p.Type, "visitor"); err == nil {
*p = vp.(Proxy)
}
p.BaseProxyConf = BaseProxyConf{
Name: base.Name, Type: base.Type, UseEncryption: base.UseEncryption,
UseCompression: base.UseCompression, Disabled: base.Disabled,
}
// Reset xtcp visitor parameters
if !p.KeepTunnelOpen {
p.MaxRetriesAnHour = 0
p.MinRetryInterval = 0
}
if p.FallbackTo == "" {
p.FallbackTimeoutMs = 0
}
} else {
// Plugins
if base.Plugin != "" {
base.LocalIP = ""
base.LocalPort = ""
if pluginParams, err := util.PruneByTag(base.PluginParams, "true", base.Plugin); err == nil {
base.PluginParams = pluginParams.(PluginParams)
}
} else {
base.PluginParams = PluginParams{}
}
// Health Check
if base.HealthCheckType != "" {
if healthCheckConf, err := util.PruneByTag(base.HealthCheckConf, "true", base.HealthCheckType); err == nil {
base.HealthCheckConf = healthCheckConf.(HealthCheckConf)
}
} else {
base.HealthCheckConf = HealthCheckConf{}
}
// Proxy type
if typedProxy, err := util.PruneByTag(*p, "true", p.Type); err == nil {
*p = typedProxy.(Proxy)
}
p.BaseProxyConf = base
}
}
type ClientConfig struct {
ClientCommon
Proxies []*Proxy
}
// Name of this config.
func (conf *ClientConfig) Name() string {
return conf.ClientCommon.Name
}
// AutoStart indicates whether this config should be started at boot.
func (conf *ClientConfig) AutoStart() bool {
return !conf.ManualStart
}
func (conf *ClientConfig) DeleteProxy(index int) {
conf.Proxies = append(conf.Proxies[:index], conf.Proxies[index+1:]...)
}
func (conf *ClientConfig) AddProxy(proxy *Proxy) {
conf.Proxies = append(conf.Proxies, proxy)
}
func (conf *ClientConfig) Save(path string) error {
if conf.LegacyFormat {
return conf.saveINI(path)
} else {
return conf.saveTOML(path)
}
}
func (conf *ClientConfig) saveINI(path string) error {
cfg := ini.Empty()
common, err := cfg.NewSection("common")
if err != nil {
return err
}
if err = common.ReflectFrom(&conf.ClientCommon); err != nil {
return err
}
for k, v := range conf.Metas {
common.Key("meta_" + k).SetValue(v)
}
for k, v := range conf.OIDCAdditionalEndpointParams {
common.Key("oidc_additional_" + k).SetValue(v)
}
for _, proxy := range conf.Proxies {
name := proxy.Name
if proxy.IsRange() && !strings.HasPrefix(name, consts.RangePrefix) {
name = consts.RangePrefix + name
}
p, err := cfg.NewSection(name)
if err != nil {
return err
}
if err = p.ReflectFrom(&proxy); err != nil {
return err
}
for k, v := range proxy.Metas {
p.Key("meta_" + k).SetValue(v)
}
for k, v := range proxy.Headers {
p.Key("header_" + k).SetValue(v)
}
for k, v := range proxy.PluginHeaders {
p.Key("plugin_header_" + k).SetValue(v)
}
}
return cfg.SaveTo(path)
}
func (conf *ClientConfig) saveTOML(path string) error {
c := ClientConfigV1{
ClientCommonConfig: ClientCommonToV1(&conf.ClientCommon),
Mgr: Mgr{
Name: conf.ClientCommon.Name,
ManualStart: conf.ManualStart,
AutoDelete: conf.AutoDelete,
},
}
for i, v := range conf.Proxies {
if v.IsVisitor() {
visitor := ClientVisitorToV1(v)
visitor.Mgr.Sort = i + 1
c.Visitors = append(c.Visitors, visitor)
} else {
proxies, err := ClientProxyToV1(v)
if err != nil {
return err
}
c.Proxies = append(c.Proxies, proxies...)
}
}
obj, err := toMap(&c, "json")
if err != nil {
return err
}
b, err := toml.Marshal(obj)
if err != nil {
return err
}
return os.WriteFile(path, b, 0666)
}
// Complete prunes and completes this config.
// When "read" is true, the config should be completed for a file loaded from source.
// Otherwise, it should be completed for file written to disk.
func (conf *ClientConfig) Complete(read bool) {
// Common config
if conf.LegacyFormat {
conf.TokenSource = ""
}
conf.ClientAuth = conf.ClientAuth.Complete()
if conf.AdminPort == 0 {
conf.AdminUser = ""
conf.AdminPwd = ""
conf.AssetsDir = ""
conf.AdminTLS = v1.TLSConfig{}
conf.PprofEnable = false
}
conf.AutoDelete = conf.AutoDelete.Complete()
if !conf.TCPMux {
conf.TCPMuxKeepaliveInterval = 0
}
if !conf.TLSEnable {
conf.TLSServerName = ""
conf.TLSCertFile = ""
conf.TLSKeyFile = ""
conf.TLSTrustedCaFile = ""
}
if conf.Protocol == consts.ProtoQUIC {
conf.DialServerTimeout = 0
conf.DialServerKeepAlive = 0
} else {
conf.QUICMaxIdleTimeout = 0
conf.QUICKeepalivePeriod = 0
conf.QUICMaxIncomingStreams = 0
}
// Proxies
for _, proxy := range conf.Proxies {
// Complete proxy
proxy.Complete()
// Check proxy status
if read && len(conf.Start) > 0 {
proxy.Disabled = !lo.Every(conf.Start, proxy.GetAlias())
}
}
if !read {
conf.Start = conf.gatherStart()
}
}
// Copy creates a new copy of this config.
func (conf *ClientConfig) Copy(all bool) *ClientConfig {
newConf := NewDefaultClientConfig()
newConf.ClientCommon = conf.ClientCommon
// We can't share the same log file between different configs
newConf.LogFile = ""
if all {
for _, proxy := range conf.Proxies {
var newProxy = *proxy
newConf.Proxies = append(newConf.Proxies, &newProxy)
}
}
return newConf
}
// gatherStart returns a list of enabled proxies name, or a nil slice if all proxies are enabled.
func (conf *ClientConfig) gatherStart() []string {
allStart := true
start := make([]string, 0)
for _, proxy := range conf.Proxies {
if !proxy.Disabled {
start = append(start, proxy.GetAlias()...)
} else {
allStart = false
}
}
if allStart {
return nil
}
return start
}
// CountStart returns the number of enabled proxies.
func (conf *ClientConfig) CountStart() int {
return len(lo.Filter(conf.Proxies, func(proxy *Proxy, i int) bool { return !proxy.Disabled }))
}
// Ext is the file extension of this config.
func (conf *ClientConfig) Ext() string {
if conf.LegacyFormat {
return ".ini"
} else {
return ".toml"
}
}
// NewProxyFromIni creates a proxy object from ini section
func NewProxyFromIni(name string, section *ini.Section) (*Proxy, error) {
proxy := NewDefaultProxyConfig(name)
if err := section.MapTo(&proxy); err != nil {
return nil, err
}
proxy.Metas = util.GetMapWithoutPrefix(section.KeysHash(), "meta_")
proxy.Headers = util.GetMapWithoutPrefix(section.KeysHash(), "header_")
proxy.PluginHeaders = util.GetMapWithoutPrefix(section.KeysHash(), "plugin_header_")
proxy.Name = strings.TrimPrefix(proxy.Name, consts.RangePrefix)
return proxy, nil
}
// UnmarshalProxyFromIni finds a single proxy section and unmarshals it from ini source.
func UnmarshalProxyFromIni(source interface{}) (*Proxy, error) {
cfg, err := ini.LoadSources(ini.LoadOptions{
IgnoreInlineComment: true,
AllowBooleanKeys: true,
}, source)
if err != nil {
return nil, err
}
var useName string
var useSection *ini.Section
// Try to find a proxy section
findSection:
for _, section := range cfg.Sections() {
switch section.Name() {
case "common":
continue
case ini.DefaultSection:
// Use the default section if no proxy is found
useName, useSection = "", section
continue
default:
useName, useSection = section.Name(), section
break findSection
}
}
if useSection == nil || len(useSection.Keys()) == 0 {
return nil, ini.ErrDelimiterNotFound{}
}
return NewProxyFromIni(useName, useSection)
}
func UnmarshalClientConfFromIni(source interface{}) (*ClientConfig, error) {
conf := NewDefaultClientConfig()
cfg, err := ini.LoadSources(ini.LoadOptions{
IgnoreInlineComment: true,
AllowBooleanKeys: true,
}, source)
if err != nil {
return nil, err
}
// Load common options
common, err := cfg.GetSection("common")
if err != nil {
return nil, err
}
if err = common.MapTo(&conf.ClientCommon); err != nil {
return nil, err
}
conf.Metas = util.GetMapWithoutPrefix(common.KeysHash(), "meta_")
conf.OIDCAdditionalEndpointParams = util.GetMapWithoutPrefix(common.KeysHash(), "oidc_additional_")
// Load all proxies
for _, section := range cfg.Sections() {
name := section.Name()
if name == ini.DefaultSection || name == "common" {
continue
}
proxy, err := NewProxyFromIni(name, section)
if err != nil {
return nil, err
}
conf.Proxies = append(conf.Proxies, proxy)
}
conf.Complete(true)
conf.LegacyFormat = true
return conf, nil
}
func UnmarshalClientConf(source interface{}) (*ClientConfig, error) {
var b []byte
var err error
if path, ok := source.(string); ok {
b, err = os.ReadFile(path)
if err != nil {
return nil, err
}
} else {
b = source.([]byte)
}
if config.DetectLegacyINIFormat(b) {
return UnmarshalClientConfFromIni(source)
}
var cfg = NewDefaultClientConfigV1()
if err = config.LoadConfigure(b, &cfg, false); err != nil {
return nil, err
}
var conf ClientConfig
conf.ClientCommon = ClientCommonFromV1(&cfg.ClientCommonConfig)
conf.ClientCommon.Name = cfg.Mgr.Name
conf.ManualStart = cfg.Mgr.ManualStart
conf.AutoDelete = cfg.Mgr.AutoDelete
// Proxies
ignore := make(map[string]struct{})
proxies := make([]*Proxy, len(cfg.Proxies))
for i, v := range cfg.Proxies {
p := ClientProxyFromV1(v)
if p.IsRange() {
for _, name := range p.GetAlias() {
if name != p.Name {
ignore[name] = struct{}{}
}
}
}
proxies[i] = p
}
conf.Proxies = lo.Filter(proxies, func(item *Proxy, index int) bool {
_, ok := ignore[item.Name]
return !ok
})
// Visitors
slices.SortStableFunc(cfg.Visitors, func(a, b TypedVisitorConfig) int {
if a.Mgr.Sort <= 0 && b.Mgr.Sort <= 0 {
return 0
}
return a.Mgr.Sort - b.Mgr.Sort
})
for _, v := range cfg.Visitors {
visitor := ClientVisitorFromV1(v)
if v.Mgr.Sort <= 0 {
conf.Proxies = append(conf.Proxies, visitor)
} else {
conf.Proxies = slices.Insert(conf.Proxies, min(v.Mgr.Sort-1, len(conf.Proxies)), visitor)
}
}
conf.Complete(true)
return &conf, nil
}
func NewDefaultClientConfig() *ClientConfig {
return &ClientConfig{
ClientCommon: ClientCommon{
ClientAuth: ClientAuth{AuthMethod: consts.AuthToken},
ServerPort: consts.DefaultServerPort,
LogLevel: consts.LogLevelInfo,
LogMaxDays: consts.DefaultLogMaxDays,
TCPMux: true,
TLSEnable: true,
DisableCustomTLSFirstByte: true,
AutoDelete: AutoDelete{DeleteMethod: consts.DeleteRelative},
},
Proxies: make([]*Proxy, 0),
}
}
func NewDefaultClientConfigV1() ClientConfigV1 {
return ClientConfigV1{
ClientCommonConfig: v1.ClientCommonConfig{
Auth: v1.AuthClientConfig{Method: v1.AuthMethodToken},
ServerPort: consts.DefaultServerPort,
Log: v1.LogConfig{Level: consts.LogLevelInfo, MaxDays: consts.DefaultLogMaxDays},
LoginFailExit: lo.ToPtr(false),
},
Mgr: Mgr{AutoDelete: AutoDelete{DeleteMethod: consts.DeleteRelative}},
}
}
func NewDefaultProxyConfig(name string) *Proxy {
return &Proxy{
BaseProxyConf: BaseProxyConf{
Name: name, Type: consts.ProxyTypeTCP,
},
}
}

73
pkg/config/client_test.go Normal file
View File

@ -0,0 +1,73 @@
package config
import (
"reflect"
"testing"
"time"
)
func TestUnmarshalClientConfFromIni(t *testing.T) {
input := `
[common]
server_addr = example.com
server_port = 7001
token = 123456
frpmgr_manual_start = true
frpmgr_delete_method = absolute
frpmgr_delete_after_date = 2023-03-23T00:00:00Z
meta_1 = value
[ssh]
type = tcp
local_ip = 192.168.1.1
local_port = 22
remote_port = 6000
meta_2 = value
`
expected := NewDefaultClientConfig()
expected.LegacyFormat = true
expected.ServerAddress = "example.com"
expected.ServerPort = 7001
expected.Token = "123456"
expected.ManualStart = true
expected.Metas = map[string]string{"1": "value"}
expected.DeleteMethod = "absolute"
expected.DeleteAfterDate = time.Date(2023, 3, 23, 0, 0, 0, 0, time.UTC)
expected.Proxies = append(expected.Proxies, &Proxy{
BaseProxyConf: BaseProxyConf{
Name: "ssh",
Type: "tcp",
LocalIP: "192.168.1.1",
LocalPort: "22",
Metas: map[string]string{"2": "value"},
},
RemotePort: "6000",
})
cc, err := UnmarshalClientConfFromIni([]byte(input))
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(cc, expected) {
t.Errorf("Expected: %v, got: %v", expected, cc)
}
}
func TestProxyGetAlias(t *testing.T) {
input := `
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
`
expected := []string{"test_tcp_0", "test_tcp_1", "test_tcp_2", "test_tcp_3",
"test_tcp_4", "test_tcp_5", "test_tcp_6", "test_tcp_7"}
proxy, err := UnmarshalProxyFromIni([]byte(input))
if err != nil {
t.Fatal(err)
}
output := proxy.GetAlias()
if !reflect.DeepEqual(output, expected) {
t.Errorf("Expected: %v, got: %v", expected, output)
}
}

64
pkg/config/conf.go Normal file
View File

@ -0,0 +1,64 @@
package config
import (
"os"
"time"
"gopkg.in/ini.v1"
"github.com/koho/frpmgr/pkg/consts"
"github.com/koho/frpmgr/pkg/util"
)
func init() {
ini.PrettyFormat = false
ini.PrettyEqual = true
}
type AutoDelete struct {
// DeleteMethod specifies what delete method to use to delete the config.
// If "absolute" is specified, the expiry date is set in config. If "relative" is specified, the expiry date
// is calculated by adding the days to the file modification time. If it's empty, the config has no expiry date.
DeleteMethod string `ini:"frpmgr_delete_method,omitempty" json:"method,omitempty"`
// DeleteAfterDays is the number of days a config will be kept, after which it may be stopped and deleted.
DeleteAfterDays int64 `ini:"frpmgr_delete_after_days,omitempty" relative:"true" json:"afterDays,omitempty"`
// DeleteAfterDate is the last date the config will be valid, after which it may be stopped and deleted.
DeleteAfterDate time.Time `ini:"frpmgr_delete_after_date,omitempty" absolute:"true" json:"afterDate,omitempty"`
}
func (ad AutoDelete) Complete() AutoDelete {
deleteMethod := ad.DeleteMethod
if deleteMethod != "" {
if d, err := util.PruneByTag(ad, "true", deleteMethod); err == nil {
ad = d.(AutoDelete)
ad.DeleteMethod = deleteMethod
}
// Reset zero day
if deleteMethod == consts.DeleteRelative && ad.DeleteAfterDays == 0 {
ad.DeleteMethod = ""
}
} else {
ad = AutoDelete{}
}
return ad
}
// Expiry returns the remaining duration, after which a config will expire.
// If a config has no expiry date, an `ErrNoDeadline` error is returned.
func Expiry(configPath string, del AutoDelete) (time.Duration, error) {
fInfo, err := os.Stat(configPath)
if err != nil {
return 0, err
}
switch del.DeleteMethod {
case consts.DeleteAbsolute:
return time.Until(del.DeleteAfterDate), nil
case consts.DeleteRelative:
if del.DeleteAfterDays > 0 {
elapsed := time.Since(fInfo.ModTime())
total := time.Hour * 24 * time.Duration(del.DeleteAfterDays)
return total - elapsed, nil
}
}
return 0, os.ErrNoDeadline
}

39
pkg/config/conf_test.go Normal file
View File

@ -0,0 +1,39 @@
package config
import (
"os"
"testing"
"time"
)
func init() {
if err := os.MkdirAll("testdata", 0750); err != nil {
panic(err)
}
if err := os.Chdir("testdata"); err != nil {
panic(err)
}
}
func TestExpiry(t *testing.T) {
if err := os.WriteFile("example.ini", []byte("test"), 0666); err != nil {
t.Fatal(err)
}
tests := []struct {
input AutoDelete
expected time.Duration
}{
{input: AutoDelete{DeleteMethod: "relative", DeleteAfterDays: 5}, expected: 5 * time.Hour * 24},
{input: AutoDelete{DeleteMethod: "absolute", DeleteAfterDate: time.Now().AddDate(0, 0, 3)}, expected: 3 * time.Hour * 24},
}
for i, test := range tests {
output, err := Expiry("example.ini", test.input)
if err != nil {
t.Error(err)
continue
}
if (test.expected - output).Abs() > 3*time.Second {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expected, output)
}
}
}

737
pkg/config/conversion.go Normal file
View File

@ -0,0 +1,737 @@
package config
import (
"fmt"
"reflect"
"strconv"
"strings"
"time"
"github.com/fatedier/frp/pkg/config/types"
"github.com/fatedier/frp/pkg/config/v1"
frputil "github.com/fatedier/frp/pkg/util/util"
"github.com/samber/lo"
"github.com/koho/frpmgr/pkg/consts"
)
func ClientCommonFromV1(c *v1.ClientCommonConfig) (r ClientCommon) {
r.APIMetadata = c.APIMetadata
// Auth client config
r.AuthMethod = string(c.Auth.Method)
r.Token = c.Auth.Token
if ts := c.Auth.TokenSource; ts != nil {
r.TokenSource = ts.Type
switch ts.Type {
case "file":
if ts.File != nil {
r.TokenSourceFile = ts.File.Path
}
}
}
r.OIDCClientId = c.Auth.OIDC.ClientID
r.OIDCClientSecret = c.Auth.OIDC.ClientSecret
r.OIDCAudience = c.Auth.OIDC.Audience
r.OIDCScope = c.Auth.OIDC.Scope
r.OIDCTokenEndpoint = c.Auth.OIDC.TokenEndpointURL
r.OIDCAdditionalEndpointParams = c.Auth.OIDC.AdditionalEndpointParams
if lo.Contains(c.Auth.AdditionalScopes, v1.AuthScopeHeartBeats) {
r.AuthenticateHeartBeats = true
}
if lo.Contains(c.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns) {
r.AuthenticateNewWorkConns = true
}
r.User = c.User
r.ServerAddress = c.ServerAddr
r.ServerPort = c.ServerPort
r.NatHoleSTUNServer = c.NatHoleSTUNServer
r.DNSServer = c.DNSServer
if c.LoginFailExit == nil || *c.LoginFailExit {
r.LoginFailExit = true
}
r.Start = c.Start
// Log
r.LogFile = c.Log.To
r.LogLevel = c.Log.Level
r.LogMaxDays = c.Log.MaxDays
// Admin
r.AdminAddr = c.WebServer.Addr
r.AdminPort = c.WebServer.Port
r.AdminUser = c.WebServer.User
r.AdminPwd = c.WebServer.Password
r.AssetsDir = c.WebServer.AssetsDir
r.PprofEnable = c.WebServer.PprofEnable
if c.WebServer.TLS != nil {
r.AdminTLS = *c.WebServer.TLS
}
// Transport
r.Protocol = c.Transport.Protocol
r.DialServerTimeout = c.Transport.DialServerTimeout
r.DialServerKeepAlive = c.Transport.DialServerKeepAlive
r.ConnectServerLocalIP = c.Transport.ConnectServerLocalIP
r.HTTPProxy = c.Transport.ProxyURL
r.PoolCount = c.Transport.PoolCount
if c.Transport.TCPMux == nil || *c.Transport.TCPMux {
r.TCPMux = true
}
r.TCPMuxKeepaliveInterval = c.Transport.TCPMuxKeepaliveInterval
r.QUICMaxIncomingStreams = c.Transport.QUIC.MaxIncomingStreams
r.QUICKeepalivePeriod = c.Transport.QUIC.KeepalivePeriod
r.QUICMaxIdleTimeout = c.Transport.QUIC.MaxIdleTimeout
r.HeartbeatInterval = c.Transport.HeartbeatInterval
r.HeartbeatTimeout = c.Transport.HeartbeatTimeout
if c.Transport.TLS.Enable == nil || *c.Transport.TLS.Enable {
r.TLSEnable = true
}
r.TLSCertFile = c.Transport.TLS.CertFile
r.TLSKeyFile = c.Transport.TLS.KeyFile
r.TLSTrustedCaFile = c.Transport.TLS.TrustedCaFile
r.TLSServerName = c.Transport.TLS.ServerName
if c.Transport.TLS.DisableCustomTLSFirstByte == nil || *c.Transport.TLS.DisableCustomTLSFirstByte {
r.DisableCustomTLSFirstByte = true
}
r.UDPPacketSize = c.UDPPacketSize
r.Metas = c.Metadatas
return
}
func ClientProxyFromV1(pxyCfg TypedProxyConfig) *Proxy {
var r Proxy
clientProxyBaseFromV1(pxyCfg.GetBaseConfig(), &r)
setRemotePort := func(port int) {
if pxyCfg.Mgr.Range.Local != "" && pxyCfg.Mgr.Range.Remote != "" && strings.HasSuffix(r.Name, "_0") {
r.Name = strings.TrimSuffix(r.Name, "_0")
r.LocalPort = pxyCfg.Mgr.Range.Local
r.RemotePort = pxyCfg.Mgr.Range.Remote
} else {
r.RemotePort = strconv.Itoa(port)
}
}
switch v := pxyCfg.ProxyConfigurer.(type) {
case *v1.TCPProxyConfig:
setRemotePort(v.RemotePort)
case *v1.UDPProxyConfig:
setRemotePort(v.RemotePort)
case *v1.HTTPProxyConfig:
r.SubDomain = v.SubDomain
r.CustomDomains = strings.Join(v.CustomDomains, ",")
r.Locations = strings.Join(v.Locations, ",")
r.HTTPUser = v.HTTPUser
r.HTTPPwd = v.HTTPPassword
r.HostHeaderRewrite = v.HostHeaderRewrite
r.RouteByHTTPUser = v.RouteByHTTPUser
r.Headers = v.RequestHeaders.Set
r.ResponseHeaders = v.ResponseHeaders.Set
case *v1.HTTPSProxyConfig:
r.SubDomain = v.SubDomain
r.CustomDomains = strings.Join(v.CustomDomains, ",")
case *v1.TCPMuxProxyConfig:
r.SubDomain = v.SubDomain
r.CustomDomains = strings.Join(v.CustomDomains, ",")
r.HTTPUser = v.HTTPUser
r.HTTPPwd = v.HTTPPassword
r.RouteByHTTPUser = v.RouteByHTTPUser
r.Multiplexer = v.Multiplexer
case *v1.STCPProxyConfig:
r.SK = v.Secretkey
r.AllowUsers = strings.Join(v.AllowUsers, ",")
case *v1.SUDPProxyConfig:
r.SK = v.Secretkey
r.AllowUsers = strings.Join(v.AllowUsers, ",")
case *v1.XTCPProxyConfig:
r.SK = v.Secretkey
r.AllowUsers = strings.Join(v.AllowUsers, ",")
}
return &r
}
func ClientVisitorFromV1(visitorCfg TypedVisitorConfig) *Proxy {
var r Proxy
clientVisitorBaseFromV1(visitorCfg.GetBaseConfig(), &r)
switch v := visitorCfg.VisitorConfigurer.(type) {
case *v1.STCPVisitorConfig:
case *v1.SUDPVisitorConfig:
case *v1.XTCPVisitorConfig:
r.Protocol = v.Protocol
r.KeepTunnelOpen = v.KeepTunnelOpen
r.MaxRetriesAnHour = v.MaxRetriesAnHour
r.MinRetryInterval = v.MinRetryInterval
r.FallbackTo = v.FallbackTo
r.FallbackTimeoutMs = v.FallbackTimeoutMs
}
return &r
}
func clientProxyBaseFromV1(c *v1.ProxyBaseConfig, out *Proxy) {
out.Name = c.Name
out.Type = c.Type
out.UseEncryption = c.Transport.UseEncryption
out.UseCompression = c.Transport.UseCompression
out.BandwidthLimitMode = c.Transport.BandwidthLimitMode
out.BandwidthLimit = c.Transport.BandwidthLimit.String()
out.ProxyProtocolVersion = c.Transport.ProxyProtocolVersion
out.Group = c.LoadBalancer.Group
out.GroupKey = c.LoadBalancer.GroupKey
out.HealthCheckType = c.HealthCheck.Type
out.HealthCheckTimeoutS = c.HealthCheck.TimeoutSeconds
out.HealthCheckMaxFailed = c.HealthCheck.MaxFailed
out.HealthCheckIntervalS = c.HealthCheck.IntervalSeconds
out.HealthCheckURL = c.HealthCheck.Path
out.HealthCheckHTTPHeaders = lo.SliceToMap(c.HealthCheck.HTTPHeaders, func(item v1.HTTPHeader) (string, string) {
return item.Name, item.Value
})
out.LocalIP = c.LocalIP
if c.LocalPort != 0 {
out.LocalPort = strconv.Itoa(c.LocalPort)
}
out.Metas = c.Metadatas
out.Annotations = c.Annotations
out.Plugin = c.Plugin.Type
switch v := c.Plugin.ClientPluginOptions.(type) {
case *v1.HTTP2HTTPSPluginOptions:
out.PluginLocalAddr = v.LocalAddr
out.PluginHostHeaderRewrite = v.HostHeaderRewrite
out.PluginHeaders = v.RequestHeaders.Set
case *v1.HTTP2HTTPPluginOptions:
out.PluginLocalAddr = v.LocalAddr
out.PluginHostHeaderRewrite = v.HostHeaderRewrite
out.PluginHeaders = v.RequestHeaders.Set
case *v1.HTTPProxyPluginOptions:
out.PluginHttpUser = v.HTTPUser
out.PluginHttpPasswd = v.HTTPPassword
case *v1.HTTPS2HTTPPluginOptions:
out.PluginLocalAddr = v.LocalAddr
out.PluginHostHeaderRewrite = v.HostHeaderRewrite
out.PluginCrtPath = v.CrtPath
out.PluginKeyPath = v.KeyPath
out.PluginHeaders = v.RequestHeaders.Set
if v.EnableHTTP2 == nil || *v.EnableHTTP2 {
out.PluginEnableHTTP2 = true
}
case *v1.HTTPS2HTTPSPluginOptions:
out.PluginLocalAddr = v.LocalAddr
out.PluginHostHeaderRewrite = v.HostHeaderRewrite
out.PluginCrtPath = v.CrtPath
out.PluginKeyPath = v.KeyPath
out.PluginHeaders = v.RequestHeaders.Set
if v.EnableHTTP2 == nil || *v.EnableHTTP2 {
out.PluginEnableHTTP2 = true
}
case *v1.Socks5PluginOptions:
out.PluginUser = v.Username
out.PluginPasswd = v.Password
case *v1.StaticFilePluginOptions:
out.PluginLocalPath = v.LocalPath
out.PluginStripPrefix = v.StripPrefix
out.PluginHttpUser = v.HTTPUser
out.PluginHttpPasswd = v.HTTPPassword
case *v1.UnixDomainSocketPluginOptions:
out.PluginUnixPath = v.UnixPath
case *v1.TLS2RawPluginOptions:
out.PluginLocalAddr = v.LocalAddr
out.PluginCrtPath = v.CrtPath
out.PluginKeyPath = v.KeyPath
}
}
func clientVisitorBaseFromV1(c *v1.VisitorBaseConfig, out *Proxy) {
out.Name = c.Name
out.Type = c.Type
out.Role = "visitor"
out.UseEncryption = c.Transport.UseEncryption
out.UseCompression = c.Transport.UseCompression
out.SK = c.SecretKey
out.ServerUser = c.ServerUser
out.ServerName = c.ServerName
out.BindAddr = c.BindAddr
out.BindPort = c.BindPort
}
func ClientCommonToV1(c *ClientCommon) (r v1.ClientCommonConfig) {
r.APIMetadata = c.APIMetadata
// Auth client config
r.Auth = v1.AuthClientConfig{
Method: v1.AuthMethod(c.AuthMethod),
Token: c.Token,
OIDC: v1.AuthOIDCClientConfig{
ClientID: c.OIDCClientId,
ClientSecret: c.OIDCClientSecret,
Audience: c.OIDCAudience,
Scope: c.OIDCScope,
TokenEndpointURL: c.OIDCTokenEndpoint,
AdditionalEndpointParams: c.OIDCAdditionalEndpointParams,
},
}
if c.TokenSource != "" {
r.Auth.TokenSource = &v1.ValueSource{Type: c.TokenSource}
switch c.TokenSource {
case "file":
r.Auth.TokenSource.File = &v1.FileSource{Path: c.TokenSourceFile}
}
}
if c.AuthenticateHeartBeats {
r.Auth.AdditionalScopes = append(r.Auth.AdditionalScopes, v1.AuthScopeHeartBeats)
}
if c.AuthenticateNewWorkConns {
r.Auth.AdditionalScopes = append(r.Auth.AdditionalScopes, v1.AuthScopeNewWorkConns)
}
r.User = c.User
r.ServerAddr = c.ServerAddress
r.ServerPort = c.ServerPort
r.NatHoleSTUNServer = c.NatHoleSTUNServer
r.DNSServer = c.DNSServer
r.LoginFailExit = &c.LoginFailExit
r.Start = c.Start
// Log
r.Log = v1.LogConfig{
To: c.LogFile,
Level: c.LogLevel,
MaxDays: c.LogMaxDays,
}
// Admin
r.WebServer = v1.WebServerConfig{
Addr: c.AdminAddr,
Port: c.AdminPort,
User: c.AdminUser,
Password: c.AdminPwd,
AssetsDir: c.AssetsDir,
PprofEnable: c.PprofEnable,
}
if lo.IsNotEmpty(c.AdminTLS) {
r.WebServer.TLS = &c.AdminTLS
}
// Transport
r.Transport = v1.ClientTransportConfig{
Protocol: c.Protocol,
DialServerTimeout: c.DialServerTimeout,
DialServerKeepAlive: c.DialServerKeepAlive,
ConnectServerLocalIP: c.ConnectServerLocalIP,
ProxyURL: c.HTTPProxy,
PoolCount: c.PoolCount,
TCPMux: &c.TCPMux,
TCPMuxKeepaliveInterval: c.TCPMuxKeepaliveInterval,
QUIC: v1.QUICOptions{
KeepalivePeriod: c.QUICKeepalivePeriod,
MaxIdleTimeout: c.QUICMaxIdleTimeout,
MaxIncomingStreams: c.QUICMaxIncomingStreams,
},
HeartbeatInterval: c.HeartbeatInterval,
HeartbeatTimeout: c.HeartbeatTimeout,
TLS: v1.TLSClientConfig{
Enable: &c.TLSEnable,
DisableCustomTLSFirstByte: &c.DisableCustomTLSFirstByte,
TLSConfig: v1.TLSConfig{
CertFile: c.TLSCertFile,
KeyFile: c.TLSKeyFile,
TrustedCaFile: c.TLSTrustedCaFile,
ServerName: c.TLSServerName,
},
},
}
r.UDPPacketSize = c.UDPPacketSize
r.Metadatas = c.Metas
return
}
func ClientProxyToV1(p *Proxy) ([]TypedProxyConfig, error) {
if p.IsRange() {
localPorts, err := frputil.ParseRangeNumbers(p.LocalPort)
if err != nil {
return nil, err
}
remotePorts, err := frputil.ParseRangeNumbers(p.RemotePort)
if err != nil {
return nil, err
}
if len(localPorts) != len(remotePorts) {
return nil, fmt.Errorf("local ports number should be same with remote ports number")
}
r := make([]TypedProxyConfig, len(localPorts))
for i := range localPorts {
subPxy := *p
subPxy.Name = fmt.Sprintf("%s_%d", p.Name, i)
subPxy.LocalPort = strconv.FormatInt(localPorts[i], 10)
subPxy.RemotePort = strconv.FormatInt(remotePorts[i], 10)
if r[i], err = singleClientProxyToV1(&subPxy); err != nil {
return nil, err
}
}
r[0].Mgr.Range.Local = p.LocalPort
r[0].Mgr.Range.Remote = p.RemotePort
return r, nil
} else {
r, err := singleClientProxyToV1(p)
if err != nil {
return nil, err
}
return []TypedProxyConfig{r}, nil
}
}
func singleClientProxyToV1(p *Proxy) (TypedProxyConfig, error) {
r := TypedProxyConfig{TypedProxyConfig: v1.TypedProxyConfig{Type: p.Type}}
base, err := clientProxyBaseToV1(&p.BaseProxyConf)
if err != nil {
return r, err
}
switch r.Type {
case consts.ProxyTypeTCP:
c := &v1.TCPProxyConfig{ProxyBaseConfig: base}
if p.RemotePort != "" {
if c.RemotePort, err = strconv.Atoi(p.RemotePort); err != nil {
return r, err
}
}
r.ProxyConfigurer = c
case consts.ProxyTypeUDP:
c := &v1.UDPProxyConfig{ProxyBaseConfig: base}
if p.RemotePort != "" {
if c.RemotePort, err = strconv.Atoi(p.RemotePort); err != nil {
return r, err
}
}
r.ProxyConfigurer = c
case consts.ProxyTypeHTTP:
c := &v1.HTTPProxyConfig{
ProxyBaseConfig: base,
DomainConfig: v1.DomainConfig{
SubDomain: p.SubDomain,
},
HTTPUser: p.HTTPUser,
HTTPPassword: p.HTTPPwd,
HostHeaderRewrite: p.HostHeaderRewrite,
RequestHeaders: v1.HeaderOperations{
Set: p.Headers,
},
ResponseHeaders: v1.HeaderOperations{
Set: p.ResponseHeaders,
},
RouteByHTTPUser: p.RouteByHTTPUser,
}
if p.CustomDomains != "" {
c.CustomDomains = strings.Split(p.CustomDomains, ",")
}
if p.Locations != "" {
c.Locations = strings.Split(p.Locations, ",")
}
r.ProxyConfigurer = c
case consts.ProxyTypeHTTPS:
c := &v1.HTTPSProxyConfig{
ProxyBaseConfig: base,
DomainConfig: v1.DomainConfig{
SubDomain: p.SubDomain,
},
}
if p.CustomDomains != "" {
c.CustomDomains = strings.Split(p.CustomDomains, ",")
}
r.ProxyConfigurer = c
case consts.ProxyTypeTCPMUX:
c := &v1.TCPMuxProxyConfig{
ProxyBaseConfig: base,
DomainConfig: v1.DomainConfig{
SubDomain: p.SubDomain,
},
HTTPUser: p.HTTPUser,
HTTPPassword: p.HTTPPwd,
RouteByHTTPUser: p.RouteByHTTPUser,
Multiplexer: p.Multiplexer,
}
if p.CustomDomains != "" {
c.CustomDomains = strings.Split(p.CustomDomains, ",")
}
r.ProxyConfigurer = c
case consts.ProxyTypeSTCP:
c := &v1.STCPProxyConfig{
ProxyBaseConfig: base,
Secretkey: p.SK,
}
if p.AllowUsers != "" {
c.AllowUsers = strings.Split(p.AllowUsers, ",")
}
r.ProxyConfigurer = c
case consts.ProxyTypeSUDP:
c := &v1.SUDPProxyConfig{
ProxyBaseConfig: base,
Secretkey: p.SK,
}
if p.AllowUsers != "" {
c.AllowUsers = strings.Split(p.AllowUsers, ",")
}
r.ProxyConfigurer = c
case consts.ProxyTypeXTCP:
c := &v1.XTCPProxyConfig{
ProxyBaseConfig: base,
Secretkey: p.SK,
}
if p.AllowUsers != "" {
c.AllowUsers = strings.Split(p.AllowUsers, ",")
}
r.ProxyConfigurer = c
}
return r, nil
}
func clientProxyBaseToV1(c *BaseProxyConf) (v1.ProxyBaseConfig, error) {
r := v1.ProxyBaseConfig{
Name: c.Name,
Type: c.Type,
Transport: v1.ProxyTransport{
UseEncryption: c.UseEncryption,
UseCompression: c.UseCompression,
BandwidthLimitMode: c.BandwidthLimitMode,
ProxyProtocolVersion: c.ProxyProtocolVersion,
},
Metadatas: c.Metas,
Annotations: c.Annotations,
LoadBalancer: v1.LoadBalancerConfig{
Group: c.Group,
GroupKey: c.GroupKey,
},
HealthCheck: v1.HealthCheckConfig{
Type: c.HealthCheckType,
TimeoutSeconds: c.HealthCheckTimeoutS,
MaxFailed: c.HealthCheckMaxFailed,
IntervalSeconds: c.HealthCheckIntervalS,
Path: c.HealthCheckURL,
HTTPHeaders: lo.MapToSlice(c.HealthCheckHTTPHeaders, func(key string, value string) v1.HTTPHeader {
return v1.HTTPHeader{Name: key, Value: value}
}),
},
ProxyBackend: v1.ProxyBackend{
LocalIP: c.LocalIP,
Plugin: v1.TypedClientPluginOptions{
Type: c.Plugin,
},
},
}
if c.LocalPort != "" {
localPort, err := strconv.Atoi(c.LocalPort)
if err != nil {
return r, err
}
r.LocalPort = localPort
}
bl, err := types.NewBandwidthQuantity(c.BandwidthLimit)
if err != nil {
return r, err
}
r.Transport.BandwidthLimit = bl
switch c.Plugin {
case consts.PluginHttp2Https:
r.Plugin.ClientPluginOptions = &v1.HTTP2HTTPSPluginOptions{
Type: c.Plugin,
LocalAddr: c.PluginLocalAddr,
HostHeaderRewrite: c.PluginHostHeaderRewrite,
RequestHeaders: v1.HeaderOperations{
Set: c.PluginHeaders,
},
}
case consts.PluginHttp2Http:
r.Plugin.ClientPluginOptions = &v1.HTTP2HTTPPluginOptions{
Type: c.Plugin,
LocalAddr: c.PluginLocalAddr,
HostHeaderRewrite: c.PluginHostHeaderRewrite,
RequestHeaders: v1.HeaderOperations{
Set: c.PluginHeaders,
},
}
case consts.PluginHttpProxy:
r.Plugin.ClientPluginOptions = &v1.HTTPProxyPluginOptions{
Type: c.Plugin,
HTTPUser: c.PluginHttpUser,
HTTPPassword: c.PluginHttpPasswd,
}
case consts.PluginHttps2Http:
r.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPPluginOptions{
Type: c.Plugin,
LocalAddr: c.PluginLocalAddr,
HostHeaderRewrite: c.PluginHostHeaderRewrite,
RequestHeaders: v1.HeaderOperations{
Set: c.PluginHeaders,
},
EnableHTTP2: &c.PluginEnableHTTP2,
CrtPath: c.PluginCrtPath,
KeyPath: c.PluginKeyPath,
}
case consts.PluginHttps2Https:
r.Plugin.ClientPluginOptions = &v1.HTTPS2HTTPSPluginOptions{
Type: c.Plugin,
LocalAddr: c.PluginLocalAddr,
HostHeaderRewrite: c.PluginHostHeaderRewrite,
RequestHeaders: v1.HeaderOperations{
Set: c.PluginHeaders,
},
EnableHTTP2: &c.PluginEnableHTTP2,
CrtPath: c.PluginCrtPath,
KeyPath: c.PluginKeyPath,
}
case consts.PluginSocks5:
r.Plugin.ClientPluginOptions = &v1.Socks5PluginOptions{
Type: c.Plugin,
Username: c.PluginUser,
Password: c.PluginPasswd,
}
case consts.PluginStaticFile:
r.Plugin.ClientPluginOptions = &v1.StaticFilePluginOptions{
Type: c.Plugin,
LocalPath: c.PluginLocalPath,
StripPrefix: c.PluginStripPrefix,
HTTPUser: c.PluginHttpUser,
HTTPPassword: c.PluginHttpPasswd,
}
case consts.PluginUnixDomain:
r.Plugin.ClientPluginOptions = &v1.UnixDomainSocketPluginOptions{
Type: c.Plugin,
UnixPath: c.PluginUnixPath,
}
case consts.PluginTLS2Raw:
r.Plugin.ClientPluginOptions = &v1.TLS2RawPluginOptions{
Type: c.Plugin,
LocalAddr: c.PluginLocalAddr,
CrtPath: c.PluginCrtPath,
KeyPath: c.PluginKeyPath,
}
}
return r, nil
}
func ClientVisitorToV1(p *Proxy) TypedVisitorConfig {
r := TypedVisitorConfig{TypedVisitorConfig: v1.TypedVisitorConfig{Type: p.Type}}
base := clientVisitorBaseToV1(p)
switch p.Type {
case consts.ProxyTypeSTCP:
r.VisitorConfigurer = &v1.STCPVisitorConfig{VisitorBaseConfig: base}
case consts.ProxyTypeSUDP:
r.VisitorConfigurer = &v1.SUDPVisitorConfig{VisitorBaseConfig: base}
case consts.ProxyTypeXTCP:
r.VisitorConfigurer = &v1.XTCPVisitorConfig{
VisitorBaseConfig: base,
Protocol: p.Protocol,
KeepTunnelOpen: p.KeepTunnelOpen,
MaxRetriesAnHour: p.MaxRetriesAnHour,
MinRetryInterval: p.MinRetryInterval,
FallbackTo: p.FallbackTo,
FallbackTimeoutMs: p.FallbackTimeoutMs,
}
}
return r
}
func clientVisitorBaseToV1(p *Proxy) v1.VisitorBaseConfig {
return v1.VisitorBaseConfig{
Name: p.Name,
Type: p.Type,
Transport: v1.VisitorTransport{
UseEncryption: p.UseEncryption,
UseCompression: p.UseCompression,
},
SecretKey: p.SK,
ServerUser: p.ServerUser,
ServerName: p.ServerName,
BindAddr: p.BindAddr,
BindPort: p.BindPort,
}
}
// toMap converts a struct to a map using the struct tags.
func toMap(in any, tag string) (map[string]any, error) {
out := make(map[string]any)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("toMap only accepts structs; got %T", v)
}
t := v.Type()
for i := 0; i < v.NumField(); i++ {
ft := t.Field(i)
fv := v.Field(i)
if fv.Kind() == reflect.Ptr && fv.Elem().Kind() == reflect.Struct {
fv = fv.Elem()
}
key := ft.Tag.Get(tag)
if key != "" {
key = strings.Split(key, ",")[0]
}
if key == "-" {
continue
}
switch fv.Kind() {
case reflect.Struct, reflect.Interface:
value := fv.Interface()
if value == nil {
continue
}
switch o := value.(type) {
case time.Time:
if !o.IsZero() && key != "" {
if b, err := o.MarshalText(); err != nil {
return nil, err
} else {
out[key] = string(b)
}
}
case types.BandwidthQuantity:
if o.String() != "" && key != "" {
out[key] = o.String()
}
default:
m, err := toMap(value, tag)
if err != nil {
return nil, err
}
if len(m) == 0 {
continue
}
if key == "" {
if ft.Anonymous {
for k, v := range m {
out[k] = v
}
}
} else {
out[key] = m
}
}
case reflect.Slice:
if key == "" || fv.Len() == 0 {
continue
}
if fv.Index(0).Kind() == reflect.Struct {
s := make([]any, fv.Len())
for k := 0; k < fv.Len(); k++ {
m, err := toMap(fv.Index(k).Interface(), tag)
if err != nil {
return nil, err
}
s[k] = m
}
out[key] = s
} else {
out[key] = fv.Interface()
}
default:
if key != "" && !fv.IsZero() {
out[key] = fv.Interface()
}
}
}
return out, nil
}

71
pkg/config/v1.go Normal file
View File

@ -0,0 +1,71 @@
package config
import (
"encoding/json"
"github.com/fatedier/frp/pkg/config/v1"
)
type ClientConfigV1 struct {
v1.ClientCommonConfig
Proxies []TypedProxyConfig `json:"proxies,omitempty"`
Visitors []TypedVisitorConfig `json:"visitors,omitempty"`
Mgr Mgr `json:"frpmgr,omitempty"`
}
type Mgr struct {
Name string `json:"name,omitempty"`
ManualStart bool `json:"manualStart,omitempty"`
AutoDelete AutoDelete `json:"autoDelete,omitempty"`
}
type TypedProxyConfig struct {
v1.TypedProxyConfig
Mgr ProxyMgr `json:"frpmgr,omitempty"`
}
type TypedVisitorConfig struct {
v1.TypedVisitorConfig
Mgr ProxyMgr `json:"frpmgr,omitempty"`
}
type ProxyMgr struct {
Range RangePort `json:"range,omitempty"`
Sort int `json:"sort,omitempty"`
}
type RangePort struct {
Local string `json:"local"`
Remote string `json:"remote"`
}
func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
err := c.TypedProxyConfig.UnmarshalJSON(b)
if err != nil {
return err
}
c.Mgr, err = unmarshalProxyMgr(b)
return err
}
func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
err := c.TypedVisitorConfig.UnmarshalJSON(b)
if err != nil {
return err
}
c.Mgr, err = unmarshalProxyMgr(b)
return err
}
func unmarshalProxyMgr(b []byte) (c ProxyMgr, err error) {
s := struct {
Mgr ProxyMgr `json:"frpmgr"`
}{}
if err = json.Unmarshal(b, &s); err != nil {
return
}
c = s.Mgr
return
}

89
pkg/consts/config.go Normal file
View File

@ -0,0 +1,89 @@
package consts
const (
RangePrefix = "range:"
DefaultSTUNServer = "stun.easyvoip.com:3478"
DefaultServerPort = 7000
)
// Protocols
const (
ProtoTCP = "tcp"
ProtoKCP = "kcp"
ProtoQUIC = "quic"
ProtoWebsocket = "websocket"
ProtoWSS = "wss"
)
var Protocols = []string{ProtoTCP, ProtoKCP, ProtoQUIC, ProtoWebsocket, ProtoWSS}
// Proxy types
const (
ProxyTypeTCP = "tcp"
ProxyTypeUDP = "udp"
ProxyTypeXTCP = "xtcp"
ProxyTypeSTCP = "stcp"
ProxyTypeSUDP = "sudp"
ProxyTypeHTTP = "http"
ProxyTypeHTTPS = "https"
ProxyTypeTCPMUX = "tcpmux"
)
var ProxyTypes = []string{
ProxyTypeTCP, ProxyTypeUDP, ProxyTypeXTCP, ProxyTypeSTCP,
ProxyTypeSUDP, ProxyTypeHTTP, ProxyTypeHTTPS, ProxyTypeTCPMUX,
}
// Plugin types
const (
PluginHttpProxy = "http_proxy"
PluginSocks5 = "socks5"
PluginStaticFile = "static_file"
PluginHttps2Http = "https2http"
PluginHttps2Https = "https2https"
PluginHttp2Https = "http2https"
PluginHttp2Http = "http2http"
PluginUnixDomain = "unix_domain_socket"
PluginTLS2Raw = "tls2raw"
)
var PluginTypes = []string{
PluginHttp2Http, PluginHttp2Https, PluginHttps2Http, PluginHttps2Https,
PluginHttpProxy, PluginSocks5, PluginStaticFile, PluginUnixDomain, PluginTLS2Raw,
}
// Auth methods
const (
AuthToken = "token"
AuthOIDC = "oidc"
)
// Delete methods
const (
DeleteAbsolute = "absolute"
DeleteRelative = "relative"
)
// TCP multiplexer
const (
HTTPConnectTCPMultiplexer = "httpconnect"
)
// Bandwidth
var (
Bandwidth = []string{"MB", "KB"}
BandwidthMode = []string{"client", "server"}
)
// Log level
const (
LogLevelTrace = "trace"
LogLevelDebug = "debug"
LogLevelInfo = "info"
LogLevelWarn = "warn"
LogLevelError = "error"
)
var LogLevels = []string{LogLevelTrace, LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError}
const DefaultLogMaxDays = 3

21
pkg/consts/state.go Normal file
View File

@ -0,0 +1,21 @@
package consts
// ConfigState is the state of FRP daemon service
type ConfigState int
const (
ConfigStateUnknown ConfigState = iota
ConfigStateStarted
ConfigStateStopped
ConfigStateStarting
ConfigStateStopping
)
// ProxyState is the state of a proxy.
type ProxyState int
const (
ProxyStateUnknown ProxyState = iota
ProxyStateRunning
ProxyStateError
)

23
pkg/ipc/client.go Normal file
View File

@ -0,0 +1,23 @@
package ipc
import "context"
// ProxyMessage is the status information of a proxy.
type ProxyMessage struct {
Name string
Type string
Status string
Err string
RemoteAddr string
}
// Client is used to query proxy state from the frp client.
// It may be a pipe client or HTTP client.
type Client interface {
// SetCallback changes the callback function for the response message.
SetCallback(cb func([]ProxyMessage))
// Run the client in blocking mode.
Run(ctx context.Context)
// Probe triggers a query request immediately.
Probe(ctx context.Context)
}

79
pkg/ipc/pipe.go Normal file
View File

@ -0,0 +1,79 @@
package ipc
import (
"context"
"encoding/gob"
"time"
"github.com/Microsoft/go-winio"
)
type PipeClient struct {
path string
payload func() []string
ch chan struct{}
cb func([]ProxyMessage)
}
func NewPipeClient(name string, payload func() []string) *PipeClient {
return &PipeClient{
path: `\\.\pipe\` + name,
payload: payload,
ch: make(chan struct{}, 1),
}
}
func (p *PipeClient) SetCallback(cb func([]ProxyMessage)) {
p.cb = cb
}
func (p *PipeClient) Run(ctx context.Context) {
conn, err := winio.DialPipeContext(ctx, p.path)
if err != nil {
return
}
defer conn.Close()
timer := time.NewTimer(0)
defer timer.Stop()
seq := []time.Duration{100 * time.Millisecond, 500 * time.Millisecond, time.Second, 2 * time.Second, 5 * time.Second}
index := -1
query := func() {
if err = gob.NewEncoder(conn).Encode(p.payload()); err != nil {
return
}
var msg []ProxyMessage
if err = gob.NewDecoder(conn).Decode(&msg); err != nil {
return
}
if p.cb != nil {
p.cb(msg)
}
}
for {
select {
case <-timer.C:
query()
if index < len(seq)-1 {
index++
}
timer.Reset(seq[index])
case <-p.ch:
index = 0
timer.Reset(seq[index])
case <-ctx.Done():
return
}
}
}
func (p *PipeClient) Probe(ctx context.Context) {
select {
case <-ctx.Done():
return
case p.ch <- struct{}{}:
default:
return
}
}

65
pkg/ipc/server.go Normal file
View File

@ -0,0 +1,65 @@
package ipc
import (
"encoding/gob"
"net"
"github.com/Microsoft/go-winio"
"github.com/fatedier/frp/client"
)
type Server struct {
listener net.Listener
exporter client.StatusExporter
}
func NewServer(name string, exporter client.StatusExporter) (*Server, error) {
listener, err := winio.ListenPipe(`\\.\pipe\`+name, &winio.PipeConfig{
MessageMode: true,
InputBufferSize: 1024,
OutputBufferSize: 2048,
})
if err != nil {
return nil, err
}
return &Server{listener, exporter}, nil
}
func (s *Server) Run() {
for {
conn, err := s.listener.Accept()
if err != nil {
return
}
go s.handle(conn)
}
}
func (s *Server) handle(conn net.Conn) {
defer conn.Close()
for {
var names []string
if err := gob.NewDecoder(conn).Decode(&names); err != nil {
return
}
msg := make([]ProxyMessage, 0, len(names))
for _, name := range names {
if status, _ := s.exporter.GetProxyStatus(name); status != nil {
msg = append(msg, ProxyMessage{
Name: status.Name,
Type: status.Type,
Status: status.Phase,
Err: status.Err,
RemoteAddr: status.RemoteAddr,
})
}
}
if err := gob.NewEncoder(conn).Encode(msg); err != nil {
return
}
}
}
func (s *Server) Close() error {
return s.listener.Close()
}

40
pkg/layout/greedy.go Normal file
View File

@ -0,0 +1,40 @@
package layout
import "github.com/lxn/walk"
// GreedyLayoutItem is like walk.NewGreedyLayoutItem, but with specific orientation support.
// If an orientation is provided, it will be greedy at the given orientation but not other orientations.
type GreedyLayoutItem struct {
*walk.LayoutItemBase
item walk.LayoutItem
orientation walk.LayoutFlags
}
// NewGreedyLayoutItem returns a layout item that is greedy at the given orientation.
func NewGreedyLayoutItem(orientation walk.Orientation) walk.LayoutItem {
layout := &GreedyLayoutItem{item: walk.NewGreedyLayoutItem()}
layout.LayoutItemBase = layout.item.AsLayoutItemBase()
switch orientation {
case walk.Horizontal:
layout.orientation = walk.GreedyVert
case walk.Vertical:
layout.orientation = walk.GreedyHorz
case walk.NoOrientation:
layout.orientation = walk.LayoutFlags(orientation)
default:
panic("invalid orientation")
}
return layout
}
func (hg *GreedyLayoutItem) LayoutFlags() walk.LayoutFlags {
return hg.item.LayoutFlags() & ^hg.orientation
}
func (hg *GreedyLayoutItem) IdealSize() walk.Size {
return hg.item.(walk.IdealSizer).IdealSize()
}
func (hg *GreedyLayoutItem) MinSize() walk.Size {
return hg.item.(walk.MinSizer).MinSize()
}

31
pkg/layout/greedy_test.go Normal file
View File

@ -0,0 +1,31 @@
package layout
import (
"testing"
"github.com/lxn/walk"
)
func TestNewGreedyLayoutItem(t *testing.T) {
tests := []struct {
input walk.Orientation
expected, unexpected []walk.LayoutFlags
}{
{input: walk.Horizontal, expected: []walk.LayoutFlags{walk.GreedyHorz}, unexpected: []walk.LayoutFlags{walk.GreedyVert}},
{input: walk.Vertical, expected: []walk.LayoutFlags{walk.GreedyVert}, unexpected: []walk.LayoutFlags{walk.GreedyHorz}},
{input: walk.NoOrientation, expected: []walk.LayoutFlags{walk.GreedyHorz, walk.GreedyVert}, unexpected: nil},
}
for i, test := range tests {
flags := NewGreedyLayoutItem(test.input).LayoutFlags()
for _, f := range test.expected {
if f&flags == 0 {
t.Errorf("Test %d: expected: %v, got: %v", i, f, flags)
}
}
for _, f := range test.unexpected {
if f&flags > 0 {
t.Errorf("Test %d: unexpected: %v, got: %v", i, f, flags)
}
}
}
}

149
pkg/res/res.go Normal file
View File

@ -0,0 +1,149 @@
package res
import (
"fmt"
"strings"
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
"github.com/lxn/win"
"github.com/samber/lo"
"golang.org/x/sys/windows"
"github.com/koho/frpmgr/i18n"
"github.com/koho/frpmgr/pkg/validators"
)
// Links
const (
ProjectURL = "https://github.com/koho/frpmgr"
FRPProjectURL = "https://github.com/fatedier/frp"
UpdateURL = "https://api.github.com/repos/koho/frpmgr/releases/latest"
ShareLinkScheme = "frp://"
)
type Icon struct {
Dll string
Index int
}
// Icons
var (
IconLogo = Icon{Index: 7}
IconRandom = Icon{"imageres", -1024}
IconSysCopy = Icon{"shell32", -243}
IconNewConf = Icon{"shell32", -258}
IconCreate = Icon{"shell32", -319}
IconFileImport = Icon{"shell32", -241}
IconURLImport = Icon{"imageres", -184}
IconClipboard = Icon{"shell32", -16763}
IconDelete = Icon{"shell32", -240}
IconExport = Icon{"imageres", -174}
IconQuickAdd = Icon{"shell32", -16769}
IconEdit = Icon{"shell32", -16775}
IconEnable = Icon{"shell32", -16810}
IconDisable = Icon{"imageres", -1027}
IconEditDialog = Icon{"imageres", -114}
IconRemote = Icon{"imageres", -25}
IconSSH = Icon{"imageres", -5372}
IconVNC = Icon{"imageres", -110}
IconWeb = Icon{"shell32", -14}
IconFtp = Icon{"imageres", -143}
IconHttpFile = Icon{"imageres", -73}
IconHttpProxy = Icon{"imageres", -120}
IconOpenPort = Icon{"shell32", -244}
IconLock = Icon{"shell32", -48}
IconFlatLock = Icon{"imageres", -1304}
IconNewVersion1 = Icon{"imageres", -1028}
IconNewVersion2 = Icon{"imageres", 1}
IconUpdate = Icon{"shell32", -47}
IconStateRunning = Icon{"imageres", -106}
IconStateStopped = Icon{Index: 21}
IconStateWorking = Icon{"shell32", -16739}
IconSettings = Icon{"shell32", -153}
IconKey = Icon{"imageres", -5360}
IconLanguage = Icon{"imageres", -94}
IconNat = Icon{"imageres", -1043}
IconFile = Icon{"shell32", -152}
IconInfo = Icon{"imageres", -81}
IconArrowUp = Icon{"shell32", -16817}
IconMove = Icon{"imageres", -5313}
IconSelectAll = Icon{"imageres", -5308}
IconProxyRunning = Icon{"imageres", -1405}
IconProxyError = Icon{"imageres", -1402}
)
// Colors
var (
ColorBlue = walk.RGB(0, 38, 247)
ColorDarkBlue = walk.RGB(0, 51, 153)
ColorLightBlue = walk.RGB(49, 94, 251)
ColorGray = walk.RGB(109, 109, 109)
ColorDarkGray = walk.RGB(85, 85, 85)
ColorGrayBG = walk.Color(win.GetSysColor(win.COLOR_BTNFACE))
)
// Text
var (
TextRegular Font
TextMedium Font
TextLarge Font
)
func init() {
var defaultFontFamily = "Microsoft YaHei UI"
versionInfo := windows.RtlGetVersion()
if versionInfo.MajorVersion == 10 && versionInfo.MinorVersion == 0 {
if versionInfo.BuildNumber < 14393 {
// Windows 10 / Windows 10 1511
IconProxyRunning.Index = IconStateRunning.Index
IconProxyError.Index = -98
// Windows 10
if versionInfo.BuildNumber == 10240 {
IconFlatLock = IconLock
}
} else if versionInfo.BuildNumber == 14393 {
// Windows Server 2016 / Windows 10 1607
IconProxyRunning.Index = -1400
IconProxyError.Index = -1405
} else if versionInfo.BuildNumber == 15063 {
// Windows 10 1703
IconProxyRunning.Index = -1400
IconProxyError.Index = -1402
}
}
TextRegular = Font{Family: defaultFontFamily, PointSize: 9}
TextMedium = Font{Family: defaultFontFamily, PointSize: 10}
TextLarge = Font{Family: defaultFontFamily, PointSize: 12}
}
var (
SupportedConfigFormats = []string{".ini", ".toml", ".json", ".yml", ".yaml"}
cfgPatterns = lo.Map(append([]string{".zip"}, SupportedConfigFormats...), func(item string, index int) string {
return "*" + item
})
)
// Filters
var (
FilterAllFiles = i18n.Sprintf("All Files") + " (*.*)|*.*"
FilterConfig = i18n.Sprintf("Configuration Files") + fmt.Sprintf(" (%s)|%s|", strings.Join(cfgPatterns, ", "), strings.Join(cfgPatterns, ";"))
FilterZip = i18n.Sprintf("Configuration Files") + " (*.zip)|*.zip"
FilterCert = i18n.Sprintf("Certificate Files") + " (*.crt, *.cer)|*.crt;*.cer|"
FilterKey = i18n.Sprintf("Key Files") + " (*.key)|*.key|"
)
// Validators
var (
ValidateNonEmpty = validators.Regexp{Pattern: "[^\\s]+"}
)
// Dialogs
const (
DialogValidate = "VALDLG"
DialogTitle = 2000
DialogStatic1 = 2001
DialogStatic2 = 2002
DialogEdit = 2003
DialogIcon = 2004
)

12
pkg/sec/passwd.go Normal file
View File

@ -0,0 +1,12 @@
package sec
import (
"crypto/sha1"
"encoding/base64"
)
// EncryptPassword returns a Base64-encoded string of the hashed password.
func EncryptPassword(password string) string {
hashed := sha1.Sum([]byte(password))
return base64.StdEncoding.EncodeToString(hashed[:])
}

11
pkg/sec/passwd_test.go Normal file
View File

@ -0,0 +1,11 @@
package sec
import "testing"
func TestEncryptPassword(t *testing.T) {
output := EncryptPassword("123456")
expected := "fEqNCco3Yq9h5ZUglD3CZJT4lBs="
if output != expected {
t.Errorf("Expected: %v, got: %v", expected, output)
}
}

174
pkg/util/file.go Normal file
View File

@ -0,0 +1,174 @@
package util
import (
"archive/zip"
"bufio"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
// SplitExt splits the path into base name and file extension
func SplitExt(path string) (string, string) {
if path == "" {
return "", ""
}
fileName := filepath.Base(path)
ext := filepath.Ext(path)
return strings.TrimSuffix(fileName, ext), ext
}
// FileExists checks whether the given path is a file
func FileExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return !info.IsDir()
}
// FindLogFiles returns the files and dates archived by date
func FindLogFiles(path string) ([]string, []time.Time, error) {
if path == "" || path == "console" {
return nil, nil, os.ErrInvalid
}
fileDir, fileName := filepath.Split(path)
baseName, ext := SplitExt(fileName)
pattern := regexp.MustCompile(`^\.\d{4}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])-([0-1][0-9]|2[0-3])([0-5][0-9])([0-5][0-9])$`)
if fileDir == "" {
fileDir = "."
}
files, err := os.ReadDir(fileDir)
if err != nil {
return nil, nil, err
}
logs := []string{filepath.Clean(path)}
dates := []time.Time{{}}
for _, file := range files {
if strings.HasPrefix(file.Name(), baseName) && strings.HasSuffix(file.Name(), ext) {
tailPart := strings.TrimPrefix(file.Name(), baseName)
datePart := strings.TrimSuffix(tailPart, ext)
if pattern.MatchString(datePart) {
if date, err := time.ParseInLocation("20060102-150405", datePart[1:], time.Local); err == nil {
logs = append(logs, filepath.Join(fileDir, file.Name()))
dates = append(dates, date)
}
}
}
}
return logs, dates, nil
}
// DeleteFiles removes the given file list ignoring errors
func DeleteFiles(files []string) {
for _, file := range files {
os.Remove(file)
}
}
// ReadFileLines reads the last n lines in a file starting at a given offset
func ReadFileLines(path string, offset int64, n int) ([]string, int, int64, error) {
file, err := os.Open(path)
if err != nil {
return nil, -1, 0, err
}
defer file.Close()
_, err = file.Seek(offset, io.SeekStart)
if err != nil {
return nil, -1, 0, err
}
reader := bufio.NewReader(file)
var line string
lines := make([]string, 0)
i := -1
for {
line, err = reader.ReadString('\n')
if err != nil {
break
}
if n < 0 || len(lines) < n {
lines = append(lines, line)
} else {
i = (i + 1) % n
lines[i] = line
}
}
offset, err = file.Seek(0, io.SeekCurrent)
if err != nil {
return nil, -1, 0, err
}
if i >= 0 {
i = (i + 1) % n
}
return lines, i, offset, nil
}
// ZipFiles compresses the given file list to a zip file
func ZipFiles(filename string, files map[string]string) error {
newZipFile, err := os.Create(filename)
if err != nil {
return err
}
defer newZipFile.Close()
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// Add files to zip
for src, dst := range files {
if err = addFileToZip(zipWriter, src, dst); err != nil {
return err
}
}
return nil
}
func addFileToZip(zipWriter *zip.Writer, src, dst string) error {
fileToZip, err := os.Open(src)
if err != nil {
return err
}
defer fileToZip.Close()
info, err := fileToZip.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.Base(dst)
// Change to deflate to gain better compression
header.Method = zip.Deflate
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, fileToZip)
return err
}
// IsDirectory determines if a file represented by `path` is a directory or not
func IsDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, err
}
return fileInfo.IsDir(), err
}
// FileNameWithoutExt returns the last element of path without the file extension.
func FileNameWithoutExt(path string) string {
if path == "" {
return ""
}
return strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
}

68
pkg/util/file_test.go Normal file
View File

@ -0,0 +1,68 @@
package util
import (
"os"
"reflect"
"testing"
"time"
)
func TestSplitExt(t *testing.T) {
tests := []struct {
input string
expectedName string
expectedExt string
}{
{input: "C:\\test\\a.ini", expectedName: "a", expectedExt: ".ini"},
{input: "b.exe", expectedName: "b", expectedExt: ".exe"},
{input: "c", expectedName: "c", expectedExt: ""},
{input: "", expectedName: "", expectedExt: ""},
}
for i, test := range tests {
name, ext := SplitExt(test.input)
if name != test.expectedName {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expectedName, name)
}
if ext != test.expectedExt {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expectedExt, ext)
}
}
}
func TestFindLogFiles(t *testing.T) {
tests := []struct {
create []string
expectedFiles []string
expectedDates []time.Time
}{
{
create: []string{"example.log", "example.20230320-000000.log", "example.20230321-010203.log", "example.2023-03-21.log"},
expectedFiles: []string{"example.log", "example.20230320-000000.log", "example.20230321-010203.log"},
expectedDates: []time.Time{
{},
time.Date(2023, 3, 20, 0, 0, 0, 0, time.Local),
time.Date(2023, 3, 21, 1, 2, 3, 0, time.Local),
},
},
}
if err := os.MkdirAll("testdata", 0750); err != nil {
t.Fatal(err)
}
os.Chdir("testdata")
for i, test := range tests {
for _, f := range test.create {
os.WriteFile(f, []byte("test"), 0666)
}
logs, dates, err := FindLogFiles(test.create[0])
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(logs, test.expectedFiles) {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expectedFiles, logs)
}
if !reflect.DeepEqual(dates, test.expectedDates) {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expectedDates, dates)
}
}
}

90
pkg/util/misc.go Normal file
View File

@ -0,0 +1,90 @@
package util
import (
"fmt"
"reflect"
"strings"
)
// PruneByTag returns a copy of "in" that only contains fields with the given tag and value
func PruneByTag(in interface{}, value string, tag string) (interface{}, error) {
inValue := reflect.ValueOf(in)
ret := reflect.New(inValue.Type()).Elem()
if err := prune(inValue, ret, value, tag); err != nil {
return nil, err
}
return ret.Interface(), nil
}
func prune(inValue reflect.Value, ret reflect.Value, value string, tag string) error {
switch inValue.Kind() {
case reflect.Ptr:
if inValue.IsNil() {
return nil
}
if ret.IsNil() {
// init ret and go to next level
ret.Set(reflect.New(inValue.Type().Elem()))
}
return prune(inValue.Elem(), ret.Elem(), value, tag)
case reflect.Struct:
var fValue reflect.Value
var fRet reflect.Value
// search tag that has key equal to value
for i := 0; i < inValue.NumField(); i++ {
f := inValue.Type().Field(i)
if key, ok := f.Tag.Lookup(tag); ok {
if key == "*" || key == value {
fValue = inValue.Field(i)
fRet = ret.Field(i)
fRet.Set(fValue)
}
}
}
}
return nil
}
func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string {
m := make(map[string]string)
for key, value := range set {
if strings.HasPrefix(key, prefix) {
m[strings.TrimPrefix(key, prefix)] = value
}
}
if len(m) == 0 {
return nil
}
return m
}
// MoveSlice moves the element s[i] to index j in s.
func MoveSlice[S ~[]E, E any](s S, i, j int) {
x := s[i]
if i < j {
copy(s[i:j], s[i+1:j+1])
} else if i > j {
copy(s[j+1:i+1], s[j:i])
}
s[j] = x
}
// ByteCountIEC converts a size in bytes to a human-readable string in IEC (binary) format.
func ByteCountIEC(b int64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB",
float64(b)/float64(div), "KMGTPE"[exp])
}

29
pkg/util/misc_test.go Normal file
View File

@ -0,0 +1,29 @@
package util
import "testing"
type tagTest struct {
Tag string
Name string `t1:"true" t2:"true"`
Age int `t2:"true"`
}
func TestPruneByTag(t *testing.T) {
tests := []struct {
input tagTest
expected tagTest
}{
{input: tagTest{Tag: "t1", Name: "John", Age: 34}, expected: tagTest{Name: "John"}},
{input: tagTest{Tag: "t2", Name: "Ben", Age: 20}, expected: tagTest{Name: "Ben", Age: 20}},
{input: tagTest{Name: "Mary", Age: 50}, expected: tagTest{}},
}
for i, test := range tests {
output, err := PruneByTag(test.input, "true", test.input.Tag)
if err != nil {
t.Fatalf("Test %d: expected no error but found one for input %v, got: %v", i, test.input, err)
}
if output != test.expected {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expected, output)
}
}
}

160
pkg/util/net.go Normal file
View File

@ -0,0 +1,160 @@
package util
import (
"context"
"errors"
"fmt"
"io"
"mime"
"net/http"
"path"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
var (
modIPHelp = syscall.NewLazyDLL("iphlpapi.dll")
procGetExtendedTcpTable = modIPHelp.NewProc("GetExtendedTcpTable")
procGetExtendedUdpTable = modIPHelp.NewProc("GetExtendedUdpTable")
)
// DownloadFile downloads a file from the given url
func DownloadFile(ctx context.Context, url string) (filename, mediaType string, data []byte, err error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return
}
client := http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("bad status: %s", resp.Status)
return
}
// Use the filename in header
if cd := resp.Header.Get("Content-Disposition"); cd != "" {
if _, params, err := mime.ParseMediaType(cd); err == nil {
filename = params["filename"]
}
}
// Use the base filename part of the URL
if filename == "" {
filename = path.Base(resp.Request.URL.Path)
}
if mediaType, _, err = mime.ParseMediaType(resp.Header.Get("Content-Type")); err == nil {
data, err = io.ReadAll(resp.Body)
return filename, mediaType, data, err
} else {
return "", "", nil, err
}
}
//nolint:unused
type mibTCPRowOwnerPid struct {
dwState uint32
dwLocalAddr uint32
dwLocalPort uint32
dwRemoteAddr uint32
dwRemotePort uint32
dwOwningPid uint32
}
//nolint:unused
type mibTCP6RowOwnerPid struct {
ucLocalAddr [16]byte
dwLocalScopeId uint32
dwLocalPort uint32
ucRemoteAddr [16]byte
dwRemoteScopeId uint32
dwRemotePort uint32
dwState uint32
dwOwningPid uint32
}
//nolint:unused
type mibUDPRowOwnerPid struct {
dwLocalAddr uint32
dwLocalPort uint32
dwOwningPid uint32
}
//nolint:unused
type mibUDP6RowOwnerPid struct {
ucLocalAddr [16]byte
dwLocalScopeId uint32
dwLocalPort uint32
dwOwningPid uint32
}
type mibTableOwnerPid[T any] struct {
dwNumEntries uint32
table [1]T
}
// countConnections returns the number of IPv4 and IPv6 connections that match the given filter.
// - https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedtcptable
// - https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getextendedudptable
func countConnections[R4, R6 any](proc *syscall.LazyProc, tableClass uintptr, filter4 func(R4) bool, filter6 func(R6) bool) (count int) {
var size uint32
var buf []byte
getTable := func(af uintptr) bool {
for {
var pTable *byte
if len(buf) > 0 {
pTable = &buf[0]
}
ret, _, _ := proc.Call(uintptr(unsafe.Pointer(pTable)), uintptr(unsafe.Pointer(&size)), 0, af, tableClass, 0)
if ret != 0 {
if errors.Is(syscall.Errno(ret), syscall.ERROR_INSUFFICIENT_BUFFER) {
buf = make([]byte, int(size))
continue
}
return false
}
return true
}
}
if getTable(windows.AF_INET) {
table := (*mibTableOwnerPid[R4])(unsafe.Pointer(&buf[0]))
for _, conn := range unsafe.Slice(&table.table[0], table.dwNumEntries) {
if filter4(conn) {
count++
}
}
}
if getTable(windows.AF_INET6) {
table := (*mibTableOwnerPid[R6])(unsafe.Pointer(&buf[0]))
for _, conn := range unsafe.Slice(&table.table[0], table.dwNumEntries) {
if filter6(conn) {
count++
}
}
}
return
}
// CountTCPConnections returns the number of connected TCP endpoints for a given process.
func CountTCPConnections(pid uint32) int {
return countConnections(procGetExtendedTcpTable, 4, func(r4 mibTCPRowOwnerPid) bool {
return r4.dwOwningPid == pid
}, func(r6 mibTCP6RowOwnerPid) bool {
return r6.dwOwningPid == pid
})
}
// CountUDPConnections returns the number of UDP endpoints for a given process.
func CountUDPConnections(pid uint32) int {
return countConnections(procGetExtendedUdpTable, 1, func(r4 mibUDPRowOwnerPid) bool {
return r4.dwOwningPid == pid
}, func(r6 mibUDP6RowOwnerPid) bool {
return r6.dwOwningPid == pid
})
}

36
pkg/util/strings.go Normal file
View File

@ -0,0 +1,36 @@
package util
import (
"crypto/rand"
"encoding/hex"
"strings"
"unicode/utf8"
)
// GetOrElse returns the given string if it's non-empty, or returns the default string.
func GetOrElse(s string, def string) string {
if strings.TrimSpace(s) != "" {
return s
}
return def
}
// RuneSizeInString returns a slice of each character's size in the given string
func RuneSizeInString(s string) []int {
sizes := make([]int, 0)
for len(s) > 0 {
_, size := utf8.DecodeRuneInString(s)
sizes = append(sizes, size)
s = s[size:]
}
return sizes
}
// RandToken generates a random hex value.
func RandToken(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}

33
pkg/util/strings_test.go Normal file
View File

@ -0,0 +1,33 @@
package util
import (
"reflect"
"testing"
)
func TestGetOrElse(t *testing.T) {
tests := []struct {
input string
def string
expected string
}{
{input: "abc", def: "def", expected: "abc"},
{input: "", def: "def", expected: "def"},
{input: " ", def: "def", expected: "def"},
}
for i, test := range tests {
output := GetOrElse(test.input, test.def)
if output != test.expected {
t.Errorf("Test %d: expected: %v, got: %v", i, test.expected, output)
}
}
}
func TestRuneSizeInString(t *testing.T) {
str := "Hello, 世界"
expected := []int{1, 1, 1, 1, 1, 1, 1, 3, 3}
output := RuneSizeInString(str)
if !reflect.DeepEqual(output, expected) {
t.Errorf("Expected: %v, got: %v", expected, output)
}
}

Some files were not shown because too many files have changed in this diff Show More