mirror of
https://github.com/kenzok8/small.git
synced 2025-01-07 03:26:39 +08:00
update
This commit is contained in:
parent
e89bcc5715
commit
9d30bd7b3d
112
.github/workflows/build.yml
vendored
Normal file
112
.github/workflows/build.yml
vendored
Normal 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
134
.github/workflows/test.yml
vendored
Normal 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 # 根据需要修改
|
@ -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))
|
||||
|
@ -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))
|
||||
|
@ -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
61
gn/Makefile
Normal 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
11
gn/patches/010-gcc.patch
Normal 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')
|
9
gn/src/out/last_commit_position.h
Normal file
9
gn/src/out/last_commit_position.h
Normal 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_
|
@ -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))
|
||||
|
@ -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
44
lua-neturl/Makefile
Normal 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))
|
20
lua-neturl/patches/010-userinfo-regex.patch
Normal file
20
lua-neturl/patches/010-userinfo-regex.patch
Normal 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
183
luci-app-passwall/Makefile
Normal 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
|
429
luci-app-passwall/luasrc/controller/passwall.lua
Normal file
429
luci-app-passwall/luasrc/controller/passwall.lua
Normal 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
|
130
luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
Normal file
130
luci-app-passwall/luasrc/model/cbi/passwall/client/acl.lua
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
564
luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
Normal file
564
luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua
Normal 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
|
140
luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
Normal file
140
luci-app-passwall/luasrc/model/cbi/passwall/client/haproxy.lua
Normal 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
|
@ -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
|
@ -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
|
151
luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
Normal file
151
luci-app-passwall/luasrc/model/cbi/passwall/client/node_list.lua
Normal 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
|
@ -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
|
@ -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
|
171
luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
Normal file
171
luci-app-passwall/luasrc/model/cbi/passwall/client/other.lua
Normal 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
|
91
luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
Normal file
91
luci-app-passwall/luasrc/model/cbi/passwall/client/rule.lua
Normal 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
|
271
luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
Normal file
271
luci-app-passwall/luasrc/model/cbi/passwall/client/rule_list.lua
Normal 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
|
@ -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
|
72
luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
Normal file
72
luci-app-passwall/luasrc/model/cbi/passwall/server/index.lua
Normal 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
|
746
luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
Normal file
746
luci-app-passwall/luasrc/model/cbi/passwall/server/user.lua
Normal 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
|
929
luci-app-passwall/luasrc/passwall/api.lua
Normal file
929
luci-app-passwall/luasrc/passwall/api.lua
Normal 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
|
99
luci-app-passwall/luasrc/passwall/com.lua
Normal file
99
luci-app-passwall/luasrc/passwall/com.lua
Normal 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
|
232
luci-app-passwall/luasrc/passwall/server_app.lua
Normal file
232
luci-app-passwall/luasrc/passwall/server_app.lua
Normal 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
|
119
luci-app-passwall/luasrc/passwall/util_hysteria.lua
Normal file
119
luci-app-passwall/luasrc/passwall/util_hysteria.lua
Normal 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
|
39
luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
Normal file
39
luci-app-passwall/luasrc/passwall/util_naiveproxy.lua
Normal 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
|
144
luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
Normal file
144
luci-app-passwall/luasrc/passwall/util_shadowsocks.lua
Normal 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
|
158
luci-app-passwall/luasrc/passwall/util_trojan.lua
Normal file
158
luci-app-passwall/luasrc/passwall/util_trojan.lua
Normal 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
|
1301
luci-app-passwall/luasrc/passwall/util_xray.lua
Normal file
1301
luci-app-passwall/luasrc/passwall/util_xray.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -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%>
|
@ -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%>" />
|
43
luci-app-passwall/luasrc/view/passwall/global/faq.htm
Normal file
43
luci-app-passwall/luasrc/view/passwall/global/faq.htm
Normal 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>
|
140
luci-app-passwall/luasrc/view/passwall/global/footer.htm
Normal file
140
luci-app-passwall/luasrc/view/passwall/global/footer.htm
Normal 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> ';
|
||||
var div2 = '<font style="font-weight:bold;" color="red">X</font> ';
|
||||
|
||||
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 += "  " + log_a.outerHTML;
|
||||
}
|
||||
|
||||
node_select.insertAdjacentHTML("afterend", "  " + 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", "  " + new_a.outerHTML);
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout("go()", 1000);
|
||||
|
||||
//]]>
|
||||
</script>
|
278
luci-app-passwall/luasrc/view/passwall/global/status.htm
Normal file
278
luci-app-passwall/luasrc/view/passwall/global/status.htm
Normal file
File diff suppressed because one or more lines are too long
26
luci-app-passwall/luasrc/view/passwall/haproxy/status.htm
Normal file
26
luci-app-passwall/luasrc/view/passwall/haproxy/status.htm
Normal 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>
|
31
luci-app-passwall/luasrc/view/passwall/log/log.htm
Normal file
31
luci-app-passwall/luasrc/view/passwall/log/log.htm
Normal 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>
|
@ -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>
|
1029
luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
Normal file
1029
luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
Normal file
File diff suppressed because it is too large
Load Diff
475
luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
Normal file
475
luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
Normal 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 + '§ion=' + 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 + '" /> ';
|
||||
//添加"置顶"按钮
|
||||
new_div += '<input class="btn cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top(\'' + id + '\')"/> ';
|
||||
//添加"应用"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_' + id + '" onclick="open_set_node_div(\'' + id + '\')"/> ';
|
||||
//添加"复制"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node(\'' + id + '\')"/> ';
|
||||
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>   ";
|
||||
}
|
||||
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>
|
76
luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
Normal file
76
luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
Normal 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>
|
35
luci-app-passwall/luasrc/view/passwall/server/log.htm
Normal file
35
luci-app-passwall/luasrc/view/passwall/server/log.htm
Normal 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>
|
@ -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\')"/> ';
|
||||
td.innerHTML = new_div + td.innerHTML;
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
1421
luci-app-passwall/po/zh-cn/passwall.po
Normal file
1421
luci-app-passwall/po/zh-cn/passwall.po
Normal file
File diff suppressed because it is too large
Load Diff
1
luci-app-passwall/po/zh_Hans
Symbolic link
1
luci-app-passwall/po/zh_Hans
Symbolic link
@ -0,0 +1 @@
|
||||
zh-cn
|
4
luci-app-passwall/root/etc/config/passwall_server
Normal file
4
luci-app-passwall/root/etc/config/passwall_server
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
config global 'global'
|
||||
option enable '0'
|
||||
|
23
luci-app-passwall/root/etc/hotplug.d/iface/98-passwall
Normal file
23
luci-app-passwall/root/etc/hotplug.d/iface/98-passwall
Normal 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}
|
||||
}
|
||||
}
|
85
luci-app-passwall/root/etc/init.d/passwall
Executable file
85
luci-app-passwall/root/etc/init.d/passwall
Executable 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}" ]
|
||||
}
|
16
luci-app-passwall/root/etc/init.d/passwall_server
Executable file
16
luci-app-passwall/root/etc/init.d/passwall_server
Executable 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
|
||||
}
|
44
luci-app-passwall/root/etc/uci-defaults/luci-passwall
Executable file
44
luci-app-passwall/root/etc/uci-defaults/luci-passwall
Executable 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
|
205
luci-app-passwall/root/usr/share/passwall/0_default_config
Normal file
205
luci-app-passwall/root/usr/share/passwall/0_default_config
Normal 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'
|
1664
luci-app-passwall/root/usr/share/passwall/app.sh
Executable file
1664
luci-app-passwall/root/usr/share/passwall/app.sh
Executable file
File diff suppressed because it is too large
Load Diff
219
luci-app-passwall/root/usr/share/passwall/haproxy.lua
Normal file
219
luci-app-passwall/root/usr/share/passwall/haproxy.lua
Normal 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()
|
18
luci-app-passwall/root/usr/share/passwall/haproxy_check.sh
Executable file
18
luci-app-passwall/root/usr/share/passwall/haproxy_check.sh
Executable 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}
|
89
luci-app-passwall/root/usr/share/passwall/helper_dnsmasq.sh
Executable file
89
luci-app-passwall/root/usr/share/passwall/helper_dnsmasq.sh
Executable 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
|
421
luci-app-passwall/root/usr/share/passwall/helper_dnsmasq_add.lua
Normal file
421
luci-app-passwall/root/usr/share/passwall/helper_dnsmasq_add.lua
Normal 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流程,将会导致域名(直连/代理域名)分流失效!!!")
|
1268
luci-app-passwall/root/usr/share/passwall/iptables.sh
Executable file
1268
luci-app-passwall/root/usr/share/passwall/iptables.sh
Executable file
File diff suppressed because it is too large
Load Diff
48
luci-app-passwall/root/usr/share/passwall/monitor.sh
Executable file
48
luci-app-passwall/root/usr/share/passwall/monitor.sh
Executable 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
|
1225
luci-app-passwall/root/usr/share/passwall/nftables.sh
Executable file
1225
luci-app-passwall/root/usr/share/passwall/nftables.sh
Executable file
File diff suppressed because it is too large
Load Diff
425
luci-app-passwall/root/usr/share/passwall/rule_update.lua
Executable file
425
luci-app-passwall/root/usr/share/passwall/rule_update.lua
Executable 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("规则更新完毕...")
|
64038
luci-app-passwall/root/usr/share/passwall/rules/chnlist
Normal file
64038
luci-app-passwall/root/usr/share/passwall/rules/chnlist
Normal file
File diff suppressed because it is too large
Load Diff
8610
luci-app-passwall/root/usr/share/passwall/rules/chnroute
Normal file
8610
luci-app-passwall/root/usr/share/passwall/rules/chnroute
Normal file
File diff suppressed because it is too large
Load Diff
1973
luci-app-passwall/root/usr/share/passwall/rules/chnroute6
Normal file
1973
luci-app-passwall/root/usr/share/passwall/rules/chnroute6
Normal file
File diff suppressed because it is too large
Load Diff
18
luci-app-passwall/root/usr/share/passwall/rules/direct_host
Normal file
18
luci-app-passwall/root/usr/share/passwall/rules/direct_host
Normal 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
|
@ -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
|
@ -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
|
6129
luci-app-passwall/root/usr/share/passwall/rules/gfwlist
Normal file
6129
luci-app-passwall/root/usr/share/passwall/rules/gfwlist
Normal file
File diff suppressed because it is too large
Load Diff
16
luci-app-passwall/root/usr/share/passwall/rules/lanlist_ipv4
Normal file
16
luci-app-passwall/root/usr/share/passwall/rules/lanlist_ipv4
Normal 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
|
12
luci-app-passwall/root/usr/share/passwall/rules/lanlist_ipv6
Normal file
12
luci-app-passwall/root/usr/share/passwall/rules/lanlist_ipv6
Normal 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
|
16
luci-app-passwall/root/usr/share/passwall/rules/proxy_host
Normal file
16
luci-app-passwall/root/usr/share/passwall/rules/proxy_host
Normal 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
|
19
luci-app-passwall/root/usr/share/passwall/rules/proxy_ip
Normal file
19
luci-app-passwall/root/usr/share/passwall/rules/proxy_ip
Normal 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
|
1283
luci-app-passwall/root/usr/share/passwall/subscribe.lua
Executable file
1283
luci-app-passwall/root/usr/share/passwall/subscribe.lua
Executable file
File diff suppressed because it is too large
Load Diff
274
luci-app-passwall/root/usr/share/passwall/test.sh
Executable file
274
luci-app-passwall/root/usr/share/passwall/test.sh
Executable 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
|
@ -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
149
luci-app-passwall2/Makefile
Normal 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
|
437
luci-app-passwall2/luasrc/controller/passwall2.lua
Normal file
437
luci-app-passwall2/luasrc/controller/passwall2.lua
Normal 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
|
||||
|
62
luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua
Normal file
62
luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
339
luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
Normal file
339
luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
146
luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
Normal file
146
luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
Normal 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
|
@ -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
|
@ -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
Loading…
Reference in New Issue
Block a user