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')
|
||||