This commit is contained in:
kenzok8 2023-04-27 03:31:00 +00:00
parent e89bcc5715
commit 9d30bd7b3d
1140 changed files with 781241 additions and 17889 deletions

112
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,112 @@
name: build 2 package
on:
workflow_dispatch:
jobs:
build:
name: bulid ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- arch: aarch64_cortex-a53
target: mvebu-cortexa53
- arch: x86_64
target: x86-64
steps:
- uses: actions/checkout@main
with:
fetch-depth: 0
- name: Determine branch name
run: |
BRANCH="master"
echo "Building for $BRANCH"
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
- name: Determine changed packages
run: |
# only detect packages with changes
PKG_ROOTS=$(find . -name Makefile | \
grep -v ".*/src/Makefile" | \
sed -e 's@./\(.*\)/Makefile@\1/@')
CHANGES=$(git diff --diff-filter=d --name-only origin/$BRANCH)
for ROOT in $PKG_ROOTS; do
for CHANGE in $CHANGES; do
if [[ "$CHANGE" == "$ROOT"* ]]; then
PACKAGES+=$(echo "$ROOT" | sed -e 's@\(.*\)/@\1 @')
break
fi
done
done
# fallback to test packages if nothing explicitly changes this is
# should run if other mechanics in packages.git changed
PACKAGES="${PACKAGES:-luci-app-ssr-plus luci-app-passwall}"
echo "Building $PACKAGES"
echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV
- name: Build
uses: immortalwrt/gh-action-sdk@master
env:
ARCH: ${{ matrix.arch }}-${{ env.BRANCH }}
FEEDNAME: packages_ci
- name: Move created packages to project dir
run: cp bin/packages/${{ matrix.arch }}/packages_ci/*.ipk . || true
- name: Collect metadata
run: |
MERGE_ID=$(git rev-parse --short HEAD)
echo "MERGE_ID=$MERGE_ID" >> $GITHUB_ENV
echo "BASE_ID=$(git rev-parse --short HEAD^1)" >> $GITHUB_ENV
echo "HEAD_ID=$(git rev-parse --short HEAD^2)" >> $GITHUB_ENV
PRNUMBER=${GITHUB_REF_NAME%/merge}
echo "PRNUMBER=$PRNUMBER" >> $GITHUB_ENV
echo "ARCHIVE_NAME=${{matrix.arch}}-PR$PRNUMBER-$MERGE_ID" >> $GITHUB_ENV
- name: Generate metadata
run: |
cat << _EOF_ > PKG-INFO
Metadata-Version: 2.1
Name: ${{env.ARCHIVE_NAME}}
Version: $BRANCH
Author: $GITHUB_ACTOR
Home-page: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$PRNUMBER
Download-URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID
Summary: $PACKAGES
Platform: ${{ matrix.arch }}
Packages for ImmortalWrt $BRANCH running on ${{matrix.arch}}
at commit $HEAD_ID, against $BRANCH at commit $BASE_ID, with merge SHA $MERGE_ID.
Modified packages:
_EOF_
for p in $PACKAGES
do
echo " "$p >> PKG-INFO
done
echo >> PKG-INFO
echo Full file listing: >> PKG-INFO
ls -al *.ipk >> PKG-INFO || true
cat PKG-INFO
- name: Store packages
uses: actions/upload-artifact@main
with:
name: ${{env.ARCHIVE_NAME}}-packages
path: |
*.ipk
- name: Upload packages
uses: ncipollo/release-action@main
with:
name: ${{ github.ref_name }}
token: ${{ secrets.ACCESS_TOKEN }}
allowUpdates: true
replacesArtifacts: true
artifacts: "bin/packages/${{ matrix.arch }}/packages_ci/*.ipk"
tag_name: v2.0.0 # 根据需要修改

134
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,134 @@
name: bulid 9 package
on:
workflow_dispatch:
jobs:
build:
name: bulid ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- arch: arm_cortex-a9_vfpv3-d16
target: mvebu-cortexa9
- arch: mips_24kc
target: ath79-generic
- arch: mipsel_24kc
target: mt7621
- arch: powerpc_464fp
target: apm821xx-nand
- arch: powerpc_8548
target: mpc85xx-p1010
- arch: aarch64_cortex-a53
target: mvebu-cortexa53
- arch: arm_cortex-a15_neon-vfpv4
target: armvirt-32
- arch: i386_pentium-mmx
target: x86-geode
- arch: x86_64
target: x86-64
steps:
- uses: actions/checkout@main
with:
fetch-depth: 0
- name: Determine branch name
run: |
BRANCH="master"
echo "Building for $BRANCH"
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
- name: Determine changed packages
run: |
# only detect packages with changes
PKG_ROOTS=$(find . -name Makefile | \
grep -v ".*/src/Makefile" | \
sed -e 's@./\(.*\)/Makefile@\1/@')
CHANGES=$(git diff --diff-filter=d --name-only origin/$BRANCH)
for ROOT in $PKG_ROOTS; do
for CHANGE in $CHANGES; do
if [[ "$CHANGE" == "$ROOT"* ]]; then
PACKAGES+=$(echo "$ROOT" | sed -e 's@\(.*\)/@\1 @')
break
fi
done
done
# fallback to test packages if nothing explicitly changes this is
# should run if other mechanics in packages.git changed
PACKAGES="${PACKAGES:-luci-app-ssr-plus}"
echo "Building $PACKAGES"
echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV
- name: Build
uses: immortalwrt/gh-action-sdk@master
env:
ARCH: ${{ matrix.arch }}-${{ env.BRANCH }}
FEEDNAME: packages_ci
- name: Move created packages to project dir
run: cp bin/packages/${{ matrix.arch }}/packages_ci/*.ipk . || true
- name: Collect metadata
run: |
MERGE_ID=$(git rev-parse --short HEAD)
echo "MERGE_ID=$MERGE_ID" >> $GITHUB_ENV
echo "BASE_ID=$(git rev-parse --short HEAD^1)" >> $GITHUB_ENV
echo "HEAD_ID=$(git rev-parse --short HEAD^2)" >> $GITHUB_ENV
PRNUMBER=${GITHUB_REF_NAME%/merge}
echo "PRNUMBER=$PRNUMBER" >> $GITHUB_ENV
echo "ARCHIVE_NAME=${{matrix.arch}}-PR$PRNUMBER-$MERGE_ID" >> $GITHUB_ENV
- name: Generate metadata
run: |
cat << _EOF_ > PKG-INFO
Metadata-Version: 2.1
Name: ${{env.ARCHIVE_NAME}}
Version: $BRANCH
Author: $GITHUB_ACTOR
Home-page: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/pull/$PRNUMBER
Download-URL: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID
Summary: $PACKAGES
Platform: ${{ matrix.arch }}
Packages for ImmortalWrt $BRANCH running on ${{matrix.arch}}, built from PR $PRNUMBER
at commit $HEAD_ID, against $BRANCH at commit $BASE_ID, with merge SHA $MERGE_ID.
Modified packages:
_EOF_
for p in $PACKAGES
do
echo " "$p >> PKG-INFO
done
echo >> PKG-INFO
echo Full file listing: >> PKG-INFO
ls -al *.ipk >> PKG-INFO || true
cat PKG-INFO
- name: Store packages
uses: actions/upload-artifact@main
with:
name: ${{env.ARCHIVE_NAME}}-packages
path: |
*.ipk
- name: Upload packages
uses: ncipollo/release-action@main
with:
name: ${{ github.ref_name }}
token: ${{ secrets.ACCESS_TOKEN }}
allowUpdates: true
replacesArtifacts: true
artifacts: "bin/packages/${{ matrix.arch }}/packages_ci/*.ipk"
tag_name: v9.0.0 # 根据需要修改

View File

@ -1,38 +1,38 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2023 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=chinadns-ng
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2023 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=chinadns-ng
PKG_VERSION:=2023.04.20
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/chinadns-ng/tar.gz/$(PKG_VERSION)?
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/chinadns-ng/tar.gz/$(PKG_VERSION)?
PKG_HASH:=e1f5197ecd96ebab4440fe069e0a06935df7ea07ac1ad0bab3f88bffe0055294
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=pexcn <i@pexcn.me>
PKG_BUILD_PARALLEL:=1
PKG_INSTALL:=1
include $(INCLUDE_DIR)/package.mk
define Package/chinadns-ng
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=ChinaDNS next generation, refactoring with epoll and ipset.
URL:=https://github.com/zfl9/chinadns-ng
DEPENDS:=+ipset
endef
define Package/chinadns-ng/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/chinadns-ng $(1)/usr/bin
endef
$(eval $(call BuildPackage,chinadns-ng))
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=pexcn <i@pexcn.me>
PKG_BUILD_PARALLEL:=1
PKG_INSTALL:=1
include $(INCLUDE_DIR)/package.mk
define Package/chinadns-ng
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=ChinaDNS next generation, refactoring with epoll and ipset.
URL:=https://github.com/zfl9/chinadns-ng
DEPENDS:=+ipset
endef
define Package/chinadns-ng/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/chinadns-ng $(1)/usr/bin
endef
$(eval $(call BuildPackage,chinadns-ng))

View File

@ -1,53 +1,53 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=dns2socks
PKG_VERSION:=2.1
PKG_RELEASE:=2
PKG_SOURCE:=SourceCode.zip
PKG_SOURCE_URL:=@SF/dns2socks
PKG_SOURCE_DATE:=2020-02-18
PKG_HASH:=406b5003523577d39da66767adfe54f7af9b701374363729386f32f6a3a995f4
PKG_MAINTAINER:=ghostmaker
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILE:=LICENSE
include $(INCLUDE_DIR)/package.mk
UNZIP_CMD:=unzip -q -d $(PKG_BUILD_DIR) $(DL_DIR)/$(PKG_SOURCE)
define Package/dns2socks
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=DNS to SOCKS or HTTP proxy
URL:=http://dns2socks.sourceforge.net/
DEPENDS:=+libpthread
endef
define Package/dns2socks/description
This is a command line utility to resolve DNS requests via
a SOCKS tunnel like Tor or a HTTP proxy.
endef
define Build/Compile
$(TARGET_CC) \
$(TARGET_CFLAGS) \
$(TARGET_CPPFLAGS) \
$(FPIC) \
-o $(PKG_BUILD_DIR)/DNS2SOCKS/dns2socks \
$(PKG_BUILD_DIR)/DNS2SOCKS/DNS2SOCKS.c \
$(TARGET_LDFLAGS) -pthread
endef
define Package/dns2socks/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/DNS2SOCKS/dns2socks $(1)/usr/bin/dns2socks
endef
$(eval $(call BuildPackage,dns2socks))
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=dns2socks
PKG_VERSION:=2.1
PKG_RELEASE:=2
PKG_SOURCE:=SourceCode.zip
PKG_SOURCE_URL:=@SF/dns2socks
PKG_SOURCE_DATE:=2020-02-18
PKG_HASH:=406b5003523577d39da66767adfe54f7af9b701374363729386f32f6a3a995f4
PKG_MAINTAINER:=ghostmaker
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILE:=LICENSE
include $(INCLUDE_DIR)/package.mk
UNZIP_CMD:=unzip -q -d $(PKG_BUILD_DIR) $(DL_DIR)/$(PKG_SOURCE)
define Package/dns2socks
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=DNS to SOCKS or HTTP proxy
URL:=http://dns2socks.sourceforge.net/
DEPENDS:=+libpthread
endef
define Package/dns2socks/description
This is a command line utility to resolve DNS requests via
a SOCKS tunnel like Tor or a HTTP proxy.
endef
define Build/Compile
$(TARGET_CC) \
$(TARGET_CFLAGS) \
$(TARGET_CPPFLAGS) \
$(FPIC) \
-o $(PKG_BUILD_DIR)/DNS2SOCKS/dns2socks \
$(PKG_BUILD_DIR)/DNS2SOCKS/DNS2SOCKS.c \
$(TARGET_LDFLAGS) -pthread
endef
define Package/dns2socks/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/DNS2SOCKS/dns2socks $(1)/usr/bin/dns2socks
endef
$(eval $(call BuildPackage,dns2socks))

View File

@ -1,44 +1,44 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2022 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=dns2tcp
PKG_VERSION:=1.1.0
PKG_RELEASE:=$(AUTORELEASE)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/zfl9/dns2tcp.git
PKG_SOURCE_DATE:=2020-05-05
PKG_SOURCE_VERSION:=10ed687778f18d1b9c98bb51187a3a9086d3a0d9
PKG_MIRROR_HASH:=bb40f027fef8534b0f905a827dbc3cc613fd06b705392148a12b80e1f9570a88
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILES:=LICENSE
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
include $(INCLUDE_DIR)/package.mk
define Package/dns2tcp
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=utility to convert dns query from udp to tcp
URL:=https://github.com/zfl9/dns2tcp
endef
TARGET_CFLAGS+= $(FPIC) -flto
MAKE_FLAGS+= \
CFLAGS="-std=c99 $(TARGET_CFLAGS)" \
EVCFLAGS="$(TARGET_CFLAGS)"
define Package/dns2tcp/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/dns2tcp $(1)/usr/bin/
endef
$(eval $(call BuildPackage,dns2tcp))
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2022 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=dns2tcp
PKG_VERSION:=1.1.0
PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/zfl9/dns2tcp.git
PKG_SOURCE_DATE:=2020-05-05
PKG_SOURCE_VERSION:=10ed687778f18d1b9c98bb51187a3a9086d3a0d9
PKG_MIRROR_HASH:=bb40f027fef8534b0f905a827dbc3cc613fd06b705392148a12b80e1f9570a88
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILES:=LICENSE
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
include $(INCLUDE_DIR)/package.mk
define Package/dns2tcp
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=utility to convert dns query from udp to tcp
URL:=https://github.com/zfl9/dns2tcp
endef
TARGET_CFLAGS+= $(FPIC) -flto
MAKE_FLAGS+= \
CFLAGS="-std=c99 $(TARGET_CFLAGS)" \
EVCFLAGS="$(TARGET_CFLAGS)"
define Package/dns2tcp/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/dns2tcp $(1)/usr/bin/
endef
$(eval $(call BuildPackage,dns2tcp))

61
gn/Makefile Normal file
View File

@ -0,0 +1,61 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2022 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=gn
PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2023-04-05
PKG_SOURCE_VERSION:=28b7b6c507eb808567e3aea446cd259f7691fddc
PKG_MIRROR_HASH:=5b112442eabea4b906cc409237fac13b8efb4f5acb25989b1883d5ce96deebd2
PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
ifneq ($(wildcard $(TOPDIR)/feeds/packages/devel/ninja/ninja.mk),)
PKG_BUILD_DEPENDS+= ninja/host
endif
PKG_HOST_ONLY:=1
HOST_BUILD_PARALLEL:=1
include $(INCLUDE_DIR)/host-build.mk
include $(INCLUDE_DIR)/package.mk
define Package/gn
SECTION:=devel
CATEGORY:=Development
TITLE:=A meta-build system that generates build files for Ninja
URL:=https://gn.googlesource.com/gn/
BUILDONLY:=1
endef
define Package/gn/description
GN can generate Ninja build files for C, C++, Rust, Objective C,
and Swift source on most popular platforms.
endef
define Host/Configure
$(PYTHON) $(HOST_BUILD_DIR)/build/gen.py \
--no-last-commit-position
endef
define Host/Compile
ninja -C $(HOST_BUILD_DIR)/out
endef
define Host/Install
$(INSTALL_DIR) $(STAGING_DIR_HOSTPKG)/bin/
$(INSTALL_BIN) $(HOST_BUILD_DIR)/out/gn $(STAGING_DIR_HOSTPKG)/bin/
endef
define Host/Clean
$(RM) $(STAGING_DIR_HOSTPKG)/bin/gn
endef
$(eval $(call HostBuild))
$(eval $(call BuildPackage,gn))

11
gn/patches/010-gcc.patch Normal file
View File

@ -0,0 +1,11 @@
--- a/build/gen.py
+++ b/build/gen.py
@@ -368,7 +368,7 @@ def WriteGNNinja(path, platform, host, o
cxx = os.environ.get('CXX', 'g++')
ld = os.environ.get('LD', 'g++')
ar = os.environ.get('AR', 'ar -X64')
- elif platform.is_msys() or platform.is_mingw():
+ elif platform.is_msys() or platform.is_mingw() or platform.is_linux():
cxx = os.environ.get('CXX', 'g++')
ld = os.environ.get('LD', 'g++')
ar = os.environ.get('AR', 'ar')

View File

@ -0,0 +1,9 @@
// Generated by build/gen.py.
#ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2085
#define LAST_COMMIT_POSITION "2085 (28b7b6c507eb)"
#endif // OUT_LAST_COMMIT_POSITION_H_

View File

@ -1,53 +1,53 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=hysteria
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2022 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=hysteria
PKG_VERSION:=1.3.4
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/v$(PKG_VERSION)?
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/apernet/hysteria/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=13862594252dcea83d791b0cf8234874154ba623f22d94cef685561a710717c1
PKG_LICENSE:=MIT
PKG_LICENSE_FILE:=LICENSE
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
GO_PKG:=github.com/apernet/hysteria
GO_PKG_BUILD_PKG:=$(GO_PKG)/app/cmd
GO_PKG_LDFLAGS_X:=main.appVersion=$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/hysteria
SECTION:=net
CATEGORY:=Network
TITLE:=A feature-packed network utility optimized for networks of poor quality
URL:=https://github.com/apernet/hysteria
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
endef
define Package/hysteria/description
Hysteria is a feature-packed network utility optimized for networks
of poor quality (e.g. satellite connections, congested public Wi-Fi,
connecting from China to servers abroad) powered by a custom version
of QUIC protocol.
endef
define Package/hysteria/install
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/cmd $(1)/usr/bin/hysteria
endef
$(eval $(call GoBinPackage,hysteria))
$(eval $(call BuildPackage,hysteria))
PKG_LICENSE:=MIT
PKG_LICENSE_FILE:=LICENSE
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
GO_PKG:=github.com/apernet/hysteria
GO_PKG_BUILD_PKG:=$(GO_PKG)/app/cmd
GO_PKG_LDFLAGS_X:=main.appVersion=$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/hysteria
SECTION:=net
CATEGORY:=Network
TITLE:=A feature-packed network utility optimized for networks of poor quality
URL:=https://github.com/apernet/hysteria
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
endef
define Package/hysteria/description
Hysteria is a feature-packed network utility optimized for networks
of poor quality (e.g. satellite connections, congested public Wi-Fi,
connecting from China to servers abroad) powered by a custom version
of QUIC protocol.
endef
define Package/hysteria/install
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/cmd $(1)/usr/bin/hysteria
endef
$(eval $(call GoBinPackage,hysteria))
$(eval $(call BuildPackage,hysteria))

View File

@ -1,45 +1,45 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=ipt2socks
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=ipt2socks
PKG_VERSION:=1.1.3
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/ipt2socks/tar.gz/v$(PKG_VERSION)?
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/zfl9/ipt2socks/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=73a2498dc95934c225d358707e7f7d060b5ce81aa45260ada09cbd15207d27d1
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
PKG_LICENSE:=AGPL-3.0
PKG_LICENSE_FILE:=LICENSE
include $(INCLUDE_DIR)/package.mk
define Package/ipt2socks
SECTION:=net
CATEGORY:=Network
TITLE:=Convert iptables to socks5
URL:=https://github.com/zfl9/ipt2socks
DEPENDS:=+libpthread
endef
define Package/ipt2socks/description
Utility for converting iptables (redirect/tproxy) to socks5.
endef
TARGET_CFLAGS+= $(FPIC) -flto
MAKE_FLAGS+= \
CFLAGS="-std=c99 -pthread $(TARGET_CFLAGS)" \
EVCFLAGS="$(TARGET_CFLAGS)"
define Package/ipt2socks/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ipt2socks $(1)/usr/bin
endef
$(eval $(call BuildPackage,ipt2socks))
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
PKG_LICENSE:=AGPL-3.0
PKG_LICENSE_FILE:=LICENSE
include $(INCLUDE_DIR)/package.mk
define Package/ipt2socks
SECTION:=net
CATEGORY:=Network
TITLE:=Convert iptables to socks5
URL:=https://github.com/zfl9/ipt2socks
DEPENDS:=+libpthread
endef
define Package/ipt2socks/description
Utility for converting iptables (redirect/tproxy) to socks5.
endef
TARGET_CFLAGS+= $(FPIC) -flto
MAKE_FLAGS+= \
CFLAGS="-std=c99 -pthread $(TARGET_CFLAGS)" \
EVCFLAGS="$(TARGET_CFLAGS)"
define Package/ipt2socks/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ipt2socks $(1)/usr/bin
endef
$(eval $(call BuildPackage,ipt2socks))

44
lua-neturl/Makefile Normal file
View File

@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2022-2023 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=neturl
PKG_VERSION:=1.1-1
PKG_RELEASE:=3
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/golgote/neturl/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=25f3a94ba9f435ef1395555de2bf17d6f934d789fa515ed965405919e42be27b
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_LICENSE:=MIT
PKG_LICNESE_FILES:=LICENSE.txt
include $(INCLUDE_DIR)/package.mk
define Package/lua-neturl
SUBMENU:=Lua
SECTION:=lang
CATEGORY:=Languages
TITLE:=URL and Query string parser, builder, normalizer for Lua
URL:=https://github.com/golgote/neturl
DEPENDS:=+lua
PKGARCH:=all
endef
define Package/lua-neturl/description
This small Lua library provides a few functions to parse URL with
querystring and build new URL easily.
endef
define Build/Compile
endef
define Package/lua-neturl/install
$(INSTALL_DIR) $(1)/usr/lib/lua
$(CP) $(PKG_BUILD_DIR)/lib/net/url.lua $(1)/usr/lib/lua/
endef
$(eval $(call BuildPackage,lua-neturl))

View File

@ -0,0 +1,20 @@
--- a/lib/net/url.lua
+++ b/lib/net/url.lua
@@ -340,7 +340,7 @@ function M:setAuthority(authority)
self.password = v
return ''
end)
- if string.find(userinfo, "^[%w%+%.]+$") then
+ if string.find(userinfo, "^[%p%w%+%.]+$") then
self.user = userinfo
else
-- incorrect userinfo
@@ -369,7 +369,7 @@ function M.parse(url)
comp.fragment = v
return ''
end)
- url =url:gsub('^([%w][%w%+%-%.]*)%:', function(v)
+ url =url:gsub('^([%w][%w%+%-%_%.]*)%:', function(v)
comp.scheme = v:lower()
return ''
end)

183
luci-app-passwall/Makefile Normal file
View File

@ -0,0 +1,183 @@
# Copyright (C) 2018-2020 L-WRT Team
# Copyright (C) 2021-2023 xiaorouji
#
# This is free software, licensed under the GNU General Public License v3.
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=4.65-2
PKG_RELEASE:=
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Brook \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Geodata \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin
LUCI_TITLE:=LuCI support for PassWall
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \
+chinadns-ng +dns2socks +dns2tcp +ip-full +libuci-lua +lua +luci-compat +luci-lib-jsonc \
+microsocks +resolveip +tcping +unzip \
+PACKAGE_$(PKG_NAME)_INCLUDE_Brook:brook \
+PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy:haproxy \
+PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria:hysteria \
+PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server:shadowsocks-libev-ss-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client:shadowsocks-rust-sslocal \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server:shadowsocks-rust-ssserver \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs:simple-obfs \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO:trojan-go \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus:trojan-plus \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Geodata:v2ray-geoip \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Geodata:v2ray-geosite \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:xray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin:xray-plugin
define Package/$(PKG_NAME)/config
menu "Configuration"
config PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy
bool "Iptables Transparent Proxy"
select PACKAGE_dnsmasq-full
select PACKAGE_ipset
select PACKAGE_ipt2socks
select PACKAGE_iptables
select PACKAGE_iptables-zz-legacy
select PACKAGE_iptables-mod-conntrack-extra
select PACKAGE_iptables-mod-iprange
select PACKAGE_iptables-mod-socket
select PACKAGE_iptables-mod-tproxy
select PACKAGE_kmod-ipt-nat
depends on PACKAGE_$(PKG_NAME)
default y if ! PACKAGE_firewall4
config PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy
bool "Nftables Transparent Proxy"
select PACKAGE_dnsmasq-full
select PACKAGE_nftables
select PACKAGE_kmod-nft-socket
select PACKAGE_kmod-nft-tproxy
select PACKAGE_kmod-nft-nat
depends on PACKAGE_$(PKG_NAME)
default y if PACKAGE_firewall4
config PACKAGE_$(PKG_NAME)_INCLUDE_Brook
bool "Include Brook"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy
bool "Include Haproxy"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria
bool "Include Hysteria"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
bool "Include NaiveProxy"
depends on !(arc||(arm&&TARGET_gemini)||armeb||mips||mips64||powerpc)
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
bool "Include Shadowsocks Libev Client"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server
bool "Include Shadowsocks Libev Server"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
bool "Include Shadowsocks Rust Client"
depends on aarch64||arm||i386||mips||mipsel||x86_64
default y if aarch64
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server
bool "Include Shadowsocks Rust Server"
depends on aarch64||arm||i386||mips||mipsel||x86_64
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client
bool "Include ShadowsocksR Libev Client"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server
bool "Include ShadowsocksR Libev Server"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs
bool "Include Simple-Obfs (Shadowsocks Plugin)"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_GO
bool "Include Trojan-GO"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus
bool "Include Trojan-Plus"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
bool "Include V2ray"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Geodata
bool "Include V2ray_Geodata"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
bool "Include V2ray-Plugin (Shadowsocks Plugin)"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
bool "Include Xray"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin
bool "Include Xray-Plugin (Shadowsocks Plugin)"
default n
endmenu
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/passwall
/etc/config/passwall_server
/usr/share/passwall/rules/direct_host
/usr/share/passwall/rules/direct_ip
/usr/share/passwall/rules/proxy_host
/usr/share/passwall/rules/proxy_ip
/usr/share/passwall/rules/block_host
/usr/share/passwall/rules/block_ip
/usr/share/passwall/rules/lanlist_ipv4
/usr/share/passwall/rules/lanlist_ipv6
/usr/share/passwall/rules/domains_excluded
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,429 @@
-- Copyright (C) 2018-2020 L-WRT Team
-- Copyright (C) 2021-2023 xiaorouji
module("luci.controller.passwall", package.seeall)
local api = require "luci.passwall.api"
local appname = api.appname
local ucic = luci.model.uci.cursor()
local http = require "luci.http"
local util = require "luci.util"
local i18n = require "luci.i18n"
function index()
appname = require "luci.passwall.api".appname
entry({"admin", "services", appname}).dependent = true
entry({"admin", "services", appname, "reset_config"}, call("reset_config")).leaf = true
entry({"admin", "services", appname, "show"}, call("show_menu")).leaf = true
entry({"admin", "services", appname, "hide"}, call("hide_menu")).leaf = true
if not nixio.fs.access("/etc/config/passwall") then return end
if nixio.fs.access("/etc/config/passwall_show") then
e = entry({"admin", "services", appname}, alias("admin", "services", appname, "settings"), _("Pass Wall"), -1)
e.dependent = true
e.acl_depends = { "luci-app-passwall" }
end
--[[ Client ]]
entry({"admin", "services", appname, "settings"}, cbi(appname .. "/client/global"), _("Basic Settings"), 1).dependent = true
entry({"admin", "services", appname, "node_list"}, cbi(appname .. "/client/node_list"), _("Node List"), 2).dependent = true
entry({"admin", "services", appname, "node_subscribe"}, cbi(appname .. "/client/node_subscribe"), _("Node Subscribe"), 3).dependent = true
entry({"admin", "services", appname, "auto_switch"}, cbi(appname .. "/client/auto_switch"), _("Auto Switch"), 4).leaf = true
entry({"admin", "services", appname, "other"}, cbi(appname .. "/client/other", {autoapply = true}), _("Other Settings"), 92).leaf = true
if nixio.fs.access("/usr/sbin/haproxy") then
entry({"admin", "services", appname, "haproxy"}, cbi(appname .. "/client/haproxy"), _("Load Balancing"), 93).leaf = true
end
entry({"admin", "services", appname, "app_update"}, cbi(appname .. "/client/app_update"), _("App Update"), 95).leaf = true
entry({"admin", "services", appname, "rule"}, cbi(appname .. "/client/rule"), _("Rule Manage"), 96).leaf = true
entry({"admin", "services", appname, "rule_list"}, cbi(appname .. "/client/rule_list"), _("Rule List"), 97).leaf = true
entry({"admin", "services", appname, "node_subscribe_config"}, cbi(appname .. "/client/node_subscribe_config")).leaf = true
entry({"admin", "services", appname, "node_config"}, cbi(appname .. "/client/node_config")).leaf = true
entry({"admin", "services", appname, "shunt_rules"}, cbi(appname .. "/client/shunt_rules")).leaf = true
entry({"admin", "services", appname, "acl"}, cbi(appname .. "/client/acl"), _("Access control"), 98).leaf = true
entry({"admin", "services", appname, "acl_config"}, cbi(appname .. "/client/acl_config")).leaf = true
entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Watch Logs"), 999).leaf = true
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
entry({"admin", "services", appname, "server_user"}, cbi(appname .. "/server/user")).leaf = true
--[[ API ]]
entry({"admin", "services", appname, "server_user_status"}, call("server_user_status")).leaf = true
entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).leaf = true
entry({"admin", "services", appname, "server_get_log"}, call("server_get_log")).leaf = true
entry({"admin", "services", appname, "server_clear_log"}, call("server_clear_log")).leaf = true
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).leaf = true
entry({"admin", "services", appname, "autoswitch_add_node"}, call("autoswitch_add_node")).leaf = true
entry({"admin", "services", appname, "autoswitch_remove_node"}, call("autoswitch_remove_node")).leaf = true
entry({"admin", "services", appname, "get_now_use_node"}, call("get_now_use_node")).leaf = true
entry({"admin", "services", appname, "get_redir_log"}, call("get_redir_log")).leaf = true
entry({"admin", "services", appname, "get_log"}, call("get_log")).leaf = true
entry({"admin", "services", appname, "clear_log"}, call("clear_log")).leaf = true
entry({"admin", "services", appname, "status"}, call("status")).leaf = true
entry({"admin", "services", appname, "haproxy_status"}, call("haproxy_status")).leaf = true
entry({"admin", "services", appname, "socks_status"}, call("socks_status")).leaf = true
entry({"admin", "services", appname, "connect_status"}, call("connect_status")).leaf = true
entry({"admin", "services", appname, "ping_node"}, call("ping_node")).leaf = true
entry({"admin", "services", appname, "urltest_node"}, call("urltest_node")).leaf = true
entry({"admin", "services", appname, "set_node"}, call("set_node")).leaf = true
entry({"admin", "services", appname, "copy_node"}, call("copy_node")).leaf = true
entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
--[[Components update]]
entry({"admin", "services", appname, "check_passwall"}, call("app_check")).leaf = true
local coms = require "luci.passwall.com"
local com
for com, _ in pairs(coms) do
entry({"admin", "services", appname, "check_" .. com}, call("com_check", com)).leaf = true
entry({"admin", "services", appname, "update_" .. com}, call("com_update", com)).leaf = true
end
end
local function http_write_json(content)
http.prepare_content("application/json")
http.write_json(content or {code = 1})
end
function reset_config()
luci.sys.call('/etc/init.d/passwall stop')
luci.sys.call('[ -f "/usr/share/passwall/0_default_config" ] && cp -f /usr/share/passwall/0_default_config /etc/config/passwall')
luci.http.redirect(api.url())
end
function show_menu()
luci.sys.call("touch /etc/config/passwall_show")
luci.sys.call("rm -rf /tmp/luci-*")
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
luci.http.redirect(api.url())
end
function hide_menu()
luci.sys.call("rm -rf /etc/config/passwall_show")
luci.sys.call("rm -rf /tmp/luci-*")
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
luci.http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
end
function link_add_node()
local lfile = "/tmp/links.conf"
local link = luci.http.formvalue("link")
luci.sys.call('echo \'' .. link .. '\' > ' .. lfile)
luci.sys.call("lua /usr/share/passwall/subscribe.lua add log")
end
function autoswitch_add_node()
local key = luci.http.formvalue("key")
if key and key ~= "" then
local new_list = ucic:get(appname, "@auto_switch[0]", "tcp_node") or {}
for i = #new_list, 1, -1 do
if (ucic:get(appname, new_list[i], "remarks") or ""):find(key) then
table.remove(new_list, i)
end
end
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and e["remark"]:find(key) then
table.insert(new_list, e.id)
end
end
ucic:set_list(appname, "@auto_switch[0]", "tcp_node", new_list)
ucic:commit(appname)
end
luci.http.redirect(api.url("auto_switch"))
end
function autoswitch_remove_node()
local key = luci.http.formvalue("key")
if key and key ~= "" then
local new_list = ucic:get(appname, "@auto_switch[0]", "tcp_node") or {}
for i = #new_list, 1, -1 do
if (ucic:get(appname, new_list[i], "remarks") or ""):find(key) then
table.remove(new_list, i)
end
end
ucic:set_list(appname, "@auto_switch[0]", "tcp_node", new_list)
ucic:commit(appname)
end
luci.http.redirect(api.url("auto_switch"))
end
function get_now_use_node()
local e = {}
local data, code, msg = nixio.fs.readfile("/tmp/etc/passwall/id/TCP")
if data then
e["TCP"] = util.trim(data)
end
local data, code, msg = nixio.fs.readfile("/tmp/etc/passwall/id/UDP")
if data then
e["UDP"] = util.trim(data)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function get_redir_log()
local proto = luci.http.formvalue("proto")
proto = proto:upper()
if proto == "UDP" and (ucic:get(appname, "@global[0]", "udp_node") or "nil") == "tcp" and not nixio.fs.access("/tmp/etc/passwall/" .. proto .. ".log") then
proto = "TCP"
end
if nixio.fs.access("/tmp/etc/passwall/" .. proto .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall/" .. proto .. ".log")
content = content:gsub("\n", "<br />")
luci.http.write(content)
else
luci.http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
end
end
function get_log()
-- luci.sys.exec("[ -f /tmp/log/passwall.log ] && sed '1!G;h;$!d' /tmp/log/passwall.log > /tmp/log/passwall_show.log")
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall.log' ] && cat /tmp/log/passwall.log"))
end
function clear_log()
luci.sys.call("echo '' > /tmp/log/passwall.log")
end
function status()
-- local dns_mode = ucic:get(appname, "@global[0]", "dns_mode")
local e = {}
e.dns_mode_status = luci.sys.call("netstat -apn | grep ':15353 ' >/dev/null") == 0
e.haproxy_status = luci.sys.call(string.format("top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0
e["tcp_node_status"] = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep -i 'TCP' >/dev/null", appname)) == 0
if (ucic:get(appname, "@global[0]", "udp_node") or "nil") == "tcp" then
e["udp_node_status"] = e["tcp_node_status"]
else
e["udp_node_status"] = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep -i 'UDP' >/dev/null", appname)) == 0
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function haproxy_status()
local e = luci.sys.call(string.format("top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function socks_status()
local e = {}
local index = luci.http.formvalue("index")
local id = luci.http.formvalue("id")
e.index = index
e.socks_status = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep 'SOCKS_' > /dev/null", appname, id)) == 0
local use_http = ucic:get(appname, id, "http_port") or 0
e.use_http = 0
if tonumber(use_http) > 0 then
e.use_http = 1
e.http_status = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", appname, id)) == 0
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function connect_status()
local e = {}
e.use_time = ""
local url = luci.http.formvalue("url")
local result = luci.sys.exec('curl --connect-timeout 3 -o /dev/null -I -sk -w "%{http_code}:%{time_starttransfer}" ' .. url)
local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0")
if code ~= 0 then
local use_time = luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $2}'")
if use_time:find("%.") then
e.use_time = string.format("%.2f", use_time * 1000)
else
e.use_time = string.format("%.2f", use_time / 1000)
end
e.ping_type = "curl"
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function ping_node()
local index = luci.http.formvalue("index")
local address = luci.http.formvalue("address")
local port = luci.http.formvalue("port")
local e = {}
e.index = index
local nodes_ping = ucic:get(appname, "@global_other[0]", "nodes_ping") or ""
if nodes_ping:find("tcping") and luci.sys.exec("echo -n $(command -v tcping)") ~= "" then
if api.is_ipv6(address) then
address = api.get_ipv6_only(address)
end
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, address))
end
if e.ping == nil or tonumber(e.ping) == 0 then
e.ping = luci.sys.exec("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % address)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function urltest_node()
local index = luci.http.formvalue("index")
local id = luci.http.formvalue("id")
local e = {}
e.index = index
local result = luci.sys.exec(string.format("/usr/share/passwall/test.sh url_test_node %s %s", id, "urltest_node"))
local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0")
if code ~= 0 then
local use_time = luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $2}'")
if use_time:find("%.") then
e.use_time = string.format("%.2f", use_time * 1000)
else
e.use_time = string.format("%.2f", use_time / 1000)
end
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function set_node()
local protocol = luci.http.formvalue("protocol")
local section = luci.http.formvalue("section")
ucic:set(appname, "@global[0]", protocol .. "_node", section)
ucic:commit(appname)
luci.sys.call("/etc/init.d/passwall restart > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
function copy_node()
local section = luci.http.formvalue("section")
local uuid = api.gen_uuid()
ucic:section(appname, "nodes", uuid)
for k, v in pairs(ucic:get_all(appname, section)) do
local filter = k:find("%.")
if filter and filter == 1 then
else
xpcall(function()
ucic:set(appname, uuid, k, v)
end,
function(e)
end)
end
end
ucic:delete(appname, uuid, "add_from")
ucic:set(appname, uuid, "add_mode", 1)
ucic:commit(appname)
luci.http.redirect(api.url("node_config", uuid))
end
function clear_all_nodes()
ucic:set(appname, '@global[0]', "enabled", "0")
ucic:set(appname, '@global[0]', "tcp_node", "nil")
ucic:set(appname, '@global[0]', "udp_node", "nil")
ucic:set_list(appname, "@auto_switch[0]", "tcp_node", {})
ucic:foreach(appname, "socks", function(t)
ucic:delete(appname, t[".name"])
end)
ucic:foreach(appname, "haproxy_config", function(t)
ucic:delete(appname, t[".name"])
end)
ucic:foreach(appname, "acl_rule", function(t)
ucic:set(appname, t[".name"], "tcp_node", "default")
ucic:set(appname, t[".name"], "udp_node", "default")
end)
ucic:foreach(appname, "nodes", function(node)
ucic:delete(appname, node['.name'])
end)
ucic:commit(appname)
luci.sys.call("/etc/init.d/" .. appname .. " stop")
end
function delete_select_nodes()
local ids = luci.http.formvalue("ids")
local auto_switch_tcp_node_list = ucic:get(appname, "@auto_switch[0]", "tcp_node") or {}
string.gsub(ids, '[^' .. "," .. ']+', function(w)
for i = #auto_switch_tcp_node_list, 1, -1 do
if w == auto_switch_tcp_node_list[i] then
table.remove(auto_switch_tcp_node_list, i)
end
end
ucic:set_list(appname, "@auto_switch[0]", "tcp_node", auto_switch_tcp_node_list)
if (ucic:get(appname, "@global[0]", "tcp_node") or "nil") == w then
ucic:set(appname, '@global[0]', "tcp_node", "nil")
end
if (ucic:get(appname, "@global[0]", "udp_node") or "nil") == w then
ucic:set(appname, '@global[0]', "udp_node", "nil")
end
ucic:foreach(appname, "socks", function(t)
if t["node"] == w then
ucic:delete(appname, t[".name"])
end
end)
ucic:foreach(appname, "haproxy_config", function(t)
if t["lbss"] == w then
ucic:delete(appname, t[".name"])
end
end)
ucic:foreach(appname, "acl_rule", function(t)
if t["tcp_node"] == w then
ucic:set(appname, t[".name"], "tcp_node", "default")
end
if t["udp_node"] == w then
ucic:set(appname, t[".name"], "udp_node", "default")
end
end)
ucic:delete(appname, w)
end)
ucic:commit(appname)
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
end
function update_rules()
local update = luci.http.formvalue("update")
luci.sys.call("lua /usr/share/passwall/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &")
http_write_json()
end
function server_user_status()
local e = {}
e.index = luci.http.formvalue("index")
e.status = luci.sys.call(string.format("top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", luci.http.formvalue("id"))) == 0
http_write_json(e)
end
function server_user_log()
local id = luci.http.formvalue("id")
if nixio.fs.access("/tmp/etc/passwall_server/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall_server/" .. id .. ".log")
content = content:gsub("\n", "<br />")
luci.http.write(content)
else
luci.http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
end
end
function server_get_log()
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall_server.log' ] && cat /tmp/log/passwall_server.log"))
end
function server_clear_log()
luci.sys.call("echo '' > /tmp/log/passwall_server.log")
end
function app_check()
local json = api.to_check_self()
http_write_json(json)
end
function com_check(comname)
local json = api.to_check("",comname)
http_write_json(json)
end
function com_update(comname)
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = api.to_extract(comname, http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = api.to_move(comname, http.formvalue("file"))
else
json = api.to_download(comname, http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end

View File

@ -0,0 +1,130 @@
local api = require "luci.passwall.api"
local appname = api.appname
local sys = api.sys
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
m = Map(appname)
s = m:section(TypedSection, "global", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
s.anonymous = true
o = s:option(Flag, "acl_enable", translate("Main switch"))
o.rmempty = false
o.default = false
local global_proxy_mode = (m:get("@global[0]", "tcp_proxy_mode") or "") .. (m:get("@global[0]", "udp_proxy_mode") or "")
-- [[ ACLs Settings ]]--
s = m:section(TypedSection, "acl_rule")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
s.extedit = api.url("acl_config", "%s")
function s.create(e, t)
t = TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function s.remove(e, t)
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_" .. t .. "*")
TypedSection.remove(e, t)
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Remarks
o = s:option(Value, "remarks", translate("Remarks"))
o.rmempty = true
local mac_t = {}
sys.net.mac_hints(function(e, t)
mac_t[e] = {
ip = t,
mac = e
}
end)
o = s:option(DummyValue, "sources", translate("Source"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local e = ''
local v = Value.cfgvalue(t, n) or ''
string.gsub(v, '[^' .. " " .. ']+', function(w)
local a = w
if mac_t[w] then
a = a .. ' (' .. mac_t[w].ip .. ')'
end
if #e > 0 then
e = e .. "<br />"
end
e = e .. a
end)
return e
end
---- TCP Proxy Mode
tcp_proxy_mode = s:option(ListValue, "tcp_proxy_mode", "TCP " .. translate("Proxy Mode"))
tcp_proxy_mode.default = "default"
tcp_proxy_mode.rmempty = false
tcp_proxy_mode:value("default", translate("Default"))
tcp_proxy_mode:value("disable", translate("No Proxy"))
tcp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
tcp_proxy_mode:value("returnhome", translate("China List"))
else
tcp_proxy_mode:value("gfwlist", translate("GFW List"))
tcp_proxy_mode:value("chnroute", translate("Not China List"))
end
tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
---- UDP Proxy Mode
udp_proxy_mode = s:option(ListValue, "udp_proxy_mode", "UDP " .. translate("Proxy Mode"))
udp_proxy_mode.default = "default"
udp_proxy_mode.rmempty = false
udp_proxy_mode:value("default", translate("Default"))
udp_proxy_mode:value("disable", translate("No Proxy"))
udp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
udp_proxy_mode:value("returnhome", translate("China List"))
else
udp_proxy_mode:value("gfwlist", translate("GFW List"))
udp_proxy_mode:value("chnroute", translate("Not China List"))
end
udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
--[[
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
---- TCP Redir Ports
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o.default = "default"
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
o:value("80,443", "80,443")
o:value("80:65535", "80 " .. translate("or more"))
o:value("1:443", "443 " .. translate("or less"))
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o.default = "default"
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
o:value("53", "53")
]]--
return m

View File

@ -0,0 +1,329 @@
local api = require "luci.passwall.api"
local appname = api.appname
local sys = api.sys
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
m = Map(appname)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local global_proxy_mode = (m:get("@global[0]", "tcp_proxy_mode") or "") .. (m:get("@global[0]", "udp_proxy_mode") or "")
local dynamicList_write = function(self, section, value)
local t = {}
local t2 = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
else
t = { value }
end
t = table.concat(t, " ")
return DynamicList.write(self, section, t)
end
-- [[ ACLs Settings ]]--
s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs"))
s.addremove = false
s.dynamic = false
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Remarks
o = s:option(Value, "remarks", translate("Remarks"))
o.default = arg[1]
o.rmempty = true
local mac_t = {}
sys.net.mac_hints(function(e, t)
mac_t[#mac_t + 1] = {
ip = t,
mac = e
}
end)
table.sort(mac_t, function(a,b)
if #a.ip < #b.ip then
return true
elseif #a.ip == #b.ip then
if a.ip < b.ip then
return true
else
return #a.ip < #b.ip
end
end
return false
end)
---- Source
sources = s:option(DynamicList, "sources", translate("Source"))
sources.description = "<ul><li>" .. translate("Example:")
.. "</li><li>" .. translate("MAC") .. ": 00:00:00:FF:FF:FF"
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
.. "</li><li>" .. translate("IP range") .. ": 192.168.1.100-192.168.1.200"
.. "</li><li>" .. translate("IPSet") .. ": ipset:lanlist"
.. "</li></ul>"
sources.cast = "string"
for _, key in pairs(mac_t) do
sources:value(key.mac, "%s (%s)" % {key.mac, key.ip})
end
sources.cfgvalue = function(self, section)
local value
if self.tag_error[section] then
value = self:formvalue(section)
else
value = self.map:get(section, self.option)
if type(value) == "string" then
local value2 = {}
string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
value = value2
end
end
return value
end
sources.validate = function(self, value, t)
local err = {}
for _, v in ipairs(value) do
local flag = false
if v:find("ipset:") and v:find("ipset:") == 1 then
local ipset = v:gsub("ipset:", "")
if ipset and ipset ~= "" then
flag = true
end
end
if flag == false and datatypes.macaddr(v) then
flag = true
end
if flag == false and datatypes.ip4addr(v) then
flag = true
end
if flag == false and api.iprange(v) then
flag = true
end
if flag == false then
err[#err + 1] = v
end
end
if #err > 0 then
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
for _, v in ipairs(err) do
self:add_error(t, "invalid", v)
end
end
return value
end
sources.write = dynamicList_write
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
---- TCP Proxy Drop Ports
o = s:option(Value, "tcp_proxy_drop_ports", translate("TCP Proxy Drop Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
---- UDP Proxy Drop Ports
o = s:option(Value, "udp_proxy_drop_ports", translate("UDP Proxy Drop Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("80,443", translate("QUIC"))
---- TCP Redir Ports
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o.default = "default"
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
o:value("80,443", "80,443")
o:value("80:65535", "80 " .. translate("or more"))
o:value("1:443", "443 " .. translate("or less"))
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o.default = "default"
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
o:value("53", "53")
---- TCP Proxy Mode
tcp_proxy_mode = s:option(ListValue, "tcp_proxy_mode", "TCP " .. translate("Proxy Mode"))
tcp_proxy_mode.default = "default"
tcp_proxy_mode.rmempty = false
tcp_proxy_mode:value("default", translate("Default"))
tcp_proxy_mode:value("disable", translate("No Proxy"))
tcp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
tcp_proxy_mode:value("returnhome", translate("China List"))
else
tcp_proxy_mode:value("gfwlist", translate("GFW List"))
tcp_proxy_mode:value("chnroute", translate("Not China List"))
end
tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
---- UDP Proxy Mode
udp_proxy_mode = s:option(ListValue, "udp_proxy_mode", "UDP " .. translate("Proxy Mode"))
udp_proxy_mode.default = "default"
udp_proxy_mode.rmempty = false
udp_proxy_mode:value("default", translate("Default"))
udp_proxy_mode:value("disable", translate("No Proxy"))
udp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
udp_proxy_mode:value("returnhome", translate("China List"))
else
udp_proxy_mode:value("gfwlist", translate("GFW List"))
udp_proxy_mode:value("chnroute", translate("Not China List"))
end
udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
tcp_node = s:option(ListValue, "tcp_node", "<a style='color: red'>" .. translate("TCP Node") .. "</a>")
tcp_node.default = "default"
tcp_node:value("default", translate("Default"))
udp_node = s:option(ListValue, "udp_node", "<a style='color: red'>" .. translate("UDP Node") .. "</a>")
udp_node.default = "default"
udp_node:value("default", translate("Default"))
udp_node:value("tcp", translate("Same as the tcp node"))
for k, v in pairs(nodes_table) do
tcp_node:value(v.id, v["remark"])
udp_node:value(v.id, v["remark"])
end
o = s:option(Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature."))
o.default = "0"
o:depends({ tcp_node = "default", ['!reverse'] = true })
---- DNS Forward Mode
o = s:option(ListValue, "dns_mode", translate("Filter Mode"))
o:depends({ tcp_node = "default", ['!reverse'] = true })
if api.is_finded("dns2socks") then
o:value("dns2socks", "dns2socks")
end
if has_xray then
o:value("xray", "Xray")
end
o = s:option(ListValue, "v2ray_dns_mode", " ")
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:depends("dns_mode", "xray")
---- DNS Forward
o = s:option(Value, "remote_dns", translate("Remote DNS"))
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
o:value("8.8.4.4", "8.8.4.4 (Google)")
o:value("8.8.8.8", "8.8.8.8 (Google)")
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
o:depends("dns_mode", "dns2socks")
o:depends("v2ray_dns_mode", "tcp")
if has_v2ray or has_xray then
o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH"))
o:value("https://1.1.1.1/dns-query", "CloudFlare")
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
o:value("https://8.8.4.4/dns-query", "Google 8844")
o:value("https://8.8.8.8/dns-query", "Google 8888")
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended")
o:value("https://208.67.222.222/dns-query", "OpenDNS")
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
o.default = "https://1.1.1.1/dns-query"
o.validate = function(self, value, t)
if value ~= "" then
value = api.trim(value)
local flag = 0
local util = require "luci.util"
local val = util.split(value, ",")
local url = val[1]
val[1] = nil
for i = 1, #val do
local v = val[i]
if v then
if not api.datatypes.ipmask4(v) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
end
o:depends("v2ray_dns_mode", "doh")
end
o = s:option(Value, "dns_client_ip", translate("EDNS Client Subnet"))
o.datatype = "ipaddr"
o:depends("v2ray_dns_mode", "doh")
if api.is_finded("chinadns-ng") then
o = s:option(Flag, "chinadns_ng", translate("ChinaDNS-NG"), translate("The effect is better, but will increase the memory."))
o.default = "0"
o:depends({ tcp_proxy_mode = "gfwlist", dns_mode = "dns2socks"})
o:depends({ tcp_proxy_mode = "gfwlist", dns_mode = "xray"})
o:depends({ tcp_proxy_mode = "chnroute", dns_mode = "dns2socks"})
o:depends({ tcp_proxy_mode = "chnroute", dns_mode = "xray"})
end
if has_chnlist then
when_chnroute_default_dns = s:option(ListValue, "when_chnroute_default_dns", translate("When using the chnroute list the default DNS"))
when_chnroute_default_dns.default = "direct"
when_chnroute_default_dns:value("remote", translate("Remote DNS"))
when_chnroute_default_dns:value("direct", translate("Direct DNS"))
when_chnroute_default_dns.description = "<ul>"
.. "<li>" .. translate("Remote DNS can avoid more DNS leaks, but some domestic domain names maybe to proxy!") .. "</li>"
.. "<li>" .. translate("Direct DNS Internet experience may be better, but DNS will be leaked!") .. "</li>"
.. "</ul>"
local _depends = {
{ dns_mode = "dns2socks" },
{ dns_mode = "xray" }
}
for i, d in ipairs(_depends) do
d["tcp_proxy_mode"] = "chnroute"
if api.is_finded("chinadns-ng") then
d["chinadns_ng"] = false
end
when_chnroute_default_dns:depends(d)
end
end
return m

View File

@ -0,0 +1,28 @@
local api = require "luci.passwall.api"
local appname = api.appname
m = Map(appname)
-- [[ App Settings ]]--
s = m:section(TypedSection, "global_app", translate("App Update"),
"<font color='red'>" ..
translate("Please confirm that your firmware supports FPU.") ..
"</font>")
s.anonymous = true
s:append(Template(appname .. "/app_update/app_version"))
local k, v
local com = require "luci.passwall.com"
for k, v in pairs(com) do
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name))
o.default = v.default_path or ("/usr/bin/" .. k)
o.rmempty = false
end
o = s:option(DummyValue, "tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
end
return m

View File

@ -0,0 +1,66 @@
local api = require "luci.passwall.api"
local appname = api.appname
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
m = Map(appname)
-- [[ Auto Switch Settings ]]--
s = m:section(TypedSection, "auto_switch")
s.anonymous = true
---- Enable
o = s:option(Flag, "enable", translate("Enable"))
o.default = 0
o.rmempty = false
o = s:option(Value, "testing_time", translate("How often to test"), translate("Units:minutes"))
o.datatype = "uinteger"
o.default = 1
o = s:option(Value, "connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
o.datatype = "uinteger"
o.default = 3
o = s:option(Value, "retry_num", translate("Timeout retry num"))
o.datatype = "uinteger"
o.default = 3
o = s:option(DynamicList, "tcp_node", "TCP " .. translate("List of backup nodes"))
for k, v in pairs(nodes_table) do
if v.node_type == "normal" then
o:value(v.id, v["remark"])
end
end
function o.write(self, section, value)
local t = {}
local t2 = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
else
t = { value }
end
return DynamicList.write(self, section, t)
end
o = s:option(Flag, "restore_switch", "TCP " .. translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
o = s:option(ListValue, "shunt_logic", "TCP " .. translate("If the main node is V2ray/Xray shunt"))
o:value("0", translate("Switch it"))
o:value("1", translate("Applying to the default node"))
o:value("2", translate("Applying to the preproxy node"))
m:append(Template(appname .. "/auto_switch/footer"))
return m

View File

@ -0,0 +1,564 @@
local api = require "luci.passwall.api"
local appname = api.appname
local uci = api.uci
local datatypes = api.datatypes
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
m = Map(appname)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local tcp_socks_server = "127.0.0.1" .. ":" .. (uci:get(appname, "@global[0]", "tcp_node_socks_port") or "1070")
local socks_table = {}
socks_table[#socks_table + 1] = {
id = tcp_socks_server,
remarks = tcp_socks_server .. " - " .. translate("TCP Node")
}
uci:foreach(appname, "socks", function(s)
if s.enabled == "1" and s.node then
local id, remarks
for k, n in pairs(nodes_table) do
if (s.node == n.id) then
remarks = n["remark"]; break
end
end
id = "127.0.0.1" .. ":" .. s.port
socks_table[#socks_table + 1] = {
id = id,
remarks = id .. " - " .. (remarks or translate("Misconfigured"))
}
end
end)
local doh_validate = function(self, value, t)
if value ~= "" then
value = api.trim(value)
local flag = 0
local util = require "luci.util"
local val = util.split(value, ",")
local url = val[1]
val[1] = nil
for i = 1, #val do
local v = val[i]
if v then
if not datatypes.ipmask4(v) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
end
local redir_mode_validate = function(self, value, t)
local tcp_proxy_mode_v = tcp_proxy_mode:formvalue(t) or ""
local udp_proxy_mode_v = udp_proxy_mode:formvalue(t) or ""
local localhost_tcp_proxy_mode_v = localhost_tcp_proxy_mode:formvalue(t) or ""
local localhost_udp_proxy_mode_v = localhost_udp_proxy_mode:formvalue(t) or ""
local s = tcp_proxy_mode_v .. udp_proxy_mode_v .. localhost_tcp_proxy_mode_v .. localhost_udp_proxy_mode_v
if s:find("returnhome") then
if s:find("chnroute") or s:find("gfwlist") then
return nil, translate("China list or gfwlist cannot be used together with outside China list!")
end
end
return value
end
m:append(Template(appname .. "/global/status"))
s = m:section(TypedSection, "global")
s.anonymous = true
s.addremove = false
s:tab("Main", translate("Main"))
-- [[ Global Settings ]]--
o = s:taboption("Main", Flag, "enabled", translate("Main switch"))
o.rmempty = false
local auto_switch_tip
local shunt_remark
local current_tcp_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/TCP' ] && echo -n $(cat /tmp/etc/%s/id/TCP)", appname, appname))
if current_tcp_node and current_tcp_node ~= "" and current_tcp_node ~= "nil" then
local n = uci:get_all(appname, current_tcp_node)
if n then
if tonumber(m:get("@auto_switch[0]", "enable") or 0) == 1 then
if n.protocol == "_shunt" then
local shunt_logic = tonumber(m:get("@auto_switch[0]", "shunt_logic"))
if shunt_logic == 1 or shunt_logic == 2 then
if shunt_logic == 1 then
shunt_remark = "default"
elseif shunt_logic == 2 then
shunt_remark = "main"
end
current_tcp_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/TCP_%s' ] && echo -n $(cat /tmp/etc/%s/id/TCP_%s)", appname, shunt_remark, appname, shunt_remark))
if current_tcp_node and current_tcp_node ~= "" and current_tcp_node ~= "nil" then
n = uci:get_all(appname, current_tcp_node)
end
end
end
if n then
local remarks = api.get_node_remarks(n)
local url = api.url("node_config", n[".name"])
auto_switch_tip = translatef("Current node: %s", string.format('<a href="%s">%s</a>', url, remarks)) .. "<br />"
end
end
end
end
---- TCP Node
tcp_node = s:taboption("Main", ListValue, "tcp_node", "<a style='color: red'>" .. translate("TCP Node") .. "</a>")
tcp_node:value("nil", translate("Close"))
if not shunt_remark and auto_switch_tip then
tcp_node.description = auto_switch_tip
end
---- UDP Node
udp_node = s:taboption("Main", ListValue, "udp_node", "<a style='color: red'>" .. translate("UDP Node") .. "</a>")
udp_node:value("nil", translate("Close"))
udp_node:value("tcp", translate("Same as the tcp node"))
-- 分流
if (has_v2ray or has_xray) and #nodes_table > 0 then
local normal_list = {}
local balancing_list = {}
local shunt_list = {}
for k, v in pairs(nodes_table) do
if v.node_type == "normal" then
normal_list[#normal_list + 1] = v
end
if v.protocol and v.protocol == "_balancing" then
balancing_list[#balancing_list + 1] = v
end
if v.protocol and v.protocol == "_shunt" then
shunt_list[#shunt_list + 1] = v
end
end
local function get_cfgvalue(shunt_node_id, option)
return function(self, section)
return m:get(shunt_node_id, option) or "nil"
end
end
local function get_write(shunt_node_id, option)
return function(self, section, value)
m:set(shunt_node_id, option, value)
end
end
if #normal_list > 0 then
for k, v in pairs(shunt_list) do
local vid = v.id:sub(1, 8)
-- shunt node type, V2ray or Xray
local type = s:taboption("Main", ListValue, vid .. "-type", translate("Type"))
if has_v2ray then
type:value("V2ray", translate("V2ray"))
end
if has_xray then
type:value("Xray", translate("Xray"))
end
type.cfgvalue = get_cfgvalue(v.id, "type")
type.write = get_write(v.id, "type")
-- pre-proxy
o = s:taboption("Main", Flag, vid .. "-preproxy_enabled", translate("Preproxy"))
o:depends("tcp_node", v.id)
o.rmempty = false
o.cfgvalue = get_cfgvalue(v.id, "preproxy_enabled")
o.write = get_write(v.id, "preproxy_enabled")
o = s:taboption("Main", Value, vid .. "-main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
o:depends(vid .. "-preproxy_enabled", "1")
for k1, v1 in pairs(balancing_list) do
o:value(v1.id, v1.remark)
end
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1.remark)
end
o.cfgvalue = get_cfgvalue(v.id, "main_node")
o.write = get_write(v.id, "main_node")
if shunt_remark == "main" and auto_switch_tip then
o.description = auto_switch_tip
end
if (has_v2ray and has_xray) or (v.type == "V2ray" and not has_v2ray) or (v.type == "Xray" and not has_xray) then
type:depends("tcp_node", v.id)
else
type:depends("tcp_node", "hide") --不存在的依赖,即始终隐藏
end
uci:foreach(appname, "shunt_rules", function(e)
local id = e[".name"]
local node_option = vid .. "-" .. id .. "_node"
if id and e.remarks then
o = s:taboption("Main", Value, node_option, string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", id), e.remarks))
o.cfgvalue = get_cfgvalue(v.id, id)
o.write = get_write(v.id, id)
o:depends("tcp_node", v.id)
o:value("nil", translate("Close"))
o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
pt.cfgvalue = get_cfgvalue(v.id, id .. "_proxy_tag")
pt.write = get_write(v.id, id .. "_proxy_tag")
pt:value("nil", translate("Close"))
pt:value("main", translate("Preproxy Node"))
pt.default = "nil"
for k1, v1 in pairs(balancing_list) do
o:value(v1.id, v1.remark)
end
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1.remark)
pt:depends({ [node_option] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
end
end
end)
local id = "default_node"
o = s:taboption("Main", Value, vid .. "-" .. id, string.format('* <a style="color:red">%s</a>', translate("Default")))
o.cfgvalue = get_cfgvalue(v.id, id)
o.write = get_write(v.id, id)
o:depends("tcp_node", v.id)
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
for k1, v1 in pairs(balancing_list) do
o:value(v1.id, v1.remark)
end
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1.remark)
end
if shunt_remark == "default" and auto_switch_tip then
o.description = auto_switch_tip
end
local id = "default_proxy_tag"
o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
o.cfgvalue = get_cfgvalue(v.id, id)
o.write = get_write(v.id, id)
o:value("nil", translate("Close"))
o:value("main", translate("Preproxy Node"))
for k1, v1 in pairs(normal_list) do
if v1.protocol ~= "_balancing" then
o:depends({ [vid .. "-default_node"] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
end
end
end
else
local tips = s:taboption("Main", DummyValue, "tips", " ")
tips.rawhtml = true
tips.cfgvalue = function(t, n)
return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first."))
end
tips:depends({ tcp_node = "nil", ["!reverse"] = true })
for k, v in pairs(shunt_list) do
tips:depends("udp_node", v.id)
end
for k, v in pairs(balancing_list) do
tips:depends("udp_node", v.id)
end
end
end
tcp_node_socks_port = s:taboption("Main", Value, "tcp_node_socks_port", translate("TCP Node") .. " Socks " .. translate("Listen Port"))
tcp_node_socks_port.default = 1070
tcp_node_socks_port.datatype = "port"
tcp_node_socks_port:depends({ tcp_node = "nil", ["!reverse"] = true })
--[[
if has_v2ray or has_xray then
tcp_node_http_port = s:taboption("Main", Value, "tcp_node_http_port", translate("TCP Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
tcp_node_http_port.default = 0
tcp_node_http_port.datatype = "port"
end
]]--
s:tab("DNS", translate("DNS"))
o = s:taboption("DNS", Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature."))
o.default = "0"
---- DNS Forward Mode
dns_mode = s:taboption("DNS", ListValue, "dns_mode", translate("Filter Mode"))
dns_mode.rmempty = false
dns_mode:reset_values()
if api.is_finded("dns2tcp") then
dns_mode:value("dns2tcp", translatef("Requery DNS By %s", "TCP"))
end
if api.is_finded("dns2socks") then
dns_mode:value("dns2socks", "dns2socks")
end
if has_xray then
dns_mode:value("xray", "Xray")
end
dns_mode:value("udp", translatef("Requery DNS By %s", "UDP"))
o = s:taboption("DNS", ListValue, "v2ray_dns_mode", " ")
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("fakedns", "FakeDNS")
o:depends("dns_mode", "xray")
o.validate = function(self, value, t)
if value == "fakedns" then
local _dns_mode = dns_mode:formvalue(t)
local _tcp_node = tcp_node:formvalue(t)
if m:get(_tcp_node, "type"):lower() ~= _dns_mode then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
return value
end
o = s:taboption("DNS", Value, "socks_server", translate("Socks Server"), translate("Make sure socks service is available on this address."))
for k, v in pairs(socks_table) do o:value(v.id, v.remarks) end
o.default = socks_table[1].id
o.validate = function(self, value, t)
if not datatypes.ipaddrport(value) then
return nil, translate("Socks Server") .. " " .. translate("Not valid IP format, please re-enter!")
end
return value
end
o:depends({dns_mode = "dns2socks"})
---- DNS Forward
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
o.datatype = "or(ipaddr,ipaddrport)"
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
o:value("8.8.4.4", "8.8.4.4 (Google)")
o:value("8.8.8.8", "8.8.8.8 (Google)")
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "dns2tcp"})
o:depends({dns_mode = "udp"})
o:depends({v2ray_dns_mode = "tcp"})
---- DoH
o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
o.default = "https://1.1.1.1/dns-query"
o:value("https://1.1.1.1/dns-query", "CloudFlare")
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
o:value("https://8.8.4.4/dns-query", "Google 8844")
o:value("https://8.8.8.8/dns-query", "Google 8888")
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended")
o:value("https://208.67.222.222/dns-query", "OpenDNS")
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
o.validate = doh_validate
o:depends("v2ray_dns_mode", "doh")
o = s:taboption("DNS", Value, "dns_client_ip", translate("EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o:depends("v2ray_dns_mode", "tcp")
o:depends("v2ray_dns_mode", "doh")
o = s:taboption("DNS", Flag, "dns_cache", translate("Cache Resolved"))
o.default = "1"
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "tcp"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "doh"})
o.rmempty = false
if api.is_finded("chinadns-ng") then
o = s:taboption("DNS", Flag, "chinadns_ng", translate("ChinaDNS-NG"), translate("The effect is better, but will increase the memory."))
o.default = "0"
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "dns2tcp"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "tcp"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "doh"})
o:depends({dns_mode = "udp"})
end
if has_chnlist then
when_chnroute_default_dns = s:taboption("DNS", ListValue, "when_chnroute_default_dns", translate("When using the chnroute list the default DNS"))
when_chnroute_default_dns.default = "direct"
when_chnroute_default_dns:value("remote", translate("Remote DNS"))
when_chnroute_default_dns:value("direct", translate("Direct DNS"))
when_chnroute_default_dns.description = "<ul>"
.. "<li>" .. translate("Remote DNS can avoid more DNS leaks, but some domestic domain names maybe to proxy!") .. "</li>"
.. "<li>" .. translate("Direct DNS Internet experience may be better, but DNS will be leaked!") .. "</li>"
.. "</ul>"
if api.is_finded("chinadns-ng") then
when_chnroute_default_dns:depends("chinadns_ng", false)
end
end
o = s:taboption("DNS", Button, "clear_ipset", translate("Clear IPSET"), translate("Try this feature if the rule modification does not take effect."))
o.inputstyle = "remove"
function o.write(e, e)
luci.sys.call("[ -n \"$(nft list sets 2>/dev/null | grep \"gfwlist\")\" ] && sh /usr/share/" .. appname .. "/nftables.sh flush_nftset || sh /usr/share/" .. appname .. "/iptables.sh flush_ipset > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
s:tab("Proxy", translate("Mode"))
---- TCP Default Proxy Mode
tcp_proxy_mode = s:taboption("Proxy", ListValue, "tcp_proxy_mode", "TCP " .. translate("Default Proxy Mode"))
tcp_proxy_mode:value("disable", translate("No Proxy"))
tcp_proxy_mode:value("global", translate("Global Proxy"))
tcp_proxy_mode:value("gfwlist", translate("GFW List"))
tcp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
tcp_proxy_mode:value("returnhome", translate("China List"))
end
tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
tcp_proxy_mode.default = "chnroute"
--tcp_proxy_mode.validate = redir_mode_validate
---- UDP Default Proxy Mode
udp_proxy_mode = s:taboption("Proxy", ListValue, "udp_proxy_mode", "UDP " .. translate("Default Proxy Mode"))
udp_proxy_mode:value("disable", translate("No Proxy"))
udp_proxy_mode:value("global", translate("Global Proxy"))
udp_proxy_mode:value("gfwlist", translate("GFW List"))
udp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
udp_proxy_mode:value("returnhome", translate("China List"))
end
udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
udp_proxy_mode.default = "chnroute"
--udp_proxy_mode.validate = redir_mode_validate
---- Localhost TCP Proxy Mode
localhost_tcp_proxy_mode = s:taboption("Proxy", ListValue, "localhost_tcp_proxy_mode", translate("Router Localhost") .. " TCP " .. translate("Proxy Mode"))
localhost_tcp_proxy_mode:value("default", translatef("Same as the %s default proxy mode", "TCP"))
localhost_tcp_proxy_mode:value("global", translate("Global Proxy"))
localhost_tcp_proxy_mode:value("gfwlist", translate("GFW List"))
localhost_tcp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
localhost_tcp_proxy_mode:value("returnhome", translate("China List"))
end
localhost_tcp_proxy_mode:value("disable", translate("No Proxy"))
localhost_tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
localhost_tcp_proxy_mode.default = "default"
--localhost_tcp_proxy_mode.validate = redir_mode_validate
---- Localhost UDP Proxy Mode
localhost_udp_proxy_mode = s:taboption("Proxy", ListValue, "localhost_udp_proxy_mode", translate("Router Localhost") .. " UDP " .. translate("Proxy Mode"))
localhost_udp_proxy_mode:value("default", translatef("Same as the %s default proxy mode", "UDP"))
localhost_udp_proxy_mode:value("global", translate("Global Proxy"))
localhost_udp_proxy_mode:value("gfwlist", translate("GFW List"))
localhost_udp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
localhost_udp_proxy_mode:value("returnhome", translate("China List"))
end
localhost_udp_proxy_mode:value("disable", translate("No Proxy"))
localhost_udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
localhost_udp_proxy_mode.default = "default"
localhost_udp_proxy_mode.validate = redir_mode_validate
tips = s:taboption("Proxy", DummyValue, "tips", " ")
tips.rawhtml = true
tips.cfgvalue = function(t, n)
return string.format('<a style="color: red" href="%s">%s</a>', api.url("acl"), translate("Want different devices to use different proxy modes/ports/nodes? Please use access control."))
end
s:tab("log", translate("Log"))
o = s:taboption("log", Flag, "close_log_tcp", translatef("%s Node Log Close", "TCP"))
o.rmempty = false
o = s:taboption("log", Flag, "close_log_udp", translatef("%s Node Log Close", "UDP"))
o.rmempty = false
loglevel = s:taboption("log", ListValue, "loglevel", "V2ray/Xray " .. translate("Log Level"))
loglevel.default = "warning"
loglevel:value("debug")
loglevel:value("info")
loglevel:value("warning")
loglevel:value("error")
trojan_loglevel = s:taboption("log", ListValue, "trojan_loglevel", "Trojan " .. translate("Log Level"))
trojan_loglevel.default = "2"
trojan_loglevel:value("0", "all")
trojan_loglevel:value("1", "info")
trojan_loglevel:value("2", "warn")
trojan_loglevel:value("3", "error")
trojan_loglevel:value("4", "fatal")
o = s:taboption("log", Flag, "advanced_log_feature", translate("Advanced log feature"), translate("For professionals only."))
o.default = "0"
o.rmempty = false
local syslog = s:taboption("log", Flag, "sys_log", translate("Logging to system log"), translate("Logging to the system log for more advanced functions. For example, send logs to a dedicated log server."))
syslog:depends("advanced_log_feature", "1")
syslog.default = "0"
syslog.rmempty = false
local logpath = s:taboption("log", Value, "persist_log_path", translate("Persist log file directory"), translate("The path to the directory used to store persist log files, the \"/\" at the end can be omitted. Leave it blank to disable this feature."))
logpath:depends({ ["advanced_log_feature"] = 1, ["sys_log"] = 0 })
s:tab("faq", "FAQ")
o = s:taboption("faq", DummyValue, "")
o.template = appname .. "/global/faq"
-- [[ Socks Server ]]--
o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch"))
o.rmempty = false
s = m:section(TypedSection, "socks", translate("Socks Config"))
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
function s.create(e, t)
TypedSection.create(e, api.gen_uuid())
end
o = s:option(DummyValue, "status", translate("Status"))
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<div class="_status" socks_id="%s"></div>', n)
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
socks_node = s:option(ListValue, "node", translate("Socks Node"))
local n = 1
uci:foreach(appname, "socks", function(s)
if s[".name"] == section then
return false
end
n = n + 1
end)
o = s:option(Value, "port", "Socks " .. translate("Listen Port"))
o.default = n + 1080
o.datatype = "port"
o.rmempty = false
if has_v2ray or has_xray then
o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
o.default = 0
o.datatype = "port"
end
for k, v in pairs(nodes_table) do
tcp_node:value(v.id, v["remark"])
udp_node:value(v.id, v["remark"])
if v.type == "Socks" then
if has_v2ray or has_xray then
socks_node:value(v.id, v["remark"])
end
else
socks_node:value(v.id, v["remark"])
end
end
m:append(Template(appname .. "/global/footer"))
return m

View File

@ -0,0 +1,140 @@
local api = require "luci.passwall.api"
local appname = api.appname
local sys = api.sys
local net = require "luci.model.network".init()
local datatypes = api.datatypes
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
obj = e,
remarks = e["remark"]
}
end
end
m = Map(appname)
-- [[ Haproxy Settings ]]--
s = m:section(TypedSection, "global_haproxy")
s.anonymous = true
s:append(Template(appname .. "/haproxy/status"))
---- Balancing Enable
o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing"))
o.rmempty = false
o.default = false
---- Console Username
o = s:option(Value, "console_user", translate("Console Username"))
o.default = ""
o:depends("balancing_enable", true)
---- Console Password
o = s:option(Value, "console_password", translate("Console Password"))
o.password = true
o.default = ""
o:depends("balancing_enable", true)
---- Console Port
o = s:option(Value, "console_port", translate("Console Port"), translate(
"In the browser input routing IP plus port access, such as:192.168.1.1:1188"))
o.default = "1188"
o:depends("balancing_enable", true)
---- Health Check Type
o = s:option(ListValue, "health_check_type", translate("Health Check Type"))
o.default = "passwall_logic"
o:value("tcp", "TCP")
o:value("passwall_logic", translate("Availability test") .. string.format("(passwall %s)", translate("Inner implement")))
o:depends("balancing_enable", true)
---- Health Check Inter
o = s:option(Value, "health_check_inter", translate("Health Check Inter"), translate("Units:seconds"))
o.default = "60"
o:depends("balancing_enable", true)
o = s:option(DummyValue, "health_check_tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<span style="color: red">%s</span>', translate("When the availability test is used, the load balancing node will be converted into a Socks node. when node list set customizing, must be a Socks node, otherwise the health check will be invalid."))
end
o:depends("health_check_type", "passwall_logic")
-- [[ Balancing Settings ]]--
s = m:section(TypedSection, "haproxy_config", "",
"<font color='red'>" ..
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") ..
"\n" .. translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") ..
"</font>")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
s.create = function(e, t)
TypedSection.create(e, api.gen_uuid())
end
s.remove = function(self, section)
for k, v in pairs(self.children) do
v.rmempty = true
v.validate = nil
end
TypedSection.remove(self, section)
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Node Address
o = s:option(Value, "lbss", translate("Node Address"))
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
o.rmempty = false
o.validate = function(self, value)
if not value then return nil end
local t = m:get(value) or nil
if t and t[".type"] == "nodes" then
return value
end
if datatypes.hostport(value) or datatypes.ip4addrport(value) then
return value
end
if api.is_ipv6addrport(value) then
return value
end
return nil, value
end
---- Haproxy Port
o = s:option(Value, "haproxy_port", translate("Haproxy Port"))
o.datatype = "port"
o.default = 1181
o.rmempty = false
---- Node Weight
o = s:option(Value, "lbweight", translate("Node Weight"))
o.datatype = "uinteger"
o.default = 5
o.rmempty = false
---- Export
o = s:option(ListValue, "export", translate("Export Of Multi WAN"))
o:value(0, translate("Auto"))
local wa = require "luci.tools.webadmin"
wa.cbi_add_networks(o)
o.default = 0
o.rmempty = false
---- Mode
o = s:option(ListValue, "backup", translate("Mode"))
o:value(0, translate("Primary"))
o:value(1, translate("Standby"))
o.rmempty = false
return m

View File

@ -0,0 +1,8 @@
local api = require "luci.passwall.api"
local appname = api.appname
f = SimpleForm(appname)
f.reset = false
f.submit = false
f:append(Template(appname .. "/log/log"))
return f

View File

@ -0,0 +1,992 @@
local api = require "luci.passwall.api"
local appname = api.appname
local uci = api.uci
if not arg[1] or not uci:get(appname, arg[1]) then
luci.http.redirect(api.url("node_list"))
end
local ss_encrypt_method_list = {
"rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
"aes-192-ctr", "aes-256-ctr", "bf-cfb", "salsa20", "chacha20", "chacha20-ietf",
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
}
local ss_rust_encrypt_method_list = {
"plain", "none",
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
}
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
"chacha20-ietf"
}
local ssr_protocol_list = {
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
"auth_chain_d", "auth_chain_e", "auth_chain_f"
}
local ssr_obfs_list = {
"plain", "http_simple", "http_post", "random_head", "tls_simple",
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
local v_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
}
local x_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
}
local encrypt_methods_ss_aead = {
"chacha20-ietf-poly1305",
"aes-128-gcm",
"aes-256-gcm",
}
m = Map(appname, translate("Node Config"))
m.redirect = api.url()
s = m:section(NamedSection, arg[1], "nodes", "")
s.addremove = false
s.dynamic = false
share = s:option(DummyValue, "passwall", " ")
share.rawhtml = true
share.template = "passwall/node_list/link_share_man"
share.value = arg[1]
remarks = s:option(Value, "remarks", translate("Node Remarks"))
remarks.default = translate("Remarks")
remarks.rmempty = false
type = s:option(ListValue, "type", translate("Type"))
if api.is_finded("ipt2socks") then
type:value("Socks", translate("Socks"))
end
if api.is_finded("ss-redir") then
type:value("SS", translate("Shadowsocks Libev"))
end
if api.is_finded("sslocal") then
type:value("SS-Rust", translate("Shadowsocks Rust"))
end
if api.is_finded("ssr-redir") then
type:value("SSR", translate("ShadowsocksR Libev"))
end
if api.is_finded("v2ray") then
type:value("V2ray", translate("V2ray"))
end
if api.is_finded("xray") then
type:value("Xray", translate("Xray"))
end
if api.is_finded("brook") then
type:value("Brook", translate("Brook"))
end
--[[
if api.is_finded("trojan-plus") or api.is_finded("trojan") then
type:value("Trojan", translate("Trojan"))
end
]]--
if api.is_finded("trojan-plus") then
type:value("Trojan-Plus", translate("Trojan-Plus"))
end
if api.is_finded("trojan-go") then
type:value("Trojan-Go", translate("Trojan-Go"))
end
if api.is_finded("naive") then
type:value("Naiveproxy", translate("NaiveProxy"))
end
if api.is_finded("hysteria") then
type:value("Hysteria", translate("Hysteria"))
end
protocol = s:option(ListValue, "protocol", translate("Protocol"))
protocol:value("vmess", translate("Vmess"))
protocol:value("vless", translate("VLESS"))
protocol:value("http", translate("HTTP"))
protocol:value("socks", translate("Socks"))
protocol:value("shadowsocks", translate("Shadowsocks"))
protocol:value("trojan", translate("Trojan"))
protocol:value("wireguard", translate("WireGuard"))
protocol:value("_balancing", translate("Balancing"))
protocol:value("_shunt", translate("Shunt"))
protocol:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
protocol:depends("type", "V2ray")
protocol:depends("type", "Xray")
iface = s:option(Value, "iface", translate("Interface"))
iface.default = "eth1"
iface:depends("protocol", "_iface")
local nodes_table = {}
local balancers_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
remarks = e["remark"]
}
end
if e.protocol == "_balancing" then
balancers_table[#balancers_table + 1] = {
id = e[".name"],
remarks = e["remark"]
}
end
end
-- 负载均衡列表
local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>"))
for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end
balancing_node:depends("protocol", "_balancing")
local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
balancingStrategy:depends("protocol", "_balancing")
balancingStrategy:value("random")
balancingStrategy:value("leastPing")
balancingStrategy.default = "random"
-- 探测地址
local useCustomProbeUrl = s:option(Flag, "useCustomProbeUrl", translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
useCustomProbeUrl:depends("balancingStrategy", "leastPing")
local probeUrl = s:option(Value, "probeUrl", translate("Probe URL"))
probeUrl:depends("useCustomProbeUrl", true)
probeUrl.default = "https://www.google.com/generate_204"
probeUrl.description = translate("The URL used to detect the connection status.")
-- 探测间隔
local probeInterval = s:option(Value, "probeInterval", translate("Probe Interval"))
probeInterval:depends("balancingStrategy", "leastPing")
probeInterval.default = "1m"
probeInterval.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>ns</code>, <code>us</code>, <code>ms</code>, <code>s</code>, <code>m</code>, <code>h</code>, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.")
-- 分流
if #nodes_table > 0 then
o = s:option(Flag, "preproxy_enabled", translate("Preproxy"))
o:depends("protocol", "_shunt")
o = s:option(Value, "main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
o:depends("preproxy_enabled", "1")
for k, v in pairs(balancers_table) do
o:value(v.id, v.remarks)
end
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
end
o.default = "nil"
end
uci:foreach(appname, "shunt_rules", function(e)
if e[".name"] and e.remarks then
o = s:option(Value, e[".name"], string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks))
o:value("nil", translate("Close"))
o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
o:depends("protocol", "_shunt")
if #nodes_table > 0 then
for k, v in pairs(balancers_table) do
o:value(v.id, v.remarks)
end
local pt = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
pt:value("nil", translate("Close"))
pt:value("main", translate("Preproxy Node"))
pt.default = "nil"
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
pt:depends({ preproxy_enabled = "1", [e[".name"]] = v.id })
end
end
end
end)
shunt_tips = s:option(DummyValue, "shunt_tips", " ")
shunt_tips.rawhtml = true
shunt_tips.cfgvalue = function(t, n)
return string.format('<a style="color: red" href="../rule">%s</a>', translate("No shunt rules? Click me to go to add."))
end
shunt_tips:depends("protocol", "_shunt")
local default_node = s:option(Value, "default_node", string.format('* <a style="color:red">%s</a>', translate("Default")))
default_node:depends("protocol", "_shunt")
default_node:value("_direct", translate("Direct Connection"))
default_node:value("_blackhole", translate("Blackhole"))
if #nodes_table > 0 then
for k, v in pairs(balancers_table) do
default_node:value(v.id, v.remarks)
end
local dpt = s:option(ListValue, "default_proxy_tag", string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
dpt:value("nil", translate("Close"))
dpt:value("main", translate("Preproxy Node"))
dpt.default = "nil"
for k, v in pairs(nodes_table) do
default_node:value(v.id, v.remarks)
dpt:depends({ preproxy_enabled = "1", default_node = v.id })
end
end
domainStrategy = s:option(ListValue, "domainStrategy", translate("Domain Strategy"))
domainStrategy:value("AsIs")
domainStrategy:value("IPIfNonMatch")
domainStrategy:value("IPOnDemand")
domainStrategy.default = "IPOnDemand"
domainStrategy.description = "<br /><ul><li>" .. translate("'AsIs': Only use domain for routing. Default value.")
.. "</li><li>" .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
.. "</li><li>" .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
.. "</li></ul>"
domainStrategy:depends("protocol", "_shunt")
domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher"))
domainMatcher:value("hybrid")
domainMatcher:value("linear")
domainMatcher:depends("protocol", "_shunt")
-- Brook协议
brook_protocol = s:option(ListValue, "brook_protocol", translate("Protocol"))
brook_protocol:value("client", translate("Brook"))
brook_protocol:value("wsclient", translate("WebSocket"))
brook_protocol:depends("type", "Brook")
function brook_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function brook_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
brook_tls = s:option(Flag, "brook_tls", translate("Use TLS"))
brook_tls:depends("brook_protocol", "wsclient")
-- Naiveproxy协议
naiveproxy_protocol = s:option(ListValue, "naiveproxy_protocol", translate("Protocol"))
naiveproxy_protocol:value("https", translate("HTTPS"))
naiveproxy_protocol:value("quic", translate("QUIC"))
naiveproxy_protocol:depends("type", "Naiveproxy")
function naiveproxy_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function naiveproxy_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
address = s:option(Value, "address", translate("Address (Support Domain Name)"))
address.rmempty = false
address:depends("type", "Socks")
address:depends("type", "SS")
address:depends("type", "SS-Rust")
address:depends("type", "SSR")
address:depends("type", "Brook")
address:depends("type", "Trojan")
address:depends("type", "Trojan-Plus")
address:depends("type", "Trojan-Go")
address:depends("type", "Naiveproxy")
address:depends("type", "Hysteria")
address:depends({ type = "V2ray", protocol = "vmess" })
address:depends({ type = "V2ray", protocol = "vless" })
address:depends({ type = "V2ray", protocol = "http" })
address:depends({ type = "V2ray", protocol = "socks" })
address:depends({ type = "V2ray", protocol = "shadowsocks" })
address:depends({ type = "V2ray", protocol = "trojan" })
address:depends({ type = "Xray", protocol = "vmess" })
address:depends({ type = "Xray", protocol = "vless" })
address:depends({ type = "Xray", protocol = "http" })
address:depends({ type = "Xray", protocol = "socks" })
address:depends({ type = "Xray", protocol = "shadowsocks" })
address:depends({ type = "Xray", protocol = "trojan" })
address:depends({ type = "Xray", protocol = "wireguard" })
port = s:option(Value, "port", translate("Port"))
port.datatype = "port"
port.rmempty = false
port:depends("type", "Socks")
port:depends("type", "SS")
port:depends("type", "SS-Rust")
port:depends("type", "SSR")
port:depends("type", "Brook")
port:depends("type", "Trojan")
port:depends("type", "Trojan-Plus")
port:depends("type", "Trojan-Go")
port:depends("type", "Naiveproxy")
port:depends("type", "Hysteria")
port:depends({ type = "V2ray", protocol = "vmess" })
port:depends({ type = "V2ray", protocol = "vless" })
port:depends({ type = "V2ray", protocol = "http" })
port:depends({ type = "V2ray", protocol = "socks" })
port:depends({ type = "V2ray", protocol = "shadowsocks" })
port:depends({ type = "V2ray", protocol = "trojan" })
port:depends({ type = "Xray", protocol = "vmess" })
port:depends({ type = "Xray", protocol = "vless" })
port:depends({ type = "Xray", protocol = "http" })
port:depends({ type = "Xray", protocol = "socks" })
port:depends({ type = "Xray", protocol = "shadowsocks" })
port:depends({ type = "Xray", protocol = "trojan" })
port:depends({ type = "Xray", protocol = "wireguard" })
hysteria_hop = s:option(Value, "hysteria_hop", translate("Additional ports for hysteria hop"))
hysteria_hop:depends("type", "Hysteria")
username = s:option(Value, "username", translate("Username"))
username:depends("type", "Socks")
username:depends("type", "Naiveproxy")
username:depends({ type = "V2ray", protocol = "http" })
username:depends({ type = "V2ray", protocol = "socks" })
username:depends({ type = "Xray", protocol = "http" })
username:depends({ type = "Xray", protocol = "socks" })
password = s:option(Value, "password", translate("Password"))
password.password = true
password:depends("type", "Socks")
password:depends("type", "SS")
password:depends("type", "SS-Rust")
password:depends("type", "SSR")
password:depends("type", "Brook")
password:depends("type", "Trojan")
password:depends("type", "Trojan-Plus")
password:depends("type", "Trojan-Go")
password:depends("type", "Naiveproxy")
password:depends({ type = "V2ray", protocol = "http" })
password:depends({ type = "V2ray", protocol = "socks" })
password:depends({ type = "V2ray", protocol = "shadowsocks" })
password:depends({ type = "V2ray", protocol = "trojan" })
password:depends({ type = "Xray", protocol = "http" })
password:depends({ type = "Xray", protocol = "socks" })
password:depends({ type = "Xray", protocol = "shadowsocks" })
password:depends({ type = "Xray", protocol = "trojan" })
hysteria_protocol = s:option(ListValue, "hysteria_protocol", translate("Protocol"))
hysteria_protocol:value("udp", "UDP")
hysteria_protocol:value("faketcp", "faketcp")
hysteria_protocol:value("wechat-video", "wechat-video")
hysteria_protocol:depends("type", "Hysteria")
function hysteria_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function hysteria_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
hysteria_obfs = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
hysteria_obfs:depends("type", "Hysteria")
hysteria_auth_type = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
hysteria_auth_type:value("disable", translate("Disable"))
hysteria_auth_type:value("string", translate("STRING"))
hysteria_auth_type:value("base64", translate("BASE64"))
hysteria_auth_type:depends("type", "Hysteria")
hysteria_auth_password = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
hysteria_auth_password.password = true
hysteria_auth_password:depends("hysteria_auth_type", "string")
hysteria_auth_password:depends("hysteria_auth_type", "base64")
hysteria_alpn = s:option(Value, "hysteria_alpn", translate("QUIC TLS ALPN"))
hysteria_alpn:depends("type", "Hysteria")
ss_encrypt_method = s:option(Value, "ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ss_encrypt_method_list) do ss_encrypt_method:value(t) end
ss_encrypt_method:depends("type", "SS")
function ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
ss_rust_encrypt_method = s:option(Value, "ss_rust_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ss_rust_encrypt_method_list) do ss_rust_encrypt_method:value(t) end
ss_rust_encrypt_method:depends("type", "SS-Rust")
function ss_rust_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ss_rust_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
ssr_encrypt_method = s:option(Value, "ssr_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ssr_encrypt_method_list) do ssr_encrypt_method:value(t) end
ssr_encrypt_method:depends("type", "SSR")
function ssr_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ssr_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
security = s:option(ListValue, "security", translate("Encrypt Method"))
for a, t in ipairs(security_list) do security:value(t) end
security:depends({ type = "V2ray", protocol = "vmess" })
security:depends({ type = "Xray", protocol = "vmess" })
encryption = s:option(Value, "encryption", translate("Encrypt Method"))
encryption.default = "none"
encryption:value("none")
encryption:depends({ type = "V2ray", protocol = "vless" })
encryption:depends({ type = "Xray", protocol = "vless" })
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(v_ss_encrypt_method_list) do v_ss_encrypt_method:value(t) end
v_ss_encrypt_method:depends({ type = "V2ray", protocol = "shadowsocks" })
function v_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function v_ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
x_ss_encrypt_method = s:option(ListValue, "x_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(x_ss_encrypt_method_list) do x_ss_encrypt_method:value(t) end
x_ss_encrypt_method:depends({ type = "Xray", protocol = "shadowsocks" })
function x_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function x_ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
iv_check = s:option(Flag, "iv_check", translate("IV Check"))
iv_check:depends({ type = "V2ray", protocol = "shadowsocks" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "aes-128-gcm" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "aes-256-gcm" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "chacha20-poly1305" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "xchacha20-poly1305" })
uot = s:option(Flag, "uot", translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side."))
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-aes-128-gcm" })
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-aes-256-gcm" })
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-chacha20-poly1305" })
ssr_protocol = s:option(Value, "ssr_protocol", translate("Protocol"))
for a, t in ipairs(ssr_protocol_list) do ssr_protocol:value(t) end
ssr_protocol:depends("type", "SSR")
function ssr_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function ssr_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
protocol_param = s:option(Value, "protocol_param", translate("Protocol_param"))
protocol_param:depends("type", "SSR")
obfs = s:option(Value, "obfs", translate("Obfs"))
for a, t in ipairs(ssr_obfs_list) do obfs:value(t) end
obfs:depends("type", "SSR")
obfs_param = s:option(Value, "obfs_param", translate("Obfs_param"))
obfs_param:depends("type", "SSR")
timeout = s:option(Value, "timeout", translate("Connection Timeout"))
timeout.datatype = "uinteger"
timeout.default = 300
timeout:depends("type", "SS")
timeout:depends("type", "SS-Rust")
timeout:depends("type", "SSR")
tcp_fast_open = s:option(ListValue, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
tcp_fast_open:value("false")
tcp_fast_open:value("true")
tcp_fast_open:depends("type", "SS")
tcp_fast_open:depends("type", "SS-Rust")
tcp_fast_open:depends("type", "SSR")
tcp_fast_open:depends("type", "Trojan")
tcp_fast_open:depends("type", "Trojan-Plus")
tcp_fast_open:depends("type", "Trojan-Go")
fast_open = s:option(Flag, "fast_open", translate("Fast Open"))
fast_open.default = "0"
fast_open:depends("type", "Hysteria")
ss_plugin = s:option(ListValue, "ss_plugin", translate("plugin"))
ss_plugin:value("none", translate("none"))
if api.is_finded("xray-plugin") then ss_plugin:value("xray-plugin") end
if api.is_finded("v2ray-plugin") then ss_plugin:value("v2ray-plugin") end
if api.is_finded("obfs-local") then ss_plugin:value("obfs-local") end
ss_plugin:depends("type", "SS")
ss_plugin:depends("type", "SS-Rust")
function ss_plugin.cfgvalue(self, section)
return m:get(section, "plugin")
end
function ss_plugin.write(self, section, value)
m:set(section, "plugin", value)
end
ss_plugin_opts = s:option(Value, "ss_plugin_opts", translate("opts"))
ss_plugin_opts:depends("ss_plugin", "xray-plugin")
ss_plugin_opts:depends("ss_plugin", "v2ray-plugin")
ss_plugin_opts:depends("ss_plugin", "obfs-local")
function ss_plugin_opts.cfgvalue(self, section)
return m:get(section, "plugin_opts")
end
function ss_plugin_opts.write(self, section, value)
m:set(section, "plugin_opts", value)
end
uuid = s:option(Value, "uuid", translate("ID"))
uuid.password = true
uuid:depends({ type = "V2ray", protocol = "vmess" })
uuid:depends({ type = "V2ray", protocol = "vless" })
uuid:depends({ type = "Xray", protocol = "vmess" })
uuid:depends({ type = "Xray", protocol = "vless" })
tls = s:option(Flag, "tls", translate("TLS"))
tls.default = 0
tls.validate = function(self, value, t)
if value then
local type = type:formvalue(t) or ""
if value == "0" and (type == "Trojan" or type == "Trojan-Plus") then
return nil, translate("Original Trojan only supported 'tls', please choose 'tls'.")
end
return value
end
end
tls:depends({ type = "V2ray", protocol = "vmess" })
tls:depends({ type = "V2ray", protocol = "vless" })
tls:depends({ type = "V2ray", protocol = "socks" })
tls:depends({ type = "V2ray", protocol = "trojan" })
tls:depends({ type = "V2ray", protocol = "shadowsocks" })
tls:depends({ type = "Xray", protocol = "vmess" })
tls:depends({ type = "Xray", protocol = "vless" })
tls:depends({ type = "Xray", protocol = "socks" })
tls:depends({ type = "Xray", protocol = "trojan" })
tls:depends({ type = "Xray", protocol = "shadowsocks" })
tls:depends("type", "Trojan")
tls:depends("type", "Trojan-Plus")
tls:depends("type", "Trojan-Go")
tlsflow = s:option(Value, "tlsflow", translate("flow"))
tlsflow.default = ""
tlsflow:value("", translate("Disable"))
tlsflow:value("xtls-rprx-vision")
tlsflow:value("xtls-rprx-vision-udp443")
tlsflow:depends({ type = "Xray", protocol = "vless", tls = true, transport = "tcp" })
reality = s:option(Flag, "reality", translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
reality.default = 0
reality:depends({ type = "Xray", tls = true, transport = "tcp" })
reality:depends({ type = "Xray", tls = true, transport = "h2" })
reality:depends({ type = "Xray", tls = true, transport = "grpc" })
alpn = s:option(ListValue, "alpn", translate("alpn"))
alpn.default = "default"
alpn:value("default", translate("Default"))
alpn:value("h2,http/1.1")
alpn:value("h2")
alpn:value("http/1.1")
alpn:depends({ type = "V2ray", tls = true })
alpn:depends({ type = "Xray", tls = true, reality = false })
-- minversion = s:option(Value, "minversion", translate("minversion"))
-- minversion.default = "1.3"
-- minversion:value("1.3")
-- minversion:depends("tls", true)
-- [[ TLS部分 ]] --
tls_sessionTicket = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
tls_sessionTicket.default = "0"
tls_sessionTicket:depends({ type = "Trojan", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Plus", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Go", tls = true })
trojan_go_fingerprint = s:option(ListValue, "trojan_go_fingerprint", translate("Finger Print"))
trojan_go_fingerprint:value("disable", translate("Disable"))
trojan_go_fingerprint:value("firefox")
trojan_go_fingerprint:value("chrome")
trojan_go_fingerprint:value("ios")
trojan_go_fingerprint.default = "disable"
trojan_go_fingerprint:depends({ type = "Trojan-Go", tls = true })
function trojan_go_fingerprint.cfgvalue(self, section)
return m:get(section, "fingerprint")
end
function trojan_go_fingerprint.write(self, section, value)
m:set(section, "fingerprint", value)
end
tls_serverName = s:option(Value, "tls_serverName", translate("Domain"))
tls_serverName:depends("tls", true)
tls_serverName:depends("type", "Hysteria")
tls_allowInsecure = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
tls_allowInsecure.default = "0"
tls_allowInsecure:depends({ tls = true, reality = false })
tls_allowInsecure:depends("type", "Hysteria")
xray_fingerprint = s:option(Value, "xray_fingerprint", translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
xray_fingerprint:value("", translate("Disable"))
xray_fingerprint:value("chrome")
xray_fingerprint:value("firefox")
xray_fingerprint:value("safari")
xray_fingerprint:value("ios")
-- xray_fingerprint:value("android")
xray_fingerprint:value("edge")
-- xray_fingerprint:value("360")
xray_fingerprint:value("qq")
xray_fingerprint:value("random")
xray_fingerprint:value("randomized")
xray_fingerprint.default = ""
xray_fingerprint:depends({ type = "Xray", tls = true, reality = false })
function xray_fingerprint.cfgvalue(self, section)
return m:get(section, "fingerprint")
end
function xray_fingerprint.write(self, section, value)
m:set(section, "fingerprint", value)
end
function xray_fingerprint.remove(self, section)
m:del(section, "fingerprint")
end
-- [[ REALITY部分 ]] --
reality_publicKey = s:option(Value, "reality_publicKey", translate("Public Key"))
reality_publicKey:depends({ type = "Xray", tls = true, reality = true })
reality_shortId = s:option(Value, "reality_shortId", translate("Short Id"))
reality_shortId:depends({ type = "Xray", tls = true, reality = true })
reality_spiderX = s:option(Value, "reality_spiderX", translate("Spider X"))
reality_spiderX.placeholder = "/"
reality_spiderX:depends({ type = "Xray", tls = true, reality = true })
reality_fingerprint = s:option(Value, "reality_fingerprint", translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
reality_fingerprint:value("chrome")
reality_fingerprint:value("firefox")
reality_fingerprint:value("safari")
reality_fingerprint:value("ios")
-- reality_fingerprint:value("android")
reality_fingerprint:value("edge")
-- reality_fingerprint:value("360")
reality_fingerprint:value("qq")
reality_fingerprint:value("random")
reality_fingerprint:value("randomized")
reality_fingerprint.default = "chrome"
reality_fingerprint:depends({ type = "Xray", tls = true, reality = true })
function reality_fingerprint.cfgvalue(self, section)
return m:get(section, "fingerprint")
end
function reality_fingerprint.write(self, section, value)
m:set(section, "fingerprint", value)
end
trojan_transport = s:option(ListValue, "trojan_transport", translate("Transport"))
trojan_transport:value("original", translate("Original"))
trojan_transport:value("ws", "WebSocket")
trojan_transport.default = "original"
trojan_transport:depends("type", "Trojan-Go")
trojan_plugin = s:option(ListValue, "plugin_type", translate("Transport Plugin"))
trojan_plugin:value("plaintext", "Plain Text")
trojan_plugin:value("shadowsocks", "ShadowSocks")
trojan_plugin:value("other", "Other")
trojan_plugin.default = "plaintext"
trojan_plugin:depends({ tls = false, trojan_transport = "original" })
trojan_plugin_cmd = s:option(Value, "plugin_cmd", translate("Plugin Binary"))
trojan_plugin_cmd.placeholder = "eg: /usr/bin/v2ray-plugin"
trojan_plugin_cmd:depends({ plugin_type = "shadowsocks" })
trojan_plugin_cmd:depends({ plugin_type = "other" })
trojan_plugin_op = s:option(Value, "plugin_option", translate("Plugin Option"))
trojan_plugin_op.placeholder = "eg: obfs=http;obfs-host=www.baidu.com"
trojan_plugin_op:depends({ plugin_type = "shadowsocks" })
trojan_plugin_op:depends({ plugin_type = "other" })
trojan_plugin_arg = s:option(DynamicList, "plugin_arg", translate("Plugin Option Args"))
trojan_plugin_arg.placeholder = "eg: [\"-config\", \"test.json\"]"
trojan_plugin_arg:depends({ plugin_type = "shadowsocks" })
trojan_plugin_arg:depends({ plugin_type = "other" })
transport = s:option(ListValue, "transport", translate("Transport"))
transport:value("tcp", "TCP")
transport:value("mkcp", "mKCP")
transport:value("ws", "WebSocket")
transport:value("h2", "HTTP/2")
transport:value("ds", "DomainSocket")
transport:value("quic", "QUIC")
transport:value("grpc", "gRPC")
transport:depends({ type = "V2ray", protocol = "vmess" })
transport:depends({ type = "V2ray", protocol = "vless" })
transport:depends({ type = "V2ray", protocol = "socks" })
transport:depends({ type = "V2ray", protocol = "shadowsocks" })
transport:depends({ type = "V2ray", protocol = "trojan" })
transport:depends({ type = "Xray", protocol = "vmess" })
transport:depends({ type = "Xray", protocol = "vless" })
transport:depends({ type = "Xray", protocol = "socks" })
transport:depends({ type = "Xray", protocol = "shadowsocks" })
transport:depends({ type = "Xray", protocol = "trojan" })
--[[
ss_transport = s:option(ListValue, "ss_transport", translate("Transport"))
ss_transport:value("ws", "WebSocket")
ss_transport:value("h2", "HTTP/2")
ss_transport:value("h2+ws", "HTTP/2 & WebSocket")
ss_transport:depends({ type = "V2ray", protocol = "shadowsocks" })
ss_transport:depends({ type = "Xray", protocol = "shadowsocks" })
]]--
wireguard_public_key = s:option(Value, "wireguard_public_key", translate("Public Key"))
wireguard_public_key:depends({ type = "Xray", protocol = "wireguard" })
wireguard_secret_key = s:option(Value, "wireguard_secret_key", translate("Private Key"))
wireguard_secret_key:depends({ type = "Xray", protocol = "wireguard" })
wireguard_preSharedKey = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
wireguard_preSharedKey:depends({ type = "Xray", protocol = "wireguard" })
wireguard_local_address = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
wireguard_local_address:depends({ type = "Xray", protocol = "wireguard" })
wireguard_mtu = s:option(Value, "wireguard_mtu", translate("MTU"))
wireguard_mtu.default = "1420"
wireguard_mtu:depends({ type = "Xray", protocol = "wireguard" })
if api.compare_versions(api.get_app_version("xray"), ">=", "1.8.0") then
wireguard_reserved = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
wireguard_reserved:depends({ type = "Xray", protocol = "wireguard" })
end
wireguard_keepAlive = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
wireguard_keepAlive.default = "0"
wireguard_keepAlive:depends({ type = "Xray", protocol = "wireguard" })
-- [[ TCP部分 ]]--
-- TCP伪装
tcp_guise = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
tcp_guise:value("none", "none")
tcp_guise:value("http", "http")
tcp_guise:depends("transport", "tcp")
-- HTTP域名
tcp_guise_http_host = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
tcp_guise_http_host:depends("tcp_guise", "http")
-- HTTP路径
tcp_guise_http_path = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
tcp_guise_http_path.placeholder = "/"
tcp_guise_http_path:depends("tcp_guise", "http")
-- [[ mKCP部分 ]]--
mkcp_guise = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
for a, t in ipairs(header_type_list) do mkcp_guise:value(t) end
mkcp_guise:depends("transport", "mkcp")
mkcp_mtu = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
mkcp_mtu.default = "1350"
mkcp_mtu:depends("transport", "mkcp")
mkcp_tti = s:option(Value, "mkcp_tti", translate("KCP TTI"))
mkcp_tti.default = "20"
mkcp_tti:depends("transport", "mkcp")
mkcp_uplinkCapacity = s:option(Value, "mkcp_uplinkCapacity", translate("KCP uplinkCapacity"))
mkcp_uplinkCapacity.default = "5"
mkcp_uplinkCapacity:depends("transport", "mkcp")
mkcp_downlinkCapacity = s:option(Value, "mkcp_downlinkCapacity", translate("KCP downlinkCapacity"))
mkcp_downlinkCapacity.default = "20"
mkcp_downlinkCapacity:depends("transport", "mkcp")
mkcp_congestion = s:option(Flag, "mkcp_congestion", translate("KCP Congestion"))
mkcp_congestion:depends("transport", "mkcp")
mkcp_readBufferSize = s:option(Value, "mkcp_readBufferSize", translate("KCP readBufferSize"))
mkcp_readBufferSize.default = "1"
mkcp_readBufferSize:depends("transport", "mkcp")
mkcp_writeBufferSize = s:option(Value, "mkcp_writeBufferSize", translate("KCP writeBufferSize"))
mkcp_writeBufferSize.default = "1"
mkcp_writeBufferSize:depends("transport", "mkcp")
mkcp_seed = s:option(Value, "mkcp_seed", translate("KCP Seed"))
mkcp_seed:depends("transport", "mkcp")
-- [[ WebSocket部分 ]]--
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
ws_host:depends("transport", "ws")
ws_host:depends("ss_transport", "ws")
ws_host:depends("trojan_transport", "ws")
ws_path = s:option(Value, "ws_path", translate("WebSocket Path"))
ws_path.placeholder = "/"
ws_path:depends("transport", "ws")
ws_path:depends("ss_transport", "ws")
ws_path:depends("trojan_transport", "ws")
ws_path:depends({ type = "Brook", brook_protocol = "wsclient" })
ws_enableEarlyData = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
ws_enableEarlyData:depends({ type = "V2ray", transport = "ws" })
ws_maxEarlyData = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
ws_maxEarlyData.default = "1024"
ws_maxEarlyData:depends("ws_enableEarlyData", true)
ws_earlyDataHeaderName = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
ws_earlyDataHeaderName:depends("ws_enableEarlyData", true)
-- [[ HTTP/2部分 ]]--
h2_host = s:option(Value, "h2_host", translate("HTTP/2 Host"))
h2_host:depends("transport", "h2")
h2_host:depends("ss_transport", "h2")
h2_path = s:option(Value, "h2_path", translate("HTTP/2 Path"))
h2_path.placeholder = "/"
h2_path:depends("transport", "h2")
h2_path:depends("ss_transport", "h2")
h2_health_check = s:option(Flag, "h2_health_check", translate("Health check"))
h2_health_check:depends({ type = "Xray", transport = "h2" })
h2_read_idle_timeout = s:option(Value, "h2_read_idle_timeout", translate("Idle timeout"))
h2_read_idle_timeout.default = "10"
h2_read_idle_timeout:depends("h2_health_check", true)
h2_health_check_timeout = s:option(Value, "h2_health_check_timeout", translate("Health check timeout"))
h2_health_check_timeout.default = "15"
h2_health_check_timeout:depends("h2_health_check", true)
-- [[ DomainSocket部分 ]]--
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
ds_path:depends("transport", "ds")
-- [[ QUIC部分 ]]--
quic_security = s:option(ListValue, "quic_security", translate("Encrypt Method"))
quic_security:value("none")
quic_security:value("aes-128-gcm")
quic_security:value("chacha20-poly1305")
quic_security:depends("transport", "quic")
quic_key = s:option(Value, "quic_key", translate("Encrypt Method") .. translate("Key"))
quic_key:depends("transport", "quic")
quic_guise = s:option(ListValue, "quic_guise", translate("Camouflage Type"))
for a, t in ipairs(header_type_list) do quic_guise:value(t) end
quic_guise:depends("transport", "quic")
-- [[ gRPC部分 ]]--
grpc_serviceName = s:option(Value, "grpc_serviceName", "ServiceName")
grpc_serviceName:depends("transport", "grpc")
grpc_mode = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
grpc_mode:value("gun")
grpc_mode:value("multi")
grpc_mode:depends({ type = "Xray", transport = "grpc" })
grpc_health_check = s:option(Flag, "grpc_health_check", translate("Health check"))
grpc_health_check:depends({ type = "Xray", transport = "grpc" })
grpc_idle_timeout = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
grpc_idle_timeout.default = "10"
grpc_idle_timeout:depends("grpc_health_check", true)
grpc_health_check_timeout = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
grpc_health_check_timeout.default = "20"
grpc_health_check_timeout:depends("grpc_health_check", true)
grpc_permit_without_stream = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
grpc_permit_without_stream.default = "0"
grpc_permit_without_stream:depends("grpc_health_check", true)
grpc_initial_windows_size = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
grpc_initial_windows_size.default = "0"
grpc_initial_windows_size:depends({ type = "Xray", transport = "grpc" })
-- [[ Trojan-Go Shadowsocks2 ]] --
ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption"))
ss_aead:depends("type", "Trojan-Go")
ss_aead.default = "0"
ss_aead_method = s:option(ListValue, "ss_aead_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_ss_aead) do ss_aead_method:value(v, v) end
ss_aead_method.default = "aes-128-gcm"
ss_aead_method:depends("ss_aead", "1")
ss_aead_pwd = s:option(Value, "ss_aead_pwd", translate("Password"))
ss_aead_pwd.password = true
ss_aead_pwd:depends("ss_aead", "1")
-- [[ Trojan-Go Mux ]]--
mux = s:option(Flag, "smux", translate("Smux"))
mux:depends("type", "Trojan-Go")
-- [[ Mux ]]--
mux = s:option(Flag, "mux", translate("Mux"))
mux:depends({ type = "V2ray", protocol = "vmess" })
mux:depends({ type = "V2ray", protocol = "vless" })
mux:depends({ type = "V2ray", protocol = "http" })
mux:depends({ type = "V2ray", protocol = "socks" })
mux:depends({ type = "V2ray", protocol = "shadowsocks" })
mux:depends({ type = "V2ray", protocol = "trojan" })
mux:depends({ type = "Xray", protocol = "vmess" })
mux:depends({ type = "Xray", protocol = "vless", tlsflow = "" })
mux:depends({ type = "Xray", protocol = "http" })
mux:depends({ type = "Xray", protocol = "socks" })
mux:depends({ type = "Xray", protocol = "shadowsocks" })
mux:depends({ type = "Xray", protocol = "trojan" })
-- [[ XUDP Mux ]]--
xmux = s:option(Flag, "xmux", translate("Mux"))
xmux.default = 1
xmux:depends({ type = "Xray", protocol = "vless", tlsflow = "xtls-rprx-vision" })
xmux:depends({ type = "Xray", protocol = "vless", tlsflow = "xtls-rprx-vision-udp443" })
mux_concurrency = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
mux_concurrency.default = 8
mux_concurrency:depends("mux", true)
mux_concurrency:depends("smux", true)
xudp_concurrency = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency"))
xudp_concurrency.default = 8
xudp_concurrency:depends("xmux", true)
smux_idle_timeout = s:option(Value, "smux_idle_timeout", translate("Mux idle timeout"))
smux_idle_timeout.default = 60
smux_idle_timeout:depends("smux", true)
hysteria_up_mbps = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
hysteria_up_mbps.default = "10"
hysteria_up_mbps:depends("type", "Hysteria")
hysteria_down_mbps = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
hysteria_down_mbps.default = "50"
hysteria_down_mbps:depends("type", "Hysteria")
hysteria_recv_window_conn = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
hysteria_recv_window_conn:depends("type", "Hysteria")
hysteria_recv_window = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
hysteria_recv_window:depends("type", "Hysteria")
hysteria_handshake_timeout = s:option(Value, "hysteria_handshake_timeout", translate("Handshake Timeout"))
hysteria_handshake_timeout:depends("type", "Hysteria")
hysteria_idle_timeout = s:option(Value, "hysteria_idle_timeout", translate("Idle Timeout"))
hysteria_idle_timeout:depends("type", "Hysteria")
hysteria_hop_interval = s:option(Value, "hysteria_hop_interval", translate("Hop Interval"))
hysteria_hop_interval:depends("type", "Hysteria")
hysteria_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
hysteria_disable_mtu_discovery:depends("type", "Hysteria")
hysteria_lazy_start = s:option(Flag, "hysteria_lazy_start", translate("Lazy Start"))
hysteria_lazy_start:depends("type", "Hysteria")
protocol.validate = function(self, value)
if value == "_shunt" or value == "_balancing" then
address.rmempty = true
port.rmempty = true
end
return value
end
return m

View File

@ -0,0 +1,151 @@
local api = require "luci.passwall.api"
local appname = api.appname
local sys = api.sys
local datatypes = api.datatypes
m = Map(appname)
-- [[ Other Settings ]]--
s = m:section(TypedSection, "global_other")
s.anonymous = true
o = s:option(MultiValue, "nodes_ping", " ")
o:value("auto_ping", translate("Auto Ping"), translate("This will automatically ping the node for latency"))
o:value("tcping", translate("Tcping"), translate("This will use tcping replace ping detection of node"))
o:value("info", translate("Show server address and port"), translate("Show server address and port"))
-- [[ Add the node via the link ]]--
s:append(Template(appname .. "/node_list/link_add_node"))
local nodes_ping = m:get("@global_other[0]", "nodes_ping") or ""
-- [[ Node List ]]--
s = m:section(TypedSection, "nodes")
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_config", "%s")
function s.create(e, t)
local uuid = api.gen_uuid()
t = uuid
TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function s.remove(e, t)
m.uci:foreach(appname, "socks", function(s)
if s["node"] == t then
m:del(s[".name"])
end
end)
m.uci:foreach(appname, "haproxy_config", function(s)
if s["lbss"] and s["lbss"] == t then
m:del(s[".name"])
end
end)
m.uci:foreach(appname, "acl_rule", function(s)
if s["tcp_node"] and s["tcp_node"] == t then
m:set(s[".name"], "tcp_node", "default")
end
if s["udp_node"] and s["udp_node"] == t then
m:set(s[".name"], "udp_node", "default")
end
end)
for k, v in ipairs(m:get("@auto_switch[0]", "tcp_node") or {}) do
if v and v == t then
sys.call(string.format("uci -q del_list %s.@auto_switch[0].tcp_node='%s'", appname, v))
end
end
TypedSection.remove(e, t)
local new_node = "nil"
local node0 = m:get("@nodes[0]") or nil
if node0 then
new_node = node0[".name"]
end
if (m:get("@global[0]", "tcp_node") or "nil") == t then
m:set('@global[0]', "tcp_node", new_node)
end
if (m:get("@global[0]", "udp_node") or "nil") == t then
m:set('@global[0]', "udp_node", new_node)
end
end
s.sortable = true
-- 简洁模式
o = s:option(DummyValue, "add_from", "")
o.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n)
if v and v ~= '' then
local group = m:get(n, "group") or ""
if group ~= "" then
v = v .. " " .. group
end
return v
else
return ''
end
end
o = s:option(DummyValue, "remarks", translate("Remarks"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local str = ""
local is_sub = m:get(n, "is_sub") or ""
local group = m:get(n, "group") or ""
local remarks = m:get(n, "remarks") or ""
local type = m:get(n, "type") or ""
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.type' value='%s'/>", appname, n, type)
if type == "V2ray" or type == "Xray" then
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
protocol = translate("Balancing")
elseif protocol == "_shunt" then
protocol = translate("Shunt")
elseif protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
else
protocol = protocol:gsub("^%l",string.upper)
end
type = type .. " " .. protocol
end
local address = m:get(n, "address") or ""
local port = m:get(n, "port") or ""
str = str .. translate(type) .. "" .. remarks
if address ~= "" and port ~= "" then
if nodes_ping:find("info") then
if datatypes.ip6addr(address) then
str = str .. string.format("[%s]:%s", address, port)
else
str = str .. string.format("%s:%s", address, port)
end
end
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.address' value='%s'/>", appname, n, address)
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.port' value='%s'/>", appname, n, port)
end
return str
end
---- Ping
o = s:option(DummyValue, "ping")
o.width = "8%"
o.rawhtml = true
o.cfgvalue = function(t, n)
local result = "---"
if not nodes_ping:find("auto_ping") then
result = string.format('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'%s\',this)">Ping</a></span>', n)
else
result = string.format('<span class="ping_value" cbiid="%s">---</span>', n)
end
return result
end
o = s:option(DummyValue, "_url_test")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<input type="button" class="cbi-button" value="%s" onclick="javascript:urltest_node(\'%s\',this)"', translate("Availability test"), n)
end
m:append(Template(appname .. "/node_list/node_list"))
return m

View File

@ -0,0 +1,137 @@
local api = require "luci.passwall.api"
local appname = api.appname
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_trojan_plus = api.is_finded("trojan-plus")
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_trojan_go = api.is_finded("trojan-go")
local ss_aead_type = {}
local trojan_type = {}
if has_ss then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-libev"
end
if has_ss_rust then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
end
if has_trojan_plus then
trojan_type[#trojan_type + 1] = "trojan-plus"
end
if has_v2ray then
trojan_type[#trojan_type + 1] = "v2ray"
ss_aead_type[#ss_aead_type + 1] = "v2ray"
end
if has_xray then
trojan_type[#trojan_type + 1] = "xray"
ss_aead_type[#ss_aead_type + 1] = "xray"
end
if has_trojan_go then
trojan_type[#trojan_type + 1] = "trojan-go"
end
m = Map(appname)
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
s.anonymous = true
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
if #ss_aead_type > 0 then
o = s:option(ListValue, "ss_aead_type", translate("SS AEAD Node Use Type"))
for key, value in pairs(ss_aead_type) do
o:value(value, translate(value:gsub("^%l",string.upper)))
end
end
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translate("Trojan Node Use Type"))
for key, value in pairs(trojan_type) do
o:value(value, translate(value:gsub("^%l",string.upper)))
end
end
---- Subscribe Delete All
o = s:option(Button, "_stop", translate("Delete All Subscribe Node"))
o.inputstyle = "remove"
function o.write(e, e)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1")
end
o = s:option(Button, "_update", translate("Manual subscription All"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("Please input the subscription url first, save and submit before manual subscription.") .. "</font>")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_subscribe_config", "%s")
function s.create(e, t)
local id = TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(id))
end
o = s:option(Value, "remark", translate("Remarks"))
o.width = "auto"
o.rmempty = false
o.validate = function(self, value, t)
if value then
local count = 0
m.uci:foreach(appname, "subscribe_list", function(e)
if e[".name"] ~= t and e["remark"] == value then
count = count + 1
end
end)
if count > 0 then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
end
o = s:option(DummyValue, "_node_count")
o.rawhtml = true
o.cfgvalue = function(t, n)
local remark = m:get(n, "remark") or ""
local num = 0
m.uci:foreach(appname, "nodes", function(s)
if s["add_from"] ~= "" and s["add_from"] == remark then
num = num + 1
end
end)
return string.format("<span title='%s' style='color:red'>%s</span>", remark .. " " .. translate("Node num") .. ": " .. num, num)
end
o = s:option(Value, "url", translate("Subscribe URL"))
o.width = "auto"
o.rmempty = false
o = s:option(Button, "_remove", translate("Delete the subscribed node"))
o.inputstyle = "remove"
function o.write(t, n)
local remark = m:get(n, "remark") or ""
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate " .. remark .. " > /dev/null 2>&1")
end
o = s:option(Button, "_update", translate("Manual subscription"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. n .. " > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
return m

View File

@ -0,0 +1,110 @@
local api = require "luci.passwall.api"
local appname = api.appname
local sys = api.sys
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_trojan_plus = api.is_finded("trojan-plus")
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_trojan_go = api.is_finded("trojan-go")
local ss_aead_type = {}
local trojan_type = {}
if has_ss then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-libev"
end
if has_ss_rust then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
end
if has_trojan_plus then
trojan_type[#trojan_type + 1] = "trojan-plus"
end
if has_v2ray then
trojan_type[#trojan_type + 1] = "v2ray"
ss_aead_type[#ss_aead_type + 1] = "v2ray"
end
if has_xray then
trojan_type[#trojan_type + 1] = "xray"
ss_aead_type[#ss_aead_type + 1] = "xray"
end
if has_trojan_go then
trojan_type[#trojan_type + 1] = "trojan-go"
end
m = Map(appname)
m.redirect = api.url("node_subscribe")
s = m:section(NamedSection, arg[1])
s.addremove = false
s.dynamic = false
o = s:option(Value, "remark", translate("Subscribe Remark"))
o.rmempty = false
o = s:option(TextValue, "url", translate("Subscribe URL"))
o.rows = 5
o.rmempty = false
o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "0"
o.rmempty = false
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o.default = "5"
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o:value("5", translate("Use global config"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o:depends("filter_keyword_mode", "1")
o:depends("filter_keyword_mode", "3")
o:depends("filter_keyword_mode", "4")
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
o:depends("filter_keyword_mode", "2")
o:depends("filter_keyword_mode", "3")
o:depends("filter_keyword_mode", "4")
if #ss_aead_type > 0 then
o = s:option(ListValue, "ss_aead_type", translate("SS AEAD Node Use Type"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(ss_aead_type) do
o:value(value, translate(value:gsub("^%l",string.upper)))
end
end
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translate("Trojan Node Use Type"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(trojan_type) do
o:value(value, translate(value:gsub("^%l",string.upper)))
end
end
---- Enable auto update subscribe
o = s:option(Flag, "auto_update", translate("Enable auto update subscribe"))
o.default = 0
o.rmempty = false
---- Week update rules
o = s:option(ListValue, "week_update", translate("Week update rules"))
o:value(7, translate("Every day"))
for e = 1, 6 do o:value(e, translate("Week") .. e) end
o:value(0, translate("Week") .. translate("day"))
o.default = 0
o:depends("auto_update", true)
---- Day update rules
o = s:option(ListValue, "time_update", translate("Day update rules"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
o.default = 0
o:depends("auto_update", true)
o = s:option(Value, "user_agent", translate("User-Agent"))
o.default = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
return m

View File

@ -0,0 +1,171 @@
local api = require "luci.passwall.api"
local appname = api.appname
local fs = api.fs
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_fw3 = api.is_finded("fw3")
local has_fw4 = api.is_finded("fw4")
m = Map(appname)
-- [[ Delay Settings ]]--
s = m:section(TypedSection, "global_delay", translate("Delay Settings"))
s.anonymous = true
s.addremove = false
---- Delay Start
o = s:option(Value, "start_delay", translate("Delay Start"),
translate("Units:seconds"))
o.default = "1"
o.rmempty = true
---- Open and close Daemon
o = s:option(Flag, "start_daemon", translate("Open and close Daemon"))
o.default = 1
o.rmempty = false
--[[
---- Open and close automatically
o = s:option(Flag, "auto_on", translate("Open and close automatically"))
o.default = 0
o.rmempty = false
---- Automatically turn off time
o = s:option(ListValue, "time_off", translate("Automatically turn off time"))
o.default = nil
o:depends("auto_on", true)
o:value(nil, translate("Disable"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
---- Automatically turn on time
o = s:option(ListValue, "time_on", translate("Automatically turn on time"))
o.default = nil
o:depends("auto_on", true)
o:value(nil, translate("Disable"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
---- Automatically restart time
o = s:option(ListValue, "time_restart", translate("Automatically restart time"))
o.default = nil
o:depends("auto_on", true)
o:value(nil, translate("Disable"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
--]]
-- [[ Forwarding Settings ]]--
s = m:section(TypedSection, "global_forwarding",
translate("Forwarding Settings"))
s.anonymous = true
s.addremove = false
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o.default = "disable"
o:value("disable", translate("No patterns are used"))
o:value("1:65535", translate("All"))
---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
"<font color='red'>" .. translate(
"Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
"</font>")
o.default = "disable"
o:value("disable", translate("No patterns are used"))
o:value("1:65535", translate("All"))
---- TCP Proxy Drop Ports
o = s:option(Value, "tcp_proxy_drop_ports", translate("TCP Proxy Drop Ports"))
o.default = "disable"
o:value("disable", translate("No patterns are used"))
---- UDP Proxy Drop Ports
o = s:option(Value, "udp_proxy_drop_ports", translate("UDP Proxy Drop Ports"))
o.default = "80,443"
o:value("disable", translate("No patterns are used"))
o:value("80,443", translate("QUIC"))
---- TCP Redir Ports
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o.default = "22,25,53,143,465,587,853,993,995,80,443"
o:value("1:65535", translate("All"))
o:value("22,25,53,143,465,587,853,993,995,80,443", translate("Common Use"))
o:value("80,443", translate("Only Web"))
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o.default = "1:65535"
o:value("1:65535", translate("All"))
o:value("53", "DNS")
---- Use nftables
o = s:option(ListValue, "use_nft", translate("Firewall tools"))
o.default = "0"
if has_fw3 then
o:value("0", "IPtables")
end
if has_fw4 then
o:value("1", "NFtables")
end
if (os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod | grep -i TPROXY >/dev/null") == 0) or (os.execute("lsmod | grep -i nft_redir >/dev/null") == 0 and os.execute("lsmod | grep -i nft_tproxy >/dev/null") == 0) then
o = s:option(ListValue, "tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "redirect"
o:value("redirect", "REDIRECT")
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", false)
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "tproxy"
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", true)
o.write = function(self, section, value)
return self.map:set(section, "tcp_proxy_way", value)
end
if os.execute("lsmod | grep -i ip6table_mangle >/dev/null") == 0 or os.execute("lsmod | grep -i nft_tproxy >/dev/null") == 0 then
---- IPv6 TProxy
o = s:option(Flag, "ipv6_tproxy", translate("IPv6 TProxy"),
"<font color='red'>" .. translate(
"Experimental feature. Make sure that your node supports IPv6.") ..
"</font>")
o.default = 0
o.rmempty = false
end
end
o = s:option(Flag, "accept_icmp", translate("Hijacking ICMP (PING)"))
o.default = 0
o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
o:depends("ipv6_tproxy", true)
o.default = 0
if has_v2ray or has_xray then
o = s:option(Flag, "sniffing", translate("Sniffing (V2Ray/Xray)"), translate("When using the V2ray/Xray shunt, must be enabled, otherwise the shunt will invalid."))
o.default = 1
o.rmempty = false
if has_xray then
route_only = s:option(Flag, "route_only", translate("Sniffing Route Only (Xray)"), translate("When enabled, the server not will resolve the domain name again."))
route_only.default = 0
route_only:depends("sniffing", true)
local domains_excluded = string.format("/usr/share/%s/rules/domains_excluded", appname)
o = s:option(TextValue, "no_sniffing_hosts", translate("No Sniffing Lists"), translate("Hosts added into No Sniffing Lists will not resolve again on server (Xray only)."))
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section) return fs.readfile(domains_excluded) or "" end
o.write = function(self, section, value) fs.writefile(domains_excluded, value:gsub("\r\n", "\n")) end
o.remove = function(self, section, value)
if route_only:formvalue(section) == "0" then
fs.writefile(domains_excluded, "")
end
end
o:depends({sniffing = true, route_only = false})
o = s:option(Value, "buffer_size", translate("Buffer Size (Xray)"), translate("Buffer size for every connection (kB)"))
o.rmempty = true
o.datatype = "uinteger"
end
end
return m

View File

@ -0,0 +1,91 @@
local api = require "luci.passwall.api"
local appname = api.appname
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
m = Map(appname)
-- [[ Rule Settings ]]--
s = m:section(TypedSection, "global_rules", translate("Rule status"))
s.anonymous = true
--[[
o = s:option(Flag, "adblock", translate("Enable adblock"))
o.rmempty = false
]]--
---- gfwlist URL
o = s:option(DynamicList, "gfwlist_url", translate("GFW domains(gfwlist) Update URL"))
o:value("https://fastly.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
o:value("https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
o:value("https://fastly.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
o:value("https://fastly.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
o.default = "https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"
----chnroute URL
o = s:option(DynamicList, "chnroute_url", translate("China IPs(chnroute) Update URL"))
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt", translate("gaoyifan/china-operator-ip/china"))
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR"))
o:value("https://fastly.jsdelivr.net/gh/soffchen/GeoIP2-CN@release/CN-ip-cidr.txt", translate("soffchen/GeoIP2-CN"))
o:value("https://fastly.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us/GeoIP2-CN"))
----chnroute6 URL
o = s:option(DynamicList, "chnroute6_url", translate("China IPv6s(chnroute6) Update URL"))
o:value("https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt", translate("gaoyifan/china-operator-ip/china6"))
o:value("https://ispip.clang.cn/all_cn_ipv6.txt", translate("Clang.CN.IPv6"))
----chnlist URL
o = s:option(DynamicList, "chnlist_url", translate("China List(Chnlist) Update URL"))
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf", translate("felixonmars/domains.china"))
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf", translate("felixonmars/apple.china"))
o:value("https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf", translate("felixonmars/google.china"))
s:append(Template(appname .. "/rule/rule_version"))
---- Auto Update
o = s:option(Flag, "auto_update", translate("Enable auto update rules"))
o.default = 0
o.rmempty = false
---- Week Update
o = s:option(ListValue, "week_update", translate("Week update rules"))
o:value(7, translate("Every day"))
for e = 1, 6 do o:value(e, translate("Week") .. e) end
o:value(0, translate("Week") .. translate("day"))
o.default = 0
o:depends("auto_update", true)
---- Time Update
o = s:option(ListValue, "time_update", translate("Day update rules"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
o.default = 0
o:depends("auto_update", true)
if has_v2ray or has_xray then
o = s:option(Value, "v2ray_location_asset", translate("Location of V2ray/Xray asset"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.rmempty = false
s = m:section(TypedSection, "shunt_rules", "V2ray/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
s.template = "cbi/tblsection"
s.anonymous = false
s.addremove = true
s.sortable = true
s.extedit = api.url("shunt_rules", "%s")
function s.create(e, t)
TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function s.remove(e, t)
m.uci:foreach(appname, "nodes", function(s)
if s["protocol"] and s["protocol"] == "_shunt" then
m:del(s[".name"], t)
end
end)
TypedSection.remove(e, t)
end
o = s:option(DummyValue, "remarks", translate("Remarks"))
end
return m

View File

@ -0,0 +1,271 @@
local api = require "luci.passwall.api"
local appname = api.appname
local fs = api.fs
local sys = api.sys
local datatypes = api.datatypes
local path = string.format("/usr/share/%s/rules/", appname)
local route_hosts_path = "/etc/"
m = Map(appname)
-- [[ Rule List Settings ]]--
s = m:section(TypedSection, "global_rules")
s.anonymous = true
s:tab("direct_list", translate("Direct List"))
s:tab("proxy_list", translate("Proxy List"))
s:tab("block_list", translate("Block List"))
s:tab("lan_ip_list", translate("Lan IP List"))
s:tab("route_hosts", translate("Route Hosts"))
---- Direct Hosts
local direct_host = path .. "direct_host"
o = s:taboption("direct_list", TextValue, "direct_host", "", "<font color='red'>" .. translate("Join the direct hosts list of domain names will not proxy.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(direct_host) or ""
end
o.write = function(self, section, value)
fs.writefile(direct_host, value:gsub("\r\n", "\n"))
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.remove = function(self, section, value)
fs.writefile(direct_host, "")
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.validate = function(self, value)
local hosts= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
if host:find("#") and host:find("#") == 1 then
return value
end
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
return value
end
---- Direct IP
local direct_ip = path .. "direct_ip"
o = s:taboption("direct_list", TextValue, "direct_ip", "", "<font color='red'>" .. translate("These had been joined ip addresses will not proxy. Please input the ip address or ip address segment,every line can input only one ip address. For example: 192.168.0.0/24 or 223.5.5.5.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(direct_ip) or ""
end
o.write = function(self, section, value)
fs.writefile(direct_ip, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(direct_ip, "")
end
o.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("#") and ipmask:find("#") == 1 then
return value
end
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
return value
end
---- Proxy Hosts
local proxy_host = path .. "proxy_host"
o = s:taboption("proxy_list", TextValue, "proxy_host", "", "<font color='red'>" .. translate("These had been joined websites will use proxy. Please input the domain names of websites, every line can input only one website domain. For example: google.com.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(proxy_host) or ""
end
o.write = function(self, section, value)
fs.writefile(proxy_host, value:gsub("\r\n", "\n"))
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.remove = function(self, section, value)
fs.writefile(proxy_host, "")
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.validate = function(self, value)
local hosts= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
if host:find("#") and host:find("#") == 1 then
return value
end
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
return value
end
---- Proxy IP
local proxy_ip = path .. "proxy_ip"
o = s:taboption("proxy_list", TextValue, "proxy_ip", "", "<font color='red'>" .. translate("These had been joined ip addresses will use proxy. Please input the ip address or ip address segment, every line can input only one ip address. For example: 35.24.0.0/24 or 8.8.4.4.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(proxy_ip) or ""
end
o.write = function(self, section, value)
fs.writefile(proxy_ip, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(proxy_ip, "")
end
o.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("#") and ipmask:find("#") == 1 then
return value
end
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
return value
end
---- Block Hosts
local block_host = path .. "block_host"
o = s:taboption("block_list", TextValue, "block_host", "", "<font color='red'>" .. translate("These had been joined websites will be block. Please input the domain names of websites, every line can input only one website domain. For example: twitter.com.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(block_host) or ""
end
o.write = function(self, section, value)
fs.writefile(block_host, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(block_host, "")
end
o.validate = function(self, value)
local hosts= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
if host:find("#") and host:find("#") == 1 then
return value
end
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
return value
end
---- Block IP
local block_ip = path .. "block_ip"
o = s:taboption("block_list", TextValue, "block_ip", "", "<font color='red'>" .. translate("These had been joined ip addresses will be block. Please input the ip address or ip address segment, every line can input only one ip address.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(block_ip) or ""
end
o.write = function(self, section, value)
fs.writefile(block_ip, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(block_ip, "")
end
o.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("#") and ipmask:find("#") == 1 then
return value
end
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
return value
end
---- Lan IPv4
local lanlist_ipv4 = path .. "lanlist_ipv4"
o = s:taboption("lan_ip_list", TextValue, "lanlist_ipv4", "", "<font color='red'>" .. translate("The list is the IPv4 LAN IP list, which represents the direct connection IP of the LAN. If you need the LAN IP in the proxy list, please clear it from the list. Do not modify this list by default.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(lanlist_ipv4) or ""
end
o.write = function(self, section, value)
fs.writefile(lanlist_ipv4, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(lanlist_ipv4, "")
end
o.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("#") and ipmask:find("#") == 1 then
return value
end
if not datatypes.ipmask4(ipmask) then
return nil, ipmask .. " " .. translate("Not valid IPv4 format, please re-enter!")
end
end
return value
end
---- Lan IPv6
local lanlist_ipv6 = path .. "lanlist_ipv6"
o = s:taboption("lan_ip_list", TextValue, "lanlist_ipv6", "", "<font color='red'>" .. translate("The list is the IPv6 LAN IP list, which represents the direct connection IP of the LAN. If you need the LAN IP in the proxy list, please clear it from the list. Do not modify this list by default.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(lanlist_ipv6) or ""
end
o.write = function(self, section, value)
fs.writefile(lanlist_ipv6, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(lanlist_ipv6, "")
end
o.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("#") and ipmask:find("#") == 1 then
return value
end
if not datatypes.ipmask6(ipmask) then
return nil, ipmask .. " " .. translate("Not valid IPv6 format, please re-enter!")
end
end
return value
end
---- Route Hosts
local hosts = route_hosts_path .. "hosts"
o = s:taboption("route_hosts", TextValue, "hosts", "", "<font color='red'>" .. translate("Configure routing etc/hosts file, if you don't know what you are doing, please don't change the content.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(hosts) or ""
end
o.write = function(self, section, value)
fs.writefile(hosts, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(hosts, "")
end
if sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0 then
m.apply_on_parse = true
function m.on_apply(self)
luci.sys.call("/etc/init.d/passwall reload > /dev/null 2>&1 &")
end
end
return m

View File

@ -0,0 +1,79 @@
local api = require "luci.passwall.api"
local appname = api.appname
local datatypes = api.datatypes
m = Map(appname, "V2ray/Xray " .. translate("Shunt Rule"))
m.redirect = api.url()
s = m:section(NamedSection, arg[1], "shunt_rules", "")
s.addremove = false
s.dynamic = false
remarks = s:option(Value, "remarks", translate("Remarks"))
remarks.default = arg[1]
remarks.rmempty = false
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
protocol:value("tls")
protocol:value("bittorrent")
domain_list = s:option(TextValue, "domain_list", translate("Domain"))
domain_list.rows = 10
domain_list.wrap = "off"
domain_list.validate = function(self, value)
local hosts= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
local flag = 1
local tmp_host = host
if host:find("regexp:") and host:find("regexp:") == 1 then
flag = 0
elseif host:find("domain:.") and host:find("domain:.") == 1 then
tmp_host = host:gsub("domain:", "")
elseif host:find("full:.") and host:find("full:.") == 1 then
tmp_host = host:gsub("full:", "")
elseif host:find("geosite:") and host:find("geosite:") == 1 then
flag = 0
elseif host:find("ext:") and host:find("ext:") == 1 then
flag = 0
end
if flag == 1 then
if not datatypes.hostname(tmp_host) then
return nil, tmp_host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
end
return value
end
domain_list.description = "<br /><ul><li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.")
.. "</li><li>" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.")
.. "</li><li>" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.")
.. "</li><li>" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.")
.. "</li><li>" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.")
.. "</li><li>" .. translate("Domains from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geosite.dat. The tag must exist in the file.")
.. "</li></ul>"
ip_list = s:option(TextValue, "ip_list", "IP")
ip_list.rows = 10
ip_list.wrap = "off"
ip_list.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 then
elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 then
else
if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
end
return value
end
ip_list.description = "<br /><ul><li>" .. translate("IP: such as '127.0.0.1'.")
.. "</li><li>" .. translate("CIDR: such as '127.0.0.0/8'.")
.. "</li><li>" .. translate("GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code.")
.. "</li><li>" .. translate("IPs from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geoip.dat. The tag must exist in the file.")
.. "</li></ul>"
return m

View File

@ -0,0 +1,72 @@
local api = require "luci.passwall.api"
m = Map("passwall_server", translate("Server-Side"))
t = m:section(NamedSection, "global", "global")
t.anonymous = true
t.addremove = false
e = t:option(Flag, "enable", translate("Enable"))
e.rmempty = false
t = m:section(TypedSection, "user", translate("Users Manager"))
t.anonymous = true
t.addremove = true
t.sortable = true
t.template = "cbi/tblsection"
t.extedit = api.url("server_user", "%s")
function t.create(e, t)
local uuid = api.gen_uuid()
t = uuid
TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function t.remove(e, t)
e.map.proceed = true
e.map:del(t)
luci.http.redirect(api.url("server"))
end
e = t:option(Flag, "enable", translate("Enable"))
e.width = "5%"
e.rmempty = false
e = t:option(DummyValue, "status", translate("Status"))
e.rawhtml = true
e.cfgvalue = function(t, n)
return string.format('<font class="_users_status">%s</font>', translate("Collecting data..."))
end
e = t:option(DummyValue, "remarks", translate("Remarks"))
e.width = "15%"
---- Type
e = t:option(DummyValue, "type", translate("Type"))
e.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n)
if v then
if v == "V2ray" or v == "Xray" then
local protocol = m:get(n, "protocol")
if protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
else
protocol = protocol:gsub("^%l",string.upper)
end
return v .. " -> " .. protocol
end
return v
end
end
e = t:option(DummyValue, "port", translate("Port"))
e = t:option(Flag, "log", translate("Log"))
e.default = "1"
e.rmempty = false
m:append(Template("passwall/server/log"))
m:append(Template("passwall/server/users_list_status"))
return m

View File

@ -0,0 +1,746 @@
local api = require "luci.passwall.api"
local ss_encrypt_method_list = {
"rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
"aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb",
"camellia-192-cfb", "camellia-256-cfb", "salsa20", "chacha20",
"chacha20-ietf", -- aead
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
}
local ss_rust_encrypt_method_list = {
"plain", "none",
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
}
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
"chacha20-ietf"
}
local ssr_protocol_list = {
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
"auth_chain_d", "auth_chain_e", "auth_chain_f"
}
local ssr_obfs_list = {
"plain", "http_simple", "http_post", "random_head", "tls_simple",
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
local v_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
}
local x_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
}
local encrypt_methods_ss_aead = {
"chacha20-ietf-poly1305",
"aes-128-gcm",
"aes-256-gcm",
}
m = Map("passwall_server", translate("Server Config"))
m.redirect = api.url("server")
s = m:section(NamedSection, arg[1], "user", "")
s.addremove = false
s.dynamic = false
enable = s:option(Flag, "enable", translate("Enable"))
enable.default = "1"
enable.rmempty = false
remarks = s:option(Value, "remarks", translate("Remarks"))
remarks.default = translate("Remarks")
remarks.rmempty = false
type = s:option(ListValue, "type", translate("Type"))
if api.is_finded("microsocks") then
type:value("Socks", translate("Socks"))
end
if api.is_finded("ss-server") then
type:value("SS", translate("Shadowsocks"))
end
if api.is_finded("ssserver") then
type:value("SS-Rust", translate("Shadowsocks Rust"))
end
if api.is_finded("ssr-server") then
type:value("SSR", translate("ShadowsocksR"))
end
if api.is_finded("v2ray") then
type:value("V2ray", translate("V2ray"))
end
if api.is_finded("xray") then
type:value("Xray", translate("Xray"))
end
if api.is_finded("brook") then
type:value("Brook", translate("Brook"))
end
--[[
if api.is_finded("trojan-plus") or api.is_finded("trojan") then
type:value("Trojan", translate("Trojan"))
end
]]--
if api.is_finded("trojan-plus") then
type:value("Trojan-Plus", translate("Trojan-Plus"))
end
if api.is_finded("trojan-go") then
type:value("Trojan-Go", translate("Trojan-Go"))
end
if api.is_finded("hysteria") then
type:value("Hysteria", translate("Hysteria"))
end
protocol = s:option(ListValue, "protocol", translate("Protocol"))
protocol:value("vmess", "Vmess")
protocol:value("vless", "VLESS")
protocol:value("http", "HTTP")
protocol:value("socks", "Socks")
protocol:value("shadowsocks", "Shadowsocks")
protocol:value("trojan", "Trojan")
protocol:value("mtproto", "MTProto")
protocol:value("dokodemo-door", "dokodemo-door")
protocol:depends("type", "V2ray")
protocol:depends("type", "Xray")
-- Brook协议
brook_protocol = s:option(ListValue, "brook_protocol", translate("Protocol"))
brook_protocol:value("server", "Brook")
brook_protocol:value("wsserver", "WebSocket")
brook_protocol:depends("type", "Brook")
function brook_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function brook_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
--brook_tls = s:option(Flag, "brook_tls", translate("Use TLS"))
--brook_tls:depends("brook_protocol", "wsserver")
port = s:option(Value, "port", translate("Listen Port"))
port.datatype = "port"
port.rmempty = false
auth = s:option(Flag, "auth", translate("Auth"))
auth.validate = function(self, value, t)
if value and value == "1" then
local user_v = username:formvalue(t) or ""
local pass_v = password:formvalue(t) or ""
if user_v == "" or pass_v == "" then
return nil, translate("Username and Password must be used together!")
end
end
return value
end
auth:depends("type", "Socks")
auth:depends({ type = "V2ray", protocol = "socks" })
auth:depends({ type = "V2ray", protocol = "http" })
auth:depends({ type = "Xray", protocol = "socks" })
auth:depends({ type = "Xray", protocol = "http" })
username = s:option(Value, "username", translate("Username"))
username:depends("auth", true)
password = s:option(Value, "password", translate("Password"))
password.password = true
password:depends("auth", true)
password:depends("type", "SS")
password:depends("type", "SS-Rust")
password:depends("type", "SSR")
password:depends("type", "Brook")
password:depends({ type = "V2ray", protocol = "shadowsocks" })
password:depends({ type = "Xray", protocol = "shadowsocks" })
mtproto_password = s:option(Value, "mtproto_password", translate("Password"), translate("The MTProto protocol must be 32 characters and can only contain characters from 0 to 9 and a to f."))
mtproto_password:depends({ type = "V2ray", protocol = "mtproto" })
mtproto_password:depends({ type = "Xray", protocol = "mtproto" })
mtproto_password.default = arg[1]
function mtproto_password.cfgvalue(self, section)
return m:get(section, "password")
end
function mtproto_password.write(self, section, value)
m:set(section, "password", value)
end
d_protocol = s:option(ListValue, "d_protocol", translate("Destination protocol"))
d_protocol:value("tcp", "TCP")
d_protocol:value("udp", "UDP")
d_protocol:value("tcp,udp", "TCP,UDP")
d_protocol:depends({ type = "V2ray", protocol = "dokodemo-door" })
d_protocol:depends({ type = "Xray", protocol = "dokodemo-door" })
d_address = s:option(Value, "d_address", translate("Destination address"))
d_address:depends({ type = "V2ray", protocol = "dokodemo-door" })
d_address:depends({ type = "Xray", protocol = "dokodemo-door" })
d_port = s:option(Value, "d_port", translate("Destination port"))
d_port.datatype = "port"
d_port:depends({ type = "V2ray", protocol = "dokodemo-door" })
d_port:depends({ type = "Xray", protocol = "dokodemo-door" })
decryption = s:option(Value, "decryption", translate("Encrypt Method"))
decryption.default = "none"
decryption:depends({ type = "V2ray", protocol = "vless" })
decryption:depends({ type = "Xray", protocol = "vless" })
hysteria_protocol = s:option(ListValue, "hysteria_protocol", translate("Protocol"))
hysteria_protocol:value("udp", "UDP")
hysteria_protocol:value("faketcp", "faketcp")
hysteria_protocol:value("wechat-video", "wechat-video")
hysteria_protocol:depends("type", "Hysteria")
function hysteria_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function hysteria_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
hysteria_obfs = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
hysteria_obfs:depends("type", "Hysteria")
hysteria_auth_type = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
hysteria_auth_type:value("disable", translate("Disable"))
hysteria_auth_type:value("string", translate("STRING"))
hysteria_auth_type:depends("type", "Hysteria")
hysteria_auth_password = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
hysteria_auth_password.password = true
hysteria_auth_password:depends("hysteria_auth_type", "string")
hysteria_alpn = s:option(Value, "hysteria_alpn", translate("QUIC TLS ALPN"))
hysteria_alpn:depends("type", "Hysteria")
hysteria_udp = s:option(Flag, "hysteria_udp", translate("UDP"))
hysteria_udp.default = "1"
hysteria_udp:depends("type", "Hysteria")
hysteria_up_mbps = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
hysteria_up_mbps.default = "10"
hysteria_up_mbps:depends("type", "Hysteria")
hysteria_down_mbps = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
hysteria_down_mbps.default = "50"
hysteria_down_mbps:depends("type", "Hysteria")
hysteria_recv_window_conn = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
hysteria_recv_window_conn:depends("type", "Hysteria")
hysteria_recv_window = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
hysteria_recv_window:depends("type", "Hysteria")
hysteria_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
hysteria_disable_mtu_discovery:depends("type", "Hysteria")
ss_encrypt_method = s:option(ListValue, "ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ss_encrypt_method_list) do ss_encrypt_method:value(t) end
ss_encrypt_method:depends("type", "SS")
function ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
ss_rust_encrypt_method = s:option(ListValue, "ss_rust_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ss_rust_encrypt_method_list) do ss_rust_encrypt_method:value(t) end
ss_rust_encrypt_method:depends("type", "SS-Rust")
function ss_rust_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ss_rust_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
ssr_encrypt_method = s:option(ListValue, "ssr_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ssr_encrypt_method_list) do ssr_encrypt_method:value(t) end
ssr_encrypt_method:depends("type", "SSR")
function ssr_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ssr_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(v_ss_encrypt_method_list) do v_ss_encrypt_method:value(t) end
v_ss_encrypt_method:depends({ type = "V2ray", protocol = "shadowsocks" })
function v_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function v_ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
x_ss_encrypt_method = s:option(ListValue, "x_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(x_ss_encrypt_method_list) do x_ss_encrypt_method:value(t) end
x_ss_encrypt_method:depends({ type = "Xray", protocol = "shadowsocks" })
function x_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function x_ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
iv_check = s:option(Flag, "iv_check", translate("IV Check"))
iv_check:depends({ type = "V2ray", protocol = "shadowsocks" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks" })
ss_network = s:option(ListValue, "ss_network", translate("Transport"))
ss_network.default = "tcp,udp"
ss_network:value("tcp", "TCP")
ss_network:value("udp", "UDP")
ss_network:value("tcp,udp", "TCP,UDP")
ss_network:depends({ type = "V2ray", protocol = "shadowsocks" })
ss_network:depends({ type = "Xray", protocol = "shadowsocks" })
ssr_protocol = s:option(ListValue, "ssr_protocol", translate("Protocol"))
for a, t in ipairs(ssr_protocol_list) do ssr_protocol:value(t) end
ssr_protocol:depends("type", "SSR")
function ssr_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function ssr_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
protocol_param = s:option(Value, "protocol_param", translate("Protocol_param"))
protocol_param:depends("type", "SSR")
obfs = s:option(ListValue, "obfs", translate("Obfs"))
for a, t in ipairs(ssr_obfs_list) do obfs:value(t) end
obfs:depends("type", "SSR")
obfs_param = s:option(Value, "obfs_param", translate("Obfs_param"))
obfs_param:depends("type", "SSR")
timeout = s:option(Value, "timeout", translate("Connection Timeout"))
timeout.datatype = "uinteger"
timeout.default = 300
timeout:depends("type", "SS")
timeout:depends("type", "SS-Rust")
timeout:depends("type", "SSR")
udp_forward = s:option(Flag, "udp_forward", translate("UDP Forward"))
udp_forward.default = "1"
udp_forward.rmempty = false
udp_forward:depends("type", "SSR")
udp_forward:depends({ type = "V2ray", protocol = "socks" })
udp_forward:depends({ type = "Xray", protocol = "socks" })
uuid = s:option(DynamicList, "uuid", translate("ID") .. "/" .. translate("Password"))
for i = 1, 3 do
uuid:value(api.gen_uuid(1))
end
uuid:depends({ type = "V2ray", protocol = "vmess" })
uuid:depends({ type = "V2ray", protocol = "vless" })
uuid:depends({ type = "V2ray", protocol = "trojan" })
uuid:depends({ type = "Xray", protocol = "vmess" })
uuid:depends({ type = "Xray", protocol = "vless" })
uuid:depends({ type = "Xray", protocol = "trojan" })
uuid:depends("type", "Trojan")
uuid:depends("type", "Trojan-Go")
uuid:depends("type", "Trojan-Plus")
tls = s:option(Flag, "tls", translate("TLS"))
tls.default = 0
tls.validate = function(self, value, t)
if value then
local type = type:formvalue(t) or ""
if value == "0" and (type == "Trojan" or type == "Trojan-Plus") then
return nil, translate("Original Trojan only supported 'tls', please choose 'tls'.")
end
if value == "1" then
local ca = tls_certificateFile:formvalue(t) or ""
local key = tls_keyFile:formvalue(t) or ""
if ca == "" or key == "" then
return nil, translate("Public key and Private key path can not be empty!")
end
end
return value
end
end
tls:depends({ type = "V2ray", protocol = "vmess" })
tls:depends({ type = "V2ray", protocol = "vless" })
tls:depends({ type = "V2ray", protocol = "socks" })
tls:depends({ type = "V2ray", protocol = "shadowsocks" })
tls:depends({ type = "V2ray", protocol = "trojan" })
tls:depends({ type = "Xray", protocol = "vmess" })
tls:depends({ type = "Xray", protocol = "vless" })
tls:depends({ type = "Xray", protocol = "socks" })
tls:depends({ type = "Xray", protocol = "shadowsocks" })
tls:depends({ type = "Xray", protocol = "trojan" })
tls:depends("type", "Trojan")
tls:depends("type", "Trojan-Plus")
tls:depends("type", "Trojan-Go")
tlsflow = s:option(Value, "tlsflow", translate("flow"))
tlsflow.default = ""
tlsflow:value("", translate("Disable"))
tlsflow:value("xtls-rprx-vision")
tlsflow:value("xtls-rprx-vision-udp443")
tlsflow:depends({ type = "Xray", protocol = "vless", tls = true })
alpn = s:option(ListValue, "alpn", translate("alpn"))
alpn.default = "h2,http/1.1"
alpn:value("h2,http/1.1")
alpn:value("h2")
alpn:value("http/1.1")
alpn:depends({ type = "V2ray", tls = true })
alpn:depends({ type = "Xray", tls = true })
-- minversion = s:option(Value, "minversion", translate("minversion"))
-- minversion.default = "1.3"
-- minversion:value("1.3")
-- minversion:depends("tls", true)
-- [[ TLS部分 ]] --
tls_certificateFile = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
tls_certificateFile.validate = function(self, value, t)
if value and value ~= "" then
if not nixio.fs.access(value) then
return nil, translate("Can't find this file!")
else
return value
end
end
return nil
end
tls_certificateFile.default = "/etc/config/ssl/" .. arg[1] .. ".pem"
tls_certificateFile:depends("tls", true)
tls_certificateFile:depends("type", "Hysteria")
tls_keyFile = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
tls_keyFile.validate = function(self, value, t)
if value and value ~= "" then
if not nixio.fs.access(value) then
return nil, translate("Can't find this file!")
else
return value
end
end
return nil
end
tls_keyFile.default = "/etc/config/ssl/" .. arg[1] .. ".key"
tls_keyFile:depends("tls", true)
tls_keyFile:depends("type", "Hysteria")
tls_sessionTicket = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
tls_sessionTicket.default = "0"
tls_sessionTicket:depends({ type = "Trojan", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Plus", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Go", tls = true })
transport = s:option(ListValue, "transport", translate("Transport"))
transport:value("tcp", "TCP")
transport:value("mkcp", "mKCP")
transport:value("ws", "WebSocket")
transport:value("h2", "HTTP/2")
transport:value("ds", "DomainSocket")
transport:value("quic", "QUIC")
transport:value("grpc", "gRPC")
transport:depends({ type = "V2ray", protocol = "vmess" })
transport:depends({ type = "V2ray", protocol = "vless" })
transport:depends({ type = "V2ray", protocol = "socks" })
transport:depends({ type = "V2ray", protocol = "shadowsocks" })
transport:depends({ type = "V2ray", protocol = "trojan" })
transport:depends({ type = "Xray", protocol = "vmess" })
transport:depends({ type = "Xray", protocol = "vless" })
transport:depends({ type = "Xray", protocol = "socks" })
transport:depends({ type = "Xray", protocol = "shadowsocks" })
transport:depends({ type = "Xray", protocol = "trojan" })
trojan_transport = s:option(ListValue, "trojan_transport", translate("Transport"))
trojan_transport:value("original", translate("Original"))
trojan_transport:value("ws", "WebSocket")
trojan_transport.default = "original"
trojan_transport:depends("type", "Trojan-Go")
trojan_plugin = s:option(ListValue, "plugin_type", translate("Transport Plugin"))
trojan_plugin:value("plaintext", "Plain Text")
trojan_plugin:value("shadowsocks", "ShadowSocks")
trojan_plugin:value("other", "Other")
trojan_plugin.default = "plaintext"
trojan_plugin:depends({ tls = false, trojan_transport = "original" })
trojan_plugin_cmd = s:option(Value, "plugin_cmd", translate("Plugin Binary"))
trojan_plugin_cmd.placeholder = "eg: /usr/bin/v2ray-plugin"
trojan_plugin_cmd:depends({ plugin_type = "shadowsocks" })
trojan_plugin_cmd:depends({ plugin_type = "other" })
trojan_plugin_op = s:option(Value, "plugin_option", translate("Plugin Option"))
trojan_plugin_op.placeholder = "eg: obfs=http;obfs-host=www.baidu.com"
trojan_plugin_op:depends({ plugin_type = "shadowsocks" })
trojan_plugin_op:depends({ plugin_type = "other" })
trojan_plugin_arg = s:option(DynamicList, "plugin_arg", translate("Plugin Option Args"))
trojan_plugin_arg.placeholder = "eg: [\"-config\", \"test.json\"]"
trojan_plugin_arg:depends({ plugin_type = "shadowsocks" })
trojan_plugin_arg:depends({ plugin_type = "other" })
-- [[ WebSocket部分 ]]--
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
ws_host:depends("transport", "ws")
ws_host:depends("ss_transport", "ws")
ws_host:depends("trojan_transport", "ws")
ws_path = s:option(Value, "ws_path", translate("WebSocket Path"))
ws_path:depends("transport", "ws")
ws_path:depends("ss_transport", "ws")
ws_path:depends("trojan_transport", "ws")
ws_path:depends({ type = "Brook", brook_protocol = "wsserver" })
-- [[ HTTP/2部分 ]]--
h2_host = s:option(Value, "h2_host", translate("HTTP/2 Host"))
h2_host:depends("transport", "h2")
h2_host:depends("ss_transport", "h2")
h2_host:depends("trojan_transport", "h2")
h2_path = s:option(Value, "h2_path", translate("HTTP/2 Path"))
h2_path:depends("transport", "h2")
h2_path:depends("ss_transport", "h2")
h2_path:depends("trojan_transport", "h2")
-- [[ TCP部分 ]]--
-- TCP伪装
tcp_guise = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
tcp_guise:value("none", "none")
tcp_guise:value("http", "http")
tcp_guise:depends("transport", "tcp")
-- HTTP域名
tcp_guise_http_host = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
tcp_guise_http_host:depends("tcp_guise", "http")
-- HTTP路径
tcp_guise_http_path = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
tcp_guise_http_path:depends("tcp_guise", "http")
-- [[ mKCP部分 ]]--
mkcp_guise = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
for a, t in ipairs(header_type_list) do mkcp_guise:value(t) end
mkcp_guise:depends("transport", "mkcp")
mkcp_mtu = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
mkcp_mtu.default = "1350"
mkcp_mtu:depends("transport", "mkcp")
mkcp_tti = s:option(Value, "mkcp_tti", translate("KCP TTI"))
mkcp_tti.default = "20"
mkcp_tti:depends("transport", "mkcp")
mkcp_uplinkCapacity = s:option(Value, "mkcp_uplinkCapacity", translate("KCP uplinkCapacity"))
mkcp_uplinkCapacity.default = "5"
mkcp_uplinkCapacity:depends("transport", "mkcp")
mkcp_downlinkCapacity = s:option(Value, "mkcp_downlinkCapacity", translate("KCP downlinkCapacity"))
mkcp_downlinkCapacity.default = "20"
mkcp_downlinkCapacity:depends("transport", "mkcp")
mkcp_congestion = s:option(Flag, "mkcp_congestion", translate("KCP Congestion"))
mkcp_congestion:depends("transport", "mkcp")
mkcp_readBufferSize = s:option(Value, "mkcp_readBufferSize", translate("KCP readBufferSize"))
mkcp_readBufferSize.default = "1"
mkcp_readBufferSize:depends("transport", "mkcp")
mkcp_writeBufferSize = s:option(Value, "mkcp_writeBufferSize", translate("KCP writeBufferSize"))
mkcp_writeBufferSize.default = "1"
mkcp_writeBufferSize:depends("transport", "mkcp")
mkcp_seed = s:option(Value, "mkcp_seed", translate("KCP Seed"))
mkcp_seed:depends("transport", "mkcp")
-- [[ DomainSocket部分 ]]--
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
ds_path:depends("transport", "ds")
-- [[ QUIC部分 ]]--
quic_security = s:option(ListValue, "quic_security", translate("Encrypt Method"))
quic_security:value("none")
quic_security:value("aes-128-gcm")
quic_security:value("chacha20-poly1305")
quic_security:depends("transport", "quic")
quic_key = s:option(Value, "quic_key", translate("Encrypt Method") .. translate("Key"))
quic_key:depends("transport", "quic")
quic_guise = s:option(ListValue, "quic_guise", translate("Camouflage Type"))
for a, t in ipairs(header_type_list) do quic_guise:value(t) end
quic_guise:depends("transport", "quic")
-- [[ gRPC部分 ]]--
grpc_serviceName = s:option(Value, "grpc_serviceName", "ServiceName")
grpc_serviceName:depends("transport", "grpc")
acceptProxyProtocol = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
acceptProxyProtocol:depends({ type = "V2ray", transport = "tcp" })
acceptProxyProtocol:depends({ type = "V2ray", transport = "ws" })
acceptProxyProtocol:depends({ type = "Xray", transport = "tcp" })
acceptProxyProtocol:depends({ type = "Xray", transport = "ws" })
-- [[ Fallback部分 ]]--
fallback = s:option(Flag, "fallback", translate("Fallback"))
fallback:depends({ type = "V2ray", protocol = "vless", transport = "tcp" })
fallback:depends({ type = "V2ray", protocol = "trojan", transport = "tcp" })
fallback:depends({ type = "Xray", protocol = "vless", transport = "tcp" })
fallback:depends({ type = "Xray", protocol = "trojan", transport = "tcp" })
--[[
fallback_alpn = s:option(Value, "fallback_alpn", "Fallback alpn")
fallback_alpn:depends("fallback", true)
fallback_path = s:option(Value, "fallback_path", "Fallback path")
fallback_path:depends("fallback", true)
fallback_dest = s:option(Value, "fallback_dest", "Fallback dest")
fallback_dest:depends("fallback", true)
fallback_xver = s:option(Value, "fallback_xver", "Fallback xver")
fallback_xver.default = 0
fallback_xver:depends("fallback", true)
]]--
fallback_list = s:option(DynamicList, "fallback_list", "Fallback", translate("dest,path"))
fallback_list:depends("fallback", true)
ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption"))
ss_aead:depends("type", "Trojan-Go")
ss_aead.default = "0"
ss_aead_method = s:option(ListValue, "ss_aead_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_ss_aead) do ss_aead_method:value(v, v) end
ss_aead_method.default = "aes-128-gcm"
ss_aead_method:depends("ss_aead", true)
ss_aead_pwd = s:option(Value, "ss_aead_pwd", translate("Password"))
ss_aead_pwd.password = true
ss_aead_pwd:depends("ss_aead", true)
tcp_fast_open = s:option(Flag, "tcp_fast_open", translate("TCP Fast Open"))
tcp_fast_open.default = "0"
tcp_fast_open:depends("type", "SS")
tcp_fast_open:depends("type", "SS-Rust")
tcp_fast_open:depends("type", "SSR")
tcp_fast_open:depends("type", "Trojan")
tcp_fast_open:depends("type", "Trojan-Plus")
tcp_fast_open:depends("type", "Trojan-Go")
remote_enable = s:option(Flag, "remote_enable", translate("Enable Remote"), translate("You can forward to Nginx/Caddy/V2ray/Xray WebSocket and more."))
remote_enable.default = "1"
remote_enable.rmempty = false
remote_enable:depends("type", "Trojan")
remote_enable:depends("type", "Trojan-Plus")
remote_enable:depends("type", "Trojan-Go")
remote_address = s:option(Value, "remote_address", translate("Remote Address"))
remote_address.default = "127.0.0.1"
remote_address:depends("remote_enable", 1)
remote_port = s:option(Value, "remote_port", translate("Remote Port"))
remote_port.datatype = "port"
remote_port.default = "80"
remote_port:depends("remote_enable", 1)
bind_local = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed locally, It is recommended to turn on when using reverse proxies or be fallback."))
bind_local.default = "0"
bind_local:depends("type", "V2ray")
bind_local:depends("type", "Xray")
accept_lan = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
accept_lan.default = "0"
accept_lan:depends("type", "V2ray")
accept_lan:depends("type", "Xray")
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and (e.type == "V2ray" or e.type == "Xray") then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
remarks = e["remark"]
}
end
end
outbound_node = s:option(ListValue, "outbound_node", translate("outbound node"))
outbound_node:value("nil", translate("Close"))
outbound_node:value("_socks", translate("Custom Socks"))
outbound_node:value("_http", translate("Custom HTTP"))
outbound_node:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
for k, v in pairs(nodes_table) do outbound_node:value(v.id, v.remarks) end
outbound_node.default = "nil"
outbound_node:depends("type", "V2ray")
outbound_node:depends("type", "Xray")
outbound_node_address = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
outbound_node_address:depends("outbound_node", "_socks")
outbound_node_address:depends("outbound_node", "_http")
outbound_node_port = s:option(Value, "outbound_node_port", translate("Port"))
outbound_node_port.datatype = "port"
outbound_node_port:depends("outbound_node", "_socks")
outbound_node_port:depends("outbound_node", "_http")
outbound_node_username = s:option(Value, "outbound_node_username", translate("Username"))
outbound_node_username:depends("outbound_node", "_socks")
outbound_node_username:depends("outbound_node", "_http")
outbound_node_password = s:option(Value, "outbound_node_password", translate("Password"))
outbound_node_password.password = true
outbound_node_password:depends("outbound_node", "_socks")
outbound_node_password:depends("outbound_node", "_http")
outbound_node_iface = s:option(Value, "outbound_node_iface", translate("Interface"))
outbound_node_iface.default = "eth1"
outbound_node_iface:depends("outbound_node", "_iface")
log = s:option(Flag, "log", translate("Log"))
log.default = "1"
log.rmempty = false
loglevel = s:option(ListValue, "loglevel", translate("Log Level"))
loglevel.default = "warning"
loglevel:value("debug")
loglevel:value("info")
loglevel:value("warning")
loglevel:value("error")
loglevel:depends({ type = "V2ray", log = true })
loglevel:depends({ type = "Xray", log = true })
trojan_loglevel = s:option(ListValue, "trojan_loglevel", translate("Log Level"))
trojan_loglevel.default = "2"
trojan_loglevel:value("0", "all")
trojan_loglevel:value("1", "info")
trojan_loglevel:value("2", "warn")
trojan_loglevel:value("3", "error")
trojan_loglevel:value("4", "fatal")
function trojan_loglevel.cfgvalue(self, section)
return m:get(section, "loglevel")
end
function trojan_loglevel.write(self, section, value)
m:set(section, "loglevel", value)
end
trojan_loglevel:depends({ type = "Trojan", log = true })
trojan_loglevel:depends({ type = "Trojan-Plus", log = true })
trojan_loglevel:depends({ type = "Trojan-Go", log = true })
return m

View File

@ -0,0 +1,929 @@
module("luci.passwall.api", package.seeall)
local com = require "luci.passwall.com"
bin = require "nixio".bin
fs = require "nixio.fs"
sys = require "luci.sys"
uci = require"luci.model.uci".cursor()
util = require "luci.util"
datatypes = require "luci.cbi.datatypes"
jsonc = require "luci.jsonc"
i18n = require "luci.i18n"
appname = "passwall"
curl_args = { "-skfL", "--connect-timeout 3", "--retry 3", "-m 60" }
command_timeout = 300
OPENWRT_ARCH = nil
DISTRIB_ARCH = nil
LOG_FILE = "/tmp/log/" .. appname .. ".log"
CACHE_PATH = "/tmp/etc/" .. appname .. "_tmp"
function log(...)
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
local f, err = io.open(LOG_FILE, "a")
if f and err == nil then
f:write(result .. "\n")
f:close()
end
end
function exec_call(cmd)
local process = io.popen(cmd .. '; echo -e "\n$?"')
local lines = {}
local result = ""
local return_code
for line in process:lines() do
lines[#lines + 1] = line
end
process:close()
if #lines > 0 then
return_code = lines[#lines]
for i = 1, #lines - 1 do
result = result .. lines[i] .. ((i == #lines - 1) and "" or "\n")
end
end
return tonumber(return_code), trim(result)
end
function base64Decode(text)
local raw = text
if not text then return '' end
text = text:gsub("%z", "")
text = text:gsub("%c", "")
text = text:gsub("_", "/")
text = text:gsub("-", "+")
local mod4 = #text % 4
text = text .. string.sub('====', mod4 + 1)
local result = nixio.bin.b64decode(text)
if result then
return result:gsub("%z", "")
else
return raw
end
end
function curl_base(url, file, args)
if not args then args = {} end
if file then
args[#args + 1] = "-o " .. file
end
local cmd = string.format('curl %s "%s"', table_join(args), url)
return exec_call(cmd)
end
function curl_proxy(url, file, args)
--使用代理
local socks_server = luci.sys.exec("[ -f /tmp/etc/passwall/TCP_SOCKS_server ] && echo -n $(cat /tmp/etc/passwall/TCP_SOCKS_server) || echo -n ''")
if socks_server ~= "" then
if not args then args = {} end
local tmp_args = clone(args)
tmp_args[#tmp_args + 1] = "-x socks5h://" .. socks_server
return curl_base(url, file, tmp_args)
end
return nil, nil
end
function curl_logic(url, file, args)
local return_code, result = curl_proxy(url, file, args)
if not return_code or return_code ~= 0 then
return_code, result = curl_base(url, file, args)
end
return return_code, result
end
function url(...)
local url = string.format("admin/services/%s", appname)
local args = { ... }
for i, v in pairs(args) do
if v ~= "" then
url = url .. "/" .. v
end
end
return require "luci.dispatcher".build_url(url)
end
function trim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
function is_exist(table, value)
for index, k in ipairs(table) do
if k == value then
return true
end
end
return false
end
function repeat_exist(table, value)
local count = 0
for index, k in ipairs(table) do
if k:find("-") and k == value then
count = count + 1
end
end
if count > 1 then
return true
end
return false
end
function remove(...)
for index, value in ipairs({...}) do
if value and #value > 0 and value ~= "/" then
sys.call(string.format("rm -rf %s", value))
end
end
end
function is_install(package)
if package and #package > 0 then
return sys.call(string.format('opkg list-installed | grep "%s" > /dev/null 2>&1', package)) == 0
end
return false
end
function get_args(arg)
local var = {}
for i, arg_k in pairs(arg) do
if i > 0 then
local v = arg[i + 1]
if v then
if repeat_exist(arg, v) == false then
var[arg_k] = v
end
end
end
end
return var
end
function get_function_args(arg)
local var = nil
if arg and #arg > 1 then
local param = {}
for i = 2, #arg do
param[#param + 1] = arg[i]
end
var = get_args(param)
end
return var
end
function strToTable(str)
if str == nil or type(str) ~= "string" then
return {}
end
return loadstring("return " .. str)()
end
function is_normal_node(e)
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
return false
end
return true
end
function is_special_node(e)
return is_normal_node(e) == false
end
function is_ip(val)
if is_ipv6(val) then
val = get_ipv6_only(val)
end
return datatypes.ipaddr(val)
end
function is_ipv6(val)
local str = val
local address = val:match('%[(.*)%]')
if address then
str = address
end
if datatypes.ip6addr(str) then
return true
end
return false
end
function is_ipv6addrport(val)
if is_ipv6(val) then
local address, port = val:match('%[(.*)%]:([^:]+)$')
if port then
return datatypes.port(port)
end
end
return false
end
function get_ipv6_only(val)
local result = ""
if is_ipv6(val) then
result = val
if val:match('%[(.*)%]') then
result = val:match('%[(.*)%]')
end
end
return result
end
function get_ipv6_full(val)
local result = ""
if is_ipv6(val) then
result = val
if not val:match('%[(.*)%]') then
result = "[" .. result .. "]"
end
end
return result
end
function get_ip_type(val)
if is_ipv6(val) then
return "6"
elseif datatypes.ip4addr(val) then
return "4"
end
return ""
end
function is_mac(val)
return datatypes.macaddr(val)
end
function ip_or_mac(val)
if val then
if get_ip_type(val) == "4" then
return "ip"
end
if is_mac(val) then
return "mac"
end
end
return ""
end
function iprange(val)
if val then
local ipStart, ipEnd = val:match("^([^/]+)-([^/]+)$")
if (ipStart and datatypes.ip4addr(ipStart)) and (ipEnd and datatypes.ip4addr(ipEnd)) then
return true
end
end
return false
end
function get_domain_from_url(url)
local domain = string.match(url, "//([^/]+)")
if domain then
return domain
end
return url
end
function get_valid_nodes()
local nodes_ping = uci_get_type("global_other", "nodes_ping") or ""
local nodes = {}
uci:foreach(appname, "nodes", function(e)
e.id = e[".name"]
if e.type and e.remarks then
if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
e["remark"] = "%s[%s] " % {i18n.translatef(e.type .. e.protocol), e.remarks}
e["node_type"] = "special"
nodes[#nodes + 1] = e
end
if e.port and e.address then
local address = e.address
if is_ip(address) or datatypes.hostname(address) then
local type = e.type
if (type == "V2ray" or type == "Xray") and e.protocol then
local protocol = e.protocol
if protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
else
protocol = protocol:gsub("^%l",string.upper)
end
type = type .. " " .. protocol
end
if is_ipv6(address) then address = get_ipv6_full(address) end
e["remark"] = "%s[%s]" % {type, e.remarks}
if nodes_ping:find("info") then
e["remark"] = "%s[%s] %s:%s" % {type, e.remarks, address, e.port}
end
e.node_type = "normal"
nodes[#nodes + 1] = e
end
end
end
end)
return nodes
end
function get_node_remarks(n)
local remarks = ""
if n then
if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface") then
remarks = "%s[%s] " % {i18n.translatef(n.type .. n.protocol), n.remarks}
else
local type2 = n.type
if (n.type == "V2ray" or n.type == "Xray") and n.protocol then
local protocol = n.protocol
if protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
else
protocol = protocol:gsub("^%l",string.upper)
end
type2 = type2 .. " " .. protocol
end
remarks = "%s[%s]" % {type2, n.remarks}
end
end
return remarks
end
function get_full_node_remarks(n)
local remarks = get_node_remarks(n)
if #remarks > 0 then
if n.address and n.port then
remarks = remarks .. " " .. n.address .. ":" .. n.port
end
end
return remarks
end
function gen_uuid(format)
local uuid = sys.exec("echo -n $(cat /proc/sys/kernel/random/uuid)")
if format == nil then
uuid = string.gsub(uuid, "-", "")
end
return uuid
end
function uci_get_type(type, config, default)
local value = uci:get_first(appname, type, config, default) or sys.exec("echo -n $(uci -q get " .. appname .. ".@" .. type .."[0]." .. config .. ")")
if (value == nil or value == "") and (default and default ~= "") then
value = default
end
return value
end
function uci_get_type_id(id, config, default)
local value = uci:get(appname, id, config, default) or sys.exec("echo -n $(uci -q get " .. appname .. "." .. id .. "." .. config .. ")")
if (value == nil or value == "") and (default and default ~= "") then
value = default
end
return value
end
local function chmod_755(file)
if file and file ~= "" then
if not fs.access(file, "rwx", "rx", "rx") then
fs.chmod(file, 755)
end
end
end
function get_customed_path(e)
return uci_get_type("global_app", e .. "_file")
end
function is_finded(e)
return luci.sys.exec('type -t -p "/bin/%s" -p "/usr/bin/%s" -p "%s" "%s"' % {e, e, get_customed_path(e), e}) ~= "" and true or false
end
function clone(org)
local function copy(org, res)
for k,v in pairs(org) do
if type(v) ~= "table" then
res[k] = v;
else
res[k] = {};
copy(v, res[k])
end
end
end
local res = {}
copy(org, res)
return res
end
local function get_bin_version_cache(file, cmd)
sys.call("mkdir -p /tmp/etc/passwall_tmp")
if fs.access(file) then
chmod_755(file)
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
if fs.access("/tmp/etc/passwall_tmp/" .. md5) then
return sys.exec("echo -n $(cat /tmp/etc/passwall_tmp/%s)" % md5)
else
local version = sys.exec(string.format("echo -n $(%s %s)", file, cmd))
if version and version ~= "" then
sys.call("echo '" .. version .. "' > " .. "/tmp/etc/passwall_tmp/" .. md5)
return version
end
end
end
return ""
end
function get_app_path(app_name)
local def_path = com[app_name].default_path
local path = uci_get_type("global_app", app_name:gsub("%-","_") .. "_file")
path = path and (#path>0 and path or def_path) or def_path
return path
end
function get_app_version(app_name, file)
if file == nil then file = get_app_path(app_name) end
return get_bin_version_cache(file, com[app_name].cmd_version)
end
local function is_file(path)
if path and #path > 1 then
if sys.exec('[ -f "%s" ] && echo -n 1' % path) == "1" then
return true
end
end
return nil
end
local function is_dir(path)
if path and #path > 1 then
if sys.exec('[ -d "%s" ] && echo -n 1' % path) == "1" then
return true
end
end
return nil
end
local function get_final_dir(path)
if is_dir(path) then
return path
else
return get_final_dir(fs.dirname(path))
end
end
local function get_free_space(dir)
if dir == nil then dir = "/" end
if sys.call("df -k " .. dir .. " >/dev/null 2>&1") == 0 then
return tonumber(sys.exec("echo -n $(df -k " .. dir .. " | awk 'NR>1' | awk '{print $4}')"))
end
return 0
end
local function get_file_space(file)
if file == nil then return 0 end
if fs.access(file) then
return tonumber(sys.exec("echo -n $(du -k " .. file .. " | awk '{print $1}')"))
end
return 0
end
function _unpack(t, i)
i = i or 1
if t[i] ~= nil then return t[i], _unpack(t, i + 1) end
end
function table_join(t, s)
if not s then
s = " "
end
local str = ""
for index, value in ipairs(t) do
str = str .. t[index] .. (index == #t and "" or s)
end
return str
end
local function exec(cmd, args, writer, timeout)
local os = require "os"
local nixio = require "nixio"
local fdi, fdo = nixio.pipe()
local pid = nixio.fork()
if pid > 0 then
fdo:close()
if writer or timeout then
local starttime = os.time()
while true do
if timeout and os.difftime(os.time(), starttime) >= timeout then
nixio.kill(pid, nixio.const.SIGTERM)
return 1
end
if writer then
local buffer = fdi:read(2048)
if buffer and #buffer > 0 then
writer(buffer)
end
end
local wpid, stat, code = nixio.waitpid(pid, "nohang")
if wpid and stat == "exited" then return code end
if not writer and timeout then nixio.nanosleep(1) end
end
else
local wpid, stat, code = nixio.waitpid(pid)
return wpid and stat == "exited" and code
end
elseif pid == 0 then
nixio.dup(fdo, nixio.stdout)
fdi:close()
fdo:close()
nixio.exece(cmd, args, nil)
nixio.stdout:close()
os.exit(1)
end
end
function compare_versions(ver1, comp, ver2)
local table = table
if not ver1 then ver1 = "" end
if not ver2 then ver2 = "" end
local av1 = util.split(ver1, "[%.%-]", nil, true)
local av2 = util.split(ver2, "[%.%-]", nil, true)
local max = table.getn(av1)
local n2 = table.getn(av2)
if (max < n2) then max = n2 end
for i = 1, max, 1 do
local s1 = tonumber(av1[i] or 0) or 0
local s2 = tonumber(av2[i] or 0) or 0
if comp == "~=" and (s1 ~= s2) then return true end
if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
if (s1 ~= s2) then return false end
end
return not (comp == "<" or comp == ">")
end
local function auto_get_arch()
local arch = nixio.uname().machine or ""
if not OPENWRT_ARCH and fs.access("/usr/lib/os-release") then
OPENWRT_ARCH = sys.exec("echo -n $(grep 'OPENWRT_ARCH' /usr/lib/os-release | awk -F '[\\042\\047]' '{print $2}')")
if OPENWRT_ARCH == "" then OPENWRT_ARCH = nil end
end
if not DISTRIB_ARCH and fs.access("/etc/openwrt_release") then
DISTRIB_ARCH = sys.exec("echo -n $(grep 'DISTRIB_ARCH' /etc/openwrt_release | awk -F '[\\042\\047]' '{print $2}')")
if DISTRIB_ARCH == "" then DISTRIB_ARCH = nil end
end
if arch:match("^i[%d]86$") then
arch = "x86"
elseif arch:match("armv5") then -- armv5l
arch = "armv5"
elseif arch:match("armv6") then
arch = "armv6"
elseif arch:match("armv7") then -- armv7l
arch = "armv7"
end
if OPENWRT_ARCH or DISTRIB_ARCH then
if arch == "mips" then
if OPENWRT_ARCH and OPENWRT_ARCH:match("mipsel") == "mipsel"
or DISTRIB_ARCH and DISTRIB_ARCH:match("mipsel") == "mipsel" then
arch = "mipsel"
end
elseif arch == "armv7" then
if OPENWRT_ARCH and not OPENWRT_ARCH:match("vfp") and not OPENWRT_ARCH:match("neon")
or DISTRIB_ARCH and not DISTRIB_ARCH:match("vfp") and not DISTRIB_ARCH:match("neon") then
arch = "armv5"
end
end
end
return util.trim(arch)
end
function parseURL(url)
if not url or url == "" then
return nil
end
local pattern = "^(%w+)://"
local protocol = url:match(pattern)
if not protocol then
--error("Invalid URL: " .. url)
return nil
end
local auth_host_port = url:sub(#protocol + 4)
local auth_pattern = "^([^@]+)@"
local auth = auth_host_port:match(auth_pattern)
local username, password
if auth then
username, password = auth:match("^([^:]+):([^:]+)$")
auth_host_port = auth_host_port:sub(#auth + 2)
end
local host, port = auth_host_port:match("^([^:]+):(%d+)$")
if not host or not port then
--error("Invalid URL: " .. url)
return nil
end
return {
protocol = protocol,
username = username,
password = password,
host = host,
port = tonumber(port)
}
end
local default_file_tree = {
x86_64 = "amd64",
x86 = "386",
aarch64 = "arm64",
mips = "mips",
mipsel = "mipsle",
armv5 = "arm.*5",
armv6 = "arm.*6[^4]*",
armv7 = "arm.*7",
armv8 = "arm64"
}
local function get_api_json(url)
local jsonc = require "luci.jsonc"
local return_code, content = curl_logic(url, nil, curl_args)
if return_code ~= 0 or content == "" then return {} end
return jsonc.parse(content) or {}
end
local function check_path(app_name)
local path = get_app_path(app_name) or ""
if path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", app_name)
}
end
return {
code = 0,
app_path = path
}
end
function to_check(arch, app_name)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = auto_get_arch() end
local file_tree = com[app_name].file_tree[arch] or default_file_tree[arch] or ""
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
local local_version = get_app_version(app_name)
local match_file_name = string.format(com[app_name].match_fmt_str, file_tree)
local json = get_api_json(com[app_name]:get_url())
if #json > 0 then
json = json[1]
end
if json.tag_name == nil then
return {
code = 1,
error = i18n.translate("Get remote version info failed.")
}
end
local remote_version = json.tag_name
local has_update = compare_versions(local_version:match("[^v]+"), "<", remote_version:match("[^v]+"))
if not has_update then
return {
code = 0,
local_version = local_version,
remote_version = remote_version
}
end
local asset = {}
for _, v in ipairs(json.assets) do
if v.name and v.name:match(match_file_name) then
asset = v
break
end
end
if not asset.browser_download_url then
return {
code = 1,
local_version = local_version,
remote_version = remote_version,
html_url = json.html_url,
data = asset,
error = i18n.translate("New version found, but failed to get new version download url.")
}
end
return {
code = 0,
has_update = true,
local_version = local_version,
remote_version = remote_version,
html_url = json.html_url,
data = asset
}
end
function to_download(app_name, url, size)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
if not url or url == "" then
return {code = 1, error = i18n.translate("Download url is required.")}
end
sys.call("/bin/rm -f /tmp/".. app_name .."_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t ".. app_name .."_download.XXXXXX"))
if size then
local kb1 = get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
local return_code, result = curl_logic(url, tmp_file, curl_args)
result = return_code == 0
if not result then
exec("/bin/rm", {"-f", tmp_file})
return {
code = 1,
error = i18n.translatef("File download failed or timed out: %s", url)
}
end
return {code = 0, file = tmp_file, zip = com[app_name].zipped }
end
function to_extract(app_name, file, subfix)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
if not file or file == "" or not fs.access(file) then
return {code = 1, error = i18n.translate("File path required.")}
end
if sys.exec("echo -n $(opkg list-installed | grep -c unzip)") ~= "1" then
exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
sys.call("/bin/rm -rf /tmp/".. app_name .."_extract.*")
local new_file_size = get_file_space(file)
local tmp_free_size = get_free_space("/tmp")
if tmp_free_size <= 0 or tmp_free_size <= new_file_size then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
local tmp_dir = util.trim(util.exec("mktemp -d -t ".. app_name .."_extract.XXXXXX"))
local output = {}
exec("/usr/bin/unzip", {"-o", file, app_name, "-d", tmp_dir},
function(chunk) output[#output + 1] = chunk end)
local files = util.split(table.concat(output))
exec("/bin/rm", {"-f", file})
return {code = 0, file = tmp_dir}
end
function to_move(app_name,file)
local result = check_path(app_name)
if result.code ~= 0 then
return result
end
local app_path = result.app_path
local bin_path = file
local cmd_rm_tmp = "/bin/rm -rf /tmp/" .. app_name .. "_download.*"
if fs.stat(file, "type") == "dir" then
bin_path = file .. "/" .. app_name
cmd_rm_tmp = "/bin/rm -rf /tmp/" .. app_name .. "_extract.*"
end
if not file or file == "" then
sys.call(cmd_rm_tmp)
return {code = 1, error = i18n.translate("Client file is required.")}
end
local new_version = get_app_version(app_name, bin_path)
if new_version == "" then
sys.call(cmd_rm_tmp)
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")..app_name.."__"..bin_path
}
end
local flag = sys.call('pgrep -af "passwall/.*'.. app_name ..'" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = get_file_space(app_path)
end
local new_app_size = get_file_space(bin_path)
local final_dir = get_final_dir(app_path)
local final_dir_free_size = get_free_space(final_dir)
if final_dir_free_size > 0 then
final_dir_free_size = final_dir_free_size + old_app_size
if new_app_size > final_dir_free_size then
sys.call(cmd_rm_tmp)
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = exec("/bin/mv", { "-f", bin_path, app_path }, nil, command_timeout) == 0
sys.call(cmd_rm_tmp)
if flag == 0 then
sys.call("/etc/init.d/passwall restart >/dev/null 2>&1 &")
end
if not result or not fs.access(app_path) then
return {
code = 1,
error = i18n.translatef("Can't move new file to path: %s", app_path)
}
end
return {code = 0}
end
function get_version()
return sys.exec("echo -n $(opkg info luci-app-passwall |grep 'Version'|awk '{print $2}')")
end
function to_check_self()
local url = "https://raw.githubusercontent.com/xiaorouji/openwrt-passwall/luci/luci-app-passwall/Makefile"
local tmp_file = "/tmp/passwall_makefile"
local return_code, result = curl_logic(url, tmp_file, curl_args)
result = return_code == 0
if not result then
exec("/bin/rm", {"-f", tmp_file})
return {
code = 1,
error = i18n.translatef("Failed")
}
end
local local_version = get_version()
local remote_version = sys.exec("echo -n $(grep 'PKG_VERSION' /tmp/passwall_makefile|awk -F '=' '{print $2}')")
local has_update = compare_versions(local_version, "<", remote_version)
if not has_update then
return {
code = 0,
local_version = local_version,
remote_version = remote_version
}
end
return {
code = 1,
has_update = true,
local_version = local_version,
remote_version = remote_version,
error = i18n.translatef("The latest version: %s, currently does not support automatic update, if you need to update, please compile or download the ipk and then manually install.", remote_version)
}
end

View File

@ -0,0 +1,99 @@
local _M = {}
local function gh_release_url(self)
return "https://api.github.com/repos/" .. self.repo .. "/releases/latest"
end
local function gh_pre_release_url(self)
return "https://api.github.com/repos/" .. self.repo .. "/releases?per_page=1"
end
_M.brook = {
name = "Brook",
repo = "txthinking/brook",
get_url = gh_release_url,
cmd_version = "-v | awk '{print $3}'",
zipped = false,
default_path = "/usr/bin/brook",
match_fmt_str = "linux_%s$",
file_tree = {}
}
_M.hysteria = {
name = "Hysteria",
repo = "HyNetwork/hysteria",
get_url = gh_release_url,
cmd_version = "-v | awk '{print $3}'",
zipped = false,
default_path = "/usr/bin/hysteria",
match_fmt_str = "linux%%-%s$",
file_tree = {
armv6 = "arm",
armv7 = "arm"
}
}
_M["trojan-go"] = {
name = "Trojan-Go",
repo = "p4gefau1t/trojan-go",
get_url = gh_release_url,
cmd_version = "-version | awk '{print $2}' | sed -n 1P",
zipped = true,
default_path = "/usr/bin/trojan-go",
match_fmt_str = "linux%%-%s%%.zip",
file_tree = {
aarch64 = "armv8",
armv8 = "armv8",
mips = "mips%-hardfloat",
mipsel = "mipsle%-hardfloat"
}
}
_M.v2ray = {
name = "V2ray",
repo = "v2fly/v2ray-core",
get_url = gh_pre_release_url,
cmd_version = "version | awk '{print $2}' | sed -n 1P",
zipped = true,
default_path = "/usr/bin/v2ray",
match_fmt_str = "linux%%-%s",
file_tree = {
x86_64 = "64",
x86 = "32",
mips = "mips32",
mipsel = "mips32le"
}
}
_M.xray = {
name = "Xray",
repo = "XTLS/Xray-core",
get_url = gh_pre_release_url,
cmd_version = _M.v2ray.cmd_version,
zipped = true,
default_path = "/usr/bin/xray",
match_fmt_str = _M.v2ray.match_fmt_str,
file_tree = _M.v2ray.file_tree
}
_M["chinadns-ng"] = {
name = "ChinaDNS-NG",
repo = "zfl9/chinadns-ng",
get_url = gh_release_url,
cmd_version = "-V | awk '{print $2}'",
zipped = false,
default_path = "/usr/bin/chinadns-ng",
match_fmt_str = "%s$",
file_tree = {
x86_64 = "x86_64",
x86 = "i686",
mipsel = "mipsel",
aarch64 = "aarch64",
armv5 = "arm%-eabi",
armv6 = "armv6%-eabihf",
armv7 = "armv7l%-eabihf",
armv8 = "aarch64"
}
}
return _M

View File

@ -0,0 +1,232 @@
#!/usr/bin/lua
local action = arg[1]
local api = require "luci.passwall.api"
local sys = api.sys
local uci = api.uci
local jsonc = api.jsonc
local CONFIG = "passwall_server"
local CONFIG_PATH = "/tmp/etc/" .. CONFIG
local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log"
local TMP_BIN_PATH = CONFIG_PATH .. "/bin"
local require_dir = "luci.passwall."
local ipt_bin = sys.exec("echo -n $(/usr/share/passwall/iptables.sh get_ipt_bin)")
local ip6t_bin = sys.exec("echo -n $(/usr/share/passwall/iptables.sh get_ip6t_bin)")
local nft_flag = api.is_finded("fw4") and "1" or "0"
local function log(...)
local f, err = io.open(LOG_APP_FILE, "a")
if f and err == nil then
local str = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
f:write(str .. "\n")
f:close()
end
end
local function cmd(cmd)
sys.call(cmd)
end
local function ipt(arg)
cmd(ipt_bin .. " -w " .. arg)
end
local function ip6t(arg)
cmd(ip6t_bin .. " -w " .. arg)
end
local function ln_run(s, d, command, output)
if not output then
output = "/dev/null"
end
d = TMP_BIN_PATH .. "/" .. d
cmd(string.format('[ ! -f "%s" ] && ln -s %s %s 2>/dev/null', d, s, d))
return string.format("%s >%s 2>&1 &", d .. " " .. command, output)
end
local function gen_include()
cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG))
if nft_flag == "1" then
cmd("echo \"\" > " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft")
local nft_cmd = "for chain in $(nft -a list chains |grep -E \"chain PSW-SERVER\" |awk -F ' ' '{print$2}'); do\n nft list chain inet fw4 ${chain} >> " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft\n done"
cmd(nft_cmd)
end
local function extract_rules(n, a)
local _ipt = ipt_bin
if n == "6" then
_ipt = ip6t_bin
end
local result = "*" .. a
result = result .. "\n" .. sys.exec(_ipt .. '-save -t ' .. a .. ' | grep "PSW-SERVER" | sed -e "s/^-A \\(INPUT\\)/-I \\1 1/"')
result = result .. "COMMIT"
return result
end
local f, err = io.open("/tmp/etc/" .. CONFIG .. ".include", "a")
if f and err == nil then
if nft_flag == "0" then
f:write(ipt_bin .. '-save -c | grep -v "PSW-SERVER" | ' .. ipt_bin .. '-restore -c' .. "\n")
f:write(ipt_bin .. '-restore -n <<-EOT' .. "\n")
f:write(extract_rules("4", "filter") .. "\n")
f:write("EOT" .. "\n")
f:write(ip6t_bin .. '-save -c | grep -v "PSW-SERVER" | ' .. ip6t_bin .. '-restore -c' .. "\n")
f:write(ip6t_bin .. '-restore -n <<-EOT' .. "\n")
f:write(extract_rules("6", "filter") .. "\n")
f:write("EOT" .. "\n")
f:close()
else
f:write("nft -f " .. CONFIG_PATH .. "/" .. CONFIG .. ".nft\n")
f:write("nft insert rule inet fw4 input position 0 counter jump PSW-SERVER")
f:close()
end
end
end
local function start()
local enabled = tonumber(uci:get(CONFIG, "@global[0]", "enable") or 0)
if enabled == nil or enabled == 0 then
return
end
cmd(string.format("mkdir -p %s %s", CONFIG_PATH, TMP_BIN_PATH))
cmd(string.format("touch %s", LOG_APP_FILE))
if nft_flag == "0" then
ipt("-N PSW-SERVER")
ipt("-I INPUT -j PSW-SERVER")
ip6t("-N PSW-SERVER")
ip6t("-I INPUT -j PSW-SERVER")
else
cmd("nft add chain inet fw4 PSW-SERVER\n")
cmd("nft insert rule inet fw4 input position 0 counter jump PSW-SERVER")
end
uci:foreach(CONFIG, "user", function(user)
local id = user[".name"]
local enable = user.enable
if enable and tonumber(enable) == 1 then
local enable_log = user.log
local log_path = nil
if enable_log and enable_log == "1" then
log_path = CONFIG_PATH .. "/" .. id .. ".log"
else
log_path = nil
end
local remarks = user.remarks
local port = tonumber(user.port)
local bin
local config = {}
local config_file = CONFIG_PATH .. "/" .. id .. ".json"
local udp_forward = 1
local type = user.type or ""
if type == "Socks" then
local auth = ""
if user.auth and user.auth == "1" then
local username = user.username or ""
local password = user.password or ""
if username ~= "" and password ~= "" then
username = "-u " .. username
password = "-P " .. password
auth = username .. " " .. password
end
end
bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path)
elseif type == "SS" or type == "SSR" then
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
local udp_param = ""
udp_forward = tonumber(user.udp_forward) or 1
if udp_forward == 1 then
udp_param = "-u"
end
type = type:lower()
bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path)
elseif type == "SS-Rust" then
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
elseif type == "V2ray" then
config = require(require_dir .. "util_xray").gen_config_server(user)
bin = ln_run(api.get_app_path("v2ray"), "v2ray", "run -c " .. config_file, log_path)
elseif type == "Xray" then
config = require(require_dir .. "util_xray").gen_config_server(user)
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
elseif type == "Trojan" then
config = require(require_dir .. "util_trojan").gen_config_server(user)
bin = ln_run("/usr/sbin/trojan", "trojan", "-c " .. config_file, log_path)
elseif type == "Trojan-Plus" then
config = require(require_dir .. "util_trojan").gen_config_server(user)
bin = ln_run("/usr/sbin/trojan-plus", "trojan-plus", "-c " .. config_file, log_path)
elseif type == "Trojan-Go" then
config = require(require_dir .. "util_trojan").gen_config_server(user)
bin = ln_run(api.get_app_path("trojan-go"), "trojan-go", "-config " .. config_file, log_path)
elseif type == "Brook" then
local brook_protocol = user.protocol
local brook_password = user.password
local brook_path = user.ws_path or "/ws"
local brook_path_arg = ""
if brook_protocol == "wsserver" and brook_path then
brook_path_arg = " --path " .. brook_path
end
bin = ln_run(api.get_app_path("brook"), "brook_" .. id, string.format("--debug %s -l :%s -p %s%s", brook_protocol, port, brook_password, brook_path_arg), log_path)
elseif type == "Hysteria" then
config = require(require_dir .. "util_hysteria").gen_config_server(user)
bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path)
end
if next(config) then
local f, err = io.open(config_file, "w")
if f and err == nil then
f:write(jsonc.stringify(config, 1))
f:close()
end
log(string.format("%s %s 生成配置文件并运行 - %s", remarks, port, config_file))
end
if bin then
cmd(bin)
end
local bind_local = user.bind_local or 0
if bind_local and tonumber(bind_local) ~= 1 then
if nft_flag == "0" then
ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
ip6t(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
if udp_forward == 1 then
ipt(string.format('-A PSW-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
ip6t(string.format('-A PSW-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
end
else
cmd(string.format('nft add rule inet fw4 PSW-SERVER meta l4proto tcp tcp dport {%s} accept', port))
if udp_forward == 1 then
cmd(string.format('nft add rule inet fw4 PSW-SERVER meta l4proto udp udp dport {%s} accept', port))
end
end
end
end
end)
gen_include()
end
local function stop()
cmd(string.format("top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH))
if nft_flag == "0" then
ipt("-D INPUT -j PSW-SERVER 2>/dev/null")
ipt("-F PSW-SERVER 2>/dev/null")
ipt("-X PSW-SERVER 2>/dev/null")
ip6t("-D INPUT -j PSW-SERVER 2>/dev/null")
ip6t("-F PSW-SERVER 2>/dev/null")
ip6t("-X PSW-SERVER 2>/dev/null")
else
local nft_cmd = "handles=$(nft -a list chain inet fw4 input | grep -E \"PSW-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done"
cmd(nft_cmd)
cmd("nft flush chain inet fw4 PSW-SERVER 2>/dev/null")
cmd("nft delete chain inet fw4 PSW-SERVER 2>/dev/null")
end
cmd(string.format("rm -rf %s %s /tmp/etc/%s.include", CONFIG_PATH, LOG_APP_FILE, CONFIG))
end
if action then
if action == "start" then
start()
elseif action == "stop" then
stop()
end
end

View File

@ -0,0 +1,119 @@
module("luci.passwall.util_hysteria", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
local config = {
listen = ":" .. node.port,
protocol = node.protocol or "udp",
obfs = node.hysteria_obfs,
cert = node.tls_certificateFile,
key = node.tls_keyFile,
auth = (node.hysteria_auth_type == "string") and {
mode = "password",
config = {
password = node.hysteria_auth_password
}
} or nil,
disable_udp = (node.hysteria_udp == "0") and true or false,
alpn = node.hysteria_alpn or nil,
up_mbps = tonumber(node.hysteria_up_mbps) or 10,
down_mbps = tonumber(node.hysteria_down_mbps) or 50,
recv_window_conn = (node.hysteria_recv_window_conn) and tonumber(node.hysteria_recv_window_conn) or nil,
recv_window = (node.hysteria_recv_window) and tonumber(node.hysteria_recv_window) or nil,
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery) and true or false
}
return config
end
function gen_config(var)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
local local_tcp_redir_port = var["-local_tcp_redir_port"]
local local_udp_redir_port = var["-local_udp_redir_port"]
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local tcp_proxy_way = var["-tcp_proxy_way"]
local server_host = var["-server_host"] or node.address
local server_port = var["-server_port"] or node.port
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_full(server_host)
end
local server = server_host .. ":" .. server_port
if (node.hysteria_hop) then
server = server .. "," .. node.hysteria_hop
end
local config = {
server = server,
protocol = node.protocol or "udp",
obfs = node.hysteria_obfs,
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
alpn = node.hysteria_alpn or nil,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
up_mbps = tonumber(node.hysteria_up_mbps) or 10,
down_mbps = tonumber(node.hysteria_down_mbps) or 50,
retry = -1,
retry_interval = 5,
recv_window_conn = (node.hysteria_recv_window_conn) and tonumber(node.hysteria_recv_window_conn) or nil,
recv_window = (node.hysteria_recv_window) and tonumber(node.hysteria_recv_window) or nil,
handshake_timeout = (node.hysteria_handshake_timeout) and tonumber(node.hysteria_handshake_timeout) or nil,
idle_timeout = (node.hysteria_idle_timeout) and tonumber(node.hysteria_idle_timeout) or nil,
hop_interval = (node.hysteria_hop_interval) and tonumber(node.hysteria_hop_interval) or nil,
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery) and true or false,
fast_open = (node.fast_open == "1") and true or false,
lazy_start = (node.hysteria_lazy_start) and true or false,
socks5 = (local_socks_address and local_socks_port) and {
listen = local_socks_address .. ":" .. local_socks_port,
timeout = 300,
disable_udp = false,
user = (local_socks_username and local_socks_password) and local_socks_username,
password = (local_socks_username and local_socks_password) and local_socks_password,
} or nil,
http = (local_http_address and local_http_port) and {
listen = local_http_address .. ":" .. local_http_port,
timeout = 300,
disable_udp = false,
user = (local_http_username and local_http_password) and local_http_username,
password = (local_http_username and local_http_password) and local_http_password,
} or nil,
redirect_tcp = ("redirect" == tcp_proxy_way and local_tcp_redir_port) and {
listen = "0.0.0.0:" .. local_tcp_redir_port,
timeout = 300
} or nil,
tproxy_tcp = ("tproxy" == tcp_proxy_way and local_tcp_redir_port) and {
listen = "0.0.0.0:" .. local_tcp_redir_port,
timeout = 300
} or nil,
tproxy_udp = (local_udp_redir_port) and {
listen = "0.0.0.0:" .. local_udp_redir_port,
timeout = 60
} or nil
}
return jsonc.stringify(config, 1)
end
_G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
end
end

View File

@ -0,0 +1,39 @@
module("luci.passwall.util_naiveproxy", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
local jsonc = api.jsonc
function gen_config(var)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
local run_type = var["-run_type"]
local local_addr = var["-local_addr"]
local local_port = var["-local_port"]
local server_host = var["-server_host"] or node.address
local server_port = var["-server_port"] or node.port
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_full(server_host)
end
local server = server_host .. ":" .. server_port
local config = {
listen = run_type .. "://" .. local_addr .. ":" .. local_port,
proxy = node.protocol .. "://" .. node.username .. ":" .. node.password .. "@" .. server
}
return jsonc.stringify(config, 1)
end
_G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
end
end

View File

@ -0,0 +1,144 @@
module("luci.passwall.util_shadowsocks", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
local jsonc = api.jsonc
function gen_config_server(node)
local config = {}
config.server_port = tonumber(node.port)
config.password = node.password
config.timeout = tonumber(node.timeout)
config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
config.method = node.method
if node.type == "SS-Rust" then
config.server = "::"
config.mode = "tcp_and_udp"
else
config.server = {"[::0]", "0.0.0.0"}
end
if node.type == "SSR" then
config.protocol = node.protocol
config.protocol_param = node.protocol_param
config.obfs = node.obfs
config.obfs_param = node.obfs_param
end
return config
end
function gen_config(var)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
local server_host = var["-server_host"] or node.address
local server_port = var["-server_port"] or node.port
local local_addr = var["-local_addr"]
local local_port = var["-local_port"]
local mode = var["-mode"]
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local local_tcp_redir_port = var["-local_tcp_redir_port"]
local local_tcp_redir_address = var["-local_tcp_redir_address"] or "0.0.0.0"
local local_udp_redir_port = var["-local_udp_redir_port"]
local local_udp_redir_address = var["-local_udp_redir_address"] or "0.0.0.0"
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_only(server_host)
end
local server = server_host
local config = {
server = server,
server_port = tonumber(server_port),
local_address = local_addr,
local_port = tonumber(local_port),
password = node.password,
method = node.method,
timeout = tonumber(node.timeout),
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
reuse_port = true,
tcp_tproxy = var["-tcp_tproxy"] and true or nil
}
if node.type == "SS" then
if node.plugin and node.plugin ~= "none" then
config.plugin = node.plugin
config.plugin_opts = node.plugin_opts or nil
end
config.mode = mode
elseif node.type == "SSR" then
config.protocol = node.protocol
config.protocol_param = node.protocol_param
config.obfs = node.obfs
config.obfs_param = node.obfs_param
elseif node.type == "SS-Rust" then
config = {
servers = {
{
address = server,
port = tonumber(server_port),
method = node.method,
password = node.password,
timeout = tonumber(node.timeout),
plugin = (node.plugin and node.plugin ~= "none") and node.plugin or nil,
plugin_opts = (node.plugin and node.plugin ~= "none") and node.plugin_opts or nil
}
},
locals = {},
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false
}
if local_socks_address and local_socks_port then
table.insert(config.locals, {
local_address = local_socks_address,
local_port = tonumber(local_socks_port),
mode = "tcp_and_udp"
})
end
if local_http_address and local_http_port then
table.insert(config.locals, {
protocol = "http",
local_address = local_http_address,
local_port = tonumber(local_http_port)
})
end
if local_tcp_redir_address and local_tcp_redir_port then
table.insert(config.locals, {
protocol = "redir",
mode = "tcp_only",
tcp_redir = var["-tcp_tproxy"] and "tproxy" or nil,
local_address = local_tcp_redir_address,
local_port = tonumber(local_tcp_redir_port)
})
end
if local_udp_redir_address and local_udp_redir_port then
table.insert(config.locals, {
protocol = "redir",
mode = "udp_only",
local_address = local_udp_redir_address,
local_port = tonumber(local_udp_redir_port)
})
end
end
return jsonc.stringify(config, 1)
end
_G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
end
end

View File

@ -0,0 +1,158 @@
module("luci.passwall.util_trojan", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
local json = api.jsonc
function gen_config_server(node)
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
local config = {
run_type = "server",
local_addr = "::",
local_port = tonumber(node.port),
remote_addr = (node.remote_enable == "1" and node.remote_address) and node.remote_address or nil,
remote_port = (node.remote_enable == "1" and node.remote_port) and tonumber(node.remote_port) or nil,
password = node.uuid,
log_level = (node.log and node.log == "1") and tonumber(node.loglevel) or 5,
ssl = {
cert = node.tls_certificateFile,
key = node.tls_keyFile,
key_password = "",
cipher = cipher,
cipher_tls13 = cipher13,
prefer_server_cipher = true,
reuse_session = true,
session_ticket = (node.tls_sessionTicket == "1") and true or false,
session_timeout = 600,
plain_http_response = "",
curves = "",
dhparam = ""
},
tcp = {
prefer_ipv4 = false,
no_delay = true,
keep_alive = true,
reuse_port = false,
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
fast_open_qlen = 20
}
}
if node.type == "Trojan-Go" then
config.ssl.cipher = nil
config.ssl.cipher_tls13 = nil
config.udp_timeout = 60
config.disable_http_check = true
config.transport_plugin = ((node.tls == nil or node.tls ~= "1") and node.trojan_transport == "original") and {
enabled = node.plugin_type ~= nil,
type = node.plugin_type or "plaintext",
command = node.plugin_type ~= "plaintext" and node.plugin_cmd or nil,
option = node.plugin_type ~= "plaintext" and node.plugin_option or nil,
arg = node.plugin_type ~= "plaintext" and { node.plugin_arg } or nil,
env = {}
} or nil
config.websocket = (node.trojan_transport == 'ws') and {
enabled = true,
path = node.ws_path or "/",
host = node.ws_host or ""
} or nil
config.shadowsocks = (node.ss_aead == "1") and {
enabled = true,
method = node.ss_aead_method or "aes_128_gcm",
password = node.ss_aead_pwd or ""
} or nil
end
return config
end
function gen_config(var)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
local run_type = var["-run_type"]
local local_addr = var["-local_addr"]
local local_port = var["-local_port"]
local server_host = var["-server_host"] or node.address
local server_port = var["-server_port"] or node.port
local loglevel = var["-loglevel"] or 2
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_only(server_host)
end
local server = server_host
local trojan = {
run_type = run_type,
local_addr = local_addr,
local_port = tonumber(local_port),
remote_addr = server,
remote_port = tonumber(server_port),
password = {node.password},
log_level = tonumber(loglevel),
ssl = {
verify = (node.tls_allowInsecure ~= "1") and true or false,
verify_hostname = true,
cert = nil,
cipher = cipher,
cipher_tls13 = cipher13,
sni = node.tls_serverName or server,
alpn = {"h2", "http/1.1"},
reuse_session = true,
session_ticket = (node.tls_sessionTicket and node.tls_sessionTicket == "1") and true or false,
curves = ""
},
udp_timeout = 60,
tcp = {
use_tproxy = (node.type == "Trojan-Plus" and var["-use_tproxy"]) and true or nil,
no_delay = true,
keep_alive = true,
reuse_port = true,
fast_open = (node.tcp_fast_open == "true") and true or false,
fast_open_qlen = 20
}
}
if node.type == "Trojan-Go" then
trojan.ssl.cipher = nil
trojan.ssl.cipher_tls13 = nil
trojan.ssl.fingerprint = (node.fingerprint ~= "disable") and node.fingerprint or ""
trojan.ssl.alpn = (node.trojan_transport == 'ws') and {} or {"h2", "http/1.1"}
if node.tls ~= "1" and node.trojan_transport == "original" then trojan.ssl = nil end
trojan.transport_plugin = ((not node.tls or node.tls ~= "1") and node.trojan_transport == "original") and {
enabled = node.plugin_type ~= nil,
type = node.plugin_type or "plaintext",
command = node.plugin_type ~= "plaintext" and node.plugin_cmd or nil,
option = node.plugin_type ~= "plaintext" and node.plugin_option or nil,
arg = node.plugin_type ~= "plaintext" and { node.plugin_arg } or nil,
env = {}
} or nil
trojan.websocket = (node.trojan_transport == 'ws') and {
enabled = true,
path = node.ws_path or "/",
host = node.ws_host or (node.tls_serverName or server)
} or nil
trojan.shadowsocks = (node.ss_aead == "1") and {
enabled = true,
method = node.ss_aead_method or "aes_128_gcm",
password = node.ss_aead_pwd or ""
} or nil
trojan.mux = (node.smux == "1") and {
enabled = true,
concurrency = tonumber(node.mux_concurrency),
idle_timeout = tonumber(node.smux_idle_timeout)
} or nil
end
return json.stringify(trojan, 1)
end
_G.gen_config = gen_config
if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,204 @@
<%
local api = require "luci.passwall.api"
local com = require "luci.passwall.com"
local version = {}
-%>
<script type="text/javascript">
//<![CDATA[
var appInfoList = new Array();
var inProgressCount = 0;
var tokenStr = '<%=token%>';
var checkUpdateText = '<%:Check update%>';
var noUpdateText = '<%:It is the latest version%>';
var updateSuccessText = '<%:Update successful%>';
var clickToUpdateText = '<%:Click to update%>';
var inProgressText = '<%:Updating...%>';
var unexpectedErrorText = '<%:Unexpected error%>';
var updateInProgressNotice = '<%:Updating, are you sure to close?%>';
var downloadingText = '<%:Downloading...%>';
var decompressioningText = '<%:Unpacking...%>';
var movingText = '<%:Moving...%>';
//window.onload = function () {};
function addPageNotice() {
if (inProgressCount === 0) {
window.onbeforeunload = function (e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
inProgressCount++;
}
function removePageNotice() {
inProgressCount--;
if (inProgressCount === 0) {
window.onbeforeunload = undefined;
}
}
function onUpdateSuccess(btn) {
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
if (inProgressCount === 0) {
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
}
function onRequestError(btn, errorMessage) {
btn.disabled = false;
btn.value = checkUpdateText;
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
if (errorMessage && ckeckDetailElm) {
ckeckDetailElm.textContent = errorMessage
}
}
function onBtnClick(btn, app) {
if (appInfoList[app] === undefined) {
checkUpdate(btn, app);
} else {
doUpdate(btn, app);
}
}
function checkUpdate(btn, app) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
if (ckeckDetailElm) {
ckeckDetailElm.textContent = "";
}
XHR.get('<%=api.url("check_")%>' + app, {
token: tokenStr,
arch: ''
}, function (x, json) {
removePageNotice();
if (json.code) {
appInfoList[app] = undefined;
onRequestError(btn, json.error);
} else {
if (json.has_update) {
appInfoList[app] = json;
btn.disabled = false;
btn.value = clickToUpdateText;
btn.placeholder = clickToUpdateText;
if (ckeckDetailElm) {
var urlNode = '';
if (json.remote_version) {
urlNode = '<em style="color:red;">' + json.remote_version + '</em>';
if (json.html_url) {
urlNode = '<a href="' + json.html_url + '" target="_blank">' + urlNode + '</a>';
}
}
ckeckDetailElm.innerHTML = urlNode;
}
} else {
btn.disabled = true;
btn.value = noUpdateText;
}
}
}, 300);
}
function doUpdate(btn, app) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice();
var appUpdateUrl = '<%=api.url("update_")%>' + app;
var appInfo = appInfoList[app];
// Download file
XHR.get(appUpdateUrl, {
token: tokenStr,
url: appInfo ? appInfo.data.browser_download_url : '',
size: appInfo ? appInfo.data.size / 1024 : null
}, function (x, json) {
if (json.code) {
removePageNotice();
onRequestError(btn, json.error);
} else if (json.zip) {
btn.value = decompressioningText;
// Extract file
XHR.get(appUpdateUrl, {
token: tokenStr,
task: 'extract',
file: json.file,
subfix: appInfo ? appInfo.type : ''
}, function (x, json) {
if (json.code) {
removePageNotice();
onRequestError(btn, json.error);
} else {
move(btn, appUpdateUrl, json.file);
}
}, 300)
} else {
move(btn, appUpdateUrl, json.file);
}
}, 300)
}
function move(btn, url, file) {
btn.value = movingText;
// Move file to target dir
XHR.get(url, {
token: tokenStr,
task: 'move',
file: file
}, function (x, json) {
removePageNotice();
if (json.code) {
onRequestError(btn, json.error);
} else {
onUpdateSuccess(btn);
}
}, 300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">Passwall <%:Version%></label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=api.get_version()%> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="passwall-check_btn"
onclick="onBtnClick(this,'passwall');" value="<%:Check update%>" />
<span id="passwall-check_btn-detail"></span>
</div>
</div>
</div>
<%for k, v in pairs(com) do
version[k] = api.get_app_version(k)%>
<div class="cbi-value">
<label class="cbi-value-title"><%=v.name%>
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=version[k] ~="" and version[k] or translate("Null") %> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-check_btn"
onclick="onBtnClick(this,'<%=k%>');" value="<%:Check update%>" />
<span id="_<%=k%>-check_btn-detail"></span>
</div>
</div>
</div>
<%end%>

View File

@ -0,0 +1,23 @@
<%
local api = require "luci.passwall.api"
-%>
<script type="text/javascript">
//<![CDATA[
function add_node_by_key() {
var key = prompt("<%:Please enter the node keyword, pay attention to distinguish between spaces, uppercase and lowercase.%>", "");
if (key) {
window.location.href = '<%=api.url("autoswitch_add_node")%>' + "?key=" + key;
}
}
function remove_node_by_key() {
var key = prompt("<%:Please enter the node keyword, pay attention to distinguish between spaces, uppercase and lowercase.%>", "");
if (key) {
window.location.href = '<%=api.url("autoswitch_remove_node")%>' + "?key=" + key;
}
}
//]]>
</script>
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_node_by_key()" value="<%:Add nodes to the standby node list by keywords%>" />
<input class="btn cbi-button cbi-button-remove" type="button" onclick="remove_node_by_key()" value="<%:Delete nodes in the standby node list by keywords%>" />

View File

@ -0,0 +1,43 @@
<%
local api = require "luci.passwall.api"
-%>
<div class="cbi-section cbi-tblsection">
<div id="dns_div">
<ul><b style="color:red"><%:About DNS issues:%></b>
<li style="color:red">1. <span><%:Some browsers may have built-in DNS, be sure to close. Example: Chrome. Settings - Security and Privacy - Security - Use secure DNS disabled.%></span></li>
<li style="color:red">2. <span><%:Sometimes after restart, you can not internet, especially the GFW mode. At this time, close all browsers (important), Windows Client, please `ipconfig /flushdns`. Please close the WiFi on the phone, cut the flight mode and then cut back.%></span></li>
<li style="color:red">3. <span><%:The client DNS and the default gateway must point to this router.%></span></li>
<li style="color:red">4. <span><%:If you have a wrong DNS process, the consequences are at your own risk!%></span></li>
</ul>
</div>
<div id="div2"></div>
</div>
<script>
var origin = window.location.origin;
var reset_url = origin + "<%=api.url("reset_config")%>";
var hide_url = origin + "<%=api.url("hide")%>";
var show_url = origin + "<%=api.url("show")%>";
function reset(url) {
if (confirm('<%:Are you sure to reset?%>') == true) {
window.location.href = reset_url;
}
}
function hide(url) {
if (confirm('<%:Are you sure to hide?%>') == true) {
window.location.href = hide_url;
}
}
var dom = document.getElementById("div2");
if (dom) {
var li = "";
li += "<%:You can use load balancing for failover.%>" + "<br />";
li += "<%:Restore the default configuration method. Input example in the address bar:%>" + "<a href='#' onclick='reset()'>" + reset_url + "</a>" + "<br />";
li += "<%:Hide menu method, input example in the address bar:%>" + "<a href='#' onclick='hide()'>" + hide_url + "</a>" + "<br />";
li += "<%:After the hidden to the display, input example in the address bar:%>" + "<a href='#'>" + show_url + "</a>" + "<br />";
dom.innerHTML = li;
}
</script>

View File

@ -0,0 +1,140 @@
<%
local api = require "luci.passwall.api"
local auto_switch = api.uci_get_type("auto_switch", "enable", 0)
-%>
<script type="text/javascript">
//<![CDATA[
function go() {
var _status = document.getElementsByClassName('_status');
for (var i = 0; i < _status.length; i++) {
var id = _status[i].getAttribute("socks_id");
XHR.get('<%=api.url("socks_status")%>', {
index: i,
id: id
},
function(x, result) {
var index = result.index;
var div = '';
var div1 = '<font style="font-weight:bold;" color="green"></font>&nbsp';
var div2 = '<font style="font-weight:bold;" color="red">X</font>&nbsp';
if (result.socks_status) {
div += div1;
} else {
div += div2;
}
if (result.use_http) {
if (result.http_status) {
div += div1;
} else {
div += div2;
}
}
_status[index].innerHTML = div;
}
);
}
var global_id = null;
var global = document.getElementById("cbi-passwall-global");
if (global) {
var node = global.getElementsByClassName("cbi-section-node")[0];
var node_id = node.getAttribute("id");
global_id = node_id;
var reg1 = new RegExp("(?<=" + node_id + "-).*?(?=(_node))")
for (var i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].childNodes && node.childNodes[i].childNodes.length > 0) {
for (var k = 0; k < node.childNodes[i].childNodes.length; k++) {
try {
var dom = node.childNodes[i].childNodes[k];
if (dom.id) {
var s = dom.id.match(reg1);
if (s) {
dom_id = dom.id.split("cbi-").join("cbid-").split("-").join(".");
var node_select = document.getElementsByName(dom_id)[0];
var node_select_value = node_select.value;
if (node_select_value && node_select_value != "nil" && node_select_value.indexOf("_default") != 0 && node_select_value.indexOf("_direct") != 0 && node_select_value.indexOf("_blackhole") != 0) {
if (global_id != null && node_select_value.indexOf("tcp") == 0) {
var d = global_id + "-tcp_node";
d = d.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
var dom = document.getElementsByName(d)[0];
var _node_select_value = dom.value;
if (_node_select_value && _node_select_value != "nil") {
node_select_value = _node_select_value;
}
}
var v = document.getElementById(dom_id + "-" + node_select_value);
if (v) {
node_select.title = v.text;
} else {
node_select.title = node_select.options[node_select.options.selectedIndex].text;
}
var new_html = "";
var new_a = document.createElement("a");
new_a.innerHTML = "<%:Edit%>";
new_a.href = "#";
new_a.setAttribute("onclick", "location.href='" + '<%=api.url("node_config")%>' + "/" + node_select_value + "'");
new_html = new_a.outerHTML;
if (s[0] == "tcp" || s[0] == "udp") {
var log_a = document.createElement("a");
log_a.innerHTML = "<%:Log%>";
log_a.href = "#";
log_a.setAttribute("onclick", "window.open('" + '<%=api.url("get_redir_log")%>' + "?proto=" + s[0] + "', '_blank')");
new_html += "&nbsp&nbsp" + log_a.outerHTML;
}
node_select.insertAdjacentHTML("afterend", "&nbsp&nbsp" + new_html);
}
}
}
} catch(err) {
}
}
}
}
}
var socks = document.getElementById("cbi-passwall-socks");
if (socks) {
var socks_enabled_dom = document.getElementById(global_id + "-socks_enabled");
socks_enabled_dom.parentNode.removeChild(socks_enabled_dom);
var descr = socks.getElementsByClassName("cbi-section-descr")[0];
descr.outerHTML = socks_enabled_dom.outerHTML;
rows = socks.getElementsByClassName("cbi-section-table-row");
for (var i = 0; i < rows.length; i++) {
try {
var row = rows[i];
var id = row.id;
if (!id) continue;
var dom_id = id + "-node";
var node = document.getElementById(dom_id);
var dom_id = dom_id.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
var node_select = document.getElementsByName(dom_id)[0];
var node_select_value = node_select.value;
if (node_select_value && node_select_value != "nil") {
var v = document.getElementById(dom_id + "-" + node_select_value);
if (v) {
node_select.title = v.text;
} else {
node_select.title = node_select.options[node_select.options.selectedIndex].text;
}
var new_a = document.createElement("a");
new_a.innerHTML = "<%:Edit%>";
new_a.href = "#";
new_a.setAttribute("onclick","location.href='" + '<%=api.url("node_config")%>' + "/" + node_select_value + "'");
node_select.insertAdjacentHTML("afterend", "&nbsp&nbsp" + new_a.outerHTML);
}
} catch(err) {
}
}
}
}
setTimeout("go()", 1000);
//]]>
</script>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
<%
local api = require "luci.passwall.api"
local console_port = api.uci_get_type("global_haproxy", "console_port", "")
-%>
<p id="_status"></p>
<script type="text/javascript">//<![CDATA[
XHR.poll(3, '<%=api.url("haproxy_status")%>', null,
function(x, result) {
if (x && x.status == 200) {
var _status = document.getElementById('_status');
if (_status) {
if (result) {
_status.innerHTML = '<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Enter interface%>" onclick="openwebui()" />';
} else {
_status.innerHTML = '';
}
}
}
});
function openwebui(){
var url = window.location.hostname + ":<%=console_port%>";
window.open('http://' + url, 'target', '');
}
//]]></script>

View File

@ -0,0 +1,31 @@
<%
local api = require "luci.passwall.api"
-%>
<script type="text/javascript">
//<![CDATA[
function clearlog(btn) {
XHR.get('<%=api.url("clear_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
log_textarea.innerHTML = "";
log_textarea.scrollTop = log_textarea.scrollHeight;
}
}
);
}
XHR.poll(5, '<%=api.url("get_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
log_textarea.innerHTML = x.responseText;
}
}
);
//]]>
</script>
<fieldset class="cbi-section" id="_log_fieldset">
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clearlog()" value="<%:Clear logs%>" />
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="40" wrap="off" readonly="readonly"></textarea>
</fieldset>

View File

@ -0,0 +1,108 @@
<%
local api = require "luci.passwall.api"
-%>
<style>
#add_link_div{
display: none;
width: auto;
position: absolute;
left:50%;
top:50%;
transform: translate(-50%, -50%);
z-index: 99;
text-align: center;
background: white;
box-shadow: darkgrey 10px 10px 30px 5px;
padding: 30px 15px;
}
</style>
<script type="text/javascript">
//<![CDATA[
function ajax_add_node(link) {
if (link) {
XHR.get('<%=api.url("link_add_node")%>', {
'link': link
},
function(x, data) {
if(x && x.status == 200) {
window.location.href = '<%=api.url("node_list")%>';
}
else {
alert("<%:Error%>");
}
});
}
}
function open_add_link_div() {
document.getElementById("add_link_div").style.display = "block";
document.getElementById("nodes_link").focus();
}
function close_add_link_div() {
document.getElementById("add_link_div").style.display = "none";
}
function add_node() {
var nodes_link = document.getElementById("nodes_link").value;
if (nodes_link.trim() != "") {
var supports = "ss ssr vmess vless trojan trojan-go hysteria";
var itype = nodes_link.split('://')[0];
if (itype.trim() != "" && supports.indexOf(itype) >= 0) {
ajax_add_node(nodes_link);
}
else {
alert("<%:Please enter the correct link.%>");
}
}
else {
document.getElementById("nodes_link").focus();
}
}
function clear_all_nodes() {
if (confirm('<%:Are you sure to clear all nodes?%>') == true){
XHR.get('<%=api.url("clear_all_nodes")%>', null,
function(x, data) {
if(x && x.status == 200) {
window.location.href = '<%=api.url("node_list")%>';
}
else {
alert("<%:Error%>");
}
});
}
}
//]]>
</script>
<div id="add_link_div">
<div class="cbi-value">
<label class="cbi-value-title"><%:SS/SSR/Vmess/VLESS/Trojan/Hysteria Link%></label>
<div class="cbi-value-field">
<textarea id="nodes_link" rows="5" cols="50"></textarea>
</div>
</div>
<div class="cbi-value">
<div class="cbi-value-field" style="display: unset">
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_node()" value="<%:Add%>" />
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_add_link_div()" value="<%:Close%>" />
</div>
</div>
</div>
<div class="cbi-value">
<label class="cbi-value-title"></label>
<div class="cbi-value-field">
<input class="btn cbi-button cbi-button-add" type="submit" name="cbi.cts.<%=api.appname%>.nodes." value="<%:Add%>" />
<input class="btn cbi-button cbi-button-add" type="button" onclick="open_add_link_div()" value="<%:Add the node via the link%>" />
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clear_all_nodes()" value="<%:Clear all nodes%>" />
<input class="btn cbi-button cbi-button-remove" type="button" onclick="delete_select_nodes()" value="<%:Delete select nodes%>" />
<input class="btn cbi-button" type="button" onclick="checked_all_node(this)" value="<%:Select all%>" />
<input class="btn cbi-button cbi-button-apply" type="submit" name="cbi.apply" value="<%:Save & Apply%>" />
<div id="div_node_count"></div>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,475 @@
<%
local api = require "luci.passwall.api"
-%>
<style>
table th, .table .th {
text-align: center;
}
table td, .table .td {
text-align: center;
/* white-space: nowrap; */
word-break: keep-all;
}
#set_node_div {
display: none;
width: 30rem;
position: fixed;
top:50%;
padding-top: 30px;
z-index: 99;
text-align: center;
background: white;
box-shadow: darkgrey 10px 10px 30px 5px;
}
._now_use {
background: #94e1ff !important;
}
.ping a:hover{
text-decoration : underline;
}
</style>
<script type="text/javascript">
//<![CDATA[
var node_list = {};
var node_count = 0;
var ajax = {
post: function(url, data, fn_success, timeout, fn_timeout) {
var xhr = new XMLHttpRequest();
var code = ajax.encode(data);
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
if (timeout && timeout > 1000) {
xhr.timeout = timeout;
}
if (fn_timeout) {
xhr.ontimeout = function() {
fn_timeout(xhr);
}
}
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
var json = null;
if (xhr.getResponseHeader("Content-Type") == "application/json") {
try {
json = eval('(' + xhr.responseText + ')');
}
catch(e) {
json = null;
}
}
fn_success(xhr, json);
}
};
xhr.send(code);
},
encode: function(obj) {
obj = obj ? obj : { };
obj['_'] = Math.random();
if (typeof obj == 'object')
{
var code = '';
var self = this;
for (var k in obj)
code += (code ? '&' : '') +
k + '=' + encodeURIComponent(obj[k]);
return code;
}
return obj;
}
}
function copy_node(cbi_id) {
window.location.href = '<%=api.url("copy_node")%>' + "?section=" + cbi_id;
}
var section = "";
function open_set_node_div(cbi_id) {
section = cbi_id;
document.getElementById("set_node_div").style.display="block";
var node_name = document.getElementById("cbid.passwall." + cbi_id + ".remarks").value;
document.getElementById("set_node_name").innerHTML = node_name;
}
function close_set_node_div() {
document.getElementById("set_node_div").style.display="none";
document.getElementById("set_node_name").innerHTML = "";
}
function _cbi_row_top(id) {
var dom = document.getElementById("cbi-passwall-" + id);
if (dom) {
var trs = document.getElementById("cbi-passwall-nodes").getElementsByClassName("cbi-section-table-row");
if (trs && trs.length > 0) {
for (var i = 0; i < trs.length; i++) {
var up = dom.getElementsByClassName("cbi-button-up");
if (up) {
cbi_row_swap(up[0], true, 'cbi.sts.passwall.nodes');
}
}
}
}
}
function checked_all_node(btn) {
var doms = document.getElementById("cbi-passwall-nodes").getElementsByClassName("nodes_select");
if (doms && doms.length > 0) {
for (var i = 0 ; i < doms.length; i++) {
doms[i].checked = true;
}
btn.value = "<%:DeSelect all%>";
btn.setAttribute("onclick", "dechecked_all_node(this)");
}
}
function dechecked_all_node(btn) {
var doms = document.getElementById("cbi-passwall-nodes").getElementsByClassName("nodes_select");
if (doms && doms.length > 0) {
for (var i = 0 ; i < doms.length; i++) {
doms[i].checked = false;
}
btn.value = "<%:Select all%>";
btn.setAttribute("onclick", "checked_all_node(this)");
}
}
function delete_select_nodes() {
var ids = [];
var doms = document.getElementById("cbi-passwall-nodes").getElementsByClassName("nodes_select");
if (doms && doms.length > 0) {
for (var i = 0 ; i < doms.length; i++) {
if (doms[i].checked) {
ids.push(doms[i].getAttribute("cbid"))
}
}
if (ids.length > 0) {
if (confirm('<%:Are you sure to delete select nodes?%>') == true){
XHR.get('<%=api.url("delete_select_nodes")%>', {
ids: ids.join()
},
function(x, data) {
if (x && x.status == 200) {
/*
for (var i = 0 ; i < ids.length; i++) {
var box = document.getElementById("cbi-passwall-" + ids[i]);
box.remove();
}
*/
window.location.href = '<%=api.url("node_list")%>';
}
else {
alert("<%:Error%>");
}
});
}
}
}
if (ids.length <= 0) {
alert("<%:You no select nodes !%>");
}
}
function set_node(protocol) {
if (confirm('<%:Are you sure set to%> ' + protocol.toUpperCase() + '<%:the server?%>')==true){
window.location.href = '<%=api.url("set_node")%>?protocol=' + protocol + '&section=' + section;
}
}
function get_address_full(id) {
try {
var address = document.getElementById("cbid.passwall." + id + ".address").value;
var port = document.getElementById("cbid.passwall." + id + ".port").value;
}
catch(err){}
//判断是否含有汉字
var reg = new RegExp("[\\u4E00-\\u9FFF]+","g");
if ((address != null && address != "") && (port != null && port != "") && reg.test(address) == false) {
return { address: address, port: port };
} else {
return null;
}
}
//获取当前使用的节点
function get_now_use_node() {
XHR.get('<%=api.url("get_now_use_node")%>', null,
function(x, result) {
var id = result["TCP"];
if (id) {
var dom = document.getElementById("cbi-passwall-" + id);
if (dom) {
dom.classList.add("_now_use");
dom.title = "当前TCP节点";
//var v = "<a style='color: red'>当前TCP节点</a>" + document.getElementById("cbid.passwall." + id + ".remarks").value;
//document.getElementById("cbi-passwall-" + id + "-remarks").innerHTML = v;
}
}
id = result["UDP"];
if (id) {
var dom = document.getElementById("cbi-passwall-" + id);
if (dom) {
dom.classList.add("_now_use");
dom.title = "当前UDP节点";
}
}
}
);
}
function urltest_node(cbi_id, dom) {
if (cbi_id != null) {
dom.disabled = true;
dom.value = "<%:Check...%>";
XHR.get('<%=api.url("urltest_node")%>', {
id: cbi_id
},
function(x, result) {
if(x && x.status == 200) {
if (result.use_time == null || result.use_time.trim() == "") {
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>";
} else {
var color = "red";
var use_time = result.use_time;
if (use_time < 1000) {
color = "green";
} else if (use_time < 2000) {
color = "#fb9a05";
} else {
color = "red";
}
dom.outerHTML = "<font style='color:" + color + "'>" + result.use_time + " ms" + "</font>";
}
}
}
);
}
}
function ping_node(cbi_id, dom) {
var full = get_address_full(cbi_id);
if (full != null) {
XHR.get('<%=api.url("ping_node")%>', {
address: full.address,
port: full.port
},
function(x, result) {
if(x && x.status == 200) {
if (result.ping == null || result.ping.trim() == "") {
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>";
} else {
var ping = parseInt(result.ping);
if (ping < 100)
dom.outerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
else if (ping < 200)
dom.outerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
else if (ping >= 200)
dom.outerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
}
}
}
);
}
}
/* 自动Ping */
var nodes = [];
const ping_value = document.getElementsByClassName('ping_value');
for (var i = 0; i < ping_value.length; i++) {
var cbi_id = ping_value[i].getAttribute("cbiid");
var full = get_address_full(cbi_id);
if (full != null) {
var flag = false;
//当有多个相同地址和端口时合在一起
for (var j = 0; j < nodes.length; j++) {
if (nodes[j].address == full.address && nodes[j].port == full.port) {
nodes[j].indexs = nodes[j].indexs + "," + i;
flag = true;
break;
}
}
if (flag)
continue;
nodes.push({
indexs: i + "",
address: full.address,
port: full.port
});
}
}
get_now_use_node();
const _xhr = (index) => {
return new Promise((res) => {
const dom = nodes[index];
if (!dom) res()
ajax.post('<%=api.url("ping_node")%>', {
index: dom.indexs,
address: dom.address,
port: dom.port
},
function(x, result) {
if (x && x.status == 200) {
var strs = dom.indexs.split(",");
for (var i = 0; i < strs.length; i++) {
if (result.ping == null || result.ping.trim() == "") {
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
} else {
var ping = parseInt(result.ping);
if (ping < 100)
ping_value[strs[i]].innerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
else if (ping < 200)
ping_value[strs[i]].innerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
else if (ping >= 200)
ping_value[strs[i]].innerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
}
}
}
res();
},
5000,
function(x) {
var strs = dom.indexs.split(",");
for (var i = 0; i < strs.length; i++) {
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
}
res();
}
);
})
}
let task = -1;
const thread = () => {
task = task + 1
if (nodes[task]) {
_xhr(task).then(thread);
}
}
for (let i = 0; i < 20; i++) {
thread()
}
/* 递归单请求方法
var index = 0;
function auto_ping() {
if (index >= nodes.length) {
return;
}
var indexs = nodes[index].indexs;
var address = nodes[index].address;
var port = nodes[index].port;
ajax.post('<%=api.url("ping_node")%>', {
index: indexs,
address: address,
port: port
},
function(x, result) {
if (x && x.status == 200) {
var strs = indexs.split(",");
for (var i = 0; i < strs.length; i++) {
if (result.ping == null || result.ping.trim() == "") {
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
} else {
var ping = parseInt(result.ping);
if (ping < 100)
ping_value[strs[i]].innerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
else if (ping < 200)
ping_value[strs[i]].innerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
else if (ping >= 200)
ping_value[strs[i]].innerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
}
}
}
index++;
return auto_ping();
},
function(x) {
var strs = indexs.split(",");
for (var i = 0; i < strs.length; i++) {
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
}
index++;
return auto_ping();
},
);
}
auto_ping();
*/
var edit_btn = document.getElementById("cbi-passwall-nodes").getElementsByClassName("cbi-button cbi-button-edit");
for (var i = 0; i < edit_btn.length; i++) {
try {
var onclick_str = edit_btn[i].getAttribute("onclick");
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
//添加"勾选"框
new_div += '<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="' + id + '" />&nbsp;&nbsp;';
//添加"置顶"按钮
new_div += '<input class="btn cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top(\'' + id + '\')"/>&nbsp;&nbsp;';
//添加"应用"按钮
new_div += '<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_' + id + '" onclick="open_set_node_div(\'' + id + '\')"/>&nbsp;&nbsp;';
//添加"复制"按钮
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node(\'' + id + '\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
var obj = {};
obj.id = id;
obj.type = document.getElementById("cbid.passwall." + id + ".type").value;
var address_dom = document.getElementById("cbid.passwall." + id + ".address");
var port_dom = document.getElementById("cbid.passwall." + id + ".port");
if (address_dom && port_dom) {
obj.address = address_dom.value;
obj.port = port_dom.value;
}
node_count++;
var add_from = document.getElementById("cbid.passwall." + id + ".add_from").value;
if (node_list[add_from])
node_list[add_from].push(obj);
else
node_list[add_from] = [];
}
catch(err) {
console.error(err);
}
}
if (true) {
var str = "";
for (var add_from in node_list) {
var num = node_list[add_from].length + 1;
if (add_from == "") {
add_from = "<%:Self add%>";
}
str += add_from + " " + "<%:Node num%>: <a style='color: red'>" + num + "</a>&nbsp&nbsp&nbsp";
}
document.getElementById("div_node_count").innerHTML = "<div style='margin-top:5px'>" + str + "</div>";
}
//]]>
</script>
<div style="display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center; -webkit-justify-content: center; justify-content: center;">
<div id="set_node_div">
<div class="cbi-value"><%:You choose node is:%><a style="color: red" id="set_node_name"></a></div>
<div class="cbi-value">
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('tcp')" value="TCP" />
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('udp')" value="UDP" />
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_set_node_div()" value="<%:Close%>" />
</div>
</div>
</div>

View File

@ -0,0 +1,76 @@
<%
local api = require "luci.passwall.api"
local gfwlist_update = api.uci_get_type("global_rules", "gfwlist_update", "1") == "1" and "checked='checked'" or ""
local chnroute_update = api.uci_get_type("global_rules", "chnroute_update", "1") == "1" and "checked='checked'" or ""
local chnroute6_update = api.uci_get_type("global_rules", "chnroute6_update", "1") == "1" and "checked='checked'" or ""
local chnlist_update = api.uci_get_type("global_rules", "chnlist_update", "1") == "1" and "checked='checked'" or ""
local geoip_update = api.uci_get_type("global_rules", "geoip_update", "1") == "1" and "checked='checked'" or ""
local geosite_update = api.uci_get_type("global_rules", "geosite_update", "1") == "1" and "checked='checked'" or ""
-%>
<script type="text/javascript">
//<![CDATA[
function update_rules(btn) {
btn.disabled = true;
btn.value = '<%:Updating...%>';
var div = document.getElementById('_rule_div');
var domList = div.getElementsByTagName('input');
var checkBoxList = [];
var len = domList.length;
while(len--) {
var dom = domList[len];  
if(dom.type == 'checkbox' && dom.checked) {  
checkBoxList.push(dom.name);  
}
}
XHR.get('<%=api.url("update_rules")%>', {
update: checkBoxList.join(",")
},
function(x, data) {
if(x && x.status == 200) {
window.location.href = '<%=api.url("log")%>';
} else {
alert("<%:Error%>");
btn.disabled = false;
btn.value = '<%:Manually update%>';
}
}
);
}
//]]>
</script>
<div class="cbi-value" id="_rule_div">
<label class="cbi-value-title">
<%:Manually update%>
</label>
<div class="cbi-value-field">
<div>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="gfwlist" value="1" <%=gfwlist_update%> />
gfwlist
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="chnroute" value="1" <%=chnroute_update%> />
chnroute
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="chnroute6" value="1" <%=chnroute6_update%> />
chnroute6
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="chnlist" value="1" <%=chnlist_update%> />
chnlist
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="geoip" value="1" <%=geoip_update%> />
geoip
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="geosite" value="1" <%=geosite_update%> />
geosite
</label>
<input class="btn cbi-button cbi-button-apply" type="button" id="update_rules_btn" onclick="update_rules(this)" value="<%:Manually update%>" />
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
<%
local api = require "luci.passwall.api"
-%>
<script type="text/javascript">
//<![CDATA[
function clear_log(btn) {
XHR.get('<%=api.url("server_clear_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
log_textarea.innerHTML = "";
log_textarea.scrollTop = log_textarea.scrollHeight;
}
}
);
}
XHR.poll(3, '<%=api.url("server_get_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
log_textarea.innerHTML = x.responseText;
log_textarea.scrollTop = log_textarea.scrollHeight;
}
}
);
//]]>
</script>
<fieldset class="cbi-section" id="_log_fieldset">
<legend>
<%:Logs%>
</legend>
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clear_log()" value="<%:Clear logs%>" />
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="20" wrap="off" readonly="readonly"></textarea>
</fieldset>

View File

@ -0,0 +1,38 @@
<%
local api = require "luci.passwall.api"
-%>
<script type="text/javascript">
//<![CDATA[
var _users_status = document.getElementsByClassName('_users_status');
for(var i = 0; i < _users_status.length; i++) {
var id = _users_status[i].parentElement.parentElement.parentElement.id;
id = id.substr(id.lastIndexOf("-") + 1);
XHR.get('<%=api.url("server_user_status")%>', {
index: i,
id: id
},
function(x, result) {
_users_status[result.index].setAttribute("style","font-weight:bold;");
_users_status[result.index].setAttribute("color",result.status ? "green":"red");
_users_status[result.index].innerHTML = (result.status ? '✓' : 'X');
}
);
}
var edit_btn = document.getElementById("cbi-passwall_server-user").getElementsByClassName("cbi-button cbi-button-edit");
for (var i = 0; i < edit_btn.length; i++) {
try {
var onclick_str = edit_btn[i].getAttribute("onclick");
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode;
var new_div = "";
//添加"日志"按钮
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_user_log")%>' + '?id=' + id + '\', \'_blank\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {
console.error(err);
}
}
//]]>
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
zh-cn

View File

@ -0,0 +1,4 @@
config global 'global'
option enable '0'

View File

@ -0,0 +1,23 @@
#!/bin/sh
[[ "$ACTION" == "ifup" && $(uci get "passwall.@global[0].enabled") == "1" ]] && {
default_device=$(ip route | grep default | awk -F 'dev ' '{print $2}' | awk '{print $1}')
[ "$default_device" == "$DEVICE" ] && {
LOCK_FILE_DIR=/var/lock
[ ! -d ${LOCK_FILE_DIR} ] && mkdir -p ${LOCK_FILE_DIR}
LOCK_FILE="${LOCK_FILE_DIR}/passwall_ifup.lock"
if [ -s ${LOCK_FILE} ]; then
SPID=$(cat ${LOCK_FILE})
if [ -e /proc/${SPID}/status ]; then
exit 1
fi
cat /dev/null > ${LOCK_FILE}
fi
echo $$ > ${LOCK_FILE}
/etc/init.d/passwall restart
echo "passwall: restart when $INTERFACE ifup" > /dev/kmsg
rm -rf ${LOCK_FILE}
}
}

View File

@ -0,0 +1,85 @@
#!/bin/sh /etc/rc.common
START=99
STOP=15
CONFIG=passwall
APP_FILE=/usr/share/${CONFIG}/app.sh
LOCK_FILE_DIR=/var/lock
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}.lock
set_lock() {
[ ! -d "$LOCK_FILE_DIR" ] && mkdir -p $LOCK_FILE_DIR
exec 999>"$LOCK_FILE"
flock -xn 999
}
unset_lock() {
flock -u 999
rm -rf "$LOCK_FILE"
}
unlock() {
failcount=1
while [ "$failcount" -le 10 ]; do
if [ -f "$LOCK_FILE" ]; then
let "failcount++"
sleep 1s
[ "$failcount" -ge 10 ] && unset_lock
else
break
fi
done
}
boot() {
local delay=$(uci -q get ${CONFIG}.@global_delay[0].start_delay || echo 1)
if [ "$delay" -gt 0 ]; then
$APP_FILE echolog "执行启动延时 $delay 秒后再启动!"
sleep $delay
fi
restart
}
start() {
set_lock
[ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0
$APP_FILE start
unset_lock
}
stop() {
unlock
set_lock
[ $? == 1 ] && $APP_FILE echolog "停止脚本等待超时,不重复运行,退出." && exit 0
$APP_FILE stop
unset_lock
}
restart() {
set_lock
[ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0
$APP_FILE stop
$APP_FILE start
unset_lock
}
disable() {
rm -f "$IPKG_INSTROOT"/etc/rc.d/S??zzz_${CONFIG}
rm -f "$IPKG_INSTROOT"/etc/rc.d/K??zzz_${CONFIG}
}
enable() {
err=1
[ "$START" ] && \
ln -sf "../init.d/${CONFIG}" "$IPKG_INSTROOT/etc/rc.d/S${START}zzz_${CONFIG}" && \
err=0
[ "$STOP" ] && \
ln -sf "../init.d/${CONFIG}" "$IPKG_INSTROOT/etc/rc.d/K${STOP}zzz_${CONFIG}" && \
err=0
return $err
}
enabled() {
[ -x "$IPKG_INSTROOT/etc/rc.d/S${START}zzz_${CONFIG}" ]
}

View File

@ -0,0 +1,16 @@
#!/bin/sh /etc/rc.common
START=99
start() {
lua /usr/lib/lua/luci/passwall/server_app.lua start
}
stop() {
lua /usr/lib/lua/luci/passwall/server_app.lua stop
}
restart() {
stop
start
}

View File

@ -0,0 +1,44 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
set dhcp.@dnsmasq[0].localuse=1
commit dhcp
delete ucitrack.@passwall[-1]
add ucitrack passwall
set ucitrack.@passwall[-1].init=passwall
commit ucitrack
delete firewall.passwall
set firewall.passwall=include
set firewall.passwall.type=script
set firewall.passwall.path=/var/etc/passwall.include
set firewall.passwall.reload=1
commit firewall
delete ucitrack.@passwall_server[-1]
add ucitrack passwall_server
set ucitrack.@passwall_server[-1].init=passwall_server
commit ucitrack
delete firewall.passwall_server
set firewall.passwall_server=include
set firewall.passwall_server.type=script
set firewall.passwall_server.path=/var/etc/passwall_server.include
set firewall.passwall_server.reload=1
commit firewall
set uhttpd.main.max_requests=50
commit uhttpd
EOF
[ ! -s "/etc/config/passwall" ] && cp -f /usr/share/passwall/0_default_config /etc/config/passwall
touch /etc/config/passwall_show >/dev/null 2>&1
[ "$(opkg list-installed | grep "firewall4")" ] && [ "$(opkg list-installed | grep "nftables")" ] && {
[ "$(opkg list-installed | grep "kmod\-nft\-socket")" ] && [ "$(opkg list-installed | grep "kmod\-nft\-tproxy")" ] && [ "$(opkg list-installed | grep "kmod\-nft\-nat")" ] && {
uci -q set passwall.@global_forwarding[0].use_nft=1
uci -q commit passwall
sed -i "s#use_nft '0'#use_nft '1'#g" /usr/share/passwall/0_default_config
}
}
rm -f /tmp/luci-indexcache
rm -rf /tmp/luci-modulecache/
killall -HUP rpcd 2>/dev/null
exit 0

View File

@ -0,0 +1,205 @@
config global
option enabled '0'
option socks_enabled '0'
option tcp_node 'nil'
option udp_node 'nil'
option tcp_node_socks_port '1070'
option dns_mode 'dns2tcp'
option remote_dns '1.1.1.1'
option filter_proxy_ipv6 '0'
option when_chnroute_default_dns 'direct'
option tcp_proxy_mode 'chnroute'
option udp_proxy_mode 'chnroute'
option localhost_tcp_proxy_mode 'default'
option localhost_udp_proxy_mode 'default'
option acl_enable '0'
option close_log_tcp '0'
option close_log_udp '0'
option loglevel 'error'
option trojan_loglevel '4'
config global_haproxy
option balancing_enable '0'
config global_delay
option auto_on '0'
option start_daemon '1'
option start_delay '1'
config global_forwarding
option tcp_no_redir_ports 'disable'
option udp_no_redir_ports 'disable'
option tcp_proxy_drop_ports 'disable'
option udp_proxy_drop_ports '80,443'
option tcp_redir_ports '22,25,53,143,465,587,853,993,995,80,443'
option udp_redir_ports '1:65535'
option accept_icmp '0'
option use_nft '0'
option tcp_proxy_way 'redirect'
option ipv6_tproxy '0'
option sniffing '1'
option route_only '0'
config global_other
option nodes_ping 'auto_ping tcping'
config global_rules
option auto_update '0'
option chnlist_update '1'
option chnroute_update '1'
option chnroute6_update '1'
option gfwlist_update '1'
option geosite_update '0'
option geoip_update '0'
list gfwlist_url 'https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt'
list chnroute_url 'https://ispip.clang.cn/all_cn.txt'
list chnroute_url 'https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china.txt'
list chnroute6_url 'https://ispip.clang.cn/all_cn_ipv6.txt'
list chnroute6_url 'https://fastly.jsdelivr.net/gh/gaoyifan/china-operator-ip@ip-lists/china6.txt'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf'
list chnlist_url 'https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf'
option v2ray_location_asset '/usr/share/v2ray/'
config global_app
option v2ray_file '/usr/bin/v2ray'
option xray_file '/usr/bin/xray'
option trojan_go_file '/usr/bin/trojan-go'
option brook_file '/usr/bin/brook'
option hysteria_file '/usr/bin/hysteria'
option chinadns_ng '/usr/bin/chinadns-ng'
config global_subscribe
option filter_keyword_mode '1'
list filter_discard_list '过期时间'
list filter_discard_list '剩余流量'
list filter_discard_list 'QQ群'
list filter_discard_list '官网'
config auto_switch
option enable '0'
option testing_time '1'
option connect_timeout '3'
option retry_num '3'
option shunt_logic '1'
config nodes 'myshunt'
option remarks '分流总节点'
option type 'Xray'
option protocol '_shunt'
option STEAM '_direct'
option AD '_blackhole'
option BT '_direct'
option Proxy '_default'
option Netflix '_default'
option OpenAI '_default'
option Direct '_direct'
option default_node 'nil'
option domainStrategy 'IPOnDemand'
config shunt_rules 'STEAM'
option remarks 'STEAM'
option domain_list 'api.steampowered.com
regexp:\.cm.steampowered.com$
regexp:\.steamserver.net$'
option ip_list '103.10.124.0/24
103.10.125.0/24
103.28.54.0/24
146.66.152.0/24
146.66.155.0/24
153.254.86.0/24
155.133.224.0/23
155.133.226.0/24
155.133.227.0/24
155.133.230.0/24
155.133.232.0/24
155.133.233.0/24
155.133.234.0/24
155.133.236.0/23
155.133.238.0/24
155.133.239.0/24
155.133.240.0/23
155.133.245.0/24
155.133.246.0/24
155.133.248.0/24
155.133.249.0/24
155.133.250.0/24
155.133.251.0/24
155.133.252.0/24
155.133.253.0/24
155.133.254.0/24
155.133.255.0/24
162.254.192.0/24
162.254.193.0/24
162.254.194.0/23
162.254.195.0/24
162.254.196.0/24
162.254.197.0/24
162.254.198.0/24
162.254.199.0/24
185.25.182.0/24
185.25.183.0/24
190.217.33.0/24
192.69.96.0/22
205.185.194.0/24
205.196.6.0/24
208.64.200.0/24
208.64.201.0/24
208.64.202.0/24
208.64.203.0/24
208.78.164.0/22'
config shunt_rules 'AD'
option remarks 'AD'
option domain_list 'geosite:category-ads-all'
config shunt_rules 'BT'
option remarks 'BT'
option protocol 'bittorrent'
config shunt_rules 'Proxy'
option remarks 'Proxy'
option domain_list 'geosite:geolocation-!cn'
option ip_list '149.154.160.0/20
91.108.4.0/22
91.108.56.0/24
109.239.140.0/24
67.198.55.0/24
8.8.4.4
8.8.8.8
208.67.222.222
208.67.220.220
1.1.1.1
1.1.1.2
1.0.0.1
9.9.9.9
149.112.112.112
2001:67c:4e8::/48
2001:b28:f23c::/48
2001:b28:f23d::/48
2001:b28:f23f::/48
2001:b28:f242::/48
2001:4860:4860::8888
2001:4860:4860::8844
2606:4700:4700::1111
2606:4700:4700::1001'
config shunt_rules 'Netflix'
option remarks 'Netflix'
option domain_list 'geosite:netflix'
config shunt_rules 'OpenAI'
option remarks 'OpenAI'
option domain_list 'openai.com'
config shunt_rules 'Direct'
option remarks 'Direct'
option domain_list 'geosite:category-games@cn
geosite:cn'
option ip_list '223.5.5.5/32
119.29.29.29/32
180.76.76.76/32
114.114.114.114/32
geoip:cn
geoip:private'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
#!/usr/bin/lua
local api = require ("luci.passwall.api")
local appname = api.appname
local fs = api.fs
local jsonc = api.jsonc
local uci = api.uci
local sys = api.sys
local log = function(...)
api.log(...)
end
function get_ip_port_from(str)
local result_port = sys.exec("echo -n " .. str .. " | sed -n 's/^.*[:#]\\([0-9]*\\)$/\\1/p'")
local result_ip = sys.exec(string.format("__host=%s;__varport=%s;", str, result_port) .. "echo -n ${__host%%${__varport:+[:#]${__varport}*}}")
return result_ip, result_port
end
local new_port
local function get_new_port()
if new_port then
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1)))
else
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname)))
end
return new_port
end
local var = api.get_args(arg)
local haproxy_path = var["-path"]
local haproxy_conf = var["-conf"]
local haproxy_dns = var["-dns"] or "119.29.29.29:53,223.5.5.5:53"
local cpu_thread = sys.exec('echo -n $(cat /proc/cpuinfo | grep "processor" | wc -l)') or "1"
local health_check_type = uci:get(appname, "@global_haproxy[0]", "health_check_type") or "tcp"
local health_check_inter = uci:get(appname, "@global_haproxy[0]", "health_check_inter") or "10"
log("HAPROXY 负载均衡...")
fs.mkdir(haproxy_path)
local haproxy_file = haproxy_path .. "/" .. haproxy_conf
local f_out = io.open(haproxy_file, "a")
local haproxy_config = [[
global
daemon
log 127.0.0.1 local2
maxconn 60000
stats socket {{path}}/haproxy.sock
nbthread {{nbthread}}
external-check
insecure-fork-wanted
defaults
mode tcp
log global
option tcplog
option dontlognull
option http-server-close
#option forwardfor except 127.0.0.0/8
option redispatch
retries 2
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
resolvers mydns
resolve_retries 1
timeout resolve 5s
hold valid 600s
{{dns}}
]]
haproxy_config = haproxy_config:gsub("{{path}}", haproxy_path)
haproxy_config = haproxy_config:gsub("{{nbthread}}", cpu_thread)
local mydns = ""
local index = 0
string.gsub(haproxy_dns, '[^' .. "," .. ']+', function(w)
index = index + 1
local s = w:gsub("#", ":")
if not s:find(":") then
s = s .. ":53"
end
mydns = mydns .. (index > 1 and "\n" or "") .. " " .. string.format("nameserver dns%s %s", index, s)
end)
haproxy_config = haproxy_config:gsub("{{dns}}", mydns)
f_out:write(haproxy_config)
local listens = {}
uci:foreach(appname, "haproxy_config", function(t)
if t.enabled == "1" then
local server_remark
local server_address
local server_port
local lbss = t.lbss
local listen_port = tonumber(t.haproxy_port) or 0
local server_node = uci:get_all(appname, lbss)
if server_node and server_node.address and server_node.port then
server_remark = server_node.address .. ":" .. server_node.port
server_address = server_node.address
server_port = server_node.port
t.origin_address = server_address
t.origin_port = server_port
if health_check_type == "passwall_logic" then
if server_node.type ~= "Socks" then
local relay_port = server_node.port
new_port = get_new_port()
local config_file = string.format("haproxy_%s_%s.json", t[".name"], new_port)
sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
appname,
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s",
new_port, --flag
server_node[".name"], --node
"127.0.0.1", --bind
new_port, --socks port
config_file --config file
)
)
)
server_address = "127.0.0.1"
server_port = new_port
end
end
else
server_address, server_port = get_ip_port_from(lbss)
server_remark = server_address .. ":" .. server_port
t.origin_address = server_address
t.origin_port = server_port
end
if server_address and server_port and listen_port > 0 then
if not listens[listen_port] then
listens[listen_port] = {}
end
t.server_remark = server_remark
t.server_address = server_address
t.server_port = server_port
table.insert(listens[listen_port], t)
else
log(" - 丢弃1个明显无效的节点")
end
end
end)
local sortTable = {}
for i in pairs(listens) do
if i ~= nil then
table.insert(sortTable, i)
end
end
table.sort(sortTable, function(a,b) return (a < b) end)
for i, port in pairs(sortTable) do
log(" + 入口 0.0.0.0:%s..." % port)
f_out:write("\n" .. string.format([[
listen %s
bind 0.0.0.0:%s
mode tcp
balance roundrobin
]], port, port))
if health_check_type == "passwall_logic" then
f_out:write(string.format([[
option external-check
external-check command "/usr/share/passwall/haproxy_check.sh"
]], port, port))
end
for i, o in ipairs(listens[port]) do
local remark = o.server_remark
local server = o.server_address .. ":" .. o.server_port
local server_conf = "server {{remark}} {{server}} weight {{weight}} {{resolvers}} check inter {{inter}} rise 1 fall 3 {{backup}}"
server_conf = server_conf:gsub("{{remark}}", remark)
server_conf = server_conf:gsub("{{server}}", server)
server_conf = server_conf:gsub("{{weight}}", o.lbweight)
local resolvers = "resolvers mydns"
if api.is_ip(o.server_address) then
resolvers = ""
end
server_conf = server_conf:gsub("{{resolvers}}", resolvers)
server_conf = server_conf:gsub("{{inter}}", tonumber(health_check_inter) .. "s")
server_conf = server_conf:gsub("{{backup}}", o.backup == "1" and "backup" or "")
f_out:write(" " .. server_conf .. "\n")
if o.export ~= "0" then
sys.call(string.format("/usr/share/passwall/app.sh add_ip2route %s %s", o.origin_address, o.export))
end
log(string.format(" | - 出口节点:%s:%s权重%s", o.origin_address, o.origin_port, o.lbweight))
end
end
--控制台配置
local console_port = uci:get(appname, "@global_haproxy[0]", "console_port")
local console_user = uci:get(appname, "@global_haproxy[0]", "console_user")
local console_password = uci:get(appname, "@global_haproxy[0]", "console_password")
local str = [[
listen console
bind 0.0.0.0:%s
mode http
stats refresh 30s
stats uri /
stats admin if TRUE
%s
]]
f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or ""))
log(string.format(" * 控制台端口:%s", console_port))
f_out:close()

View File

@ -0,0 +1,18 @@
#!/bin/sh
listen_address=$1
listen_port=$2
server_address=$3
server_port=$4
status=$(/usr/bin/curl -I -o /dev/null -skL -x socks5h://${server_address}:${server_port} --connect-timeout 3 --retry 3 -w %{http_code} "https://www.google.com/generate_204")
case "$status" in
204|\
200)
status=200
;;
esac
return_code=1
if [ "$status" = "200" ]; then
return_code=0
fi
exit ${return_code}

View File

@ -0,0 +1,89 @@
#!/bin/sh
stretch() {
#zhenduiluanshezhiDNSderen
local dnsmasq_server=$(uci -q get dhcp.@dnsmasq[0].server)
local dnsmasq_noresolv=$(uci -q get dhcp.@dnsmasq[0].noresolv)
local _flag
for server in $dnsmasq_server; do
[ -z "$(echo $server | grep '\/')" ] && _flag=1
done
[ -z "$_flag" ] && [ "$dnsmasq_noresolv" = "1" ] && {
uci -q delete dhcp.@dnsmasq[0].noresolv
uci -q set dhcp.@dnsmasq[0].resolvfile="$RESOLVFILE"
uci commit dhcp
}
}
backup_servers() {
DNSMASQ_DNS=$(uci show dhcp | grep "@dnsmasq" | grep ".server=" | awk -F '=' '{print $2}' | sed "s/'//g" | tr ' ' ',')
if [ -n "${DNSMASQ_DNS}" ]; then
uci -q set $CONFIG.@global[0].dnsmasq_servers="${DNSMASQ_DNS}"
uci commit $CONFIG
fi
}
restore_servers() {
OLD_SERVER=$(uci -q get $CONFIG.@global[0].dnsmasq_servers | tr "," " ")
for server in $OLD_SERVER; do
uci -q del_list dhcp.@dnsmasq[0].server=$server
uci -q add_list dhcp.@dnsmasq[0].server=$server
done
uci commit dhcp
uci -q delete $CONFIG.@global[0].dnsmasq_servers
uci commit $CONFIG
}
logic_restart() {
local no_log
eval_set_val $@
_LOG_FILE=$LOG_FILE
[ -n "$no_log" ] && LOG_FILE="/dev/null"
if [ -f "$TMP_PATH/default_DNS" ]; then
backup_servers
#sed -i "/list server/d" /etc/config/dhcp >/dev/null 2>&1
for server in $(uci -q get dhcp.@dnsmasq[0].server); do
[ -n "$(echo $server | grep '\/')" ] || uci -q del_list dhcp.@dnsmasq[0].server="$server"
done
/etc/init.d/dnsmasq restart >/dev/null 2>&1
restore_servers
else
/etc/init.d/dnsmasq restart >/dev/null 2>&1
fi
echolog "重启 dnsmasq 服务"
LOG_FILE=${_LOG_FILE}
}
restart() {
local no_log
eval_set_val $@
_LOG_FILE=$LOG_FILE
[ -n "$no_log" ] && LOG_FILE="/dev/null"
/etc/init.d/dnsmasq restart >/dev/null 2>&1
echolog "重启 dnsmasq 服务"
LOG_FILE=${_LOG_FILE}
}
del() {
rm -rf /tmp/dnsmasq.d/dnsmasq-$CONFIG.conf
rm -rf $DNSMASQ_PATH/dnsmasq-$CONFIG.conf
rm -rf $TMP_DNSMASQ_PATH
}
arg1=$1
shift
case $arg1 in
stretch)
stretch $@
;;
del)
del $@
;;
restart)
restart $@
;;
logic_restart)
logic_restart $@
;;
*) ;;
esac

View File

@ -0,0 +1,421 @@
require "luci.sys"
local api = require "luci.passwall.api"
local var = api.get_args(arg)
local FLAG = var["-FLAG"]
local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"]
local DEFAULT_DNS = var["-DEFAULT_DNS"]
local LOCAL_DNS = var["-LOCAL_DNS"]
local TUN_DNS = var["-TUN_DNS"]
local REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
local CHNROUTE_MODE_DEFAULT_DNS = var["-CHNROUTE_MODE_DEFAULT_DNS"]
local CHINADNS_DNS = var["-CHINADNS_DNS"]
local TCP_NODE = var["-TCP_NODE"]
local PROXY_MODE = var["-PROXY_MODE"]
local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"]
local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"]
local NFTFLAG = var["-NFTFLAG"]
local CACHE_PATH = api.CACHE_PATH
local CACHE_FLAG = "dnsmasq_" .. FLAG
local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG
local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt"
local uci = api.uci
local sys = api.sys
local appname = api.appname
local fs = api.fs
local datatypes = api.datatypes
local list1 = {}
local excluded_domain = {}
local excluded_domain_str = "!"
local function log(...)
if NO_LOGIC_LOG == "1" then
return
end
api.log(...)
end
local function check_dns(domain, dns)
if domain == "" or domain:find("#") then
return false
end
if not dns then
return
end
for k,v in ipairs(list1[domain].dns) do
if dns == v then
return true
end
end
return false
end
local function check_ipset(domain, ipset)
if domain == "" or domain:find("#") then
return false
end
if not ipset then
return
end
for k,v in ipairs(list1[domain].ipsets) do
if ipset == v then
return true
end
end
return false
end
local function set_domain_address(domain, address)
if domain == "" or domain:find("#") then
return
end
if not list1[domain] then
list1[domain] = {
dns = {},
ipsets = {}
}
end
if not list1[domain].address then
list1[domain].address = address
end
end
local function set_domain_dns(domain, dns)
if domain == "" or domain:find("#") then
return
end
if not dns then
return
end
if not list1[domain] then
list1[domain] = {
dns = {},
ipsets = {}
}
end
for line in string.gmatch(dns, '[^' .. "," .. ']+') do
if not check_dns(domain, line) then
table.insert(list1[domain].dns, line)
end
end
end
local function set_domain_ipset(domain, ipset)
if domain == "" or domain:find("#") then
return
end
if not ipset then
return
end
if not list1[domain] then
list1[domain] = {
dns = {},
ipsets = {}
}
end
for line in string.gmatch(ipset, '[^' .. "," .. ']+') do
if not check_ipset(domain, line) then
table.insert(list1[domain].ipsets, line)
end
end
end
local function add_excluded_domain(domain)
if domain == "" or domain:find("#") then
return
end
table.insert(excluded_domain, domain)
excluded_domain_str = excluded_domain_str .. "|" .. domain
end
local function check_excluded_domain(domain)
if domain == "" or domain:find("#") then
return false
end
for k,v in ipairs(excluded_domain) do
if domain:find(v) then
return true
end
end
return false
end
local cache_text = ""
local new_rules = luci.sys.exec("echo -n $(find /usr/share/passwall/rules -type f | xargs md5sum)")
local new_text = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. REMOTE_FAKEDNS .. CHNROUTE_MODE_DEFAULT_DNS .. CHINADNS_DNS .. PROXY_MODE .. NO_PROXY_IPV6 .. new_rules .. NFTFLAG
if fs.access(CACHE_TEXT_FILE) then
for line in io.lines(CACHE_TEXT_FILE) do
cache_text = line
end
end
if cache_text ~= new_text then
api.remove(CACHE_DNS_PATH .. "*")
end
local global = PROXY_MODE:find("global")
local returnhome = PROXY_MODE:find("returnhome")
local chnlist = PROXY_MODE:find("chnroute")
local gfwlist = PROXY_MODE:find("gfwlist")
local only_global
local dnsmasq_default_dns
if CHNROUTE_MODE_DEFAULT_DNS ~= "nil" then
if chnlist and CHNROUTE_MODE_DEFAULT_DNS == "remote" then
dnsmasq_default_dns = TUN_DNS
end
if (chnlist or gfwlist) and CHNROUTE_MODE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0" then
dnsmasq_default_dns = CHINADNS_DNS
end
end
if global and (not returnhome and not chnlist and not gfwlist) then
--只有全局模式时
dnsmasq_default_dns = TUN_DNS
only_global = 1
end
local setflag_4= (NFTFLAG == "1") and "4#inet#fw4#" or ""
local setflag_6= (NFTFLAG == "1") and "6#inet#fw4#" or ""
if not fs.access(CACHE_DNS_PATH) then
fs.mkdir("/tmp/dnsmasq.d")
fs.mkdir(CACHE_DNS_PATH)
--屏蔽列表
for line in io.lines("/usr/share/passwall/rules/block_host") do
if line ~= "" and not line:find("#") then
set_domain_address(line, "0.0.0.0")
end
end
--始终用国内DNS解析节点域名
uci:foreach(appname, "nodes", function(t)
local address = t.address
if datatypes.hostname(address) then
set_domain_dns(address, LOCAL_DNS)
set_domain_ipset(address, setflag_4 .. "vpsiplist," .. setflag_6 .. "vpsiplist6")
end
end)
log(string.format(" - 节点列表中的域名(vpsiplist)%s", LOCAL_DNS or "默认"))
--始终用国内DNS解析直连白名单列表
for line in io.lines("/usr/share/passwall/rules/direct_host") do
if line ~= "" and not line:find("#") then
add_excluded_domain(line)
set_domain_dns(line, LOCAL_DNS)
set_domain_ipset(line, setflag_4 .. "whitelist," .. setflag_6 .. "whitelist6")
end
end
log(string.format(" - 域名白名单(whitelist)%s", LOCAL_DNS or "默认"))
local fwd_dns
local ipset_flag
local no_ipv6
--始终使用远程DNS解析代理黑名单列表
for line in io.lines("/usr/share/passwall/rules/proxy_host") do
if line ~= "" and not line:find("#") then
add_excluded_domain(line)
local ipset_flag = setflag_4 .. "blacklist," .. setflag_6 .. "blacklist6"
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
ipset_flag = setflag_4 .. "blacklist"
end
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_dns(line, TUN_DNS)
set_domain_ipset(line, ipset_flag)
end
end
log(string.format(" - 代理域名表(blacklist)%s", TUN_DNS or "默认"))
--分流规则
if uci:get(appname, TCP_NODE, "protocol") == "_shunt" then
local t = uci:get_all(appname, TCP_NODE)
local default_node_id = t["default_node"] or "_direct"
uci:foreach(appname, "shunt_rules", function(s)
local _node_id = t[s[".name"]] or "nil"
if _node_id ~= "nil" and _node_id ~= "_blackhole" then
if _node_id == "_default" then
_node_id = default_node_id
end
fwd_dns = nil
ipset_flag = nil
no_ipv6 = nil
if _node_id == "_direct" then
fwd_dns = LOCAL_DNS
ipset_flag = setflag_4 .. "whitelist," .. setflag_6 .. "whitelist6"
else
fwd_dns = TUN_DNS
ipset_flag = setflag_4 .. "shuntlist," .. setflag_6 .. "shuntlist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = setflag_4 .. "shuntlist"
no_ipv6 = true
end
if not only_global then
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
end
end
local domain_list = s.domain_list or ""
for line in string.gmatch(domain_list, "[^\r\n]+") do
if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("geosite:") and not line:find("ext:") then
if line:find("domain:") or line:find("full:") then
line = string.match(line, ":([^:]+)$")
end
add_excluded_domain(line)
if no_ipv6 then
set_domain_address(line, "::")
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, ipset_flag)
end
end
if _node_id ~= "_direct" then
log(string.format(" - V2ray/Xray分流规则(%s)%s", s.remarks, fwd_dns or "默认"))
end
end
end)
end
--如果没有使用回国模式
if not returnhome then
if fs.access("/usr/share/passwall/rules/gfwlist") then
fwd_dns = TUN_DNS
if CHNROUTE_MODE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0" then
fwd_dns = nil
else
local ipset_flag = setflag_4 .. "gfwlist," .. setflag_6 .. "gfwlist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = setflag_4 .. "gfwlist"
end
if not only_global then
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
end
local gfwlist_str = sys.exec('cat /usr/share/passwall/rules/gfwlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(gfwlist_str, "[^\r\n]+") do
if line ~= "" then
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
end
if not only_global then
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, ipset_flag)
end
end
end
end
log(string.format(" - 防火墙域名表(gfwlist)%s", fwd_dns or "默认"))
end
if chnlist and fs.access("/usr/share/passwall/rules/chnlist") and (CHNROUTE_MODE_DEFAULT_DNS == "remote" or (CHNROUTE_MODE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0")) then
fwd_dns = LOCAL_DNS
if CHNROUTE_MODE_DEFAULT_DNS == "chinadns_ng" and CHINADNS_DNS ~= "0" then
fwd_dns = nil
else
local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(chnlist_str, "[^\r\n]+") do
if line ~= "" then
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, setflag_4 .. "chnroute," .. setflag_6 .. "chnroute6")
end
end
end
log(string.format(" - 中国域名表(chnroute)%s", fwd_dns or "默认"))
end
else
if fs.access("/usr/share/passwall/rules/chnlist") then
local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(chnlist_str, "[^\r\n]+") do
if line ~= "" then
local ipset_flag = setflag_4 .. "chnroute," .. setflag_6 .. "chnroute6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = setflag_4 .. "chnroute"
set_domain_address(line, "::")
end
if not only_global then
set_domain_dns(line, TUN_DNS)
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_ipset(line, ipset_flag)
end
end
end
log(string.format(" - 中国域名表(chnroute)%s", TUN_DNS or "默认"))
end
end
local address_out = io.open(CACHE_DNS_PATH .. "/000-address.conf", "a")
local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a")
local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a")
local set_name = "ipset"
if NFTFLAG == "1" then
set_name = "nftset"
end
for key, value in pairs(list1) do
if value.address and #value.address > 0 then
address_out:write(string.format("address=/.%s/%s\n", key, value.address))
end
if value.dns and #value.dns > 0 then
for i, dns in ipairs(value.dns) do
server_out:write(string.format("server=/.%s/%s\n", key, dns))
end
end
if value.ipsets and #value.ipsets > 0 then
local ipsets_str = ""
for i, ipset in ipairs(value.ipsets) do
ipsets_str = ipsets_str .. ipset .. ","
end
ipsets_str = ipsets_str:sub(1, #ipsets_str - 1)
ipset_out:write(string.format("%s=/.%s/%s\n", set_name, key, ipsets_str))
end
end
address_out:close()
server_out:close()
ipset_out:close()
local f_out = io.open(CACHE_TEXT_FILE, "a")
f_out:write(new_text)
f_out:close()
end
if api.is_install("procd\\-ujail") then
fs.copyr(CACHE_DNS_PATH, TMP_DNSMASQ_PATH)
else
api.remove(TMP_DNSMASQ_PATH)
fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH)
end
if DNSMASQ_CONF_FILE ~= "nil" then
local conf_out = io.open(DNSMASQ_CONF_FILE, "a")
conf_out:write(string.format("conf-dir=%s\n", TMP_DNSMASQ_PATH))
if dnsmasq_default_dns then
conf_out:write(string.format("server=%s\n", dnsmasq_default_dns))
conf_out:write("all-servers\n")
conf_out:write("no-poll\n")
conf_out:write("no-resolv\n")
conf_out:close()
log(string.format(" - 以上所列以外及默认:%s", dnsmasq_default_dns))
if FLAG == "default" then
local f_out = io.open("/tmp/etc/passwall/default_DNS", "a")
f_out:write(DEFAULT_DNS)
f_out:close()
end
end
end
log(" - PassWall必须依赖于Dnsmasq如果你自行配置了错误的DNS流程将会导致域名(直连/代理域名)分流失效!!!")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
#!/bin/sh
CONFIG=passwall
TMP_PATH=/tmp/etc/$CONFIG
TMP_BIN_PATH=$TMP_PATH/bin
TMP_SCRIPT_FUNC_PATH=$TMP_PATH/script_func
TMP_ID_PATH=$TMP_PATH/id
LOCK_FILE_DIR=/tmp/lock
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_script.lock
config_n_get() {
local ret=$(uci -q get $CONFIG.$1.$2 2>/dev/null)
echo ${ret:=$3}
}
config_t_get() {
local index=0
[ -n "$4" ] && index=$4
local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null)
echo ${ret:=$3}
}
ENABLED=$(config_t_get global enabled 0)
[ "$ENABLED" != 1 ] && return 1
ENABLED=$(config_t_get global_delay start_daemon 0)
[ "$ENABLED" != 1 ] && return 1
sleep 58s
while [ "$ENABLED" -eq 1 ]; do
[ -f "$LOCK_FILE" ] && {
sleep 6s
continue
}
touch $LOCK_FILE
for filename in $(ls ${TMP_SCRIPT_FUNC_PATH}); do
cmd=$(cat ${TMP_SCRIPT_FUNC_PATH}/${filename})
cmd_check=$(echo $cmd | awk -F '>' '{print $1}')
[ -n "$(echo $cmd_check | grep "dns2socks")" ] && cmd_check=$(echo $cmd_check | sed "s#:# #g")
icount=$(pgrep -f "$(echo $cmd_check)" | wc -l)
if [ $icount = 0 ]; then
#echo "${cmd} 进程挂掉,重启" >> /tmp/log/passwall.log
eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 &
fi
done
rm -f $LOCK_FILE
sleep 58s
done

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,425 @@
#!/usr/bin/lua
require 'nixio'
require 'luci.sys'
local luci = luci
local ucic = luci.model.uci.cursor()
local jsonc = require "luci.jsonc"
local name = 'passwall'
local api = require ("luci.passwall.api")
local arg1 = arg[1]
local rule_path = "/usr/share/" .. name .. "/rules"
local reboot = 0
local gfwlist_update = 0
local chnroute_update = 0
local chnroute6_update = 0
local chnlist_update = 0
local geoip_update = 0
local geosite_update = 0
-- match comments/title/whitelist/ip address/excluded_domain
local comment_pattern = "^[!\\[@]+"
local ip_pattern = "^%d+%.%d+%.%d+%.%d+"
local ip4_ipset_pattern = "^%d+%.%d+%.%d+%.%d+[%/][%d]+$"
local ip6_ipset_pattern = ":-[%x]+%:+[%x]-[%/][%d]+$"
local domain_pattern = "([%w%-%_]+%.[%w%.%-%_]+)[%/%*]*"
local excluded_domain = {"apple.com","sina.cn","sina.com.cn","baidu.com","byr.cn","jlike.com","weibo.com","zhongsou.com","youdao.com","sogou.com","so.com","soso.com","aliyun.com","taobao.com","jd.com","qq.com","bing.com"}
local gfwlist_url = ucic:get(name, "@global_rules[0]", "gfwlist_url") or {"https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"}
local chnroute_url = ucic:get(name, "@global_rules[0]", "chnroute_url") or {"https://ispip.clang.cn/all_cn.txt"}
local chnroute6_url = ucic:get(name, "@global_rules[0]", "chnroute6_url") or {"https://ispip.clang.cn/all_cn_ipv6.txt"}
local chnlist_url = ucic:get(name, "@global_rules[0]", "chnlist_url") or {"https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf","https://fastly.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf"}
local geoip_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geosite_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local v2ray_asset_location = ucic:get_first(name, 'global_rules', "v2ray_location_asset", "/usr/share/v2ray/")
local log = function(...)
if arg1 then
if arg1 == "log" then
api.log(...)
elseif arg1 == "print" then
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
print(result)
end
end
end
-- curl
local function curl(url, file, valifile)
local args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
}
if file then
args[#args + 1] = "-o " .. file
end
if valifile then
args[#args + 1] = "--dump-header " .. valifile
end
local return_code, result = api.curl_logic(url, nil, args)
return tonumber(result)
end
--check excluded domain
local function check_excluded_domain(value)
for k,v in ipairs(excluded_domain) do
if value:find(v) then
return true
end
end
end
local function line_count(file_path)
local num = 0
for _ in io.lines(file_path) do
num = num + 1
end
return num;
end
local function non_file_check(file_path, vali_file)
if nixio.fs.readfile(file_path, 10) then
local remote_file_size = tonumber(luci.sys.exec("cat " .. vali_file .. " | grep -i 'Content-Length' | awk '{print $2}'"))
local local_file_size = tonumber(nixio.fs.stat(file_path, "size"))
if remote_file_size and local_file_size then
if remote_file_size == local_file_size then
return nil;
else
log("下载文件大小校验出错,原始文件大小" .. remote_file_size .. "B下载文件大小" .. local_file_size .. "B。")
return true;
end
else
return nil;
end
else
log("下载文件读取出错。")
return true;
end
end
--fetch rule
local function fetch_rule(rule_name,rule_type,url,exclude_domain)
local sret = 200
local sret_tmp = 0
local domains = {}
local file_tmp = "/tmp/" ..rule_name.. "_tmp"
local vali_file = "/tmp/" ..rule_name.. "_vali"
local download_file_tmp = "/tmp/" ..rule_name.. "_dl"
local unsort_file_tmp = "/tmp/" ..rule_name.. "_unsort"
log(rule_name.. " 开始更新...")
for k,v in ipairs(url) do
sret_tmp = curl(v, download_file_tmp..k, vali_file..k)
if sret_tmp == 200 and non_file_check(download_file_tmp..k, vali_file..k) then
log(rule_name.. "" ..k.. "条规则:" ..v.. "下载文件过程出错,尝试重新下载。")
os.remove(download_file_tmp..k)
os.remove(vali_file..k)
sret_tmp = curl(v, download_file_tmp..k, vali_file..k)
if sret_tmp == 200 and non_file_check(download_file_tmp..k, vali_file..k) then
sret = 0
sret_tmp = 0
log(rule_name.. "" ..k.. "条规则:" ..v.. "下载文件过程出错,请检查网络或下载链接后重试!")
end
end
if sret_tmp == 200 then
if rule_name == "gfwlist" then
local domains = {}
local gfwlist = io.open(download_file_tmp..k, "r")
local decode = api.base64Decode(gfwlist:read("*all"))
gfwlist:close()
gfwlist = io.open(download_file_tmp..k, "w")
gfwlist:write(decode)
gfwlist:close()
end
if rule_type == "domain" and exclude_domain == true then
for line in io.lines(download_file_tmp..k) do
if not (string.find(line, comment_pattern) or string.find(line, ip_pattern) or check_excluded_domain(line)) then
local start, finish, match = string.find(line, domain_pattern)
if (start) then
domains[match] = true
end
end
end
elseif rule_type == "domain" then
for line in io.lines(download_file_tmp..k) do
if not (string.find(line, comment_pattern) or string.find(line, ip_pattern)) then
local start, finish, match = string.find(line, domain_pattern)
if (start) then
domains[match] = true
end
end
end
elseif rule_type == "ip4" then
local out = io.open(unsort_file_tmp, "a")
for line in io.lines(download_file_tmp..k) do
local start, finish, match = string.find(line, ip4_ipset_pattern)
if (start) then
out:write(string.format("%s\n", line))
end
end
out:close()
elseif rule_type == "ip6" then
local out = io.open(unsort_file_tmp, "a")
for line in io.lines(download_file_tmp..k) do
local start, finish, match = string.find(line, ip6_ipset_pattern)
if (start) then
out:write(string.format("%s\n", line))
end
end
out:close()
end
else
sret = 0
log(rule_name.. "" ..k.. "条规则:" ..v.. "下载失败,请检查网络或下载链接后重试!")
end
os.remove(download_file_tmp..k)
os.remove(vali_file..k)
end
if sret == 200 then
if rule_type == "domain" then
local out = io.open(unsort_file_tmp, "w")
for k,v in pairs(domains) do
out:write(string.format("%s\n", k))
end
out:close()
end
luci.sys.call("cat " ..unsort_file_tmp.. " | sort -u > "..file_tmp)
os.remove(unsort_file_tmp)
local old_md5 = luci.sys.exec("echo -n $(md5sum " .. rule_path .. "/" ..rule_name.. " | awk '{print $1}')")
local new_md5 = luci.sys.exec("echo -n $([ -f '" ..file_tmp.. "' ] && md5sum " ..file_tmp.." | awk '{print $1}')")
if old_md5 ~= new_md5 then
local count = line_count(file_tmp)
luci.sys.exec("mv -f "..file_tmp .. " " ..rule_path .. "/" ..rule_name)
reboot = 1
log(rule_name.. " 更新成功,总规则数 " ..count.. " 条。")
else
log(rule_name.. " 版本一致,无需更新。")
end
else
log(rule_name.. " 文件下载失败!")
end
os.remove(file_tmp)
return 0
end
local function fetch_gfwlist()
fetch_rule("gfwlist","domain",gfwlist_url,true)
end
local function fetch_chnroute()
fetch_rule("chnroute","ip4",chnroute_url,false)
end
local function fetch_chnroute6()
fetch_rule("chnroute6","ip6",chnroute6_url,false)
end
local function fetch_chnlist()
fetch_rule("chnlist","domain",chnlist_url,false)
end
--获取geoip
local function fetch_geoip()
--请求geoip
xpcall(function()
local return_code, content = api.curl_logic(geoip_api)
local json = jsonc.parse(content)
if json.tag_name and json.assets then
for _, v in ipairs(json.assets) do
if v.name and v.name == "geoip.dat.sha256sum" then
local sret = curl(v.browser_download_url, "/tmp/geoip.dat.sha256sum")
if sret == 200 then
local f = io.open("/tmp/geoip.dat.sha256sum", "r")
local content = f:read()
f:close()
f = io.open("/tmp/geoip.dat.sha256sum", "w")
f:write(content:gsub("geoip.dat", "/tmp/geoip.dat"), "")
f:close()
if nixio.fs.access(v2ray_asset_location .. "geoip.dat") then
luci.sys.call(string.format("cp -f %s %s", v2ray_asset_location .. "geoip.dat", "/tmp/geoip.dat"))
if luci.sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
log("geoip 版本一致,无需更新。")
return 1
end
end
for _2, v2 in ipairs(json.assets) do
if v2.name and v2.name == "geoip.dat" then
sret = curl(v2.browser_download_url, "/tmp/geoip.dat")
if luci.sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
luci.sys.call(string.format("mkdir -p %s && cp -f %s %s", v2ray_asset_location, "/tmp/geoip.dat", v2ray_asset_location .. "geoip.dat"))
reboot = 1
log("geoip 更新成功。")
return 1
else
log("geoip 更新失败,请稍后再试。")
end
break
end
end
end
break
end
end
end
end,
function(e)
end)
return 0
end
--获取geosite
local function fetch_geosite()
--请求geosite
xpcall(function()
local return_code, content = api.curl_logic(geosite_api)
local json = jsonc.parse(content)
if json.tag_name and json.assets then
for _, v in ipairs(json.assets) do
if v.name and v.name == "geosite.dat.sha256sum" then
local sret = curl(v.browser_download_url, "/tmp/geosite.dat.sha256sum")
if sret == 200 then
local f = io.open("/tmp/geosite.dat.sha256sum", "r")
local content = f:read()
f:close()
f = io.open("/tmp/geosite.dat.sha256sum", "w")
f:write(content:gsub("geosite.dat", "/tmp/geosite.dat"), "")
f:close()
if nixio.fs.access(v2ray_asset_location .. "geosite.dat") then
luci.sys.call(string.format("cp -f %s %s", v2ray_asset_location .. "geosite.dat", "/tmp/geosite.dat"))
if luci.sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
log("geosite 版本一致,无需更新。")
return 1
end
end
for _2, v2 in ipairs(json.assets) do
if v2.name and v2.name == "geosite.dat" then
sret = curl(v2.browser_download_url, "/tmp/geosite.dat")
if luci.sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
luci.sys.call(string.format("mkdir -p %s && cp -f %s %s", v2ray_asset_location, "/tmp/geosite.dat", v2ray_asset_location .. "geosite.dat"))
reboot = 1
log("geosite 更新成功。")
return 1
else
log("geosite 更新失败,请稍后再试。")
end
break
end
end
end
break
end
end
end
end,
function(e)
end)
return 0
end
if arg[2] then
string.gsub(arg[2], '[^' .. "," .. ']+', function(w)
if w == "gfwlist" then
gfwlist_update = 1
end
if w == "chnroute" then
chnroute_update = 1
end
if w == "chnroute6" then
chnroute6_update = 1
end
if w == "chnlist" then
chnlist_update = 1
end
if w == "geoip" then
geoip_update = 1
end
if w == "geosite" then
geosite_update = 1
end
end)
else
gfwlist_update = ucic:get_first(name, 'global_rules', "gfwlist_update", 1)
chnroute_update = ucic:get_first(name, 'global_rules', "chnroute_update", 1)
chnroute6_update = ucic:get_first(name, 'global_rules', "chnroute6_update", 1)
chnlist_update = ucic:get_first(name, 'global_rules', "chnlist_update", 1)
geoip_update = ucic:get_first(name, 'global_rules', "geoip_update", 1)
geosite_update = ucic:get_first(name, 'global_rules', "geosite_update", 1)
end
if gfwlist_update == 0 and chnroute_update == 0 and chnroute6_update == 0 and chnlist_update == 0 and geoip_update == 0 and geosite_update == 0 then
os.exit(0)
end
log("开始更新规则...")
if tonumber(gfwlist_update) == 1 then
xpcall(fetch_gfwlist,function(e)
log(e)
log(debug.traceback())
log('更新gfwlist发生错误...')
end)
end
if tonumber(chnroute_update) == 1 then
xpcall(fetch_chnroute,function(e)
log(e)
log(debug.traceback())
log('更新chnroute发生错误...')
end)
end
if tonumber(chnroute6_update) == 1 then
xpcall(fetch_chnroute6,function(e)
log(e)
log(debug.traceback())
log('更新chnroute6发生错误...')
end)
end
if tonumber(chnlist_update) == 1 then
xpcall(fetch_chnlist,function(e)
log(e)
log(debug.traceback())
log('更新chnlist发生错误...')
end)
end
if tonumber(geoip_update) == 1 then
log("geoip 开始更新...")
local status = fetch_geoip()
os.remove("/tmp/geoip.dat")
os.remove("/tmp/geoip.dat.sha256sum")
end
if tonumber(geosite_update) == 1 then
log("geosite 开始更新...")
local status = fetch_geosite()
os.remove("/tmp/geosite.dat")
os.remove("/tmp/geosite.dat.sha256sum")
end
ucic:set(name, ucic:get_first(name, 'global_rules'), "gfwlist_update", gfwlist_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "chnroute_update", chnroute_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "chnroute6_update", chnroute6_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "chnlist_update", chnlist_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "geoip_update", geoip_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "geosite_update", geosite_update)
ucic:save(name)
luci.sys.call("uci commit " .. name)
if reboot == 1 then
log("重启服务,应用新的规则。")
luci.sys.call("/usr/share/" .. name .. "/iptables.sh flush_ipset > /dev/null 2>&1 &")
end
log("规则更新完毕...")

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

View File

@ -0,0 +1,18 @@
apple.com
microsoft.com
dyndns.com
douyucdn.cn
douyucdn2.cn
#steam
steamcontent.com
dl.steam.clngaa.com
dl.steam.ksyna.com
st.dl.bscstorage.net
st.dl.eccdnx.com
st.dl.pinyuncloud.com
cdn.mileweb.cs.steampowered.com.8686c.com
cdn-ws.content.steamchina.com
cdn-qc.content.steamchina.com
cdn-ali.content.steamchina.com
epicgames-download1-1251447533.file.myqcloud.com

View File

@ -0,0 +1,6 @@
114.114.114.114
114.114.115.115
223.5.5.5
223.6.6.6
119.29.29.29
180.76.76.76

View File

@ -0,0 +1,24 @@
courier.push.apple.com
rbsxbxp-mim.vivox.com
rbsxbxp.www.vivox.com
rbsxbxp-ws.vivox.com
rbspsxp.www.vivox.com
rbspsxp-mim.vivox.com
rbspsxp-ws.vivox.com
rbswxp.www.vivox.com
rbswxp-mim.vivox.com
disp-rbspsp-5-1.vivox.com
disp-rbsxbp-5-1.vivox.com
proxy.rbsxbp.vivox.com
proxy.rbspsp.vivox.com
proxy.rbswp.vivox.com
rbswp.vivox.com
rbsxbp.vivox.com
rbspsp.vivox.com
rbspsp.www.vivox.com
rbswp.www.vivox.com
rbsxbp.www.vivox.com
rbsxbxp.vivox.com
rbspsxp.vivox.com
rbswxp.vivox.com
Mijia Cloud

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/24
192.0.2.0/24
192.88.99.0/24
192.168.0.0/16
198.19.0.0/16
198.51.100.0/24
203.0.113.0/24
224.0.0.0/4
240.0.0.0/4
255.255.255.255/32

View File

@ -0,0 +1,12 @@
::/128
::1/128
::ffff:0:0:0/96
64:ff9b::/96
100::/64
2001::/32
2001:20::/28
2001:db8::/32
2002::/16
fc00::/7
fe80::/10
ff00::/8

View File

@ -0,0 +1,16 @@
bing.com
sspanel.net
v2ex.com
#google
googleapis.cn
googleapis.com
google.com.tw
google.com.hk
gstatic.com
xn--ngstr-lra8j.com
#github
github.com
github.global.ssl.fastly.net
assets-cdn.github.com

View File

@ -0,0 +1,19 @@
149.154.160.0/20
91.108.4.0/22
91.108.56.0/24
109.239.140.0/24
67.198.55.0/24
8.8.4.4
8.8.8.8
208.67.222.222
208.67.220.220
1.1.1.1
1.1.1.2
1.0.0.1
9.9.9.9
149.112.112.112
2001:67c:4e8::/48
2001:b28:f23c::/48
2001:b28:f23d::/48
2001:b28:f23f::/48
2001:b28:f242::/48

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,274 @@
#!/bin/sh
CONFIG=passwall
LOG_FILE=/tmp/log/$CONFIG.log
LOCK_FILE_DIR=/tmp/lock
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_script.lock
echolog() {
local d="$(date "+%Y-%m-%d %H:%M:%S")"
#echo -e "$d: $1"
echo -e "$d: $1" >> $LOG_FILE
}
config_n_get() {
local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
echo "${ret:=$3}"
}
config_t_get() {
local index=0
[ -n "$4" ] && index=$4
local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null)
echo ${ret:=$3}
}
test_url() {
local url=$1
local try=1
[ -n "$2" ] && try=$2
local timeout=2
[ -n "$3" ] && timeout=$3
local extra_params=$4
curl --help all | grep "\-\-retry-all-errors" > /dev/null
[ $? == 0 ] && extra_params="--retry-all-errors ${extra_params}"
status=$(/usr/bin/curl -I -o /dev/null -skL $extra_params --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url")
case "$status" in
204|\
200)
status=200
;;
esac
echo $status
}
test_proxy() {
result=0
status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout})
if [ "$status" = "200" ]; then
result=0
else
status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout})
if [ "$status2" = "200" ]; then
result=1
else
result=2
ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1
[ $? -eq 0 ] && {
result=1
}
fi
fi
echo $result
}
url_test_node() {
result=0
local node_id=$1
local _type=$(echo $(config_n_get ${node_id} type nil) | tr 'A-Z' 'a-z')
[ "${_type}" != "nil" ] && {
if [ "${_type}" == "socks" ]; then
local _address=$(config_n_get ${node_id} address)
local _port=$(config_n_get ${node_id} port)
[ -n "${_address}" ] && [ -n "${_port}" ] && {
local curlx="socks5h://${_address}:${_port}"
local _username=$(config_n_get ${node_id} username)
local _password=$(config_n_get ${node_id} password)
[ -n "${_username}" ] && [ -n "${_password}" ] && curlx="socks5h://${_username}:${_password}@${_address}:${_port}"
}
else
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp)
/usr/share/${CONFIG}/app.sh run_socks flag="url_test_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=url_test_${node_id}.json
local curlx="socks5h://127.0.0.1:${_tmp_port}"
fi
sleep 1s
result=$(curl --connect-timeout 3 -o /dev/null -I -skL -w "%{http_code}:%{time_starttransfer}" -x $curlx "https://www.google.com/generate_204")
pgrep -af "url_test_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
rm -rf "/tmp/etc/${CONFIG}/url_test_${node_id}.json"
}
echo $result
}
test_node() {
local node_id=$1
local _type=$(echo $(config_n_get ${node_id} type nil) | tr 'A-Z' 'a-z')
[ "${_type}" != "nil" ] && {
if [ "${_type}" == "socks" ]; then
local _address=$(config_n_get ${node_id} address)
local _port=$(config_n_get ${node_id} port)
[ -n "${_address}" ] && [ -n "${_port}" ] && {
local curlx="socks5h://${_address}:${_port}"
local _username=$(config_n_get ${node_id} username)
local _password=$(config_n_get ${node_id} password)
[ -n "${_username}" ] && [ -n "${_password}" ] && curlx="socks5h://${_username}:${_password}@${_address}:${_port}"
}
else
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp)
/usr/share/${CONFIG}/app.sh run_socks flag="test_node_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=test_node_${node_id}.json
local curlx="socks5h://127.0.0.1:${_tmp_port}"
fi
sleep 1s
_proxy_status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout} "-x $curlx")
pgrep -af "test_node_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
rm -rf "/tmp/etc/${CONFIG}/test_node_${node_id}.json"
if [ "${_proxy_status}" -eq 200 ]; then
return 0
fi
}
return 1
}
flag=0
main_node=$(config_t_get global tcp_node nil)
test_auto_switch() {
flag=$(expr $flag + 1)
local TYPE=$1
local b_tcp_nodes=$2
local now_node=$3
[ -z "$now_node" ] && {
if [ -f "/tmp/etc/$CONFIG/id/${TYPE}" ]; then
now_node=$(cat /tmp/etc/$CONFIG/id/${TYPE})
if [ "$(config_n_get $now_node protocol nil)" = "_shunt" ]; then
if [ "$shunt_logic" == "1" ] && [ -f "/tmp/etc/$CONFIG/id/${TYPE}_default" ]; then
now_node=$(cat /tmp/etc/$CONFIG/id/${TYPE}_default)
elif [ "$shunt_logic" == "2" ] && [ -f "/tmp/etc/$CONFIG/id/${TYPE}_main" ]; then
now_node=$(cat /tmp/etc/$CONFIG/id/${TYPE}_main)
else
shunt_logic=0
fi
else
shunt_logic=0
fi
else
#echolog "自动切换检测:未知错误"
return 1
fi
}
[ $flag -le 1 ] && {
main_node=$now_node
}
status=$(test_proxy)
if [ "$status" == 2 ]; then
echolog "自动切换检测:无法连接到网络,请检查网络是否正常!"
return 2
fi
#检测主节点是否能使用
if [ "$restore_switch" == "1" ] && [ "$main_node" != "nil" ] && [ "$now_node" != "$main_node" ]; then
test_node ${main_node}
[ $? -eq 0 ] && {
#主节点正常,切换到主节点
echolog "自动切换检测:${TYPE}主节点【$(config_n_get $main_node type)[$(config_n_get $main_node remarks)]】正常,切换到主节点!"
/usr/share/${CONFIG}/app.sh node_switch flag=${TYPE} new_node=${main_node} shunt_logic=${shunt_logic}
[ $? -eq 0 ] && {
echolog "自动切换检测:${TYPE}节点切换完毕!"
[ "$shunt_logic" != "0" ] && {
local tcp_node=$(config_t_get global tcp_node nil)
[ "$(config_n_get $tcp_node protocol nil)" = "_shunt" ] && {
if [ "$shunt_logic" == "1" ]; then
uci set $CONFIG.$tcp_node.default_node="$main_node"
elif [ "$shunt_logic" == "2" ]; then
uci set $CONFIG.$tcp_node.main_node="$main_node"
fi
uci commit $CONFIG
}
}
}
return 0
}
fi
if [ "$status" == 0 ]; then
#echolog "自动切换检测:${TYPE}节点【$(config_n_get $now_node type)[$(config_n_get $now_node remarks)]】正常。"
return 0
elif [ "$status" == 1 ]; then
echolog "自动切换检测:${TYPE}节点【$(config_n_get $now_node type)[$(config_n_get $now_node remarks)]】异常,切换到下一个备用节点检测!"
local new_node
in_backup_nodes=$(echo $b_tcp_nodes | grep $now_node)
# 判断当前节点是否存在于备用节点列表里
if [ -z "$in_backup_nodes" ]; then
# 如果不存在,设置第一个节点为新的节点
new_node=$(echo $b_tcp_nodes | awk -F ' ' '{print $1}')
else
# 如果存在,设置下一个备用节点为新的节点
#local count=$(expr $(echo $b_tcp_nodes | grep -o ' ' | wc -l) + 1)
local next_node=$(echo $b_tcp_nodes | awk -F "$now_node" '{print $2}' | awk -F " " '{print $1}')
if [ -z "$next_node" ]; then
new_node=$(echo $b_tcp_nodes | awk -F ' ' '{print $1}')
else
new_node=$next_node
fi
fi
test_node ${new_node}
if [ $? -eq 0 ]; then
[ "$restore_switch" == "0" ] && {
[ "$shunt_logic" == "0" ] && uci set $CONFIG.@global[0].tcp_node=$new_node
[ -z "$(echo $b_tcp_nodes | grep $main_node)" ] && uci add_list $CONFIG.@auto_switch[0].tcp_node=$main_node
uci commit $CONFIG
}
echolog "自动切换检测:${TYPE}节点【$(config_n_get $new_node type)[$(config_n_get $new_node remarks)]】正常,切换到此节点!"
/usr/share/${CONFIG}/app.sh node_switch flag=${TYPE} new_node=${new_node} shunt_logic=${shunt_logic}
[ $? -eq 0 ] && {
[ "$restore_switch" == "1" ] && [ "$shunt_logic" != "0" ] && {
local tcp_node=$(config_t_get global tcp_node nil)
[ "$(config_n_get $tcp_node protocol nil)" = "_shunt" ] && {
if [ "$shunt_logic" == "1" ]; then
uci set $CONFIG.$tcp_node.default_node="$main_node"
elif [ "$shunt_logic" == "2" ]; then
uci set $CONFIG.$tcp_node.main_node="$main_node"
fi
uci commit $CONFIG
}
}
echolog "自动切换检测:${TYPE}节点切换完毕!"
}
return 0
else
test_auto_switch ${TYPE} "${b_tcp_nodes}" ${new_node}
fi
fi
}
start() {
ENABLED=$(config_t_get global enabled 0)
[ "$ENABLED" != 1 ] && return 1
ENABLED=$(config_t_get auto_switch enable 0)
[ "$ENABLED" != 1 ] && return 1
delay=$(config_t_get auto_switch testing_time 1)
#sleep 9s
connect_timeout=$(config_t_get auto_switch connect_timeout 3)
retry_num=$(config_t_get auto_switch retry_num 3)
restore_switch=$(config_t_get auto_switch restore_switch 0)
shunt_logic=$(config_t_get auto_switch shunt_logic 0)
while [ "$ENABLED" -eq 1 ]; do
[ -f "$LOCK_FILE" ] && {
sleep 6s
continue
}
touch $LOCK_FILE
TCP_NODE=$(config_t_get auto_switch tcp_node nil)
[ -n "$TCP_NODE" -a "$TCP_NODE" != "nil" ] && {
TCP_NODE=$(echo $TCP_NODE | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
test_auto_switch TCP "$TCP_NODE"
}
rm -f $LOCK_FILE
sleep ${delay}m
done
}
arg1=$1
shift
case $arg1 in
test_url)
test_url $@
;;
url_test_node)
url_test_node $@
;;
*)
start
;;
esac

View File

@ -0,0 +1,11 @@
{
"luci-app-passwall": {
"description": "Grant UCI access for luci-app-passwall",
"read": {
"uci": [ "passwall", "passwall_server" ]
},
"write": {
"uci": [ "passwall", "passwall_server" ]
}
}
}

149
luci-app-passwall2/Makefile Normal file
View File

@ -0,0 +1,149 @@
# Copyright (C) 2022-2023 xiaorouji
#
# This is free software, licensed under the GNU General Public License v3.
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=1.12-1
PKG_RELEASE:=
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_Transparent_Proxy_Iptables-nft \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Brook \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
LUCI_TITLE:=LuCI support for PassWall 2
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \
+ip-full +libuci-lua +lua +luci-compat +luci-lib-jsonc +resolveip +tcping \
+xray-core +v2ray-geoip +v2ray-geosite \
+unzip \
+PACKAGE_$(PKG_NAME)_INCLUDE_Brook:brook \
+PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria:hysteria \
+PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat:ip6tables-mod-nat \
+PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server:shadowsocks-libev-ss-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client:shadowsocks-rust-sslocal \
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server:shadowsocks-rust-ssserver \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-local \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \
+PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs:simple-obfs \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin
define Package/$(PKG_NAME)/config
menu "Configuration"
config PACKAGE_$(PKG_NAME)_Transparent_Proxy
bool "Transparent Proxy"
select PACKAGE_dnsmasq-full
select PACKAGE_dnsmasq_full_ipset
select PACKAGE_ipset
select PACKAGE_iptables
select PACKAGE_iptables-zz-legacy
select PACKAGE_iptables-mod-iprange
select PACKAGE_iptables-mod-socket
select PACKAGE_iptables-mod-tproxy
select PACKAGE_iptables-mod-conntrack-extra
select PACKAGE_kmod-ipt-nat
depends on PACKAGE_$(PKG_NAME)
default y if ! PACKAGE_firewall4
config PACKAGE_$(PKG_NAME)_Transparent_Proxy_Iptables-nft
bool "Transparent Proxy Use Iptables-nft"
select PACKAGE_dnsmasq-full
select PACKAGE_dnsmasq_full_ipset
select PACKAGE_ipset
select PACKAGE_iptables-nft
select PACKAGE_iptables-mod-iprange
select PACKAGE_iptables-mod-socket
select PACKAGE_iptables-mod-tproxy
select PACKAGE_iptables-mod-conntrack-extra
select PACKAGE_kmod-ipt-nat
depends on PACKAGE_$(PKG_NAME)
default y if PACKAGE_firewall4
config PACKAGE_$(PKG_NAME)_INCLUDE_Brook
bool "Include Brook"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria
bool "Include Hysteria"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat
depends on PACKAGE_ip6tables
bool "Include IPv6 Nat"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
bool "Include NaiveProxy"
depends on !(arc||(arm&&TARGET_gemini)||armeb||mips||mips64||powerpc)
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
bool "Include Shadowsocks Libev Client"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server
bool "Include Shadowsocks Libev Server"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
bool "Include Shadowsocks Rust Client"
depends on aarch64||arm||i386||mips||mipsel||x86_64
default y if aarch64
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server
bool "Include Shadowsocks Rust Server"
depends on aarch64||arm||i386||mips||mipsel||x86_64
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client
bool "Include ShadowsocksR Libev Client"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server
bool "Include ShadowsocksR Libev Server"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs
bool "Include Simple-Obfs (Shadowsocks Plugin)"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
bool "Include V2ray"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
bool "Include V2ray-Plugin (Shadowsocks Plugin)"
default y if aarch64||arm||i386||x86_64
endmenu
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/passwall2
/etc/config/passwall2_server
/usr/share/passwall2/domains_excluded
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,437 @@
-- Copyright (C) 2022-2023 xiaorouji
module("luci.controller.passwall2", package.seeall)
local api = require "luci.passwall2.api"
local appname = api.appname
local ucic = luci.model.uci.cursor()
local http = require "luci.http"
local util = require "luci.util"
local i18n = require "luci.i18n"
local brook = require("luci.passwall2.brook")
local v2ray = require("luci.passwall2.v2ray")
local xray = require("luci.passwall2.xray")
local hysteria = require("luci.passwall2.hysteria")
function index()
appname = require "luci.passwall2.api".appname
entry({"admin", "services", appname}).dependent = true
entry({"admin", "services", appname, "reset_config"}, call("reset_config")).leaf = true
entry({"admin", "services", appname, "show"}, call("show_menu")).leaf = true
entry({"admin", "services", appname, "hide"}, call("hide_menu")).leaf = true
if not nixio.fs.access("/etc/config/passwall2") then return end
if nixio.fs.access("/etc/config/passwall2_show") then
e = entry({"admin", "services", appname}, alias("admin", "services", appname, "settings"), _("PassWall 2"), -1)
e.dependent = true
e.acl_depends = { "luci-app-passwall2" }
end
--[[ Client ]]
entry({"admin", "services", appname, "settings"}, cbi(appname .. "/client/global"), _("Basic Settings"), 1).dependent = true
entry({"admin", "services", appname, "node_list"}, cbi(appname .. "/client/node_list"), _("Node List"), 2).dependent = true
entry({"admin", "services", appname, "node_subscribe"}, cbi(appname .. "/client/node_subscribe"), _("Node Subscribe"), 3).dependent = true
entry({"admin", "services", appname, "auto_switch"}, cbi(appname .. "/client/auto_switch"), _("Auto Switch"), 4).leaf = true
entry({"admin", "services", appname, "other"}, cbi(appname .. "/client/other", {autoapply = true}), _("Other Settings"), 92).leaf = true
entry({"admin", "services", appname, "app_update"}, cbi(appname .. "/client/app_update"), _("App Update"), 95).leaf = true
entry({"admin", "services", appname, "rule"}, cbi(appname .. "/client/rule"), _("Rule Manage"), 96).leaf = true
entry({"admin", "services", appname, "node_subscribe_config"}, cbi(appname .. "/client/node_subscribe_config")).leaf = true
entry({"admin", "services", appname, "node_config"}, cbi(appname .. "/client/node_config")).leaf = true
entry({"admin", "services", appname, "shunt_rules"}, cbi(appname .. "/client/shunt_rules")).leaf = true
entry({"admin", "services", appname, "acl"}, cbi(appname .. "/client/acl"), _("Access control"), 98).leaf = true
entry({"admin", "services", appname, "acl_config"}, cbi(appname .. "/client/acl_config")).leaf = true
entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Watch Logs"), 999).leaf = true
--[[ Server ]]
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
entry({"admin", "services", appname, "server_user"}, cbi(appname .. "/server/user")).leaf = true
--[[ API ]]
entry({"admin", "services", appname, "server_user_status"}, call("server_user_status")).leaf = true
entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).leaf = true
entry({"admin", "services", appname, "server_get_log"}, call("server_get_log")).leaf = true
entry({"admin", "services", appname, "server_clear_log"}, call("server_clear_log")).leaf = true
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).leaf = true
entry({"admin", "services", appname, "autoswitch_add_node"}, call("autoswitch_add_node")).leaf = true
entry({"admin", "services", appname, "autoswitch_remove_node"}, call("autoswitch_remove_node")).leaf = true
entry({"admin", "services", appname, "get_now_use_node"}, call("get_now_use_node")).leaf = true
entry({"admin", "services", appname, "get_redir_log"}, call("get_redir_log")).leaf = true
entry({"admin", "services", appname, "get_log"}, call("get_log")).leaf = true
entry({"admin", "services", appname, "clear_log"}, call("clear_log")).leaf = true
entry({"admin", "services", appname, "status"}, call("status")).leaf = true
entry({"admin", "services", appname, "socks_status"}, call("socks_status")).leaf = true
entry({"admin", "services", appname, "connect_status"}, call("connect_status")).leaf = true
entry({"admin", "services", appname, "ping_node"}, call("ping_node")).leaf = true
entry({"admin", "services", appname, "urltest_node"}, call("urltest_node")).leaf = true
entry({"admin", "services", appname, "set_node"}, call("set_node")).leaf = true
entry({"admin", "services", appname, "copy_node"}, call("copy_node")).leaf = true
entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "brook_check"}, call("brook_check")).leaf = true
entry({"admin", "services", appname, "brook_update"}, call("brook_update")).leaf = true
entry({"admin", "services", appname, "v2ray_check"}, call("v2ray_check")).leaf = true
entry({"admin", "services", appname, "v2ray_update"}, call("v2ray_update")).leaf = true
entry({"admin", "services", appname, "xray_check"}, call("xray_check")).leaf = true
entry({"admin", "services", appname, "xray_update"}, call("xray_update")).leaf = true
entry({"admin", "services", appname, "hysteria_check"}, call("hysteria_check")).leaf = true
entry({"admin", "services", appname, "hysteria_update"}, call("hysteria_update")).leaf = true
end
local function http_write_json(content)
http.prepare_content("application/json")
http.write_json(content or {code = 1})
end
function reset_config()
luci.sys.call('/etc/init.d/passwall2 stop')
luci.sys.call('[ -f "/usr/share/passwall2/0_default_config" ] && cp -f /usr/share/passwall2/0_default_config /etc/config/passwall2')
luci.http.redirect(api.url())
end
function show_menu()
luci.sys.call("touch /etc/config/passwall2_show")
luci.sys.call("rm -rf /tmp/luci-*")
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
luci.http.redirect(api.url())
end
function hide_menu()
luci.sys.call("rm -rf /etc/config/passwall2_show")
luci.sys.call("rm -rf /tmp/luci-*")
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
luci.http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
end
function link_add_node()
local lfile = "/tmp/links.conf"
local link = luci.http.formvalue("link")
luci.sys.call('echo \'' .. link .. '\' > ' .. lfile)
luci.sys.call("lua /usr/share/passwall2/subscribe.lua add log")
end
function autoswitch_add_node()
local key = luci.http.formvalue("key")
if key and key ~= "" then
local new_list = ucic:get(appname, "@auto_switch[0]", "node") or {}
for i = #new_list, 1, -1 do
if (ucic:get(appname, new_list[i], "remarks") or ""):find(key) then
table.remove(new_list, i)
end
end
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and e["remark"]:find(key) then
table.insert(new_list, e.id)
end
end
ucic:set_list(appname, "@auto_switch[0]", "node", new_list)
ucic:commit(appname)
end
luci.http.redirect(api.url("auto_switch"))
end
function autoswitch_remove_node()
local key = luci.http.formvalue("key")
if key and key ~= "" then
local new_list = ucic:get(appname, "@auto_switch[0]", "node") or {}
for i = #new_list, 1, -1 do
if (ucic:get(appname, new_list[i], "remarks") or ""):find(key) then
table.remove(new_list, i)
end
end
ucic:set_list(appname, "@auto_switch[0]", "node", new_list)
ucic:commit(appname)
end
luci.http.redirect(api.url("auto_switch"))
end
function get_now_use_node()
local e = {}
local data, code, msg = nixio.fs.readfile("/tmp/etc/passwall2/id/global")
if data then
e["global"] = util.trim(data)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function get_redir_log()
local id = luci.http.formvalue("id")
if nixio.fs.access("/tmp/etc/passwall2/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall2/" .. id .. ".log")
content = content:gsub("\n", "<br />")
luci.http.write(content)
else
luci.http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
end
end
function get_log()
-- luci.sys.exec("[ -f /tmp/log/passwall2.log ] && sed '1!G;h;$!d' /tmp/log/passwall2.log > /tmp/log/passwall2_show.log")
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log"))
end
function clear_log()
luci.sys.call("echo '' > /tmp/log/passwall2.log")
end
function status()
local e = {}
e["global_status"] = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep -i 'global\\.json' >/dev/null", appname)) == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function socks_status()
local e = {}
local index = luci.http.formvalue("index")
local id = luci.http.formvalue("id")
e.index = index
e.socks_status = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep 'SOCKS_' > /dev/null", appname, id)) == 0
local use_http = ucic:get(appname, id, "http_port") or 0
e.use_http = 0
if tonumber(use_http) > 0 then
e.use_http = 1
e.http_status = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", appname, id)) == 0
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function connect_status()
local e = {}
e.use_time = ""
local url = luci.http.formvalue("url")
local result = luci.sys.exec('curl --connect-timeout 3 -o /dev/null -I -sk -w "%{http_code}:%{time_starttransfer}" ' .. url)
local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0")
if code ~= 0 then
local use_time = luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $2}'")
if use_time:find("%.") then
e.use_time = string.format("%.2f", use_time * 1000)
else
e.use_time = string.format("%.2f", use_time / 1000)
end
e.ping_type = "curl"
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function ping_node()
local index = luci.http.formvalue("index")
local address = luci.http.formvalue("address")
local port = luci.http.formvalue("port")
local e = {}
e.index = index
local nodes_ping = ucic:get(appname, "@global_other[0]", "nodes_ping") or ""
if nodes_ping:find("tcping") and luci.sys.exec("echo -n $(command -v tcping)") ~= "" then
if api.is_ipv6(address) then
address = api.get_ipv6_only(address)
end
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, address))
end
if e.ping == nil or tonumber(e.ping) == 0 then
e.ping = luci.sys.exec("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % address)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function urltest_node()
local index = luci.http.formvalue("index")
local id = luci.http.formvalue("id")
local e = {}
e.index = index
local result = luci.sys.exec(string.format("/usr/share/passwall2/test.sh url_test_node %s %s", id, "urltest_node"))
local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0")
if code ~= 0 then
local use_time = luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $2}'")
if use_time:find("%.") then
e.use_time = string.format("%.2f", use_time * 1000)
else
e.use_time = string.format("%.2f", use_time / 1000)
end
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function set_node()
local type = luci.http.formvalue("type")
local config = luci.http.formvalue("config")
local section = luci.http.formvalue("section")
ucic:set(appname, type, config, section)
ucic:commit(appname)
luci.sys.call("/etc/init.d/passwall2 restart > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
function copy_node()
local section = luci.http.formvalue("section")
local uuid = api.gen_uuid()
ucic:section(appname, "nodes", uuid)
for k, v in pairs(ucic:get_all(appname, section)) do
local filter = k:find("%.")
if filter and filter == 1 then
else
xpcall(function()
ucic:set(appname, uuid, k, v)
end,
function(e)
end)
end
end
ucic:delete(appname, uuid, "add_from")
ucic:set(appname, uuid, "add_mode", 1)
ucic:commit(appname)
luci.http.redirect(api.url("node_config", uuid))
end
function clear_all_nodes()
ucic:set(appname, '@global[0]', "enabled", "0")
ucic:set(appname, '@global[0]', "node", "nil")
ucic:set_list(appname, "@auto_switch[0]", "node", {})
ucic:foreach(appname, "socks", function(t)
ucic:delete(appname, t[".name"])
end)
ucic:foreach(appname, "acl_rule", function(t)
ucic:set(appname, t[".name"], "node", "default")
end)
ucic:foreach(appname, "nodes", function(node)
ucic:delete(appname, node['.name'])
end)
ucic:commit(appname)
luci.sys.call("/etc/init.d/" .. appname .. " stop")
end
function delete_select_nodes()
local ids = luci.http.formvalue("ids")
local auto_switch_node_list = ucic:get(appname, "@auto_switch[0]", "node") or {}
string.gsub(ids, '[^' .. "," .. ']+', function(w)
for i = #auto_switch_node_list, 1, -1 do
if w == auto_switch_node_list[i] then
table.remove(auto_switch_node_list, i)
end
end
ucic:set_list(appname, "@auto_switch[0]", "node", auto_switch_node_list)
if (ucic:get(appname, "@global[0]", "node") or "nil") == w then
ucic:set(appname, '@global[0]', "node", "nil")
end
ucic:foreach(appname, "socks", function(t)
if t["node"] == w then
ucic:delete(appname, t[".name"])
end
end)
ucic:foreach(appname, "acl_rule", function(t)
if t["node"] == w then
ucic:set(appname, t[".name"], "node", "default")
end
end)
ucic:delete(appname, w)
end)
ucic:commit(appname)
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
end
function update_rules()
local update = luci.http.formvalue("update")
luci.sys.call("lua /usr/share/passwall2/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &")
http_write_json()
end
function server_user_status()
local e = {}
e.index = luci.http.formvalue("index")
e.status = luci.sys.call(string.format("top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", luci.http.formvalue("id"))) == 0
http_write_json(e)
end
function server_user_log()
local id = luci.http.formvalue("id")
if nixio.fs.access("/tmp/etc/passwall2_server/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall2_server/" .. id .. ".log")
content = content:gsub("\n", "<br />")
luci.http.write(content)
else
luci.http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
end
end
function server_get_log()
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall2_server.log' ] && cat /tmp/log/passwall2_server.log"))
end
function server_clear_log()
luci.sys.call("echo '' > /tmp/log/passwall2_server.log")
end
function brook_check()
local json = brook.to_check("")
http_write_json(json)
end
function brook_update()
local json = nil
local task = http.formvalue("task")
if task == "move" then
json = brook.to_move(http.formvalue("file"))
else
json = brook.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function v2ray_check()
local json = v2ray.to_check("")
http_write_json(json)
end
function v2ray_update()
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = v2ray.to_extract(http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = v2ray.to_move(http.formvalue("file"))
else
json = v2ray.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function xray_check()
local json = xray.to_check("")
http_write_json(json)
end
function xray_update()
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = xray.to_extract(http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = xray.to_move(http.formvalue("file"))
else
json = xray.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function hysteria_check()
local json = hysteria.to_check("")
http_write_json(json)
end
function hysteria_update()
local json = nil
local task = http.formvalue("task")
if task == "move" then
json = hysteria.to_move(http.formvalue("file"))
else
json = hysteria.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end

View File

@ -0,0 +1,62 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local sys = api.sys
local has_chnlist = api.fs.access("/usr/share/passwall2/rules/chnlist")
m = Map(appname)
s = m:section(TypedSection, "global", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
s.anonymous = true
o = s:option(Flag, "acl_enable", translate("Main switch"))
o.rmempty = false
o.default = false
-- [[ ACLs Settings ]]--
s = m:section(TypedSection, "acl_rule")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
s.extedit = api.url("acl_config", "%s")
function s.create(e, t)
t = TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Remarks
o = s:option(Value, "remarks", translate("Remarks"))
o.rmempty = true
local mac_t = {}
sys.net.mac_hints(function(e, t)
mac_t[e] = {
ip = t,
mac = e
}
end)
o = s:option(DummyValue, "sources", translate("Source"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local e = ''
local v = Value.cfgvalue(t, n) or ''
string.gsub(v, '[^' .. " " .. ']+', function(w)
local a = w
if mac_t[w] then
a = a .. ' (' .. mac_t[w].ip .. ')'
end
if #e > 0 then
e = e .. "<br />"
end
e = e .. a
end)
return e
end
return m

View File

@ -0,0 +1,284 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local sys = api.sys
m = Map(appname)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local dynamicList_write = function(self, section, value)
local t = {}
local t2 = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
else
t = { value }
end
t = table.concat(t, " ")
return DynamicList.write(self, section, t)
end
local doh_validate = function(self, value, t)
if value ~= "" then
local flag = 0
local util = require "luci.util"
local val = util.split(value, ",")
local url = val[1]
val[1] = nil
for i = 1, #val do
local v = val[i]
if v then
if not datatypes.ipmask4(v) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
end
-- [[ ACLs Settings ]]--
s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs"))
s.addremove = false
s.dynamic = false
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
---- Remarks
o = s:option(Value, "remarks", translate("Remarks"))
o.default = arg[1]
o.rmempty = true
local mac_t = {}
sys.net.mac_hints(function(e, t)
mac_t[#mac_t + 1] = {
ip = t,
mac = e
}
end)
table.sort(mac_t, function(a,b)
if #a.ip < #b.ip then
return true
elseif #a.ip == #b.ip then
if a.ip < b.ip then
return true
else
return #a.ip < #b.ip
end
end
return false
end)
---- Source
sources = s:option(DynamicList, "sources", translate("Source"))
sources.description = "<ul><li>" .. translate("Example:")
.. "</li><li>" .. translate("MAC") .. ": 00:00:00:FF:FF:FF"
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
.. "</li><li>" .. translate("IP range") .. ": 192.168.1.100-192.168.1.200"
.. "</li><li>" .. translate("IPSet") .. ": ipset:lanlist"
.. "</li></ul>"
sources.cast = "string"
for _, key in pairs(mac_t) do
sources:value(key.mac, "%s (%s)" % {key.mac, key.ip})
end
sources.cfgvalue = function(self, section)
local value
if self.tag_error[section] then
value = self:formvalue(section)
else
value = self.map:get(section, self.option)
if type(value) == "string" then
local value2 = {}
string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
value = value2
end
end
return value
end
sources.validate = function(self, value, t)
local err = {}
for _, v in ipairs(value) do
local flag = false
if v:find("ipset:") and v:find("ipset:") == 1 then
local ipset = v:gsub("ipset:", "")
if ipset and ipset ~= "" then
flag = true
end
end
if flag == false and datatypes.macaddr(v) then
flag = true
end
if flag == false and datatypes.ip4addr(v) then
flag = true
end
if flag == false and api.iprange(v) then
flag = true
end
if flag == false then
err[#err + 1] = v
end
end
if #err > 0 then
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
for _, v in ipairs(err) do
self:add_error(t, "invalid", v)
end
end
return value
end
sources.write = dynamicList_write
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
---- TCP Redir Ports
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o.default = "default"
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
o:value("22,25,53,143,465,587,853,993,995,80,443", translate("Common Use"))
o:value("80,443", "80,443")
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o.default = "default"
o:value("default", translate("Default"))
o:value("1:65535", translate("All"))
node = s:option(ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
node.default = "default"
node:value("default", translate("Default"))
for k, v in pairs(nodes_table) do
node:value(v.id, v["remark"])
end
o = s:option(ListValue, "direct_dns_protocol", translate("Direct DNS Protocol"))
o.default = "auto"
o:value("auto", translate("Auto"))
o:value("udp", "UDP")
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:depends({ node = "default", ['!reverse'] = true })
---- DNS Forward
o = s:option(Value, "direct_dns", translate("Direct DNS"))
o.datatype = "or(ipaddr,ipaddrport)"
o.default = "119.29.29.29"
o:value("114.114.114.114", "114.114.114.114 (114DNS)")
o:value("119.29.29.29", "119.29.29.29 (DNSPod)")
o:value("223.5.5.5", "223.5.5.5 (AliDNS)")
o:depends("direct_dns_protocol", "udp")
o:depends("direct_dns_protocol", "tcp")
---- DoH
o = s:option(Value, "direct_dns_doh", translate("Direct DNS DoH"))
o.default = "https://223.5.5.5/dns-query"
o:value("https://1.12.12.12/dns-query", "DNSPod 1")
o:value("https://120.53.53.53/dns-query", "DNSPod 2")
o:value("https://223.5.5.5/dns-query", "AliDNS")
o.validate = doh_validate
o:depends("direct_dns_protocol", "doh")
o = s:option(Value, "direct_dns_client_ip", translate("Direct DNS EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o:depends("direct_dns_protocol", "tcp")
o:depends("direct_dns_protocol", "doh")
o = s:option(ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
o.default = "UseIP"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o:depends({ node = "default", ['!reverse'] = true })
o = s:option(ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("udp", "UDP")
o:value("fakedns", "FakeDNS")
o:depends({ node = "default", ['!reverse'] = true })
---- DNS Forward
o = s:option(Value, "remote_dns", translate("Remote DNS"))
o.datatype = "or(ipaddr,ipaddrport)"
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
o:value("8.8.4.4", "8.8.4.4 (Google)")
o:value("8.8.8.8", "8.8.8.8 (Google)")
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
o:depends("remote_dns_protocol", "tcp")
o:depends("remote_dns_protocol", "udp")
---- DoH
o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH"))
o:value("https://1.1.1.1/dns-query", "CloudFlare")
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
o:value("https://8.8.4.4/dns-query", "Google 8844")
o:value("https://8.8.8.8/dns-query", "Google 8888")
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended")
o:value("https://208.67.222.222/dns-query", "OpenDNS")
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
o.default = "https://1.1.1.1/dns-query"
o.validate = doh_validate
o:depends("remote_dns_protocol", "doh")
o = s:option(Value, "remote_dns_client_ip", translate("Remote DNS EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o:depends("remote_dns_protocol", "tcp")
o:depends("remote_dns_protocol", "doh")
o = s:option(ListValue, "remote_dns_query_strategy", translate("Remote Query Strategy"))
o.default = "UseIPv4"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o:depends({ node = "default", ['!reverse'] = true })
hosts = s:option(TextValue, "dns_hosts", translate("Domain Override"))
hosts.rows = 5
hosts.wrap = "off"
hosts:depends({ node = "default", ['!reverse'] = true })
return m

View File

@ -0,0 +1,39 @@
local api = require "luci.passwall2.api"
local appname = api.appname
m = Map(appname)
-- [[ App Settings ]]--
s = m:section(TypedSection, "global_app", translate("App Update"),
"<font color='red'>" ..
translate("Please confirm that your firmware supports FPU.") ..
"</font>")
s.anonymous = true
s:append(Template(appname .. "/app_update/v2ray_version"))
s:append(Template(appname .. "/app_update/xray_version"))
s:append(Template(appname .. "/app_update/brook_version"))
s:append(Template(appname .. "/app_update/hysteria_version"))
o = s:option(Value, "v2ray_file", translatef("%s App Path", "V2ray"))
o.default = "/usr/bin/v2ray"
o.rmempty = false
o = s:option(Value, "xray_file", translatef("%s App Path", "Xray"))
o.default = "/usr/bin/xray"
o.rmempty = false
o = s:option(Value, "brook_file", translatef("%s App Path", "Brook"))
o.default = "/usr/bin/brook"
o.rmempty = false
o = s:option(Value, "hysteria_file", translatef("%s App Path", "Hysteria"))
o.default = "/usr/bin/hysteria"
o.rmempty = false
o = s:option(DummyValue, "tips", " ")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
end
return m

View File

@ -0,0 +1,66 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
m = Map(appname)
-- [[ Auto Switch Settings ]]--
s = m:section(TypedSection, "auto_switch")
s.anonymous = true
---- Enable
o = s:option(Flag, "enable", translate("Enable"))
o.default = 0
o.rmempty = false
o = s:option(Value, "testing_time", translate("How often to test"), translate("Units:minutes"))
o.datatype = "uinteger"
o.default = 1
o = s:option(Value, "connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
o.datatype = "uinteger"
o.default = 3
o = s:option(Value, "retry_num", translate("Timeout retry num"))
o.datatype = "uinteger"
o.default = 3
o = s:option(DynamicList, "node", translate("List of backup nodes"))
for k, v in pairs(nodes_table) do
if v.node_type == "normal" then
o:value(v.id, v["remark"])
end
end
function o.write(self, section, value)
local t = {}
local t2 = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
else
t = { value }
end
return DynamicList.write(self, section, t)
end
o = s:option(Flag, "restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
o = s:option(ListValue, "shunt_logic", translate("If the main node is shunt"))
o:value("0", translate("Switch it"))
o:value("1", translate("Applying to the default node"))
o:value("2", translate("Applying to the default preproxy node"))
m:append(Template(appname .. "/auto_switch/footer"))
return m

View File

@ -0,0 +1,339 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local uci = api.uci
local datatypes = api.datatypes
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
m = Map(appname)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local doh_validate = function(self, value, t)
if value ~= "" then
local flag = 0
local util = require "luci.util"
local val = util.split(value, ",")
local url = val[1]
val[1] = nil
for i = 1, #val do
local v = val[i]
if v then
if not datatypes.ipmask4(v) then
flag = 1
end
end
end
if flag == 0 then
return value
end
end
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
end
m:append(Template(appname .. "/global/status"))
s = m:section(TypedSection, "global")
s.anonymous = true
s.addremove = false
s:tab("Main", translate("Main"))
-- [[ Global Settings ]]--
o = s:taboption("Main", Flag, "enabled", translate("Main switch"))
o.rmempty = false
local auto_switch_tip
local shunt_remark
local current_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/global' ] && echo -n $(cat /tmp/etc/%s/id/global)", appname, appname))
if current_node and current_node ~= "" and current_node ~= "nil" then
local n = uci:get_all(appname, current_node)
if n then
if tonumber(m:get("@auto_switch[0]", "enable") or 0) == 1 then
if n.protocol == "_shunt" then
local shunt_logic = tonumber(m:get("@auto_switch[0]", "shunt_logic"))
if shunt_logic == 1 or shunt_logic == 2 then
if shunt_logic == 1 then
shunt_remark = "default"
elseif shunt_logic == 2 then
shunt_remark = "main"
end
current_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/global_%s' ] && echo -n $(cat /tmp/etc/%s/id/global_%s)", appname, shunt_remark, appname, shunt_remark))
if current_node and current_node ~= "" and current_node ~= "nil" then
n = uci:get_all(appname, current_node)
end
end
end
if n then
local remarks = api.get_node_remarks(n)
local url = api.url("node_config", n[".name"])
auto_switch_tip = translatef("Current node: %s", string.format('<a href="%s">%s</a>', url, remarks)) .. "<br />"
end
end
end
end
---- Node
node = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
node:value("nil", translate("Close"))
if not shunt_remark and auto_switch_tip then
node.description = auto_switch_tip
end
-- 分流
if (has_v2ray or has_xray) and #nodes_table > 0 then
local normal_list = {}
local shunt_list = {}
for k, v in pairs(nodes_table) do
if v.node_type == "normal" then
normal_list[#normal_list + 1] = v
end
if v.protocol and v.protocol == "_shunt" then
shunt_list[#shunt_list + 1] = v
end
end
for k, v in pairs(shunt_list) do
uci:foreach(appname, "shunt_rules", function(e)
local id = e[".name"]
if id and e.remarks then
o = s:taboption("Main", ListValue, v.id .. "." .. id .. "_node", string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", id), e.remarks))
o:depends("node", v.id)
o:value("nil", translate("Close"))
o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"])
end
o.cfgvalue = function(self, section)
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
end
end)
local id = "default_node"
o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* <a style="color:red">%s</a>', translate("Default")))
o:depends("node", v.id)
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"])
end
o.cfgvalue = function(self, section)
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
if shunt_remark == "default" and auto_switch_tip then
o.description = auto_switch_tip
end
local id = "main_node"
o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
o:depends("node", v.id)
o:value("nil", translate("Close"))
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"])
end
o.cfgvalue = function(self, section)
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
if shunt_remark == "main" and auto_switch_tip then
o.description = auto_switch_tip
end
end
end
o = s:taboption("Main", Flag, "localhost_proxy", translate("Localhost Proxy"), translate("When selected, localhost can transparent proxy."))
o.default = "1"
o.rmempty = false
node_socks_port = s:taboption("Main", Value, "node_socks_port", translate("Node") .. " Socks " .. translate("Listen Port"))
node_socks_port.default = 1070
node_socks_port.datatype = "port"
--[[
if has_v2ray or has_xray then
node_http_port = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
node_http_port.default = 0
node_http_port.datatype = "port"
end
]]--
s:tab("DNS", translate("DNS"))
o = s:taboption("DNS", ListValue, "direct_dns_protocol", translate("Direct DNS Protocol"))
o.default = "auto"
o:value("auto", translate("Auto"))
o:value("udp", "UDP")
o:value("tcp", "TCP")
o:value("doh", "DoH")
---- DNS Forward
o = s:taboption("DNS", Value, "direct_dns", translate("Direct DNS"))
o.datatype = "or(ipaddr,ipaddrport)"
o.default = "119.29.29.29"
o:value("114.114.114.114", "114.114.114.114 (114DNS)")
o:value("119.29.29.29", "119.29.29.29 (DNSPod)")
o:value("223.5.5.5", "223.5.5.5 (AliDNS)")
o:depends("direct_dns_protocol", "udp")
o:depends("direct_dns_protocol", "tcp")
---- DoH
o = s:taboption("DNS", Value, "direct_dns_doh", translate("Direct DNS DoH"))
o.default = "https://223.5.5.5/dns-query"
o:value("https://1.12.12.12/dns-query", "DNSPod 1")
o:value("https://120.53.53.53/dns-query", "DNSPod 2")
o:value("https://223.5.5.5/dns-query", "AliDNS")
o.validate = doh_validate
o:depends("direct_dns_protocol", "doh")
o = s:taboption("DNS", Value, "direct_dns_client_ip", translate("Direct DNS EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o:depends("direct_dns_protocol", "tcp")
o:depends("direct_dns_protocol", "doh")
o = s:taboption("DNS", ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
o.default = "UseIP"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("udp", "UDP")
o:value("fakedns", "FakeDNS")
---- DNS Forward
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
o.datatype = "or(ipaddr,ipaddrport)"
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
o:value("8.8.4.4", "8.8.4.4 (Google)")
o:value("8.8.8.8", "8.8.8.8 (Google)")
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
o:depends("remote_dns_protocol", "tcp")
o:depends("remote_dns_protocol", "udp")
---- DoH
o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
o.default = "https://1.1.1.1/dns-query"
o:value("https://1.1.1.1/dns-query", "CloudFlare")
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
o:value("https://8.8.4.4/dns-query", "Google 8844")
o:value("https://8.8.8.8/dns-query", "Google 8888")
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended")
o:value("https://208.67.222.222/dns-query", "OpenDNS")
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
o.validate = doh_validate
o:depends("remote_dns_protocol", "doh")
o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("Remote DNS EDNS Client Subnet"))
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
o.datatype = "ipaddr"
o:depends("remote_dns_protocol", "tcp")
o:depends("remote_dns_protocol", "doh")
o = s:taboption("DNS", ListValue, "remote_dns_query_strategy", translate("Remote Query Strategy"))
o.default = "UseIPv4"
o:value("UseIP")
o:value("UseIPv4")
o:value("UseIPv6")
hosts = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
hosts.rows = 5
hosts.wrap = "off"
s:tab("log", translate("Log"))
o = s:taboption("log", Flag, "close_log", translate("Close Node Log"))
o.rmempty = false
loglevel = s:taboption("log", ListValue, "loglevel", translate("Log Level"))
loglevel.default = "warning"
loglevel:value("debug")
loglevel:value("info")
loglevel:value("warning")
loglevel:value("error")
s:tab("faq", "FAQ")
o = s:taboption("faq", DummyValue, "")
o.template = appname .. "/global/faq"
-- [[ Socks Server ]]--
o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch"))
o.rmempty = false
s = m:section(TypedSection, "socks", translate("Socks Config"))
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
function s.create(e, t)
TypedSection.create(e, api.gen_uuid())
end
o = s:option(DummyValue, "status", translate("Status"))
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<div class="_status" socks_id="%s"></div>', n)
end
---- Enable
o = s:option(Flag, "enabled", translate("Enable"))
o.default = 1
o.rmempty = false
socks_node = s:option(ListValue, "node", translate("Socks Node"))
local n = 1
uci:foreach(appname, "socks", function(s)
if s[".name"] == section then
return false
end
n = n + 1
end)
o = s:option(Value, "port", "Socks " .. translate("Listen Port"))
o.default = n + 1080
o.datatype = "port"
o.rmempty = false
if has_v2ray or has_xray then
o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
o.default = 0
o.datatype = "port"
end
for k, v in pairs(nodes_table) do
node:value(v.id, v["remark"])
if v.type == "Socks" then
if has_v2ray or has_xray then
socks_node:value(v.id, v["remark"])
end
else
socks_node:value(v.id, v["remark"])
end
end
m:append(Template(appname .. "/global/footer"))
return m

View File

@ -0,0 +1,8 @@
local api = require "luci.passwall2.api"
local appname = api.appname
f = SimpleForm(appname)
f.reset = false
f.submit = false
f:append(Template(appname .. "/log/log"))
return f

View File

@ -0,0 +1,871 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local uci = api.uci
if not arg[1] or not uci:get(appname, arg[1]) then
luci.http.redirect(api.url("node_list"))
end
local ss_encrypt_method_list = {
"rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
"aes-192-ctr", "aes-256-ctr", "bf-cfb", "salsa20", "chacha20", "chacha20-ietf",
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
}
local ss_rust_encrypt_method_list = {
"plain", "none",
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
}
local ssr_encrypt_method_list = {
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
"chacha20-ietf"
}
local ssr_protocol_list = {
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
"auth_chain_d", "auth_chain_e", "auth_chain_f"
}
local ssr_obfs_list = {
"plain", "http_simple", "http_post", "random_head", "tls_simple",
"tls1.0_session_auth", "tls1.2_ticket_auth"
}
local v_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
}
local x_ss_encrypt_method_list = {
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local security_list = {"none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero"}
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
}
local encrypt_methods_ss_aead = {
"chacha20-ietf-poly1305",
"aes-128-gcm",
"aes-256-gcm",
}
m = Map(appname, translate("Node Config"))
m.redirect = api.url()
s = m:section(NamedSection, arg[1], "nodes", "")
s.addremove = false
s.dynamic = false
share = s:option(DummyValue, "passwall2", " ")
share.rawhtml = true
share.template = "passwall2/node_list/link_share_man"
share.value = arg[1]
remarks = s:option(Value, "remarks", translate("Node Remarks"))
remarks.default = translate("Remarks")
remarks.rmempty = false
type = s:option(ListValue, "type", translate("Type"))
if api.is_finded("ss-redir") then
type:value("SS", translate("Shadowsocks Libev"))
end
if api.is_finded("sslocal") then
type:value("SS-Rust", translate("Shadowsocks Rust"))
end
if api.is_finded("ssr-redir") then
type:value("SSR", translate("ShadowsocksR Libev"))
end
if api.is_finded("v2ray") then
type:value("V2ray", translate("V2ray"))
end
if api.is_finded("xray") then
type:value("Xray", translate("Xray"))
end
if api.is_finded("brook") then
type:value("Brook", translate("Brook"))
end
if api.is_finded("naive") then
type:value("Naiveproxy", translate("NaiveProxy"))
end
if api.is_finded("hysteria") then
type:value("Hysteria", translate("Hysteria"))
end
protocol = s:option(ListValue, "protocol", translate("Protocol"))
protocol:value("vmess", translate("Vmess"))
protocol:value("vless", translate("VLESS"))
protocol:value("http", translate("HTTP"))
protocol:value("socks", translate("Socks"))
protocol:value("shadowsocks", translate("Shadowsocks"))
protocol:value("trojan", translate("Trojan"))
protocol:value("wireguard", translate("WireGuard"))
protocol:value("_balancing", translate("Balancing"))
protocol:value("_shunt", translate("Shunt"))
protocol:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
protocol:depends("type", "V2ray")
protocol:depends("type", "Xray")
iface = s:option(Value, "iface", translate("Interface"))
iface.default = "eth1"
iface:depends("protocol", "_iface")
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
nodes_table[#nodes_table + 1] = {
id = e[".name"],
remarks = e["remark"]
}
end
end
-- 负载均衡列表
local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>"))
for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end
balancing_node:depends("protocol", "_balancing")
local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
balancingStrategy:depends("protocol", "_balancing")
balancingStrategy:value("random")
balancingStrategy:value("leastPing")
balancingStrategy.default = "random"
local probeInterval = s:option(Value, "probeInterval", translate("Probe Interval"))
probeInterval:depends("balancingStrategy", "leastPing")
probeInterval.default = "1m"
probeInterval.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>ns</code>, <code>us</code>, <code>ms</code>, <code>s</code>, <code>m</code>, <code>h</code>, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.")
-- 分流
uci:foreach(appname, "shunt_rules", function(e)
if e[".name"] and e.remarks then
o = s:option(ListValue, e[".name"], string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks))
o:value("nil", translate("Close"))
o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
o:depends("protocol", "_shunt")
if #nodes_table > 0 then
_proxy_tag = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
_proxy_tag:value("nil", translate("Close"))
_proxy_tag:value("default", translate("Default"))
_proxy_tag:value("main", translate("Default Preproxy"))
_proxy_tag.default = "nil"
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
_proxy_tag:depends(e[".name"], v.id)
end
end
end
end)
shunt_tips = s:option(DummyValue, "shunt_tips", " ")
shunt_tips.rawhtml = true
shunt_tips.cfgvalue = function(t, n)
return string.format('<a style="color: red" href="../rule">%s</a>', translate("No shunt rules? Click me to go to add."))
end
shunt_tips:depends("protocol", "_shunt")
default_node = s:option(ListValue, "default_node", string.format('* <a style="color:red">%s</a>', translate("Default")))
default_node:value("_direct", translate("Direct Connection"))
default_node:value("_blackhole", translate("Blackhole"))
for k, v in pairs(nodes_table) do default_node:value(v.id, v.remarks) end
default_node:depends("protocol", "_shunt")
if #nodes_table > 0 then
o = s:option(ListValue, "main_node", string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
o:value("nil", translate("Close"))
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
o:depends("default_node", v.id)
end
end
dialerProxy = s:option(Flag, "dialerProxy", translate("dialerProxy"))
dialerProxy:depends({ type = "Xray", protocol = "_shunt"})
domainStrategy = s:option(ListValue, "domainStrategy", translate("Domain Strategy"))
domainStrategy:value("AsIs")
domainStrategy:value("IPIfNonMatch")
domainStrategy:value("IPOnDemand")
domainStrategy.default = "IPOnDemand"
domainStrategy.description = "<br /><ul><li>" .. translate("'AsIs': Only use domain for routing. Default value.")
.. "</li><li>" .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
.. "</li><li>" .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
.. "</li></ul>"
domainStrategy:depends("protocol", "_shunt")
domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher"))
domainMatcher:value("hybrid")
domainMatcher:value("linear")
domainMatcher:depends("protocol", "_shunt")
-- Brook协议
brook_protocol = s:option(ListValue, "brook_protocol", translate("Protocol"))
brook_protocol:value("client", translate("Brook"))
brook_protocol:value("wsclient", translate("WebSocket"))
brook_protocol:depends("type", "Brook")
function brook_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function brook_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
brook_tls = s:option(Flag, "brook_tls", translate("Use TLS"))
brook_tls:depends("brook_protocol", "wsclient")
-- Naiveproxy协议
naiveproxy_protocol = s:option(ListValue, "naiveproxy_protocol", translate("Protocol"))
naiveproxy_protocol:value("https", translate("HTTPS"))
naiveproxy_protocol:value("quic", translate("QUIC"))
naiveproxy_protocol:depends("type", "Naiveproxy")
function naiveproxy_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function naiveproxy_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
address = s:option(Value, "address", translate("Address (Support Domain Name)"))
address.rmempty = false
address:depends("type", "SS")
address:depends("type", "SS-Rust")
address:depends("type", "SSR")
address:depends("type", "Brook")
address:depends("type", "Naiveproxy")
address:depends("type", "Hysteria")
address:depends({ type = "V2ray", protocol = "vmess" })
address:depends({ type = "V2ray", protocol = "vless" })
address:depends({ type = "V2ray", protocol = "http" })
address:depends({ type = "V2ray", protocol = "socks" })
address:depends({ type = "V2ray", protocol = "shadowsocks" })
address:depends({ type = "V2ray", protocol = "trojan" })
address:depends({ type = "Xray", protocol = "vmess" })
address:depends({ type = "Xray", protocol = "vless" })
address:depends({ type = "Xray", protocol = "http" })
address:depends({ type = "Xray", protocol = "socks" })
address:depends({ type = "Xray", protocol = "shadowsocks" })
address:depends({ type = "Xray", protocol = "trojan" })
address:depends({ type = "Xray", protocol = "wireguard" })
--[[
use_ipv6 = s:option(Flag, "use_ipv6", translate("Use IPv6"))
use_ipv6.default = 0
use_ipv6:depends("type", "SS")
use_ipv6:depends("type", "SS-Rust")
use_ipv6:depends("type", "SSR")
use_ipv6:depends("type", "Brook")
use_ipv6:depends("type", "Hysteria")
use_ipv6:depends({ type = "V2ray", protocol = "vmess" })
use_ipv6:depends({ type = "V2ray", protocol = "vless" })
use_ipv6:depends({ type = "V2ray", protocol = "http" })
use_ipv6:depends({ type = "V2ray", protocol = "socks" })
use_ipv6:depends({ type = "V2ray", protocol = "shadowsocks" })
use_ipv6:depends({ type = "V2ray", protocol = "trojan" })
use_ipv6:depends({ type = "Xray", protocol = "vmess" })
use_ipv6:depends({ type = "Xray", protocol = "vless" })
use_ipv6:depends({ type = "Xray", protocol = "http" })
use_ipv6:depends({ type = "Xray", protocol = "socks" })
use_ipv6:depends({ type = "Xray", protocol = "shadowsocks" })
use_ipv6:depends({ type = "Xray", protocol = "trojan" })
--]]
port = s:option(Value, "port", translate("Port"))
port.datatype = "port"
port.rmempty = false
port:depends("type", "SS")
port:depends("type", "SS-Rust")
port:depends("type", "SSR")
port:depends("type", "Brook")
port:depends("type", "Naiveproxy")
port:depends("type", "Hysteria")
port:depends({ type = "V2ray", protocol = "vmess" })
port:depends({ type = "V2ray", protocol = "vless" })
port:depends({ type = "V2ray", protocol = "http" })
port:depends({ type = "V2ray", protocol = "socks" })
port:depends({ type = "V2ray", protocol = "shadowsocks" })
port:depends({ type = "V2ray", protocol = "trojan" })
port:depends({ type = "Xray", protocol = "vmess" })
port:depends({ type = "Xray", protocol = "vless" })
port:depends({ type = "Xray", protocol = "http" })
port:depends({ type = "Xray", protocol = "socks" })
port:depends({ type = "Xray", protocol = "shadowsocks" })
port:depends({ type = "Xray", protocol = "trojan" })
port:depends({ type = "Xray", protocol = "wireguard" })
hysteria_hop = s:option(Value, "hysteria_hop", translate("Additional ports for hysteria hop"))
hysteria_hop:depends("type", "Hysteria")
username = s:option(Value, "username", translate("Username"))
username:depends("type", "Naiveproxy")
username:depends({ type = "V2ray", protocol = "http" })
username:depends({ type = "V2ray", protocol = "socks" })
username:depends({ type = "Xray", protocol = "http" })
username:depends({ type = "Xray", protocol = "socks" })
password = s:option(Value, "password", translate("Password"))
password.password = true
password:depends("type", "SS")
password:depends("type", "SS-Rust")
password:depends("type", "SSR")
password:depends("type", "Brook")
password:depends("type", "Naiveproxy")
password:depends({ type = "V2ray", protocol = "http" })
password:depends({ type = "V2ray", protocol = "socks" })
password:depends({ type = "V2ray", protocol = "shadowsocks" })
password:depends({ type = "V2ray", protocol = "trojan" })
password:depends({ type = "Xray", protocol = "http" })
password:depends({ type = "Xray", protocol = "socks" })
password:depends({ type = "Xray", protocol = "shadowsocks" })
password:depends({ type = "Xray", protocol = "trojan" })
hysteria_protocol = s:option(ListValue, "hysteria_protocol", translate("Protocol"))
hysteria_protocol:value("udp", "UDP")
hysteria_protocol:value("faketcp", "faketcp")
hysteria_protocol:value("wechat-video", "wechat-video")
hysteria_protocol:depends("type", "Hysteria")
function hysteria_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function hysteria_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
hysteria_obfs = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
hysteria_obfs:depends("type", "Hysteria")
hysteria_auth_type = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
hysteria_auth_type:value("disable", translate("Disable"))
hysteria_auth_type:value("string", translate("STRING"))
hysteria_auth_type:value("base64", translate("BASE64"))
hysteria_auth_type:depends("type", "Hysteria")
hysteria_auth_password = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
hysteria_auth_password.password = true
hysteria_auth_password:depends("hysteria_auth_type", "string")
hysteria_auth_password:depends("hysteria_auth_type", "base64")
hysteria_alpn = s:option(Value, "hysteria_alpn", translate("QUIC TLS ALPN"))
hysteria_alpn:depends("type", "Hysteria")
ss_encrypt_method = s:option(Value, "ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ss_encrypt_method_list) do ss_encrypt_method:value(t) end
ss_encrypt_method:depends("type", "SS")
function ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
ss_rust_encrypt_method = s:option(Value, "ss_rust_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ss_rust_encrypt_method_list) do ss_rust_encrypt_method:value(t) end
ss_rust_encrypt_method:depends("type", "SS-Rust")
function ss_rust_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ss_rust_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
ssr_encrypt_method = s:option(Value, "ssr_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(ssr_encrypt_method_list) do ssr_encrypt_method:value(t) end
ssr_encrypt_method:depends("type", "SSR")
function ssr_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function ssr_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
security = s:option(ListValue, "security", translate("Encrypt Method"))
for a, t in ipairs(security_list) do security:value(t) end
security:depends({ type = "V2ray", protocol = "vmess" })
security:depends({ type = "Xray", protocol = "vmess" })
encryption = s:option(Value, "encryption", translate("Encrypt Method"))
encryption.default = "none"
encryption:value("none")
encryption:depends({ type = "V2ray", protocol = "vless" })
encryption:depends({ type = "Xray", protocol = "vless" })
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(v_ss_encrypt_method_list) do v_ss_encrypt_method:value(t) end
v_ss_encrypt_method:depends({ type = "V2ray", protocol = "shadowsocks" })
function v_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function v_ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
x_ss_encrypt_method = s:option(ListValue, "x_ss_encrypt_method", translate("Encrypt Method"))
for a, t in ipairs(x_ss_encrypt_method_list) do x_ss_encrypt_method:value(t) end
x_ss_encrypt_method:depends({ type = "Xray", protocol = "shadowsocks" })
function x_ss_encrypt_method.cfgvalue(self, section)
return m:get(section, "method")
end
function x_ss_encrypt_method.write(self, section, value)
m:set(section, "method", value)
end
iv_check = s:option(Flag, "iv_check", translate("IV Check"))
iv_check:depends({ type = "V2ray", protocol = "shadowsocks" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "aes-128-gcm" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "aes-256-gcm" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "chacha20-poly1305" })
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "xchacha20-poly1305" })
uot = s:option(Flag, "uot", translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side."))
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-aes-128-gcm" })
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-aes-256-gcm" })
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-chacha20-poly1305" })
ssr_protocol = s:option(Value, "ssr_protocol", translate("Protocol"))
for a, t in ipairs(ssr_protocol_list) do ssr_protocol:value(t) end
ssr_protocol:depends("type", "SSR")
function ssr_protocol.cfgvalue(self, section)
return m:get(section, "protocol")
end
function ssr_protocol.write(self, section, value)
m:set(section, "protocol", value)
end
protocol_param = s:option(Value, "protocol_param", translate("Protocol_param"))
protocol_param:depends("type", "SSR")
obfs = s:option(Value, "obfs", translate("Obfs"))
for a, t in ipairs(ssr_obfs_list) do obfs:value(t) end
obfs:depends("type", "SSR")
obfs_param = s:option(Value, "obfs_param", translate("Obfs_param"))
obfs_param:depends("type", "SSR")
timeout = s:option(Value, "timeout", translate("Connection Timeout"))
timeout.datatype = "uinteger"
timeout.default = 300
timeout:depends("type", "SS")
timeout:depends("type", "SS-Rust")
timeout:depends("type", "SSR")
tcp_fast_open = s:option(ListValue, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
tcp_fast_open:value("false")
tcp_fast_open:value("true")
tcp_fast_open:depends("type", "SS")
tcp_fast_open:depends("type", "SS-Rust")
tcp_fast_open:depends("type", "SSR")
fast_open = s:option(Flag, "fast_open", translate("Fast Open"))
fast_open.default = "0"
fast_open:depends("type", "Hysteria")
ss_plugin = s:option(ListValue, "ss_plugin", translate("plugin"))
ss_plugin:value("none", translate("none"))
if api.is_finded("xray-plugin") then ss_plugin:value("xray-plugin") end
if api.is_finded("v2ray-plugin") then ss_plugin:value("v2ray-plugin") end
if api.is_finded("obfs-local") then ss_plugin:value("obfs-local") end
ss_plugin:depends("type", "SS")
ss_plugin:depends("type", "SS-Rust")
function ss_plugin.cfgvalue(self, section)
return m:get(section, "plugin")
end
function ss_plugin.write(self, section, value)
m:set(section, "plugin", value)
end
ss_plugin_opts = s:option(Value, "ss_plugin_opts", translate("opts"))
ss_plugin_opts:depends("ss_plugin", "xray-plugin")
ss_plugin_opts:depends("ss_plugin", "v2ray-plugin")
ss_plugin_opts:depends("ss_plugin", "obfs-local")
function ss_plugin_opts.cfgvalue(self, section)
return m:get(section, "plugin_opts")
end
function ss_plugin_opts.write(self, section, value)
m:set(section, "plugin_opts", value)
end
uuid = s:option(Value, "uuid", translate("ID"))
uuid.password = true
uuid:depends({ type = "V2ray", protocol = "vmess" })
uuid:depends({ type = "V2ray", protocol = "vless" })
uuid:depends({ type = "Xray", protocol = "vmess" })
uuid:depends({ type = "Xray", protocol = "vless" })
tls = s:option(Flag, "tls", translate("TLS"))
tls.default = 0
tls:depends({ type = "V2ray", protocol = "vmess" })
tls:depends({ type = "V2ray", protocol = "vless" })
tls:depends({ type = "V2ray", protocol = "socks" })
tls:depends({ type = "V2ray", protocol = "trojan" })
tls:depends({ type = "V2ray", protocol = "shadowsocks" })
tls:depends({ type = "Xray", protocol = "vmess" })
tls:depends({ type = "Xray", protocol = "vless" })
tls:depends({ type = "Xray", protocol = "socks" })
tls:depends({ type = "Xray", protocol = "trojan" })
tls:depends({ type = "Xray", protocol = "shadowsocks" })
tlsflow = s:option(Value, "tlsflow", translate("flow"))
tlsflow.default = ""
tlsflow:value("", translate("Disable"))
tlsflow:value("xtls-rprx-vision")
tlsflow:value("xtls-rprx-vision-udp443")
tlsflow:depends({ type = "Xray", protocol = "vless", tls = true, transport = "tcp" })
reality = s:option(Flag, "reality", translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
reality.default = 0
reality:depends({ type = "Xray", tls = true, transport = "tcp" })
reality:depends({ type = "Xray", tls = true, transport = "h2" })
reality:depends({ type = "Xray", tls = true, transport = "grpc" })
alpn = s:option(ListValue, "alpn", translate("alpn"))
alpn.default = "default"
alpn:value("default", translate("Default"))
alpn:value("h2,http/1.1")
alpn:value("h2")
alpn:value("http/1.1")
alpn:depends({ type = "V2ray", tls = true })
alpn:depends({ type = "Xray", tls = true, reality = false })
tls_serverName = s:option(Value, "tls_serverName", translate("Domain"))
tls_serverName:depends("tls", true)
tls_serverName:depends("type", "Hysteria")
tls_allowInsecure = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
tls_allowInsecure.default = "0"
tls_allowInsecure:depends({ tls = true, reality = false })
tls_allowInsecure:depends("type", "Hysteria")
xray_fingerprint = s:option(Value, "xray_fingerprint", translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
xray_fingerprint:value("", translate("Disable"))
xray_fingerprint:value("chrome")
xray_fingerprint:value("firefox")
xray_fingerprint:value("safari")
xray_fingerprint:value("ios")
--xray_fingerprint:value("android")
xray_fingerprint:value("edge")
--xray_fingerprint:value("360")
xray_fingerprint:value("qq")
xray_fingerprint:value("random")
xray_fingerprint:value("randomized")
xray_fingerprint.default = ""
xray_fingerprint:depends({ type = "Xray", tls = true, reality = false })
function xray_fingerprint.cfgvalue(self, section)
return m:get(section, "fingerprint")
end
function xray_fingerprint.write(self, section, value)
m:set(section, "fingerprint", value)
end
function xray_fingerprint.remove(self, section)
m:del(section, "fingerprint")
end
-- [[ REALITY部分 ]] --
reality_publicKey = s:option(Value, "reality_publicKey", translate("Public Key"))
reality_publicKey:depends({ type = "Xray", tls = true, reality = true })
reality_shortId = s:option(Value, "reality_shortId", translate("Short Id"))
reality_shortId:depends({ type = "Xray", tls = true, reality = true })
reality_spiderX = s:option(Value, "reality_spiderX", translate("Spider X"))
reality_spiderX.placeholder = "/"
reality_spiderX:depends({ type = "Xray", tls = true, reality = true })
reality_fingerprint = s:option(Value, "reality_fingerprint", translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
reality_fingerprint:value("chrome")
reality_fingerprint:value("firefox")
reality_fingerprint:value("safari")
reality_fingerprint:value("ios")
--reality_fingerprint:value("android")
reality_fingerprint:value("edge")
--reality_fingerprint:value("360")
reality_fingerprint:value("qq")
reality_fingerprint:value("random")
reality_fingerprint:value("randomized")
reality_fingerprint.default = "chrome"
reality_fingerprint:depends({ type = "Xray", tls = true, reality = true })
function reality_fingerprint.cfgvalue(self, section)
return m:get(section, "fingerprint")
end
function reality_fingerprint.write(self, section, value)
m:set(section, "fingerprint", value)
end
transport = s:option(ListValue, "transport", translate("Transport"))
transport:value("tcp", "TCP")
transport:value("mkcp", "mKCP")
transport:value("ws", "WebSocket")
transport:value("h2", "HTTP/2")
transport:value("ds", "DomainSocket")
transport:value("quic", "QUIC")
transport:value("grpc", "gRPC")
transport:depends({ type = "V2ray", protocol = "vmess" })
transport:depends({ type = "V2ray", protocol = "vless" })
transport:depends({ type = "V2ray", protocol = "socks" })
transport:depends({ type = "V2ray", protocol = "shadowsocks" })
transport:depends({ type = "V2ray", protocol = "trojan" })
transport:depends({ type = "Xray", protocol = "vmess" })
transport:depends({ type = "Xray", protocol = "vless" })
transport:depends({ type = "Xray", protocol = "socks" })
transport:depends({ type = "Xray", protocol = "shadowsocks" })
transport:depends({ type = "Xray", protocol = "trojan" })
--[[
ss_transport = s:option(ListValue, "ss_transport", translate("Transport"))
ss_transport:value("ws", "WebSocket")
ss_transport:value("h2", "HTTP/2")
ss_transport:value("h2+ws", "HTTP/2 & WebSocket")
ss_transport:depends({ type = "V2ray", protocol = "shadowsocks" })
ss_transport:depends({ type = "Xray", protocol = "shadowsocks" })
]]--
wireguard_public_key = s:option(Value, "wireguard_public_key", translate("Public Key"))
wireguard_public_key:depends({ type = "Xray", protocol = "wireguard" })
wireguard_secret_key = s:option(Value, "wireguard_secret_key", translate("Private Key"))
wireguard_secret_key:depends({ type = "Xray", protocol = "wireguard" })
wireguard_preSharedKey = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
wireguard_preSharedKey:depends({ type = "Xray", protocol = "wireguard" })
wireguard_local_address = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
wireguard_local_address:depends({ type = "Xray", protocol = "wireguard" })
wireguard_mtu = s:option(Value, "wireguard_mtu", translate("MTU"))
wireguard_mtu.default = "1420"
wireguard_mtu:depends({ type = "Xray", protocol = "wireguard" })
if api.compare_versions(api.get_xray_version(), ">=", "1.8.0") then
wireguard_reserved = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
wireguard_reserved:depends({ type = "Xray", protocol = "wireguard" })
end
wireguard_keepAlive = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
wireguard_keepAlive.default = "0"
wireguard_keepAlive:depends({ type = "Xray", protocol = "wireguard" })
-- [[ TCP部分 ]]--
-- TCP伪装
tcp_guise = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
tcp_guise:value("none", "none")
tcp_guise:value("http", "http")
tcp_guise:depends("transport", "tcp")
-- HTTP域名
tcp_guise_http_host = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
tcp_guise_http_host:depends("tcp_guise", "http")
-- HTTP路径
tcp_guise_http_path = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
tcp_guise_http_path.placeholder = "/"
tcp_guise_http_path:depends("tcp_guise", "http")
-- [[ mKCP部分 ]]--
mkcp_guise = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
for a, t in ipairs(header_type_list) do mkcp_guise:value(t) end
mkcp_guise:depends("transport", "mkcp")
mkcp_mtu = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
mkcp_mtu.default = "1350"
mkcp_mtu:depends("transport", "mkcp")
mkcp_tti = s:option(Value, "mkcp_tti", translate("KCP TTI"))
mkcp_tti.default = "20"
mkcp_tti:depends("transport", "mkcp")
mkcp_uplinkCapacity = s:option(Value, "mkcp_uplinkCapacity", translate("KCP uplinkCapacity"))
mkcp_uplinkCapacity.default = "5"
mkcp_uplinkCapacity:depends("transport", "mkcp")
mkcp_downlinkCapacity = s:option(Value, "mkcp_downlinkCapacity", translate("KCP downlinkCapacity"))
mkcp_downlinkCapacity.default = "20"
mkcp_downlinkCapacity:depends("transport", "mkcp")
mkcp_congestion = s:option(Flag, "mkcp_congestion", translate("KCP Congestion"))
mkcp_congestion:depends("transport", "mkcp")
mkcp_readBufferSize = s:option(Value, "mkcp_readBufferSize", translate("KCP readBufferSize"))
mkcp_readBufferSize.default = "1"
mkcp_readBufferSize:depends("transport", "mkcp")
mkcp_writeBufferSize = s:option(Value, "mkcp_writeBufferSize", translate("KCP writeBufferSize"))
mkcp_writeBufferSize.default = "1"
mkcp_writeBufferSize:depends("transport", "mkcp")
mkcp_seed = s:option(Value, "mkcp_seed", translate("KCP Seed"))
mkcp_seed:depends("transport", "mkcp")
-- [[ WebSocket部分 ]]--
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
ws_host:depends("transport", "ws")
ws_host:depends("ss_transport", "ws")
ws_path = s:option(Value, "ws_path", translate("WebSocket Path"))
ws_path.placeholder = "/"
ws_path:depends("transport", "ws")
ws_path:depends("ss_transport", "ws")
ws_path:depends({ type = "Brook", brook_protocol = "wsclient" })
ws_enableEarlyData = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
ws_enableEarlyData:depends({ type = "V2ray", transport = "ws" })
ws_maxEarlyData = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
ws_maxEarlyData.default = "1024"
ws_maxEarlyData:depends("ws_enableEarlyData", true)
ws_earlyDataHeaderName = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
ws_earlyDataHeaderName:depends("ws_enableEarlyData", true)
-- [[ HTTP/2部分 ]]--
h2_host = s:option(Value, "h2_host", translate("HTTP/2 Host"))
h2_host:depends("transport", "h2")
h2_host:depends("ss_transport", "h2")
h2_path = s:option(Value, "h2_path", translate("HTTP/2 Path"))
h2_path.placeholder = "/"
h2_path:depends("transport", "h2")
h2_path:depends("ss_transport", "h2")
h2_health_check = s:option(Flag, "h2_health_check", translate("Health check"))
h2_health_check:depends({ type = "Xray", transport = "h2"})
h2_read_idle_timeout = s:option(Value, "h2_read_idle_timeout", translate("Idle timeout"))
h2_read_idle_timeout.default = "10"
h2_read_idle_timeout:depends("h2_health_check", true)
h2_health_check_timeout = s:option(Value, "h2_health_check_timeout", translate("Health check timeout"))
h2_health_check_timeout.default = "15"
h2_health_check_timeout:depends("h2_health_check", true)
-- [[ DomainSocket部分 ]]--
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
ds_path:depends("transport", "ds")
-- [[ QUIC部分 ]]--
quic_security = s:option(ListValue, "quic_security", translate("Encrypt Method"))
quic_security:value("none")
quic_security:value("aes-128-gcm")
quic_security:value("chacha20-poly1305")
quic_security:depends("transport", "quic")
quic_key = s:option(Value, "quic_key", translate("Encrypt Method") .. translate("Key"))
quic_key:depends("transport", "quic")
quic_guise = s:option(ListValue, "quic_guise", translate("Camouflage Type"))
for a, t in ipairs(header_type_list) do quic_guise:value(t) end
quic_guise:depends("transport", "quic")
-- [[ gRPC部分 ]]--
grpc_serviceName = s:option(Value, "grpc_serviceName", "ServiceName")
grpc_serviceName:depends("transport", "grpc")
grpc_mode = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
grpc_mode:value("gun")
grpc_mode:value("multi")
grpc_mode:depends({ type = "Xray", transport = "grpc"})
grpc_health_check = s:option(Flag, "grpc_health_check", translate("Health check"))
grpc_health_check:depends({ type = "Xray", transport = "grpc"})
grpc_idle_timeout = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
grpc_idle_timeout.default = "10"
grpc_idle_timeout:depends("grpc_health_check", true)
grpc_health_check_timeout = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
grpc_health_check_timeout.default = "20"
grpc_health_check_timeout:depends("grpc_health_check", true)
grpc_permit_without_stream = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
grpc_permit_without_stream.default = "0"
grpc_permit_without_stream:depends("grpc_health_check", true)
grpc_initial_windows_size = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
grpc_initial_windows_size.default = "0"
grpc_initial_windows_size:depends({ type = "Xray", transport = "grpc"})
-- [[ Mux ]]--
mux = s:option(Flag, "mux", translate("Mux"))
mux:depends({ type = "V2ray", protocol = "vmess" })
mux:depends({ type = "V2ray", protocol = "vless" })
mux:depends({ type = "V2ray", protocol = "http" })
mux:depends({ type = "V2ray", protocol = "socks" })
mux:depends({ type = "V2ray", protocol = "shadowsocks" })
mux:depends({ type = "V2ray", protocol = "trojan" })
mux:depends({ type = "Xray", protocol = "vmess" })
mux:depends({ type = "Xray", protocol = "vless", tlsflow = "" })
mux:depends({ type = "Xray", protocol = "http" })
mux:depends({ type = "Xray", protocol = "socks" })
mux:depends({ type = "Xray", protocol = "shadowsocks" })
mux:depends({ type = "Xray", protocol = "trojan" })
-- [[ XUDP Mux ]]--
xmux = s:option(Flag, "xmux", translate("Mux"))
xmux.default = 1
xmux:depends({ type = "Xray", protocol = "vless", tlsflow = "xtls-rprx-vision" })
xmux:depends({ type = "Xray", protocol = "vless", tlsflow = "xtls-rprx-vision-udp443" })
mux_concurrency = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
mux_concurrency.default = 8
mux_concurrency:depends("mux", true)
mux_concurrency:depends("smux", true)
xudp_concurrency = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency"))
xudp_concurrency.default = 8
xudp_concurrency:depends("xmux", true)
smux_idle_timeout = s:option(Value, "smux_idle_timeout", translate("Mux idle timeout"))
smux_idle_timeout.default = 60
smux_idle_timeout:depends("smux", true)
hysteria_up_mbps = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
hysteria_up_mbps.default = "10"
hysteria_up_mbps:depends("type", "Hysteria")
hysteria_down_mbps = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
hysteria_down_mbps.default = "50"
hysteria_down_mbps:depends("type", "Hysteria")
hysteria_recv_window_conn = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
hysteria_recv_window_conn:depends("type", "Hysteria")
hysteria_recv_window = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
hysteria_recv_window:depends("type", "Hysteria")
hysteria_handshake_timeout = s:option(Value, "hysteria_handshake_timeout", translate("Handshake Timeout"))
hysteria_handshake_timeout:depends("type", "Hysteria")
hysteria_idle_timeout = s:option(Value, "hysteria_idle_timeout", translate("Idle Timeout"))
hysteria_idle_timeout:depends("type", "Hysteria")
hysteria_hop_interval = s:option(Value, "hysteria_hop_interval", translate("Hop Interval"))
hysteria_hop_interval:depends("type", "Hysteria")
hysteria_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
hysteria_disable_mtu_discovery:depends("type", "Hysteria")
hysteria_lazy_start = s:option(Flag, "hysteria_lazy_start", translate("Lazy Start"))
hysteria_lazy_start:depends("type", "Hysteria")
protocol.validate = function(self, value)
if value == "_shunt" or value == "_balancing" then
address.rmempty = true
port.rmempty = true
end
return value
end
return m

View File

@ -0,0 +1,140 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local sys = api.sys
local datatypes = api.datatypes
m = Map(appname)
-- [[ Other Settings ]]--
s = m:section(TypedSection, "global_other")
s.anonymous = true
o = s:option(MultiValue, "nodes_ping", " ")
o:value("auto_ping", translate("Auto Ping"), translate("This will automatically ping the node for latency"))
o:value("tcping", translate("Tcping"), translate("This will use tcping replace ping detection of node"))
o:value("info", translate("Show server address and port"), translate("Show server address and port"))
-- [[ Add the node via the link ]]--
s:append(Template(appname .. "/node_list/link_add_node"))
local nodes_ping = m:get("@global_other[0]", "nodes_ping") or ""
-- [[ Node List ]]--
s = m:section(TypedSection, "nodes")
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_config", "%s")
function s.create(e, t)
local uuid = api.gen_uuid()
t = uuid
TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function s.remove(e, t)
m.uci:foreach(appname, "socks", function(s)
if s["node"] == t then
m:del(s[".name"])
end
end)
m.uci:foreach(appname, "acl_rule", function(s)
if s["node"] and s["node"] == t then
m:set(s[".name"], "node", "default")
end
end)
for k, v in ipairs(m:get("@auto_switch[0]", "node") or {}) do
if v and v == t then
sys.call(string.format("uci -q del_list %s.@auto_switch[0].node='%s'", appname, v))
end
end
TypedSection.remove(e, t)
local new_node = "nil"
local node0 = m:get("@nodes[0]") or nil
if node0 then
new_node = node0[".name"]
end
if (m:get("@global[0]", "node") or "nil") == t then
m:set('@global[0]', "node", new_node)
end
end
s.sortable = true
-- 简洁模式
o = s:option(DummyValue, "add_from", "")
o.cfgvalue = function(t, n)
local v = Value.cfgvalue(t, n)
if v and v ~= '' then
local group = m:get(n, "group") or ""
if group ~= "" then
v = v .. " " .. group
end
return v
else
return ''
end
end
o = s:option(DummyValue, "remarks", translate("Remarks"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local str = ""
local is_sub = m:get(n, "is_sub") or ""
local group = m:get(n, "group") or ""
local remarks = m:get(n, "remarks") or ""
local type = m:get(n, "type") or ""
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.type' value='%s'/>", appname, n, type)
if type == "V2ray" or type == "Xray" then
local protocol = m:get(n, "protocol")
if protocol == "_balancing" then
protocol = translate("Balancing")
elseif protocol == "_shunt" then
protocol = translate("Shunt")
elseif protocol == "vmess" then
protocol = "VMess"
elseif protocol == "vless" then
protocol = "VLESS"
else
protocol = protocol:gsub("^%l",string.upper)
end
type = type .. " " .. protocol
end
local address = m:get(n, "address") or ""
local port = m:get(n, "port") or ""
str = str .. translate(type) .. "" .. remarks
if address ~= "" and port ~= "" then
if nodes_ping:find("info") then
if datatypes.ip6addr(address) then
str = str .. string.format("[%s]:%s", address, port)
else
str = str .. string.format("%s:%s", address, port)
end
end
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.address' value='%s'/>", appname, n, address)
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.port' value='%s'/>", appname, n, port)
end
return str
end
---- Ping
o = s:option(DummyValue, "ping")
o.width = "8%"
o.rawhtml = true
o.cfgvalue = function(t, n)
local result = "---"
if not nodes_ping:find("auto_ping") then
result = string.format('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'%s\',this)">Ping</a></span>', n)
else
result = string.format('<span class="ping_value" cbiid="%s">---</span>', n)
end
return result
end
o = s:option(DummyValue, "_url_test")
o.rawhtml = true
o.cfgvalue = function(t, n)
return string.format('<input type="button" class="cbi-button" value="%s" onclick="javascript:urltest_node(\'%s\',this)"', translate("Availability test"), n)
end
m:append(Template(appname .. "/node_list/node_list"))
return m

View File

@ -0,0 +1,119 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local ss_aead_type = {}
if has_ss then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-libev"
end
if has_ss_rust then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
end
if has_v2ray then
ss_aead_type[#ss_aead_type + 1] = "v2ray"
end
if has_xray then
ss_aead_type[#ss_aead_type + 1] = "xray"
end
m = Map(appname)
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
s.anonymous = true
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
if #ss_aead_type > 0 then
o = s:option(ListValue, "ss_aead_type", translate("SS AEAD Node Use Type"))
for key, value in pairs(ss_aead_type) do
o:value(value, translate(value:gsub("^%l",string.upper)))
end
end
---- Subscribe Delete All
o = s:option(Button, "_stop", translate("Delete All Subscribe Node"))
o.inputstyle = "remove"
function o.write(e, e)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1")
end
o = s:option(Button, "_update", translate("Manual subscription All"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("Please input the subscription url first, save and submit before manual subscription.") .. "</font>")
s.addremove = true
s.anonymous = true
s.sortable = true
s.template = "cbi/tblsection"
s.extedit = api.url("node_subscribe_config", "%s")
function s.create(e, t)
local id = TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(id))
end
o = s:option(Value, "remark", translate("Remarks"))
o.width = "auto"
o.rmempty = false
o.validate = function(self, value, t)
if value then
local count = 0
m.uci:foreach(appname, "subscribe_list", function(e)
if e[".name"] ~= t and e["remark"] == value then
count = count + 1
end
end)
if count > 0 then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
end
o = s:option(DummyValue, "_node_count")
o.rawhtml = true
o.cfgvalue = function(t, n)
local remark = m:get(n, "remark") or ""
local num = 0
m.uci:foreach(appname, "nodes", function(s)
if s["add_from"] ~= "" and s["add_from"] == remark then
num = num + 1
end
end)
return string.format("<span title='%s' style='color:red'>%s</span>", remark .. " " .. translate("Node num") .. ": " .. num, num)
end
o = s:option(Value, "url", translate("Subscribe URL"))
o.width = "auto"
o.rmempty = false
o = s:option(Button, "_remove", translate("Delete the subscribed node"))
o.inputstyle = "remove"
function o.write(t, n)
local remark = m:get(n, "remark") or ""
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate " .. remark .. " > /dev/null 2>&1")
end
o = s:option(Button, "_update", translate("Manual subscription"))
o.inputstyle = "apply"
function o.write(t, n)
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. n .. " > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
return m

View File

@ -0,0 +1,90 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local sys = api.sys
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local ss_aead_type = {}
if has_ss then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-libev"
end
if has_ss_rust then
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
end
if has_v2ray then
ss_aead_type[#ss_aead_type + 1] = "v2ray"
end
if has_xray then
ss_aead_type[#ss_aead_type + 1] = "xray"
end
m = Map(appname)
m.redirect = api.url("node_subscribe")
s = m:section(NamedSection, arg[1])
s.addremove = false
s.dynamic = false
o = s:option(Value, "remark", translate("Subscribe Remark"))
o.rmempty = false
o = s:option(TextValue, "url", translate("Subscribe URL"))
o.rows = 5
o.rmempty = false
o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "1"
o.rmempty = false
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
o.default = "5"
o:value("0", translate("Close"))
o:value("1", translate("Discard List"))
o:value("2", translate("Keep List"))
o:value("3", translate("Discard List,But Keep List First"))
o:value("4", translate("Keep List,But Discard List First"))
o:value("5", translate("Use global config"))
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
o:depends("filter_keyword_mode", "1")
o:depends("filter_keyword_mode", "3")
o:depends("filter_keyword_mode", "4")
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
o:depends("filter_keyword_mode", "2")
o:depends("filter_keyword_mode", "3")
o:depends("filter_keyword_mode", "4")
if #ss_aead_type > 0 then
o = s:option(ListValue, "ss_aead_type", translate("SS AEAD Node Use Type"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(ss_aead_type) do
o:value(value, translate(value:gsub("^%l",string.upper)))
end
end
---- Enable auto update subscribe
o = s:option(Flag, "auto_update", translate("Enable auto update subscribe"))
o.default = 0
o.rmempty = false
---- Week update rules
o = s:option(ListValue, "week_update", translate("Week update rules"))
o:value(7, translate("Every day"))
for e = 1, 6 do o:value(e, translate("Week") .. e) end
o:value(0, translate("Week") .. translate("day"))
o.default = 0
o:depends("auto_update", true)
---- Day update rules
o = s:option(ListValue, "time_update", translate("Day update rules"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
o.default = 0
o:depends("auto_update", true)
o = s:option(Value, "user_agent", translate("User-Agent"))
o.default = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
return m

View File

@ -0,0 +1,146 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local fs = api.fs
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
m = Map(appname)
-- [[ Delay Settings ]]--
s = m:section(TypedSection, "global_delay", translate("Delay Settings"))
s.anonymous = true
s.addremove = false
---- Delay Start
o = s:option(Value, "start_delay", translate("Delay Start"), translate("Units:seconds"))
o.default = "1"
o.rmempty = true
---- Open and close Daemon
o = s:option(Flag, "start_daemon", translate("Open and close Daemon"))
o.default = 1
o.rmempty = false
--[[
---- Open and close automatically
o = s:option(Flag, "auto_on", translate("Open and close automatically"))
o.default = 0
o.rmempty = false
---- Automatically turn off time
o = s:option(ListValue, "time_off", translate("Automatically turn off time"))
o.default = nil
o:depends("auto_on", true)
o:value(nil, translate("Disable"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
---- Automatically turn on time
o = s:option(ListValue, "time_on", translate("Automatically turn on time"))
o.default = nil
o:depends("auto_on", true)
o:value(nil, translate("Disable"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
---- Automatically restart time
o = s:option(ListValue, "time_restart", translate("Automatically restart time"))
o.default = nil
o:depends("auto_on", true)
o:value(nil, translate("Disable"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
--]]
-- [[ Forwarding Settings ]]--
s = m:section(TypedSection, "global_forwarding", translate("Forwarding Settings"))
s.anonymous = true
s.addremove = false
---- TCP No Redir Ports
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
o.default = "disable"
o:value("disable", translate("No patterns are used"))
o:value("1:65535", translate("All"))
---- UDP No Redir Ports
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
"<font color='red'>" ..
translate("Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
"</font>")
o.default = "disable"
o:value("disable", translate("No patterns are used"))
o:value("1:65535", translate("All"))
---- TCP Redir Ports
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
o.default = "22,25,53,143,465,587,853,993,995,80,443"
o:value("1:65535", translate("All"))
o:value("22,25,53,143,465,587,853,993,995,80,443", translate("Common Use"))
o:value("80,443", translate("Only Web"))
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o.default = "1:65535"
o:value("1:65535", translate("All"))
if os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod | grep -i TPROXY >/dev/null") == 0 then
o = s:option(ListValue, "tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "redirect"
o:value("redirect", "REDIRECT")
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", false)
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))
o.default = "tproxy"
o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", true)
o.write = function(self, section, value)
return self.map:set(section, "tcp_proxy_way", value)
end
if os.execute("lsmod | grep -i ip6table_mangle >/dev/null") == 0 then
---- IPv6 TProxy
o = s:option(Flag, "ipv6_tproxy", translate("IPv6 TProxy"),
"<font color='red'>" ..
translate("Experimental feature. Make sure that your node supports IPv6.") ..
"</font>")
o.default = 0
o.rmempty = false
end
end
o = s:option(Flag, "accept_icmp", translate("Hijacking ICMP (PING)"))
o.default = 0
o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
o:depends("ipv6_tproxy", true)
o.default = 0
if has_v2ray or has_xray then
o = s:option(Flag, "sniffing", translate("Sniffing (V2Ray/Xray)"), translate("When using the V2ray/Xray shunt, must be enabled, otherwise the shunt will invalid."))
o.default = 1
o.rmempty = false
if has_xray then
route_only = s:option(Flag, "route_only", translate("Sniffing Route Only (Xray)"), translate("When enabled, the server not will resolve the domain name again."))
route_only.default = 0
route_only:depends("sniffing", true)
local domains_excluded = string.format("/usr/share/%s/domains_excluded", appname)
o = s:option(TextValue, "no_sniffing_hosts", translate("No Sniffing Lists"), translate("Hosts added into No Sniffing Lists will not resolve again on server (Xray only)."))
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section) return fs.readfile(domains_excluded) or "" end
o.write = function(self, section, value) fs.writefile(domains_excluded, value:gsub("\r\n", "\n")) end
o.remove = function(self, section, value)
if route_only:formvalue(section) == "0" then
fs.writefile(domains_excluded, "")
end
end
o:depends({sniffing = true, route_only = false})
o = s:option(Value, "buffer_size", translate("Buffer Size (Xray)"), translate("Buffer size for every connection (kB)"))
o.rmempty = true
o.datatype = "uinteger"
end
end
return m

View File

@ -0,0 +1,65 @@
local api = require "luci.passwall2.api"
local appname = api.appname
m = Map(appname)
-- [[ Rule Settings ]]--
s = m:section(TypedSection, "global_rules", translate("Rule status"))
s.anonymous = true
s:append(Template(appname .. "/rule/rule_version"))
---- Auto Update
o = s:option(Flag, "auto_update", translate("Enable auto update rules"))
o.default = 0
o.rmempty = false
---- Week Update
o = s:option(ListValue, "week_update", translate("Week update rules"))
o:value(7, translate("Every day"))
for e = 1, 6 do o:value(e, translate("Week") .. e) end
o:value(0, translate("Week") .. translate("day"))
o.default = 0
o:depends("auto_update", true)
---- Time Update
o = s:option(ListValue, "time_update", translate("Day update rules"))
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
o.default = 0
o:depends("auto_update", true)
o = s:option(Value, "v2ray_location_asset", translate("Location of V2ray/Xray asset"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
o.default = "/usr/share/v2ray/"
o.rmempty = false
---- Custom geo file url
o = s:option(Value, "geoip_url", translate("Custom geoip URL"))
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
o.rmempty = false
o = s:option(Value, "geosite_url", translate("Custom geosite URL"))
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
o.rmempty = false
----
s = m:section(TypedSection, "shunt_rules", "V2ray/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
s.template = "cbi/tblsection"
s.anonymous = false
s.addremove = true
s.sortable = true
s.extedit = api.url("shunt_rules", "%s")
function s.create(e, t)
TypedSection.create(e, t)
luci.http.redirect(e.extedit:format(t))
end
function s.remove(e, t)
m.uci:foreach(appname, "nodes", function(s)
if s["protocol"] and s["protocol"] == "_shunt" then
m:del(s[".name"], t)
end
end)
TypedSection.remove(e, t)
end
o = s:option(DummyValue, "remarks", translate("Remarks"))
return m

View File

@ -0,0 +1,158 @@
local api = require "luci.passwall2.api"
local appname = api.appname
local datatypes = api.datatypes
m = Map(appname, "V2ray/Xray " .. translate("Shunt Rule"))
m.redirect = api.url()
s = m:section(NamedSection, arg[1], "shunt_rules", "")
s.addremove = false
s.dynamic = false
remarks = s:option(Value, "remarks", translate("Remarks"))
remarks.default = arg[1]
remarks.rmempty = false
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
protocol:value("tls")
protocol:value("bittorrent")
network = s:option(ListValue, "network", translate("Network"))
network:value("tcp,udp", "TCP UDP")
network:value("tcp", "TCP")
network:value("udp", "UDP")
source = s:option(DynamicList, "source", translate("Source"))
source.description = "<ul><li>" .. translate("Example:")
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
.. "</li><li>" .. translate("GeoIP") .. ": geoip:private"
.. "</li></ul>"
source.cast = "string"
source.cfgvalue = function(self, section)
local value
if self.tag_error[section] then
value = self:formvalue(section)
else
value = self.map:get(section, self.option)
if type(value) == "string" then
local value2 = {}
string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
value = value2
end
end
return value
end
source.validate = function(self, value, t)
local err = {}
for _, v in ipairs(value) do
local flag = false
if datatypes.ip4addr(v) then
flag = true
end
if flag == false and v:find("geoip:") and v:find("geoip:") == 1 then
flag = true
end
if flag == false then
err[#err + 1] = v
end
end
if #err > 0 then
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
for _, v in ipairs(err) do
self:add_error(t, "invalid", v)
end
end
return value
end
local dynamicList_write = function(self, section, value)
local t = {}
local t2 = {}
if type(value) == "table" then
local x
for _, x in ipairs(value) do
if x and #x > 0 then
if not t2[x] then
t2[x] = x
t[#t+1] = x
end
end
end
else
t = { value }
end
t = table.concat(t, " ")
return DynamicList.write(self, section, t)
end
source.write = dynamicList_write
sourcePort = s:option(Value, "sourcePort", translate("Source port"))
port = s:option(Value, "port", translate("port"))
domain_list = s:option(TextValue, "domain_list", translate("Domain"))
domain_list.rows = 10
domain_list.wrap = "off"
domain_list.validate = function(self, value)
local hosts= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
for index, host in ipairs(hosts) do
local flag = 1
local tmp_host = host
if host:find("regexp:") and host:find("regexp:") == 1 then
flag = 0
elseif host:find("domain:.") and host:find("domain:.") == 1 then
tmp_host = host:gsub("domain:", "")
elseif host:find("full:.") and host:find("full:.") == 1 then
tmp_host = host:gsub("full:", "")
elseif host:find("geosite:") and host:find("geosite:") == 1 then
flag = 0
elseif host:find("ext:") and host:find("ext:") == 1 then
flag = 0
end
if flag == 1 then
if not datatypes.hostname(tmp_host) then
return nil, tmp_host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
end
return value
end
domain_list.description = "<br /><ul><li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.")
.. "</li><li>" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.")
.. "</li><li>" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.")
.. "</li><li>" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.")
.. "</li><li>" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.")
.. "</li><li>" .. translate("Domains from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geosite.dat. The tag must exist in the file.")
.. "</li></ul>"
ip_list = s:option(TextValue, "ip_list", "IP")
ip_list.rows = 10
ip_list.wrap = "off"
ip_list.validate = function(self, value)
local ipmasks= {}
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
for index, ipmask in ipairs(ipmasks) do
if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 then
elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 then
else
if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
end
return value
end
ip_list.description = "<br /><ul><li>" .. translate("IP: such as '127.0.0.1'.")
.. "</li><li>" .. translate("CIDR: such as '127.0.0.0/8'.")
.. "</li><li>" .. translate("GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code.")
.. "</li><li>" .. translate("IPs from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geoip.dat. The tag must exist in the file.")
.. "</li></ul>"
return m

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