mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 01:55:13 +08:00
🎈 Sync 2026-09-29 04:42:56
This commit is contained in:
1 parent
464f033b91
commit
796b5b3c57
32 files changed
+22765
-18
No files matched your search
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<https://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
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,357 @@
|
||||
# NetWiz (luci-app-netwiz) 🚀
|
||||
|
||||
[English](#english) | [简体中文](#简体中文)
|
||||
|
||||
**NetWiz** —— 您的专属 OpenWrt 极简网络设置向导。开箱即用,化繁为简。
|
||||
|
||||
凭借严谨缜密的底层逻辑,NetWiz 不仅为您提供直观的交互与大小屏极致丝滑的 UI 体验,更带来全方位的企业级网络守护:
|
||||
|
||||
🛡️ 智能配置与防呆守护:搭载企业级 LAN 口防失联机制与全网段冲突排雷(智能解决 IP、网关、WAN/LAN 冲突)。支持 WAN 口上网方式盲插智能切换(断开 10 秒后重新接入自动侦测)、智能防错校验、一键开启 IPv6,以及智能 Wi-Fi 满血释放(无物理硬件时自动隐藏)。
|
||||
|
||||
📊 企业级网络管控:深入系统内核,提供灾备级全自动配置管理。涵盖终端流量透视、IP 智能分组、一键批量绑定静态 IP 策略组以及一键防火墙控制,让复杂网络管理得心应手。
|
||||
|
||||
📦 首创 SmartGhost 智能恢复:NetWiz 不仅仅是设置向导,更打造了独家首创智能备份与恢复的完美闭环。搭载「SmartGhost 全量离线系统恢复」技术,无需全程联网,无惧跨版本刷机!能在彻底断网的新固件上,一键完美恢复您的插件与复杂网络配置,并提供商用级的防内存溢出(OOM)保护与跨网段无缝跃迁的丝滑恢复体验。
|
||||
|
||||
**NetWiz (Network Wizard)** — Your exclusive, minimalist OpenWrt network setup wizard. Ready out of the box, turning complexity into simplicity.
|
||||
|
||||
Built upon rigorous and well-structured underlying logic, NetWiz delivers not only a seamlessly smooth and responsive UI across all screen sizes but also comprehensive, enterprise-grade network protection:
|
||||
|
||||
🛡️ Intelligent Setup & Failsafe Protection: Features an enterprise-grade LAN fail-safe mechanism and proactive conflict resolution across IP, Gateway, WAN, and LAN subnets. It supports smart WAN mode blind-switching (auto-detects when the cable is reinserted after a 10-second disconnection), smart error-prevention validation, one-click IPv6 activation, and full-performance Wi-Fi configuration (automatically hides when no physical hardware is detected).
|
||||
|
||||
📊 Enterprise-Grade Network Management: Diving deep into the system kernel, NetWiz empowers you with disaster-recovery-level automated management. It offers terminal traffic visibility, smart IP grouping, one-click batch binding for static IP policy groups, and one-click firewall control.
|
||||
|
||||
📦 SmartGhost Offline Full Recovery: Much more than just a setup wizard, NetWiz pioneers a perfect closed-loop backup and restore ecosystem. Powered by the original "SmartGhost Full Offline System Recovery" technology, it requires no continuous internet connection and eliminates the fear of cross-version firmware flashing. It can perfectly clone and restore all your third-party plugins and complex network configs onto newly flashed, completely offline firmware—all while delivering commercial-grade Out-of-Memory (OOM) protection and a seamless cross-subnet recovery experience.
|
||||
|
||||
     
|
||||
|
||||
---
|
||||
|
||||
## English
|
||||
|
||||
### Description
|
||||
|
||||
**NetWiz (Network Setup Wizard)** is a minimalist, secure, and "zero-destructive" network configuration interface exclusively designed for OpenWrt and ImmortalWrt.
|
||||
|
||||
Tailored perfectly for novice users, it enables secure, one-click configuration of secondary routers (Dynamic/Static IP) and Bypass/AP (Wired Repeater) environments. For users who have just flashed a new system, setting up the network is essential for internet access, yet native settings are often deeply hidden and error-prone. Blindly and forcefully resetting network configuration files can **wipe out your configured device lists**, causing carefully crafted network bridges, VLANs, and physical NIC bindings to **crash instantly or trigger inexplicable issues**. **NetWiz is born to solve this exact pain point.**
|
||||
|
||||
### Installation Methods
|
||||
|
||||
[Use the one-click command for quick installation.](#install)
|
||||
|
||||
```bash
|
||||
if wget -qO /tmp/nw_inst.sh https://raw.githubusercontent.com/huchd0/luci-app-netwiz/master/install.sh; then sh /tmp/nw_inst.sh; else sh /etc/netwiz/custom_pkgs/install.sh; fi
|
||||
```
|
||||
|
||||
## 🤝 Compatibility Guide
|
||||
|
||||
* **Firmware Support:** OpenWrt 23.05+ and ImmortalWrt 23.05+ are recommended (Flawlessly supports the new 25.x+ branches and is fully compatible with the `nftables` environment).
|
||||
* **Architecture Support:** Universal compatibility across all architectures (x86_64, aarch64, ramips/mt7621, etc.).
|
||||
|
||||
**💡 Ultimate System Compatibility:**
|
||||
NetWiz achieves true "zero external dependencies," utilizing no C/C++/Go compiled binaries or third-party modules. Backed by a highly rigorous underlying safeguard and fallback architecture, NetWiz delivers outstanding compatibility across various branches and heavily modified (custom-built) firmwares. Whether on legacy systems or the new generation `fw4`/DSA architecture post-23.05, it adapts perfectly and runs with rock-solid stability.
|
||||
|
||||
## ✨ Core Features & Advantages
|
||||
|
||||
### 💻 Modern Architecture
|
||||
* **Lightning-Fast & Stutter-Free:** Built on a modern responsive and elastic architecture, saying goodbye to clunky and laggy interfaces. It perfectly adapts to both desktop and mobile screens for a silky-smooth experience.
|
||||
* **Strict Privilege Isolation:** Complete separation of UI operations and underlying system privileges. The frontend has no direct permission to modify core configs; all changes are safely executed by a trusted sandbox proxy.
|
||||
* **Native Multi-Language Support:** Automatically switches between Simplified Chinese, Traditional Chinese, and English based on your system environment.
|
||||
* **Extensive Hardware Compatibility:** Intelligently adapts to multi-chip and multi-band hardware, maximizing hardware potential and unleashing full Wi-Fi signal strength.
|
||||
|
||||
### 🛡️ Comprehensive Safeguards
|
||||
* **Failsafe Rollback Mechanism:** Afraid that a configuration error will lock you out of the backend? Don't worry! Applying new configurations triggers a 120-second safety countdown. If no real new IP connections access the LAN port after the change, the system automatically reverts to the previous configuration **(gracefully recovers even upon unexpected power loss or reboot)**.
|
||||
* **Smart Error Prevention:** Strictly and accurately intercepts high-risk operations such as "IP conflicts or incorrectly filled information," preventing router endless loops right from the source.
|
||||
* **Bypass/AP Mode Auto-Safeguard:** Switch to Bypass or AP (Wired Repeater) mode safely with one click. The system automatically disables conflicting services, provides smart setup guidance, and helps you fill in the correct information without disrupting your existing home network topology. Beginner-friendly.
|
||||
* **Comprehensive Device Dashboard:** Master the real-time dynamics of all online, offline, and static devices.
|
||||
* **Traffic Analysis Probe:** Hover to view the P2P and Web connection distribution for any specific device.
|
||||
* **Full-Link Disaster Recovery:** Roll back or import external configuration files at any time.
|
||||
|
||||
### ⚡ Ultimate Seamless Experience
|
||||
* **Smart WAN Mode Switching:** Detects the physical plug/unplug status of the WAN cable and intelligently adapts the network mode. It perfectly filters out "pseudo-disconnections" caused by dial-up failures or software reboots **(unplugging the network cable and reconnecting after 10 seconds triggers the smart mode-switching mechanism)**.
|
||||
* **Auto-Detection & Instant Redirection:** After modifying the IP, the frontend silently probes the new address. Once the network connects, it instantly redirects, ensuring a buttery-smooth experience.
|
||||
* **Non-Disruptive Network Settings:** Applying partial network settings keeps Wi-Fi stable and connected without interfering with other devices' internet access.
|
||||
* **Seamless Bi-Directional Sync:** When switching between "Band Steering (Merged)" and "Split Band" modes, Wi-Fi passwords and SSIDs are intelligently synchronized, eliminating the hassle of repetitive typing. Built-in historical config memory.
|
||||
* **One-Click IPv6:** Intelligently issues IPv6 addresses with one click, saying goodbye to obscure DHCPv6, SLAAC, and relay settings. With a simple tap, the system automatically assigns IPv6 addresses to your devices.
|
||||
* **One-Click Seamless Wi-Fi Roaming:** Enable seamless roaming without requiring professional technical skills. Just meet two conditions for automatic, silky-smooth Wi-Fi transitions between multiple routers: identical Wi-Fi names/passwords and devices on the same LAN. *(Note: Ensure no multiple DHCP servers exist in the environment; if routers are close to each other, stagger channels to avoid co-channel signal interference.)*
|
||||
* **Quick Wireless Repeater (WISP):** Enter the upstream Wi-Fi credentials to receive the signal and broadcast your own exclusive Wi-Fi network.
|
||||
|
||||
### 📶 Powerful Smart Wi-Fi Control
|
||||
* **Universal Hardware Adaptation:** Automatically takes over all physical wireless NICs in the system, seamlessly adapting whether it's a single-band, dual-band, tri-band, or even multi-band router.
|
||||
* **Hidden Fault Repair:** Targets incomplete, conflicting, or corrupted underlying configurations. Automatically cleans up legacy error Wi-Fi configs in the system and corrects stuck network states, ensuring highly stable wireless performance.
|
||||
* **Full Bandwidth Unleashed:** Say goodbye to crippled network speeds! When saving configurations, it intelligently identifies hardware limits and silently optimizes Wi-Fi bandwidth for maximum throughput.
|
||||
|
||||
### 🧭 Quick Navigation
|
||||
👉 **Find the 👉 Network Wizard directly at the end of the LuCI primary menu (right before the "Logout" button).**
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Four Core Supported Modules:
|
||||
|
||||
1. 🔌 **Broadband Dial-up (PPPoE)**
|
||||
* **Scenario:** The optical modem is in pure bridge mode; this router handles the dial-up directly, acting as the whole-house network hub.
|
||||
* **Behavior:** Accurately writes broadband accounts and passwords, strips residual legacy gateway settings, and safely restarts underlying dial-up processes.
|
||||
|
||||
2. 📶 **Wi-Fi Settings (Auto-hides if no hardware is detected)**
|
||||
* **Scenario:** Initializing the wireless network for the first time, or modifying the whole-house Wi-Fi name, password, and security encryption protocol.
|
||||
* **Behavior:** Intelligently detects the number of underlying physical chips and dynamically renders the UI. Supports one-click "Band Steering (Merged)" for a whole-house smart roaming network, or splitting into independent 2.4G/5G/5G_Game networks. Perfectly compatible with legacy 802.11b IoT devices.
|
||||
|
||||
3. 🌐 **Secondary Router Mode (DHCP / Static IP)**
|
||||
* **Scenario:** The modem handles dial-up, or there is an existing main router upstream. This device acts as a secondary router or subnet router.
|
||||
* **Behavior:** Freely choose "Dynamic IP (DHCP)" or "Static IP". Automatically takes over WAN port configurations and intelligently validates subnets to prevent routing loops.
|
||||
|
||||
4. 🏠 **LAN Settings (Main Router / Bypass/AP Mode Toggle)**
|
||||
* **Scenario:** Only modifying the device's internal management IP; or a main router already exists, and this device serves purely as an auxiliary gateway / Bypass router (AP Wired Repeater).
|
||||
* **Behavior:** One-click toggle for "Bypass/AP Mode". The system automatically disables the local DHCP service and enforces main router gateway input. If acting as the main router, it smartly prompts to leave the gateway blank, guaranteeing zero destruction to the existing LAN topology.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Device Management Expert: Core Features
|
||||
|
||||
### ⚡ Connection Radar & Kernel-Level Traffic Insight
|
||||
* **Real-Time Connection Tracking:** Deeply parses the core modules of network connection states, capturing the underlying network connections of terminal devices in real-time.
|
||||
* **Smart Traffic Categorization:** Automatically and accurately categorizes traffic into Web/HTTPS, DNS/NTP, UDP Media, and P2P High-Port downloads, displayed via an **intuitive dynamic pie chart**.
|
||||
* **Abnormal Terminal Alert:** Automatically identifies cross-subnet devices and suspected spoofed MAC devices.
|
||||
|
||||
### 🏷️ Smart IP Grouping & Batch Static Assignment
|
||||
* **Fully Automated Categorization:** Accurately identifies Mobile (Phones/Tablets), PC (Workstations), and IoT (Smart Home devices).
|
||||
* **Multi-Strategy Batch Deployment:** Select multiple devices to execute a one-click **sequential assignment**, **smart exclusive subnet assignment by device type**, or assign to **custom departments/rooms**.
|
||||
* **Secure Anti-Conflict:** Built-in smart anti-conflict algorithm. Automatically postpones IP allocation when conflicts occur, eliminating network paralysis.
|
||||
|
||||
### 🌐 Pure-Blood IPv6 Deep Support
|
||||
* **Seamless Parsing:** Automatically filters out link-local addresses (`fe80`) to accurately extract public IPv6 addresses.
|
||||
* **PC Keep-Alive Mechanism:** An exclusive `v6pc_` active timestamp mechanism prevents PC IPv6 addresses from frequently shifting, which leads to lost connections.
|
||||
|
||||
### 🛡️ One-Click Firewall & Access Control
|
||||
* **⛔ Block Internet:** Cut off external network access with one click while preserving LAN communication. Takes effect in seconds.
|
||||
* **🛡️ LAN Isolation:** One-click ban on accessing other devices within the internal network (prevents bypass router snooping and smart home privacy leaks).
|
||||
* **🚀 DMZ Host:** Expose all ports of a specific device to the public network with one click (with automatic mutual-exclusion validation to prevent conflicts).
|
||||
* **🔌 Wake on LAN (WOL):** Deeply integrated; the wake button automatically appears when a device goes offline.
|
||||
|
||||
### 📦 Disaster-Recovery Level Config Management
|
||||
* **Lossless Auto-Backup:** When the network topology changes (e.g., modifying DHCP or firewall), the backend automatically calculates the MD5 of core configuration files and generates a `.tar.gz` backup archive upon changes.
|
||||
* **One-Click Import/Export:** Pack and download all current static IPs, blacklists, and isolation rules to your PC. Or, easily restore them from the cloud/local storage after flashing the firmware, automatically waking up all advanced proxy and DNS plugins post-recovery.
|
||||
|
||||
---
|
||||
|
||||
## 简体中文
|
||||
|
||||
### 📖简介 | [English](#english)
|
||||
|
||||
**NetWiz (网络设置向导)** 是一款专为 OpenWrt / ImmortalWrt 设计的极简、安全且“零破坏”的网络配置界面。
|
||||
|
||||
NetWiz极其适合新手用户,能够一键安全地配置二级路由(动态 / 静态 IP)以及旁路由 / AP(有线中继)环境。对于刚刷完系统的玩家,正确配置网络是上网的第一步,但系统自带的设置隐藏较深且容易配错。此时若盲目、暴力地重置整个网络配置文件,会**清空路由器的已配置清单**,导致精心设置的网桥(Bridge)、VLAN 和物理网卡绑定**崩溃或引发莫名其妙的故障**。**NetWiz 专为解决此痛点而生。**
|
||||
|
||||
## 🤝 兼容性说明
|
||||
|
||||
* **固件支持**:建议 OpenWrt 23.05 及以上版本、ImmortalWrt 23.05 及以上版本(完美支持 25.x+ 新分支,全面兼容 nftables 环境)。
|
||||
* **架构支持**:全架构通用 (x86_64, aarch64, ramips/mt7621 等)。
|
||||
|
||||
**💡 极致的系统兼容性:**
|
||||
NetWiz 的“零外部依赖”,无任何 C/C++/Go 编译件、第三方模块 和 扎实严谨的底层防护与兜底架构,使 NetWiz 在不同分支和各类魔改固件中表现出众的兼容能力。无论是老旧系统,还是 23.05 之后的新一代 fw4/DSA 架构,皆能适配,稳定运行。
|
||||
|
||||
## ✨ 核心特性与优势
|
||||
|
||||
### 💻 现代化架构
|
||||
* **极速加载不卡顿:** 采用最新响应式、弹性架构,告别传统界面的简陋与卡顿,完美适配电脑和手机屏幕,操作如丝般顺滑。
|
||||
* **严格的权限隔离:** 界面操作与底层系统权限彻底分离。前端不具备直接修改底层配置的权限,所有变更均由受信任的沙箱代理安全执行。
|
||||
* **优雅的 UI 细节:** 告别烦人的弹窗打扰,版本更新采用简洁的右上角小红点提示,状态一目了然。(暂未启用)
|
||||
* **原生多语言支持:** 根据您的系统环境,自动切换简体中文、繁体中文或英文界面。
|
||||
* **兼容众多品牌硬件:** 智能适配多芯片频段硬件,最大化挖掘硬件性能,满血释放 Wi-Fi 信号。
|
||||
|
||||
### 🛡️ 全方位的守护
|
||||
* **安全回退机制:** 配置错误导致无法连接后台?不用怕!应用新配置后会启动 120 秒安全倒计时。LAN 口配置后若无真实的新 IP 访问连接,系统将自动恢复到修改前的配置 **(意外断电重启依然优雅恢复)**。
|
||||
* **智能防错校验:** 严格并精准拦截“ IP 冲突、信息填错”等高危操作,从源头防止路由死循环。
|
||||
* **旁路由 / AP 模式自动排雷:** 一键安全切换旁路由或 AP(有线中继)模式。系统会自动帮您关掉冲突服务,智能指引您正确填写信息,绝不破坏现有的家庭网络拓扑,新手也能轻松设置。
|
||||
* **设备综合大盘:** 掌握所有在线、离线及静态设备的实时动态。
|
||||
* **流量分析探针:** 悬停即可查看该设备的 P2P 与 Web 连接数分布。
|
||||
* **全链路灾备:** 随时回滚或导入外部配置文件。
|
||||
|
||||
### ⚡ 极致的无感体验
|
||||
* **WAN 口上网方式智能切换:** 自动识别 WAN 口网线的物理插拔状态并智能适配,完美过滤由拨号失败、软件重启引起的“假性掉线” **(拔插 WAN 口网线间隔十秒,即可触发智能切换机制)**。
|
||||
* **自动探活,秒级跳转:** 修改 IP 后,前端会静默探测新地址,网络一旦连通立刻瞬间跳转,体验丝滑流畅。
|
||||
* **改设置不断网:** 在应用部分网络设置时,Wi-Fi 保持稳定不断开,不干扰其他设备的正常上网。
|
||||
* **双向数据无缝同步:** 在“多频合一”与“分频独立”模式间切换时,Wi-Fi 密码与 SSID 智能双向传递,彻底告别重复输入的烦恼。自带历史配置记忆功能。
|
||||
* **一键开启 IPv6:** 智能下发 IPv6 地址,告别晦涩难懂的 DHCPv6、SLAAC 和中继设置。轻轻一点,系统即可为设备自动下发公网 IPv6。
|
||||
* **一键 Wi-Fi 无缝漫游:** 用户无需专业技术,仅需满足两项条件即可拥有多路由器间 Wi-Fi 的自动“丝滑”跳转:Wi-Fi 名称、密码一致,且设备处于同一局域网。**(注意:环境中请勿存在多个 DHCP 服务;若路由器摆放距离较近,建议错开信道,避免同频干扰。)**
|
||||
* **快速启用无线中继 (WISP):** 输入上级 Wi-Fi 帐号密码接收信号,并广播您自己的专属 Wi-Fi 网络。
|
||||
|
||||
### 📶 强大的无线智控
|
||||
* **自动适配所有硬件:** 自动接管系统内所有物理无线网卡,无论是单频、双频还是三频(甚至多频)路由器,均能完美适配。
|
||||
* **修复隐藏故障:** 针对各种残缺、冲突或被带偏的底层配置,自动清理系统中遗留的错误 Wi-Fi 配置,纠正被卡死的网络状态,让无线表现更稳定。
|
||||
* **智能频宽满血释放:** 告别残血网络!在保存配置时,智能识别硬件极限,静默优化并提升 Wi-Fi 频宽,轻松跑满带宽。
|
||||
|
||||
### 🧭 快捷导航
|
||||
👉 **在 LuCI 一级菜单的最后面(“退出”按钮的前一个),即可直接找到 👉 网络向导**。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 核心支持的四大模块
|
||||
|
||||
1. 🔌 **宽带拨号 (PPPoE)**
|
||||
* **适用场景**:光猫为纯桥接模式,由本路由器直接进行宽带拨号上网,承担全屋网络枢纽。
|
||||
* **行为**:精准写入宽带账号与密码,剥离多余网关遗留,安全重启底层拨号进程。
|
||||
|
||||
2. 📶 **Wi-Fi 设置(无硬件时自动隐藏)**
|
||||
* **适用场景**:首次配置无线网络,或 修改全屋 Wi-Fi 名称、密码 及 是否启用无线中继 (WISP)。
|
||||
* **行为**:智能接管底层物理芯片并动态渲染界面。支持一键“多频合一”组建全屋智能漫游网,或拆分为独立的 2.4G / 5G / 5G_Game 频段;可以启用兼容旧版古董智能物联设备 **(网速会被拖累、稳定性变差)**;默认开启不同路由器间5G频段的Wi-Fi信号顺滑切换功能。
|
||||
|
||||
3. 🌐 **二级路由模式 (DHCP / 静态 IP)**
|
||||
* **适用场景**:光猫已经负责拨号,或者上级有主路由,本设备作为二级路由或子网段路由接入。
|
||||
* **行为**:可自由选择“动态获取”或“静态绑定”,自动接管 WAN 口配置,智能校验网段避免死循环。
|
||||
|
||||
4. 🏠 **局域网设置 (主路由 / 旁路由切换)**
|
||||
* **适用场景**:仅需修改设备内网管理 IP;或者网络内已有主路由,本设备仅作为辅助网关或旁路由(AP 有线中继)。
|
||||
* **行为**:一键开启“旁路由模式”,系统将自动关闭本机 DHCP 服务,并要求必须填写主路由网关;若为主路由,则提示网关留空,不会破坏现有局域网拓扑。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 设备管理专家核心特性
|
||||
|
||||
### ⚡ 终端雷达 & 内核级流量透视 (Connection Radar)
|
||||
* **实时连接追踪**:深入解析网络连接状态的核心模块,实时捕获终端设备的底层网络连接。
|
||||
* **智能流量分类**:自动将流量精准分类为 Web/HTTPS、DNS/NTP、UDP 媒体、P2P 高端口下载,并以**直观的动态圆饼图**展示。
|
||||
* **异常终端预警**:自动识别跨网段设备与疑似伪装 MAC 设备。
|
||||
|
||||
### 🏷️ 智能 IP 分组与批量绑定固定 IP (Smart IP Assignment)
|
||||
* **全自动分类**:精准识别 Mobile(手机 / 平板)、PC(电脑 / 工作站)、IoT(智能家居)。
|
||||
* **多策略批量下发**:支持选择多个设备,一键执行**顺序分配**、**按设备类型智能分配专属网段**,或分配至**自定义部门 / 房间**。
|
||||
* **安全防冲突**:内置智能防冲突算法,IP 冲突时自动顺延,杜绝网络瘫痪。
|
||||
|
||||
### 🌐 纯血 IPv6 深度支持
|
||||
* **无感解析**:自动过滤本地链路地址(`fe80`),精准提取公网 IPv6。
|
||||
* **PC 存活保活机制**:独创的 `v6pc_` 活跃时间戳机制,防止 PC 设备的 IPv6 地址频繁变动导致失联。
|
||||
|
||||
### 🛡️ 一键防火墙与访问控制 (Firewall Control)
|
||||
* **⛔ 断网 (Block)**:一键阻断设备访问外网,保留局域网通信,秒级生效。
|
||||
* **🛡️ 局域网隔离 (Isolate)**:一键禁止设备访问内网其他设备(防旁路由嗅探、防智能家居隐私泄露)。
|
||||
* **🚀 DMZ 主机**:一键将特定设备的全部端口暴露至公网(自动互斥校验,防止冲突)。
|
||||
* **🔌 局域网唤醒 (WOL)**:深度集成,设备离线时自动显示唤醒按钮。
|
||||
|
||||
### 📦 灾备级配置管理 (Auto-Backup & Disaster Recovery)
|
||||
* **无损自动备份**:当网络拓扑发生改变时(如修改 DHCP、防火墙),后台自动计算核心配置文件的 MD5,并在变动时自动生成 `.tar.gz` 备份存档。
|
||||
* **一键导入 / 导出**:支持将当前所有的静态 IP、黑名单、隔离规则打包下载到电脑,或在刷机后一键从云端 / 本地恢复,恢复后自动唤醒所有高级代理与 DNS 插件。
|
||||
|
||||
---
|
||||
|
||||
<span id="install"></span>
|
||||
## 一、🚀 极速安装 (推荐)
|
||||
|
||||
我们提供了一键智能安装脚本。无论你是老系统还是新系统,只需在 SSH 终端中直接复制并执行以下单行命令,即可自动完成 **【判断系统架构 -> 下载对应格式 -> 安装 -> 修复权限与清理缓存】** 的全流程:
|
||||
|
||||
```bash
|
||||
if wget -qO /tmp/nw_inst.sh https://raw.githubusercontent.com/huchd0/luci-app-netwiz/master/install.sh; then sh /tmp/nw_inst.sh; else sh /etc/netwiz/custom_pkgs/install.sh; fi
|
||||
```
|
||||
|
||||
💡 **提示**:如果你的网络无法直接访问 GitHub Raw,可以在链接前加上代理加速,例如:
|
||||
```bash
|
||||
if wget -qO /tmp/nw_inst.sh https://ghproxy.net/https://raw.githubusercontent.com/huchd0/luci-app-netwiz/master/install.sh; then sh /tmp/nw_inst.sh; else sh /etc/netwiz/custom_pkgs/install.sh; fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、🛠️ 手动安装 (离线包)
|
||||
|
||||
如果你习惯手动操作,请前往 [Releases 页面] 下载最新的安装包,并使用 WinSCP 将其上传至路由器的 `/tmp/` 目录。
|
||||
|
||||
**对于 OpenWrt 25.x 及最新快照版 (`.apk` 格式):**
|
||||
上传至 `/tmp/` 目录后,运行以下命令(注意:新版 25.x 由于签名限制,官方界面直接上传安装会报 `Error 99`,优先使用上方的命令行安装)。
|
||||
|
||||
**手动上传:**
|
||||
```bash
|
||||
apk add --allow-untrusted /tmp/*netwiz*.apk
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/rpcd restart
|
||||
```
|
||||
**官方界面上传(不要点安装):**
|
||||
```bash
|
||||
apk add --allow-untrusted /tmp/upload.apk
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/rpcd restart
|
||||
```
|
||||
**对于 OpenWrt 23.05 及更早系统 (`.ipk` 格式):**
|
||||
以下两种方式任选其一:
|
||||
1. 网页端:在系统后台使用 “系统 -> 软件包 -> 上传软件包” 正常安装。
|
||||
2. 命令行:上传至 `/tmp/` 目录后,运行以下命令:
|
||||
```bash
|
||||
opkg install /tmp/*netwiz*.ipk
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/rpcd restart
|
||||
```
|
||||
|
||||
## 💡 常见问题 (FAQ)
|
||||
|
||||
**Q:安装后点击菜单提示 `HTTP 404` 或 `Object not found (-32000)`?**
|
||||
A:这是 OpenWrt 系统的权限通行证 (ACL) 和前端缓存机制未更新所致。请在 SSH 中执行彻底清理命令:
|
||||
```bash
|
||||
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/* && /etc/init.d/rpcd restart
|
||||
```
|
||||
**做完后,请务必点击网页右上角“退出登录”,重新输入密码进入后台即可解决。**
|
||||
|
||||
**Q:在局域网设置里修改了 IP 后,为什么一直在转圈,打不开网页了?**
|
||||
A:如果您修改了本机的局域网 IP,点击“确认应用”后底层网络会立刻重置。系统会在几秒后尝试为您自动跳转,如果由于浏览器安全策略未跳转成功,原有的旧 IP 网页将失效。请直接在浏览器地址栏手动输入您新设置的 IP 地址即可恢复访问。
|
||||
|
||||
**Q:切换上网方式后无法打开网页,打不开网页了?**
|
||||
A:通常是因为操作系统的 DNS 缓存没清,请尝试断开电脑/手机的 Wi-Fi 重新连接,或在电脑执行:ipconfig /flushdns。
|
||||
|
||||
**Q:🆘 救援指南:进不去后台怎么办**
|
||||
|
||||
检查网络连接与手动指定 IP(如果电脑无法自动获取 IP 地址),导致浏览器打不开后台:
|
||||
* 请确保网线插在路由器的 **LAN 口** 上。
|
||||
* 将电脑网卡的 IP 获取方式改为“手动/静态 IP”。
|
||||
* IP 地址设置为:`192.168.100.2`(与路由器同网段的其他地址)。
|
||||
* 子网掩码设置为:`255.255.255.0`。
|
||||
* 设置完成后,再次尝试访问路由器后台 `192.168.100.1`。
|
||||
|
||||
## Q:🆘 救援指南:构建固件时内置Netwiz,刷后进不去后台怎么办?
|
||||
|
||||
如果在刷入固件后遇到无法连接、向导界面异常或无法进入管理后台的情况,请不要紧张,按照以下步骤进行排查与救援:
|
||||
|
||||
### 1. 触发隐藏的紧急救援通道 (Netwiz 防卡死机)
|
||||
如果您已经连接上路由器的网络,但遇到了前端白屏、向导无限循环或界面卡死的问题,可以利用系统内置的防劫持救援通道:
|
||||
* 请在浏览器中直接输入并访问:`http://192.168.100.1/cgi-bin/luci/rescue`(请将 IP 替换为您的实际路由器默认 IP)。
|
||||
* 此操作会触发底层的紧急释放指令,强制解除向导的网关劫持,并将您安全地送回官方原生的 LuCI 登录页面。
|
||||
|
||||
### 2. SSH 后台命令修复
|
||||
如果您依然可以通过 SSH 连接路由器(默认端口 `22`),您可以通过命令行手动解除向导锁定并重启网络:
|
||||
```bash
|
||||
# 解除向导锁定并恢复原生 LuCI 入口
|
||||
uci set netwiz.global.configured='1'
|
||||
uci commit netwiz
|
||||
[ -f /www/cgi-bin/luci.bak ] && mv -f /www/cgi-bin/luci.bak /www/cgi-bin/luci
|
||||
|
||||
# 重启网页服务器与网络服务
|
||||
/etc/init.d/uhttpd restart
|
||||
/etc/init.d/network restart
|
||||
```
|
||||
---
|
||||
|
||||
## 📸 界面预览
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/40905f7e-fa8b-4bc6-8bc8-846f81b3673c" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/6bb6a808-901a-4911-9769-a27ad5700508" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/66a9e97b-c2d1-431f-8981-995f4358b4fa" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/7d056b14-9a88-4d0e-a0ea-2fae7a5f8233" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/2254dffb-bd82-40e0-9d58-0c9f90d60512" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/f2bd9091-2a1d-4b37-9104-3071dbf9d71a" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/01bbdd3a-ab4c-493f-adbd-09a5087f2de4" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/e02aefe9-51f8-4c15-8a93-932b48e4594d" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/52e42b52-11e3-4c61-87e6-17a689451be7" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/d55ff995-4e19-4fca-9059-60779777c4d9" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/a1593cdb-1384-4b78-af35-107fa7ed4f60" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/6b3b6aeb-8ed0-4612-a187-c7d8a40b0d67" />
|
||||
|
||||
---
|
||||
|
||||
## 📄 协议与鸣谢
|
||||
|
||||
本项目基于 GPL-3.0 license 开源。
|
||||
|
||||
特别感谢 **OpenWrt 开源社区**、**LuCI 框架** 以及 **ImmortalWrt 团队** 提供坚实的基础架构支撑。
|
||||
|
||||
---
|
||||
|
||||
## ☕ 赞助与支持
|
||||
|
||||
如果您觉得这个插件让您的网络配置体验变得更加优雅、省心,欢迎请作者喝杯咖啡,这将是持续维护与更新的最大动力!❤️
|
||||
|
||||
<img src="https://github.com/huchd0/openwrt-x86-64-build/blob/master/.github/Donate.jpg" width="680" alt="Donate">
|
||||
|
||||
---
|
||||
|
||||
[返回顶部](#top)
|
||||
@@ -0,0 +1,230 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "🚀 开始极速安装/升级/修复 NetWiz"
|
||||
|
||||
# ==========================================
|
||||
# 0. 环境初始化与版本识别
|
||||
# ==========================================
|
||||
PKG_TYPE="ipk"
|
||||
if command -v apk >/dev/null 2>&1; then
|
||||
PKG_TYPE="apk"
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
PKG_TYPE="ipk"
|
||||
else
|
||||
echo "❌ 找不到支持的软件包管理器 (未找到 apk 或 opkg)!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 函数:获取当前系统中已安装的版本
|
||||
get_installed_version() {
|
||||
if [ "$PKG_TYPE" = "apk" ]; then
|
||||
apk info -v 2>/dev/null | grep -E "^luci-app-netwiz-[0-9]" | head -n 1 | sed 's/^luci-app-netwiz-//'
|
||||
else
|
||||
opkg status luci-app-netwiz 2>/dev/null | grep -i "^Version:" | awk '{print $2}'
|
||||
fi
|
||||
}
|
||||
|
||||
# 函数:透视获取本地保险箱中安装包的版本
|
||||
get_local_version() {
|
||||
local file="$1"
|
||||
local ver=""
|
||||
|
||||
if [ "$PKG_TYPE" = "apk" ]; then
|
||||
# 1. 尝试直接跨流提取 .PKGINFO
|
||||
ver=$(tar -Ozxf "$file" .PKGINFO 2>/dev/null | grep "^pkgver =" | cut -d'=' -f2 | tr -d ' ')
|
||||
# 2. 核心修复:如果由于多段 gzip 拼接导致 tar 失败,利用 zcat 暴力读取压缩流明文
|
||||
[ -z "$ver" ] && ver=$(zcat "$file" 2>/dev/null | grep -a -m 1 "^pkgver =" | cut -d'=' -f2 | tr -d ' ')
|
||||
# 3. 终极穿透:利用 strings 强制提取二进制文件中的可见版本字符
|
||||
[ -z "$ver" ] && ver=$(strings "$file" 2>/dev/null | grep -m 1 "^pkgver =" | cut -d'=' -f2 | tr -d ' ')
|
||||
else
|
||||
# ipk 架构的提取非常稳定
|
||||
ver=$(tar -Ozxf "$file" ./control.tar.gz 2>/dev/null | tar -Ozxf - ./control 2>/dev/null | grep -i "^Version:" | awk '{print $2}')
|
||||
fi
|
||||
|
||||
# 4. 从文件名兜底提取
|
||||
[ -z "$ver" ] && ver=$(basename "$file" | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?(-r[0-9]+)?' | head -n 1)
|
||||
|
||||
echo "$ver"
|
||||
}
|
||||
|
||||
# 函数:版本对比算法
|
||||
version_ge() {
|
||||
awk -v v1="$1" -v v2="$2" '
|
||||
function split_ver(v, a) {
|
||||
gsub(/^[vV]/, "", v); gsub(/-.*/, "", v);
|
||||
return split(v, a, ".");
|
||||
}
|
||||
BEGIN {
|
||||
if (v1 == "" && v2 == "") exit 0;
|
||||
if (v1 == "") exit 1;
|
||||
if (v2 == "") exit 0;
|
||||
n1 = split_ver(v1, a1); n2 = split_ver(v2, a2);
|
||||
max = (n1 > n2) ? n1 : n2;
|
||||
for (i=1; i<=max; i++) {
|
||||
if (a1[i]+0 > a2[i]+0) exit 0;
|
||||
if (a1[i]+0 < a2[i]+0) exit 1;
|
||||
}
|
||||
exit 0;
|
||||
}'
|
||||
}
|
||||
|
||||
INSTALLED_VER=$(get_installed_version)
|
||||
echo "🔍 当前系统架构: $PKG_TYPE"
|
||||
echo "📦 当前运行版本: ${INSTALLED_VER:-未安装 (或已被精简)}"
|
||||
|
||||
# ==========================================
|
||||
# 1. 备份脚本自身,确保离线可用
|
||||
# ==========================================
|
||||
mkdir -p /etc/netwiz/custom_pkgs/
|
||||
cp -f "$0" /etc/netwiz/custom_pkgs/install.sh 2>/dev/null
|
||||
|
||||
# ==========================================
|
||||
# 2. 尝试从云端拉取最新版 (高透明度探测)
|
||||
# ==========================================
|
||||
echo "⬇️ 正在从云端尝试获取最新版本..."
|
||||
FILES="luci-app-netwiz luci-i18n-netwiz-zh-cn luci-i18n-netwiz-zh-tw"
|
||||
DOWNLOAD_SUCCESS=0
|
||||
|
||||
for FILE in $FILES; do
|
||||
TARGET_FILE="${PKG_TYPE}_${FILE}.${PKG_TYPE}"
|
||||
URL_DIRECT="https://github.com/huchd0/luci-app-netwiz/releases/latest/download/${TARGET_FILE}"
|
||||
|
||||
PROXY_1="https://mirror.ghproxy.com/${URL_DIRECT}"
|
||||
PROXY_2="https://ghfast.top/${URL_DIRECT}"
|
||||
PROXY_3="https://ghp.ci/${URL_DIRECT}"
|
||||
|
||||
echo -n "正在拉取: ${TARGET_FILE} ... "
|
||||
|
||||
# 尝试 1:官方直连 (仅等待 5 秒)
|
||||
wget -qO "/tmp/${TARGET_FILE}" --no-check-certificate -T 5 "$URL_DIRECT"
|
||||
if [ "$?" -eq 0 ] && [ -s "/tmp/${TARGET_FILE}" ]; then
|
||||
DOWNLOAD_SUCCESS=$((DOWNLOAD_SUCCESS + 1))
|
||||
echo "👉 👉 👉 [官方直连] 成功!✅✅✅"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 尝试 2:代理节点 1
|
||||
wget -qO "/tmp/${TARGET_FILE}" --no-check-certificate -T 5 "$PROXY_1"
|
||||
if [ "$?" -eq 0 ] && [ -s "/tmp/${TARGET_FILE}" ]; then
|
||||
DOWNLOAD_SUCCESS=$((DOWNLOAD_SUCCESS + 1))
|
||||
echo "👉 👉 👉 [加速节点1] 成功!✅✅✅"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 尝试 3:代理节点 2
|
||||
wget -qO "/tmp/${TARGET_FILE}" --no-check-certificate -T 5 "$PROXY_2"
|
||||
if [ "$?" -eq 0 ] && [ -s "/tmp/${TARGET_FILE}" ]; then
|
||||
DOWNLOAD_SUCCESS=$((DOWNLOAD_SUCCESS + 1))
|
||||
echo "👉 👉 👉 [加速节点2] 成功!✅✅✅"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 尝试 4:代理节点 3
|
||||
wget -qO "/tmp/${TARGET_FILE}" --no-check-certificate -T 5 "$PROXY_3"
|
||||
if [ "$?" -eq 0 ] && [ -s "/tmp/${TARGET_FILE}" ]; then
|
||||
DOWNLOAD_SUCCESS=$((DOWNLOAD_SUCCESS + 1))
|
||||
echo "👉 👉 👉 [加速节点3] 成功!✅✅✅"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "❌ 所有节点均失败!"
|
||||
done
|
||||
|
||||
# ==========================================
|
||||
# 3. 云端全新安装 vs 离线智能对比
|
||||
# ==========================================
|
||||
if [ "$DOWNLOAD_SUCCESS" -eq 3 ]; then
|
||||
echo "🌐 云端下载完整!正在覆盖至本地保险箱..."
|
||||
for FILE in $FILES; do
|
||||
TARGET_FILE="${PKG_TYPE}_${FILE}.${PKG_TYPE}"
|
||||
mv -f "/tmp/${TARGET_FILE}" "/etc/netwiz/custom_pkgs/${TARGET_FILE}"
|
||||
done
|
||||
echo "👉 准备执行全新安装/升级!"
|
||||
else
|
||||
echo "⚠️ 云端连接失败或文件不完整,转入【离线恢复模式】!"
|
||||
echo "🔍 正在检查本地保险箱状态..."
|
||||
|
||||
LOCAL_MAIN=$(ls /etc/netwiz/custom_pkgs/*luci-app-netwiz*.${PKG_TYPE} 2>/dev/null | head -n 1)
|
||||
if [ -n "$LOCAL_MAIN" ] && [ -f "$LOCAL_MAIN" ]; then
|
||||
LOCAL_VER=$(get_local_version "$LOCAL_MAIN")
|
||||
echo "📦 发现本地储备版本: ${LOCAL_VER:-未知}"
|
||||
|
||||
# 双向对比机制
|
||||
if [ -n "$INSTALLED_VER" ] && [ -n "$LOCAL_VER" ]; then
|
||||
if version_ge "$LOCAL_VER" "$INSTALLED_VER"; then
|
||||
echo "✅ 本地储备版 ($LOCAL_VER) >= 当前运行版 ($INSTALLED_VER),允许覆盖恢复!"
|
||||
else
|
||||
echo "🚫 智能拦截:本地储备版 ($LOCAL_VER) 低于当前运行版 ($INSTALLED_VER)!"
|
||||
echo "💡 为保护系统,无需进行降级安装,已自动安全退出。"
|
||||
exit 0
|
||||
fi
|
||||
elif [ -n "$INSTALLED_VER" ] && [ -z "$LOCAL_VER" ]; then
|
||||
echo "⚠️ 无法准确识别本地包版本,默认放行执行覆盖安装以尝试修复系统!"
|
||||
else
|
||||
echo "✅ 系统核心未挂载,允许执行离线急救部署!"
|
||||
fi
|
||||
else
|
||||
echo "❌ 致命错误:云端下载失败,且本地保险箱无可用急救包!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ==========================================
|
||||
# 4. 卸载旧版并注入新版
|
||||
# ==========================================
|
||||
echo "🧹 正在清理系统旧版本残留..."
|
||||
if [ "$PKG_TYPE" = "apk" ]; then
|
||||
apk del luci-app-netwiz luci-i18n-netwiz-zh-cn luci-i18n-netwiz-zh-tw >/dev/null 2>&1
|
||||
else
|
||||
opkg remove luci-app-netwiz luci-i18n-netwiz-zh-cn luci-i18n-netwiz-zh-tw --force-remove >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo "⚙️ 正在执行批量注入安装..."
|
||||
if [ "$PKG_TYPE" = "apk" ]; then
|
||||
apk add --allow-untrusted --force-overwrite /etc/netwiz/custom_pkgs/*luci-*netwiz*.apk 2>/dev/null
|
||||
else
|
||||
opkg install /etc/netwiz/custom_pkgs/*luci-*netwiz*.ipk --force-reinstall --force-overwrite 2>/dev/null
|
||||
fi
|
||||
|
||||
# ==========================================
|
||||
# 5. 重建缓存与唤醒服务
|
||||
# ==========================================
|
||||
echo "🔄 正在重建 LuCI 缓存并唤醒服务..."
|
||||
# 1. 清空所有 LuCI 缓存
|
||||
rm -rf /tmp/luci-indexcache* /tmp/luci-modulecache* /var/run/luci-indexcache* /var/run/luci-modulecache* 2>/dev/null
|
||||
rm -rf /tmp/luci-sessions/* /var/run/luci-sessions/* 2>/dev/null
|
||||
|
||||
# 2. 重启 rpcd,确保后端的 netwiz 脚本被正确加载入内核
|
||||
/etc/init.d/rpcd restart 2>/dev/null
|
||||
|
||||
# 3. 智能重启 Web 服务
|
||||
if [ -f "/etc/init.d/uhttpd" ]; then
|
||||
/etc/init.d/uhttpd restart 2>/dev/null
|
||||
elif [ -f "/etc/init.d/nginx" ]; then
|
||||
/etc/init.d/nginx restart 2>/dev/null
|
||||
fi
|
||||
|
||||
echo -e "\n👉 👉 👉 🎉 NetWiz 核心程序部署完成!✅✅✅"
|
||||
echo -e "💡 登录状态已安全重置,请返回浏览器按下 【F5】 刷新即可看到新菜单!"
|
||||
|
||||
# 6. 匿名安装量统计
|
||||
(
|
||||
if [ -f /etc/openwrt_release ]; then
|
||||
. /etc/openwrt_release
|
||||
OW_VER=${DISTRIB_RELEASE:-"unknown"}
|
||||
OW_ARCH=${DISTRIB_TARGET:-"unknown"}
|
||||
else
|
||||
OW_VER="unknown"
|
||||
OW_ARCH=$(uname -m 2>/dev/null || echo "unknown")
|
||||
fi
|
||||
API_URL="https://netwiz-tracker.vercel.app/api/track"
|
||||
REQ_URL="${API_URL}?app=netwiz&ver=${OW_VER}&arch=${OW_ARCH}"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -s -m 3 "$REQ_URL" >/dev/null 2>&1
|
||||
else
|
||||
wget -qO- -T 3 "$REQ_URL" >/dev/null 2>&1
|
||||
fi
|
||||
) &
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
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
|
||||
<https://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
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,17 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
# 明确声明包名与版本
|
||||
PKG_NAME:=luci-app-netwiz
|
||||
PKG_VERSION:=1.7.5
|
||||
PKG_RELEASE:=1
|
||||
|
||||
# LuCI 核心定义
|
||||
LUCI_TITLE:=LuCI Support for Network Setup Wizard
|
||||
LUCI_DEPENDS:=+luci-base +iwinfo +rpcd +rpcd-mod-iwinfo +luci-compat +ppp-mod-pppoe
|
||||
# 声明为纯脚本架构,生成的 IPK/APK 可以在任何 CPU 路由上通用安装
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
# 使用绝对路径引入 luci.mk,保证任何目录下都能成功触发多国语言编译
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,139 @@
|
||||
# NetWiz (luci-app-netwiz) 🚀
|
||||
|
||||
   
|
||||
|
||||
---
|
||||
### Description
|
||||
**NetWiz(luci-app-netwiz)** is a minimalist, safe, and non-destructive network configuration interface for OpenWrt/ImmortalWrt.
|
||||
|
||||
minimalist and ready out of the box. Built with rigorous, well-structured logic, it features an enterprise-grade LAN fail-safe mechanism, intelligent WAN protocol switching, smart validation, and automatic conflict detection for IPs, gateways, WAN, and LAN interfaces. The front-end delivers a smooth, fluid user experience, eliminating the traditional frustrations of “errors after saving” and frequent configuration conflicts.
|
||||
|
||||
It is designed to be highly user-friendly for novices setting up secondary routers (DHCP/Static IP) or bypass routers.
|
||||
|
||||
### 🌟 Key Features
|
||||
|
||||
**🛡️ Enterprise-Grade Fail-Safes**
|
||||
* **Persistent Power-Loss Recovery:** Backups are written to non-volatile flash (`/etc/config`). An early-boot `init.d` script guarantees an automatic network restoration even if the router loses power mid-test.
|
||||
* **Smart Concurrency Radar (LAN Auto-Defuse):** The 120-second rollback bomb uses `netstat` to count concurrent connections, accurately distinguishing true browser access from background IoT probes to prevent false-positive defusals.
|
||||
* **Smart WAN Auto-Detect (Anti-Jitter):** Features a time-based debounce algorithm that requires 3 continuous down-cycles to confirm a physical cable unplug, safely ignoring temporary software interface bounces.
|
||||
* **Flash-Friendly Safe Logging:** Includes a persistent, auto-trimming logging system (`/etc/netwiz.log`) that survives reboots but strictly caps file size to protect the router's flash memory lifespan.
|
||||
|
||||
### 📶 Powerful Smart Wi-Fi Engine
|
||||
* **Infinite Chip Array Detection:** Breaks the rigid limits of traditional single/dual-chip plugins. Automatically scans and takes over all physical wireless adapters in the system. Perfectly adapts to single-band, dual-band, tri-band, or even multi-band routers, ensuring no hardware is left idle.
|
||||
* **Ultimate Low-Level Override:** Designed for incomplete, conflicting, or misconfigured underlying settings (e.g., a single chip stuck in 5G mode). Features an exclusive "Will Override Rule" that ignores residual hardware history, forcibly cleansing and rewriting the correct physical protocols and channels to guarantee a 100% successful application.
|
||||
* **Smart Bandwidth Unleashed:** Say goodbye to crippled networks! When applying configurations, it intelligently identifies hardware limits and silently maximizes channel width (e.g., boosting 2.4G to HT40 and 5G to VHT80). It safely preserves existing Wi-Fi 6 (HE) configurations, ensuring effortless gigabit speeds.
|
||||
* **Seamless Two-Way Data Sync:** When switching between "Smart Connect" and "Separate Bands", Wi-Fi passwords and SSIDs are intelligently synced back and forth, eliminating repetitive typing. Features historical config memory—retrieve your old password instantly even after disabling and re-enabling Wi-Fi.
|
||||
|
||||
**⚙️ Core Architecture & Security**
|
||||
* **Zero-Zombie `procd` Daemon:** Replaces unreliable `hotplug` tricks with a single, ultra-lightweight native OpenWrt daemon (`netwiz-monitor`) that reads `ubus` states with near 0% CPU footprint.
|
||||
* **Strict ACL:** The frontend has zero direct write access to UCI. All modifications are safely encapsulated and validated within the backend `rpcd` script.
|
||||
* **Safe Configuration:** Strictly prevents routing loops and network conflicts (e.g., actively stops users from putting WAN and LAN in the same subnet).
|
||||
|
||||
**🚀 Smooth UX & Frontend**
|
||||
* **Pure CSR Architecture:** Built with modern Client-Side Rendering for a blazing-fast, app-like experience.
|
||||
* **Asynchronous UI & Hot-Reload:** Eliminates traditional blind wait times. The UI asynchronously "knocks" on the new IP with a real-time stopwatch. The backend applies changes via `reload` instead of a full `restart` to prevent Wi-Fi dropouts.
|
||||
* **Bypass Router Mode:** One-click deployment that auto-configures complex DHCP, DNS, and gateway settings flawlessly.
|
||||
* **Multi-language:** Native built-in i18n support, automatically adapting to English, zh-Hans, and zh-Hant.
|
||||
|
||||
### Breadcrumb Trail
|
||||
* 👉 **primary menu entry appearing right before “Logout -> Network Wizard**。
|
||||
(Placing the Network Setup Wizard immediately before “Logout” is a deliberate UI decision that benefits both beginners and users seeking quick configuration. It offers a highly visible, easily accessible entry point, eliminating the need to navigate complex OpenWrt menus. Moreover, this layout aligns with the polished, user-centric design conventions adopted by established router brands such as ASUS, TP-Link, and Xiaomi.)
|
||||
|
||||
🌐 **Secondary Router Mode (DHCP / Static IP)**
|
||||
* Use Case: When the upstream modem (ONT) already handles PPPoE dialing, or an existing primary router is present. This device operates as a secondary router or segmented subnet router.
|
||||
* Behavior: Supports both dynamic IP assignment (DHCP) and static IP configuration. Automatically provisions WAN interface settings and performs intelligent subnet validation to prevent routing loops or address conflicts.
|
||||
|
||||
🔌 **Broadband Dial-Up (PPPoE)**
|
||||
* Use Case: When the modem is configured in full bridge mode and this router is responsible for establishing the PPPoE connection, acting as the primary network gateway.
|
||||
* Behavior: Accurately applies PPPoE credentials (username and password), removes residual gateway configurations, and safely restarts the underlying dialer process to ensure a clean connection.
|
||||
|
||||
🏠 **LAN Configuration (Primary / Bypass Router Switching)**
|
||||
* Use Case: For modifying the device’s LAN management IP, or when a primary router already exists and this device functions as an auxiliary gateway (bypass router).
|
||||
* Behavior: One-click activation of “Bypass Router Mode” automatically disables the local DHCP service and enforces manual configuration of the upstream gateway. In primary router mode, safeguards ensure the gateway field remains empty, preventing disruption to the existing LAN topology.
|
||||
|
||||
A bypass router lets you add advanced features without touching the main network. The primary router keeps handling DHCP and NAT, while the bypass router processes selected traffic for tasks like policy routing or proxies. This keeps the network stable while giving you more control.
|
||||
|
||||
**A simple, common example:**
|
||||
|
||||
* You have a main router providing Wi-Fi and DHCP for your home. You add a bypass router and set only your laptop’s gateway to it. Now, your laptop’s traffic goes through the bypass router (e.g., for proxy or special routing), while all other devices continue using the main router normally. This way, you get advanced control on one device without affecting the rest of the network.
|
||||
|
||||
📶 **Wi-Fi Settings(Auto-hide when Wi-Fi is unavailable.)**
|
||||
* **Use Case**: Initial wireless network setup, or modifying the SSID, password, and encryption protocols for your whole-home network.
|
||||
* **Behavior**: Intelligently detects the number of underlying physical chips to dynamically render the UI. Supports one-click "Smart Connect (All Bands)" to build a seamless roaming network, or splits into independent 2.4G/5G networks. Perfectly backwards compatible with legacy 802.11b IoT devices.
|
||||
|
||||
### 💡 Execution Permissions Requirement (For GitHub Actions / Documentation)**
|
||||
|
||||
**Required Executable Permissions (chmod +x):**
|
||||
|
||||
To ensure the backend daemons and RPC interfaces function correctly, the following files must have executable permissions assigned before building the package:
|
||||
|
||||
```bash
|
||||
chmod +x luci-app-netwiz/root/usr/libexec/rpcd/netwiz
|
||||
chmod +x luci-app-netwiz/root/usr/libexec/rpcd/netwiz_dev
|
||||
chmod +x luci-app-netwiz/root/usr/libexec/netwiz-autodetect.sh
|
||||
chmod +x luci-app-netwiz/root/usr/libexec/netwiz-monitor-loop.sh
|
||||
chmod +x luci-app-netwiz/root/etc/init.d/netwiz-monitor
|
||||
chmod +x luci-app-netwiz/root/etc/init.d/netwiz-recovery
|
||||
chmod +x luci-app-netwiz/root/etc/init.d/netwiz-watchdog
|
||||
chmod +x luci-app-netwiz/root/etc/hotplug.d/dhcp/99-netwiz-guard
|
||||
```
|
||||
---
|
||||
|
||||
```bash
|
||||
luci-app-netwiz/
|
||||
├── README.md # Project Documentation (Features, Installation Guide, Changelog)
|
||||
├── LICENSE # Open Source License
|
||||
├── Makefile # OpenWrt Standard Makefile (Package Definition, Dependencies)
|
||||
│
|
||||
├── htdocs/ # Frontend UI Layer (Pure Static Resources)
|
||||
│ └── luci-static/resources/view/
|
||||
│ ├── netwiz.css # Global Common Stylesheet (Modals, Responsive, UI Beautification)
|
||||
│ ├── netwiz.js # Frontend Core 1: Wizard Engine (incl. Smart Backup, Anti-brick Validation, QR Code Dispatcher)
|
||||
│ ├── netwiz_dev.js # Frontend Core 2: Manager Engine (Device Radar, Smart Segmentation, UI Interaction)
|
||||
│ └── qrcode.min.js # Frontend Dependency: Pure Local Micro QR Code Generation Engine (for scan-to-connect Wi-Fi)
|
||||
│
|
||||
├── po/ # Multilingual Internationalization (i18n)
|
||||
│ ├── zh_Hans/netwiz.po # Simplified Chinese Translation Dictionary
|
||||
│ └── zh_Hant/netwiz.po # Traditional Chinese Translation Dictionary
|
||||
│
|
||||
└── root/ # Backend System Layer (Mapped to router root directory '/' upon packaging)
|
||||
├── etc/
|
||||
│ ├── config/
|
||||
│ │ └── netwiz # Low-level Configuration Library (Stores wizard status, manager grouping, and device activity timestamps)
|
||||
│ │
|
||||
│ ├── init.d/ # [Core Daemon Services / System Init Scripts] (Manages all background tasks)
|
||||
│ │ ├── netwiz-monitor # [Wizard Engine] Resident monitor daemon (Handles status during setup & frontend monitoring)
|
||||
│ │ ├── netwiz-recovery # [Rescue Engine] Auto-recovery on power loss (Anti-disconnect, extremely early boot START=15)
|
||||
│ │ └── netwiz-watchdog # [Keepalive Engine] IPv6 heartbeat & self-healing daemon (Late boot START=99)
|
||||
│ │
|
||||
│ └── hotplug.d/dhcp/ # [Manager Engine Stealth Guard Module]
|
||||
│ └── 99-netwiz-guard # MAC Anti-spoofing Trigger (Event-driven microsecond interception, zero background overhead)
|
||||
│
|
||||
└── usr/
|
||||
├── libexec/
|
||||
│ ├── netwiz-autodetect.sh # WAN Protocol Auto-detection Engine
|
||||
│ ├── netwiz-monitor-loop.sh # Wizard Debounce and Fallback Loop Logic
|
||||
│ │
|
||||
│ └── rpcd/ # RPC Interface Layer (Frontend-Backend Data Communication Bridge)
|
||||
│ ├── netwiz # Backend Interface 1: Dedicated to Wizard/Backup (Handles network protocol modification, kernel-level fingerprint post-mortem)
|
||||
│ └── netwiz_dev # Backend Interface 2: Dedicated to Manager (ARP/DHCP access, includes crash self-rescue and silent cleanup mechanisms)
|
||||
│
|
||||
└── share/
|
||||
├── luci/menu.d/
|
||||
│ └── luci-app-netwiz.json # System Menu Entry Registration
|
||||
└── rpcd/acl.d/
|
||||
└── luci-app-netwiz.json # UBUS Access Control List (Authorizes frontend access to backend interfaces)
|
||||
```
|
||||
---
|
||||
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/b2fca517-cbd2-4aa0-b557-bac5fd557dba" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/cce37944-d90a-4653-923c-d957812d8f37" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/8cae623d-766f-49a8-8c6c-ab78738d6b88" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/f0bd7bf8-16b7-4955-9a83-55673a7bf66e" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/ea664013-9f76-49e3-b10f-8307dbb6fc24" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/5c0370fc-efd9-4e51-be3c-ed35233786d6" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/bd4afb47-0dec-4606-91a3-da47e99ca6e0" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/51c10c7b-85df-4f69-b456-ea7523696ae5" />
|
||||
<img width="680" alt="Image" src="https://github.com/user-attachments/assets/8603b708-59c6-4a6d-b953-a83a26b00ef9" />
|
||||
|
||||
---
|
||||
@@ -0,0 +1,715 @@
|
||||
/* ---------------------------------------------------------
|
||||
1. 全局主容器
|
||||
--------------------------------------------------------- */
|
||||
body header {
|
||||
margin: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: clip !important;
|
||||
}
|
||||
|
||||
/* 所有输入框的占位符样式 */
|
||||
/* ------- 开始 ------*/
|
||||
body input::placeholder {
|
||||
color: #94a3b8 !important;
|
||||
font-weight: 500 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
body input::-webkit-input-placeholder {
|
||||
color: #94a3b8 !important;
|
||||
font-weight: 500 !important;
|
||||
opacity: 0.99 !important; /* 需要不一样,骗压缩工具 */
|
||||
}
|
||||
|
||||
body input::-moz-placeholder {
|
||||
color: #94a3b8 !important;
|
||||
font-weight: 500 !important;
|
||||
opacity: 0.98 !important; /* 需要不一样,骗压缩工具具 */
|
||||
}
|
||||
|
||||
body input:-ms-input-placeholder {
|
||||
color: #94a3b8 !important;
|
||||
font-weight: 500 !important;
|
||||
opacity: 0.97 !important; /* 需要不一样,骗压缩工具 */
|
||||
}
|
||||
/* ------- 结束 ------*/
|
||||
|
||||
#netwiz-container, #netwiz-dev-container {
|
||||
display: block !important; width: 100% !important;
|
||||
/* 屏幕高度 + 2px,使滚动条出现 */
|
||||
min-height: calc(100vh + 2px) !important;
|
||||
padding: 0 !important; margin: 0 auto !important; box-sizing: border-box !important;
|
||||
color: #64748b; font-size: 13.5px;
|
||||
/* clip 剪掉溢出像素 */
|
||||
overflow-x: clip !important;
|
||||
/* 底部防遮挡缓冲:防止被批量操作栏盖住最后一张卡片 */
|
||||
padding-bottom: 30px !important;
|
||||
}
|
||||
|
||||
.nw-wrapper {
|
||||
display: flex !important; flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
justify-content: flex-start !important;
|
||||
width: 100% !important; margin: 0 auto !important;
|
||||
padding: 0px 15px 15px 15px !important;
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif !important;
|
||||
box-sizing: border-box !important;
|
||||
min-height: auto !important;
|
||||
background: transparent !important;
|
||||
/* 顶部导航列重新恢复悬浮 */
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.nw-header, .nw-step, .nd-control-bar, .nd-list, .nd-list-header, .nd-category-tabs {
|
||||
width: 100% !important;
|
||||
max-width: 1080px !important; /* 页面宽度 */
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
2. 顶部蓝色大横幅 (Header)
|
||||
--------------------------------------------------------- */
|
||||
.nw-header { top: -20px; display: block !important; text-align: center !important; background-color: #5e72e4 !important; padding: 22px 15px 10px 15px !important; border-radius: 0 0 14px 14px !important; position: relative !important; box-shadow: 0 8px 20px rgba(94, 114, 228, 0.15) !important; z-index: 1 !important; margin: 0 auto 20px auto !important; border: none !important; width: 100% !important; max-width: 650px !important; }
|
||||
.nw-main-title { font-size: 36px !important; letter-spacing: 4px !important; font-weight: 600 !important; margin: 0 0 8px 0 !important; color: #ffffff !important; line-height: 1.2 !important; }
|
||||
.nw-header p { color: #ffffff !important; font-size: 20px !important; letter-spacing: 2px !important; opacity: 0.9 !important; margin: 8px 0 !important; }
|
||||
|
||||
/* 标题包裹器 */
|
||||
.nw-title-wrap { position: relative; display: inline-block; cursor: default; }
|
||||
|
||||
.nw-reopen-btn { margin: 10px 0 5px 0; display: inline-flex; align-items: center; justify-content: center; gap: 5px; cursor: pointer; background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.2); padding: 6px 18px; border-radius: 20px; font-size: 13.5px; font-weight: 500; color: #fff; transition: all 0.25s ease; }
|
||||
.nw-reopen-btn:hover { background: rgba(255,255,255,0.25); transform: translateY(-1px); }
|
||||
|
||||
/* 版本号标签 (悬浮显示,不可点击) */
|
||||
.nw-version-tag { position: absolute; top: 10px; left: 100%; margin-left: 2px; background: rgba(255, 255, 255, 0.15); color: #fff; font-size: 12px; font-weight: bold; padding: 2px 8px; border-radius: 6px; font-family: monospace; z-index: 50; border: 1px solid rgba(255, 255, 255, 0.3); white-space: nowrap; box-shadow: 0 4px 10px rgba(0,0,0,0.08); opacity: 0; pointer-events: none; transition: all 0.3s ease; transform: translateY(0px);letter-spacing: 0; }
|
||||
.nw-title-wrap:hover .nw-version-tag { opacity: 1; top: 5px; transform: translateY(-5px); }
|
||||
|
||||
/* 当有更新时,JS 会动态加入 .has-update 类 (常亮显示、变绿、允许点击) */
|
||||
.nw-version-tag.has-update { pointer-events: auto; cursor: pointer; opacity: 1; top: 5px; transform: translateY(-5px); background: #67A57B; border-color: #67A57B; }
|
||||
.nw-version-tag.has-update:hover { background: #5a936d; }
|
||||
|
||||
/* 小红点默认隐藏,增加呼吸动画定位在右上角 */
|
||||
.nw-version-dot { display: none; position: absolute; top: -3px; right: -3px; width: 8px; height: 8px; background-color: #ef4444; border-radius: 50%; box-shadow: 0 0 0 2px rgba(15, 23, 42, 0.9); animation: pulse-dot 2s infinite; }
|
||||
@keyframes pulse-dot { 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.8); } 70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); } 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } }
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
3. 主向导 4 个彩色大卡片
|
||||
--------------------------------------------------------- */
|
||||
.nw-step { text-align: center !important; animation: slideUp 0.4s ease-out; }
|
||||
.nw-form-area, .nw-confirm-board { position: relative; max-width: 460px; margin: -15px auto 25px auto; text-align: left; padding: 30px 35px; border-radius: 16px; background-color: rgba(255, 255, 255, 0.88); box-shadow: 0 10px 30px rgba(0,0,0,0.06); box-sizing: border-box; display: block !important; overflow: hidden; word-break: break-word; }
|
||||
.nw-step-title { text-align: center; margin-bottom: 25px; color: #111; font-weight: 600; font-size: 18px; }
|
||||
|
||||
.nw-card-group { display: flex !important; gap: 18px !important; justify-content: center !important; flex-wrap: wrap !important; margin-top: -20px !important; width: 100% !important; box-sizing: border-box !important; position: relative !important; z-index: 10 !important; }
|
||||
.nw-card { flex: 1 !important; min-width: 170px !important; max-width: 220px !important; aspect-ratio: 1 / 1 !important; padding: 28px 18px !important; border-radius: 14px !important; cursor: pointer !important; backdrop-filter: blur(12px) !important; border: 1px solid rgba(255,255,255,0.2) !important; box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important; transition: transform 0.25s ease !important; display: flex !important; flex-direction: column !important; align-items: center !important; box-sizing: border-box !important; }
|
||||
.nw-card:hover { transform: translateY(-5px) !important; }
|
||||
|
||||
.nw-card[data-mode="pppoe"] { background-color: rgba(79, 150, 101, 0.95) !important; }
|
||||
.nw-card[data-mode="wifi"] { background-color: rgba(245, 54, 92, 0.95) !important; }
|
||||
.nw-card[data-mode="router"] { background-color: rgba(80, 0, 183, 0.95) !important; }
|
||||
.nw-card[data-mode="lan"] { background-color: rgba(245, 158, 11, 0.95) !important; }
|
||||
|
||||
.nw-card-title { font-size: 17px !important; margin: 0 0 8px 0 !important; color: #ffffff !important; font-weight: 600 !important; line-height: 1.2 !important; text-align: center !important; }
|
||||
.nw-card span { font-size: 14.5px !important; color: #ffffff !important; line-height: 1.4 !important; opacity: 0.9 !important; display: block !important; text-align: center !important; }
|
||||
|
||||
.nw-badge { width: 50px !important; height: 50px !important; border-radius: 50% !important; display: flex !important; align-items: center !important; justify-content: center !important; margin-bottom: 15px !important; }
|
||||
.nw-badge-pppoe { background-color: #fef3c7 !important; color: #059669 !important; }
|
||||
.nw-badge-wifi { background-color: #f3e8ff !important; color: #0284c7 !important; }
|
||||
.nw-badge-dhcp { background-color: #e0f2fe !important; color: #9333ea !important; }
|
||||
.nw-badge-bypass { background-color: #d1fae5 !important; color: #d97706 !important; }
|
||||
.nw-badge svg { width: 24px !important; height: 24px !important; display: block !important; }
|
||||
|
||||
.nw-current-mode-display { margin-top: 20px !important; background: #5e72e4 !important; padding: 18px 30px !important; border-radius: 12px !important; display: flex !important; flex-direction: column !important; align-items: center !important; box-shadow: 0 8px 20px rgba(94, 114, 228, 0.3) !important; text-align: center !important; box-sizing: border-box !important; word-break: break-all !important; overflow-wrap: anywhere !important; overflow: hidden !important; width: 100% !important; max-width: 820px !important; margin-left: auto !important; margin-right: auto !important; }
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
4. 设备管家组件
|
||||
--------------------------------------------------------- */
|
||||
.nd-control-bar {
|
||||
position: sticky !important; top: 0px !important; z-index: 750 !important;
|
||||
display: flex !important; align-items: center !important; justify-content: space-between !important;
|
||||
background: rgba(94, 114, 228) !important; backdrop-filter: blur(10px) !important;
|
||||
padding: 12px 20px !important; border-radius: 14px !important; color: #fff !important;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15) !important; margin: -15px auto 5px auto !important;
|
||||
}
|
||||
.nd-cb-back { width: 36px; height: 36px; background: rgba(255,255,255,0.2); border-radius: 10px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: 0.2s; flex-shrink: 0; }
|
||||
.nd-cb-back:hover { background: rgba(255,255,255,0.3); transform: translateX(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
.nd-cb-title-wrap { flex: 1; margin: 0 15px; text-align: left; }
|
||||
.nd-cb-title { font-size: 17px; font-weight: bold; line-height: 1.2; margin-bottom: 4px; color: #fff; }
|
||||
.nd-cb-sub { font-size: 12.5px; opacity: 0.85; margin: 0; }
|
||||
.nd-cb-refresh { padding: 6px 14px; background: transparent; border: 1px solid rgba(255,255,255,0.3); border-radius: 8px; font-size: 13.5px; font-weight: bold; cursor: pointer; display: flex; align-items: center; gap: 6px; transition: 0.2s; flex-shrink: 0; }
|
||||
.nd-cb-refresh:hover { background: rgba(255,255,255,0.15); }
|
||||
|
||||
.nd-list {
|
||||
display: flex !important; flex-direction: column !important; gap: 12px !important;
|
||||
padding: 0 !important; margin-bottom: 10px !important;
|
||||
}
|
||||
.nd-card {
|
||||
display: flex !important; align-items: center !important; justify-content: space-between !important;
|
||||
background: #ffffff !important; padding: 18px 20px !important; border-radius: 14px !important;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.03) !important; border: 1px dashed #cbd5e1 !important;
|
||||
transition: all 0.2s ease !important; gap: 12px !important;
|
||||
width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; margin: 0 !important;
|
||||
}
|
||||
.nd-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.06) !important; transform: translateY(-2px) !important; border-color: #3b82f6 !important; }
|
||||
.nd-card-left { flex: 2; min-width: 180px; display: flex; flex-direction: column; gap: 6px; }
|
||||
.nd-card-name { font-size: 15px; font-weight: 600; color: #334155; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; line-height: 1.3; }
|
||||
.nd-card-mac { font-size: 12.5px; font-family: monospace; color: #64748b; background: #f8fafc; padding: 3px 6px; border-radius: 6px; width: fit-content; border: 1px dashed #cbd5e1; letter-spacing: 0.5px; }
|
||||
.nd-card-mid { flex: 1.2; min-width: 140px; display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
|
||||
.nd-card-ip { font-size: 15.5px; font-weight: bold; color: #3b82f6; font-family: monospace; letter-spacing: 0.5px; }
|
||||
.nd-lease-info { font-size: 12px; color: #94a3b8; font-family: monospace; display: flex; align-items: center; gap: 4px; }
|
||||
.nd-card-right { flex: 0 0 auto; display: flex; justify-content: flex-end; gap: 10px; }
|
||||
|
||||
.nd-status-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 6px; border-radius: 4px; font-size: 11px; font-weight: bold; margin-left: 2px; }
|
||||
.nd-status-online { background: #d1fae5; color: #059669; }
|
||||
.nd-status-offline { background: #f1f5f9; color: #64748b; }
|
||||
.nd-dot-online { width: 5px; height: 5px; background: #10b981; border-radius: 50%; box-shadow: 0 0 4px #10b981; }
|
||||
.nd-dot-offline { width: 5px; height: 5px; background: #94a3b8; border-radius: 50%; }
|
||||
.nd-badge { padding: 2px 6px; border-radius: 4px; font-size: 11px; border: 1px solid transparent; white-space: nowrap; margin-left: 4px; font-weight: bold; }
|
||||
.nd-badge-static { background: #e0f2fe; color: #0284c7; border-color: #bae6fd; }
|
||||
.nd-badge-gw { background: #fef08a; color: #854d0e; border-color: #fde047; }
|
||||
.nd-badge-local { background: #e0e7ff; color: #3730a3; border-color: #c7d2fe; }
|
||||
.nd-badge-visitor { background: #fae8ff !important; color: #86198f !important; border-color: #f0abfc !important; }
|
||||
.nd-badge-pending { background: #fffbeb !important; color: #d97706 !important; border-color: #fde68a !important; }
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
5. 表单控件与按钮
|
||||
--------------------------------------------------------- */
|
||||
.nw-setting-row, .nw-setting-row-alt, .nw-switch-row-padded, .nw-adv-setting-row, .nw-roam-row, .nw-roam-row-alt, .nw-split-header-row, .nw-legacy-row, .nw-wisp-header { display: flex; align-items: center; justify-content: space-between; margin: 12px 0; }
|
||||
.nw-setting-row { padding: 12px 0; border-bottom: 1px solid #f1f5f9; }
|
||||
.nw-switch-row-padded { padding: 8px 0 12px 0; border-bottom: 1px dashed #e2e8f0; }
|
||||
|
||||
.nw-setting-row-label, .nw-desc-title, .nw-wisp-title, .nw-roam-title {
|
||||
font-size: 15px !important; font-weight: 700 !important; color: #1e293b !important; flex: 1; line-height: 1.3; margin: 0 10px 0 0 !important;
|
||||
}
|
||||
|
||||
.nw-adv-setting-row { padding: 4px 0 12px 0; border-bottom: 1px dashed #cbd5e1; animation: fadeIn 1s ease; }
|
||||
.nw-roam-row { padding: 4px 0 12px 0; border-bottom: 1px dashed #cbd5e1; }
|
||||
.nw-wisp-header { margin: 8px 0 4px 0; }
|
||||
.nw-value { border: none !important; padding-bottom: 10px !important; display: flex !important; flex-direction: column !important; width: 100% !important; margin: 0 !important; background: transparent !important; }
|
||||
.nw-value-title { text-align: left !important; font-weight: 600 !important; color: #334155 !important; font-size: 14.5px !important; margin: 8px 0 8px 4px !important; line-height: 1.2 !important; display: block !important; }
|
||||
.nw-value-field { width: 100% !important; margin: 0 !important; padding: 0 !important; display: block !important; }
|
||||
|
||||
.nw-form-area input[type="text"], .nw-form-area input[type="password"], .nw-form-area select, .nw-form-area textarea, .nw-wiz-modal-box input[type="text"], .nw-wiz-modal-box textarea, .nd-input { appearance: none !important; width: 100% !important; box-sizing: border-box !important; padding: 12px 14px !important; border: 1px solid #cbd5e1 !important; border-radius: 8px !important; font-size: 14px !important; outline: none !important; background: #f8fafc !important; color: #0f172a !important; min-height: 44px !important; line-height: 1.5 !important; box-shadow: inset 0 1px 2px rgba(0,0,0,0.02) !important; transition: all 0.2s ease !important; font-family: inherit; resize: none; word-break: break-all; }
|
||||
.nw-form-area input:focus, .nw-form-area select:focus, .nw-form-area textarea:focus, .nw-wiz-modal-box input:focus, .nw-wiz-modal-box textarea:focus, .nd-input:focus { border-color: #3b82f6 !important; background: #ffffff !important; box-shadow: 0 0 0 3px rgba(59,130,246,0.15) !important; }
|
||||
|
||||
.nw-switch { position: relative; display: inline-block; width: 42px; height: 22px; margin: 0; }
|
||||
.nw-switch input { opacity: 0; width: 0; height: 0; }
|
||||
.nw-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #cbd5e1; transition: background-color 0.3s ease; border-radius: 24px; }
|
||||
.nw-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: transform 0.3s ease; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
|
||||
input:checked + .nw-slider { background-color: #10b981; }
|
||||
input:checked + .nw-slider:before { transform: translateX(20px); }
|
||||
|
||||
.nw-actions { margin-top: 20px; padding-bottom: 20px; display: flex; justify-content: center; gap: 15px; width: 100%; box-sizing: border-box; }
|
||||
.nw-u-btn, .nd-btn { appearance: none !important; border: none; border-radius: 8px !important; padding: 10px 15px !important; font-weight: 600 !important; font-size: 14px !important; cursor: pointer !important; transition: all 0.25s ease !important; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; min-width: 110px; letter-spacing: 0.5px; }
|
||||
.nw-u-btn-green, .nd-btn-green { background: #10b981 !important; color: white !important; }
|
||||
.nw-u-btn-green:hover, .nd-btn-green:hover { background: #059669 !important; transform: translateY(-2px) !important; box-shadow: 0 4px 12px rgba(16, 185, 129, 0.35) !important; }
|
||||
.nw-u-btn-blue, .nd-btn-blue { background: #3b82f6 !important; color: white !important; }
|
||||
.nw-u-btn-blue:hover, .nd-btn-blue:hover { background: #2563eb !important; transform: translateY(-2px) !important; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.35) !important; }
|
||||
.nw-u-btn-red, .nd-btn-red { background: #ef4444 !important; color: white !important; }
|
||||
.nw-u-btn-red:hover, .nd-btn-red:hover { background: #dc2626 !important; transform: translateY(-2px) !important; box-shadow: 0 4px 12px rgba(239, 68, 68, 0.35) !important; }
|
||||
.nw-u-btn-gray, .nd-btn-gray { background: #f1f5f9 !important; color: #475569 !important; border: 1px solid #e2e8f0 !important; }
|
||||
.nw-u-btn-gray:hover, .nd-btn-gray:hover { background: #e2e8f0 !important; color: #0f172a !important; transform: translateY(-2px) !important; }
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
6. 弹窗与浮层 (Modals)
|
||||
--------------------------------------------------------- */
|
||||
#nw-global-modal, .nw-wisp-modal, #nd-modal-overlay {
|
||||
display: none; position: fixed !important; top: 0 !important; left: 0 !important;
|
||||
width: 100% !important; height: 100% !important;
|
||||
background: rgba(0,0,0,0.65) !important; z-index: 2000000 !important;
|
||||
align-items: center !important; justify-content: center !important;
|
||||
backdrop-filter: blur(4px) !important;
|
||||
/* 裁切动画瞬間的 20px 溢出,消除出现的滚动条 */
|
||||
overflow: hidden !important;
|
||||
}
|
||||
#nw-global-modal .nw-modal-box, .nw-wisp-modal-box, .nw-wiz-modal-box, .nd-modal-box {
|
||||
background: #fff;
|
||||
width: 98% !important; /* 不超过屏幕 98% */
|
||||
max-width: 460px !important;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
box-sizing: border-box !important; /* 防止内边距把弹窗撑大 */
|
||||
}
|
||||
#nw-global-modal .nw-modal-box, .nd-modal-box { padding: 35px 15px; text-align: center; max-width: 440px; box-sizing: border-box; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4); }
|
||||
.nw-wisp-modal-box { max-width: 400px; max-height: 80vh; }
|
||||
.nw-wiz-modal-box { max-width: 460px; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4); }
|
||||
.nw-spinner, .nd-spinner { width: 36px; height: 36px; border: 4px solid #f1f5f9; border-top: 4px solid #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin: 10px auto 15px; }
|
||||
|
||||
.nd-modal-title { font-size: 18px; color: #0f172a; margin: 0 0 15px 0 !important; font-weight: 600; border: none !important; }
|
||||
.nd-input-group { text-align: left; margin-bottom: 15px; }
|
||||
.nd-input-label { display: block; font-size: 14px; color: #64748b; margin-bottom: 5px; font-weight: bold; }
|
||||
.nd-empty { text-align: center; padding: 50px 20px; color: #64748b; font-size: 14.5px; background: #fff; border-radius: 14px; border: 1px dashed #cbd5e1; width: 100%; box-sizing: border-box; margin-top: 15px; }
|
||||
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
|
||||
@keyframes slideUp {
|
||||
0% { opacity: 0; transform: scale(0.95); }
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
/* 7. 顶部返回按钮 */
|
||||
.nw-top-back { position: absolute; top: 16px; left: 16px; width: 45px; height: 45px; border-radius: 50%; background: #f1f5f9; color: #64748b; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; z-index: 10; }
|
||||
.nw-top-back:hover { background: #e2e8f0; color: #0f172a; transform: translateX(-3px); box-shadow: 2px 2px 8px rgba(0,0,0,0.05); }
|
||||
|
||||
/* 8. 单选按钮组 (Radio) */
|
||||
.nw-radio-group { display: flex; gap: 12px; align-items: stretch; margin: 0; padding: 0; width: 100%; padding: 1px;}
|
||||
.nw-radio-btn { cursor: pointer; position: relative; display: block; flex: 1; margin: 0 !important; padding: 0 !important; }
|
||||
.nw-radio-btn input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
|
||||
.nw-radio-btn-text { display: flex; align-items: center; justify-content: center; height: 100%; padding: 15px 8px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 16px; color: #475569; background: #fff; transition: all 0.25s ease; position: relative; overflow: hidden; font-weight: normal; box-sizing: border-box; line-height: 1.3; text-align: center; }
|
||||
.nw-radio-btn:hover .nw-radio-btn-text { border-color: #3b82f6; color: #3b82f6; background: #f8fafc; }
|
||||
.nw-radio-btn input[type="radio"]:checked + .nw-radio-btn-text { border-color: #3b82f6; color: #ef4444; font-weight: 600; box-shadow: 0 0 0 1px #3b82f6; background: #eff6ff; }
|
||||
.nw-radio-btn-text::after { content: ""; position: absolute; right: 0; bottom: 0; width: 0; height: 0; border-bottom: 35px solid #3b82f6; border-left: 30px solid transparent; opacity: 0; transition: opacity 0.2s ease; }
|
||||
.nw-radio-btn-text::before { content: ""; position: absolute; right: 4px; bottom: 3px; width: 8px; height: 18px; border: solid white; border-width: 0 3px 3px 0; transform: rotate(38deg); opacity: 0; transition: opacity 0.2s ease; z-index: 2; }
|
||||
.nw-radio-btn input[type="radio"]:checked + .nw-radio-btn-text::after, .nw-radio-btn input[type="radio"]:checked + .nw-radio-btn-text::before { opacity: 1; }
|
||||
|
||||
/* 9. 自定义复选框 (Checkbox) */
|
||||
.nw-wiz-cb-wrap { display: inline-flex; align-items: center; cursor: pointer; user-select: none; position: relative; margin: 0; }
|
||||
.nw-wiz-cb-wrap input { position: absolute !important; opacity: 0 !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important; border: none !important; left: -9999px !important; }
|
||||
.nw-wiz-checkmark { box-sizing: border-box; height: 18px; width: 18px; background: #fff; border: 2px solid #cbd5e1; border-radius: 4px; margin-right: 8px; transition: all 0.2s ease; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.nw-wiz-cb-wrap:hover input ~ .nw-wiz-checkmark { border-color: #3b82f6; }
|
||||
.nw-wiz-cb-wrap input:checked ~ .nw-wiz-checkmark { background-color: #3b82f6; border-color: #3b82f6; }
|
||||
.nw-wiz-checkmark:after { content: ""; display: none; width: 5px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); margin-bottom: 2px; }
|
||||
.nw-wiz-cb-wrap input:checked ~ .nw-wiz-checkmark:after { display: block; }
|
||||
|
||||
/* 10. 面板与提示框 (Advanced, Notes, Alerts) */
|
||||
.nw-adv-btn { text-align: center; margin: 12px; cursor: pointer; color: #64748b; font-size: 13.5px; font-weight: bold; user-select: none; transition: color 0.25s ease; }
|
||||
.nw-adv-btn:hover { color: #3b82f6; }
|
||||
.nw-adv-panel { background: rgba(241, 245, 249, 0.5); border-radius: 10px; padding: 15px; margin-top: 12px; border: 1px solid #e2e8f0; box-shadow: inset 0 2px 5px rgba(0,0,0,0.02); }
|
||||
.nw-note-box { background-color: #f8fafc; padding: 12px; font-size: 13px; margin-top: 15px; border: 1px solid #e2e8f0; line-height: 1.6; color: #475569; border-radius: 10px; }
|
||||
.nw-note-title { font-weight: bold; color: #0f172a; margin-bottom: 6px; font-size: 14px; }
|
||||
.nw-confirm-mode-text { color: #fff; background: #0055bb; padding: 18px 4px; border-radius: 10px; box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); margin-top: 12px; }
|
||||
.nw-roam-desc { font-size: 13.5px; color: #64748b; line-height: 1.5; word-break: break-word; }
|
||||
.nw-roam-warn { font-size: 13px; color: #ef4444; margin-top: 5px; font-weight: 600; background: #fef2f2; padding: 5px 8px; border-radius: 6px; border: 1px solid #fecaca; line-height: 1.4; }
|
||||
.nw-warn-bypass, .nw-warn-main { padding: 10px; border-radius: 8px; font-size: 15.5px; margin-bottom: 12px; border: 1px solid; line-height: 1.6; font-weight: bolder; }
|
||||
.nw-warn-bypass { background: #fef2f2; color: #ef4444; border-color: #fecaca; }
|
||||
.nw-warn-main { background: #f0fdf4; color: #059669; border-color: #bbf7d0; }
|
||||
|
||||
/* 11. 快速向导弹窗专用类与辅助工具类 */
|
||||
.nw-wiz-header-responsive { display: flex; justify-content: space-between; align-items: center; padding: 12px 10px; }
|
||||
.nw-wiz-step-wrap { flex: 1; display: flex; justify-content: flex-start; order: 1; }
|
||||
.nw-wiz-title-responsive { flex: 2; margin: 0; padding: 0; text-align: center; font-size: 17px; letter-spacing: 0.5px; white-space: nowrap; color: #fff; order: 2; }
|
||||
.nw-wiz-close-wrap { flex: 1; display: flex; justify-content: flex-end; order: 3; }
|
||||
.nw-wisp-modal-title, .nw-wiz-modal-title { margin: 0 15px; font-size: 15px; color: #eee; background: #0f172a; text-align: center; border-radius: 10px; }
|
||||
.nw-wiz-modal-title { background: transparent; font-size: 25px; font-weight: 600; margin: 0; padding: 0; letter-spacing: 0.5px; }
|
||||
.nw-wisp-ui-panel { flex-direction: column; align-items: center; background: #f8fafc; padding: 10px; border-radius: 8px; border: 1px solid #e2e8f0; }
|
||||
.nw-wisp-target-input { background: #e2e8f0 !important; color: #475569 !important; }
|
||||
.nw-wifi-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||
.nw-tab-btn { padding: 10px 0; flex: 1; border: none; border-radius: 6px; font-size: 17px; font-weight: bold; cursor: pointer; transition: all 0.2s; }
|
||||
.nw-hl { color: #facc15; font-weight: bold; margin-left: 0; }
|
||||
.nw-flex-1 { flex: 1; padding-right: 12px; min-width: 0; }
|
||||
.nw-flex-shrink-0 { flex-shrink: 0; }
|
||||
.nw-scale-switch { transform: scale(0.9); transform-origin: left; }
|
||||
.nw-m0 { margin: 0 !important; }
|
||||
.nw-pointer { cursor: pointer; }
|
||||
.nw-step-line, .nw-step-line svg {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* 首页 Wi-Fi 状态面板细节 */
|
||||
.nw-info-item { display: inline-block; white-space: normal; word-break: break-all; margin: 0 8px; max-width: 100%; }
|
||||
.nw-wifi-line { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px 10px; margin-bottom: 6px; width: 100%; max-width: 100%; min-width: 0; box-sizing: border-box; }
|
||||
.nw-wifi-left { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center; max-width: 100%; min-width: 0; }
|
||||
.nw-wifi-badge { padding: 6px 8px; background: #ffffff; border-radius: 6px; font-size: 14px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); white-space: nowrap; flex-shrink: 0; }
|
||||
.nw-wifi-colon { font-size: 14px; opacity: 0.9; font-weight: 600; margin: 0 4px; flex-shrink: 0; }
|
||||
.nw-wifi-name { font-size: 15px; letter-spacing: 0.5px; display: inline-flex; flex-wrap: wrap; justify-content: center; align-items: center; word-break: break-all; overflow-wrap: anywhere; white-space: normal; text-align: center; max-width: 100%; min-width: 0; }
|
||||
.nw-wifi-pwd { color: #ffffff; font-size: 14px; font-weight: 600; white-space: normal; word-break: break-all; max-width: 100%; min-width: 0; }
|
||||
|
||||
/* 13. 批量操作悬浮底栏与选择框 */
|
||||
.nd-card-checkbox { margin-right: 15px; flex-shrink: 0; }
|
||||
|
||||
.nd-batch-bar {
|
||||
position: fixed !important;
|
||||
bottom: -150px !important;
|
||||
z-index: 99999 !important;
|
||||
background: rgba(255, 255, 255, 0.98) !important;
|
||||
backdrop-filter: blur(12px) !important;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2) !important;
|
||||
border-radius: 16px !important;
|
||||
padding: 15px 45px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important;
|
||||
|
||||
/* 防止 padding 把宽度撐大 */
|
||||
box-sizing: border-box !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
margin: 0 auto !important;
|
||||
width: calc(100% - 30px) !important;
|
||||
max-width: 820px !important;
|
||||
transform: none !important;
|
||||
|
||||
border: 1px solid #cbd5e1 !important;
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
||||
}
|
||||
|
||||
.nd-batch-bar.show {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
pointer-events: auto !important;
|
||||
bottom: 25px !important;
|
||||
}
|
||||
.nd-batch-info { width: 100%; text-align: center; font-weight: 600; font-size: 20px; color: #000;}
|
||||
.nd-batch-actions { width: 100%; justify-content: space-between; display: flex; gap: 10px; }
|
||||
.nd-batch-actions button { flex: 1; }
|
||||
|
||||
/* ======== 批量分配与分段组件 ======== */
|
||||
.nd-category-tabs { display: flex; gap: 10px; width: 100%; max-width: 860px; margin: 15px auto 25px auto; flex-wrap: wrap; justify-content: center; padding-bottom: 5px; align-items: center; }
|
||||
.nd-cat-btn { white-space: nowrap; padding: 8px 14px; margin: 1px; border-radius: 20px; background: #fff; border: 1px solid #cbd5e1; color: #64748b; font-size: 13.5px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 4px; }
|
||||
.nd-cat-btn:hover { background: #f8fafc; border-color: #94a3b8; transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0,0,0,0.05); }
|
||||
.nd-cat-btn.active { background: #3b82f6 !important; color: #fff !important; border-color: #3b82f6; box-shadow: 0 4px 10px rgba(59,130,246,0.3); }
|
||||
.nd-list-header { display:flex; width:100%; margin: 0 auto 15px auto; padding: 0 5px; align-items:center; max-width: 860px; }
|
||||
.nd-strategy-card { padding: 12px 15px; border: 2px solid #e2e8f0; border-radius: 10px; cursor: pointer; font-weight: bold; color: #475569; transition: all 0.2s; background: #f8fafc; line-height: 1.4; display: flex; flex-direction: column; gap: 4px; text-align: left; }
|
||||
.nd-strategy-card:hover { border-color: #94a3b8; }
|
||||
.nd-strategy-card.active { border-color: #3b82f6; background: #eff6ff; color: #1e3a8a; box-shadow: 0 4px 12px rgba(59,130,246,0.15); }
|
||||
|
||||
/* 修复后的智能分段布局:全局强制单行并排 */
|
||||
.nd-smart-row { flex-direction: column !important; align-items: flex-start !important; padding-bottom: 5px !important; margin: 5px 0 !important; width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; }
|
||||
.nd-smart-label { width: 100% !important; margin-bottom: 3px !important; font-size: 14.5px !important; }
|
||||
.nd-smart-inputs { align-items: center; gap: 4px; width: 100% !important; max-width: 100% !important; background: #f8fafc !important; padding: 6px !important; box-sizing: border-box !important; display: flex !important; }
|
||||
.nd-ip-prefix {letter-spacing: 1px; margin-right: auto !important; font-size: 18px !important; flex-shrink: 1 !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important;}
|
||||
.nd-input-sm { flex: 1 !important; min-width: 0 !important; padding: 8px 2px !important; box-sizing: border-box !important; text-align: CENTER; border: 1px solid #cbd5e1 !important;border-radius: 8px !important;font-size: 14px !important;outline: none !important;background: #f8fafc !important;color: #0f172a !important; line-height: 1.5 !important;transition: all 0.2s ease !important;font-family: inherit;resize: none;word-break: break-all;}
|
||||
.nd-input-sm:focus { border-color: #3b82f6 !important; }
|
||||
.nd-smart-dash { color: #cbd5e1 !important; font-weight: bold !important; padding: 0 4px !important; }
|
||||
|
||||
/* ======== 入场动画 ======== */
|
||||
@keyframes nwPageFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.96); /* 初始稍微縮小 */
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: none; /*悬浮条不失效 */
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nwPageFadeOut {
|
||||
0% { opacity: 1; transform: none; }
|
||||
100% { opacity: 0; transform: scale(0.96); /* 出场時也微微缩小消失 */ }
|
||||
}
|
||||
|
||||
/* ==============================================
|
||||
2. 弹窗太长不会让确定按鈕被挤出屏幕
|
||||
============================================== */
|
||||
.nd-modal-box {
|
||||
max-height: 95vh !important; /* 占屏幕 95% */
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
#nd-m-form {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important; /* 出现滚动条 */
|
||||
overflow-x: hidden !important;
|
||||
padding-right: 8px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
#nd-m-form::-webkit-scrollbar { width: 6px; }
|
||||
#nd-m-form::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
|
||||
|
||||
|
||||
/* ======== 全局弹窗 ======== */
|
||||
.nw-modal-box, .nw-wiz-modal-box, .nd-modal-box {
|
||||
padding: 0 !important;
|
||||
text-align: center !important;
|
||||
box-sizing: border-box !important;
|
||||
overflow: hidden !important; /* 强制裁切顶部直角为圆角 */
|
||||
}
|
||||
|
||||
.nw-confirm-board {
|
||||
padding: 30px 4px !important;
|
||||
text-align: center !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.nw-confirm-board h3,
|
||||
.nw-modal-box h3,
|
||||
.nw-confirm-board .nw-modal-title,
|
||||
.nd-modal-box .nd-modal-title {
|
||||
margin: 0 0 10px 0 !important;
|
||||
line-height: 1.4 !important;
|
||||
background: #3b82f6 !important;
|
||||
color: #fff !important;
|
||||
padding: 15px 0 !important;
|
||||
width: 100% !important;
|
||||
font-size: 25px !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.nw-confirm-board p,
|
||||
.nw-modal-box p,
|
||||
.nw-confirm-board .nw-modal-desc,
|
||||
.nw-modal-box #nw-global-msg,
|
||||
.nd-modal-box #nd-m-content,
|
||||
.nd-modal-box #nd-m-form {
|
||||
margin: 0 !important;
|
||||
line-height: 1.6 !important;
|
||||
color: #475569 !important;
|
||||
font-size: 16px !important;
|
||||
padding: 6px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.nw-modal-box .nw-actions,
|
||||
.nw-wiz-modal-box .nw-actions,
|
||||
.nw-confirm-board .nw-actions,
|
||||
.nd-modal-box .nw-actions,
|
||||
#nw-global-btn-wrap,
|
||||
.nd-modal-box > div:last-child {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: center !important;
|
||||
gap: 15px !important;
|
||||
width: 100% !important;
|
||||
padding: 0 10px 25px 10px !important;
|
||||
margin-top: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.nw-modal-box .nw-u-btn,
|
||||
.nw-wiz-modal-box .nw-u-btn,
|
||||
.nw-confirm-board .nw-u-btn,
|
||||
.nd-modal-box .nd-btn {
|
||||
margin: 0 !important;
|
||||
min-width: 120px !important;
|
||||
flex: 1 !important;
|
||||
max-width: 220px !important;
|
||||
}
|
||||
|
||||
.nw-actions [style*="none"],
|
||||
.nw-actions button:empty,
|
||||
.nw-actions a:empty {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#nw-global-modal {
|
||||
z-index: 9999999 !important;
|
||||
}
|
||||
|
||||
/* ======== 防主题劫持:强制向导的弹窗按钮横排 ======== */
|
||||
.nw-wiz-modal-box div:has(> .nw-u-btn-red):has(> .nw-u-btn-green) {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: center !important;
|
||||
gap: 15px !important;
|
||||
width: 100% !important;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.nw-wiz-modal-box .nw-u-btn-red,
|
||||
.nw-wiz-modal-box .nw-u-btn-green {
|
||||
flex: 1 !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
margin: 2px !important;
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
/* 只要带 none 就不让显示 */
|
||||
.nw-wiz-modal-box button[style*="none"],
|
||||
.nw-wiz-modal-box button.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.nd-control-bar label.nw-switch {
|
||||
width: auto !important; height: auto !important; flex-shrink: 0 !important;
|
||||
overflow: visible !important; display: flex !important; align-items: center !important; margin: 0 !important;
|
||||
}
|
||||
.nd-control-bar label.nw-switch > span { white-space: nowrap !important; }
|
||||
.nd-control-bar label.nw-switch > div { width: 42px !important; height: 22px !important; flex-shrink: 0 !important; }
|
||||
|
||||
/* 开关开启时变成绿色! */
|
||||
.nd-control-bar input:checked + .nw-slider { background-color: #10b981 !important; }
|
||||
.btn-fw:active { transform: scale(0.95); opacity: 0.8; }
|
||||
|
||||
#nd-modal-overlay .nd-modal-box { max-width: 460px !important; padding: 0 0 !important; }
|
||||
|
||||
.nd-card-mac.btn-fw-mac {
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
.nd-card-mac.btn-fw-mac:hover {
|
||||
background: #ede9fe !important;
|
||||
color: #7c3aed !important;
|
||||
border-color: #c4b5fd !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 2px 6px rgba(124, 58, 237, 0.15) !important;
|
||||
}
|
||||
.nd-card-mac.btn-fw-mac:active {
|
||||
transform: scale(0.95) !important;
|
||||
}
|
||||
|
||||
.nd-card-warning { border: 2px solid #ff4d4f !important; background-color: #fff1f0 !important; box-shadow: 0 4px 12px rgba(255, 77, 79, 0.2) !important; margin-top: 15px !important; }
|
||||
@keyframes blink-warning { 0% { opacity: 1; } 50% { opacity: 0.1; } 100% { opacity: 1; }
|
||||
|
||||
}
|
||||
|
||||
/* 呼吸灯闪烁动画 */
|
||||
@keyframes blink-warning {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.nd-conn-badge { position: relative; }
|
||||
.nd-conn-tooltip { visibility: hidden; opacity: 0; position: absolute; bottom: 135%; left: 50%; transform: translateX(-50%); background: #0f172a; color: #f8fafc; padding: 12px; border-radius: 12px; width: 170px; z-index: 999; box-shadow: 0 10px 25px rgba(0,0,0,0.5); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; text-align: left; }
|
||||
.nd-conn-tooltip::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -6px; border-width: 6px; border-style: solid; border-color: #0f172a transparent transparent transparent; }
|
||||
.nd-conn-badge:hover .nd-conn-tooltip { visibility: visible; opacity: 1; bottom: 125%; }
|
||||
.nd-pie-chart { width: 75px; height: 75px; border-radius: 50%; margin: 0 auto 12px auto; box-shadow: inset 0 0 10px rgba(0,0,0,0.3); }
|
||||
.nd-legend-item { display: flex; justify-content: space-between; font-size: 11.5px; margin-bottom: 5px; line-height: 1; align-items: center; }
|
||||
.nd-legend-color { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 6px; }
|
||||
|
||||
|
||||
/* 动态弹窗专用样式 (由 JS 呼叫) */
|
||||
/* 红色严重警告主文字 */
|
||||
.nw-alert-danger {
|
||||
font-size: 14.5px;
|
||||
color: #ef4444;
|
||||
font-weight: bold;
|
||||
line-height: 1.6;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* 黄色依赖警告主文字 */
|
||||
.nw-alert-warning {
|
||||
font-size: 14.5px;
|
||||
color: #f59e0b;
|
||||
font-weight: bold;
|
||||
line-height: 1.6;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* 警告弹窗列表(带有关闭/开启图标的那行)*/
|
||||
.nw-alert-detail-item {
|
||||
padding: 6px 0;
|
||||
color: #0f172a;
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* =========================================================
|
||||
12. 手机端响应式适配
|
||||
========================================================= */
|
||||
@media screen and (max-width: 768px) {
|
||||
#maincontent, .main, .main-right { padding-bottom: 40px !important; height: auto !important; overflow: visible !important; }
|
||||
#netwiz-container, #netwiz-dev-container { padding-bottom: 40px !important; }
|
||||
.nw-current-mode-display { padding-bottom: 10px !important; margin-bottom: 10px !important; height: auto !important; min-height: max-content !important; }
|
||||
.nw-step, .nw-form-area { height: auto !important; overflow: visible !important; }
|
||||
.nd-cb-title {display: none; font-size: 14px; margin: 0; }
|
||||
.nd-cb-title-wrap { margin: 0; }
|
||||
.nw-reopen-btn { margin-bottom: 0; }
|
||||
|
||||
.main, .container, #maincontent { padding: 0 !important; margin: 0 !important; width: 100% !important; max-width: 100% !important; }
|
||||
|
||||
.nw-wrapper { padding: 0 0 5px 0 !important; width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; }
|
||||
|
||||
.nw-header, .nw-step, .nd-control-bar, .nd-list, .nd-list-header, .nd-category-tabs {
|
||||
width: 100% !important;
|
||||
max-width: 96% !important; /* 左右留一点空间 */
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.nw-form-area, .nw-confirm-board, .nw-current-mode-display {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.nw-header { top: -5px !important; border-radius: 14px !important; padding: 10px 0 10px 0 !important; margin-top: 0 !important; max-width: 100% !important; }
|
||||
.nw-main-title { font-size: 24px !important; letter-spacing: 0 !important; }
|
||||
.nw-header p { font-size: 14px !important; letter-spacing: 0 !important; margin-bottom: 0 !important;}
|
||||
|
||||
.nw-card-group { display: block !important; width: 100% !important; padding: 0 !important; margin-top: -10px !important; margin-bottom: 0 !important; position: relative !important; z-index: 10 !important; }
|
||||
.nw-card {
|
||||
width: 100% !important; max-width: 100% !important;
|
||||
margin: 0 0 15px 0 !important;
|
||||
padding: 24px 15px !important;
|
||||
height: auto !important; min-height: 140px !important;
|
||||
aspect-ratio: auto !important;
|
||||
display: flex !important; flex-direction: column !important; align-items: center !important; justify-content: center !important;
|
||||
}
|
||||
|
||||
.nw-actions button[style*="none"], .nw-actions button.hidden, .nw-actions button:empty { display: none !important; flex: none !important; min-width: 0 !important; width: 0 !important; padding: 0 !important; margin: 0 !important; border: none !important; }
|
||||
.nw-actions { display: flex !important; flex-direction: row !important; justify-content: center !important; gap: 15px !important; margin-top: 15px !important; padding-bottom: 25px !important; }
|
||||
.nw-actions button, .nw-actions .nw-u-btn { flex: 0 1 auto !important; min-width: 140px !important; padding: 10px 20px !important; margin: 0 !important; font-size: 14px !important; text-align: center !important; justify-content: center !important; }
|
||||
|
||||
/* ======== 设备卡片 ======== */
|
||||
.nd-control-bar { position: sticky !important; top: 0 !important; z-index: 750 !important; padding: 8px !important; border-radius: 12px !important; flex-wrap: wrap !important; gap: 3px !important; margin: -5px auto 0 auto !important; max-width: calc(100% - 12px) !important; }
|
||||
.nd-cb-sub { display: none; }
|
||||
|
||||
.nd-list { gap: 0 !important; }
|
||||
.nd-card {
|
||||
width: 100% !important; max-width: 100% !important;
|
||||
margin: 0 0 5px 0 !important;
|
||||
flex-direction: column !important; align-items: flex-start !important;
|
||||
padding: 6px 10px !important; gap: 8px !important; box-sizing: border-box !important;
|
||||
}
|
||||
.nd-card-name svg, .nd-card-name i, .nd-card-name img { display: none !important; }
|
||||
.nd-card-left { display: flex !important; flex-direction: row !important; flex-wrap: wrap !important; align-items: center !important; width: 100% !important; gap: 6px 8px !important; }
|
||||
.nd-card-left > div:first-child, .nd-card-name { display: contents !important; }
|
||||
.nd-card-checkbox { margin: 0 !important; }
|
||||
.nd-card-left > div:first-child > div[style*="33px"] { margin-right: 0 !important; }
|
||||
.nd-card-name span:first-of-type { font-size: 15px !important; font-weight: 600 !important; color: #334155 !important; word-break: break-all !important; margin: 0 !important; line-height: 1.2 !important; }
|
||||
.nd-status-badge, .nd-badge, .nd-card-mac { display: inline-flex !important; white-space: nowrap !important; flex-shrink: 0 !important; margin: 0 !important; align-items: center !important; }
|
||||
.nd-card-mac { font-size: 12px !important; color: #94a3b8 !important; background: transparent !important; border: none !important; padding: 3px 0 0 0 !important; font-family: monospace !important; letter-spacing: 0px !important; }
|
||||
|
||||
.nd-card-mid { display: flex !important; flex-direction: row !important; align-items: center !important; justify-content: space-between !important; width: 100% !important; max-width: 100% !important; background: #f8fafc !important; padding: 4px 6px !important; border-radius: 8px !important; box-sizing: border-box !important; margin: 2px 0 !important; border: none !important; }
|
||||
.nd-lease-info { order: 1 !important; font-size: 11.5px !important; color: #64748b !important; white-space: nowrap !important; margin: 0 !important; background: transparent !important; padding: 0 !important; }
|
||||
.nd-card-ip { order: 2 !important; font-size: 16px !important; font-weight: 800 !important; color: #3b82f6 !important; white-space: nowrap !important; font-family: monospace !important; margin: 0 !important; text-align: right !important; white-space: normal !important; word-break: break-word !important; line-height: 1.3 !important;}
|
||||
|
||||
.nd-card-right { width: 100% !important; margin-top: 4px !important; }
|
||||
.nd-btn { flex: 1 !important; padding: 9px 0 !important; font-size: 14px !important; }
|
||||
|
||||
.nw-top-back { top: 12px; left: 12px; width: 45px; height: 45px; }
|
||||
.nw-radio-group { flex-wrap: wrap; gap: 10px; }
|
||||
.nw-wiz-header-responsive { flex-wrap: wrap; padding: 12px 15px !important; }
|
||||
.nw-wiz-title-responsive { flex: 1; text-align: center; font-size: 16px; order: 1; }
|
||||
.nw-wiz-close-wrap { flex: 0 0 auto; order: 2; }
|
||||
.nw-wiz-step-wrap { flex: 0 0 100%; justify-content: center; margin-top: 5px; order: 3; }
|
||||
.nw-info-item { display: block; margin: 6px 0; text-align: center; max-width: 100%; min-width: 0; }
|
||||
.nw-wifi-line { flex-direction: column; gap: 8px; margin-bottom: 6px; padding-bottom: 6px; border-bottom: 1px dashed rgba(255,255,255,0.2); max-width: 100%; min-width: 0; }
|
||||
.nw-wifi-line:last-child { border-bottom: none; margin-bottom: 0; padding: 0 0 5px 0; }
|
||||
.nw-wifi-left { flex-direction: column; gap: 6px; max-width: 100%; min-width: 0; }
|
||||
.nw-wifi-colon { display: none; }
|
||||
.nw-hl { margin-left: 0 !important; max-width: 100%; min-width: 0; display: inline-block; word-break: break-all; white-space: normal; }
|
||||
.nw-wifi-name { flex-direction: column; gap: 4px; max-width: 100%; min-width: 0; }
|
||||
.nw-wifi-name span[title] { margin-left: 0 !important; }
|
||||
|
||||
.nd-batch-bar.show { bottom: 25px !important; padding: 12px 15px !important; border-radius: 16px !important; flex-direction: column !important; gap: 12px !important; }
|
||||
.nd-batch-info { width: 100% !important; text-align: center !important; }
|
||||
.nd-batch-actions { width: 100% !important; justify-content: space-between !important; display: flex !important; gap: 10px !important; }
|
||||
.nd-batch-actions button { flex: 1 !important; }
|
||||
|
||||
.nd-smart-row { padding: 10px 4px !important; }
|
||||
.nd-smart-label { font-size: 13px !important; }
|
||||
.nd-input-sm { width: 60px !important; flex: 0 0 60px !important; font-size: 13.5px !important; padding: 6px 0 !important; }
|
||||
|
||||
#nd-m-form { width: 100% !important; max-width: 100% !important; overflow-x: hidden !important; box-sizing: border-box !important; }
|
||||
.nd-modal-box { width: 100% !important; max-width: calc(100% - 20px) !important; box-sizing: border-box !important; margin: 0 auto !important; }
|
||||
.nw-form-area { padding: 30px 10px !important; }
|
||||
#nd-modal-overlay .nd-modal-box { max-width: calc(100% - 20px) !important; padding: 0 !important; font-size: 15px}
|
||||
.nw-switch span{font-size: 12px}
|
||||
/* .nw-switch span{color: transparent !important; font-size: 0 !important; } */
|
||||
.nd-modal-box .nd-modal-title { font-size: 18px !important; }
|
||||
.nd-cb-refresh {padding: 6px 5px;}
|
||||
.nw-wiz-modal-title { font-size: 22px; margin: 0; padding: 0 0 0 22px; }
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1
@@ -0,0 +1 @@
|
||||
var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,cLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,5 @@
|
||||
config global 'global'
|
||||
option configured '0'
|
||||
|
||||
config main 'main'
|
||||
option wizard_enable '1'
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
# 作用: Netwiz 设备防伪装 (防止修改 MAC 绕过断网功能)
|
||||
|
||||
# 1. 获取 IP 时触发
|
||||
[ "$ACTION" = "add" ] || [ "$ACTION" = "update" ] || exit 0
|
||||
[ -z "$MACADDR" ] || [ -z "$HOSTNAME" ] || [ "$HOSTNAME" = "*" ] && exit 0
|
||||
|
||||
MAC_SAFE=$(echo "$MACADDR" | tr -d ':' | tr 'a-z' 'A-Z')
|
||||
|
||||
# 2. 检查新 MAC 是否已被拉黑
|
||||
ALREADY_BLOCKED=$(uci -q show firewall | grep -E "\.name=.*nw_blk_${MAC_SAFE}")
|
||||
[ -n "$ALREADY_BLOCKED" ] && exit 0
|
||||
|
||||
# 3.DHCP 租约查找所有与该设备“同名”的历史 MAC 地址
|
||||
KNOWN_MACS=$(awk -v hn="$HOSTNAME" '$4 == hn {print $2}' /tmp/dhcp.leases 2>/dev/null)
|
||||
|
||||
for k_mac in $KNOWN_MACS; do
|
||||
K_MAC_SAFE=$(echo "$k_mac" | tr -d ':' | tr 'a-z' 'A-Z')
|
||||
|
||||
# 4. 检查以前用过的 MAC 是不是在断网黑名单里
|
||||
IS_BLOCKED=$(uci -q show firewall | grep -E "\.name=.*nw_blk_${K_MAC_SAFE}")
|
||||
|
||||
if [ -n "$IS_BLOCKED" ]; then
|
||||
logger -t Netwiz "🚨 Anti-Spoofing! Device [$HOSTNAME] changed MAC to $MACADDR. Auto-blocking..."
|
||||
|
||||
# 5. 发现伪装!自动将它的新 MAC 追加到防火墙断网规则中
|
||||
uci -q add firewall rule >/dev/null
|
||||
uci -q set firewall.@rule[-1].name="nw_blk_${MAC_SAFE}"
|
||||
uci -q set firewall.@rule[-1].src="lan"
|
||||
uci -q set firewall.@rule[-1].dest="wan"
|
||||
uci -q set firewall.@rule[-1].src_mac="$MACADDR"
|
||||
uci -q set firewall.@rule[-1].family="any"
|
||||
uci -q set firewall.@rule[-1].target="DROP"
|
||||
uci -q set firewall.@rule[-1].enabled="1"
|
||||
|
||||
uci commit firewall
|
||||
|
||||
# 6. 踢下线并重载防火墙
|
||||
if command -v iptables >/dev/null 2>&1; then iptables -I FORWARD 1 -m mac --mac-source $MACADDR -j DROP >/dev/null 2>&1; fi
|
||||
if command -v nft >/dev/null 2>&1; then nft insert rule inet fw4 forward ether saddr $MACADDR drop >/dev/null 2>&1; fi
|
||||
/etc/init.d/firewall reload </dev/null >/dev/null 2>&1 &
|
||||
break
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
LOG_FILE="/tmp/netwiz.log"
|
||||
|
||||
log() {
|
||||
echo "$(date '+%F %T') [Service] $1" >> "$LOG_FILE"
|
||||
if [ $(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) -gt 600 ]; then
|
||||
tail -n 500 "$LOG_FILE" > "$LOG_FILE.tmp"
|
||||
mv "$LOG_FILE.tmp" "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
log "Netwiz 后台守护进程已启动"
|
||||
|
||||
# 获取向导配置状态
|
||||
local configured=$(uci -q get netwiz.global.configured)
|
||||
|
||||
# 配置为 0,触发拦截
|
||||
if [ "$configured" != "1" ]; then
|
||||
log "触发初次向导配置,生成拦截 CGI 脚本"
|
||||
|
||||
local is_lang_init=$(uci -q get netwiz.global.lang_init)
|
||||
if [ "$is_lang_init" != "1" ]; then
|
||||
uci -q set luci.main.lang="auto"
|
||||
uci commit luci
|
||||
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/* 2>/dev/null
|
||||
/etc/init.d/rpcd restart 2>/dev/null
|
||||
/etc/init.d/uhttpd restart 2>/dev/null
|
||||
uci -q set netwiz.global.lang_init='1'
|
||||
uci commit netwiz
|
||||
fi
|
||||
|
||||
[ -f /www/cgi-bin/luci.bak ] || cp -p /www/cgi-bin/luci /www/cgi-bin/luci.bak
|
||||
|
||||
cat << 'EOF' > /www/cgi-bin/luci
|
||||
#!/bin/sh
|
||||
CONF=$(uci -q get netwiz.global.configured)
|
||||
|
||||
if [ "$CONF" != "1" ]; then
|
||||
case "$PATH_INFO" in
|
||||
*rescue*)
|
||||
uci -q set netwiz.global.configured='1'
|
||||
uci commit netwiz
|
||||
[ -f /www/cgi-bin/luci.bak ] && mv -f /www/cgi-bin/luci.bak /www/cgi-bin/luci
|
||||
printf "Status: 302 Found\r\n"
|
||||
printf "Location: /cgi-bin/luci/\r\n"
|
||||
printf "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n\r\n"
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*admin/netwiz*|*admin/translations*|*admin/menu*|*admin/ubus*|*luci-static*|*rpc*)
|
||||
export HTTP_ACCEPT_LANGUAGE="zh-CN,zh;q=0.9"
|
||||
exec /www/cgi-bin/luci.bak
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
# 浏览器带登录凭证,弹出向导
|
||||
if echo "$HTTP_COOKIE" | grep -q "sysauth="; then
|
||||
printf "Status: 302 Found\r\n"
|
||||
printf "Location: /cgi-bin/luci/admin/netwiz\r\n"
|
||||
printf "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n\r\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 网址带官方重定向的 url= 弹出向导
|
||||
if echo "$QUERY_STRING" | grep -q "url="; then
|
||||
printf "Status: 302 Found\r\n"
|
||||
printf "Location: /cgi-bin/luci/admin/netwiz\r\n"
|
||||
printf "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n\r\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IS_EMPTY_PWD=$(awk -F: '/^root:/ {print $2}' /etc/shadow)
|
||||
if [ -z "$IS_EMPTY_PWD" ] || [ "$IS_EMPTY_PWD" = "!" ] || [ "$IS_EMPTY_PWD" = "*" ] || [ "$IS_EMPTY_PWD" = "x" ]; then
|
||||
|
||||
printf "Status: 200 OK\r\n"
|
||||
printf "Content-Type: text/html; charset=utf-8\r\n"
|
||||
printf "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n\r\n"
|
||||
printf "<!DOCTYPE html><html><head><script>\n"
|
||||
|
||||
printf "var nav = (navigator.language || navigator.userLanguage || '').toLowerCase();\n"
|
||||
printf "var l = 'en';\n"
|
||||
printf "if(nav.indexOf('zh') > -1) { l = (nav.indexOf('tw') > -1 || nav.indexOf('hk') > -1) ? 'zh-tw' : 'zh-cn'; }\n"
|
||||
printf "else if(nav.indexOf('ru') > -1) { l = 'ru'; }\n"
|
||||
|
||||
printf "try { localStorage.setItem('lang', l); } catch(e) {}\n"
|
||||
printf "document.cookie = 'luci_lang=' + l + '; path=/; max-age=86400';\n"
|
||||
|
||||
printf "</script></head><body style='background:#f8fafc;'>"
|
||||
printf "<form id='al' action='/cgi-bin/luci/admin/netwiz' method='post'>"
|
||||
printf "<input type='hidden' name='luci_username' value='root'>"
|
||||
printf "<input type='hidden' name='luci_password' value=''>"
|
||||
printf "<input type='hidden' id='frmLang' name='luci_lang' value=''>"
|
||||
printf "</form>"
|
||||
|
||||
printf "<script>\n"
|
||||
printf "document.getElementById('frmLang').value = l;\n"
|
||||
printf "document.getElementById('al').submit();\n"
|
||||
printf "</script></body></html>\n"
|
||||
exit 0
|
||||
|
||||
else
|
||||
# 固件自带默认密码,官方登录
|
||||
printf "Status: 302 Found\r\n"
|
||||
printf "Location: /cgi-bin/luci/?url=/cgi-bin/luci/admin/netwiz\r\n"
|
||||
printf "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n\r\n"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
exec /www/cgi-bin/luci.bak
|
||||
EOF
|
||||
|
||||
# 赋予执行权限
|
||||
chmod +x /www/cgi-bin/luci
|
||||
else
|
||||
# 已配置过,恢复官方网关
|
||||
if [ -f /www/cgi-bin/luci.bak ]; then
|
||||
log "向导已完成,还原官方网关"
|
||||
mv /www/cgi-bin/luci.bak /www/cgi-bin/luci
|
||||
fi
|
||||
fi
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command /usr/libexec/netwiz-monitor-loop.sh
|
||||
procd_set_param respawn
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
log "Netwiz 后台守护进程已停止"
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "netwiz"
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
restart
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
# START=15 确保在 network 服务前执行
|
||||
START=15
|
||||
|
||||
RECOVERY_LOG="/etc/netwiz_recovery.log"
|
||||
AUTODETECT_SCRIPT="/usr/libexec/netwiz-autodetect.sh"
|
||||
|
||||
boot() {
|
||||
# 处理由于 LAN 口 / 全局设置向导导致的异常断电
|
||||
if [ -f /etc/config/network.netwiz_bak ]; then
|
||||
logger -t Netwiz "Power loss detected during LAN/Global setup! Forcefully recovering..."
|
||||
|
||||
echo "=========================================================" >> "$RECOVERY_LOG"
|
||||
echo "⚠️ [BOOT-RECOVERY] $(date '+%F %T') 检测到 LAN/全局 配置异常断电,正从闪存强制回退..." >> "$RECOVERY_LOG"
|
||||
|
||||
cat /etc/config/network.netwiz_bak > /etc/config/network
|
||||
[ -f /etc/config/dhcp.netwiz_bak ] && cat /etc/config/dhcp.netwiz_bak > /etc/config/dhcp
|
||||
|
||||
rm -f /etc/config/network.netwiz_bak /etc/config/dhcp.netwiz_bak
|
||||
rm -f /tmp/netwiz_rollback_time /tmp/netwiz_target_ip
|
||||
|
||||
echo "✅ [BOOT-RECOVERY] LAN/全局 异常回退完成!" >> "$RECOVERY_LOG"
|
||||
echo "=========================================================" >> "$RECOVERY_LOG"
|
||||
fi
|
||||
|
||||
# 处理由于 WAN 口自动探测导致的异常断电
|
||||
if [ -f /etc/config/network.netwiz_wan_bak ]; then
|
||||
logger -t Netwiz "Power loss detected during WAN autodetect! Forcefully recovering..."
|
||||
|
||||
echo "=========================================================" >> "$RECOVERY_LOG"
|
||||
echo "⚠️ [BOOT-RECOVERY] $(date '+%F %T') 检测到 WAN 探测过程异常断电,强制回退..." >> "$RECOVERY_LOG"
|
||||
|
||||
cat /etc/config/network.netwiz_wan_bak > /etc/config/network
|
||||
rm -f /etc/config/network.netwiz_wan_bak
|
||||
|
||||
echo "✅ [BOOT-RECOVERY] WAN 探测回退完成!" >> "$RECOVERY_LOG"
|
||||
|
||||
# 只有因为 WAN 探测断电,才需要在后台重新拉起探测探针
|
||||
if [ -x "$AUTODETECT_SCRIPT" ]; then
|
||||
echo "🔄 [BOOT-RECOVERY] 已埋设后台探针,等待网卡就绪后触发检测..." >> "$RECOVERY_LOG"
|
||||
# 使用子 shell 放入后台执行,完全不阻塞系统的后续启动
|
||||
(
|
||||
WAN_DEV=$(uci -q get network.wan.device || uci -q get network.wan.device || echo "eth0")
|
||||
# 轮询等待最多 60 秒 (30 次 * 2 秒)
|
||||
for i in $(seq 1 30); do
|
||||
if ubus call network.device status "{\"name\":\"$WAN_DEV\"}" 2>/dev/null | grep -q '"carrier": true'; then
|
||||
sleep 3 # 给内核几秒钟梳理路由表
|
||||
"$AUTODETECT_SCRIPT"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
) &
|
||||
fi
|
||||
echo "=========================================================" >> "$RECOVERY_LOG"
|
||||
fi
|
||||
|
||||
# ======================================================================
|
||||
# 备份恢复后重启 (300 秒跨重启看门狗)
|
||||
# 如果在 300 秒内前端没有发送 confirm 确认存活,则判定为失联变砖并强制回滚
|
||||
# ======================================================================
|
||||
if [ -f "/etc/netwiz_pending_recovery" ]; then
|
||||
logger -t Netwiz "[RECOVERY] ⚠️ 检测到未清除的恢复备份防砖标记,启动 300 秒幽灵看门狗..."
|
||||
|
||||
# 剥离子进程进入后台倒数,绝不阻塞路由器的正常开机流程
|
||||
(
|
||||
sleep 300
|
||||
|
||||
# 300秒后苏醒,如果标记还在,说明前端没发来 confirm
|
||||
if [ -f "/etc/netwiz_pending_recovery" ]; then
|
||||
|
||||
# ====================================================
|
||||
# 判断是否为“假失联”
|
||||
# ====================================================
|
||||
# 检测 1: LAN 接口是否在底层总线 (ubus) 真正 up
|
||||
LAN_UBUS=$(ubus call network.interface.lan status 2>/dev/null | grep '"up": true')
|
||||
|
||||
# 检测 2: Web 网页服务器 (uhttpd) 必须存活!如果用户打不开网页,等于半砖
|
||||
HTTPD_ALIVE=$(pgrep uhttpd)
|
||||
|
||||
# 检测 3: 核心 DHCP 守护进程必须存活 (除非用户处于 AP 模式,豁免此项)
|
||||
IS_AP=$(uci -q get dhcp.lan.ignore)
|
||||
if [ "$IS_AP" = "1" ]; then
|
||||
DNS_ALIVE="AP_MODE_SKIP" # 旁路由模式,豁免检查
|
||||
else
|
||||
DNS_ALIVE=$(pgrep dnsmasq)
|
||||
fi
|
||||
|
||||
# 三大核心系统必须全部存活,允许静默拆弹
|
||||
if [ -n "$LAN_UBUS" ] && [ -n "$HTTPD_ALIVE" ] && [ -n "$DNS_ALIVE" ]; then
|
||||
logger -t Netwiz "[RECOVERY] 🛡️ 300秒超时,底层网络、Web与DNS服务完全健康!判定为假阳性。"
|
||||
logger -t Netwiz "[RECOVERY] 终止回滚动作,仅静默拆除炸弹。"
|
||||
echo "$(date '+%F %T') [RECOVERY] 🛡️ 核心网络自检通过 (无论有无设备连接),取消回滚,静默拆弹。" >> "$RECOVERY_LOG"
|
||||
|
||||
rm -f /etc/netwiz_pending_recovery
|
||||
rm -rf /etc/netwiz/failsafe
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 如果上面三大核心有任何一个挂了 (比如 uhttpd 崩溃进不去网页),才执行硬回滚!
|
||||
logger -t Netwiz "[RECOVERY] 🚨 300秒超时且核心网络自检未完全通过!判断为恢复后变砖,启动硬回滚!"
|
||||
|
||||
# 备用配置覆盖当前损坏的配置
|
||||
cp -p /etc/netwiz/failsafe/network_bak /etc/config/network 2>/dev/null
|
||||
cp -p /etc/netwiz/failsafe/dhcp_bak /etc/config/dhcp 2>/dev/null
|
||||
cp -p /etc/netwiz/failsafe/wireless_bak /etc/config/wireless 2>/dev/null
|
||||
|
||||
# 删除,防止无限循环回滚
|
||||
rm -f /etc/netwiz_pending_recovery
|
||||
rm -rf /etc/netwiz/failsafe
|
||||
|
||||
logger -t Netwiz "[RECOVERY] 回滚覆盖完成,正在强制重启系统以自救..."
|
||||
# 底层架构可能被错误插件破坏,最安全的自救方式是再次整机重启
|
||||
sleep 3
|
||||
reboot
|
||||
fi
|
||||
) </dev/null >/dev/null 2>&1 &
|
||||
fi
|
||||
# ======================================================================
|
||||
|
||||
# 日志体积控制,保留最后 100 行即可
|
||||
if [ $(wc -l < "$RECOVERY_LOG" 2>/dev/null || echo 0) -gt 150 ]; then
|
||||
tail -n 100 "$RECOVERY_LOG" > "$RECOVERY_LOG.tmp"
|
||||
mv "$RECOVERY_LOG.tmp" "$RECOVERY_LOG"
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
# 描述: Netwiz IPv6 背景守护进程
|
||||
|
||||
START=99
|
||||
USE_PROCD=1
|
||||
|
||||
start_service() {
|
||||
# 1. 检查前端是否开启了开关,没开就不启动进程,0內存损耗
|
||||
local enable=$(uci -q get netwiz.main.watchdog_enable)
|
||||
[ "$enable" != "1" ] && return 0
|
||||
|
||||
# 2. 注册背景守护进程(交由procd监控)
|
||||
procd_open_instance
|
||||
# 指向我们唯一的后端脚本,并传入daemon参数
|
||||
procd_set_param command /usr/libexec/rpcd/netwiz daemon
|
||||
# 开启崩溃自动重启机制(Respawn)
|
||||
procd_set_param respawn
|
||||
# 捕获脚本的标准输出到系统日志
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
# 连动:在前端修改netwiz界面并储存时,procd会自动重新启动此进程套用新网址
|
||||
procd_add_reload_trigger "netwiz"
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/bin/sh
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
|
||||
LOG_FILE="/tmp/netwiz.log"
|
||||
|
||||
log() {
|
||||
echo "$(date '+%F %T') [Engine] $1" >> "$LOG_FILE"
|
||||
if [ $(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) -gt 600 ]; then
|
||||
tail -n 500 "$LOG_FILE" > "$LOG_FILE.tmp"
|
||||
mv "$LOG_FILE.tmp" "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# === 智能推算子网掩码 ===
|
||||
calc_netmask() {
|
||||
local ip="$1"
|
||||
local b="${ip%%.*}"
|
||||
case "$b" in ''|*[!0-9]*) echo "255.255.255.0"; return ;; esac
|
||||
|
||||
if [ "$b" -ge 1 ] && [ "$b" -le 126 ]; then
|
||||
echo "255.0.0.0"
|
||||
elif [ "$b" -ge 128 ] && [ "$b" -le 191 ]; then
|
||||
echo "255.255.0.0"
|
||||
else
|
||||
echo "255.255.255.0"
|
||||
fi
|
||||
}
|
||||
|
||||
LOCK_FILE="/var/run/netwiz_autodetect.lock"
|
||||
# WAN 备份名,与 LAN 向导的备份隔离
|
||||
BAK_FILE="/etc/config/network.netwiz_wan_bak"
|
||||
|
||||
# ================= 退出与异常清理机制 =================
|
||||
cleanup() {
|
||||
rm -f "$LOCK_FILE"
|
||||
# 如果脚本退出时,备份文件依然存在,说明脚本被意外中止
|
||||
if [ -f "$BAK_FILE" ]; then
|
||||
log "⚠️ 检测到脚本被外部异常中断,触发安全清理,正在恢复原配置..."
|
||||
cp "$BAK_FILE" /etc/config/network
|
||||
rm -f "$BAK_FILE"
|
||||
/etc/init.d/network reload &
|
||||
fi
|
||||
}
|
||||
# 捕获 EXIT(正常或报错退出), INT(Ctrl+C), TERM(系统kill)
|
||||
trap cleanup EXIT INT TERM
|
||||
# ==========================================================
|
||||
|
||||
# 防止 kill -9 (强制抹杀,不触发 trap) 导致的残酷遗留
|
||||
if [ -f "$BAK_FILE" ] && [ ! -f "$LOCK_FILE" ]; then
|
||||
log "警告:发现无主遗留备份文件,先恢复配置..."
|
||||
cp "$BAK_FILE" /etc/config/network
|
||||
rm -f "$BAK_FILE"
|
||||
fi
|
||||
|
||||
if [ -f "$LOCK_FILE" ]; then exit 0; fi
|
||||
touch "$LOCK_FILE"
|
||||
|
||||
WAN_DEV=$(uci -q get network.wan.device)
|
||||
[ -z "$WAN_DEV" ] && WAN_DEV=$(uci -q get network.wan.device)
|
||||
[ -z "$WAN_DEV" ] && WAN_DEV="eth0"
|
||||
|
||||
wait_for_internet() {
|
||||
local max_wait=20
|
||||
local i=0
|
||||
local offline_strikes=0
|
||||
|
||||
while [ $i -lt $max_wait ]; do
|
||||
if ! ubus call network.device status "{\"name\":\"$WAN_DEV\"}" 2>/dev/null | grep -q '"carrier": true'; then
|
||||
offline_strikes=$((offline_strikes+1))
|
||||
else
|
||||
offline_strikes=0
|
||||
fi
|
||||
|
||||
if [ $offline_strikes -ge 3 ]; then
|
||||
log "检测到网线已物理拔出,终止探测"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 多目标 Ping,只要一个通就算有网
|
||||
if ping -c 1 -W 1 223.5.5.5 >/dev/null 2>&1 || \
|
||||
ping -c 1 -W 1 114.114.114.114 >/dev/null 2>&1 || \
|
||||
ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
i=$((i+1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
log "检测到 WAN 口网线插入,启动自动探测引擎..."
|
||||
sleep 5
|
||||
if wait_for_internet; then
|
||||
log "当前配置网络正常,无需切换协议"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "当前配置无法连通互联网,准备进行协议切换探测"
|
||||
cp /etc/config/network "$BAK_FILE"
|
||||
sync
|
||||
|
||||
ORIG_PROTO=$(uci -q get network.wan.proto)
|
||||
HAS_PPPOE_USER=$(uci -q get network.wan.username)
|
||||
SAVED_STATIC_IP=$(uci -q get network.wan.ipaddr)
|
||||
SAVED_STATIC_GW=$(uci -q get network.wan.gateway)
|
||||
SAVED_STATIC_MASK=$(uci -q get network.wan.netmask)
|
||||
success=0
|
||||
|
||||
# 1、切换DHCP
|
||||
if [ "$ORIG_PROTO" != "dhcp" ]; then
|
||||
log "正在尝试通过 DHCP 自动获取 IP 地址..."
|
||||
uci set network.wan.proto='dhcp'
|
||||
uci commit network
|
||||
/etc/init.d/network reload
|
||||
if wait_for_internet; then success=1; fi
|
||||
fi
|
||||
|
||||
test_static_ip() {
|
||||
local try_mask="$1"
|
||||
log "正在探测静态 IP: $SAVED_STATIC_IP,网关: $SAVED_STATIC_GW,掩码: $try_mask"
|
||||
cp "$BAK_FILE" /etc/config/network
|
||||
uci set network.wan.proto='static'
|
||||
uci set network.wan.ipaddr="$SAVED_STATIC_IP"
|
||||
uci set network.wan.gateway="$SAVED_STATIC_GW"
|
||||
uci set network.wan.netmask="$try_mask"
|
||||
uci commit network
|
||||
/etc/init.d/network reload
|
||||
if wait_for_internet; then return 0; else return 1; fi
|
||||
}
|
||||
|
||||
# 2、切换静态 IP
|
||||
if [ "$success" -eq 0 ] && [ "$ORIG_PROTO" != "static" ] && [ -n "$SAVED_STATIC_IP" ] && [ -n "$SAVED_STATIC_GW" ]; then
|
||||
CALC_MASK=$(calc_netmask "$SAVED_STATIC_IP")
|
||||
|
||||
if [ -n "$SAVED_STATIC_MASK" ]; then
|
||||
if test_static_ip "$SAVED_STATIC_MASK"; then
|
||||
success=1
|
||||
else
|
||||
if [ "$SAVED_STATIC_MASK" != "$CALC_MASK" ]; then
|
||||
log "原掩码无响应,尝试切换为推算掩码再试..."
|
||||
if test_static_ip "$CALC_MASK"; then success=1; fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if test_static_ip "$CALC_MASK"; then success=1; fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3、切换 PPPoE
|
||||
if [ "$success" -eq 0 ] && [ "$ORIG_PROTO" != "pppoe" ] && [ -n "$HAS_PPPOE_USER" ]; then
|
||||
log "前置探测均失败,正在探测 PPPoE 服务器..."
|
||||
cp "$BAK_FILE" /etc/config/network
|
||||
uci set network.wan.proto='pppoe'
|
||||
uci commit network
|
||||
/etc/init.d/network reload
|
||||
if wait_for_internet; then success=1; fi
|
||||
fi
|
||||
|
||||
# === 最终 ===
|
||||
if [ "$success" -eq 1 ]; then
|
||||
log "新协议探测连通成功,已保存配置"
|
||||
rm -f "$BAK_FILE"
|
||||
else
|
||||
log "未检测到有效的网络协议,正在回退到原始配置"
|
||||
cp "$BAK_FILE" /etc/config/network
|
||||
rm -f "$BAK_FILE"
|
||||
/etc/init.d/network reload
|
||||
fi
|
||||
|
||||
# 正常退出时,清理变量已消除,Trap 将只执行清理 LOCK_FILE
|
||||
exit 0
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/bin/sh
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
|
||||
# 日志统一存放在 /tmp/ 下,避免损耗路由器闪存
|
||||
LOG_FILE="/tmp/netwiz.log"
|
||||
LOCK_FILE="/var/run/netwiz_autodetect.lock"
|
||||
|
||||
# 定义最大保留500行
|
||||
MAX_LINES=500
|
||||
|
||||
log() {
|
||||
# 写入新日志
|
||||
echo "$(date '+%F %T') [Monitor] $1" >> "$LOG_FILE"
|
||||
|
||||
# 自动删除
|
||||
# 日志超过 600 行时,自动删除,只保留最新的 500 行
|
||||
if [ $(wc -l < "$LOG_FILE" 2>/dev/null || echo 0) -gt 600 ]; then
|
||||
tail -n $MAX_LINES "$LOG_FILE" > "$LOG_FILE.tmp"
|
||||
mv "$LOG_FILE.tmp" "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取当前的 WAN 网卡名称
|
||||
WAN_DEV=$(uci -q get network.wan.device)
|
||||
[ -z "$WAN_DEV" ] && WAN_DEV=$(uci -q get network.wan.device)
|
||||
[ -z "$WAN_DEV" ] && WAN_DEV="eth0"
|
||||
|
||||
# 检查 WAN 口物理连接状态
|
||||
check_wan_link() {
|
||||
if ubus call network.device status "{\"name\":\"$WAN_DEV\"}" 2>/dev/null | grep -q '"carrier": true'; then
|
||||
echo "up"
|
||||
else
|
||||
echo "down"
|
||||
fi
|
||||
}
|
||||
|
||||
LAST_WAN_STATE=$(check_wan_link)
|
||||
# 记录连续断开的次数,用于防抖
|
||||
DOWN_COUNT=0
|
||||
|
||||
log "服务已启动,正在监控 WAN 插拔和 LAN 回退定时器"
|
||||
|
||||
while true; do
|
||||
# --- 1. WAN 接口插拔监控 (带防抖逻辑) ---
|
||||
CURRENT_WAN_STATE=$(check_wan_link)
|
||||
|
||||
# 如果自动探测引擎没有在运行,才执行监控
|
||||
if [ ! -f "$LOCK_FILE" ]; then
|
||||
if [ "$CURRENT_WAN_STATE" = "down" ]; then
|
||||
# 发现断开,累加次数
|
||||
DOWN_COUNT=$((DOWN_COUNT+1))
|
||||
else
|
||||
# 发现连通,检查之前是否真的拔出过
|
||||
if [ "$LAST_WAN_STATE" = "down" ]; then
|
||||
# 只有断开超过 3 个周期 (约 9 秒),才认为是人工插拔
|
||||
if [ "$DOWN_COUNT" -ge 3 ]; then
|
||||
log "确认 WAN 口物理插拔,正在启动探测引擎"
|
||||
/usr/libexec/netwiz-autodetect.sh >/dev/null 2>&1 </dev/null &
|
||||
else
|
||||
log "忽略短时间的软件网络波动"
|
||||
fi
|
||||
fi
|
||||
DOWN_COUNT=0
|
||||
fi
|
||||
LAST_WAN_STATE="$CURRENT_WAN_STATE"
|
||||
fi
|
||||
|
||||
# --- 2. LAN 接口防失联雷达与炸弹 ---
|
||||
if [ -f /tmp/netwiz_rollback_time ] && [ -f /tmp/netwiz_target_ip ]; then
|
||||
TARGET_IP=$(cat /tmp/netwiz_target_ip 2>/dev/null)
|
||||
|
||||
# 不仅查并发数,还要强制校验“客户端 IP 子网”!
|
||||
# 只有当您的电脑 IP (如 192.168.10.100) 和目标 IP (192.168.10.1) 在同一个 /24 网段时,才算有效连接!
|
||||
conns=$(netstat -nt 2>/dev/null | awk -v tip="$TARGET_IP" '
|
||||
BEGIN {
|
||||
# 提取目标 IP 的前三段作为基准前缀 (例如 192.168.10.)
|
||||
split(tip, p, ".")
|
||||
prefix = p[1]"."p[2]"."p[3]"."
|
||||
}
|
||||
/ESTABLISHED/ {
|
||||
if ($4 == tip":80" || $4 == tip":443") {
|
||||
split($5, c, ":")
|
||||
client_ip = c[1]
|
||||
# 客户端 IP 的前缀不等于目标 IP 的前缀,直接无视!
|
||||
if (substr(client_ip, 1, length(prefix)) == prefix) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
END { print count+0 }
|
||||
')
|
||||
|
||||
# 阈值依然是 2,完美过滤前端的单线程探针
|
||||
if [ "$conns" -ge 2 ]; then
|
||||
log "成功:雷达检测到同网段真实浏览器访问新 IP ($TARGET_IP),自动解除定时"
|
||||
# 彻底清理所有相关的临时文件和备份
|
||||
rm -f /tmp/netwiz_rollback_time /tmp/netwiz_target_ip /etc/config/network.netwiz_bak /etc/config/dhcp.netwiz_bak
|
||||
else
|
||||
log "等待用户浏览器跳转中... (当前有效目标连接数: $conns)"
|
||||
|
||||
# 读取时间方式:剔除任何可能的空行或隐藏符号
|
||||
TARGET_TIME=$(cat /tmp/netwiz_rollback_time 2>/dev/null | tr -cd '0-9')
|
||||
CURRENT_TIME=$(date +%s)
|
||||
|
||||
# 只有读到了纯数字时间,且当前时间大于目标时间,才执行回退
|
||||
if [ -n "$TARGET_TIME" ] && [ "$CURRENT_TIME" -ge "$TARGET_TIME" ]; then
|
||||
log "时间到!未检测到任何浏览器访问,确认为失联,开始执行强制性回退"
|
||||
rm -f /tmp/netwiz_rollback_time /tmp/netwiz_target_ip
|
||||
|
||||
# 从 /tmp/ 读取纯净备份,并使用 cat 暴力覆盖以防权限断裂
|
||||
if [ -f /etc/config/network.netwiz_bak ]; then
|
||||
log "正在从闪存中恢复原始配置..."
|
||||
cat /etc/config/network.netwiz_bak > /etc/config/network
|
||||
[ -f /etc/config/dhcp.netwiz_bak ] && cat /etc/config/dhcp.netwiz_bak > /etc/config/dhcp
|
||||
|
||||
(
|
||||
exec >/dev/null 2>&1 </dev/null
|
||||
/etc/init.d/network restart
|
||||
/etc/init.d/dnsmasq restart
|
||||
/etc/init.d/uhttpd restart
|
||||
sleep 3
|
||||
echo "$(date '+%F %T') [Monitor] 回退操作已全部完成,网络已强制恢复旧 IP" >> "$LOG_FILE"
|
||||
# 恢复完后删除闪存备份
|
||||
rm -f /etc/config/network.netwiz_bak /etc/config/dhcp.netwiz_bak
|
||||
) &
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 每 3 秒执行一次检查
|
||||
sleep 3
|
||||
done
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"admin/netwiz": {
|
||||
"title": "Network Wizard",
|
||||
"order": 99,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netwiz"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-netwiz" ]
|
||||
}
|
||||
},
|
||||
"admin/netwiz_dev": {
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "netwiz_dev"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-netwiz" ]
|
||||
},
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"luci-app-netwiz": {
|
||||
"description": "Grant access to netwiz and device manager",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"netwiz": [ "*" ],
|
||||
"netwiz_dev": [ "*" ]
|
||||
}
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"netwiz": [ "*" ],
|
||||
"netwiz_dev": [ "*" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
# Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
# Licensed under the GNU General Public License v3.0
|
||||
|
||||
# ==================================================
|
||||
# English | IPv6 Watchdog - (VPS Edition)
|
||||
# ==================================================
|
||||
# [Environment Requirements]
|
||||
# 1. Ultra-lightweight: Uses built-in Python 3 on Linux (heavyweight environments like Node.js, Docker, or Nginx can also be used).
|
||||
# 2. The VPS must have IPv6 network outbound capability.
|
||||
#
|
||||
# [Native Daemon]
|
||||
# Uses systemd for process management, enabling auto-start on boot and sub-second self-healing on crashes.
|
||||
#
|
||||
# [Ultra-lightweight Deployment Steps]
|
||||
# 1. Save the file:
|
||||
# Copy all the code inside the `probe.py` file in this project and save it to your VPS at /root/probe.py
|
||||
#
|
||||
# 2. Create the daemon service:
|
||||
# Execute the command in the terminal: nano /etc/systemd/system/netwiz-probe.service
|
||||
# Paste the content inside the dashed lines below and save (Ctrl+O, Enter, Ctrl+X):
|
||||
# ----------------------------------
|
||||
# [Unit]
|
||||
# Description=Netwiz IPv6 Probe Service
|
||||
# After=network.target
|
||||
#
|
||||
# [Service]
|
||||
# Type=simple
|
||||
# User=root
|
||||
# ExecStart=/usr/bin/python3 /root/probe.py
|
||||
# Restart=always
|
||||
# RestartSec=3
|
||||
#
|
||||
# [Install]
|
||||
# WantedBy=multi-user.target
|
||||
# ----------------------------------
|
||||
#
|
||||
# 3. Activate and start the probe (execute sequentially):
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable netwiz-probe
|
||||
# systemctl start netwiz-probe
|
||||
#
|
||||
# 4. Plugin Configuration:
|
||||
# In the <📡 IPv6 Watchdog> section of the Netwiz Advanced Utility Settings, enter: http://YOUR_VPS_IP:18080
|
||||
# (Note: Port 18080 can be freely modified in the basic configuration below: LISTEN_PORT = 18080.
|
||||
# Please ensure the VPS firewall and cloud provider security group have allowed port 18080)
|
||||
#
|
||||
# [Common Management Commands]
|
||||
# Check status: systemctl status netwiz-probe
|
||||
# Stop probe: systemctl stop netwiz-probe
|
||||
# Restart probe: systemctl restart netwiz-probe
|
||||
# Real-time log: journalctl -u netwiz-probe -f
|
||||
#
|
||||
#
|
||||
# ==================================================
|
||||
# 简体中文 | IPv6 深度保活 - (VPS 版)
|
||||
# ==================================================
|
||||
# 【环境要求】
|
||||
# 1. 极致轻量:使用 Linux 自带的 Python 3(当然也可以使用 Node.js、Docker 或 Nginx 等庞大环境)。
|
||||
# 2. VPS 必须具备 IPv6 网络出口能力。
|
||||
#
|
||||
# 【原生守护】
|
||||
# 采用 systemd 进程守护,开机自启、崩溃秒级自愈。
|
||||
#
|
||||
# 【极致轻量部署步骤】
|
||||
# 1. 保存文件:
|
||||
# 复制本项目中的 `probe.py` 里的全部代码,保存到 VPS 的 /root/probe.py
|
||||
#
|
||||
# 2. 创建守护服务:
|
||||
# 在终端执行命令: nano /etc/systemd/system/netwiz-probe.service
|
||||
# 将下方虚线内的内容贴入并保存 (Ctrl+O, Enter, Ctrl+X):
|
||||
# ----------------------------------
|
||||
# [Unit]
|
||||
# Description=Netwiz IPv6 Probe Service
|
||||
# After=network.target
|
||||
#
|
||||
# [Service]
|
||||
# Type=simple
|
||||
# User=root
|
||||
# ExecStart=/usr/bin/python3 /root/probe.py
|
||||
# Restart=always
|
||||
# RestartSec=3
|
||||
#
|
||||
# [Install]
|
||||
# WantedBy=multi-user.target
|
||||
# ----------------------------------
|
||||
#
|
||||
# 3. 激活并启动探针(依次执行):
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable netwiz-probe
|
||||
# systemctl start netwiz-probe
|
||||
#
|
||||
# 4. 插件填写:
|
||||
# 在 Netwiz 高级实用设置的 <📡 IPv6 深度保活> 设置中,填写:http://您的VPS_IP:18080
|
||||
# (注:18080 端口可以在下方的基础配置:LISTEN_PORT = 18080 随意修改,请确保 VPS 防火墙与云服务商安全组已放行 18080 端口)
|
||||
#
|
||||
# 【常用管理命令】
|
||||
# 查看状态: systemctl status netwiz-probe
|
||||
# 停止探针: systemctl stop netwiz-probe
|
||||
# 重启探针: systemctl restart netwiz-probe
|
||||
# 实时日志: journalctl -u netwiz-probe -f
|
||||
#
|
||||
#
|
||||
# ==================================================
|
||||
# 繁體中文 | IPv6 深度保活 - (VPS 版)
|
||||
# ==================================================
|
||||
# 【環境要求】
|
||||
# 1. 極致輕量:使用 Linux 內建的 Python 3(當然也可以使用 Node.js、Docker 或 Nginx 等龐大環境)。
|
||||
# 2. VPS 必須具備 IPv6 網路出口能力。
|
||||
#
|
||||
# 【原生守護】
|
||||
# 採用 systemd 進程守護,開機自啟、崩潰秒級自癒。
|
||||
#
|
||||
# 【極致輕量部署步驟】
|
||||
# 1. 保存文件:
|
||||
# 複製本項目中的 `probe.py` 裡的全部代碼,保存到 VPS 的 /root/probe.py
|
||||
#
|
||||
# 2. 創建守護服務:
|
||||
# 在終端執行命令: nano /etc/systemd/system/netwiz-probe.service
|
||||
# 將下方虛線內的內容貼入並保存 (Ctrl+O, Enter, Ctrl+X):
|
||||
# ----------------------------------
|
||||
# [Unit]
|
||||
# Description=Netwiz IPv6 Probe Service
|
||||
# After=network.target
|
||||
#
|
||||
# [Service]
|
||||
# Type=simple
|
||||
# User=root
|
||||
# ExecStart=/usr/bin/python3 /root/probe.py
|
||||
# Restart=always
|
||||
# RestartSec=3
|
||||
#
|
||||
# [Install]
|
||||
# WantedBy=multi-user.target
|
||||
# ----------------------------------
|
||||
#
|
||||
# 3. 激活並啟動探針(依次執行):
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable netwiz-probe
|
||||
# systemctl start netwiz-probe
|
||||
#
|
||||
# 4. 外掛填寫:
|
||||
# 在 Netwiz 高級實用設置的 <📡 IPv6 深度保活> 設置中,填寫:http://您的VPS_IP:18080
|
||||
# (註:18080 端口可以在下方的基礎配置:LISTEN_PORT = 18080 隨意修改,請確保 VPS 防火牆與雲端服務商安全組已放行 18080 端口)
|
||||
#
|
||||
# 【常用管理命令】
|
||||
# 查看狀態: systemctl status netwiz-probe
|
||||
# 停止探針: systemctl stop netwiz-probe
|
||||
# 重啟探針: systemctl restart netwiz-probe
|
||||
# 即時日誌: journalctl -u netwiz-probe -f
|
||||
#
|
||||
# ============================ END ============================
|
||||
|
||||
# 基础配置 (可随意修改)
|
||||
LISTEN_PORT = 18080 # 绑定您要求的端口,可随时更改
|
||||
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import socket
|
||||
import re
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
# 强制绑定双栈 (支持 IPv6 和 IPv4)
|
||||
class HTTPServerV6(HTTPServer):
|
||||
address_family = socket.AF_INET6
|
||||
allow_reuse_address = True # 允许端口复用,防止重启时报 Address already in use 错误
|
||||
|
||||
class ProbeHandler(BaseHTTPRequestHandler):
|
||||
# 屏蔽默认的终端日志输出,保持清爽
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
# 统一返回 200 状态码和纯文本
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
|
||||
# 检测到当前系统没有 IPv6 网络出口,返回提示
|
||||
try:
|
||||
# 创建 UDP 套接字,探测本地是否有 IPv6 路由
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
s.connect(('2404:6800:4005:805::200e', 80))
|
||||
s.close()
|
||||
except Exception:
|
||||
self.wfile.write(b"FAIL_VPS_NO_IPV6_SUPPORT")
|
||||
return
|
||||
|
||||
# 1. 解码 URL (防止 IPv6 的方括号被浏览器转义为 %5B 和 %5D)
|
||||
decoded_path = urllib.parse.unquote(self.path)
|
||||
|
||||
# 2. 解析探针目标: 直接剥离掉最前面的 '/' 即可,不再需要任何前缀
|
||||
target = decoded_path.lstrip('/')
|
||||
|
||||
# 没带目标时,返回提示
|
||||
if not target:
|
||||
self.wfile.write(b"MISSING_TARGET")
|
||||
return
|
||||
|
||||
# 3. 协议补全 (默认补上 http://)
|
||||
if not target.startswith('http://') and not target.startswith('https://'):
|
||||
target = 'http://' + target
|
||||
|
||||
# 4. 提取 Hostname
|
||||
parsed_url = urllib.parse.urlparse(target)
|
||||
hostname = parsed_url.hostname or ''
|
||||
|
||||
# 5. 终极防呆:拦截私有局域网 IP
|
||||
private_ip_regex = r'^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|127\.|0\.|\[?fd[0-9a-f]{2}:)'
|
||||
if re.match(private_ip_regex, hostname, re.I):
|
||||
self.wfile.write(b"FAIL_PRIVATE_IP_BLOCKED")
|
||||
return
|
||||
|
||||
# 6. 发起真实探测
|
||||
try:
|
||||
req = urllib.request.Request(target)
|
||||
# 设定 6 秒超时 (与 CF Worker 代码保持一致)
|
||||
with urllib.request.urlopen(req, timeout=6) as res:
|
||||
self.wfile.write(b"OK")
|
||||
except urllib.error.HTTPError as e:
|
||||
# 收到密码拦截 (401/403) 也视为网络畅通
|
||||
if e.code in [401, 403]:
|
||||
self.wfile.write(b"OK")
|
||||
else:
|
||||
self.wfile.write(f"FAIL_HTTP_{e.code}".encode('utf-8'))
|
||||
except Exception as e:
|
||||
# 连接超时、拒绝连接、无法解析等网络层错误
|
||||
self.wfile.write(f"FAIL_NETWORK_VPS: {str(e)}".encode('utf-8'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f"Netwiz Probe 正在运行 (监听端口: {LISTEN_PORT})...")
|
||||
# 启动监听
|
||||
server = HTTPServerV6(('::', LISTEN_PORT), ProbeHandler)
|
||||
server.serve_forever()
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2026 huchd0 <https://github.com/huchd0/luci-app-netwiz>
|
||||
* Licensed under the GNU General Public License v3.0
|
||||
*/
|
||||
|
||||
/*
|
||||
# ==================================================
|
||||
# English | IPv6 Watchdog - (Cloudflare Edition)
|
||||
# ==================================================
|
||||
|
||||
Developed specifically to address the instability of IPv6 direct-connected devices. Leveraging Cloudflare's global edge network, it allows you to deploy quickly with zero cost and zero configuration.
|
||||
|
||||
### Perfectly integrates with the router's `luci-app-netwiz` to achieve enterprise-grade IPv6 direct connection keep-alive and self-healing.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Core Features
|
||||
|
||||
- **🎉 Free**: Hosted on Cloudflare Workers, providing up to 100,000 free probe requests per day, which is more than enough for home or normal usage.
|
||||
- **⚡ Native IPv6 Ultra-fast Response**: Utilizes Cloudflare's massive global IPv6 backbone network for precise and fast connections.
|
||||
- **🛡️ Privacy & Anti-abuse**: Pure probing logic with no user data collection. Built-in strict LAN IP protection (SSRF interception) to eliminate the risk of malicious scanning.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Guide
|
||||
|
||||
No complex local environment needed. You can build your exclusive probe directly through your browser.
|
||||
|
||||
### Step 1: Get the Probe Source Code
|
||||
Copy all the code inside the `worker.js` file in this project to use in the next step.
|
||||
|
||||
### Step 2: One-click Deployment to Cloudflare
|
||||
1. Visit the **[Cloudflare Dashboard](https://dash.cloudflare.com/)** and log in to your account.
|
||||
2. Find and click on **`Workers & Pages`** in the left navigation bar.
|
||||
3. Click **`Create application`**, then click **`Create Worker`**.
|
||||
4. Give your probe a name (e.g., `netwiz-probe`) and click **`Deploy`**.
|
||||
5. Once deployed successfully, click **`Edit code`**.
|
||||
6. Delete all default code in the input box, **select all and paste the entire content of `worker.js`** into it.
|
||||
7. Click **`Save and deploy`** in the top right corner.
|
||||
8. Note down the exclusive probe domain assigned to you by Cloudflare (e.g., `netwiz-probe.yourusername.workers.dev`).
|
||||
|
||||
### Step 3: Configure the Router Dashboard
|
||||
1. Copy the **probe base domain** you just obtained (e.g., `netwiz-probe.xxxx.workers.dev`).
|
||||
2. Log in to your router dashboard and navigate to the **Netwiz** advanced settings panel.
|
||||
3. Enable the **"📡 IPv6 Watchdog"** feature.
|
||||
4. Paste the copied domain directly into the **"Probe Target URL"** input box.
|
||||
5. Click **Save & Apply**.
|
||||
|
||||
🎉 **All done!** Your router is now equipped with self-healing capabilities for IPv6 direct connections!
|
||||
|
||||
|
||||
# ==================================================
|
||||
# 简体中文 | IPv6 深度保活 - (Cloudflare 版)
|
||||
# ==================================================
|
||||
|
||||
专门针对 IPv6 直连设备时不稳定的痛点开发,借助 Cloudflare 覆盖全球的边缘节点网络优势,让您可以零成本、免配置地快速部署。
|
||||
|
||||
### 完美配合路由器的 `luci-app-netwiz`,实现企业级的 IPv6 直联设备保活与故障自愈。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
- **🎉 免费**: 基于 Cloudflare Workers 托管,每天提供高达 10 万次的免费探测额度,足够家庭或正常用户使用。
|
||||
- **⚡ 原生 IPv6 极速响应**: 利用 Cloudflare 全球最庞大的 IPv6 骨干网进行精准连接。
|
||||
- **🛡️ 隐私与防滥用**: 纯净探测逻辑,不收集任何用户隐私数据,内置严格的局域网 IP 防护(SSRF 拦截),杜绝被恶意扫描的风险。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署指南
|
||||
|
||||
无需复杂的本地环境,通过浏览器即可完成专属探针的搭建。
|
||||
|
||||
### 第一步:获取探针源码
|
||||
复制本项目中的 `worker.js` 里的全部代码,准备在下一步使用。
|
||||
|
||||
### 第二步:一键部署到 Cloudflare
|
||||
1. 访问 **[Cloudflare 控制台](https://dash.cloudflare.com/)** 并登录您的账号。
|
||||
2. 在左侧导航栏找到并点击 **`Workers 和 Pages`**。
|
||||
3. 点击 **`创建应用程序 (Create application)`**,然后点击 **`创建 Worker (Create Worker)`**。
|
||||
4. 为您的探针起一个名字(例如:`netwiz-probe`),点击 **`部署 (Deploy)`**。
|
||||
5. 部署成功后,点击 **`编辑代码 (Edit code)`**。
|
||||
6. 将原来输入框里默认的代码全部删除,**全选并复制当前文件全部内容**粘贴进去。
|
||||
7. 点击右上角的 **`保存并部署 (Save and deploy)`**。
|
||||
8. 记录下 Cloudflare 为您分配的专属探针域名(例如:`netwiz-probe.您的用户名.workers.dev`)。
|
||||
|
||||
### 第三步:配置路由器后台
|
||||
1. 复制刚刚获取的**探针基础域名**(例如:`netwiz-probe.xxxx.workers.dev`)。
|
||||
2. 登录您的路由器后台,进入 **Netwiz 插件设置**高级面板。
|
||||
3. 开启 **“📡 IPv6 深度保活 (IPv6 Watchdog)”** 功能。
|
||||
4. 将复制的域名直接粘贴到 **“探测源目标网址 (Probe Target URL)”** 输入框中。
|
||||
5. 点击**保存并应用**。
|
||||
|
||||
🎉 **完成!** 现在您的路由器已经具备了 IPv6 直联的自愈能力!
|
||||
|
||||
|
||||
# ==================================================
|
||||
# 繁體中文 | IPv6 深度保活 - (Cloudflare 版)
|
||||
# ==================================================
|
||||
|
||||
專門針對 IPv6 直連設備時不穩定的痛點開發,借助 Cloudflare 覆蓋全球的邊緣節點網路優勢,讓您可以零成本、免配置地快速部署。
|
||||
|
||||
### 完美配合路由器的 `luci-app-netwiz`,實現企業級的 IPv6 直聯設備保活與故障自癒。
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
- **🎉 免費**: 基於 Cloudflare Workers 託管,每天提供高達 10 萬次的免費探測額度,足夠家庭或正常用戶使用。
|
||||
- **⚡ 原生 IPv6 極速響應**: 利用 Cloudflare 全球最龐大的 IPv6 骨幹網進行精準連接。
|
||||
- **🛡️ 隱私與防濫用**: 純淨探測邏輯,不收集任何用戶隱私數據,內置嚴格的區域網 IP 防護(SSRF 攔截),杜絕被惡意掃描的風險。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署指南
|
||||
|
||||
無需複雜的本地環境,透過瀏覽器即可完成專屬探針的搭建。
|
||||
|
||||
### 第一步:獲取探針源碼
|
||||
複製本項目中的 `worker.js` 裡的全部代碼,準備在下一步使用。
|
||||
|
||||
### 第二步:一鍵部署到 Cloudflare
|
||||
1. 訪問 **[Cloudflare 控制台](https://dash.cloudflare.com/)** 並登入您的帳號。
|
||||
2. 在左側導航列找到並點擊 **`Workers 和 Pages`**。
|
||||
3. 點擊 **`創建應用程式 (Create application)`**,然後點擊 **`創建 Worker (Create Worker)`**。
|
||||
4. 為您的探針起一個名字(例如:`netwiz-probe`),點擊 **`部署 (Deploy)`**。
|
||||
5. 部署成功後,點擊 **`編輯代碼 (Edit code)`**。
|
||||
6. 將原來輸入框裡預設的代碼全部刪除,**全選並複製當前文件全部內容**貼上進去。
|
||||
7. 點擊右上角的 **`保存並部署 (Save and deploy)`**。
|
||||
8. 記錄下 Cloudflare 為您分配的專屬探針域名(例如:`netwiz-probe.您的用戶名.workers.dev`)。
|
||||
|
||||
### 第三步:配置路由器後台
|
||||
1. 複製剛剛獲取的**探針基礎域名**(例如:`netwiz-probe.xxxx.workers.dev`)。
|
||||
2. 登入您的路由器後台,進入 **Netwiz 外掛設定**高級面板。
|
||||
3. 開啟 **「📡 IPv6 深度保活 (IPv6 Watchdog)」** 功能。
|
||||
4. 將複製的域名直接貼到 **「探測源目標網址 (Probe Target URL)」** 輸入框中。
|
||||
5. 點擊**保存並應用**。
|
||||
|
||||
🎉 **完成!** 現在您的路由器已經具備了 IPv6 直聯的自癒能力!
|
||||
|
||||
# ================================== END ==================================
|
||||
*/
|
||||
|
||||
|
||||
// ==========================================
|
||||
// 核心引擎 (通用探測版)
|
||||
// ==========================================
|
||||
async function runProbe(rawTarget) {
|
||||
if (!rawTarget) return "MISSING_TARGET";
|
||||
|
||||
// 1. 协议补全与斜杠修复
|
||||
// 修复可能被浏览器或代理压缩的斜杠,例如把 http:/[2408...] 还原成 http://[2408...]
|
||||
let target = rawTarget.replace(/^(https?):\/+/i, '$1://');
|
||||
|
||||
let fetchUrl = target;
|
||||
// 如果用户没自带协议,默认补上 http://
|
||||
if (!fetchUrl.startsWith('http://') && !fetchUrl.startsWith('https://')) {
|
||||
fetchUrl = 'http://' + fetchUrl;
|
||||
}
|
||||
|
||||
// 2. 提取 Hostname 进行局域网 IP 防护
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = new URL(fetchUrl);
|
||||
} catch (e) {
|
||||
return "FAIL_INVALID_URL";
|
||||
}
|
||||
|
||||
// 防呆:拦截私有局域网 IP
|
||||
const privateIPRegex = /^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|127\.|0\.|\[?fd[0-9a-f]{2}:)/i;
|
||||
// 使用 parsedUrl.hostname 进行检测,比单纯测字符串更精准
|
||||
if (privateIPRegex.test(parsedUrl.hostname)) return "FAIL_PRIVATE_IP_BLOCKED";
|
||||
|
||||
// 3. 发起真实探测
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 6000);
|
||||
|
||||
const response = await fetch(fetchUrl, { signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// 状态码为 200~299,或 401/403 (密码拦截) 均视为网络畅通
|
||||
if (response.ok || response.status === 401 || response.status === 403) {
|
||||
return "OK";
|
||||
} else {
|
||||
return "FAIL_HTTP_" + response.status;
|
||||
}
|
||||
} catch (err) {
|
||||
return "FAIL_NETWORK_CF: " + err.message + " | " + err.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 以下Cloudflare的「专属接头」,可以更换成其它平台的接头:
|
||||
// ==========================================
|
||||
// Cloudflare 专属接头
|
||||
// ==========================================
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// 提取域名后面的路径(不包含最前面的 '/')
|
||||
let targetPath = url.pathname.substring(1);
|
||||
|
||||
if (!targetPath) return new Response("MISSING_TARGET");
|
||||
|
||||
// 防止 IPv6 的方括号 [] 等特殊字符被 URL 编码导致解析失败
|
||||
try {
|
||||
targetPath = decodeURIComponent(targetPath);
|
||||
} catch (e) {
|
||||
// 忽略解码错误
|
||||
}
|
||||
|
||||
return new Response(await runProbe(targetPath));
|
||||
}
|
||||
};
|
||||
// === 结束 ===
|
||||
@@ -17,12 +17,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-wwand
|
||||
PKG_VERSION:=1.6.8_p8
|
||||
PKG_RELEASE:=31
|
||||
PKG_VERSION:=1.6.8_p13
|
||||
PKG_RELEASE:=32
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/luci-app-wwand.git
|
||||
PKG_SOURCE_VERSION:=ee53e2130cc68616aabb0f8e9343d124ae5dfe63
|
||||
PKG_SOURCE_VERSION:=210f4be24144a605276c108c0abb56c55e29eef2
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
@@ -79,7 +79,7 @@ endef
|
||||
# statistics graph definition would have been installed on boxes with no
|
||||
# luci-app-statistics. A new resource has to be added here, and that is the
|
||||
# point of listing them.
|
||||
LUCI_WWAND_VIEW:=modems.js settings.js status.js
|
||||
LUCI_WWAND_VIEW:=modems.js settings.js sims.js status.js
|
||||
LUCI_WWAND_RES:=bands.js esim.js format.js graph.js logbox.js mccmnc.js \
|
||||
modemopts.js modemsid.js netsel.js rpc.js signal.svg simlist.js
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-proto-wwand
|
||||
PKG_VERSION:=1.6.8_p1
|
||||
PKG_RELEASE:=15
|
||||
PKG_VERSION:=1.6.8_p2
|
||||
PKG_RELEASE:=16
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/luci-proto-wwand.git
|
||||
PKG_SOURCE_VERSION:=43c71ef81faa7c2a164a9026ab526d3e2544a88e
|
||||
PKG_SOURCE_VERSION:=d2b2e85f5885581547f5863379fc44d64e9c9051
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand-ipa
|
||||
PKG_VERSION:=0.1.0_p2
|
||||
PKG_RELEASE:=5
|
||||
PKG_VERSION:=0.1.0_p6
|
||||
PKG_RELEASE:=6
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand-ipa.git
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand-ipad
|
||||
PKG_VERSION:=0.1.0_p21
|
||||
PKG_RELEASE:=6
|
||||
PKG_VERSION:=0.1.0_p40
|
||||
PKG_RELEASE:=7
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/ipad.git
|
||||
|
||||
+6
-4
@@ -21,8 +21,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand-rsim
|
||||
PKG_VERSION:=0.0.1_p37
|
||||
PKG_RELEASE:=4
|
||||
PKG_VERSION:=0.0.1_p55
|
||||
PKG_RELEASE:=5
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand-rsim.git
|
||||
@@ -151,8 +151,10 @@ define Package/rsim-card/description
|
||||
libusb) or the SIM of a modem it reaches over its AT port (at:<tty>,
|
||||
AT+CSIM; that modem's radio is switched off while its card is lent), and
|
||||
speaks JSON lines on stdin/stdout; a paired phone's SIM over the
|
||||
Bluetooth SIM Access Profile (bt:<address>, kernel sockets only).
|
||||
`rsim-card --list` shows what the machine offers; `rsim-card --serve` is
|
||||
Bluetooth SIM Access Profile (bt:<address>, kernel sockets only); or a
|
||||
card in an osmo-remsim SIM bank, as its RSPRO client
|
||||
(rspro:<server>[:port][/<bank>:<slot>], used by the router itself — never
|
||||
served over SSH). `rsim-card --list` shows what the machine offers; `rsim-card --serve` is
|
||||
the forced command for the router's SSH key. Install it on a router that only holds
|
||||
the reader or the modem (a SIM host); the router with the using modem
|
||||
reaches it over SSH (rsim_reader ssh:<user>@<host>:<reader>). Needs
|
||||
|
||||
+3
-3
@@ -50,12 +50,12 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=wwand
|
||||
PKG_VERSION:=1.6.8_p56
|
||||
PKG_RELEASE:=60
|
||||
PKG_VERSION:=1.6.8_p66
|
||||
PKG_RELEASE:=61
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git
|
||||
PKG_SOURCE_VERSION:=ed275d3cf5ab201fcbdf213b4665a443ca776c5c
|
||||
PKG_SOURCE_VERSION:=d91a638f92d8f058456d943d74f5afa033f792f9
|
||||
PKG_MIRROR_HASH:=skip
|
||||
|
||||
PKG_LICENSE:=GPL-2.0-only
|
||||
|
||||
Reference in new issue
Block a user