init
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/trojanZ.iml" filepath="$PROJECT_DIR$/trojanZ.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: #github页面手动触发
|
||||||
|
push:
|
||||||
|
tags: [ 'v*' ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# github.repository as <account>/<repo>
|
||||||
|
IMAGE_NAME: jrohy/trojan
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
buildx:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Get latest tag
|
||||||
|
uses: oprypin/find-latest-tag@v1
|
||||||
|
id: octokit # The step ID to refer to later.
|
||||||
|
with:
|
||||||
|
repository: ${{ github.repository }} # The repository to scan.
|
||||||
|
|
||||||
|
# https://github.com/docker/login-action
|
||||||
|
- name: Login
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
# https://github.com/docker/build-push-action
|
||||||
|
- name: Build & Push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./asset/Dockerfile
|
||||||
|
platforms: linux/amd64,linux/arm64/v8
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.IMAGE_NAME }}:${{ steps.octokit.outputs.tag }}
|
||||||
|
${{ env.IMAGE_NAME }}:latest
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
.vscode
|
||||||
|
.idea
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
{project} Copyright (C) {year} {fullname}
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# trojan
|
||||||
|

|
||||||
|

|
||||||
|
[](https://goreportcard.com/report/github.com/Jrohy/trojan)
|
||||||
|
[](https://img.shields.io/github/downloads/Jrohy/trojan/total.svg)
|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
|
|
||||||
|
trojan多用户管理部署程序
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
- 在线web页面和命令行两种方式管理trojan多用户
|
||||||
|
- 启动 / 停止 / 重启 trojan 服务端
|
||||||
|
- 支持流量统计和流量限制
|
||||||
|
- 命令行模式管理, 支持命令补全
|
||||||
|
- 集成acme.sh证书申请
|
||||||
|
- 生成客户端配置文件
|
||||||
|
- 在线实时查看trojan日志
|
||||||
|
- 在线trojan和trojan-go随时切换
|
||||||
|
- 支持trojan://分享链接和二维码分享(仅限web页面)
|
||||||
|
- 支持转化为clash订阅地址并导入到[clash_for_windows](https://github.com/Fndroid/clash_for_windows_pkg/releases)(仅限web页面)
|
||||||
|
- 限制用户使用期限
|
||||||
|
|
||||||
|
## 安装方式
|
||||||
|
*trojan使用请提前准备好服务器可用的域名*
|
||||||
|
|
||||||
|
### a. 一键脚本安装
|
||||||
|
```
|
||||||
|
#安装/更新
|
||||||
|
source <(curl -sL https://git.io/trojan-install)
|
||||||
|
|
||||||
|
#卸载
|
||||||
|
source <(curl -sL https://git.io/trojan-install) --remove
|
||||||
|
|
||||||
|
```
|
||||||
|
安装完后输入'trojan'可进入管理程序
|
||||||
|
浏览器访问 https://域名 可在线web页面管理trojan用户
|
||||||
|
前端页面源码地址: [trojan-web](https://github.com/Jrohy/trojan-web)
|
||||||
|
|
||||||
|
### b. docker运行
|
||||||
|
1. 安装mysql
|
||||||
|
|
||||||
|
因为mariadb内存使用比mysql至少减少一半, 所以推荐使用mariadb数据库
|
||||||
|
```
|
||||||
|
docker run --name trojan-mariadb --restart=always -p 3306:3306 -v /home/mariadb:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=trojan -e MYSQL_ROOT_HOST=% -e MYSQL_DATABASE=trojan -d mariadb:10.2
|
||||||
|
```
|
||||||
|
端口和root密码以及持久化目录都可以改成其他的
|
||||||
|
|
||||||
|
2. 安装trojan
|
||||||
|
```
|
||||||
|
docker run -it -d --name trojan --net=host --restart=always --privileged jrohy/trojan init
|
||||||
|
```
|
||||||
|
运行完后进入容器 `docker exec -it trojan bash`, 然后输入'trojan'即可进行初始化安装
|
||||||
|
|
||||||
|
启动web服务: `systemctl start trojan-web`
|
||||||
|
|
||||||
|
设置自启动: `systemctl enable trojan-web`
|
||||||
|
|
||||||
|
更新管理程序: `source <(curl -sL https://git.io/trojan-install)`
|
||||||
|
|
||||||
|
## 运行截图
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 命令行
|
||||||
|
```
|
||||||
|
Usage:
|
||||||
|
trojan [flags]
|
||||||
|
trojan [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
add 添加用户
|
||||||
|
clean 清空指定用户流量
|
||||||
|
completion 自动命令补全(支持bash和zsh)
|
||||||
|
del 删除用户
|
||||||
|
help Help about any command
|
||||||
|
info 用户信息列表
|
||||||
|
log 查看trojan日志
|
||||||
|
port 修改trojan端口
|
||||||
|
restart 重启trojan
|
||||||
|
start 启动trojan
|
||||||
|
status 查看trojan状态
|
||||||
|
stop 停止trojan
|
||||||
|
tls 证书安装
|
||||||
|
update 更新trojan
|
||||||
|
updateWeb 更新trojan管理程序
|
||||||
|
version 显示版本号
|
||||||
|
import [path] 导入sql文件
|
||||||
|
export [path] 导出sql文件
|
||||||
|
web 以web方式启动
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for trojan
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
安装完trojan后强烈建议开启BBR等加速: [one_click_script](https://github.com/jinwyp/one_click_script)
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
感谢JetBrains提供的免费GoLand
|
||||||
|
[](https://jb.gg/OpenSource)
|
||||||
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
FROM --platform=$TARGETPLATFORM centos:7
|
||||||
|
|
||||||
|
LABEL maintainer="Jrohy <euvkzx@Jrohy.com>"
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ARG VERSION_CHECK="https://api.github.com/repos/Jrohy/trojan/releases/latest"
|
||||||
|
|
||||||
|
ARG DOWNLAOD_URL="https://github.com/Jrohy/trojan/releases/download/"
|
||||||
|
|
||||||
|
ARG SERVICE_URL="https://raw.githubusercontent.com/Jrohy/trojan/master/asset/trojan-web.service"
|
||||||
|
|
||||||
|
ARG SYSTEMCTL_URL="https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl.py"
|
||||||
|
|
||||||
|
RUN yum install socat bash-completion crontabs iptables openssl unzip -y && \
|
||||||
|
LATEST_VERSION=`curl -H 'Cache-Control: no-cache' -s "$VERSION_CHECK" | grep 'tag_name' | cut -d\" -f4` && \
|
||||||
|
[[ $TARGETARCH =~ "arm64" ]] && ARCH="arm64" || ARCH="amd64" && \
|
||||||
|
curl -fL "$DOWNLAOD_URL/$LATEST_VERSION/trojan-linux-$ARCH" -o /usr/local/bin/trojan && \
|
||||||
|
curl -L $SERVICE_URL -o /etc/systemd/system/trojan-web.service && \
|
||||||
|
curl -L $SYSTEMCTL_URL -o /usr/bin/systemctl && \
|
||||||
|
chmod +x /usr/local/bin/trojan /usr/bin/systemctl && \
|
||||||
|
echo "source <(trojan completion bash)" >> ~/.bashrc && \
|
||||||
|
yum clean all
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package asset
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed trojan-install.sh client.json clash-rules.yaml
|
||||||
|
var f embed.FS
|
||||||
|
|
||||||
|
// GetAsset 获取资源[]byte
|
||||||
|
func GetAsset(name string) []byte {
|
||||||
|
data, _ := f.ReadFile(name)
|
||||||
|
return data
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# Source: https://github.com/Loyalsoldier/clash-rules
|
||||||
|
rules:
|
||||||
|
- RULE-SET,applications,DIRECT
|
||||||
|
- DOMAIN,clash.razord.top,DIRECT
|
||||||
|
- DOMAIN,yacd.haishan.me,DIRECT
|
||||||
|
- RULE-SET,private,DIRECT
|
||||||
|
- RULE-SET,reject,REJECT
|
||||||
|
- RULE-SET,icloud,DIRECT
|
||||||
|
- RULE-SET,apple,DIRECT
|
||||||
|
- RULE-SET,google,DIRECT
|
||||||
|
- RULE-SET,proxy,PROXY
|
||||||
|
- RULE-SET,direct,DIRECT
|
||||||
|
- RULE-SET,lancidr,DIRECT
|
||||||
|
- RULE-SET,cncidr,DIRECT
|
||||||
|
- RULE-SET,telegramcidr,PROXY
|
||||||
|
- GEOIP,LAN,DIRECT
|
||||||
|
- GEOIP,CN,DIRECT
|
||||||
|
- MATCH,PROXY
|
||||||
|
|
||||||
|
rule-providers:
|
||||||
|
reject:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/reject.txt"
|
||||||
|
path: ./ruleset/reject.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
icloud:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/icloud.txt"
|
||||||
|
path: ./ruleset/icloud.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
apple:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/apple.txt"
|
||||||
|
path: ./ruleset/apple.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
google:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/google.txt"
|
||||||
|
path: ./ruleset/google.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
proxy:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/proxy.txt"
|
||||||
|
path: ./ruleset/proxy.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
direct:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/direct.txt"
|
||||||
|
path: ./ruleset/direct.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
private:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/private.txt"
|
||||||
|
path: ./ruleset/private.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
gfw:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/gfw.txt"
|
||||||
|
path: ./ruleset/gfw.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
greatfire:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/greatfire.txt"
|
||||||
|
path: ./ruleset/greatfire.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
tld-not-cn:
|
||||||
|
type: http
|
||||||
|
behavior: domain
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/tld-not-cn.txt"
|
||||||
|
path: ./ruleset/tld-not-cn.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
telegramcidr:
|
||||||
|
type: http
|
||||||
|
behavior: ipcidr
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/telegramcidr.txt"
|
||||||
|
path: ./ruleset/telegramcidr.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
cncidr:
|
||||||
|
type: http
|
||||||
|
behavior: ipcidr
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/cncidr.txt"
|
||||||
|
path: ./ruleset/cncidr.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
lancidr:
|
||||||
|
type: http
|
||||||
|
behavior: ipcidr
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/lancidr.txt"
|
||||||
|
path: ./ruleset/lancidr.yaml
|
||||||
|
interval: 86400
|
||||||
|
|
||||||
|
applications:
|
||||||
|
type: http
|
||||||
|
behavior: classical
|
||||||
|
url: "https://cdn.jsdelivr.net/gh/Loyalsoldier/clash-rules@release/applications.txt"
|
||||||
|
path: ./ruleset/applications.yaml
|
||||||
|
interval: 86400
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"run_type": "client",
|
||||||
|
"local_addr": "127.0.0.1",
|
||||||
|
"local_port": 1080,
|
||||||
|
"remote_addr": "example.com",
|
||||||
|
"remote_port": 443,
|
||||||
|
"password": [
|
||||||
|
"password1"
|
||||||
|
],
|
||||||
|
"log_level": 1,
|
||||||
|
"ssl": {
|
||||||
|
"verify": true,
|
||||||
|
"verify_hostname": true,
|
||||||
|
"cert": "",
|
||||||
|
"cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA",
|
||||||
|
"cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",
|
||||||
|
"sni": "",
|
||||||
|
"alpn": [
|
||||||
|
"h2",
|
||||||
|
"http/1.1"
|
||||||
|
],
|
||||||
|
"reuse_session": true,
|
||||||
|
"session_ticket": false,
|
||||||
|
"curves": ""
|
||||||
|
},
|
||||||
|
"tcp": {
|
||||||
|
"no_delay": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"reuse_port": false,
|
||||||
|
"fast_open": false,
|
||||||
|
"fast_open_qlen": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="120.1px" height="130.2px" viewBox="0 0 120.1 130.2" style="enable-background:new 0 0 120.1 130.2;" xml:space="preserve"
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<linearGradient id="XMLID_2_" gradientUnits="userSpaceOnUse" x1="31.8412" y1="120.5578" x2="110.2402" y2="73.24">
|
||||||
|
<stop offset="0" style="stop-color:#FCEE39"/>
|
||||||
|
<stop offset="1" style="stop-color:#F37B3D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path id="XMLID_3041_" style="fill:url(#XMLID_2_);" d="M118.6,71.8c0.9-0.8,1.4-1.9,1.5-3.2c0.1-2.6-1.8-4.7-4.4-4.9
|
||||||
|
c-1.2-0.1-2.4,0.4-3.3,1.1l0,0l-83.8,45.9c-1.9,0.8-3.6,2.2-4.7,4.1c-2.9,4.8-1.3,11,3.6,13.9c3.4,2,7.5,1.8,10.7-0.2l0,0l0,0
|
||||||
|
c0.2-0.2,0.5-0.3,0.7-0.5l78-54.8C117.3,72.9,118.4,72.1,118.6,71.8L118.6,71.8L118.6,71.8z"/>
|
||||||
|
<linearGradient id="XMLID_3_" gradientUnits="userSpaceOnUse" x1="48.3607" y1="6.9083" x2="119.9179" y2="69.5546">
|
||||||
|
<stop offset="0" style="stop-color:#EF5A6B"/>
|
||||||
|
<stop offset="0.57" style="stop-color:#F26F4E"/>
|
||||||
|
<stop offset="1" style="stop-color:#F37B3D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path id="XMLID_3049_" style="fill:url(#XMLID_3_);" d="M118.8,65.1L118.8,65.1L55,2.5C53.6,1,51.6,0,49.3,0
|
||||||
|
c-4.3,0-7.7,3.5-7.7,7.7v0c0,2.1,0.8,3.9,2.1,5.3l0,0l0,0c0.4,0.4,0.8,0.7,1.2,1l67.4,57.7l0,0c0.8,0.7,1.8,1.2,3,1.3
|
||||||
|
c2.6,0.1,4.7-1.8,4.9-4.4C120.2,67.3,119.7,66,118.8,65.1z"/>
|
||||||
|
<linearGradient id="XMLID_4_" gradientUnits="userSpaceOnUse" x1="52.9467" y1="63.6407" x2="10.5379" y2="37.1562">
|
||||||
|
<stop offset="0" style="stop-color:#7C59A4"/>
|
||||||
|
<stop offset="0.3852" style="stop-color:#AF4C92"/>
|
||||||
|
<stop offset="0.7654" style="stop-color:#DC4183"/>
|
||||||
|
<stop offset="0.957" style="stop-color:#ED3D7D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path id="XMLID_3042_" style="fill:url(#XMLID_4_);" d="M57.1,59.5C57,59.5,17.7,28.5,16.9,28l0,0l0,0c-0.6-0.3-1.2-0.6-1.8-0.9
|
||||||
|
c-5.8-2.2-12.2,0.8-14.4,6.6c-1.9,5.1,0.2,10.7,4.6,13.4l0,0l0,0C6,47.5,6.6,47.8,7.3,48c0.4,0.2,45.4,18.8,45.4,18.8l0,0
|
||||||
|
c1.8,0.8,3.9,0.3,5.1-1.2C59.3,63.7,59,61,57.1,59.5z"/>
|
||||||
|
<linearGradient id="XMLID_5_" gradientUnits="userSpaceOnUse" x1="52.1736" y1="3.7019" x2="10.7706" y2="37.8971">
|
||||||
|
<stop offset="0" style="stop-color:#EF5A6B"/>
|
||||||
|
<stop offset="0.364" style="stop-color:#EE4E72"/>
|
||||||
|
<stop offset="1" style="stop-color:#ED3D7D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path id="XMLID_3057_" style="fill:url(#XMLID_5_);" d="M49.3,0c-1.7,0-3.3,0.6-4.6,1.5L4.9,28.3c-0.1,0.1-0.2,0.1-0.2,0.2l-0.1,0
|
||||||
|
l0,0c-1.7,1.2-3.1,3-3.9,5.1C-1.5,39.4,1.5,45.9,7.3,48c3.6,1.4,7.5,0.7,10.4-1.4l0,0l0,0c0.7-0.5,1.3-1,1.8-1.6l34.6-31.2l0,0
|
||||||
|
c1.8-1.4,3-3.6,3-6.1v0C57.1,3.5,53.6,0,49.3,0z"/>
|
||||||
|
<g id="XMLID_3008_">
|
||||||
|
<rect id="XMLID_3033_" x="34.6" y="37.4" style="fill:#000000;" width="51" height="51"/>
|
||||||
|
<rect id="XMLID_3032_" x="39" y="78.8" style="fill:#FFFFFF;" width="19.1" height="3.2"/>
|
||||||
|
<g id="XMLID_3009_">
|
||||||
|
<path id="XMLID_3030_" style="fill:#FFFFFF;" d="M38.8,50.8l1.5-1.4c0.4,0.5,0.8,0.8,1.3,0.8c0.6,0,0.9-0.4,0.9-1.2l0-5.3l2.3,0
|
||||||
|
l0,5.3c0,1-0.3,1.8-0.8,2.3c-0.5,0.5-1.3,0.8-2.3,0.8C40.2,52.2,39.4,51.6,38.8,50.8z"/>
|
||||||
|
<path id="XMLID_3028_" style="fill:#FFFFFF;" d="M45.3,43.8l6.7,0v1.9l-4.4,0V47l4,0l0,1.8l-4,0l0,1.3l4.5,0l0,2l-6.7,0
|
||||||
|
L45.3,43.8z"/>
|
||||||
|
<path id="XMLID_3026_" style="fill:#FFFFFF;" d="M55,45.8l-2.5,0l0-2l7.3,0l0,2l-2.5,0l0,6.3l-2.3,0L55,45.8z"/>
|
||||||
|
<path id="XMLID_3022_" style="fill:#FFFFFF;" d="M39,54l4.3,0c1,0,1.8,0.3,2.3,0.7c0.3,0.3,0.5,0.8,0.5,1.4v0
|
||||||
|
c0,1-0.5,1.5-1.3,1.9c1,0.3,1.6,0.9,1.6,2v0c0,1.4-1.2,2.3-3.1,2.3l-4.3,0L39,54z M43.8,56.6c0-0.5-0.4-0.7-1-0.7l-1.5,0l0,1.5
|
||||||
|
l1.4,0C43.4,57.3,43.8,57.1,43.8,56.6L43.8,56.6z M43,59l-1.8,0l0,1.5H43c0.7,0,1.1-0.3,1.1-0.8v0C44.1,59.2,43.7,59,43,59z"/>
|
||||||
|
<path id="XMLID_3019_" style="fill:#FFFFFF;" d="M46.8,54l3.9,0c1.3,0,2.1,0.3,2.7,0.9c0.5,0.5,0.7,1.1,0.7,1.9v0
|
||||||
|
c0,1.3-0.7,2.1-1.7,2.6l2,2.9l-2.6,0l-1.7-2.5h-1l0,2.5l-2.3,0L46.8,54z M50.6,58c0.8,0,1.2-0.4,1.2-1v0c0-0.7-0.5-1-1.2-1
|
||||||
|
l-1.5,0v2H50.6z"/>
|
||||||
|
<path id="XMLID_3016_" style="fill:#FFFFFF;" d="M56.8,54l2.2,0l3.5,8.4l-2.5,0l-0.6-1.5l-3.2,0l-0.6,1.5l-2.4,0L56.8,54z
|
||||||
|
M58.8,59l-0.9-2.3L57,59L58.8,59z"/>
|
||||||
|
<path id="XMLID_3014_" style="fill:#FFFFFF;" d="M62.8,54l2.3,0l0,8.3l-2.3,0L62.8,54z"/>
|
||||||
|
<path id="XMLID_3012_" style="fill:#FFFFFF;" d="M65.7,54l2.1,0l3.4,4.4l0-4.4l2.3,0l0,8.3l-2,0L68,57.8l0,4.6l-2.3,0L65.7,54z"
|
||||||
|
/>
|
||||||
|
<path id="XMLID_3010_" style="fill:#FFFFFF;" d="M73.7,61.1l1.3-1.5c0.8,0.7,1.7,1,2.7,1c0.6,0,1-0.2,1-0.6v0
|
||||||
|
c0-0.4-0.3-0.5-1.4-0.8c-1.8-0.4-3.1-0.9-3.1-2.6v0c0-1.5,1.2-2.7,3.2-2.7c1.4,0,2.5,0.4,3.4,1.1l-1.2,1.6
|
||||||
|
c-0.8-0.5-1.6-0.8-2.3-0.8c-0.6,0-0.8,0.2-0.8,0.5v0c0,0.4,0.3,0.5,1.4,0.8c1.9,0.4,3.1,1,3.1,2.6v0c0,1.7-1.3,2.7-3.4,2.7
|
||||||
|
C76.1,62.5,74.7,62,73.7,61.1z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,200 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# source: https://github.com/trojan-gfw/trojan-quickstart
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# trojan: 0, trojan-go: 1
|
||||||
|
TYPE=0
|
||||||
|
|
||||||
|
INSTALL_VERSION=""
|
||||||
|
|
||||||
|
while [[ $# > 0 ]];do
|
||||||
|
KEY="$1"
|
||||||
|
case $KEY in
|
||||||
|
-v|--version)
|
||||||
|
INSTALL_VERSION="$2"
|
||||||
|
echo -e "prepare install $INSTALL_VERSION version..\n"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-g|--go)
|
||||||
|
TYPE=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift # past argument or value
|
||||||
|
done
|
||||||
|
#############################
|
||||||
|
|
||||||
|
function prompt() {
|
||||||
|
while true; do
|
||||||
|
read -p "$1 [y/N] " yn
|
||||||
|
case $yn in
|
||||||
|
[Yy] ) return 0;;
|
||||||
|
[Nn]|"" ) return 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $(id -u) != 0 ]]; then
|
||||||
|
echo Please run this script as root.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARCH=$(uname -m 2> /dev/null)
|
||||||
|
if [[ $ARCH != x86_64 && $ARCH != aarch64 ]];then
|
||||||
|
echo "not support $ARCH machine".
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ $TYPE == 0 && $ARCH != x86_64 ]];then
|
||||||
|
echo "trojan not support $ARCH machine"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $TYPE == 0 ]];then
|
||||||
|
CHECKVERSION="https://api.github.com/repos/trojan-gfw/trojan/releases/latest"
|
||||||
|
else
|
||||||
|
CHECKVERSION="https://api.github.com/repos/p4gefau1t/trojan-go/releases"
|
||||||
|
fi
|
||||||
|
NAME=trojan
|
||||||
|
if [[ -z $INSTALL_VERSION ]];then
|
||||||
|
VERSION=$(curl -H 'Cache-Control: no-cache' -s "$CHECKVERSION" | grep 'tag_name' | cut -d\" -f4 | sed 's/v//g' | head -n 1)
|
||||||
|
else
|
||||||
|
if [[ -z `curl -H 'Cache-Control: no-cache' -s "$CHECKVERSION"|grep 'tag_name'|grep $INSTALL_VERSION` ]];then
|
||||||
|
echo "no $INSTALL_VERSION version file!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
VERSION=`echo "$INSTALL_VERSION"|sed 's/v//g'`
|
||||||
|
fi
|
||||||
|
if [[ $TYPE == 0 ]];then
|
||||||
|
TARBALL="$NAME-$VERSION-linux-amd64.tar.xz"
|
||||||
|
DOWNLOADURL="https://github.com/trojan-gfw/$NAME/releases/download/v$VERSION/$TARBALL"
|
||||||
|
else
|
||||||
|
[[ $ARCH == x86_64 ]] && TARBALL="trojan-go-linux-amd64.zip" || TARBALL="trojan-go-linux-armv8.zip"
|
||||||
|
DOWNLOADURL="https://github.com/p4gefau1t/trojan-go/releases/download/v$VERSION/$TARBALL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMPDIR="$(mktemp -d)"
|
||||||
|
INSTALLPREFIX="/usr/bin/$NAME"
|
||||||
|
SYSTEMDPREFIX=/etc/systemd/system
|
||||||
|
|
||||||
|
BINARYPATH="$INSTALLPREFIX/$NAME"
|
||||||
|
CONFIGPATH="/usr/local/etc/$NAME/config.json"
|
||||||
|
SYSTEMDPATH="$SYSTEMDPREFIX/$NAME.service"
|
||||||
|
|
||||||
|
echo Creating $NAME install directory
|
||||||
|
mkdir -p $INSTALLPREFIX /usr/local/etc/$NAME
|
||||||
|
|
||||||
|
echo Entering temp directory $TMPDIR...
|
||||||
|
cd "$TMPDIR"
|
||||||
|
|
||||||
|
echo Downloading $NAME $VERSION...
|
||||||
|
curl -LO --progress-bar "$DOWNLOADURL" || wget -q --show-progress "$DOWNLOADURL"
|
||||||
|
|
||||||
|
echo Unpacking $NAME $VERSION...
|
||||||
|
if [[ $TYPE == 0 ]];then
|
||||||
|
tar xf "$TARBALL"
|
||||||
|
cd "$NAME"
|
||||||
|
else
|
||||||
|
if [[ -z `command -v unzip` ]];then
|
||||||
|
if [[ `command -v dnf` ]];then
|
||||||
|
dnf install unzip -y
|
||||||
|
elif [[ `command -v yum` ]];then
|
||||||
|
yum install unzip -y
|
||||||
|
elif [[ `command -v apt-get` ]];then
|
||||||
|
apt-get install unzip -y
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
unzip "$TARBALL"
|
||||||
|
mv trojan-go trojan
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Installing $NAME $VERSION to $BINARYPATH...
|
||||||
|
install -Dm755 "$NAME" "$BINARYPATH"
|
||||||
|
|
||||||
|
echo Installing $NAME server config to $CONFIGPATH...
|
||||||
|
if ! [[ -f "$CONFIGPATH" ]] || prompt "The server config already exists in $CONFIGPATH, overwrite?"; then
|
||||||
|
cat > "$CONFIGPATH" << EOF
|
||||||
|
{
|
||||||
|
"run_type": "server",
|
||||||
|
"local_addr": "0.0.0.0",
|
||||||
|
"local_port": 443,
|
||||||
|
"remote_addr": "127.0.0.1",
|
||||||
|
"remote_port": 80,
|
||||||
|
"password": [
|
||||||
|
"password1",
|
||||||
|
"password2"
|
||||||
|
],
|
||||||
|
"log_level": 1,
|
||||||
|
"ssl": {
|
||||||
|
"cert": "/path/to/certificate.crt",
|
||||||
|
"key": "/path/to/private.key",
|
||||||
|
"key_password": "",
|
||||||
|
"cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384",
|
||||||
|
"cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",
|
||||||
|
"prefer_server_cipher": true,
|
||||||
|
"alpn": [
|
||||||
|
"http/1.1"
|
||||||
|
],
|
||||||
|
"alpn_port_override": {
|
||||||
|
"h2": 81
|
||||||
|
},
|
||||||
|
"reuse_session": true,
|
||||||
|
"session_ticket": false,
|
||||||
|
"session_timeout": 600,
|
||||||
|
"plain_http_response": "",
|
||||||
|
"curves": "",
|
||||||
|
"dhparam": ""
|
||||||
|
},
|
||||||
|
"tcp": {
|
||||||
|
"prefer_ipv4": false,
|
||||||
|
"no_delay": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"reuse_port": false,
|
||||||
|
"fast_open": false,
|
||||||
|
"fast_open_qlen": 20
|
||||||
|
},
|
||||||
|
"mysql": {
|
||||||
|
"enabled": false,
|
||||||
|
"server_addr": "127.0.0.1",
|
||||||
|
"server_port": 3306,
|
||||||
|
"database": "trojan",
|
||||||
|
"username": "trojan",
|
||||||
|
"password": "",
|
||||||
|
"key": "",
|
||||||
|
"cert": "",
|
||||||
|
"ca": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
echo Skipping installing $NAME server config...
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$SYSTEMDPREFIX" ]]; then
|
||||||
|
echo Installing $NAME systemd service to $SYSTEMDPATH...
|
||||||
|
[[ $TYPE == 1 ]] && { NAME="trojan-go"; FLAG="-config"; }
|
||||||
|
cat > "$SYSTEMDPATH" << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=$NAME
|
||||||
|
After=network.target network-online.target nss-lookup.target mysql.service mariadb.service mysqld.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
StandardError=journal
|
||||||
|
ExecStart=$BINARYPATH $FLAG $CONFIGPATH
|
||||||
|
ExecReload=/bin/kill -HUP \$MAINPID
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
echo Reloading systemd daemon...
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Deleting temp directory $TMPDIR...
|
||||||
|
rm -rf "$TMPDIR"
|
||||||
|
|
||||||
|
echo Done!
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=trojan-web
|
||||||
|
Documentation=https://github.com/Jrohy/trojan
|
||||||
|
After=network.target network-online.target nss-lookup.target mysql.service mariadb.service mysqld.service docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
StandardError=journal
|
||||||
|
ExecStart=/usr/local/bin/trojan web
|
||||||
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
github_token=""
|
||||||
|
|
||||||
|
project="Jrohy/trojan"
|
||||||
|
|
||||||
|
#获取当前的这个脚本所在绝对路径
|
||||||
|
shell_path=$(cd `dirname $0`; pwd)
|
||||||
|
|
||||||
|
function uploadfile() {
|
||||||
|
file=$1
|
||||||
|
ctype=$(file -b --mime-type $file)
|
||||||
|
|
||||||
|
curl -H "Authorization: token ${github_token}" -H "Content-Type: ${ctype}" --data-binary @$file "https://uploads.github.com/repos/$project/releases/${release_id}/assets?name=$(basename $file)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload() {
|
||||||
|
file=$1
|
||||||
|
dgst=$1.dgst
|
||||||
|
openssl dgst -md5 $file | sed 's/([^)]*)//g' >> $dgst
|
||||||
|
openssl dgst -sha1 $file | sed 's/([^)]*)//g' >> $dgst
|
||||||
|
openssl dgst -sha256 $file | sed 's/([^)]*)//g' >> $dgst
|
||||||
|
openssl dgst -sha512 $file | sed 's/([^)]*)//g' >> $dgst
|
||||||
|
uploadfile $file
|
||||||
|
uploadfile $dgst
|
||||||
|
}
|
||||||
|
|
||||||
|
cd $shell_path
|
||||||
|
|
||||||
|
version=`git describe --tags $(git rev-list --tags --max-count=1)`
|
||||||
|
now=`TZ=Asia/Shanghai date "+%Y%m%d-%H%M"`
|
||||||
|
go_version=`go version|awk '{print $3,$4}'`
|
||||||
|
git_version=`git rev-parse HEAD`
|
||||||
|
ldflags="-w -s -X 'trojan/trojan.MVersion=$version' -X 'trojan/trojan.BuildDate=$now' -X 'trojan/trojan.GoVersion=$go_version' -X 'trojan/trojan.GitVersion=$git_version'"
|
||||||
|
|
||||||
|
GOOS=linux GOARCH=amd64 go build -ldflags "$ldflags" -o "result/trojan-linux-amd64" .
|
||||||
|
GOOS=linux GOARCH=arm64 go build -ldflags "$ldflags" -o "result/trojan-linux-arm64" .
|
||||||
|
|
||||||
|
if [[ $# == 0 ]];then
|
||||||
|
cd result
|
||||||
|
|
||||||
|
upload_item=($(ls -l|awk '{print $9}'|xargs -r))
|
||||||
|
|
||||||
|
curl -X POST -H "Authorization: token ${github_token}" -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/$project/releases -d '{"tag_name":"'$version'", "name":"'$version'"}'
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
release_id=`curl -H 'Cache-Control: no-cache' -s https://api.github.com/repos/$project/releases/latest|grep id|awk 'NR==1{print $2}'|sed 's/,//'`
|
||||||
|
|
||||||
|
for item in ${upload_item[@]}
|
||||||
|
do
|
||||||
|
upload $item
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "upload completed!"
|
||||||
|
|
||||||
|
cd $shell_path
|
||||||
|
|
||||||
|
rm -rf result
|
||||||
|
fi
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// addCmd represents the add command
|
||||||
|
var addCmd = &cobra.Command{
|
||||||
|
Use: "add",
|
||||||
|
Short: "添加用户",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.AddUser()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(addCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cleanCmd represents the clean command
|
||||||
|
var cleanCmd = &cobra.Command{
|
||||||
|
Use: "clean",
|
||||||
|
Short: "清空指定用户流量",
|
||||||
|
Long: `传入指定用户名来清空用户流量, 多个用户名空格隔开, 例如:
|
||||||
|
trojan clean zhangsan lisi
|
||||||
|
`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.CleanDataByName(args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(cleanCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// completionCmd represents the completion command
|
||||||
|
var completionCmd = &cobra.Command{
|
||||||
|
Use: "completion",
|
||||||
|
Short: "自动命令补全(支持bash和zsh)",
|
||||||
|
Long: `
|
||||||
|
支持bash和zsh的命令补全
|
||||||
|
a. bash环境添加下面命令到 ~/.bashrc
|
||||||
|
source <(trojan completion bash)
|
||||||
|
|
||||||
|
b. zsh环境添加以下命令到~/.zshrc
|
||||||
|
source <(trojan completion zsh)
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(completionCmd)
|
||||||
|
completionCmd.AddCommand(&cobra.Command{Use: "bash", Short: "bash命令补全", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
rootCmd.GenBashCompletion(os.Stdout)
|
||||||
|
}})
|
||||||
|
completionCmd.AddCommand(&cobra.Command{Use: "zsh", Short: "zsh命令补全", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
rootCmd.GenZshCompletion(os.Stdout)
|
||||||
|
}})
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// delCmd represents the del command
|
||||||
|
var delCmd = &cobra.Command{
|
||||||
|
Use: "del",
|
||||||
|
Short: "删除用户",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.DelUser()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(delCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// exportCmd represents the export command
|
||||||
|
var exportCmd = &cobra.Command{
|
||||||
|
Use: "export",
|
||||||
|
Short: "导出数据库sql文件",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.DumpSql(args[0]); err != nil {
|
||||||
|
fmt.Println(util.Red(err.Error()))
|
||||||
|
} else {
|
||||||
|
fmt.Println(util.Green("导出sql成功!"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(exportCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// importCmd represents the import command
|
||||||
|
var importCmd = &cobra.Command{
|
||||||
|
Use: "import",
|
||||||
|
Short: "导入数据库sql文件",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.ExecSql(args[0]); err != nil {
|
||||||
|
fmt.Println(util.Red(err.Error()))
|
||||||
|
} else {
|
||||||
|
fmt.Println(util.Green("导入sql成功!"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(importCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"trojan/trojan"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// infoCmd represents the info command
|
||||||
|
var infoCmd = &cobra.Command{
|
||||||
|
Use: "info",
|
||||||
|
Short: "用户信息列表",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.UserList()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(infoCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"trojan/util"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var line int
|
||||||
|
|
||||||
|
// logCmd represents the log command
|
||||||
|
var logCmd = &cobra.Command{
|
||||||
|
Use: "log",
|
||||||
|
Short: "查看trojan日志",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
util.Log("trojan", line)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logCmd.Flags().IntVarP(&line, "line", "n", 300, "查看日志行数")
|
||||||
|
rootCmd.AddCommand(logCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"trojan/trojan"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// portCmd represents the info command
|
||||||
|
var portCmd = &cobra.Command{
|
||||||
|
Use: "port",
|
||||||
|
Short: "修改trojan端口",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.ChangePort()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(portCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// restartCmd represents the restart command
|
||||||
|
var restartCmd = &cobra.Command{
|
||||||
|
Use: "restart",
|
||||||
|
Short: "重启trojan",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.Restart()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(restartCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/trojan"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rootCmd represents the base command when called without any subcommands
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "trojan",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
mainMenu()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func check() {
|
||||||
|
if !util.IsExists("/usr/local/etc/trojan/config.json") {
|
||||||
|
fmt.Println("本机未安装trojan, 正在自动安装...")
|
||||||
|
trojan.InstallTrojan("")
|
||||||
|
core.WritePassword(nil)
|
||||||
|
trojan.InstallTls()
|
||||||
|
trojan.InstallMysql()
|
||||||
|
util.SystemctlRestart("trojan-web")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mainMenu() {
|
||||||
|
check()
|
||||||
|
exit:
|
||||||
|
for {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println(util.Cyan("欢迎使用trojan管理程序"))
|
||||||
|
fmt.Println()
|
||||||
|
menuList := []string{"trojan管理", "用户管理", "安装管理", "web管理", "查看配置", "生成json"}
|
||||||
|
switch util.LoopInput("请选择: ", menuList, false) {
|
||||||
|
case 1:
|
||||||
|
trojan.ControlMenu()
|
||||||
|
case 2:
|
||||||
|
trojan.UserMenu()
|
||||||
|
case 3:
|
||||||
|
trojan.InstallMenu()
|
||||||
|
case 4:
|
||||||
|
trojan.WebMenu()
|
||||||
|
case 5:
|
||||||
|
trojan.UserList()
|
||||||
|
case 6:
|
||||||
|
trojan.GenClientJson()
|
||||||
|
default:
|
||||||
|
break exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// startCmd represents the start command
|
||||||
|
var startCmd = &cobra.Command{
|
||||||
|
Use: "start",
|
||||||
|
Short: "启动trojan",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.Start()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(startCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"trojan/trojan"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// statusCmd represents the status command
|
||||||
|
var statusCmd = &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
|
Short: "查看trojan状态",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.Status(true)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(statusCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stopCmd represents the stop command
|
||||||
|
var stopCmd = &cobra.Command{
|
||||||
|
Use: "stop",
|
||||||
|
Short: "停止trojan",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.Stop()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(stopCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tlsCmd represents the tls command
|
||||||
|
var tlsCmd = &cobra.Command{
|
||||||
|
Use: "tls",
|
||||||
|
Short: "证书安装",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
trojan.InstallTls()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(tlsCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// upgradeCmd represents the update command
|
||||||
|
var updateCmd = &cobra.Command{
|
||||||
|
Use: "update",
|
||||||
|
Short: "更新trojan",
|
||||||
|
Long: "可添加版本号更新特定版本, 比如'trojan update v0.10.0', 不添加版本号则安装最新版",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
version := ""
|
||||||
|
if len(args) == 1 {
|
||||||
|
version = args[0]
|
||||||
|
}
|
||||||
|
trojan.InstallTrojan(version)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(updateCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// updateWebCmd represents the update command
|
||||||
|
var updateWebCmd = &cobra.Command{
|
||||||
|
Use: "updateWeb",
|
||||||
|
Short: "更新trojan管理程序",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
util.RunWebShell("https://git.io/trojan-install")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(updateWebCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// upgradeCmd represents the update command
|
||||||
|
var upgradeCmd = &cobra.Command{
|
||||||
|
Use: "upgrade",
|
||||||
|
Short: "升级数据库和trojan配置文件",
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgradeConfig() {
|
||||||
|
domain, _ := core.GetValue("domain")
|
||||||
|
if domain == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
core.WriteDomain(domain)
|
||||||
|
trojan.Restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(upgradeCmd)
|
||||||
|
upgradeCmd.AddCommand(&cobra.Command{Use: "db", Short: "升级数据库", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := core.GetMysql().UpgradeDB(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
upgradeCmd.AddCommand(&cobra.Command{Use: "config", Short: "升级配置文件", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
upgradeConfig()
|
||||||
|
}})
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"trojan/trojan"
|
||||||
|
"trojan/util"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// versionCmd represents the Version command
|
||||||
|
var versionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "显示版本号",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
upTime := trojan.UpTime()
|
||||||
|
trojanVersion := trojan.Version()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Version: %s\n\n", util.Cyan(trojan.MVersion))
|
||||||
|
fmt.Printf("BuildDate: %s\n\n", util.Cyan(trojan.BuildDate))
|
||||||
|
fmt.Printf("GoVersion: %s\n\n", util.Cyan(trojan.GoVersion))
|
||||||
|
fmt.Printf("GitVersion: %s\n\n", util.Cyan(trojan.GitVersion))
|
||||||
|
fmt.Printf("TrojanVersion: %s\n\n", util.Cyan(trojanVersion))
|
||||||
|
fmt.Printf("TrojanUpTime: %s\n\n", util.Cyan(upTime))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(versionCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"trojan/util"
|
||||||
|
"trojan/web"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
ssl bool
|
||||||
|
timeout int
|
||||||
|
)
|
||||||
|
|
||||||
|
// webCmd represents the web command
|
||||||
|
var webCmd = &cobra.Command{
|
||||||
|
Use: "web",
|
||||||
|
Short: "以web方式启动",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
web.Start(host, port, timeout, ssl)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
webCmd.Flags().StringVarP(&host, "host", "", "0.0.0.0", "web服务监听地址")
|
||||||
|
webCmd.Flags().IntVarP(&port, "port", "p", 80, "web服务启动端口")
|
||||||
|
webCmd.Flags().BoolVarP(&ssl, "ssl", "", false, "web服务是否以https方式运行")
|
||||||
|
webCmd.Flags().IntVarP(&timeout, "timeout", "t", 120, "登录超时时间(min)")
|
||||||
|
webCmd.AddCommand(&cobra.Command{Use: "stop", Short: "停止trojan-web", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
util.SystemctlStop("trojan-web")
|
||||||
|
}})
|
||||||
|
webCmd.AddCommand(&cobra.Command{Use: "start", Short: "启动trojan-web", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
util.SystemctlStart("trojan-web")
|
||||||
|
}})
|
||||||
|
webCmd.AddCommand(&cobra.Command{Use: "restart", Short: "重启trojan-web", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
util.SystemctlRestart("trojan-web")
|
||||||
|
}})
|
||||||
|
webCmd.AddCommand(&cobra.Command{Use: "status", Short: "查看trojan-web状态", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
fmt.Println(util.SystemctlStatus("trojan-web"))
|
||||||
|
}})
|
||||||
|
webCmd.AddCommand(&cobra.Command{Use: "log", Short: "查看trojan-web日志", Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
util.Log("trojan-web", 300)
|
||||||
|
}})
|
||||||
|
rootCmd.AddCommand(webCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
// Config 结构体
|
||||||
|
type Config struct {
|
||||||
|
RunType string `json:"run_type"`
|
||||||
|
LocalAddr string `json:"local_addr"`
|
||||||
|
LocalPort int `json:"local_port"`
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
Password []string `json:"password"`
|
||||||
|
LogLevel int `json:"log_level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSL 结构体
|
||||||
|
type SSL struct {
|
||||||
|
Cert string `json:"cert"`
|
||||||
|
Cipher string `json:"cipher"`
|
||||||
|
CipherTls13 string `json:"cipher_tls13"`
|
||||||
|
Alpn []string `json:"alpn"`
|
||||||
|
ReuseSession bool `json:"reuse_session"`
|
||||||
|
SessionTicket bool `json:"session_ticket"`
|
||||||
|
Curves string `json:"curves"`
|
||||||
|
Sni string `json:"sni"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP 结构体
|
||||||
|
type TCP struct {
|
||||||
|
NoDelay bool `json:"no_delay"`
|
||||||
|
KeepAlive bool `json:"keep_alive"`
|
||||||
|
ReusePort bool `json:"reuse_port"`
|
||||||
|
FastOpen bool `json:"fast_open"`
|
||||||
|
FastOpenQlen int `json:"fast_open_qlen"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"trojan/asset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConfig 结构体
|
||||||
|
type ClientConfig struct {
|
||||||
|
Config
|
||||||
|
SSl ClientSSL `json:"ssl"`
|
||||||
|
Tcp ClientTCP `json:"tcp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientSSL 结构体
|
||||||
|
type ClientSSL struct {
|
||||||
|
SSL
|
||||||
|
Verify bool `json:"verify"`
|
||||||
|
VerifyHostname bool `json:"verify_hostname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientTCP 结构体
|
||||||
|
type ClientTCP struct {
|
||||||
|
TCP
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteClient 生成客户端json
|
||||||
|
func WriteClient(port int, password, domain, writePath string) bool {
|
||||||
|
data := asset.GetAsset("client.json")
|
||||||
|
config := ClientConfig{}
|
||||||
|
if err := json.Unmarshal(data, &config); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
config.RemoteAddr = domain
|
||||||
|
config.RemotePort = port
|
||||||
|
config.Password = []string{password}
|
||||||
|
outData, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(writePath, outData, 0644); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dbPath = "/var/lib/trojan-manager"
|
||||||
|
|
||||||
|
// GetValue 获取leveldb值
|
||||||
|
func GetValue(key string) (string, error) {
|
||||||
|
db, err := leveldb.OpenFile(dbPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
result, err := db.Get([]byte(key), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(result), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValue 设置leveldb值
|
||||||
|
func SetValue(key string, value string) error {
|
||||||
|
db, err := leveldb.OpenFile(dbPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return db.Put([]byte(key), []byte(value), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelValue 删除值
|
||||||
|
func DelValue(key string) error {
|
||||||
|
db, err := leveldb.OpenFile(dbPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
return db.Delete([]byte(key), nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
mysqlDriver "github.com/go-sql-driver/mysql"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
// mysql sql驱动
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mysql 结构体
|
||||||
|
type Mysql struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
ServerAddr string `json:"server_addr"`
|
||||||
|
ServerPort int `json:"server_port"`
|
||||||
|
Database string `json:"database"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Cafile string `json:"cafile"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User 用户表记录结构体
|
||||||
|
type User struct {
|
||||||
|
ID uint
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
EncryptPass string
|
||||||
|
Quota int64
|
||||||
|
Download uint64
|
||||||
|
Upload uint64
|
||||||
|
UseDays uint
|
||||||
|
ExpiryDate string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageQuery 分页查询的结构体
|
||||||
|
type PageQuery struct {
|
||||||
|
PageNum int
|
||||||
|
CurPage int
|
||||||
|
Total int
|
||||||
|
PageSize int
|
||||||
|
DataList []*User
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTableSql 创表sql
|
||||||
|
var CreateTableSql = `
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
username VARCHAR(64) NOT NULL,
|
||||||
|
password CHAR(56) NOT NULL,
|
||||||
|
passwordShow VARCHAR(255) NOT NULL,
|
||||||
|
quota BIGINT NOT NULL DEFAULT 0,
|
||||||
|
download BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
upload BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
useDays int(10) DEFAULT 0,
|
||||||
|
expiryDate char(10) DEFAULT '',
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
INDEX (password)
|
||||||
|
) DEFAULT CHARSET=utf8mb4;
|
||||||
|
`
|
||||||
|
|
||||||
|
// GetDB 获取mysql数据库连接
|
||||||
|
func (mysql *Mysql) GetDB() *sql.DB {
|
||||||
|
// 屏蔽mysql驱动包的日志输出
|
||||||
|
mysqlDriver.SetLogger(log.New(io.Discard, "", 0))
|
||||||
|
conn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", mysql.Username, mysql.Password, mysql.ServerAddr, mysql.ServerPort, mysql.Database)
|
||||||
|
db, err := sql.Open("mysql", conn)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTable 不存在trojan user表则自动创建
|
||||||
|
func (mysql *Mysql) CreateTable() {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
defer db.Close()
|
||||||
|
if _, err := db.Exec(CreateTableSql); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryUserList(db *sql.DB, sql string) ([]*User, error) {
|
||||||
|
var (
|
||||||
|
username string
|
||||||
|
encryptPass string
|
||||||
|
passShow string
|
||||||
|
download uint64
|
||||||
|
upload uint64
|
||||||
|
quota int64
|
||||||
|
id uint
|
||||||
|
useDays uint
|
||||||
|
expiryDate string
|
||||||
|
)
|
||||||
|
var userList []*User
|
||||||
|
rows, err := db.Query(sql)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
if err := rows.Scan(&id, &username, &encryptPass, &passShow, "a, &download, &upload, &useDays, &expiryDate); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userList = append(userList, &User{
|
||||||
|
ID: id,
|
||||||
|
Username: username,
|
||||||
|
Password: passShow,
|
||||||
|
EncryptPass: encryptPass,
|
||||||
|
Download: download,
|
||||||
|
Upload: upload,
|
||||||
|
Quota: quota,
|
||||||
|
UseDays: useDays,
|
||||||
|
ExpiryDate: expiryDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return userList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryUser(db *sql.DB, sql string) (*User, error) {
|
||||||
|
var (
|
||||||
|
username string
|
||||||
|
encryptPass string
|
||||||
|
passShow string
|
||||||
|
download uint64
|
||||||
|
upload uint64
|
||||||
|
quota int64
|
||||||
|
id uint
|
||||||
|
useDays uint
|
||||||
|
expiryDate string
|
||||||
|
)
|
||||||
|
row := db.QueryRow(sql)
|
||||||
|
if err := row.Scan(&id, &username, &encryptPass, &passShow, "a, &download, &upload, &useDays, &expiryDate); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &User{ID: id, Username: username, Password: passShow, EncryptPass: encryptPass, Download: download, Upload: upload, Quota: quota, UseDays: useDays, ExpiryDate: expiryDate}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser 创建Trojan用户
|
||||||
|
func (mysql *Mysql) CreateUser(username string, base64Pass string, originPass string) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
encryPass := sha256.Sum224([]byte(originPass))
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("INSERT INTO users(username, password, passwordShow, quota) VALUES ('%s', '%x', '%s', -1);", username, encryPass, base64Pass)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser 更新Trojan用户名和密码
|
||||||
|
func (mysql *Mysql) UpdateUser(id uint, username string, base64Pass string, originPass string) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
encryPass := sha256.Sum224([]byte(originPass))
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET username='%s', password='%x', passwordShow='%s' WHERE id=%d;", username, encryPass, base64Pass, id)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser 删除用户
|
||||||
|
func (mysql *Mysql) DeleteUser(id uint) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if userList, err := mysql.GetData(strconv.Itoa(int(id))); err != nil {
|
||||||
|
return err
|
||||||
|
} else if userList != nil && len(userList) == 0 {
|
||||||
|
return fmt.Errorf("不存在id为%d的用户", id)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("DELETE FROM users WHERE id=%d;", id)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonthlyResetData 设置了过期时间的用户,每月定时清空使用流量
|
||||||
|
func (mysql *Mysql) MonthlyResetData() error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
userList, err := queryUserList(db, "SELECT * FROM users WHERE useDays != 0 AND quota != 0")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, user := range userList {
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET download=0, upload=0 WHERE id=%d;", user.ID)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DailyCheckExpire 检查是否有过期,过期了设置流量上限为0
|
||||||
|
func (mysql *Mysql) DailyCheckExpire() (bool, error) {
|
||||||
|
needRestart := false
|
||||||
|
now := time.Now()
|
||||||
|
utc, err := time.LoadLocation("Asia/Shanghai")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
addDay, _ := time.ParseDuration("-24h")
|
||||||
|
yesterdayStr := now.Add(addDay).In(utc).Format("2006-01-02")
|
||||||
|
yesterday, _ := time.Parse("2006-01-02", yesterdayStr)
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return false, errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
userList, err := queryUserList(db, "SELECT * FROM users WHERE quota != 0")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, user := range userList {
|
||||||
|
if expireDate, err := time.Parse("2006-01-02", user.ExpiryDate); err == nil {
|
||||||
|
if yesterday.Sub(expireDate).Seconds() >= 0 {
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET quota=0 WHERE id=%d;", user.ID)); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !needRestart {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return needRestart, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelExpire 取消过期时间
|
||||||
|
func (mysql *Mysql) CancelExpire(id uint) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET useDays=0, expiryDate='' WHERE id=%d;", id)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExpire 设置过期时间
|
||||||
|
func (mysql *Mysql) SetExpire(id uint, useDays uint) error {
|
||||||
|
now := time.Now()
|
||||||
|
utc, err := time.LoadLocation("Asia/Shanghai")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
addDay, _ := time.ParseDuration(strconv.Itoa(int(24*useDays)) + "h")
|
||||||
|
expiryDate := now.Add(addDay).In(utc).Format("2006-01-02")
|
||||||
|
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET useDays=%d, expiryDate='%s' WHERE id=%d;", useDays, expiryDate, id)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQuota 限制流量
|
||||||
|
func (mysql *Mysql) SetQuota(id uint, quota int) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET quota=%d WHERE id=%d;", quota, id)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanData 清空流量统计
|
||||||
|
func (mysql *Mysql) CleanData(id uint) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET download=0, upload=0 WHERE id=%d;", id)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanDataByName 清空指定用户名流量统计数据
|
||||||
|
func (mysql *Mysql) CleanDataByName(usernames []string) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
runSql := "UPDATE users SET download=0, upload=0 WHERE BINARY username in ("
|
||||||
|
for i, name := range usernames {
|
||||||
|
runSql = runSql + "'" + name + "'"
|
||||||
|
if i == len(usernames)-1 {
|
||||||
|
runSql = runSql + ")"
|
||||||
|
} else {
|
||||||
|
runSql = runSql + ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(runSql); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByName 通过用户名来获取用户
|
||||||
|
func (mysql *Mysql) GetUserByName(name string) *User {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
user, err := queryUser(db, fmt.Sprintf("SELECT * FROM users WHERE BINARY username='%s'", name))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByPass 通过密码来获取用户
|
||||||
|
func (mysql *Mysql) GetUserByPass(pass string) *User {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
user, err := queryUser(db, fmt.Sprintf("SELECT * FROM users WHERE BINARY passwordShow='%s'", pass))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageList 通过分页获取用户记录
|
||||||
|
func (mysql *Mysql) PageList(curPage int, pageSize int) (*PageQuery, error) {
|
||||||
|
var (
|
||||||
|
total int
|
||||||
|
)
|
||||||
|
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return nil, errors.New("连接mysql失败")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
offset := (curPage - 1) * pageSize
|
||||||
|
querySQL := fmt.Sprintf("SELECT * FROM users LIMIT %d, %d", offset, pageSize)
|
||||||
|
userList, err := queryUserList(db, querySQL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db.QueryRow("SELECT COUNT(id) FROM users").Scan(&total)
|
||||||
|
return &PageQuery{
|
||||||
|
CurPage: curPage,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Total: total,
|
||||||
|
DataList: userList,
|
||||||
|
PageNum: (total + pageSize - 1) / pageSize,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetData 获取用户记录
|
||||||
|
func (mysql *Mysql) GetData(ids ...string) ([]*User, error) {
|
||||||
|
querySQL := "SELECT * FROM users"
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return nil, errors.New("连接mysql失败")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if len(ids) > 0 {
|
||||||
|
querySQL = querySQL + " WHERE id in (" + strings.Join(ids, ",") + ")"
|
||||||
|
}
|
||||||
|
userList, err := queryUserList(db, querySQL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userList, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/tidwall/pretty"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configPath = "/usr/local/etc/trojan/config.json"
|
||||||
|
|
||||||
|
// ServerConfig 结构体
|
||||||
|
type ServerConfig struct {
|
||||||
|
Config
|
||||||
|
SSl ServerSSL `json:"ssl"`
|
||||||
|
Tcp ServerTCP `json:"tcp"`
|
||||||
|
Mysql Mysql `json:"mysql"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerSSL 结构体
|
||||||
|
type ServerSSL struct {
|
||||||
|
SSL
|
||||||
|
Key string `json:"key"`
|
||||||
|
KeyPassword string `json:"key_password"`
|
||||||
|
PreferServerCipher bool `json:"prefer_server_cipher"`
|
||||||
|
SessionTimeout int `json:"session_timeout"`
|
||||||
|
PlainHttpResponse string `json:"plain_http_response"`
|
||||||
|
Dhparam string `json:"dhparam"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerTCP 结构体
|
||||||
|
type ServerTCP struct {
|
||||||
|
TCP
|
||||||
|
PreferIPv4 bool `json:"prefer_ipv4"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load 加载服务端配置文件
|
||||||
|
func Load(path string) []byte {
|
||||||
|
if path == "" {
|
||||||
|
path = configPath
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save 保存服务端配置文件
|
||||||
|
func Save(data []byte, path string) bool {
|
||||||
|
if path == "" {
|
||||||
|
path = configPath
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, pretty.Pretty(data), 0644); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig 获取config配置
|
||||||
|
func GetConfig() *ServerConfig {
|
||||||
|
data := Load("")
|
||||||
|
config := ServerConfig{}
|
||||||
|
if err := json.Unmarshal(data, &config); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &config
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMysql 获取mysql连接
|
||||||
|
func GetMysql() *Mysql {
|
||||||
|
return &GetConfig().Mysql
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMysql 写mysql配置
|
||||||
|
func WriteMysql(mysql *Mysql) bool {
|
||||||
|
mysql.Enabled = true
|
||||||
|
data := Load("")
|
||||||
|
result, _ := sjson.SetBytes(data, "mysql", mysql)
|
||||||
|
return Save(result, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTls 写tls配置
|
||||||
|
func WriteTls(cert, key, domain string) bool {
|
||||||
|
data := Load("")
|
||||||
|
data, _ = sjson.SetBytes(data, "ssl.cert", cert)
|
||||||
|
data, _ = sjson.SetBytes(data, "ssl.key", key)
|
||||||
|
data, _ = sjson.SetBytes(data, "ssl.sni", domain)
|
||||||
|
return Save(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteDomain 写域名
|
||||||
|
func WriteDomain(domain string) bool {
|
||||||
|
data := Load("")
|
||||||
|
data, _ = sjson.SetBytes(data, "ssl.sni", domain)
|
||||||
|
return Save(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePassword 写密码
|
||||||
|
func WritePassword(pass []string) bool {
|
||||||
|
data := Load("")
|
||||||
|
data, _ = sjson.SetBytes(data, "password", pass)
|
||||||
|
return Save(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePort 写trojan端口
|
||||||
|
func WritePort(port int) bool {
|
||||||
|
data := Load("")
|
||||||
|
data, _ = sjson.SetBytes(data, "local_port", port)
|
||||||
|
return Save(data, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteLogLevel 写日志等级
|
||||||
|
func WriteLogLevel(level int) bool {
|
||||||
|
data := Load("")
|
||||||
|
data, _ = sjson.SetBytes(data, "log_level", level)
|
||||||
|
return Save(data, "")
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpgradeDB 升级数据库表结构以及迁移数据
|
||||||
|
func (mysql *Mysql) UpgradeDB() error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return errors.New("can't connect mysql")
|
||||||
|
}
|
||||||
|
var field string
|
||||||
|
err := db.QueryRow("SHOW COLUMNS FROM users LIKE 'passwordShow';").Scan(&field)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
fmt.Println(util.Yellow("正在进行数据库升级, 请稍等.."))
|
||||||
|
if _, err := db.Exec("ALTER TABLE users ADD COLUMN passwordShow VARCHAR(255) NOT NULL AFTER password;"); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userList, err := mysql.GetData()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, user := range userList {
|
||||||
|
pass, _ := GetValue(fmt.Sprintf("%s_pass", user.Username))
|
||||||
|
if pass != "" {
|
||||||
|
base64Pass := base64.StdEncoding.EncodeToString([]byte(pass))
|
||||||
|
if _, err := db.Exec(fmt.Sprintf("UPDATE users SET passwordShow='%s' WHERE id=%d;", base64Pass, user.ID)); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
DelValue(fmt.Sprintf("%s_pass", user.Username))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = db.QueryRow("SHOW COLUMNS FROM users LIKE 'useDays';").Scan(&field)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
fmt.Println(util.Yellow("正在进行数据库升级, 请稍等.."))
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN useDays int(10) DEFAULT 0,
|
||||||
|
ADD COLUMN expiryDate char(10) DEFAULT '';
|
||||||
|
`); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var tableName string
|
||||||
|
err = db.QueryRow(fmt.Sprintf(
|
||||||
|
"SELECT * FROM information_schema.TABLES WHERE TABLE_NAME = 'users' AND TABLE_SCHEMA = '%s' ",
|
||||||
|
mysql.Database) + " AND TABLE_COLLATION LIKE 'utf8%';").Scan(&tableName)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
tempFile := "temp.sql"
|
||||||
|
mysql.DumpSql(tempFile)
|
||||||
|
mysql.ExecSql(tempFile)
|
||||||
|
os.Remove(tempFile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DumpSql 导出sql
|
||||||
|
func (mysql *Mysql) DumpSql(filePath string) error {
|
||||||
|
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
writer := bufio.NewWriter(file)
|
||||||
|
writer.WriteString("DROP TABLE IF EXISTS users;")
|
||||||
|
writer.WriteString(CreateTableSql)
|
||||||
|
db := mysql.GetDB()
|
||||||
|
userList, err := queryUserList(db, "SELECT * FROM users;")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, user := range userList {
|
||||||
|
writer.WriteString(fmt.Sprintf(`
|
||||||
|
INSERT INTO users(username, password, passwordShow, quota, download, upload, useDays, expiryDate) VALUES ('%s','%s','%s', %d, %d, %d, %d, '%s');`,
|
||||||
|
user.Username, user.EncryptPass, user.Password, user.Quota, user.Download, user.Upload, user.UseDays, user.ExpiryDate))
|
||||||
|
}
|
||||||
|
writer.WriteString("\n")
|
||||||
|
writer.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecSql 执行sql
|
||||||
|
func (mysql *Mysql) ExecSql(filePath string) error {
|
||||||
|
db := mysql.GetDB()
|
||||||
|
fileByte, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sqlStr := string(fileByte)
|
||||||
|
sqls := strings.Split(strings.Replace(sqlStr, "\r\n", "\n", -1), ";\n")
|
||||||
|
for _, s := range sqls {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s != "" {
|
||||||
|
if _, err = db.Exec(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
module trojan
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/appleboy/gin-jwt/v2 v2.9.1
|
||||||
|
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
||||||
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
|
github.com/gorilla/websocket v1.5.0
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
|
github.com/spf13/cobra v1.7.0
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||||
|
github.com/tidwall/gjson v1.17.0
|
||||||
|
github.com/tidwall/pretty v1.2.1
|
||||||
|
github.com/tidwall/sjson v1.2.5
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.10.1 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.15.4 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
|
golang.org/x/arch v0.5.0 // indirect
|
||||||
|
golang.org/x/crypto v0.13.0 // indirect
|
||||||
|
golang.org/x/net v0.15.0 // indirect
|
||||||
|
golang.org/x/sys v0.12.0 // indirect
|
||||||
|
golang.org/x/text v0.13.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
github.com/appleboy/gin-jwt/v2 v2.9.1 h1:l29et8iLW6omcHltsOP6LLk4s3v4g2FbFs0koxGWVZs=
|
||||||
|
github.com/appleboy/gin-jwt/v2 v2.9.1/go.mod h1:jwcPZJ92uoC9nOUTOKWoN/f6JZOgMSKlFSHw5/FrRUk=
|
||||||
|
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
||||||
|
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
|
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||||
|
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/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/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
|
||||||
|
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
|
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
|
github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs=
|
||||||
|
github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
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.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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
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.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||||
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||||
|
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||||
|
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/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.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||||
|
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
|
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
|
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
|
golang.org/x/text v0.3.0/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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/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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Author: Jrohy
|
||||||
|
# github: https://github.com/Jrohy/trojan
|
||||||
|
|
||||||
|
# Supported invocation modes:
|
||||||
|
# bash install.sh [--remove]
|
||||||
|
# source <(curl -fsSL https://<your-domain>/install.sh) [--remove]
|
||||||
|
if (return 0 2>/dev/null); then
|
||||||
|
sourced=1
|
||||||
|
else
|
||||||
|
sourced=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
#定义操作变量, 0为否, 1为是
|
||||||
|
help=0
|
||||||
|
|
||||||
|
remove=0
|
||||||
|
|
||||||
|
update=0
|
||||||
|
|
||||||
|
download_url="https://github.com/Jrohy/trojan/releases/download/"
|
||||||
|
|
||||||
|
version_check="https://api.github.com/repos/Jrohy/trojan/releases/latest"
|
||||||
|
|
||||||
|
service_url="https://raw.githubusercontent.com/Jrohy/trojan/master/asset/trojan-web.service"
|
||||||
|
|
||||||
|
[[ -e /var/lib/trojan-manager ]] && update=1
|
||||||
|
|
||||||
|
#Centos 临时取消别名
|
||||||
|
[[ -f /etc/redhat-release && -z $(echo $SHELL|grep zsh) ]] && unalias -a
|
||||||
|
|
||||||
|
[[ -z $(echo $SHELL|grep zsh) ]] && shell_way="bash" || shell_way="zsh"
|
||||||
|
|
||||||
|
#######color code########
|
||||||
|
red="31m"
|
||||||
|
green="32m"
|
||||||
|
yellow="33m"
|
||||||
|
blue="36m"
|
||||||
|
fuchsia="35m"
|
||||||
|
|
||||||
|
colorEcho(){
|
||||||
|
color=$1
|
||||||
|
echo -e "\033[${color}${@:2}\033[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######get params#########
|
||||||
|
while [[ $# > 0 ]];do
|
||||||
|
key="$1"
|
||||||
|
case $key in
|
||||||
|
--remove)
|
||||||
|
remove=1
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
help=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# unknown option
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift # past argument or value
|
||||||
|
done
|
||||||
|
#############################
|
||||||
|
|
||||||
|
help(){
|
||||||
|
echo "Usage: bash install.sh [-h|--help] [--remove]"
|
||||||
|
echo " or: source <(curl -fsSL https://<your-domain>/install.sh) [--remove]"
|
||||||
|
echo " -h, --help Show help"
|
||||||
|
echo " --remove remove trojan"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTrojan() {
|
||||||
|
#移除trojan
|
||||||
|
rm -rf /usr/bin/trojan >/dev/null 2>&1
|
||||||
|
rm -rf /usr/local/etc/trojan >/dev/null 2>&1
|
||||||
|
rm -f /etc/systemd/system/trojan.service >/dev/null 2>&1
|
||||||
|
|
||||||
|
#移除trojan管理程序
|
||||||
|
rm -f /usr/local/bin/trojan >/dev/null 2>&1
|
||||||
|
rm -rf /var/lib/trojan-manager >/dev/null 2>&1
|
||||||
|
rm -f /etc/systemd/system/trojan-web.service >/dev/null 2>&1
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
#移除trojan的专用db
|
||||||
|
docker rm -f trojan-mysql trojan-mariadb >/dev/null 2>&1
|
||||||
|
rm -rf /home/mysql /home/mariadb >/dev/null 2>&1
|
||||||
|
|
||||||
|
#移除环境变量
|
||||||
|
sed -i '/trojan/d' ~/.${shell_way}rc
|
||||||
|
source ~/.${shell_way}rc
|
||||||
|
|
||||||
|
colorEcho ${green} "uninstall success!"
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSys() {
|
||||||
|
#检查是否为Root
|
||||||
|
[ $(id -u) != "0" ] && { colorEcho ${red} "Error: You must be root to run this script"; return 1; }
|
||||||
|
|
||||||
|
arch=$(uname -m 2> /dev/null)
|
||||||
|
if [[ $arch != x86_64 && $arch != aarch64 ]];then
|
||||||
|
colorEcho $yellow "not support $arch machine".
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ `command -v apt-get` ]];then
|
||||||
|
package_manager='apt-get'
|
||||||
|
elif [[ `command -v dnf` ]];then
|
||||||
|
package_manager='dnf'
|
||||||
|
elif [[ `command -v yum` ]];then
|
||||||
|
package_manager='yum'
|
||||||
|
else
|
||||||
|
colorEcho $red "Not support OS!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 缺失/usr/local/bin路径时自动添加
|
||||||
|
[[ -z `echo $PATH|grep /usr/local/bin` ]] && { echo 'export PATH=$PATH:/usr/local/bin' >> /etc/bashrc; source /etc/bashrc; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#安装依赖
|
||||||
|
installDependent(){
|
||||||
|
if [[ ${package_manager} == 'dnf' || ${package_manager} == 'yum' ]];then
|
||||||
|
${package_manager} install socat crontabs bash-completion -y
|
||||||
|
else
|
||||||
|
${package_manager} update
|
||||||
|
${package_manager} install socat cron bash-completion xz-utils -y
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCron() {
|
||||||
|
if [[ `crontab -l 2>/dev/null|grep acme` ]]; then
|
||||||
|
if [[ -z `crontab -l 2>/dev/null|grep trojan-web` || `crontab -l 2>/dev/null|grep trojan-web|grep "&"` ]]; then
|
||||||
|
#计算北京时间早上3点时VPS的实际时间
|
||||||
|
origin_time_zone=$(date -R|awk '{printf"%d",$6}')
|
||||||
|
local_time_zone=${origin_time_zone%00}
|
||||||
|
beijing_zone=8
|
||||||
|
beijing_update_time=3
|
||||||
|
diff_zone=$[$beijing_zone-$local_time_zone]
|
||||||
|
local_time=$[$beijing_update_time-$diff_zone]
|
||||||
|
if [ $local_time -lt 0 ];then
|
||||||
|
local_time=$[24+$local_time]
|
||||||
|
elif [ $local_time -ge 24 ];then
|
||||||
|
local_time=$[$local_time-24]
|
||||||
|
fi
|
||||||
|
crontab -l 2>/dev/null|sed '/acme.sh/d' > crontab.txt
|
||||||
|
echo "0 ${local_time}"' * * * systemctl stop trojan-web; "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null; systemctl start trojan-web' >> crontab.txt
|
||||||
|
crontab crontab.txt
|
||||||
|
rm -f crontab.txt
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
installTrojan(){
|
||||||
|
local show_tip=0
|
||||||
|
if [[ $update == 1 ]];then
|
||||||
|
systemctl stop trojan-web >/dev/null 2>&1
|
||||||
|
rm -f /usr/local/bin/trojan
|
||||||
|
fi
|
||||||
|
lastest_version=$(curl -H 'Cache-Control: no-cache' -s "$version_check" | grep 'tag_name' | cut -d\" -f4)
|
||||||
|
echo "正在下载管理程序`colorEcho $blue $lastest_version`版本..."
|
||||||
|
[[ $arch == x86_64 ]] && bin="trojan-linux-amd64" || bin="trojan-linux-arm64"
|
||||||
|
curl -L "$download_url/$lastest_version/$bin" -o /usr/local/bin/trojan
|
||||||
|
chmod +x /usr/local/bin/trojan
|
||||||
|
if [[ ! -e /etc/systemd/system/trojan-web.service ]];then
|
||||||
|
show_tip=1
|
||||||
|
curl -L $service_url -o /etc/systemd/system/trojan-web.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable trojan-web
|
||||||
|
fi
|
||||||
|
#命令补全环境变量
|
||||||
|
[[ -z $(grep trojan ~/.${shell_way}rc) ]] && echo "source <(trojan completion ${shell_way})" >> ~/.${shell_way}rc
|
||||||
|
source ~/.${shell_way}rc
|
||||||
|
if [[ $update == 0 ]];then
|
||||||
|
colorEcho $green "安装trojan管理程序成功!\n"
|
||||||
|
echo -e "运行命令`colorEcho $blue trojan`可进行trojan管理\n"
|
||||||
|
/usr/local/bin/trojan
|
||||||
|
else
|
||||||
|
if [[ `cat /usr/local/etc/trojan/config.json|grep -w "\"db\""` ]];then
|
||||||
|
sed -i "s/\"db\"/\"database\"/g" /usr/local/etc/trojan/config.json
|
||||||
|
systemctl restart trojan
|
||||||
|
fi
|
||||||
|
/usr/local/bin/trojan upgrade db
|
||||||
|
if [[ -z `cat /usr/local/etc/trojan/config.json|grep sni` ]];then
|
||||||
|
/usr/local/bin/trojan upgrade config
|
||||||
|
fi
|
||||||
|
systemctl restart trojan-web
|
||||||
|
colorEcho $green "更新trojan管理程序成功!\n"
|
||||||
|
fi
|
||||||
|
setupCron
|
||||||
|
[[ $show_tip == 1 ]] && echo "浏览器访问'`colorEcho $blue https://域名`'可在线trojan多用户管理"
|
||||||
|
}
|
||||||
|
|
||||||
|
main(){
|
||||||
|
[[ ${help} == 1 ]] && help && return
|
||||||
|
[[ ${remove} == 1 ]] && removeTrojan && return
|
||||||
|
[[ $update == 0 ]] && echo "正在安装trojan管理程序.." || echo "正在更新trojan管理程序.."
|
||||||
|
checkSys || return 1
|
||||||
|
[[ $update == 0 ]] && installDependent
|
||||||
|
installTrojan
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
result=$?
|
||||||
|
|
||||||
|
# Do not terminate the interactive shell when the script was invoked by source.
|
||||||
|
if [[ $sourced == 1 ]]; then
|
||||||
|
return $result
|
||||||
|
fi
|
||||||
|
exit $result
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "trojan/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clientPath = "/root/config.json"
|
||||||
|
|
||||||
|
// GenClientJson 生成客户端json
|
||||||
|
func GenClientJson() {
|
||||||
|
fmt.Println()
|
||||||
|
var user core.User
|
||||||
|
domain, port := GetDomainAndPort()
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
userList, err := mysql.GetData()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(userList) == 1 {
|
||||||
|
user = *userList[0]
|
||||||
|
} else {
|
||||||
|
UserList()
|
||||||
|
choice := util.LoopInput("请选择要生成配置文件的用户序号: ", userList, true)
|
||||||
|
if choice < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user = *userList[choice-1]
|
||||||
|
}
|
||||||
|
pass, err := base64.StdEncoding.DecodeString(user.Password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(util.Red("Base64解码失败: " + err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !core.WriteClient(port, string(pass), domain, clientPath) {
|
||||||
|
fmt.Println(util.Red("生成配置文件失败!"))
|
||||||
|
} else {
|
||||||
|
fmt.Println("成功生成配置文件: " + util.Green(clientPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
var (
|
||||||
|
// MVersion 程序版本号
|
||||||
|
MVersion string
|
||||||
|
// BuildDate 编译时间
|
||||||
|
BuildDate string
|
||||||
|
// GoVersion go版本
|
||||||
|
GoVersion string
|
||||||
|
// GitVersion git版本
|
||||||
|
GitVersion string
|
||||||
|
)
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"trojan/asset"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dockerInstallUrl = "https://docker-install.netlify.app/install.sh"
|
||||||
|
dbDockerRun = "docker run --name trojan-mariadb --restart=always -p %d:3306 -v /home/mariadb:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=%s -e MYSQL_ROOT_HOST=%% -e MYSQL_DATABASE=trojan -d mariadb:10.2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstallMenu 安装目录
|
||||||
|
func InstallMenu() {
|
||||||
|
fmt.Println()
|
||||||
|
menu := []string{"更新trojan", "证书申请", "安装mysql"}
|
||||||
|
switch util.LoopInput("请选择: ", menu, true) {
|
||||||
|
case 1:
|
||||||
|
InstallTrojan("")
|
||||||
|
case 2:
|
||||||
|
InstallTls()
|
||||||
|
case 3:
|
||||||
|
InstallMysql()
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallDocker 安装docker
|
||||||
|
func InstallDocker() {
|
||||||
|
if !util.CheckCommandExists("docker") {
|
||||||
|
util.RunWebShell(dockerInstallUrl)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallTrojan 安装trojan
|
||||||
|
func InstallTrojan(version string) {
|
||||||
|
fmt.Println()
|
||||||
|
data := string(asset.GetAsset("trojan-install.sh"))
|
||||||
|
checkTrojan := util.ExecCommandWithResult("systemctl list-unit-files|grep trojan.service")
|
||||||
|
if (checkTrojan == "" && runtime.GOARCH != "amd64") || Type() == "trojan-go" {
|
||||||
|
data = strings.ReplaceAll(data, "TYPE=0", "TYPE=1")
|
||||||
|
}
|
||||||
|
if version != "" {
|
||||||
|
data = strings.ReplaceAll(data, "INSTALL_VERSION=\"\"", "INSTALL_VERSION=\""+version+"\"")
|
||||||
|
}
|
||||||
|
util.ExecCommand(data)
|
||||||
|
util.OpenPort(443)
|
||||||
|
util.SystemctlRestart("trojan")
|
||||||
|
util.SystemctlEnable("trojan")
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallTls 安装证书
|
||||||
|
func InstallTls() {
|
||||||
|
domain := ""
|
||||||
|
server := "letsencrypt"
|
||||||
|
fmt.Println()
|
||||||
|
choice := util.LoopInput("请选择使用证书方式: ", []string{"Let's Encrypt 证书", "ZeroSSL 证书", "BuyPass 证书", "自定义证书路径"}, true)
|
||||||
|
if choice < 0 {
|
||||||
|
return
|
||||||
|
} else if choice == 4 {
|
||||||
|
crtFile := util.Input("请输入证书的cert文件路径: ", "")
|
||||||
|
keyFile := util.Input("请输入证书的key文件路径: ", "")
|
||||||
|
if !util.IsExists(crtFile) || !util.IsExists(keyFile) {
|
||||||
|
fmt.Println("输入的cert或者key文件不存在!")
|
||||||
|
} else {
|
||||||
|
domain = util.Input("请输入此证书对应的域名: ", "")
|
||||||
|
if domain == "" {
|
||||||
|
fmt.Println("输入域名为空!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
core.WriteTls(crtFile, keyFile, domain)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if choice == 2 {
|
||||||
|
server = "zerossl"
|
||||||
|
} else if choice == 3 {
|
||||||
|
server = "buypass"
|
||||||
|
}
|
||||||
|
localIP := util.GetLocalIP()
|
||||||
|
fmt.Printf("本机ip: %s\n", localIP)
|
||||||
|
for {
|
||||||
|
domain = util.Input("请输入申请证书的域名: ", "")
|
||||||
|
ipList, err := net.LookupIP(domain)
|
||||||
|
fmt.Printf("%s 解析到的ip: %v\n", domain, ipList)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println("域名有误,请重新输入")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checkIp := false
|
||||||
|
for _, ip := range ipList {
|
||||||
|
if localIP == ip.String() {
|
||||||
|
checkIp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if checkIp {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
fmt.Println("输入的域名和本机ip不一致, 请重新输入!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
util.InstallPack("socat")
|
||||||
|
if !util.IsExists("/root/.acme.sh/acme.sh") {
|
||||||
|
util.RunWebShell("https://get.acme.sh")
|
||||||
|
}
|
||||||
|
util.SystemctlStop("trojan-web")
|
||||||
|
util.OpenPort(80)
|
||||||
|
checkResult := util.ExecCommandWithResult("/root/.acme.sh/acme.sh -v|tr -cd '[0-9]'")
|
||||||
|
acmeVersion, _ := strconv.Atoi(checkResult)
|
||||||
|
if acmeVersion < 300 {
|
||||||
|
util.ExecCommand("/root/.acme.sh/acme.sh --upgrade")
|
||||||
|
}
|
||||||
|
if server != "letsencrypt" {
|
||||||
|
var email string
|
||||||
|
for {
|
||||||
|
email = util.Input(fmt.Sprintf("请输入申请%s域名所需的邮箱: ", server), "")
|
||||||
|
if email == "" {
|
||||||
|
fmt.Println("申请域名的邮箱地址为空!")
|
||||||
|
return
|
||||||
|
} else if util.VerifyEmailFormat(email) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
fmt.Println("邮箱格式不正确, 请重新输入!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
util.ExecCommand(fmt.Sprintf("bash /root/.acme.sh/acme.sh --server %s --register-account -m %s", server, email))
|
||||||
|
}
|
||||||
|
issueCommand := fmt.Sprintf("bash /root/.acme.sh/acme.sh --issue -d %s --debug --standalone --keylength ec-256 --force --server %s", domain, server)
|
||||||
|
if server == "buypass" {
|
||||||
|
issueCommand = issueCommand + " --days 170"
|
||||||
|
}
|
||||||
|
util.ExecCommand(issueCommand)
|
||||||
|
crtFile := "/root/.acme.sh/" + domain + "_ecc" + "/fullchain.cer"
|
||||||
|
keyFile := "/root/.acme.sh/" + domain + "_ecc" + "/" + domain + ".key"
|
||||||
|
core.WriteTls(crtFile, keyFile, domain)
|
||||||
|
}
|
||||||
|
Restart()
|
||||||
|
util.SystemctlRestart("trojan-web")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallMysql 安装mysql
|
||||||
|
func InstallMysql() {
|
||||||
|
var (
|
||||||
|
mysql core.Mysql
|
||||||
|
choice int
|
||||||
|
)
|
||||||
|
fmt.Println()
|
||||||
|
if util.IsExists("/.dockerenv") {
|
||||||
|
choice = 2
|
||||||
|
} else {
|
||||||
|
choice = util.LoopInput("请选择: ", []string{"安装docker版mysql(mariadb)", "输入自定义mysql连接"}, true)
|
||||||
|
}
|
||||||
|
if choice < 0 {
|
||||||
|
return
|
||||||
|
} else if choice == 1 {
|
||||||
|
mysql = core.Mysql{ServerAddr: "127.0.0.1", ServerPort: util.RandomPort(), Password: util.RandString(8, util.LETTER+util.DIGITS), Username: "root", Database: "trojan"}
|
||||||
|
InstallDocker()
|
||||||
|
fmt.Println(fmt.Sprintf(dbDockerRun, mysql.ServerPort, mysql.Password))
|
||||||
|
if util.CheckCommandExists("setenforce") {
|
||||||
|
util.ExecCommand("setenforce 0")
|
||||||
|
}
|
||||||
|
util.OpenPort(mysql.ServerPort)
|
||||||
|
util.ExecCommand(fmt.Sprintf(dbDockerRun, mysql.ServerPort, mysql.Password))
|
||||||
|
db := mysql.GetDB()
|
||||||
|
for {
|
||||||
|
fmt.Printf("%s mariadb启动中,请稍等...\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
err := db.Ping()
|
||||||
|
if err == nil {
|
||||||
|
db.Close()
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("mariadb启动成功!")
|
||||||
|
} else if choice == 2 {
|
||||||
|
mysql = core.Mysql{}
|
||||||
|
for {
|
||||||
|
for {
|
||||||
|
mysqlUrl := util.Input("请输入mysql连接地址(格式: host:port), 默认连接地址为127.0.0.1:3306, 使用直接回车, 否则输入自定义连接地址: ",
|
||||||
|
"127.0.0.1:3306")
|
||||||
|
urlInfo := strings.Split(mysqlUrl, ":")
|
||||||
|
if len(urlInfo) != 2 {
|
||||||
|
fmt.Printf("输入的%s不符合匹配格式(host:port)\n", mysqlUrl)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(urlInfo[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s不是数字\n", urlInfo[1])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mysql.ServerAddr, mysql.ServerPort = urlInfo[0], port
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mysql.Username = util.Input("请输入mysql的用户名(回车使用root): ", "root")
|
||||||
|
mysql.Password = util.Input(fmt.Sprintf("请输入mysql %s用户的密码: ", mysql.Username), "")
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if db != nil && db.Ping() == nil {
|
||||||
|
mysql.Database = util.Input("请输入使用的数据库名(不存在可自动创建, 回车使用trojan): ", "trojan")
|
||||||
|
db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s;", mysql.Database))
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
fmt.Println("连接mysql失败, 请重新输入")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mysql.CreateTable()
|
||||||
|
core.WriteMysql(&mysql)
|
||||||
|
if userList, _ := mysql.GetData(); len(userList) == 0 {
|
||||||
|
AddUser()
|
||||||
|
}
|
||||||
|
Restart()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControlMenu Trojan控制菜单
|
||||||
|
func ControlMenu() {
|
||||||
|
fmt.Println()
|
||||||
|
tType := Type()
|
||||||
|
if tType == "trojan" {
|
||||||
|
tType = "trojan-go"
|
||||||
|
} else {
|
||||||
|
tType = "trojan"
|
||||||
|
}
|
||||||
|
menu := []string{"启动trojan", "停止trojan", "重启trojan", "查看trojan状态", "查看trojan日志", "修改trojan端口"}
|
||||||
|
menu = append(menu, "切换为"+tType)
|
||||||
|
switch util.LoopInput("请选择: ", menu, true) {
|
||||||
|
case 1:
|
||||||
|
Start()
|
||||||
|
case 2:
|
||||||
|
Stop()
|
||||||
|
case 3:
|
||||||
|
Restart()
|
||||||
|
case 4:
|
||||||
|
Status(true)
|
||||||
|
case 5:
|
||||||
|
go util.Log("trojan", 300)
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt, os.Kill)
|
||||||
|
//阻塞
|
||||||
|
<-c
|
||||||
|
case 6:
|
||||||
|
ChangePort()
|
||||||
|
case 7:
|
||||||
|
if err := SwitchType(tType); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart 重启trojan
|
||||||
|
func Restart() {
|
||||||
|
util.OpenPort(core.GetConfig().LocalPort)
|
||||||
|
util.SystemctlRestart("trojan")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动trojan
|
||||||
|
func Start() {
|
||||||
|
util.OpenPort(core.GetConfig().LocalPort)
|
||||||
|
util.SystemctlStart("trojan")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 停止trojan
|
||||||
|
func Stop() {
|
||||||
|
util.SystemctlStop("trojan")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status 获取trojan状态
|
||||||
|
func Status(isPrint bool) string {
|
||||||
|
result := util.SystemctlStatus("trojan")
|
||||||
|
if isPrint {
|
||||||
|
fmt.Println(result)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpTime Trojan运行时间
|
||||||
|
func UpTime() string {
|
||||||
|
result := strings.TrimSpace(util.ExecCommandWithResult("ps -Ao etime,args|grep -v grep|grep /usr/local/etc/trojan/config.json"))
|
||||||
|
resultSlice := strings.Split(result, " ")
|
||||||
|
if len(resultSlice) > 0 {
|
||||||
|
return resultSlice[0]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePort 修改trojan端口
|
||||||
|
func ChangePort() {
|
||||||
|
config := core.GetConfig()
|
||||||
|
oldPort := config.LocalPort
|
||||||
|
randomPort := util.RandomPort()
|
||||||
|
fmt.Println("当前trojan端口: " + util.Green(strconv.Itoa(oldPort)))
|
||||||
|
newPortStr := util.Input(fmt.Sprintf("请输入新的trojan端口(若要使用随机端口%s直接回车即可): ", util.Blue(strconv.Itoa(randomPort))), strconv.Itoa(randomPort))
|
||||||
|
newPort, err := strconv.Atoi(newPortStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("修改端口失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if core.WritePort(newPort) {
|
||||||
|
util.OpenPort(newPort)
|
||||||
|
fmt.Println(util.Green("端口修改成功!"))
|
||||||
|
Restart()
|
||||||
|
} else {
|
||||||
|
fmt.Println(util.Red("端口修改成功!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version Trojan版本
|
||||||
|
func Version() string {
|
||||||
|
flag := "-v"
|
||||||
|
if Type() == "trojan-go" {
|
||||||
|
flag = "-version"
|
||||||
|
}
|
||||||
|
result := strings.TrimSpace(util.ExecCommandWithResult("/usr/bin/trojan/trojan " + flag))
|
||||||
|
if len(result) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
firstLine := strings.Split(result, "\n")[0]
|
||||||
|
tempSlice := strings.Split(firstLine, " ")
|
||||||
|
return tempSlice[len(tempSlice)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchType 切换Trojan类型
|
||||||
|
func SwitchType(tType string) error {
|
||||||
|
ARCH := runtime.GOARCH
|
||||||
|
if ARCH != "amd64" && ARCH != "arm64" {
|
||||||
|
return errors.New("not support " + ARCH + " machine")
|
||||||
|
}
|
||||||
|
if tType == "trojan" && ARCH != "amd64" {
|
||||||
|
return errors.New("trojan not support " + ARCH + " machine")
|
||||||
|
}
|
||||||
|
if err := core.SetValue("trojanType", tType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
InstallTrojan("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type Trojan类型
|
||||||
|
func Type() string {
|
||||||
|
tType, _ := core.GetValue("trojanType")
|
||||||
|
if tType == "" {
|
||||||
|
if strings.Contains(Status(false), "trojan-go") {
|
||||||
|
tType = "trojan-go"
|
||||||
|
} else {
|
||||||
|
tType = "trojan"
|
||||||
|
}
|
||||||
|
_ = core.SetValue("trojanType", tType)
|
||||||
|
}
|
||||||
|
return tType
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserMenu 用户管理菜单
|
||||||
|
func UserMenu() {
|
||||||
|
fmt.Println()
|
||||||
|
menu := []string{"新增用户", "删除用户", "限制流量", "清空流量", "设置限期", "取消限期"}
|
||||||
|
switch util.LoopInput("请选择: ", menu, false) {
|
||||||
|
case 1:
|
||||||
|
AddUser()
|
||||||
|
case 2:
|
||||||
|
DelUser()
|
||||||
|
case 3:
|
||||||
|
SetUserQuota()
|
||||||
|
case 4:
|
||||||
|
CleanData()
|
||||||
|
case 5:
|
||||||
|
SetupExpire()
|
||||||
|
case 6:
|
||||||
|
CancelExpire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUser 添加用户
|
||||||
|
func AddUser() {
|
||||||
|
randomUser := util.RandString(4, util.LETTER)
|
||||||
|
randomPass := util.RandString(8, util.LETTER+util.DIGITS)
|
||||||
|
inputUser := util.Input(fmt.Sprintf("生成随机用户名: %s, 使用直接回车, 否则输入自定义用户名: ", randomUser), randomUser)
|
||||||
|
if inputUser == "admin" {
|
||||||
|
fmt.Println(util.Yellow("不能新建用户名为'admin'的用户!"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if user := mysql.GetUserByName(inputUser); user != nil {
|
||||||
|
fmt.Println(util.Yellow("已存在用户名为: " + inputUser + " 的用户!"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputPass := util.Input(fmt.Sprintf("生成随机密码: %s, 使用直接回车, 否则输入自定义密码: ", randomPass), randomPass)
|
||||||
|
base64Pass := base64.StdEncoding.EncodeToString([]byte(inputPass))
|
||||||
|
if user := mysql.GetUserByPass(base64Pass); user != nil {
|
||||||
|
fmt.Println(util.Yellow("已存在密码为: " + inputPass + " 的用户!"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mysql.CreateUser(inputUser, base64Pass, inputPass) == nil {
|
||||||
|
fmt.Println("新增用户成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelUser 删除用户
|
||||||
|
func DelUser() {
|
||||||
|
userList := UserList()
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
choice := util.LoopInput("请选择要删除的用户序号: ", userList, true)
|
||||||
|
if choice == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mysql.DeleteUser(userList[choice-1].ID) == nil {
|
||||||
|
fmt.Println("删除用户成功!")
|
||||||
|
Restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserQuota 限制用户流量
|
||||||
|
func SetUserQuota() {
|
||||||
|
var (
|
||||||
|
limit int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
userList := UserList()
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
choice := util.LoopInput("请选择要限制流量的用户序号: ", userList, true)
|
||||||
|
if choice == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
quota := util.Input("请输入用户"+userList[choice-1].Username+"限制的流量大小(单位byte)", "")
|
||||||
|
limit, err = strconv.Atoi(quota)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s 不是数字, 请重新输入!\n", quota)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mysql.SetQuota(userList[choice-1].ID, limit) == nil {
|
||||||
|
fmt.Println("成功设置用户" + userList[choice-1].Username + "限制流量" + util.Bytefmt(uint64(limit)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanData 清空用户流量
|
||||||
|
func CleanData() {
|
||||||
|
userList := UserList()
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
choice := util.LoopInput("请选择要清空流量的用户序号: ", userList, true)
|
||||||
|
if choice == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mysql.CleanData(userList[choice-1].ID) == nil {
|
||||||
|
fmt.Println("清空流量成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelExpire 取消限期
|
||||||
|
func CancelExpire() {
|
||||||
|
userList := UserList()
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
choice := util.LoopInput("请选择要取消限期的用户序号: ", userList, true)
|
||||||
|
if choice == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userList[choice-1].UseDays == 0 {
|
||||||
|
fmt.Println(util.Yellow("选择的用户未设置限期!"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mysql.CancelExpire(userList[choice-1].ID) == nil {
|
||||||
|
fmt.Println("取消限期成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupExpire 设置限期
|
||||||
|
func SetupExpire() {
|
||||||
|
userList := UserList()
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
choice := util.LoopInput("请选择要设置限期的用户序号: ", userList, true)
|
||||||
|
if choice == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
useDayStr := util.Input("请输入要限制使用的天数: ", "")
|
||||||
|
if useDayStr == "" {
|
||||||
|
return
|
||||||
|
} else if strings.Contains(useDayStr, "-") {
|
||||||
|
fmt.Println(util.Yellow("天数不能为负数"))
|
||||||
|
return
|
||||||
|
} else if !util.IsInteger(useDayStr) {
|
||||||
|
fmt.Println(util.Yellow("输入为非整数!"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
useDays, _ := strconv.Atoi(useDayStr)
|
||||||
|
if mysql.SetExpire(userList[choice-1].ID, uint(useDays)) == nil {
|
||||||
|
fmt.Println("设置限期成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanDataByName 清空指定用户流量
|
||||||
|
func CleanDataByName(usernames []string) {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.CleanDataByName(usernames); err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Println("清空流量成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserList 获取用户列表并打印显示
|
||||||
|
func UserList(ids ...string) []*core.User {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
userList, err := mysql.GetData(ids...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
domain, port := GetDomainAndPort()
|
||||||
|
for i, k := range userList {
|
||||||
|
pass, err := base64.StdEncoding.DecodeString(k.Password)
|
||||||
|
if err != nil {
|
||||||
|
pass = []byte("")
|
||||||
|
}
|
||||||
|
fmt.Printf("%d.\n", i+1)
|
||||||
|
fmt.Println("用户名: " + k.Username)
|
||||||
|
fmt.Println("密码: " + string(pass))
|
||||||
|
fmt.Println("上传流量: " + util.Cyan(util.Bytefmt(k.Upload)))
|
||||||
|
fmt.Println("下载流量: " + util.Cyan(util.Bytefmt(k.Download)))
|
||||||
|
if k.Quota < 0 {
|
||||||
|
fmt.Println("流量限额: " + util.Cyan("无限制"))
|
||||||
|
} else {
|
||||||
|
fmt.Println("流量限额: " + util.Cyan(util.Bytefmt(uint64(k.Quota))))
|
||||||
|
}
|
||||||
|
if k.UseDays == 0 {
|
||||||
|
fmt.Println("到期日期: " + util.Cyan("无限制"))
|
||||||
|
} else {
|
||||||
|
fmt.Println("到期日期: " + util.Cyan(k.ExpiryDate))
|
||||||
|
}
|
||||||
|
remark := url.QueryEscape(fmt.Sprintf("%s:%d", domain, port))
|
||||||
|
fmt.Println("分享链接: " + util.Green(fmt.Sprintf("trojan://%s@%s:%d#%s", string(pass), domain, port, remark)))
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
return userList
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebMenu web管理菜单
|
||||||
|
func WebMenu() {
|
||||||
|
fmt.Println()
|
||||||
|
menu := []string{"重置web管理员密码", "修改显示的域名(非申请证书)"}
|
||||||
|
switch util.LoopInput("请选择: ", menu, true) {
|
||||||
|
case 1:
|
||||||
|
ResetAdminPass()
|
||||||
|
case 2:
|
||||||
|
SetDomain("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAdminPass 重置管理员密码
|
||||||
|
func ResetAdminPass() {
|
||||||
|
inputPass := util.Input("请输入admin用户密码: ", "")
|
||||||
|
if inputPass == "" {
|
||||||
|
fmt.Println("撤销更改!")
|
||||||
|
} else {
|
||||||
|
encryPass := sha256.Sum224([]byte(inputPass))
|
||||||
|
err := core.SetValue("admin_pass", fmt.Sprintf("%x", encryPass))
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(util.Green("重置admin密码成功!"))
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDomain 设置显示的域名
|
||||||
|
func SetDomain(domain string) {
|
||||||
|
if domain == "" {
|
||||||
|
domain = util.Input("请输入要显示的域名地址: ", "")
|
||||||
|
}
|
||||||
|
if domain == "" {
|
||||||
|
fmt.Println("撤销更改!")
|
||||||
|
} else {
|
||||||
|
core.WriteDomain(domain)
|
||||||
|
Restart()
|
||||||
|
fmt.Println("修改domain成功!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainAndPort 获取域名和端口
|
||||||
|
func GetDomainAndPort() (string, int) {
|
||||||
|
config := core.GetConfig()
|
||||||
|
return config.SSl.Sni, config.LocalPort
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BYTE 字节
|
||||||
|
BYTE = 1 << (10 * iota)
|
||||||
|
// KILOBYTE 千字节
|
||||||
|
KILOBYTE
|
||||||
|
// MEGABYTE 兆字节
|
||||||
|
MEGABYTE
|
||||||
|
// GIGABYTE 吉字节
|
||||||
|
GIGABYTE
|
||||||
|
// TERABYTE 太字节
|
||||||
|
TERABYTE
|
||||||
|
// PETABYTE 拍字节
|
||||||
|
PETABYTE
|
||||||
|
// EXABYTE 艾字节
|
||||||
|
EXABYTE
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bytefmt returns a human-readable byte string of the form 10M, 12.5K, and so forth. The following units are available:
|
||||||
|
// E: Exabyte
|
||||||
|
// P: Petabyte
|
||||||
|
// T: Terabyte
|
||||||
|
// G: Gigabyte
|
||||||
|
// M: Megabyte
|
||||||
|
// K: Kilobyte
|
||||||
|
// B: Byte
|
||||||
|
// The unit that results in the smallest number greater than or equal to 1 is always chosen.
|
||||||
|
func Bytefmt(bytes uint64) string {
|
||||||
|
unit := ""
|
||||||
|
value := float64(bytes)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case bytes >= EXABYTE:
|
||||||
|
unit = "E"
|
||||||
|
value = value / EXABYTE
|
||||||
|
case bytes >= PETABYTE:
|
||||||
|
unit = "P"
|
||||||
|
value = value / PETABYTE
|
||||||
|
case bytes >= TERABYTE:
|
||||||
|
unit = "T"
|
||||||
|
value = value / TERABYTE
|
||||||
|
case bytes >= GIGABYTE:
|
||||||
|
unit = "G"
|
||||||
|
value = value / GIGABYTE
|
||||||
|
case bytes >= MEGABYTE:
|
||||||
|
unit = "M"
|
||||||
|
value = value / MEGABYTE
|
||||||
|
case bytes >= KILOBYTE:
|
||||||
|
unit = "K"
|
||||||
|
value = value / KILOBYTE
|
||||||
|
case bytes >= BYTE:
|
||||||
|
unit = "B"
|
||||||
|
case bytes == 0:
|
||||||
|
return "0B"
|
||||||
|
}
|
||||||
|
|
||||||
|
result := strconv.FormatFloat(value, 'f', 2, 64)
|
||||||
|
result = strings.TrimSuffix(result, ".0")
|
||||||
|
return result + unit
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func systemctlReplace(out string) (bool, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
isReplace bool
|
||||||
|
)
|
||||||
|
if IsExists("/.dockerenv") && strings.Contains(out, "Failed to get D-Bus") {
|
||||||
|
isReplace = true
|
||||||
|
fmt.Println(Yellow("正在下载并替换适配的systemctl。。"))
|
||||||
|
if err = ExecCommand("curl -L https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl.py -o /usr/bin/systemctl && chmod +x /usr/bin/systemctl"); err != nil {
|
||||||
|
return isReplace, err
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
return isReplace, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func systemctlBase(name, operate string) (string, error) {
|
||||||
|
out, err := exec.Command("bash", "-c", fmt.Sprintf("systemctl %s %s", operate, name)).CombinedOutput()
|
||||||
|
if v, _ := systemctlReplace(string(out)); v {
|
||||||
|
out, err = exec.Command("bash", "-c", fmt.Sprintf("systemctl %s %s", operate, name)).CombinedOutput()
|
||||||
|
}
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemctlStart 服务启动
|
||||||
|
func SystemctlStart(name string) {
|
||||||
|
if _, err := systemctlBase(name, "start"); err != nil {
|
||||||
|
fmt.Println(Red(fmt.Sprintf("启动%s失败!", name)))
|
||||||
|
} else {
|
||||||
|
fmt.Println(Green(fmt.Sprintf("启动%s成功!", name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemctlStop 服务停止
|
||||||
|
func SystemctlStop(name string) {
|
||||||
|
if _, err := systemctlBase(name, "stop"); err != nil {
|
||||||
|
fmt.Println(Red(fmt.Sprintf("停止%s失败!", name)))
|
||||||
|
} else {
|
||||||
|
fmt.Println(Green(fmt.Sprintf("停止%s成功!", name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemctlRestart 服务重启
|
||||||
|
func SystemctlRestart(name string) {
|
||||||
|
if _, err := systemctlBase(name, "restart"); err != nil {
|
||||||
|
fmt.Println(Red(fmt.Sprintf("重启%s失败!", name)))
|
||||||
|
} else {
|
||||||
|
fmt.Println(Green(fmt.Sprintf("重启%s成功!", name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemctlEnable 服务设置开机自启
|
||||||
|
func SystemctlEnable(name string) {
|
||||||
|
if _, err := systemctlBase(name, "enable"); err != nil {
|
||||||
|
fmt.Println(Red(fmt.Sprintf("设置%s开机自启失败!", name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemctlStatus 服务状态查看
|
||||||
|
func SystemctlStatus(name string) string {
|
||||||
|
out, _ := systemctlBase(name, "status")
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckCommandExists 检查命令是否存在
|
||||||
|
func CheckCommandExists(command string) bool {
|
||||||
|
if _, err := exec.LookPath(command); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWebShell 运行网上的脚本
|
||||||
|
func RunWebShell(webShellPath string) {
|
||||||
|
if !strings.HasPrefix(webShellPath, "http") && !strings.HasPrefix(webShellPath, "https") {
|
||||||
|
fmt.Printf("shell path must start with http or https!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := http.Get(webShellPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
installShell, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
ExecCommand(string(installShell))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommand 运行命令并实时查看运行结果
|
||||||
|
func ExecCommand(command string) error {
|
||||||
|
cmd := exec.Command("bash", "-c", command)
|
||||||
|
|
||||||
|
stdout, _ := cmd.StdoutPipe()
|
||||||
|
stderr, _ := cmd.StderrPipe()
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
fmt.Println("Error:The command is err: ", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ch := make(chan string, 100)
|
||||||
|
stdoutScan := bufio.NewScanner(stdout)
|
||||||
|
stderrScan := bufio.NewScanner(stderr)
|
||||||
|
go func() {
|
||||||
|
for stdoutScan.Scan() {
|
||||||
|
line := stdoutScan.Text()
|
||||||
|
ch <- line
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for stderrScan.Scan() {
|
||||||
|
line := stderrScan.Text()
|
||||||
|
ch <- line
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
go func() {
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "exit status") {
|
||||||
|
fmt.Println("wait:", err.Error())
|
||||||
|
}
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
for line := range ch {
|
||||||
|
fmt.Println(line)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommandWithResult 运行命令并获取结果
|
||||||
|
func ExecCommandWithResult(command string) string {
|
||||||
|
out, err := exec.Command("bash", "-c", command).CombinedOutput()
|
||||||
|
if strings.Contains(command, "systemctl") {
|
||||||
|
if v, _ := systemctlReplace(string(out)); v {
|
||||||
|
out, err = exec.Command("bash", "-c", command).CombinedOutput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "exit status") {
|
||||||
|
fmt.Println("err: " + err.Error())
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortIsUse 判断端口是否占用
|
||||||
|
func PortIsUse(port int) bool {
|
||||||
|
_, tcpError := net.DialTimeout("tcp", fmt.Sprintf(":%d", port), time.Millisecond*50)
|
||||||
|
udpAddr, _ := net.ResolveUDPAddr("udp4", fmt.Sprintf(":%d", port))
|
||||||
|
udpConn, udpError := net.ListenUDP("udp", udpAddr)
|
||||||
|
if udpConn != nil {
|
||||||
|
defer udpConn.Close()
|
||||||
|
}
|
||||||
|
return tcpError == nil || udpError != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandomPort 获取没占用的随机端口
|
||||||
|
func RandomPort() int {
|
||||||
|
for {
|
||||||
|
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
newPort := rand.Intn(65536)
|
||||||
|
if !PortIsUse(newPort) {
|
||||||
|
return newPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExists 检测指定路径文件或者文件夹是否存在
|
||||||
|
func IsExists(path string) bool {
|
||||||
|
_, err := os.Stat(path) //os.Stat获取文件信息
|
||||||
|
if err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocalIP 获取本机ipv4地址
|
||||||
|
func GetLocalIP() string {
|
||||||
|
resp, err := http.Get("http://api.ipify.org")
|
||||||
|
if err != nil {
|
||||||
|
resp, _ = http.Get("http://icanhazip.com")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
s, _ := io.ReadAll(resp.Body)
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallPack 安装指定名字软件
|
||||||
|
func InstallPack(name string) {
|
||||||
|
if !CheckCommandExists(name) {
|
||||||
|
if CheckCommandExists("yum") {
|
||||||
|
ExecCommand("yum install -y " + name)
|
||||||
|
} else if CheckCommandExists("apt-get") {
|
||||||
|
ExecCommand("apt-get update")
|
||||||
|
ExecCommand("apt-get install -y " + name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenPort 开通指定端口
|
||||||
|
func OpenPort(port int) {
|
||||||
|
if CheckCommandExists("firewall-cmd") {
|
||||||
|
ExecCommand(fmt.Sprintf("firewall-cmd --zone=public --add-port=%d/tcp --add-port=%d/udp --permanent >/dev/null 2>&1", port, port))
|
||||||
|
ExecCommand("firewall-cmd --reload >/dev/null 2>&1")
|
||||||
|
} else {
|
||||||
|
if len(ExecCommandWithResult(fmt.Sprintf(`iptables -nvL --line-number|grep -w "%d"`, port))) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ExecCommand(fmt.Sprintf("iptables -I INPUT -p tcp --dport %d -j ACCEPT", port))
|
||||||
|
ExecCommand(fmt.Sprintf("iptables -I INPUT -p udp --dport %d -j ACCEPT", port))
|
||||||
|
ExecCommand(fmt.Sprintf("iptables -I OUTPUT -p udp --sport %d -j ACCEPT", port))
|
||||||
|
ExecCommand(fmt.Sprintf("iptables -I OUTPUT -p tcp --sport %d -j ACCEPT", port))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log 实时打印指定服务日志
|
||||||
|
func Log(serviceName string, line int) {
|
||||||
|
result, _ := LogChan(serviceName, "-n "+strconv.Itoa(line), make(chan byte))
|
||||||
|
for line := range result {
|
||||||
|
fmt.Println(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogChan 指定服务实时日志, 返回chan
|
||||||
|
func LogChan(serviceName, param string, closeChan chan byte) (chan string, error) {
|
||||||
|
cmd := exec.Command("bash", "-c", fmt.Sprintf("journalctl -f -u %s -o cat %s", serviceName, param))
|
||||||
|
|
||||||
|
stdout, _ := cmd.StdoutPipe()
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
fmt.Println("Error:The command is err: ", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ch := make(chan string, 100)
|
||||||
|
stdoutScan := bufio.NewScanner(stdout)
|
||||||
|
go func() {
|
||||||
|
for stdoutScan.Scan() {
|
||||||
|
select {
|
||||||
|
case <-closeChan:
|
||||||
|
stdout.Close()
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
ch <- stdoutScan.Text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/eiannone/keyboard"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RED 红色
|
||||||
|
RED = "\033[31m"
|
||||||
|
// GREEN 绿色
|
||||||
|
GREEN = "\033[32m"
|
||||||
|
// YELLOW 黄色
|
||||||
|
YELLOW = "\033[33m"
|
||||||
|
// BLUE 蓝色
|
||||||
|
BLUE = "\033[34m"
|
||||||
|
// FUCHSIA 紫红色
|
||||||
|
FUCHSIA = "\033[35m"
|
||||||
|
// CYAN 青色
|
||||||
|
CYAN = "\033[36m"
|
||||||
|
// WHITE 白色
|
||||||
|
WHITE = "\033[37m"
|
||||||
|
// RESET 重置颜色
|
||||||
|
RESET = "\033[0m"
|
||||||
|
// LETTER 大小写英文字母常量
|
||||||
|
LETTER = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
// DIGITS 数字常量
|
||||||
|
DIGITS = "0123456789"
|
||||||
|
// SPECIALS 特殊字符常量
|
||||||
|
SPECIALS = "~=+%^*/()[]{}/!@#$?|"
|
||||||
|
// ALL 全部字符常量
|
||||||
|
ALL = LETTER + DIGITS + SPECIALS
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsInteger 判断字符串是否为整数
|
||||||
|
func IsInteger(input string) bool {
|
||||||
|
_, err := strconv.Atoi(input)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RandString 随机字符串
|
||||||
|
func RandString(length int, source string) string {
|
||||||
|
var runes = []rune(source)
|
||||||
|
b := make([]rune, length)
|
||||||
|
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
for i := range b {
|
||||||
|
b[i] = runes[rand.Intn(len(runes))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyEmailFormat 邮箱验证
|
||||||
|
func VerifyEmailFormat(email string) bool {
|
||||||
|
pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$`
|
||||||
|
reg := regexp.MustCompile(pattern)
|
||||||
|
return reg.MatchString(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChar(str string) string {
|
||||||
|
err := keyboard.Open()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer keyboard.Close()
|
||||||
|
fmt.Print(str)
|
||||||
|
char, _, _ := keyboard.GetKey()
|
||||||
|
fmt.Printf("%c\n", char)
|
||||||
|
if char == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(char)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoopInput 循环输入选择, 或者直接回车退出
|
||||||
|
func LoopInput(tip string, choices interface{}, singleRowPrint bool) int {
|
||||||
|
reflectValue := reflect.ValueOf(choices)
|
||||||
|
if reflectValue.Kind() != reflect.Slice && reflectValue.Kind() != reflect.Array {
|
||||||
|
fmt.Println("only support slice or array type!")
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
length := reflectValue.Len()
|
||||||
|
if reflectValue.Type().String() == "[]string" {
|
||||||
|
if singleRowPrint {
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
fmt.Printf("%d.%s\n\n", i+1, reflectValue.Index(i).Interface())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
fmt.Printf("%d.%-15s\t", i+1, reflectValue.Index(i).Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%d.%-15s\n\n", i+1, reflectValue.Index(i).Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
inputString := ""
|
||||||
|
if length < 10 {
|
||||||
|
inputString = getChar(tip)
|
||||||
|
} else {
|
||||||
|
fmt.Print(tip)
|
||||||
|
_, _ = fmt.Scanln(&inputString)
|
||||||
|
}
|
||||||
|
if inputString == "" {
|
||||||
|
return -1
|
||||||
|
} else if !IsInteger(inputString) {
|
||||||
|
fmt.Println("输入有误,请重新输入")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
number, _ := strconv.Atoi(inputString)
|
||||||
|
if number <= length && number > 0 {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
fmt.Println("输入数字越界,请重新输入")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input 读取终端用户输入
|
||||||
|
func Input(tip string, defaultValue string) string {
|
||||||
|
input := ""
|
||||||
|
fmt.Print(tip)
|
||||||
|
_, _ = fmt.Scanln(&input)
|
||||||
|
if input == "" && defaultValue != "" {
|
||||||
|
input = defaultValue
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
// Red 红色
|
||||||
|
func Red(str string) string {
|
||||||
|
return RED + str + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
// Green 绿色
|
||||||
|
func Green(str string) string {
|
||||||
|
return GREEN + str + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yellow 黄色
|
||||||
|
func Yellow(str string) string {
|
||||||
|
return YELLOW + str + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blue 蓝色
|
||||||
|
func Blue(str string) string {
|
||||||
|
return BLUE + str + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuchsia 紫红色
|
||||||
|
func Fuchsia(str string) string {
|
||||||
|
return FUCHSIA + str + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cyan 青色
|
||||||
|
func Cyan(str string) string {
|
||||||
|
return CYAN + str + RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
// White 白色
|
||||||
|
func White(str string) string {
|
||||||
|
return WHITE + str + RESET
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// http升级websocket协议的配置
|
||||||
|
var wsUpgrader = websocket.Upgrader{
|
||||||
|
// 允许所有CORS跨域请求
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsMessage websocket消息
|
||||||
|
type WsMessage struct {
|
||||||
|
MessageType int
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsConnection 封装websocket连接
|
||||||
|
type WsConnection struct {
|
||||||
|
wsSocket *websocket.Conn // 底层websocket
|
||||||
|
inChan chan *WsMessage // 读取队列
|
||||||
|
outChan chan *WsMessage // 发送队列
|
||||||
|
|
||||||
|
mutex sync.Mutex // 避免重复关闭管道
|
||||||
|
isClosed bool
|
||||||
|
CloseChan chan byte // 关闭通知
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取协程
|
||||||
|
func (wsConn *WsConnection) wsReadLoop() {
|
||||||
|
var (
|
||||||
|
msgType int
|
||||||
|
data []byte
|
||||||
|
msg *WsMessage
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// 读一个message
|
||||||
|
if msgType, data, err = wsConn.wsSocket.ReadMessage(); err != nil {
|
||||||
|
fmt.Println("Read error: " + err.Error())
|
||||||
|
goto CLOSED
|
||||||
|
}
|
||||||
|
msg = &WsMessage{
|
||||||
|
msgType,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
// 放入请求队列
|
||||||
|
select {
|
||||||
|
case wsConn.inChan <- msg:
|
||||||
|
if string(data) == "exit" {
|
||||||
|
goto CLOSED
|
||||||
|
}
|
||||||
|
case <-wsConn.CloseChan:
|
||||||
|
goto CLOSED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CLOSED:
|
||||||
|
wsConn.WsClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送协程
|
||||||
|
func (wsConn *WsConnection) wsWriteLoop() {
|
||||||
|
var (
|
||||||
|
msg *WsMessage
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// 取一个应答
|
||||||
|
case msg = <-wsConn.outChan:
|
||||||
|
// 写给websocket
|
||||||
|
if err = wsConn.wsSocket.WriteMessage(msg.MessageType, msg.Data); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
goto CLOSED
|
||||||
|
}
|
||||||
|
case <-wsConn.CloseChan:
|
||||||
|
goto CLOSED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CLOSED:
|
||||||
|
wsConn.WsClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWebsocket 初始化ws
|
||||||
|
func InitWebsocket(resp http.ResponseWriter, req *http.Request) (wsConn *WsConnection, err error) {
|
||||||
|
var (
|
||||||
|
wsSocket *websocket.Conn
|
||||||
|
)
|
||||||
|
// 应答客户端告知升级连接为websocket
|
||||||
|
if wsSocket, err = wsUpgrader.Upgrade(resp, req, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wsConn = &WsConnection{
|
||||||
|
wsSocket: wsSocket,
|
||||||
|
inChan: make(chan *WsMessage, 1000),
|
||||||
|
outChan: make(chan *WsMessage, 1000),
|
||||||
|
CloseChan: make(chan byte),
|
||||||
|
isClosed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读协程
|
||||||
|
go wsConn.wsReadLoop()
|
||||||
|
// 写协程
|
||||||
|
go wsConn.wsWriteLoop()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsWrite 发送消息
|
||||||
|
func (wsConn *WsConnection) WsWrite(messageType int, data []byte) (err error) {
|
||||||
|
select {
|
||||||
|
case wsConn.outChan <- &WsMessage{messageType, data}:
|
||||||
|
case <-wsConn.CloseChan:
|
||||||
|
err = errors.New("websocket closed")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsRead 读取消息
|
||||||
|
func (wsConn *WsConnection) WsRead() (msg *WsMessage, err error) {
|
||||||
|
select {
|
||||||
|
case msg = <-wsConn.inChan:
|
||||||
|
return
|
||||||
|
case <-wsConn.CloseChan:
|
||||||
|
err = errors.New("websocket closed")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WsClose 关闭连接
|
||||||
|
func (wsConn *WsConnection) WsClose() {
|
||||||
|
wsConn.wsSocket.Close()
|
||||||
|
wsConn.mutex.Lock()
|
||||||
|
defer wsConn.mutex.Unlock()
|
||||||
|
if !wsConn.isClosed {
|
||||||
|
wsConn.isClosed = true
|
||||||
|
close(wsConn.CloseChan)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/appleboy/gin-jwt/v2"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"time"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
"trojan/web/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
identityKey = "id"
|
||||||
|
authMiddleware *jwt.GinJWTMiddleware
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login auth用户验证结构体
|
||||||
|
type Login struct {
|
||||||
|
Username string `form:"username" json:"username" binding:"required"`
|
||||||
|
Password string `form:"password" json:"password" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecretKey() string {
|
||||||
|
sk, _ := core.GetValue("secretKey")
|
||||||
|
if sk == "" {
|
||||||
|
sk = util.RandString(15, util.ALL)
|
||||||
|
core.SetValue("secretKey", sk)
|
||||||
|
}
|
||||||
|
return sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func jwtInit(timeout int) {
|
||||||
|
authMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
|
||||||
|
Realm: "trojan-manager",
|
||||||
|
Key: []byte(getSecretKey()),
|
||||||
|
Timeout: time.Minute * time.Duration(timeout),
|
||||||
|
MaxRefresh: time.Minute * time.Duration(timeout),
|
||||||
|
IdentityKey: identityKey,
|
||||||
|
SendCookie: true,
|
||||||
|
PayloadFunc: func(data interface{}) jwt.MapClaims {
|
||||||
|
if v, ok := data.(*Login); ok {
|
||||||
|
return jwt.MapClaims{
|
||||||
|
identityKey: v.Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jwt.MapClaims{}
|
||||||
|
},
|
||||||
|
IdentityHandler: func(c *gin.Context) interface{} {
|
||||||
|
claims := jwt.ExtractClaims(c)
|
||||||
|
return &Login{
|
||||||
|
Username: claims[identityKey].(string),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Authenticator: func(c *gin.Context) (interface{}, error) {
|
||||||
|
var (
|
||||||
|
password string
|
||||||
|
loginVals Login
|
||||||
|
)
|
||||||
|
if err := c.ShouldBind(&loginVals); err != nil {
|
||||||
|
return "", jwt.ErrMissingLoginValues
|
||||||
|
}
|
||||||
|
userID := loginVals.Username
|
||||||
|
pass := loginVals.Password
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if userID != "admin" {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
user := mysql.GetUserByName(userID)
|
||||||
|
if user == nil {
|
||||||
|
return nil, jwt.ErrFailedAuthentication
|
||||||
|
}
|
||||||
|
password = user.EncryptPass
|
||||||
|
} else {
|
||||||
|
if password, err = core.GetValue(userID + "_pass"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if password == pass {
|
||||||
|
return &loginVals, nil
|
||||||
|
}
|
||||||
|
return nil, jwt.ErrFailedAuthentication
|
||||||
|
},
|
||||||
|
Authorizator: func(data interface{}, c *gin.Context) bool {
|
||||||
|
if _, ok := data.(*Login); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Unauthorized: func(c *gin.Context, code int, message string) {
|
||||||
|
c.JSON(code, gin.H{
|
||||||
|
"code": code,
|
||||||
|
"message": message,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
TokenLookup: "header: Authorization, query: token, cookie: jwt",
|
||||||
|
TokenHeadName: "Bearer",
|
||||||
|
TimeFunc: time.Now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("JWT Error:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUser(c *gin.Context) {
|
||||||
|
responseBody := controller.ResponseBody{Msg: "success"}
|
||||||
|
defer controller.TimeCost(time.Now(), &responseBody)
|
||||||
|
username := c.DefaultPostForm("username", "admin")
|
||||||
|
pass := c.PostForm("password")
|
||||||
|
err := core.SetValue(fmt.Sprintf("%s_pass", username), pass)
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
c.JSON(200, responseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestUsername 获取请求接口的用户名
|
||||||
|
func RequestUsername(c *gin.Context) string {
|
||||||
|
claims := jwt.ExtractClaims(c)
|
||||||
|
return claims[identityKey].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth 权限router
|
||||||
|
func Auth(r *gin.Engine, timeout int) *jwt.GinJWTMiddleware {
|
||||||
|
jwtInit(timeout)
|
||||||
|
|
||||||
|
newInstall := gin.H{"code": 201, "message": "No administrator account found inside the database", "data": nil}
|
||||||
|
r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
|
||||||
|
claims := jwt.ExtractClaims(c)
|
||||||
|
fmt.Printf("NoRoute claims: %#v\n", claims)
|
||||||
|
c.JSON(404, gin.H{"code": 404, "message": "Page not found"})
|
||||||
|
})
|
||||||
|
r.GET("/auth/check", func(c *gin.Context) {
|
||||||
|
result, _ := core.GetValue("admin_pass")
|
||||||
|
if result == "" {
|
||||||
|
c.JSON(201, newInstall)
|
||||||
|
} else {
|
||||||
|
title, err := core.GetValue("login_title")
|
||||||
|
if err != nil {
|
||||||
|
title = "trojan 管理平台"
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": map[string]string{
|
||||||
|
"title": title,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
r.POST("/auth/login", authMiddleware.LoginHandler)
|
||||||
|
r.POST("/auth/register", updateUser)
|
||||||
|
authO := r.Group("/auth")
|
||||||
|
authO.Use(authMiddleware.MiddlewareFunc())
|
||||||
|
{
|
||||||
|
authO.GET("/loginUser", func(c *gin.Context) {
|
||||||
|
result, _ := core.GetValue("admin_pass")
|
||||||
|
if result == "" {
|
||||||
|
c.JSON(201, newInstall)
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": map[string]string{
|
||||||
|
"username": RequestUsername(c),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
authO.POST("/reset_pass", updateUser)
|
||||||
|
authO.POST("/logout", authMiddleware.LogoutHandler)
|
||||||
|
authO.POST("/refresh_token", authMiddleware.RefreshHandler)
|
||||||
|
}
|
||||||
|
return authMiddleware
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"github.com/shirou/gopsutil/cpu"
|
||||||
|
"github.com/shirou/gopsutil/disk"
|
||||||
|
"github.com/shirou/gopsutil/load"
|
||||||
|
"github.com/shirou/gopsutil/mem"
|
||||||
|
"github.com/shirou/gopsutil/net"
|
||||||
|
"time"
|
||||||
|
"trojan/asset"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseBody 结构体
|
||||||
|
type ResponseBody struct {
|
||||||
|
Duration string
|
||||||
|
Data interface{}
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
type speedInfo struct {
|
||||||
|
Up uint64
|
||||||
|
Down uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
var si *speedInfo
|
||||||
|
|
||||||
|
// TimeCost web函数执行用时统计方法
|
||||||
|
func TimeCost(start time.Time, body *ResponseBody) {
|
||||||
|
body.Duration = time.Since(start).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clashRules() string {
|
||||||
|
rules, _ := core.GetValue("clash-rules")
|
||||||
|
if rules == "" {
|
||||||
|
rules = string(asset.GetAsset("clash-rules.yaml"))
|
||||||
|
}
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version 获取版本信息
|
||||||
|
func Version() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
responseBody.Data = map[string]string{
|
||||||
|
"version": trojan.MVersion,
|
||||||
|
"buildDate": trojan.BuildDate,
|
||||||
|
"goVersion": trojan.GoVersion,
|
||||||
|
"gitVersion": trojan.GitVersion,
|
||||||
|
"trojanVersion": trojan.Version(),
|
||||||
|
"trojanUptime": trojan.UpTime(),
|
||||||
|
"trojanType": trojan.Type(),
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLoginInfo 设置登录页信息
|
||||||
|
func SetLoginInfo(title string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
err := core.SetValue("login_title", title)
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDomain 设置域名
|
||||||
|
func SetDomain(domain string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
trojan.SetDomain(domain)
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClashRules 设置clash规则
|
||||||
|
func SetClashRules(rules string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
core.SetValue("clash-rules", rules)
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetClashRules 重置clash规则
|
||||||
|
func ResetClashRules() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
core.DelValue("clash-rules")
|
||||||
|
responseBody.Data = clashRules()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClashRules 获取clash规则
|
||||||
|
func GetClashRules() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
responseBody.Data = clashRules()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrojanType 设置trojan类型
|
||||||
|
func SetTrojanType(tType string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
err := trojan.SwitchType(tType)
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectTask 启动收集主机信息任务
|
||||||
|
func CollectTask() {
|
||||||
|
var recvCount, sentCount uint64
|
||||||
|
c := cron.New()
|
||||||
|
lastIO, _ := net.IOCounters(true)
|
||||||
|
var lastRecvCount, lastSentCount uint64
|
||||||
|
for _, k := range lastIO {
|
||||||
|
lastRecvCount = lastRecvCount + k.BytesRecv
|
||||||
|
lastSentCount = lastSentCount + k.BytesSent
|
||||||
|
}
|
||||||
|
si = &speedInfo{}
|
||||||
|
c.AddFunc("@every 2s", func() {
|
||||||
|
result, _ := net.IOCounters(true)
|
||||||
|
recvCount, sentCount = 0, 0
|
||||||
|
for _, k := range result {
|
||||||
|
recvCount = recvCount + k.BytesRecv
|
||||||
|
sentCount = sentCount + k.BytesSent
|
||||||
|
}
|
||||||
|
si.Up = (sentCount - lastSentCount) / 2
|
||||||
|
si.Down = (recvCount - lastRecvCount) / 2
|
||||||
|
lastSentCount = sentCount
|
||||||
|
lastRecvCount = recvCount
|
||||||
|
lastIO = result
|
||||||
|
})
|
||||||
|
c.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerInfo 获取服务器信息
|
||||||
|
func ServerInfo() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
cpuPercent, _ := cpu.Percent(0, false)
|
||||||
|
vmInfo, _ := mem.VirtualMemory()
|
||||||
|
smInfo, _ := mem.SwapMemory()
|
||||||
|
diskInfo, _ := disk.Usage("/")
|
||||||
|
loadInfo, _ := load.Avg()
|
||||||
|
tcpCon, _ := net.Connections("tcp")
|
||||||
|
udpCon, _ := net.Connections("udp")
|
||||||
|
netCount := map[string]int{
|
||||||
|
"tcp": len(tcpCon),
|
||||||
|
"udp": len(udpCon),
|
||||||
|
}
|
||||||
|
responseBody.Data = map[string]interface{}{
|
||||||
|
"cpu": cpuPercent,
|
||||||
|
"memory": vmInfo,
|
||||||
|
"swap": smInfo,
|
||||||
|
"disk": diskInfo,
|
||||||
|
"load": loadInfo,
|
||||||
|
"speed": si,
|
||||||
|
"netCount": netCount,
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
var c *cron.Cron
|
||||||
|
|
||||||
|
// SetData 设置流量限制
|
||||||
|
func SetData(id uint, quota int) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.SetQuota(id, quota); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanData 清空流量
|
||||||
|
func CleanData(id uint) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.CleanData(id); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
func monthlyResetJob() {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.MonthlyResetData(); err != nil {
|
||||||
|
fmt.Println("MonthlyResetError: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResetDay 获取重置日
|
||||||
|
func GetResetDay() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
dayStr, _ := core.GetValue("reset_day")
|
||||||
|
day, _ := strconv.Atoi(dayStr)
|
||||||
|
responseBody.Data = map[string]interface{}{
|
||||||
|
"resetDay": day,
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResetDay 更新重置流量日
|
||||||
|
func UpdateResetDay(day uint) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
if day > 31 || day < 0 {
|
||||||
|
responseBody.Msg = fmt.Sprintf("%d为非正常日期", day)
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
dayStr, _ := core.GetValue("reset_day")
|
||||||
|
oldDay, _ := strconv.Atoi(dayStr)
|
||||||
|
if day == uint(oldDay) {
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if len(c.Entries()) > 1 {
|
||||||
|
c.Remove(c.Entries()[len(c.Entries())-1].ID)
|
||||||
|
}
|
||||||
|
if day != 0 {
|
||||||
|
c.AddFunc(fmt.Sprintf("0 0 %d * *", day), func() {
|
||||||
|
monthlyResetJob()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
core.SetValue("reset_day", strconv.Itoa(int(day)))
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScheduleTask 定时任务
|
||||||
|
func ScheduleTask() {
|
||||||
|
loc, _ := time.LoadLocation("Asia/Shanghai")
|
||||||
|
c = cron.New(cron.WithLocation(loc))
|
||||||
|
c.AddFunc("@daily", func() {
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if needRestart, err := mysql.DailyCheckExpire(); err != nil {
|
||||||
|
fmt.Println("DailyCheckError: " + err.Error())
|
||||||
|
} else if needRestart {
|
||||||
|
trojan.Restart()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dayStr, _ := core.GetValue("reset_day")
|
||||||
|
if dayStr == "" {
|
||||||
|
dayStr = "1"
|
||||||
|
core.SetValue("reset_day", dayStr)
|
||||||
|
}
|
||||||
|
day, _ := strconv.Atoi(dayStr)
|
||||||
|
if day != 0 {
|
||||||
|
c.AddFunc(fmt.Sprintf("0 0 %d * *", day), func() {
|
||||||
|
monthlyResetJob()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.Start()
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
ws "github.com/gorilla/websocket"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/trojan"
|
||||||
|
"trojan/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start 启动trojan
|
||||||
|
func Start() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
trojan.Start()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 停止trojan
|
||||||
|
func Stop() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
trojan.Stop()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart 重启trojan
|
||||||
|
func Restart() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
trojan.Restart()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update trojan更新
|
||||||
|
func Update() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
trojan.InstallTrojan("")
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogLevel 修改trojan日志等级
|
||||||
|
func SetLogLevel(level int) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
core.WriteLogLevel(level)
|
||||||
|
trojan.Restart()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogLevel 获取trojan日志等级
|
||||||
|
func GetLogLevel() *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
config := core.GetConfig()
|
||||||
|
responseBody.Data = map[string]interface{}{
|
||||||
|
"loglevel": &config.LogLevel,
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log 通过ws查看trojan实时日志
|
||||||
|
func Log(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
wsConn *util.WsConnection
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if wsConn, err = util.InitWebsocket(c.Writer, c.Request); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer wsConn.WsClose()
|
||||||
|
param := c.DefaultQuery("line", "300")
|
||||||
|
if !util.IsInteger(param) {
|
||||||
|
fmt.Println("invalid param: " + param)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if param == "-1" {
|
||||||
|
param = "--no-tail"
|
||||||
|
} else {
|
||||||
|
param = "-n " + param
|
||||||
|
}
|
||||||
|
result, err := util.LogChan("trojan", param, wsConn.CloseChan)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for line := range result {
|
||||||
|
if err := wsConn.WsWrite(ws.TextMessage, []byte(line+"\n")); err != nil {
|
||||||
|
fmt.Println("can't send: ", line)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportCsv 导入csv文件到trojan数据库
|
||||||
|
func ImportCsv(c *gin.Context) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
|
||||||
|
file, header, err := c.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
filename := header.Filename
|
||||||
|
if !strings.Contains(filename, ".csv") {
|
||||||
|
responseBody.Msg = "仅支持导入csv格式的文件"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
reader := csv.NewReader(bufio.NewReader(file))
|
||||||
|
var userList []*core.User
|
||||||
|
for {
|
||||||
|
line, readErr := reader.Read()
|
||||||
|
if readErr == io.EOF {
|
||||||
|
break
|
||||||
|
} else if readErr != nil {
|
||||||
|
responseBody.Msg = readErr.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
quota, _ := strconv.Atoi(line[4])
|
||||||
|
download, _ := strconv.Atoi(line[5])
|
||||||
|
upload, _ := strconv.Atoi(line[6])
|
||||||
|
useDays, _ := strconv.Atoi(line[7])
|
||||||
|
userList = append(userList, &core.User{
|
||||||
|
Username: line[1],
|
||||||
|
Password: line[2],
|
||||||
|
EncryptPass: line[3],
|
||||||
|
Quota: int64(quota),
|
||||||
|
Download: uint64(download),
|
||||||
|
Upload: uint64(upload),
|
||||||
|
UseDays: uint(useDays),
|
||||||
|
ExpiryDate: line[8],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
db := mysql.GetDB()
|
||||||
|
if _, err = db.Exec("DROP TABLE IF EXISTS users;"); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if _, err = db.Exec(core.CreateTableSql); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
for _, user := range userList {
|
||||||
|
if _, err = db.Exec(fmt.Sprintf(`
|
||||||
|
INSERT INTO users(username, password, passwordShow, quota, download, upload, useDays, expiryDate) VALUES ('%s','%s','%s', %d, %d, %d, %d, '%s');`,
|
||||||
|
user.Username, user.EncryptPass, user.Password, user.Quota, user.Download, user.Upload, user.UseDays, user.ExpiryDate)); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportCsv 导出trojan表数据到csv文件
|
||||||
|
func ExportCsv(c *gin.Context) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
var dataBytes = new(bytes.Buffer)
|
||||||
|
//设置UTF-8 BOM, 防止中文乱码
|
||||||
|
dataBytes.WriteString("\xEF\xBB\xBF")
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
userList, err := mysql.GetData()
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
wr := csv.NewWriter(dataBytes)
|
||||||
|
for _, user := range userList {
|
||||||
|
singleUser := []string{
|
||||||
|
strconv.Itoa(int(user.ID)),
|
||||||
|
user.Username,
|
||||||
|
user.Password,
|
||||||
|
user.EncryptPass,
|
||||||
|
strconv.Itoa(int(user.Quota)),
|
||||||
|
strconv.Itoa(int(user.Download)),
|
||||||
|
strconv.Itoa(int(user.Upload)),
|
||||||
|
strconv.Itoa(int(user.UseDays)),
|
||||||
|
user.ExpiryDate,
|
||||||
|
}
|
||||||
|
wr.Write(singleUser)
|
||||||
|
}
|
||||||
|
wr.Flush()
|
||||||
|
c.Writer.Header().Set("Content-type", "application/octet-stream")
|
||||||
|
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s", fmt.Sprintf("%s.csv", mysql.Database)))
|
||||||
|
c.String(200, dataBytes.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/trojan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserList 获取用户列表
|
||||||
|
func UserList(requestUser string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
userList, err := mysql.GetData()
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if requestUser != "admin" {
|
||||||
|
findUser := false
|
||||||
|
for _, user := range userList {
|
||||||
|
if user.Username == requestUser {
|
||||||
|
userList = []*core.User{user}
|
||||||
|
findUser = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !findUser {
|
||||||
|
userList = []*core.User{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
domain, port := trojan.GetDomainAndPort()
|
||||||
|
responseBody.Data = map[string]interface{}{
|
||||||
|
"domain": domain,
|
||||||
|
"port": port,
|
||||||
|
"userList": userList,
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageUserList 分页查询获取用户列表
|
||||||
|
func PageUserList(curPage int, pageSize int) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
pageData, err := mysql.PageList(curPage, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
domain, port := trojan.GetDomainAndPort()
|
||||||
|
responseBody.Data = map[string]interface{}{
|
||||||
|
"domain": domain,
|
||||||
|
"port": port,
|
||||||
|
"pageData": pageData,
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser 创建用户
|
||||||
|
func CreateUser(username string, password string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
if username == "admin" {
|
||||||
|
responseBody.Msg = "不能创建用户名为admin的用户!"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if user := mysql.GetUserByName(username); user != nil {
|
||||||
|
responseBody.Msg = "已存在用户名为: " + username + " 的用户!"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
pass, err := base64.StdEncoding.DecodeString(password)
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = "Base64解码失败: " + err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if user := mysql.GetUserByPass(password); user != nil {
|
||||||
|
responseBody.Msg = "已存在密码为: " + string(pass) + " 的用户!"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if err := mysql.CreateUser(username, password, string(pass)); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUser 更新用户
|
||||||
|
func UpdateUser(id uint, username string, password string) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
if username == "admin" {
|
||||||
|
responseBody.Msg = "不能更改用户名为admin的用户!"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
userList, err := mysql.GetData(strconv.Itoa(int(id)))
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if userList[0].Username != username {
|
||||||
|
if user := mysql.GetUserByName(username); user != nil {
|
||||||
|
responseBody.Msg = "已存在用户名为: " + username + " 的用户!"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pass, err := base64.StdEncoding.DecodeString(password)
|
||||||
|
if err != nil {
|
||||||
|
responseBody.Msg = "Base64解码失败: " + err.Error()
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
if userList[0].Password != password {
|
||||||
|
if user := mysql.GetUserByPass(password); user != nil {
|
||||||
|
responseBody.Msg = "已存在密码为: " + string(pass) + " 的用户!"
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := mysql.UpdateUser(id, username, password, string(pass)); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelUser 删除用户
|
||||||
|
func DelUser(id uint) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.DeleteUser(id); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
} else {
|
||||||
|
trojan.Restart()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExpire 设置用户过期
|
||||||
|
func SetExpire(id uint, useDays uint) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.SetExpire(id, useDays); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelExpire 取消设置用户过期
|
||||||
|
func CancelExpire(id uint) *ResponseBody {
|
||||||
|
responseBody := ResponseBody{Msg: "success"}
|
||||||
|
defer TimeCost(time.Now(), &responseBody)
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
if err := mysql.CancelExpire(id); err != nil {
|
||||||
|
responseBody.Msg = err.Error()
|
||||||
|
}
|
||||||
|
return &responseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClashSubInfo 获取clash订阅信息
|
||||||
|
func ClashSubInfo(c *gin.Context) {
|
||||||
|
token := c.Query("token")
|
||||||
|
if token == "" {
|
||||||
|
c.String(200, "token is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decodeByte, err := base64.StdEncoding.DecodeString(token)
|
||||||
|
if err != nil {
|
||||||
|
c.String(200, "token is error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !gjson.GetBytes(decodeByte, "user").Exists() || !gjson.GetBytes(decodeByte, "pass").Exists() {
|
||||||
|
c.String(200, "token is error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username := gjson.GetBytes(decodeByte, "user").String()
|
||||||
|
password := gjson.GetBytes(decodeByte, "pass").String()
|
||||||
|
|
||||||
|
mysql := core.GetMysql()
|
||||||
|
user := mysql.GetUserByName(username)
|
||||||
|
if user != nil {
|
||||||
|
pass, _ := base64.StdEncoding.DecodeString(user.Password)
|
||||||
|
if password == string(pass) {
|
||||||
|
var wsData, wsHost string
|
||||||
|
userInfo := fmt.Sprintf("upload=%d, download=%d", user.Upload, user.Download)
|
||||||
|
if user.Quota != -1 {
|
||||||
|
userInfo = fmt.Sprintf("%s, total=%d", userInfo, user.Quota)
|
||||||
|
}
|
||||||
|
if user.ExpiryDate != "" {
|
||||||
|
utc, _ := time.LoadLocation("Asia/Shanghai")
|
||||||
|
t, _ := time.ParseInLocation("2006-01-02", user.ExpiryDate, utc)
|
||||||
|
userInfo = fmt.Sprintf("%s, expire=%d", userInfo, t.Unix())
|
||||||
|
}
|
||||||
|
c.Header("content-disposition", fmt.Sprintf("attachment; filename=%s", user.Username))
|
||||||
|
c.Header("subscription-userinfo", userInfo)
|
||||||
|
|
||||||
|
domain, port := trojan.GetDomainAndPort()
|
||||||
|
name := fmt.Sprintf("%s:%d", domain, port)
|
||||||
|
configData := string(core.Load(""))
|
||||||
|
if gjson.Get(configData, "websocket").Exists() && gjson.Get(configData, "websocket.enabled").Bool() {
|
||||||
|
if gjson.Get(configData, "websocket.host").Exists() {
|
||||||
|
hostTemp := gjson.Get(configData, "websocket.host").String()
|
||||||
|
if hostTemp != "" {
|
||||||
|
wsHost = fmt.Sprintf(", headers: {Host: %s}", hostTemp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wsOpt := fmt.Sprintf("{path: %s%s}", gjson.Get(configData, "websocket.path").String(), wsHost)
|
||||||
|
wsData = fmt.Sprintf(", network: ws, udp: true, ws-opts: %s", wsOpt)
|
||||||
|
}
|
||||||
|
proxyData := fmt.Sprintf(" - {name: %s, server: %s, port: %d, type: trojan, password: %s, sni: %s%s}",
|
||||||
|
name, domain, port, password, domain, wsData)
|
||||||
|
result := fmt.Sprintf(`proxies:
|
||||||
|
%s
|
||||||
|
|
||||||
|
proxy-groups:
|
||||||
|
- name: PROXY
|
||||||
|
type: select
|
||||||
|
proxies:
|
||||||
|
- %s
|
||||||
|
|
||||||
|
%s
|
||||||
|
`, proxyData, name, clashRules())
|
||||||
|
c.String(200, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.String(200, "token is error")
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/gzip"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"trojan/core"
|
||||||
|
"trojan/util"
|
||||||
|
"trojan/web/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates/*
|
||||||
|
var f embed.FS
|
||||||
|
|
||||||
|
func userRouter(router *gin.Engine) {
|
||||||
|
user := router.Group("/trojan/user")
|
||||||
|
{
|
||||||
|
user.GET("", func(c *gin.Context) {
|
||||||
|
requestUser := RequestUsername(c)
|
||||||
|
c.JSON(200, controller.UserList(requestUser))
|
||||||
|
})
|
||||||
|
user.GET("/page", func(c *gin.Context) {
|
||||||
|
curPageStr := c.DefaultQuery("curPage", "1")
|
||||||
|
pageSizeStr := c.DefaultQuery("pageSize", "10")
|
||||||
|
curPage, _ := strconv.Atoi(curPageStr)
|
||||||
|
pageSize, _ := strconv.Atoi(pageSizeStr)
|
||||||
|
c.JSON(200, controller.PageUserList(curPage, pageSize))
|
||||||
|
})
|
||||||
|
user.POST("", func(c *gin.Context) {
|
||||||
|
username := c.PostForm("username")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
c.JSON(200, controller.CreateUser(username, password))
|
||||||
|
})
|
||||||
|
user.POST("/update", func(c *gin.Context) {
|
||||||
|
sid := c.PostForm("id")
|
||||||
|
username := c.PostForm("username")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
id, _ := strconv.Atoi(sid)
|
||||||
|
c.JSON(200, controller.UpdateUser(uint(id), username, password))
|
||||||
|
})
|
||||||
|
user.POST("/expire", func(c *gin.Context) {
|
||||||
|
sid := c.PostForm("id")
|
||||||
|
sDays := c.PostForm("useDays")
|
||||||
|
id, _ := strconv.Atoi(sid)
|
||||||
|
useDays, _ := strconv.Atoi(sDays)
|
||||||
|
c.JSON(200, controller.SetExpire(uint(id), uint(useDays)))
|
||||||
|
})
|
||||||
|
user.DELETE("/expire", func(c *gin.Context) {
|
||||||
|
sid := c.Query("id")
|
||||||
|
id, _ := strconv.Atoi(sid)
|
||||||
|
c.JSON(200, controller.CancelExpire(uint(id)))
|
||||||
|
})
|
||||||
|
user.DELETE("", func(c *gin.Context) {
|
||||||
|
stringId := c.Query("id")
|
||||||
|
id, _ := strconv.Atoi(stringId)
|
||||||
|
c.JSON(200, controller.DelUser(uint(id)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojanRouter(router *gin.Engine) {
|
||||||
|
router.POST("/trojan/start", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.Start())
|
||||||
|
})
|
||||||
|
router.POST("/trojan/stop", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.Stop())
|
||||||
|
})
|
||||||
|
router.POST("/trojan/restart", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.Restart())
|
||||||
|
})
|
||||||
|
router.GET("/trojan/loglevel", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.GetLogLevel())
|
||||||
|
})
|
||||||
|
router.GET("/trojan/export", func(c *gin.Context) {
|
||||||
|
result := controller.ExportCsv(c)
|
||||||
|
if result != nil {
|
||||||
|
c.JSON(200, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.POST("/trojan/import", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.ImportCsv(c))
|
||||||
|
})
|
||||||
|
router.POST("/trojan/update", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.Update())
|
||||||
|
})
|
||||||
|
router.POST("/trojan/switch", func(c *gin.Context) {
|
||||||
|
tType := c.DefaultPostForm("type", "trojan")
|
||||||
|
c.JSON(200, controller.SetTrojanType(tType))
|
||||||
|
})
|
||||||
|
router.POST("/trojan/loglevel", func(c *gin.Context) {
|
||||||
|
slevel := c.DefaultPostForm("level", "1")
|
||||||
|
level, _ := strconv.Atoi(slevel)
|
||||||
|
c.JSON(200, controller.SetLogLevel(level))
|
||||||
|
})
|
||||||
|
router.POST("/trojan/domain", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.SetDomain(c.PostForm("domain")))
|
||||||
|
})
|
||||||
|
router.GET("/trojan/log", func(c *gin.Context) {
|
||||||
|
controller.Log(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataRouter(router *gin.Engine) {
|
||||||
|
data := router.Group("/trojan/data")
|
||||||
|
{
|
||||||
|
data.POST("", func(c *gin.Context) {
|
||||||
|
sID := c.PostForm("id")
|
||||||
|
sQuota := c.PostForm("quota")
|
||||||
|
id, _ := strconv.Atoi(sID)
|
||||||
|
quota, _ := strconv.Atoi(sQuota)
|
||||||
|
c.JSON(200, controller.SetData(uint(id), quota))
|
||||||
|
})
|
||||||
|
data.DELETE("", func(c *gin.Context) {
|
||||||
|
sID := c.Query("id")
|
||||||
|
id, _ := strconv.Atoi(sID)
|
||||||
|
c.JSON(200, controller.CleanData(uint(id)))
|
||||||
|
})
|
||||||
|
data.POST("/resetDay", func(c *gin.Context) {
|
||||||
|
dayStr := c.DefaultPostForm("day", "1")
|
||||||
|
day, _ := strconv.Atoi(dayStr)
|
||||||
|
c.JSON(200, controller.UpdateResetDay(uint(day)))
|
||||||
|
})
|
||||||
|
data.GET("/resetDay", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.GetResetDay())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commonRouter(router *gin.Engine) {
|
||||||
|
common := router.Group("/common")
|
||||||
|
{
|
||||||
|
common.GET("/version", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.Version())
|
||||||
|
})
|
||||||
|
common.GET("/serverInfo", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.ServerInfo())
|
||||||
|
})
|
||||||
|
common.GET("/clashRules", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.GetClashRules())
|
||||||
|
})
|
||||||
|
common.POST("/clashRules", func(c *gin.Context) {
|
||||||
|
rules := c.PostForm("rules")
|
||||||
|
c.JSON(200, controller.SetClashRules(rules))
|
||||||
|
})
|
||||||
|
common.DELETE("/clashRules", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.ResetClashRules())
|
||||||
|
})
|
||||||
|
common.POST("/loginInfo", func(c *gin.Context) {
|
||||||
|
c.JSON(200, controller.SetLoginInfo(c.PostForm("title")))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func staticRouter(router *gin.Engine) {
|
||||||
|
staticFs, _ := fs.Sub(f, "templates/static")
|
||||||
|
router.StaticFS("/static", http.FS(staticFs))
|
||||||
|
|
||||||
|
router.GET("/", func(c *gin.Context) {
|
||||||
|
indexHTML, _ := f.ReadFile("templates/" + "index.html")
|
||||||
|
c.Writer.Write(indexHTML)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func noTokenRouter(router *gin.Engine) {
|
||||||
|
router.GET("/trojan/user/subscribe", func(c *gin.Context) {
|
||||||
|
controller.ClashSubInfo(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start web启动入口
|
||||||
|
func Start(host string, port, timeout int, isSSL bool) {
|
||||||
|
router := gin.Default()
|
||||||
|
router.SetTrustedProxies(nil)
|
||||||
|
router.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
|
staticRouter(router)
|
||||||
|
noTokenRouter(router)
|
||||||
|
router.Use(Auth(router, timeout).MiddlewareFunc())
|
||||||
|
trojanRouter(router)
|
||||||
|
userRouter(router)
|
||||||
|
dataRouter(router)
|
||||||
|
commonRouter(router)
|
||||||
|
controller.ScheduleTask()
|
||||||
|
controller.CollectTask()
|
||||||
|
util.OpenPort(port)
|
||||||
|
if isSSL {
|
||||||
|
config := core.GetConfig()
|
||||||
|
ssl := &config.SSl
|
||||||
|
router.RunTLS(fmt.Sprintf("%s:%d", host, port), ssl.Cert, ssl.Key)
|
||||||
|
} else {
|
||||||
|
router.Run(fmt.Sprintf("%s:%d", host, port))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended'
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote":true,
|
||||||
|
"semi":false
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# trojan-web
|
||||||
|
|
||||||
|
[trojan管理程序](https://github.com/Jrohy/trojan)前端页面
|
||||||
|
|
||||||
|
技术栈: vue3.0 + element-plus + vite
|
||||||
|
|
||||||
|
### 项目启动步骤
|
||||||
|
1. 安装包
|
||||||
|
`npm install`
|
||||||
|
2. 开发运行
|
||||||
|
`vite`
|
||||||
|
3. 生产打包
|
||||||
|
`vite build`
|
||||||
|
|
||||||
|
**欢迎fork以改进优化页面**
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
<link rel="icon" href="data:;base64,=">
|
||||||
|
<link rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css">
|
||||||
|
<link rel="stylesheet" href="https://lib.baomitu.com/element-plus/2.3.14/index.min.css">
|
||||||
|
<script src="https://lib.baomitu.com/vue/latest/vue.global.prod.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/vue-router/4.2.5/vue-router.global.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/vuex/4.1.0/vuex.global.prod.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/vue-i18n/9.4.1/vue-i18n.global.prod.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/axios/1.5.0/axios.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/crypto-js/4.1.1/crypto-js.min.js"></script>
|
||||||
|
<script src="https://npm.elemecdn.com/easyqrcodejs@4.4.13/dist/easy.qrcode.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/dayjs/1.11.10/dayjs.min.js"></script>
|
||||||
|
<script src="https://lib.baomitu.com/element-plus/2.3.14/index.full.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>Please enable javascript to use.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "trojan-web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.5.0",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"easyqrcodejs": "^4.4.13",
|
||||||
|
"element-plus": "^2.3.14",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"vue": "^3.3.4",
|
||||||
|
"vue-i18n": "^9.4.1",
|
||||||
|
"vue-router": "^4.2.5",
|
||||||
|
"vuex": "^4.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^4.3.4",
|
||||||
|
"@vue/compiler-sfc": "^3.3.4",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
|
"eslint": "^8.50.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"rollup-plugin-external-globals": "^0.8.0",
|
||||||
|
"sass": "^1.68.0",
|
||||||
|
"terser": "^5.20.0",
|
||||||
|
"vite": "^4.4.9",
|
||||||
|
"vite-plugin-svg-icons": "^1.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function version() {
|
||||||
|
return request.get('/common/version')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serverInfo() {
|
||||||
|
return request.get('/common/serverInfo')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLoginInfo(data) {
|
||||||
|
return request.post('/common/loginInfo', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setClashRules(data) {
|
||||||
|
return request.post('/common/clashRules', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getClashRules() {
|
||||||
|
return request.get('/common/clashRules')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetClashRules() {
|
||||||
|
return request.delete('/common/clashRules')
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function setQuota(data) {
|
||||||
|
return request.post('/trojan/data', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanData(id) {
|
||||||
|
return request.delete(`/trojan/data?id=${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResetDay() {
|
||||||
|
return request.get('/trojan/data/resetDay')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateResetDay(data) {
|
||||||
|
return request.post('/trojan/data/resetDay', data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 登录接口
|
||||||
|
export function login(data) {
|
||||||
|
return request.post('/auth/login', data)
|
||||||
|
}
|
||||||
|
// 注册接口
|
||||||
|
export function register(data) {
|
||||||
|
return request.post('/auth/register', data)
|
||||||
|
}
|
||||||
|
// 检查有没有初始化接口
|
||||||
|
export function check() {
|
||||||
|
return request.get('/auth/check')
|
||||||
|
}
|
||||||
|
// 重置密码接口
|
||||||
|
export function resetPass(data) {
|
||||||
|
return request.post('/auth/reset_pass', data)
|
||||||
|
}
|
||||||
|
// 获取请求用户名
|
||||||
|
export function loginUser() {
|
||||||
|
return request.get('/auth/loginUser')
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function start() {
|
||||||
|
return request.post('/trojan/start')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stop() {
|
||||||
|
return request.post('/trojan/stop')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restart() {
|
||||||
|
return request.post('/trojan/restart')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update() {
|
||||||
|
return request.post('/trojan/update')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLoglevel() {
|
||||||
|
return request.get('/trojan/loglevel')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLoglevel(data) {
|
||||||
|
return request.post('/trojan/loglevel', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDomain(data) {
|
||||||
|
return request.post('/trojan/domain', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trojanSwitch(data) {
|
||||||
|
return request.post('/trojan/switch', data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function userList() {
|
||||||
|
return request.get('/trojan/user')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addUser(data) {
|
||||||
|
return request.post('/trojan/user', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateUser(data) {
|
||||||
|
return request.post('/trojan/user/update', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delUser(id) {
|
||||||
|
return request.delete(`/trojan/user?id=${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setExpire(data) {
|
||||||
|
return request.post('/trojan/user/expire', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cancelExpire(id) {
|
||||||
|
return request.delete(`/trojan/user/expire?id=${id}`)
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||||
|
<transition-group name="breadcrumb">
|
||||||
|
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||||
|
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">
|
||||||
|
{{ generateTitle(item.meta.title) }}
|
||||||
|
</span>
|
||||||
|
<a v-else @click.prevent="handleLink(item)">{{ generateTitle(item.meta.title) }}</a>
|
||||||
|
</el-breadcrumb-item>
|
||||||
|
</transition-group>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
levelList: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getBreadcrumb()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
generateTitle(title) {
|
||||||
|
const hasKey = this.$t('route.' + title)
|
||||||
|
if (hasKey) {
|
||||||
|
const translatedTitle = this.$t('route.' + title)
|
||||||
|
return translatedTitle
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
},
|
||||||
|
getBreadcrumb() {
|
||||||
|
// only show routes with meta.title
|
||||||
|
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||||
|
const first = matched[0]
|
||||||
|
if (!this.isDashboard(first)) {
|
||||||
|
matched = [{ path: '/dashboard', meta: { title: 'dashboard' } }].concat(matched)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||||
|
},
|
||||||
|
isDashboard(route) {
|
||||||
|
const name = route && route.name
|
||||||
|
if (!name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
|
||||||
|
},
|
||||||
|
handleLink(item) {
|
||||||
|
const { redirect, path } = item
|
||||||
|
if (redirect) {
|
||||||
|
this.$router.push(redirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-breadcrumb.el-breadcrumb {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 50px;
|
||||||
|
margin-left: 8px;
|
||||||
|
|
||||||
|
.no-redirect {
|
||||||
|
color: #97a8be;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding: 0 15px;" @click="toggleClick">
|
||||||
|
<svg
|
||||||
|
:class="{'is-active':isActive}"
|
||||||
|
class="hamburger"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
>
|
||||||
|
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Hamburger',
|
||||||
|
props: {
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleClick() {
|
||||||
|
this.$emit('toggleClick')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hamburger {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger.is-active {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
:class="svgClass"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use :xlink:href="iconName"></use>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'svg-icon',
|
||||||
|
props: {
|
||||||
|
iconClass: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
className: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
iconName() {
|
||||||
|
return `#icon-${this.iconClass}`
|
||||||
|
},
|
||||||
|
svgClass() {
|
||||||
|
if (this.className) {
|
||||||
|
return 'svg-icon ' + this.className
|
||||||
|
} else {
|
||||||
|
return 'svg-icon'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.svg-icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
const Mixins = import.meta.globEager("./svg/*.svg")
|
||||||
|
export default {
|
||||||
|
mixins: Object.values(Mixins).map((v) => v.default)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>
|
||||||
|
After Width: | Height: | Size: 418 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
|
||||||
|
After Width: | Height: | Size: 944 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
|
||||||
|
After Width: | Height: | Size: 623 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
|
||||||
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,122 @@
|
|||||||
|
export default {
|
||||||
|
name: 'en',
|
||||||
|
el: {
|
||||||
|
colorpicker: {
|
||||||
|
confirm: 'OK',
|
||||||
|
clear: 'Clear',
|
||||||
|
},
|
||||||
|
datepicker: {
|
||||||
|
now: 'Now',
|
||||||
|
today: 'Today',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
clear: 'Clear',
|
||||||
|
confirm: 'OK',
|
||||||
|
selectDate: 'Select date',
|
||||||
|
selectTime: 'Select time',
|
||||||
|
startDate: 'Start Date',
|
||||||
|
startTime: 'Start Time',
|
||||||
|
endDate: 'End Date',
|
||||||
|
endTime: 'End Time',
|
||||||
|
prevYear: 'Previous Year',
|
||||||
|
nextYear: 'Next Year',
|
||||||
|
prevMonth: 'Previous Month',
|
||||||
|
nextMonth: 'Next Month',
|
||||||
|
year: '',
|
||||||
|
month1: 'January',
|
||||||
|
month2: 'February',
|
||||||
|
month3: 'March',
|
||||||
|
month4: 'April',
|
||||||
|
month5: 'May',
|
||||||
|
month6: 'June',
|
||||||
|
month7: 'July',
|
||||||
|
month8: 'August',
|
||||||
|
month9: 'September',
|
||||||
|
month10: 'October',
|
||||||
|
month11: 'November',
|
||||||
|
month12: 'December',
|
||||||
|
week: 'week',
|
||||||
|
weeks: {
|
||||||
|
sun: 'Sun',
|
||||||
|
mon: 'Mon',
|
||||||
|
tue: 'Tue',
|
||||||
|
wed: 'Wed',
|
||||||
|
thu: 'Thu',
|
||||||
|
fri: 'Fri',
|
||||||
|
sat: 'Sat',
|
||||||
|
},
|
||||||
|
months: {
|
||||||
|
jan: 'Jan',
|
||||||
|
feb: 'Feb',
|
||||||
|
mar: 'Mar',
|
||||||
|
apr: 'Apr',
|
||||||
|
may: 'May',
|
||||||
|
jun: 'Jun',
|
||||||
|
jul: 'Jul',
|
||||||
|
aug: 'Aug',
|
||||||
|
sep: 'Sep',
|
||||||
|
oct: 'Oct',
|
||||||
|
nov: 'Nov',
|
||||||
|
dec: 'Dec',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
loading: 'Loading',
|
||||||
|
noMatch: 'No matching data',
|
||||||
|
noData: 'No data',
|
||||||
|
placeholder: 'Select',
|
||||||
|
},
|
||||||
|
cascader: {
|
||||||
|
noMatch: 'No matching data',
|
||||||
|
loading: 'Loading',
|
||||||
|
placeholder: 'Select',
|
||||||
|
noData: 'No data',
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
goto: 'Go to',
|
||||||
|
pagesize: '/page',
|
||||||
|
total: 'Total {total}',
|
||||||
|
pageClassifier: '',
|
||||||
|
deprecationWarning: 'Deprecated usages detected, please refer to the el-pagination documentation for more details',
|
||||||
|
},
|
||||||
|
messagebox: {
|
||||||
|
title: 'Message',
|
||||||
|
confirm: 'OK',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
error: 'Illegal input',
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
deleteTip: 'press delete to remove',
|
||||||
|
delete: 'Delete',
|
||||||
|
preview: 'Preview',
|
||||||
|
continue: 'Continue',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
emptyText: 'No Data',
|
||||||
|
confirmFilter: 'Confirm',
|
||||||
|
resetFilter: 'Reset',
|
||||||
|
clearFilter: 'All',
|
||||||
|
sumText: 'Sum',
|
||||||
|
},
|
||||||
|
tree: {
|
||||||
|
emptyText: 'No Data',
|
||||||
|
},
|
||||||
|
transfer: {
|
||||||
|
noMatch: 'No matching data',
|
||||||
|
noData: 'No data',
|
||||||
|
titles: ['List 1', 'List 2'],
|
||||||
|
filterPlaceholder: 'Enter keyword',
|
||||||
|
noCheckedFormat: '{total} items',
|
||||||
|
hasCheckedFormat: '{checked}/{total} checked', // to be translated
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
error: 'FAILED',
|
||||||
|
},
|
||||||
|
pageHeader: {
|
||||||
|
title: 'Back', // to be translated
|
||||||
|
},
|
||||||
|
popconfirm: {
|
||||||
|
confirmButtonText: 'Yes',
|
||||||
|
cancelButtonText: 'No',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
export default {
|
||||||
|
name: 'zh-cn',
|
||||||
|
el: {
|
||||||
|
colorpicker: {
|
||||||
|
confirm: '确定',
|
||||||
|
clear: '清空',
|
||||||
|
},
|
||||||
|
datepicker: {
|
||||||
|
now: '此刻',
|
||||||
|
today: '今天',
|
||||||
|
cancel: '取消',
|
||||||
|
clear: '清空',
|
||||||
|
confirm: '确定',
|
||||||
|
selectDate: '选择日期',
|
||||||
|
selectTime: '选择时间',
|
||||||
|
startDate: '开始日期',
|
||||||
|
startTime: '开始时间',
|
||||||
|
endDate: '结束日期',
|
||||||
|
endTime: '结束时间',
|
||||||
|
prevYear: '前一年',
|
||||||
|
nextYear: '后一年',
|
||||||
|
prevMonth: '上个月',
|
||||||
|
nextMonth: '下个月',
|
||||||
|
year: '年',
|
||||||
|
month1: '1 月',
|
||||||
|
month2: '2 月',
|
||||||
|
month3: '3 月',
|
||||||
|
month4: '4 月',
|
||||||
|
month5: '5 月',
|
||||||
|
month6: '6 月',
|
||||||
|
month7: '7 月',
|
||||||
|
month8: '8 月',
|
||||||
|
month9: '9 月',
|
||||||
|
month10: '10 月',
|
||||||
|
month11: '11 月',
|
||||||
|
month12: '12 月',
|
||||||
|
// week: '周次',
|
||||||
|
weeks: {
|
||||||
|
sun: '日',
|
||||||
|
mon: '一',
|
||||||
|
tue: '二',
|
||||||
|
wed: '三',
|
||||||
|
thu: '四',
|
||||||
|
fri: '五',
|
||||||
|
sat: '六',
|
||||||
|
},
|
||||||
|
months: {
|
||||||
|
jan: '一月',
|
||||||
|
feb: '二月',
|
||||||
|
mar: '三月',
|
||||||
|
apr: '四月',
|
||||||
|
may: '五月',
|
||||||
|
jun: '六月',
|
||||||
|
jul: '七月',
|
||||||
|
aug: '八月',
|
||||||
|
sep: '九月',
|
||||||
|
oct: '十月',
|
||||||
|
nov: '十一月',
|
||||||
|
dec: '十二月',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
loading: '加载中',
|
||||||
|
noMatch: '无匹配数据',
|
||||||
|
noData: '无数据',
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
cascader: {
|
||||||
|
noMatch: '无匹配数据',
|
||||||
|
loading: '加载中',
|
||||||
|
placeholder: '请选择',
|
||||||
|
noData: '暂无数据',
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
goto: '前往',
|
||||||
|
pagesize: '条/页',
|
||||||
|
total: '共 {total} 条',
|
||||||
|
pageClassifier: '页',
|
||||||
|
deprecationWarning: '你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档',
|
||||||
|
},
|
||||||
|
messagebox: {
|
||||||
|
title: '提示',
|
||||||
|
confirm: '确定',
|
||||||
|
cancel: '取消',
|
||||||
|
error: '输入的数据不合法!',
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
deleteTip: '按 delete 键可删除',
|
||||||
|
delete: '删除',
|
||||||
|
preview: '查看图片',
|
||||||
|
continue: '继续上传',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
emptyText: '暂无数据',
|
||||||
|
confirmFilter: '筛选',
|
||||||
|
resetFilter: '重置',
|
||||||
|
clearFilter: '全部',
|
||||||
|
sumText: '合计',
|
||||||
|
},
|
||||||
|
tree: {
|
||||||
|
emptyText: '暂无数据',
|
||||||
|
},
|
||||||
|
transfer: {
|
||||||
|
noMatch: '无匹配数据',
|
||||||
|
noData: '无数据',
|
||||||
|
titles: ['列表 1', '列表 2'],
|
||||||
|
filterPlaceholder: '请输入搜索内容',
|
||||||
|
noCheckedFormat: '共 {total} 项',
|
||||||
|
hasCheckedFormat: '已选 {checked}/{total} 项',
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
error: '加载失败',
|
||||||
|
},
|
||||||
|
pageHeader: {
|
||||||
|
title: '返回',
|
||||||
|
},
|
||||||
|
popconfirm: {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
export default {
|
||||||
|
ok: 'ok',
|
||||||
|
cancel: 'cancel',
|
||||||
|
reset: 'reset',
|
||||||
|
inputPass: 'input password',
|
||||||
|
inputPassAgain: 'input password again',
|
||||||
|
passLessError: "password number can't be less than 5",
|
||||||
|
passDifferentError: 'The two inputs are inconsistent!',
|
||||||
|
setupPass: 'setup admin password',
|
||||||
|
inputNotNull: "username or password can't be null!",
|
||||||
|
route: {
|
||||||
|
dashboard: 'Dashboard',
|
||||||
|
trojan: 'Trojan',
|
||||||
|
user: 'User'
|
||||||
|
},
|
||||||
|
navbar: {
|
||||||
|
version: 'System Version',
|
||||||
|
setting: 'System Setting',
|
||||||
|
password: 'Reset Password',
|
||||||
|
title: 'Change Title',
|
||||||
|
importExport: 'Import Export',
|
||||||
|
resetDay: 'Change Reset Day',
|
||||||
|
clashRules: 'Change Clash Rules',
|
||||||
|
resetTitle: 'Change data reset day of the month',
|
||||||
|
changeTitle: 'Change login page title',
|
||||||
|
changeRules: 'Change clash rules',
|
||||||
|
changeTitleSuccess: 'Change title success!',
|
||||||
|
changeRulesSuccess: 'Change clash rules success!',
|
||||||
|
resetRulesSuccess: 'Reset clash rules success!',
|
||||||
|
changeDaySuccess: 'Change data reset day success!',
|
||||||
|
closeResetSuccess: 'Close data reset success!',
|
||||||
|
inputTitle: 'Input login page title',
|
||||||
|
versionTitle: 'Trojan-manager Version',
|
||||||
|
passwordTitle: 'Change admin password',
|
||||||
|
resetSuccess: 'reset password success!',
|
||||||
|
meanClose: '0 means close data reset',
|
||||||
|
exportTip: 'export all database record to csv file',
|
||||||
|
importTip: 'import csv to database',
|
||||||
|
importSuccess: 'Import Success',
|
||||||
|
importCsv: 'import csv file',
|
||||||
|
exportCsv: 'export csv file'
|
||||||
|
},
|
||||||
|
dashboard: {
|
||||||
|
memory: 'memory',
|
||||||
|
disk: 'disk',
|
||||||
|
trojanVersion: 'Trojan Version',
|
||||||
|
trojanUser: 'Trojan User',
|
||||||
|
trojanUptime: 'Trojan Uptime',
|
||||||
|
netSpeed: 'Network Speed',
|
||||||
|
netCount: 'Connection Count',
|
||||||
|
load: 'Load',
|
||||||
|
upload: 'upload',
|
||||||
|
download: 'download',
|
||||||
|
total: 'total',
|
||||||
|
day: ' day',
|
||||||
|
hour: ' hour',
|
||||||
|
minute: ' minute',
|
||||||
|
second: ' second'
|
||||||
|
},
|
||||||
|
trojan: {
|
||||||
|
restartSuccess: 'restart trojan success!',
|
||||||
|
startSuccess: 'start trojan success!',
|
||||||
|
stopSuccess: 'stop trojan success!',
|
||||||
|
updateSuccess: 'update trojan success!',
|
||||||
|
loglevelSuccess: 'set loglevel success!',
|
||||||
|
switchSuccess: 'switch trojan type success!'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
upload: 'upload',
|
||||||
|
download: 'download',
|
||||||
|
total: 'total',
|
||||||
|
quota: 'quota',
|
||||||
|
expiryDate: 'expiryDate',
|
||||||
|
setExpire: 'setExpire',
|
||||||
|
editExpire: 'editExpire',
|
||||||
|
cancelExpire: 'cancelExpire',
|
||||||
|
reset: 'reset data',
|
||||||
|
unlimit: 'unlimited',
|
||||||
|
search: 'search username',
|
||||||
|
limitData: 'limit data',
|
||||||
|
modifyUser: 'modify user',
|
||||||
|
trojanShareLink: 'trojan share link',
|
||||||
|
clashShareLink: 'clash share link',
|
||||||
|
importClash: 'import to clash',
|
||||||
|
meanUnlimit: '-1 means unlimited',
|
||||||
|
inputUsername: 'username',
|
||||||
|
inputPassword: 'password',
|
||||||
|
operate: 'operate',
|
||||||
|
addUser: 'add trojan user',
|
||||||
|
patchLimitUser: 'patch limit user',
|
||||||
|
patchDelUser: 'patch delete user?',
|
||||||
|
modifyUser2: 'modify user ',
|
||||||
|
userpass: 'username and password',
|
||||||
|
delUser: 'delete user ',
|
||||||
|
patchReset: 'patch reset data?',
|
||||||
|
resetUser: 'reset user ',
|
||||||
|
data: ' data?',
|
||||||
|
limitUser: 'limit user ',
|
||||||
|
limitUser2: 'limit user ',
|
||||||
|
setupUser: 'setup user ',
|
||||||
|
limitSuccess: ' data success!',
|
||||||
|
delUser1: 'delete user ',
|
||||||
|
success: ' success!',
|
||||||
|
resetUser1: 'reset user ',
|
||||||
|
limitAdmin: "can't create username is admin",
|
||||||
|
passLimit: "password doesn't support chinese!",
|
||||||
|
addUser2: 'add user ',
|
||||||
|
week: 'a week',
|
||||||
|
month: 'a month',
|
||||||
|
season: 'one season',
|
||||||
|
halfYear: 'half a year',
|
||||||
|
year: 'one year',
|
||||||
|
preset: 'preset',
|
||||||
|
days: 'days',
|
||||||
|
setExpireSuccess: 'setup expire success!',
|
||||||
|
cancelExpireSuccess: 'cancel expire success!',
|
||||||
|
remaining: 'remaining days'
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
requestError: 'request error',
|
||||||
|
authFail: 'auth fail, please login again',
|
||||||
|
accessDenied: 'access denied',
|
||||||
|
notFound: '404 not found',
|
||||||
|
serverError: 'server error',
|
||||||
|
connectError: 'connect error'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import elementEnLocale from './element/en' // element-ui lang
|
||||||
|
import elementZhLocale from './element/zh-cn'// element-ui lang
|
||||||
|
import enLocale from './en'
|
||||||
|
import zhLocale from './zh'
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
en: {
|
||||||
|
...enLocale,
|
||||||
|
...elementEnLocale
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
...zhLocale,
|
||||||
|
...elementZhLocale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getLanguage() {
|
||||||
|
const chooseLanguage = localStorage.getItem('language')
|
||||||
|
if (chooseLanguage) return chooseLanguage
|
||||||
|
|
||||||
|
// if has not choose language
|
||||||
|
const language = (navigator.language || navigator.browserLanguage).toLowerCase()
|
||||||
|
const locales = Object.keys(messages)
|
||||||
|
for (const locale of locales) {
|
||||||
|
if (language.indexOf(locale) > -1) {
|
||||||
|
return locale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'en'
|
||||||
|
}
|
||||||
|
const i18n = createI18n({
|
||||||
|
// set locale
|
||||||
|
// options: en | zh
|
||||||
|
locale: getLanguage(),
|
||||||
|
// set locale messages
|
||||||
|
messages: messages,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
silentFallbackWarn: true,
|
||||||
|
silentTranslationWarn: true
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
export default {
|
||||||
|
ok: '确定',
|
||||||
|
cancel: '取消',
|
||||||
|
reset: '重置',
|
||||||
|
login: '登录',
|
||||||
|
register: '注册',
|
||||||
|
log: '日志',
|
||||||
|
edit: '编辑',
|
||||||
|
share: '分享',
|
||||||
|
terminal: '终端',
|
||||||
|
refresh: '刷新',
|
||||||
|
restart: '重启',
|
||||||
|
start: '启动',
|
||||||
|
stop: '暂停',
|
||||||
|
update: '更新',
|
||||||
|
add: '添加',
|
||||||
|
username: '用户名',
|
||||||
|
password: '密码',
|
||||||
|
loglevel: '日志等级',
|
||||||
|
all: '所有',
|
||||||
|
type: '类型',
|
||||||
|
line: '初始行数',
|
||||||
|
choice: '请选择',
|
||||||
|
latest: '最新日志',
|
||||||
|
delete: '删除',
|
||||||
|
inputPass: '输入密码',
|
||||||
|
inputPassAgain: '请再次输入密码',
|
||||||
|
passLessError: '密码不能小于5位',
|
||||||
|
passDifferentError: '两次输入不一致!',
|
||||||
|
setupPass: '设置admin密码',
|
||||||
|
inputNotNull: '用户名或密码不能为空!',
|
||||||
|
route: {
|
||||||
|
dashboard: '首页',
|
||||||
|
trojan: 'trojan管理',
|
||||||
|
user: '用户管理'
|
||||||
|
},
|
||||||
|
navbar: {
|
||||||
|
version: '系统版本',
|
||||||
|
setting: '系统设置',
|
||||||
|
password: '修改密码',
|
||||||
|
title: '修改标题',
|
||||||
|
clashRules: '修改规则',
|
||||||
|
importExport: '导入导出',
|
||||||
|
resetDay: '改重置日',
|
||||||
|
resetTitle: '修改流量重置日',
|
||||||
|
changeTitle: '修改登录页标题',
|
||||||
|
changeRules: '修改clash规则',
|
||||||
|
inputTitle: '输入登录页标题',
|
||||||
|
changeTitleSuccess: '修改登录页标题成功!',
|
||||||
|
changeRulesSuccess: '修改clash规则成功!',
|
||||||
|
resetRulesSuccess: '重置clash规则成功!',
|
||||||
|
changeDaySuccess: '修改流量重置日成功!',
|
||||||
|
closeResetSuccess: '关闭流量重置成功',
|
||||||
|
versionTitle: 'trojan管理程序版本',
|
||||||
|
passwordTitle: '变更管理员密码',
|
||||||
|
resetSuccess: '重置密码成功!',
|
||||||
|
meanClose: '0代表关闭流量重置',
|
||||||
|
exportTip: '导出数据库里所有记录到csv文件',
|
||||||
|
importTip: '导入csv文件数据到数据库',
|
||||||
|
importSuccess: '导入成功',
|
||||||
|
importCsv: '导入csv文件',
|
||||||
|
exportCsv: '导出csv文件'
|
||||||
|
},
|
||||||
|
dashboard: {
|
||||||
|
memory: '内存',
|
||||||
|
disk: '硬盘',
|
||||||
|
trojanVersion: 'trojan 版本',
|
||||||
|
trojanUser: 'trojan 用户数',
|
||||||
|
trojanUptime: 'trojan 已运行',
|
||||||
|
netSpeed: '网速',
|
||||||
|
netCount: '连接数',
|
||||||
|
load: '服务器负载',
|
||||||
|
upload: '上传',
|
||||||
|
download: '下载',
|
||||||
|
total: '总共',
|
||||||
|
day: '天',
|
||||||
|
hour: '时',
|
||||||
|
minute: '分',
|
||||||
|
second: '秒'
|
||||||
|
},
|
||||||
|
trojan: {
|
||||||
|
restartSuccess: '重启trojan成功!',
|
||||||
|
startSuccess: '启动trojan成功!',
|
||||||
|
stopSuccess: '停止trojan成功!',
|
||||||
|
updateSuccess: '更新trojan成功!',
|
||||||
|
loglevelSuccess: '设置日志等级成功!',
|
||||||
|
switchSuccess: '切换trojan类型成功!'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
upload: '上传流量',
|
||||||
|
download: '下载流量',
|
||||||
|
total: '总流量',
|
||||||
|
quota: '流量限制',
|
||||||
|
expiryDate: '到期日期',
|
||||||
|
setExpire: '设置限期',
|
||||||
|
editExpire: '修改限期',
|
||||||
|
cancelExpire: '取消限期',
|
||||||
|
reset: '重置流量',
|
||||||
|
unlimit: '无',
|
||||||
|
search: '输入用户名搜索',
|
||||||
|
limitData: '限制流量',
|
||||||
|
modifyUser: '修改账密',
|
||||||
|
trojanShareLink: 'trojan链接',
|
||||||
|
clashShareLink: 'clash链接',
|
||||||
|
importClash: '导到clash',
|
||||||
|
meanUnlimit: '-1代表无流量限制',
|
||||||
|
inputUsername: '输入用户名',
|
||||||
|
inputPassword: '输入密码',
|
||||||
|
operate: '操作',
|
||||||
|
addUser: '新增trojan用户',
|
||||||
|
patchLimitUser: '限制以下用户流量',
|
||||||
|
patchDelUser: '确定批量删除以下用户?',
|
||||||
|
modifyUser2: '修改用户 ',
|
||||||
|
userpass: '的用户名和密码',
|
||||||
|
delUser: '确定删除用户 ',
|
||||||
|
patchReset: '确定重置以下用户流量?',
|
||||||
|
resetUser: '确定重置用户 ',
|
||||||
|
data: ' 的流量?',
|
||||||
|
limitUser: '确定限制用户 ',
|
||||||
|
limitUser2: '限制用户 ',
|
||||||
|
limitSuccess: '流量成功!',
|
||||||
|
delUser1: '删除用户',
|
||||||
|
success: '成功!',
|
||||||
|
resetUser1: '重置用户',
|
||||||
|
limitAdmin: '不能创建用户名为admin的用户!',
|
||||||
|
passLimit: '密码不支持中文!',
|
||||||
|
addUser2: '新增用户',
|
||||||
|
week: '单周',
|
||||||
|
month: '单月',
|
||||||
|
season: '单季',
|
||||||
|
halfYear: '半年',
|
||||||
|
year: '一年',
|
||||||
|
preset: '预设',
|
||||||
|
days: '天数',
|
||||||
|
setExpireSuccess: '设置用户限期成功!',
|
||||||
|
cancelExpireSuccess: '取消用户限期成功!',
|
||||||
|
remaining: '剩余天数'
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
requestError: '请求出错',
|
||||||
|
authFail: '授权失败,请重新登录',
|
||||||
|
accessDenied: '拒绝访问',
|
||||||
|
notFound: '请求错误,未找到该资源',
|
||||||
|
serverError: '服务端错误',
|
||||||
|
connectError: '连接服务器失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from '@/App'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import '@/styles/index.scss' // global css
|
||||||
|
import store from '@/store/index'
|
||||||
|
import router from '@/router/index'
|
||||||
|
import i18n from './lang/index' // internationalization
|
||||||
|
import 'virtual:svg-icons-register'
|
||||||
|
import SvgIcon from '@/components/SvgIcon'// svg组件
|
||||||
|
import '@/icons'
|
||||||
|
|
||||||
|
const whiteList = ['/login', '/register'] // no redirect whitelist
|
||||||
|
const adminList = ['/trojan'] // need admin role
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
if (store.state.UserToken) {
|
||||||
|
if (store.state.isAdmin === null) {
|
||||||
|
await store.dispatch('loginUser')
|
||||||
|
}
|
||||||
|
if (!store.state.isAdmin) {
|
||||||
|
router.options.routes.map((x) => {
|
||||||
|
if (adminList.indexOf(x.path) !== -1) {
|
||||||
|
x.hidden = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (to.path === '/login') {
|
||||||
|
// if is logged in, redirect to the home page
|
||||||
|
next({ path: '/' })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
createApp(App).use(ElementPlus).use(router).use(store).use(i18n).component('svg-icon', SvgIcon).mount('#app')
|
||||||