├── .dependabot └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── build-package.yml │ ├── pull-request.yml │ └── release-package.yml ├── .gitignore ├── .huskyrc ├── .lintstagedrc ├── README.md ├── gulpfile.js ├── package.json ├── public ├── Makefile ├── luasrc │ └── controller │ │ └── v2ray.lua ├── po │ ├── templates │ │ └── v2ray.pot │ └── zh_Hans │ │ └── v2ray.po └── root │ ├── etc │ ├── config │ │ └── v2ray │ ├── firewall.v2ray │ ├── init.d │ │ └── v2ray │ ├── uci-defaults │ │ └── 40_luci-v2ray │ └── v2ray │ │ ├── chnroute.txt │ │ ├── chnroute6.txt │ │ ├── directlist.txt │ │ ├── gfwlist.txt │ │ ├── proxylist.txt │ │ ├── srcdirectlist.txt │ │ └── transport.json │ └── usr │ ├── libexec │ └── rpcd │ │ └── luci.v2ray │ └── share │ ├── luci │ └── menu.d │ │ └── luci-app-v2ray.json │ └── rpcd │ └── acl.d │ └── luci-app-v2ray.json ├── scripts ├── i18n-scan.pl └── i18n-update.pl ├── src ├── typings │ └── v2ray.d.ts ├── v2ray.ts └── view │ └── v2ray │ ├── about.ts │ ├── dns.ts │ ├── inbound.ts │ ├── include │ └── custom.ts │ ├── main.ts │ ├── outbound.ts │ ├── policy.ts │ ├── reverse.ts │ ├── routing.ts │ ├── tools │ ├── base64.ts │ └── converters.ts │ └── transparent-proxy.ts ├── tsconfig.json └── yarn.lock /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | - package_manager: "javascript" 4 | directory: "/" 5 | update_schedule: "weekly" 6 | automerged_updates: 7 | - match: 8 | dependency_type: "all" 9 | update_type: "all" 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [Makefile,*.md] 11 | indent_style = tab 12 | indent_size = 2 13 | 14 | [*v2ray] 15 | indent_style = tab 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.*.js 2 | output/ 3 | package/ 4 | *.po~ 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | "eslint:recommended", 10 | "plugin:prettier/recommended", 11 | "prettier/@typescript-eslint", 12 | ], 13 | parser: "@typescript-eslint/parser", 14 | plugins: ["@typescript-eslint", "prettier", "eslint-plugin-tsdoc"], 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: "module", 18 | }, 19 | rules: { 20 | "no-var": "error", 21 | "prefer-const": "warn", 22 | }, 23 | overrides: [ 24 | { 25 | files: ["src/typings/**/*.d.ts"], 26 | rules: { 27 | "no-unused-vars": "off", 28 | }, 29 | }, 30 | ], 31 | globals: { 32 | _: "readonly", 33 | E: "readonly", 34 | L: "readonly", 35 | LuCI: "readonly", 36 | 37 | baseclass: "readonly", 38 | dom: "readonly", 39 | form: "readonly", 40 | fs: "readonly", 41 | network: "readonly", 42 | poll: "readonly", 43 | request: "readonly", 44 | rpc: "readonly", 45 | uci: "readonly", 46 | ui: "readonly", 47 | validation: "readonly", 48 | view: "readonly", 49 | widgets: "readonly", 50 | xhr: "readonly", 51 | 52 | base64: "readonly", 53 | converters: "readonly", 54 | custom: "readonly", 55 | v2ray: "readonly", 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /.github/workflows/build-package.yml: -------------------------------------------------------------------------------- 1 | name: Push Package Sources and Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags-ignore: 7 | - '*' 8 | 9 | env: 10 | PACKAGE_NAME: luci-app-v2ray 11 | CACHE_DIR: ~/cache 12 | 13 | jobs: 14 | push-and-build: 15 | runs-on: ubuntu-latest 16 | env: 17 | SDK_URL_PATH: https://downloads.openwrt.org/releases/19.07.3/targets/x86/64 18 | SDK_NAME: -sdk-19.07.3-x86-64_ 19 | CONFIG_CCACHE: y 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | with: 24 | persist-credentials: false 25 | fetch-depth: 2 26 | 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: '12.x' 31 | 32 | - name: Setup Node.js Cache 33 | uses: actions/cache@v1 34 | with: 35 | path: node_modules 36 | key: node-${{ env.PACKAGE_NAME }}-${{ hashFiles('yarn.lock') }} 37 | restore-keys: | 38 | node-${{ env.PACKAGE_NAME }}- 39 | 40 | - name: Test Source 41 | run: | 42 | yarn install 43 | yarn lint 44 | 45 | - name: Build Package Source 46 | run: | 47 | yarn install 48 | yarn package 49 | 50 | - name: Deploy Package Source 51 | uses: peaceiris/actions-gh-pages@v3 52 | with: 53 | github_token: ${{ secrets.GITHUB_TOKEN }} 54 | publish_dir: ./package 55 | publish_branch: luci2 56 | user_name: 'github-actions[bot]' 57 | user_email: 'github-actions[bot]@users.noreply.github.com' 58 | commit_message: ${{ github.event.head_commit.message }} 59 | 60 | - name: Prepare Cache Key 61 | id: cache_key 62 | run: echo "::set-output name=timestamp::$(date +"%s")" 63 | 64 | - name: Setup OpenWrt Cache 65 | uses: actions/cache@v1 66 | with: 67 | path: ${{ env.CACHE_DIR }} 68 | key: openwrt-${{ env.PACKAGE_NAME }}-${{ steps.cache_key.outputs.timestamp }} 69 | restore-keys: | 70 | openwrt-${{ env.PACKAGE_NAME }}- 71 | 72 | - name: Install Dependencies 73 | run: | 74 | DEBIAN_FRONTEND=noninteractive \ 75 | sudo apt-get install -y ccache gettext libncurses5-dev xsltproc 76 | 77 | - name: Create Directories 78 | run: | 79 | CACHE_DIR_SDK="$(eval echo "$CACHE_DIR/sdk")" 80 | CACHE_DIR_DL="$(eval echo "$CACHE_DIR/dl")" 81 | CACHE_DIR_FEEDS="$(eval echo "$CACHE_DIR/feeds")" 82 | 83 | CCACHE_DIR_HOST="$(eval echo "$CACHE_DIR/ccache/host")" 84 | CCACHE_DIR_TARGET="$(eval echo "$CACHE_DIR/ccache/target")" 85 | CCACHE_DIR_TOOLCHAIN="$(eval echo "$CACHE_DIR/ccache/toolchain")" 86 | 87 | test -d "$CACHE_DIR_SDK" || mkdir -p "$CACHE_DIR_SDK" 88 | test -d "$CACHE_DIR_DL" || mkdir -p "$CACHE_DIR_DL" 89 | test -d "$CACHE_DIR_FEEDS" || mkdir -p "$CACHE_DIR_FEEDS" 90 | 91 | test -d "$CCACHE_DIR_HOST" || mkdir -p "$CCACHE_DIR_HOST" 92 | test -d "$CCACHE_DIR_TARGET" || mkdir -p "$CCACHE_DIR_TARGET" 93 | test -d "$CCACHE_DIR_TOOLCHAIN" || mkdir -p "$CCACHE_DIR_TOOLCHAIN" 94 | 95 | echo "::set-env name=CACHE_DIR_SDK::$CACHE_DIR_SDK" 96 | echo "::set-env name=CACHE_DIR_DL::$CACHE_DIR_DL" 97 | echo "::set-env name=CACHE_DIR_FEEDS::$CACHE_DIR_FEEDS" 98 | 99 | echo "::set-env name=CCACHE_DIR_HOST::$CCACHE_DIR_HOST" 100 | echo "::set-env name=CCACHE_DIR_TARGET::$CCACHE_DIR_TARGET" 101 | echo "::set-env name=CCACHE_DIR_TOOLCHAIN::$CCACHE_DIR_TOOLCHAIN" 102 | 103 | echo "::set-env name=SDK_HOME::$(mktemp -d)" 104 | 105 | - name: Download and Unzip SDK 106 | run: | 107 | cd "$CACHE_DIR_SDK" 108 | 109 | if ! ( wget -q -O - "$SDK_URL_PATH/sha256sums" | \ 110 | grep -- "$SDK_NAME" > sha256sums.small 2>/dev/null ) ; then 111 | echo "::error::Can not find ${SDK_NAME} file in sha256sums." 112 | exit 1 113 | fi 114 | 115 | SDK_FILE="$(cat sha256sums.small | cut -d' ' -f2 | sed 's/*//g')" 116 | 117 | if ! sha256sum -c ./sha256sums.small >/dev/null 2>&1 ; then 118 | wget -q -O "$SDK_FILE" "$SDK_URL_PATH/$SDK_FILE" 119 | if ! sha256sum -c ./sha256sums.small >/dev/null 2>&1 ; then 120 | echo "::error::SDK can not be verified!" 121 | exit 1 122 | fi 123 | fi 124 | 125 | cd - 126 | 127 | file "$CACHE_DIR_SDK/$SDK_FILE" 128 | 129 | tar -Jxf "$CACHE_DIR_SDK/$SDK_FILE" -C "$SDK_HOME" --strip=1 130 | 131 | cd "$SDK_HOME" 132 | 133 | cp feeds.conf.default feeds.conf 134 | 135 | sed -i 's#git.openwrt.org/openwrt/openwrt#github.com/openwrt/openwrt#' feeds.conf 136 | sed -i 's#git.openwrt.org/feed/packages#github.com/openwrt/packages#' feeds.conf 137 | sed -i 's#git.openwrt.org/project/luci#github.com/openwrt/luci#' feeds.conf 138 | sed -i 's#git.openwrt.org/feed/telephony#github.com/openwrt/telephony#' feeds.conf 139 | 140 | cd - 141 | 142 | - name: Restore OpenWrt Cache 143 | run: | 144 | cd "$SDK_HOME" 145 | 146 | test -d "dl" && rm -rf "dl" || true 147 | test -d "feeds" && rm -rf "feeds" || true 148 | 149 | ln -s "$CACHE_DIR_DL" "dl" 150 | ln -s "$CACHE_DIR_FEEDS" "feeds" 151 | 152 | staging_dir_host="staging_dir/host" 153 | staging_dir_target="staging_dir/target-x86_64_musl" 154 | staging_dir_toolchain="$(eval echo "staging_dir/toolchain-*")" 155 | 156 | test -d "$staging_dir_host" || mkdir -p "$staging_dir_host" 157 | test -d "$staging_dir_target" || mkdir -p "$staging_dir_target" 158 | test -d "$staging_dir_toolchain" || mkdir -p "$staging_dir_toolchain" 159 | 160 | ln -s "$CCACHE_DIR_HOST" "$staging_dir_host/ccache" 161 | ln -s "$CCACHE_DIR_TARGET" "$staging_dir_target/ccache" 162 | ln -s "$CCACHE_DIR_TOOLCHAIN" "$staging_dir_toolchain/ccache" 163 | 164 | CCACHE_DIR="$staging_dir_host/ccache" staging_dir/host/bin/ccache -s 165 | CCACHE_DIR="$staging_dir_target/ccache" staging_dir/host/bin/ccache -s 166 | CCACHE_DIR="$staging_dir_toolchain/ccache" staging_dir/host/bin/ccache -s 167 | 168 | cd - 169 | 170 | - name: Update and Install Packages 171 | run: | 172 | cd "$SDK_HOME" 173 | 174 | cp -rf "${{ github.workspace }}/package" "package/$PACKAGE_NAME" 175 | 176 | ./scripts/feeds update -a 177 | ./scripts/feeds install -a 178 | 179 | cd - 180 | 181 | - name: Build Package 182 | run: | 183 | cd "$SDK_HOME" 184 | 185 | make defconfig 186 | make package/${PACKAGE_NAME}/compile V=s 187 | 188 | find "$SDK_HOME/bin/" -type f -name "*.ipk" -exec ls -lh {} \; 189 | 190 | cd - 191 | 192 | - name: Copy Build Results 193 | run: | 194 | # Copy bin files 195 | find "$SDK_HOME/bin/" -type f -name "${PACKAGE_NAME}*.ipk" -exec cp {} "${{ github.workspace }}" \; 196 | 197 | # Copy translations 198 | find "$SDK_HOME/bin/" -type f -name "luci-i18n*.ipk" -exec cp {} "${{ github.workspace }}" \; 199 | 200 | find "${{ github.workspace }}" -type f -name "*.ipk" -exec ls -lh {} \; 201 | 202 | - name: Update Archives 203 | uses: actions/upload-artifact@v2 204 | with: 205 | name: ${{ env.PACKAGE_NAME }} 206 | path: '*.ipk' 207 | 208 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Build Test for Pull Request 2 | on: [pull_request] 3 | 4 | jobs: 5 | test-and-build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | with: 11 | persist-credentials: false 12 | fetch-depth: 2 13 | 14 | - name: Setup node 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | 19 | - name: Test 20 | run: | 21 | yarn install 22 | yarn lint 23 | 24 | - name: Build 25 | run: | 26 | yarn install 27 | yarn package 28 | -------------------------------------------------------------------------------- /.github/workflows/release-package.yml: -------------------------------------------------------------------------------- 1 | name: Package Build and Release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | env: 8 | PACKAGE_NAME: luci-app-v2ray 9 | CACHE_DIR: ~/cache 10 | 11 | jobs: 12 | build-release: 13 | runs-on: ubuntu-latest 14 | env: 15 | SDK_URL_PATH: https://downloads.openwrt.org/releases/19.07.3/targets/x86/64 16 | SDK_NAME: -sdk-19.07.3-x86-64_ 17 | CONFIG_CCACHE: y 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | with: 22 | persist-credentials: false 23 | fetch-depth: 2 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '12.x' 29 | 30 | - name: Setup Node.js Cache 31 | uses: actions/cache@v1 32 | with: 33 | path: node_modules 34 | key: node-${{ env.PACKAGE_NAME }}-${{ hashFiles('yarn.lock') }} 35 | restore-keys: | 36 | node-${{ env.PACKAGE_NAME }}- 37 | 38 | - name: Build Package Source 39 | run: | 40 | yarn install 41 | yarn package 42 | 43 | - name: Prepare Cache Key 44 | id: cache_key 45 | run: echo "::set-output name=timestamp::$(date +"%s")" 46 | 47 | - name: Setup OpenWrt Cache 48 | uses: actions/cache@v1 49 | with: 50 | path: ${{ env.CACHE_DIR }} 51 | key: openwrt-${{ env.PACKAGE_NAME }}-${{ steps.cache_key.outputs.timestamp }} 52 | restore-keys: | 53 | openwrt-${{ env.PACKAGE_NAME }}- 54 | 55 | - name: Install Dependencies 56 | run: | 57 | DEBIAN_FRONTEND=noninteractive \ 58 | sudo apt-get install -y ccache gettext libncurses5-dev xsltproc 59 | 60 | - name: Create Directories 61 | run: | 62 | CACHE_DIR_SDK="$(eval echo "$CACHE_DIR/sdk")" 63 | CACHE_DIR_DL="$(eval echo "$CACHE_DIR/dl")" 64 | CACHE_DIR_FEEDS="$(eval echo "$CACHE_DIR/feeds")" 65 | 66 | CCACHE_DIR_HOST="$(eval echo "$CACHE_DIR/ccache/host")" 67 | CCACHE_DIR_TARGET="$(eval echo "$CACHE_DIR/ccache/target")" 68 | CCACHE_DIR_TOOLCHAIN="$(eval echo "$CACHE_DIR/ccache/toolchain")" 69 | 70 | test -d "$CACHE_DIR_SDK" || mkdir -p "$CACHE_DIR_SDK" 71 | test -d "$CACHE_DIR_DL" || mkdir -p "$CACHE_DIR_DL" 72 | test -d "$CACHE_DIR_FEEDS" || mkdir -p "$CACHE_DIR_FEEDS" 73 | 74 | test -d "$CCACHE_DIR_HOST" || mkdir -p "$CCACHE_DIR_HOST" 75 | test -d "$CCACHE_DIR_TARGET" || mkdir -p "$CCACHE_DIR_TARGET" 76 | test -d "$CCACHE_DIR_TOOLCHAIN" || mkdir -p "$CCACHE_DIR_TOOLCHAIN" 77 | 78 | echo "::set-env name=CACHE_DIR_SDK::$CACHE_DIR_SDK" 79 | echo "::set-env name=CACHE_DIR_DL::$CACHE_DIR_DL" 80 | echo "::set-env name=CACHE_DIR_FEEDS::$CACHE_DIR_FEEDS" 81 | 82 | echo "::set-env name=CCACHE_DIR_HOST::$CCACHE_DIR_HOST" 83 | echo "::set-env name=CCACHE_DIR_TARGET::$CCACHE_DIR_TARGET" 84 | echo "::set-env name=CCACHE_DIR_TOOLCHAIN::$CCACHE_DIR_TOOLCHAIN" 85 | 86 | echo "::set-env name=SDK_HOME::$(mktemp -d)" 87 | 88 | - name: Download and Unzip SDK 89 | run: | 90 | cd "$CACHE_DIR_SDK" 91 | 92 | if ! ( wget -q -O - "$SDK_URL_PATH/sha256sums" | \ 93 | grep -- "$SDK_NAME" > sha256sums.small 2>/dev/null ) ; then 94 | echo "::error::Can not find ${SDK_NAME} file in sha256sums." 95 | exit 1 96 | fi 97 | 98 | SDK_FILE="$(cat sha256sums.small | cut -d' ' -f2 | sed 's/*//g')" 99 | 100 | if ! sha256sum -c ./sha256sums.small >/dev/null 2>&1 ; then 101 | wget -q -O "$SDK_FILE" "$SDK_URL_PATH/$SDK_FILE" 102 | if ! sha256sum -c ./sha256sums.small >/dev/null 2>&1 ; then 103 | echo "::error::SDK can not be verified!" 104 | exit 1 105 | fi 106 | fi 107 | 108 | cd - 109 | 110 | file "$CACHE_DIR_SDK/$SDK_FILE" 111 | 112 | tar -Jxf "$CACHE_DIR_SDK/$SDK_FILE" -C "$SDK_HOME" --strip=1 113 | 114 | cd "$SDK_HOME" 115 | 116 | cp feeds.conf.default feeds.conf 117 | 118 | sed -i 's#git.openwrt.org/openwrt/openwrt#github.com/openwrt/openwrt#' feeds.conf 119 | sed -i 's#git.openwrt.org/feed/packages#github.com/openwrt/packages#' feeds.conf 120 | sed -i 's#git.openwrt.org/project/luci#github.com/openwrt/luci#' feeds.conf 121 | sed -i 's#git.openwrt.org/feed/telephony#github.com/openwrt/telephony#' feeds.conf 122 | 123 | cd - 124 | 125 | - name: Restore OpenWrt Cache 126 | run: | 127 | cd "$SDK_HOME" 128 | 129 | test -d "dl" && rm -rf "dl" || true 130 | test -d "feeds" && rm -rf "feeds" || true 131 | 132 | ln -s "$CACHE_DIR_DL" "dl" 133 | ln -s "$CACHE_DIR_FEEDS" "feeds" 134 | 135 | staging_dir_host="staging_dir/host" 136 | staging_dir_target="staging_dir/target-x86_64_musl" 137 | staging_dir_toolchain="$(eval echo "staging_dir/toolchain-*")" 138 | 139 | test -d "$staging_dir_host" || mkdir -p "$staging_dir_host" 140 | test -d "$staging_dir_target" || mkdir -p "$staging_dir_target" 141 | test -d "$staging_dir_toolchain" || mkdir -p "$staging_dir_toolchain" 142 | 143 | ln -s "$CCACHE_DIR_HOST" "$staging_dir_host/ccache" 144 | ln -s "$CCACHE_DIR_TARGET" "$staging_dir_target/ccache" 145 | ln -s "$CCACHE_DIR_TOOLCHAIN" "$staging_dir_toolchain/ccache" 146 | 147 | CCACHE_DIR="$staging_dir_host/ccache" staging_dir/host/bin/ccache -s 148 | CCACHE_DIR="$staging_dir_target/ccache" staging_dir/host/bin/ccache -s 149 | CCACHE_DIR="$staging_dir_toolchain/ccache" staging_dir/host/bin/ccache -s 150 | 151 | cd - 152 | 153 | - name: Update and Install Packages 154 | run: | 155 | cd "$SDK_HOME" 156 | 157 | cp -rf "${{ github.workspace }}/package" "package/$PACKAGE_NAME" 158 | 159 | ./scripts/feeds update -a 160 | ./scripts/feeds install -a 161 | 162 | cd - 163 | 164 | - name: Build Package 165 | run: | 166 | cd "$SDK_HOME" 167 | 168 | make defconfig 169 | make package/${PACKAGE_NAME}/compile V=s 170 | 171 | find "$SDK_HOME/bin/" -type f -name "*.ipk" -exec ls -lh {} \; 172 | 173 | cd - 174 | 175 | - name: Copy Build Results 176 | run: | 177 | # Copy bin files 178 | find "$SDK_HOME/bin/" -type f -name "${PACKAGE_NAME}*.ipk" -exec cp {} "${{ github.workspace }}" \; 179 | 180 | # Copy translations 181 | find "$SDK_HOME/bin/" -type f -name "luci-i18n*.ipk" -exec cp {} "${{ github.workspace }}" \; 182 | 183 | find "${{ github.workspace }}" -type f -name "*.ipk" -exec ls -lh {} \; 184 | 185 | - name: Release and Upload Assets 186 | uses: softprops/action-gh-release@v1 187 | with: 188 | files: '*.ipk' 189 | env: 190 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 191 | 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.po~ 3 | .idea 4 | node_modules 5 | output 6 | package 7 | *.log 8 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged", 4 | "post-merge": "install-deps-postmerge" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": [ 3 | "eslint --fix" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luci-app-v2ray 2 | 3 | Luci support for V2Ray 4 | 5 | **This branch is new LuCI for OpenWrt 19.07 and later.** 6 | 7 | **For legacy version: [Branch legacy](https://github.com/kuoruan/luci-app-v2ray/tree/legacy)** 8 | 9 | [![Release Version](https://img.shields.io/github/release/kuoruan/luci-app-v2ray.svg)](https://github.com/kuoruan/luci-app-v2ray/releases/latest) [![Latest Release Download](https://img.shields.io/github/downloads/kuoruan/luci-app-v2ray/latest/total.svg)](https://github.com/kuoruan/luci-app-v2ray/releases/latest) [![Total Download](https://img.shields.io/github/downloads/kuoruan/luci-app-v2ray/total.svg)](https://github.com/kuoruan/luci-app-v2ray/releases) 10 | 11 | ## Install 12 | 13 | ### Install via OPKG (recommend) 14 | 15 | 1. Add new opkg key: 16 | 17 | ```sh 18 | wget -O kuoruan-public.key http://openwrt.kuoruan.net/packages/public.key 19 | opkg-key add kuoruan-public.key 20 | ``` 21 | 22 | 2. Add opkg repository from kuoruan: 23 | 24 | ```sh 25 | echo "src/gz kuoruan_universal http://openwrt.kuoruan.net/packages/releases/all" \ 26 | >> /etc/opkg/customfeeds.conf 27 | opkg update 28 | ``` 29 | 30 | 3. Install package: 31 | 32 | ```sh 33 | opkg install luci-app-v2ray 34 | opkg install luci-i18n-v2ray-zh-cn 35 | ``` 36 | 37 | We also support HTTPS protocol. 38 | 39 | 4. Upgrade package: 40 | 41 | ```sh 42 | opkg update 43 | opkg upgrade luci-app-v2ray 44 | opkg upgrade luci-i18n-v2ray-zh-cn 45 | ``` 46 | 47 | ### Manual install 48 | 49 | 1. Download ipk files from [release](https://github.com/kuoruan/luci-app-v2ray/releases) page 50 | 51 | 2. Upload files to your router 52 | 53 | 3. Install package with opkg: 54 | 55 | ```sh 56 | opkg install luci-app-v2ray_*.ipk 57 | ``` 58 | 59 | Dependencies: 60 | 61 | - jshn 62 | - ip (ip-tiny or ip-full) 63 | - ipset 64 | - iptables 65 | - iptables-mod-tproxy 66 | - resolveip 67 | - dnsmasq-full (dnsmasq ipset is required) 68 | 69 | For translations, please install ```luci-i18n-v2ray-*```. 70 | 71 | > You may need to remove ```dnsmasq``` before installing this package. 72 | 73 | ## Configure 74 | 75 | 1. Download V2Ray file from V2Ray release [link](https://github.com/v2ray/v2ray-core/releases) or V2Ray ipk release [link](https://github.com/kuoruan/openwrt-v2ray/releases). 76 | 77 | 2. Upload V2Ray file to your router, or install the ipk file. 78 | 79 | 3. Config V2Ray file path in LuCI page. 80 | 81 | 4. Add your inbound and outbound rules. 82 | 83 | 5. Enable the service via LuCI. 84 | 85 | ## Build 86 | 87 | Package files is in branch [luci2](https://github.com/kuoruan/luci-app-v2ray/tree/luci2) 88 | 89 | Download with Git: 90 | 91 | ```sh 92 | git clone -b luci2 https://github.com/kuoruan/luci-app-v2ray.git luci-app-v2ray 93 | ``` 94 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const child = require("child_process"); 2 | const fs = require("fs"); 3 | const gulp = require("gulp"); 4 | const terser = require("gulp-terser"); 5 | const ts = require("gulp-typescript"); 6 | const replace = require("gulp-replace"); 7 | 8 | const pkg = require("./package.json"); 9 | 10 | process.env.LUCI_VERSION = pkg.version; 11 | process.env.LUCI_RELEASE = pkg.release; 12 | 13 | const resDest = "package/htdocs/luci-static/resources"; 14 | 15 | const tsProject = ts.createProject("tsconfig.json"); 16 | 17 | function clean(...paths) { 18 | return child.spawn("rm", ["-rf", ...paths]); 19 | } 20 | 21 | function replaceEnvs() { 22 | return replace(/process\.env\.(\w+)/g, function (_, pl) { 23 | return JSON.stringify(process.env[pl]); 24 | }); 25 | } 26 | 27 | gulp.task("clean-package", function () { 28 | return clean("package"); 29 | }); 30 | 31 | gulp.task("clean-output", function () { 32 | return clean("output"); 33 | }); 34 | 35 | gulp.task("compile", function () { 36 | return tsProject 37 | .src() 38 | .pipe(tsProject()) 39 | .js.pipe( 40 | terser({ 41 | parse: { 42 | // allow 'return' outside of function 43 | bare_returns: true, 44 | }, 45 | compress: { 46 | directives: false, 47 | }, 48 | mangle: {}, 49 | output: { 50 | comments: "some", 51 | beautify: false, 52 | }, 53 | }) 54 | ) 55 | .pipe(replaceEnvs()) 56 | .pipe(gulp.dest("output")); 57 | }); 58 | 59 | gulp.task("compile:test", function () { 60 | return tsProject 61 | .src() 62 | .pipe(tsProject()) 63 | .js.pipe( 64 | terser({ 65 | parse: { 66 | bare_returns: true, 67 | }, 68 | compress: false, 69 | mangle: false, 70 | output: { 71 | comments: "some", 72 | beautify: true, 73 | indent_level: 2, 74 | }, 75 | }) 76 | ) 77 | .pipe(replaceEnvs()) 78 | .pipe(gulp.dest("output")); 79 | }); 80 | 81 | gulp.task("copy-output", function () { 82 | return gulp.src("output/**").pipe(gulp.dest(resDest)); 83 | }); 84 | 85 | gulp.task("build", gulp.series("clean-output", "compile", "copy-output")); 86 | 87 | gulp.task( 88 | "build:test", 89 | gulp.series("clean-output", "compile:test", "copy-output") 90 | ); 91 | 92 | gulp.task("copy-makefile", function () { 93 | return gulp 94 | .src("public/Makefile") 95 | .pipe( 96 | replace(/#\{(\w+)\}/g, function (_, pl) { 97 | return pkg[pl]; 98 | }) 99 | ) 100 | .pipe(gulp.dest("package")); 101 | }); 102 | 103 | gulp.task("copy-public", function () { 104 | return gulp 105 | .src(["public/luasrc/**/*", "public/po/**/*.po", "public/root/**/*"], { 106 | cwd: ".", 107 | base: "public", 108 | dot: false, 109 | }) 110 | .pipe(gulp.dest("package")); 111 | }); 112 | 113 | gulp.task( 114 | "test", 115 | gulp.series( 116 | "clean-package", 117 | gulp.parallel("build:test", "copy-public", "copy-makefile") 118 | ) 119 | ); 120 | 121 | gulp.task("i18n:scan", function () { 122 | const scan = child.spawn("./scripts/i18n-scan.pl", ["package"]); 123 | scan.stdout.pipe(fs.createWriteStream("public/po/templates/v2ray.pot")); 124 | return scan; 125 | }); 126 | 127 | gulp.task("i18n:update", function () { 128 | return child.spawn("./scripts/i18n-update.pl", ["public/po"]); 129 | }); 130 | 131 | gulp.task("i18n:sync", gulp.series("test", "i18n:scan", "i18n:update")); 132 | 133 | gulp.task( 134 | "default", 135 | gulp.series( 136 | "clean-package", 137 | gulp.parallel("build", "copy-public", "copy-makefile") 138 | ) 139 | ); 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "luci-app-v2ray", 3 | "version": "2.0.0", 4 | "release": "1", 5 | "description": "LuCI support for v2ray", 6 | "repository": "git@github.com:kuoruan/luci-app-v2ray.git", 7 | "author": "Xingwang Liao ", 8 | "license": "MIT", 9 | "private": true, 10 | "scripts": { 11 | "package": "gulp", 12 | "package:test": "gulp test", 13 | "lint": "eslint '**/*.{js,ts}'", 14 | "i18n:sync": "gulp i18n:sync" 15 | }, 16 | "devDependencies": { 17 | "@kuoruan/luci-types": "^1.1.2", 18 | "@typescript-eslint/eslint-plugin": "^4.0.0", 19 | "@typescript-eslint/parser": "^3.10.1", 20 | "eslint": "^7.31.0", 21 | "eslint-config-prettier": "^7.2.0", 22 | "eslint-plugin-prettier": "^3.4.0", 23 | "eslint-plugin-tsdoc": "^0.2.14", 24 | "gulp": "^4.0.2", 25 | "gulp-cli": "^2.3.0", 26 | "gulp-replace": "^1.1.3", 27 | "gulp-terser": "^2.0.1", 28 | "gulp-typescript": "^6.0.0-alpha.1", 29 | "husky": "^7.0.1", 30 | "install-deps-postmerge": "^2.0.1", 31 | "lint-staged": "^11.1.1", 32 | "prettier": "^2.2.1", 33 | "typescript": "^4.3.5" 34 | }, 35 | "dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /public/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Xingwang Liao 3 | # Licensed to the public under the MIT License. 4 | # 5 | 6 | include $(TOPDIR)/rules.mk 7 | 8 | PKG_NAME:=#{name} 9 | PKG_VERSION:=#{version} 10 | PKG_RELEASE:=#{release} 11 | 12 | PKG_LICENSE:=#{license} 13 | PKG_MAINTAINER:=#{author} 14 | 15 | LUCI_TITLE:=#{description} 16 | LUCI_DEPENDS:=+jshn +ip +ipset +iptables +iptables-mod-tproxy +resolveip \ 17 | +dnsmasq-full 18 | LUCI_PKGARCH:=all 19 | 20 | define Package/$(PKG_NAME)/conffiles 21 | /etc/config/v2ray 22 | /etc/v2ray/transport.json 23 | /etc/v2ray/directlist.txt 24 | /etc/v2ray/proxylist.txt 25 | endef 26 | 27 | include $(TOPDIR)/feeds/luci/luci.mk 28 | 29 | define Package/$(PKG_NAME)/postinst 30 | #!/bin/sh 31 | 32 | if [ -z "$${IPKG_INSTROOT}" ] ; then 33 | ( . /etc/uci-defaults/40_luci-v2ray ) && rm -f /etc/uci-defaults/40_luci-v2ray 34 | 35 | rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/ 36 | 37 | killall -HUP rpcd 2>/dev/null 38 | fi 39 | 40 | chmod 755 "$${IPKG_INSTROOT}/etc/init.d/v2ray" >/dev/null 2>&1 41 | ln -sf "../init.d/v2ray" \ 42 | "$${IPKG_INSTROOT}/etc/rc.d/S99v2ray" >/dev/null 2>&1 43 | 44 | exit 0 45 | endef 46 | 47 | define Package/$(PKG_NAME)/postrm 48 | #!/bin/sh 49 | 50 | if [ -s "$${IPKG_INSTROOT}/etc/rc.d/S99v2ray" ] ; then 51 | rm -f "$${IPKG_INSTROOT}/etc/rc.d/S99v2ray" 52 | fi 53 | 54 | if [ -z "$${IPKG_INSTROOT}" ] ; then 55 | rm -rf /tmp/luci-indexcache /tmp/luci-modulecache/ 56 | fi 57 | 58 | exit 0 59 | endef 60 | 61 | # call BuildPackage - OpenWrt buildroot signature 62 | -------------------------------------------------------------------------------- /public/luasrc/controller/v2ray.lua: -------------------------------------------------------------------------------- 1 | module("luci.controller.v2ray", package.seeall) 2 | 3 | local fs = require "nixio.fs" 4 | local http = require "luci.http" 5 | local i18n = require "luci.i18n" 6 | local sys = require "luci.sys" 7 | 8 | function index() 9 | if not nixio.fs.access("/etc/config/v2ray") then 10 | return 11 | end 12 | 13 | entry({"admin", "services", "v2ray"}, firstchild(), _("V2Ray")).dependent = false 14 | 15 | entry({"admin", "services", "v2ray", "main"}, view("v2ray/main"), _("Global Settings"), 10) 16 | 17 | entry({"admin", "services", "v2ray", "inbound"}, view("v2ray/inbound"), _("Inbound"), 20).leaf = true 18 | 19 | entry({"admin", "services", "v2ray", "outbound"}, view("v2ray/outbound"), _("Outbound"), 30).leaf = true 20 | 21 | entry({"admin", "services", "v2ray", "dns"}, view("v2ray/dns"), _("DNS"), 40) 22 | 23 | entry({"admin", "services", "v2ray", "routing"}, view("v2ray/routing"), _("Routing"), 50) 24 | 25 | entry({"admin", "services", "v2ray", "policy"}, view("v2ray/policy"), _("Policy"), 60) 26 | 27 | entry({"admin", "services", "v2ray", "reverse"}, view("v2ray/reverse"), _("Reverse"), 70) 28 | 29 | entry({"admin", "services", "v2ray", "transparent-proxy"}, view("v2ray/transparent-proxy"), _("Transparent Proxy"), 80) 30 | 31 | entry({"admin", "services", "v2ray", "about"}, view("v2ray/about"), _("About"), 90) 32 | 33 | entry({"admin", "services", "v2ray", "request"}, call("action_request")) 34 | end 35 | 36 | function action_request() 37 | local url = http.formvalue("url") 38 | 39 | if not url or url == "" then 40 | http.prepare_content("application/json") 41 | http.write_json({ 42 | code = 1, 43 | message = i18n.translate("Invalid url") 44 | }) 45 | return 46 | end 47 | 48 | if string.sub(url, 1, 5) == "https" and 49 | not fs.stat("/lib/libustream-ssl.so") then 50 | http.prepare_content("application/json") 51 | http.write_json({ 52 | code = 1, 53 | message = i18n.translatef("wget: SSL support not available, please install %s or %s.", "libustream-openssl", "libustream-mbedtls") 54 | }) 55 | return 56 | end 57 | 58 | local content = sys.httpget(url, false) 59 | 60 | if not content or content == "" then 61 | http.prepare_content("application/json") 62 | http.write_json({ 63 | code = 1, 64 | message = i18n.translate("Failed to request.") 65 | }) 66 | else 67 | http.prepare_content("application/json") 68 | http.write_json({ 69 | code = 0, 70 | content = content 71 | }) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /public/po/templates/v2ray.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "Content-Type: text/plain; charset=UTF-8" 3 | 4 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:88 5 | msgid "%s is required." 6 | msgstr "" 7 | 8 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:95 9 | msgid "transport field in top level configuration, JSON string" 10 | msgstr "" 11 | 12 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:271 13 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:286 14 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:335 15 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:400 16 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:412 17 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:461 18 | msgid "A list of HTTP headers, format: header=value. eg: %s" 19 | msgstr "" 20 | 21 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:19 22 | msgid "A list of bridges, format: tag|domain. eg: %s" 23 | msgstr "" 24 | 25 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:20 26 | msgid "A list of portals, format: tag|domain. eg: %s" 27 | msgstr "" 28 | 29 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:31 30 | msgid "A list of static addresses, format: domain|address. eg: %s" 31 | msgstr "" 32 | 33 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:35 34 | msgid "A platform for building proxies to bypass network restrictions." 35 | msgstr "" 36 | 37 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:168 38 | msgid "APNIC delegated mirror" 39 | msgstr "" 40 | 41 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 42 | #: package/luasrc/controller/v2ray.lua:31 43 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:82 44 | msgid "About" 45 | msgstr "" 46 | 47 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:60 48 | msgid "Access log file" 49 | msgstr "" 50 | 51 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:86 52 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:155 53 | msgid "Account password" 54 | msgstr "" 55 | 56 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:83 57 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:152 58 | msgid "Account user" 59 | msgstr "" 60 | 61 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:32 62 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:182 63 | msgid "Add" 64 | msgstr "" 65 | 66 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:37 67 | msgid "Add DNS servers here" 68 | msgstr "" 69 | 70 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:37 71 | msgid "Add policy levels here" 72 | msgstr "" 73 | 74 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:77 75 | msgid "Add routing balancers here" 76 | msgstr "" 77 | 78 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:42 79 | msgid "Add routing rules here" 80 | msgstr "" 81 | 82 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:43 83 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:57 84 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:218 85 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:258 86 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:308 87 | msgid "Address" 88 | msgstr "" 89 | 90 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:57 91 | msgid "Address of the destination server." 92 | msgstr "" 93 | 94 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:41 95 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:39 96 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:189 97 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:47 98 | msgid "Alias" 99 | msgstr "" 100 | 101 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:79 102 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:98 103 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:109 104 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:171 105 | msgid "All connections share this level" 106 | msgstr "" 107 | 108 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:387 109 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:392 110 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:395 111 | msgid "Allocate" 112 | msgstr "" 113 | 114 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:224 115 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:355 116 | msgid "Allow insecure" 117 | msgstr "" 118 | 119 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:227 120 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:358 121 | msgid "Allow insecure ciphers" 122 | msgstr "" 123 | 124 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:90 125 | msgid "Allow transparent" 126 | msgstr "" 127 | 128 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:319 129 | msgid "Alter ID" 130 | msgstr "" 131 | 132 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:71 133 | msgid "Attrs" 134 | msgstr "" 135 | 136 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:145 137 | msgid "Auth" 138 | msgstr "" 139 | 140 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 141 | msgid "Author: %s" 142 | msgstr "" 143 | 144 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:327 145 | msgid "Auto" 146 | msgstr "" 147 | 148 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:74 149 | msgid "Balancer tag" 150 | msgstr "" 151 | 152 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:37 153 | msgid "Balancers" 154 | msgstr "" 155 | 156 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:19 157 | msgid "Bridges" 158 | msgstr "" 159 | 160 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:63 161 | msgid "Buffer size" 162 | msgstr "" 163 | 164 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:173 165 | msgid "CHNRoute" 166 | msgstr "" 167 | 168 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:109 169 | msgid "CHNRoute list updated." 170 | msgstr "" 171 | 172 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:165 173 | msgid "CN Direct" 174 | msgstr "" 175 | 176 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:166 177 | msgid "CN Proxy" 178 | msgstr "" 179 | 180 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:240 181 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:371 182 | msgid "Certificate file" 183 | msgstr "" 184 | 185 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:233 186 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:364 187 | msgid "Certificate usage" 188 | msgstr "" 189 | 190 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:175 191 | msgid "Client ID" 192 | msgstr "" 193 | 194 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:29 195 | msgid "Client IP" 196 | msgstr "" 197 | 198 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:185 199 | msgid "Client User level" 200 | msgstr "" 201 | 202 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:178 203 | msgid "Client alter ID" 204 | msgstr "" 205 | 206 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:182 207 | msgid "Client email" 208 | msgstr "" 209 | 210 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:197 211 | msgid "Collecting data..." 212 | msgstr "" 213 | 214 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:395 215 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:508 216 | msgid "Concurrency" 217 | msgstr "" 218 | 219 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:57 220 | msgid "Config file" 221 | msgstr "" 222 | 223 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:309 224 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:435 225 | msgid "Congestion enabled" 226 | msgstr "" 227 | 228 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:48 229 | msgid "Connection idle" 230 | msgstr "" 231 | 232 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 233 | msgid "Current Config File: %s" 234 | msgstr "" 235 | 236 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:21 237 | #: package/luasrc/controller/v2ray.lua:21 238 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:42 239 | msgid "DNS" 240 | msgstr "" 241 | 242 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:32 243 | msgid "DNS Servers" 244 | msgstr "" 245 | 246 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:37 247 | msgid "DNS server" 248 | msgstr "" 249 | 250 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:199 251 | msgid "" 252 | "DNS used for domains in direct list, format: ip#port. eg: %s" 253 | msgstr "" 254 | 255 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:198 256 | msgid "" 257 | "DNS used for domains in proxy list, format: ip#port. eg: %s" 258 | msgstr "" 259 | 260 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:66 261 | msgid "Debug" 262 | msgstr "" 263 | 264 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:164 265 | msgid "Default" 266 | msgstr "" 267 | 268 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:189 269 | msgid "Default alter ID" 270 | msgstr "" 271 | 272 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:193 273 | msgid "Default user level" 274 | msgstr "" 275 | 276 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:383 277 | msgid "Dest override" 278 | msgstr "" 279 | 280 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:21 281 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:23 282 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:13 283 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:21 284 | msgid "Details: %s" 285 | msgstr "" 286 | 287 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:197 288 | msgid "Detour to" 289 | msgstr "" 290 | 291 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:199 292 | msgid "Direct list DNS" 293 | msgstr "" 294 | 295 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:51 296 | msgid "" 297 | "Directory where geoip.dat and geosite.dat files are, default: same directory " 298 | "as V2Ray file." 299 | msgstr "" 300 | 301 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:200 302 | msgid "Disable insecure encryption" 303 | msgstr "" 304 | 305 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:230 306 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:361 307 | msgid "Disable system root" 308 | msgstr "" 309 | 310 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:156 311 | msgid "Dismiss" 312 | msgstr "" 313 | 314 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:51 315 | msgid "Domain" 316 | msgstr "" 317 | 318 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:27 319 | msgid "Domain resolution strategy" 320 | msgstr "" 321 | 322 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:225 323 | msgid "Domain strategy" 324 | msgstr "" 325 | 326 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:47 327 | msgid "Domains" 328 | msgstr "" 329 | 330 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 331 | msgid "Donate: %s" 332 | msgstr "" 333 | 334 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:304 335 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:430 336 | msgid "Downlink capacity" 337 | msgstr "" 338 | 339 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:55 340 | msgid "Downlink only" 341 | msgstr "" 342 | 343 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:113 344 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:255 345 | msgid "Email" 346 | msgstr "" 347 | 348 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:143 349 | msgid "Empty field." 350 | msgstr "" 351 | 352 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:150 353 | msgid "Enable proxy on selected interfaces." 354 | msgstr "" 355 | 356 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:143 357 | msgid "Enable transparent proxy on Dokodemo-door port." 358 | msgstr "" 359 | 360 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:26 361 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:381 362 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:41 363 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:91 364 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:93 365 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:506 366 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:28 367 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:17 368 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:26 369 | msgid "Enabled" 370 | msgstr "" 371 | 372 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:69 373 | msgid "Error" 374 | msgstr "" 375 | 376 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:72 377 | msgid "Error log file" 378 | msgstr "" 379 | 380 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:49 381 | msgid "Expect IPs" 382 | msgstr "" 383 | 384 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:193 385 | msgid "Extra direct list" 386 | msgstr "" 387 | 388 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:188 389 | msgid "Extra proxy list" 390 | msgstr "" 391 | 392 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:76 393 | msgid "Failed to decode GFWList." 394 | msgstr "" 395 | 396 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:117 397 | msgid "Failed to fetch CHNRoute list." 398 | msgstr "" 399 | 400 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:79 401 | msgid "Failed to fetch GFWList." 402 | msgstr "" 403 | 404 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:29 405 | msgid "Failed to open file." 406 | msgstr "" 407 | 408 | #: package/luasrc/controller/v2ray.lua:64 409 | msgid "Failed to request." 410 | msgstr "" 411 | 412 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:372 413 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:501 414 | msgid "False" 415 | msgstr "" 416 | 417 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:76 418 | msgid "Follow redirect" 419 | msgstr "" 420 | 421 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:35 422 | msgid "For more information, please visit: %s" 423 | msgstr "" 424 | 425 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:183 426 | msgid "GFWList" 427 | msgstr "" 428 | 429 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:167 430 | msgid "GFWList Proxy" 431 | msgstr "" 432 | 433 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:178 434 | msgid "GFWList mirror" 435 | msgstr "" 436 | 437 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:68 438 | msgid "GFWList updated." 439 | msgstr "" 440 | 441 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:35 442 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:185 443 | msgid "General Settings" 444 | msgstr "" 445 | 446 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:29 447 | msgid "Get my public IP address" 448 | msgstr "" 449 | 450 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:198 451 | msgid "Getting..." 452 | msgstr "" 453 | 454 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:35 455 | #: package/luasrc/controller/v2ray.lua:15 456 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:18 457 | msgid "Global Settings" 458 | msgstr "" 459 | 460 | #: package/root/usr/share/rpcd/acl.d/luci-app-v2ray.json:3 461 | msgid "Grant access to LuCI app V2ray" 462 | msgstr "" 463 | 464 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:256 465 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:386 466 | msgid "HTTP request method" 467 | msgstr "" 468 | 469 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:252 470 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:383 471 | msgid "HTTP request version" 472 | msgstr "" 473 | 474 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:282 475 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:409 476 | msgid "HTTP response reason" 477 | msgstr "" 478 | 479 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:278 480 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:406 481 | msgid "HTTP response status" 482 | msgstr "" 483 | 484 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:274 485 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:403 486 | msgid "HTTP response version" 487 | msgstr "" 488 | 489 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:45 490 | msgid "Handshake" 491 | msgstr "" 492 | 493 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:246 494 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:322 495 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:359 496 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:377 497 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:448 498 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:485 499 | msgid "Header type" 500 | msgstr "" 501 | 502 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:335 503 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:461 504 | msgid "Headers" 505 | msgstr "" 506 | 507 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:338 508 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:464 509 | msgid "Host" 510 | msgstr "" 511 | 512 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:31 513 | msgid "Hosts" 514 | msgstr "" 515 | 516 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:162 517 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:53 518 | msgid "IP" 519 | msgstr "" 520 | 521 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:163 522 | msgid "" 523 | "If enabled, iptables rules will be added to pre-filter traffic and then sent " 524 | "to V2Ray." 525 | msgstr "" 526 | 527 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:65 528 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:76 529 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:374 530 | msgid "" 531 | "If transparent proxy enabled on current inbound, this option will be ignored." 532 | msgstr "" 533 | 534 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:495 535 | msgid "" 536 | "If transparent proxy is enabled, this option is ignored and will be set to " 537 | "255." 538 | msgstr "" 539 | 540 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:519 541 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:521 542 | msgid "Import" 543 | msgstr "" 544 | 545 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:116 546 | msgid "Imported %d links." 547 | msgstr "" 548 | 549 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:25 550 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:32 551 | #: package/luasrc/controller/v2ray.lua:17 552 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:26 553 | msgid "Inbound" 554 | msgstr "" 555 | 556 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:65 557 | msgid "Inbound tag" 558 | msgstr "" 559 | 560 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:79 561 | msgid "Inbounds enabled" 562 | msgstr "" 563 | 564 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:67 565 | msgid "Info" 566 | msgstr "" 567 | 568 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:98 569 | msgid "Invalid JSON content." 570 | msgstr "" 571 | 572 | #: package/luasrc/controller/v2ray.lua:43 573 | msgid "Invalid url" 574 | msgstr "" 575 | 576 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:355 577 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:481 578 | msgid "Key" 579 | msgstr "" 580 | 581 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:243 582 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:374 583 | msgid "Key file" 584 | msgstr "" 585 | 586 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:150 587 | msgid "LAN interfaces" 588 | msgstr "" 589 | 590 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 591 | msgid "Latest: %s" 592 | msgstr "" 593 | 594 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:42 595 | msgid "Level" 596 | msgstr "" 597 | 598 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:30 599 | msgid "Levels" 600 | msgstr "" 601 | 602 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:68 603 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:109 604 | msgid "List Update" 605 | msgstr "" 606 | 607 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:41 608 | msgid "Listen" 609 | msgstr "" 610 | 611 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:113 612 | msgid "Listtype is required" 613 | msgstr "" 614 | 615 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:200 616 | msgid "Local devices direct outbound list" 617 | msgstr "" 618 | 619 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:64 620 | msgid "Log level" 621 | msgstr "" 622 | 623 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 624 | msgid "LuCI support for V2Ray." 625 | msgstr "" 626 | 627 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:495 628 | msgid "Mark" 629 | msgstr "" 630 | 631 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:289 632 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:415 633 | msgid "Maximum transmission unit (MTU)" 634 | msgstr "" 635 | 636 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:54 637 | msgid "Memory percentage" 638 | msgstr "" 639 | 640 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:116 641 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:266 642 | msgid "Method" 643 | msgstr "" 644 | 645 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:506 646 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:508 647 | msgid "Mux" 648 | msgstr "" 649 | 650 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:65 651 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:139 652 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:203 653 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:212 654 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:335 655 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:58 656 | msgid "Network" 657 | msgstr "" 658 | 659 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:149 660 | msgid "No Auth" 661 | msgstr "" 662 | 663 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:129 664 | msgid "No links imported." 665 | msgstr "" 666 | 667 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:214 668 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:250 669 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:326 670 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:352 671 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:363 672 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:59 673 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:70 674 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:210 675 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:330 676 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:346 677 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:381 678 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:452 679 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:478 680 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:489 681 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:144 682 | msgid "None" 683 | msgstr "" 684 | 685 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:180 686 | msgid "Not Running" 687 | msgstr "" 688 | 689 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:126 690 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:134 691 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:73 692 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:114 693 | msgid "OK" 694 | msgstr "" 695 | 696 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:286 697 | msgid "OTA" 698 | msgstr "" 699 | 700 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:379 701 | msgid "Off" 702 | msgstr "" 703 | 704 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:136 705 | msgid "One Time Auth (OTA)" 706 | msgstr "" 707 | 708 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:188 709 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:193 710 | msgid "One address per line. Allow types: DOMAIN, IP, CIDR. eg: %s, %s, %s" 711 | msgstr "" 712 | 713 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:200 714 | msgid "One address per line. Allow types: IP, CIDR. eg: %s, %s" 715 | msgstr "" 716 | 717 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:158 718 | msgid "Only privileged ports" 719 | msgstr "" 720 | 721 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:158 722 | msgid "Only redirect traffic on ports below 1024." 723 | msgstr "" 724 | 725 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:197 726 | msgid "" 727 | "Optional feature to suggest client to take a detour. If specified, this " 728 | "inbound will instruct the outbound to use another inbound." 729 | msgstr "" 730 | 731 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:37 732 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:187 733 | msgid "Other Settings" 734 | msgstr "" 735 | 736 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:175 737 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:182 738 | #: package/luasrc/controller/v2ray.lua:19 739 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:34 740 | msgid "Outbound" 741 | msgstr "" 742 | 743 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:116 744 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:129 745 | msgid "Outbound Import" 746 | msgstr "" 747 | 748 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:73 749 | msgid "Outbound tag" 750 | msgstr "" 751 | 752 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:85 753 | msgid "Outbounds enabled" 754 | msgstr "" 755 | 756 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:128 757 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:150 758 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:251 759 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:278 760 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:300 761 | msgid "Password" 762 | msgstr "" 763 | 764 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:332 765 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:341 766 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:345 767 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:458 768 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:467 769 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:471 770 | msgid "Path" 771 | msgstr "" 772 | 773 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:23 774 | #: package/luasrc/controller/v2ray.lua:25 775 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:58 776 | msgid "Policy" 777 | msgstr "" 778 | 779 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:37 780 | msgid "Policy Level" 781 | msgstr "" 782 | 783 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:44 784 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:47 785 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:61 786 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:221 787 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:262 788 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:312 789 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:55 790 | msgid "Port" 791 | msgstr "" 792 | 793 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:61 794 | msgid "Port of the destination server." 795 | msgstr "" 796 | 797 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:20 798 | msgid "Portals" 799 | msgstr "" 800 | 801 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:50 802 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:197 803 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:66 804 | msgid "Protocol" 805 | msgstr "" 806 | 807 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:198 808 | msgid "Proxy list DNS" 809 | msgstr "" 810 | 811 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:163 812 | msgid "Proxy mode" 813 | msgstr "" 814 | 815 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:504 816 | msgid "Proxy settings" 817 | msgstr "" 818 | 819 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:312 820 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:438 821 | msgid "Read buffer size" 822 | msgstr "" 823 | 824 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:233 825 | msgid "Redirect" 826 | msgstr "" 827 | 828 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:160 829 | msgid "Redirect DNS" 830 | msgstr "" 831 | 832 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:160 833 | msgid "Redirect DNS traffic to V2Ray." 834 | msgstr "" 835 | 836 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:159 837 | msgid "Redirect UDP" 838 | msgstr "" 839 | 840 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:159 841 | msgid "Redirect UDP traffic to V2Ray." 842 | msgstr "" 843 | 844 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:143 845 | msgid "Redirect port" 846 | msgstr "" 847 | 848 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:392 849 | msgid "Refresh" 850 | msgstr "" 851 | 852 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:45 853 | msgid "Reload" 854 | msgstr "" 855 | 856 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:43 857 | msgid "Reload Service" 858 | msgstr "" 859 | 860 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:23 861 | msgid "Reload service failed with code %d" 862 | msgstr "" 863 | 864 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 865 | msgid "Report Bugs: %s" 866 | msgstr "" 867 | 868 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:271 869 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:400 870 | msgid "Request headers" 871 | msgstr "" 872 | 873 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:268 874 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:397 875 | msgid "Request path" 876 | msgstr "" 877 | 878 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:286 879 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:412 880 | msgid "Response headers" 881 | msgstr "" 882 | 883 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:206 884 | msgid "Response type" 885 | msgstr "" 886 | 887 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:13 888 | #: package/luasrc/controller/v2ray.lua:27 889 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:66 890 | msgid "Reverse" 891 | msgstr "" 892 | 893 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:21 894 | #: package/luasrc/controller/v2ray.lua:23 895 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:50 896 | msgid "Routing" 897 | msgstr "" 898 | 899 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:77 900 | msgid "Routing Balancer" 901 | msgstr "" 902 | 903 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:42 904 | msgid "Routing Rule" 905 | msgstr "" 906 | 907 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:32 908 | msgid "Rules" 909 | msgstr "" 910 | 911 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:183 912 | msgid "Running" 913 | msgstr "" 914 | 915 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:166 916 | msgid "Save" 917 | msgstr "" 918 | 919 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:211 920 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:348 921 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:323 922 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:343 923 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:474 924 | msgid "Security" 925 | msgstr "" 926 | 927 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:32 928 | msgid "Select DNS servers to use" 929 | msgstr "" 930 | 931 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:30 932 | msgid "Select policy levels" 933 | msgstr "" 934 | 935 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:37 936 | msgid "Select routing balancers to use" 937 | msgstr "" 938 | 939 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:32 940 | msgid "Select routing rules to use" 941 | msgstr "" 942 | 943 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:82 944 | msgid "Selector" 945 | msgstr "" 946 | 947 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:191 948 | msgid "Send through" 949 | msgstr "" 950 | 951 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:240 952 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:289 953 | msgid "Server address" 954 | msgstr "" 955 | 956 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:216 957 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:348 958 | msgid "Server name" 959 | msgstr "" 960 | 961 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:244 962 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:293 963 | msgid "Server port" 964 | msgstr "" 965 | 966 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:47 967 | msgid "Set the V2Ray executable file path." 968 | msgstr "" 969 | 970 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:95 971 | msgid "Settings" 972 | msgstr "" 973 | 974 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:157 975 | msgid "Setup redirect rules with TProxy." 976 | msgstr "" 977 | 978 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:381 979 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:383 980 | msgid "Sniffing" 981 | msgstr "" 982 | 983 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:369 984 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:374 985 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:495 986 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:498 987 | msgid "Sockopt" 988 | msgstr "" 989 | 990 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:61 991 | msgid "Source" 992 | msgstr "" 993 | 994 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 995 | msgid "Source: %s" 996 | msgstr "" 997 | 998 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:91 999 | msgid "Stats" 1000 | msgstr "" 1001 | 1002 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:36 1003 | msgid "Stats inbound downlink" 1004 | msgstr "" 1005 | 1006 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:35 1007 | msgid "Stats inbound uplink" 1008 | msgstr "" 1009 | 1010 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:61 1011 | msgid "Stats user downlink" 1012 | msgstr "" 1013 | 1014 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:59 1015 | msgid "Stats user uplink" 1016 | msgstr "" 1017 | 1018 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:387 1019 | msgid "Strategy" 1020 | msgstr "" 1021 | 1022 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:36 1023 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:186 1024 | msgid "Stream Settings" 1025 | msgstr "" 1026 | 1027 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:35 1028 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:36 1029 | msgid "System" 1030 | msgstr "" 1031 | 1032 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:369 1033 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:498 1034 | msgid "TCP fast open" 1035 | msgstr "" 1036 | 1037 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:374 1038 | msgid "TProxy" 1039 | msgstr "" 1040 | 1041 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:28 1042 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:380 1043 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:503 1044 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:504 1045 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:80 1046 | msgid "Tag" 1047 | msgstr "" 1048 | 1049 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:54 1050 | msgid "The maximum percentage of memory used by V2Ray." 1051 | msgstr "" 1052 | 1053 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:43 1054 | msgid "This will restart service when config file changes." 1055 | msgstr "" 1056 | 1057 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:71 1058 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:93 1059 | msgid "Time limit for inbound data(seconds)" 1060 | msgstr "" 1061 | 1062 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:128 1063 | msgid "Time: %s" 1064 | msgstr "" 1065 | 1066 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:71 1067 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:93 1068 | msgid "Timeout" 1069 | msgstr "" 1070 | 1071 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:128 1072 | msgid "Total: %s" 1073 | msgstr "" 1074 | 1075 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:294 1076 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:420 1077 | msgid "Transmission time interval (TTI)" 1078 | msgstr "" 1079 | 1080 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:140 1081 | #: package/luasrc/controller/v2ray.lua:29 1082 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:74 1083 | msgid "Transparent Proxy" 1084 | msgstr "" 1085 | 1086 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:93 1087 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:95 1088 | msgid "Transport" 1089 | msgstr "" 1090 | 1091 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:373 1092 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:502 1093 | msgid "True" 1094 | msgstr "" 1095 | 1096 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:49 1097 | msgid "Type" 1098 | msgstr "" 1099 | 1100 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:159 1101 | msgid "UDP" 1102 | msgstr "" 1103 | 1104 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:174 1105 | msgid "Unable to get V2Ray version." 1106 | msgstr "" 1107 | 1108 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:129 1109 | msgid "Unexpected error." 1110 | msgstr "" 1111 | 1112 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:48 1113 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:117 1114 | msgid "Unknown" 1115 | msgstr "" 1116 | 1117 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:175 1118 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:185 1119 | msgid "Update" 1120 | msgstr "" 1121 | 1122 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:299 1123 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:425 1124 | msgid "Uplink capacity" 1125 | msgstr "" 1126 | 1127 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:51 1128 | msgid "Uplink only" 1129 | msgstr "" 1130 | 1131 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:157 1132 | msgid "Use TProxy" 1133 | msgstr "" 1134 | 1135 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:57 1136 | msgid "Use custom config file." 1137 | msgstr "" 1138 | 1139 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:248 1140 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:297 1141 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:63 1142 | msgid "User" 1143 | msgstr "" 1144 | 1145 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:316 1146 | msgid "User ID" 1147 | msgstr "" 1148 | 1149 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:102 1150 | msgid "User email" 1151 | msgstr "" 1152 | 1153 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:79 1154 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:98 1155 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:109 1156 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:132 1157 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:171 1158 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:236 1159 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:282 1160 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:304 1161 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:331 1162 | msgid "User level" 1163 | msgstr "" 1164 | 1165 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:105 1166 | msgid "User secret" 1167 | msgstr "" 1168 | 1169 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 1170 | #: package/htdocs/luci-static/resources/view/v2ray/dns.js:21 1171 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:25 1172 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:175 1173 | #: package/htdocs/luci-static/resources/view/v2ray/policy.js:23 1174 | #: package/htdocs/luci-static/resources/view/v2ray/reverse.js:13 1175 | #: package/htdocs/luci-static/resources/view/v2ray/routing.js:21 1176 | #: package/htdocs/luci-static/resources/view/v2ray/transparent-proxy.js:140 1177 | #: package/luasrc/controller/v2ray.lua:13 1178 | msgid "V2Ray" 1179 | msgstr "" 1180 | 1181 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:51 1182 | msgid "V2Ray asset location" 1183 | msgstr "" 1184 | 1185 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:47 1186 | msgid "V2Ray file" 1187 | msgstr "" 1188 | 1189 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:35 1190 | #: package/root/usr/share/luci/menu.d/luci-app-v2ray.json:3 1191 | msgid "V2ray" 1192 | msgstr "" 1193 | 1194 | #: package/htdocs/luci-static/resources/view/v2ray/about.js:27 1195 | #: package/htdocs/luci-static/resources/view/v2ray/include/custom.js:172 1196 | msgid "Version: %s" 1197 | msgstr "" 1198 | 1199 | #: package/htdocs/luci-static/resources/view/v2ray/main.js:68 1200 | msgid "Warning" 1201 | msgstr "" 1202 | 1203 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:329 1204 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:366 1205 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:455 1206 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:492 1207 | msgid "Wechat Video" 1208 | msgstr "" 1209 | 1210 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:162 1211 | msgid "" 1212 | "When UDP is enabled, V2Ray needs to know the IP address of current host." 1213 | msgstr "" 1214 | 1215 | #: package/htdocs/luci-static/resources/view/v2ray/inbound.js:317 1216 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:443 1217 | msgid "Write buffer size" 1218 | msgstr "" 1219 | 1220 | #: package/htdocs/luci-static/resources/view/v2ray/outbound.js:140 1221 | msgid "You can add multiple links at once, one link per line." 1222 | msgstr "" 1223 | 1224 | #: package/luasrc/controller/v2ray.lua:53 1225 | msgctxt "libustream-openssl" 1226 | msgid "wget: SSL support not available, please install %s or %s." 1227 | msgstr "" 1228 | -------------------------------------------------------------------------------- /public/root/etc/config/v2ray: -------------------------------------------------------------------------------- 1 | config v2ray 'main' 2 | option enabled '0' 3 | option v2ray_file '/usr/bin/v2ray' 4 | option mem_percentage '0' 5 | option config_file '' 6 | option loglevel 'warning' 7 | option access_log '/dev/null' 8 | option error_log '/var/log/v2ray-error.log' 9 | option inbounds 'socks_proxy dokodemo_door' 10 | option outbounds 'vmess direct block dns_out' 11 | option stats_enabled '0' 12 | option transport_enabled '0' 13 | 14 | config dns 'main_dns' 15 | option enabled '1' 16 | list hosts 'example.com|127.0.0.1' 17 | option servers 'cloudflare_dns_1 google_dns_1 114_dns_1 ali_dns_1' 18 | 19 | config dns_server 'cloudflare_dns_1' 20 | option alias 'cloudflare_dns_1' 21 | option address '1.1.1.1' 22 | 23 | config dns_server 'google_dns_1' 24 | option alias 'google_dns_1' 25 | option address '8.8.8.8' 26 | 27 | config dns_server 'op_dns_1' 28 | option alias 'op_dns_1' 29 | option address '208.67.222.222' 30 | option port '5353' 31 | 32 | config dns_server '114_dns_1' 33 | option alias '114_dns_1' 34 | option address '114.114.114.114' 35 | option port '53' 36 | list domains 'geosite:cn' 37 | 38 | config dns_server 'ali_dns_1' 39 | option alias 'ali_dns_1' 40 | option address '223.5.5.5' 41 | list domains 'geosite:cn' 42 | 43 | config routing 'main_routing' 44 | option enabled '1' 45 | option domain_strategy 'IPOnDemand' 46 | option rules 'direct_cn_ip direct_cn_domain direct_bt route_dns direct_dns proxy_dns direct_ntp' 47 | 48 | config routing_rule 'direct_cn_ip' 49 | option alias 'direct_cn_ip' 50 | option type 'field' 51 | list ip 'geoip:private' 52 | list ip 'geoip:cn' 53 | option outbound_tag 'direct' 54 | 55 | config routing_rule 'direct_cn_domain' 56 | option alias 'direct_cn_domain' 57 | option type 'field' 58 | list domain 'geosite:cn' 59 | option outbound_tag 'direct' 60 | 61 | config routing_rule 'direct_speedtest' 62 | option alias 'direct_speedtest' 63 | option type 'field' 64 | list domain 'geosite:speedtest' 65 | option outbound_tag 'direct' 66 | 67 | config routing_rule 'direct_bt' 68 | option alias 'direct_bt' 69 | option type 'field' 70 | option protocol 'bittorrent' 71 | option outbound_tag 'direct' 72 | 73 | config routing_rule 'block_ad' 74 | option alias 'block_ad' 75 | option type 'field' 76 | list domain 'geosite:category-ads-all' 77 | option outbound_tag 'block' 78 | 79 | config routing_rule 'route_dns' 80 | option alias 'route_dns' 81 | option type 'field' 82 | list network 'udp' 83 | list port '53' 84 | list inbound_tag 'transparent' 85 | option outbound_tag 'dns_out' 86 | 87 | config routing_rule 'direct_dns' 88 | option alias 'direct_dns' 89 | option type 'field' 90 | list ip '114.114.114.114' 91 | list ip '223.5.5.5' 92 | option outbound_tag 'direct' 93 | 94 | config routing_rule 'proxy_dns' 95 | option alias 'proxy_dns' 96 | option type 'field' 97 | list ip '1.1.1.1' 98 | list ip '8.8.8.8' 99 | list ip '208.67.222.222' 100 | option outbound_tag 'proxy' 101 | 102 | config routing_rule 'direct_ntp' 103 | option alias 'direct_ntp' 104 | option type 'field' 105 | list network 'udp' 106 | list port '123' 107 | option outbound_tag 'direct' 108 | 109 | config routing_balancer 'routing_balancer_1' 110 | option tag 'balancer' 111 | list selector 'a' 112 | list selector 'ab' 113 | 114 | config policy 'main_policy' 115 | option enabled '0' 116 | 117 | config policy_level 'policy_level_0' 118 | option level '0' 119 | option handshake '4' 120 | option conn_idle '300' 121 | option uplink_only '2' 122 | option downlink_only '5' 123 | option buffer_size '0' 124 | 125 | config reverse 'main_reverse' 126 | option enabled '0' 127 | list bridges 'bridge|test.v2ray.com' 128 | list portals 'portal|test.v2ray.com' 129 | 130 | config inbound 'socks_proxy' 131 | option alias 'socks_proxy' 132 | option listen '0.0.0.0' 133 | option port '1080' 134 | option protocol 'socks' 135 | option s_socks_auth 'noauth' 136 | option s_socks_udp '1' 137 | option s_socks_ip '127.0.0.1' 138 | option sniffing_enabled '1' 139 | option sniffing_dest_override 'http tls' 140 | 141 | config inbound 'dokodemo_door' 142 | option alias 'dokodemo_door' 143 | option port '1081' 144 | option protocol 'dokodemo-door' 145 | option tag 'transparent' 146 | option sniffing_enabled '1' 147 | option sniffing_dest_override 'http tls' 148 | 149 | config outbound 'vmess' 150 | option alias 'vmess' 151 | option protocol 'vmess' 152 | option s_vmess_address '0.0.0.0' 153 | option s_vmess_port '10086' 154 | option s_vmess_user_id 'b831381d-6324-4d53-ad4f-8cda48b30811' 155 | option tag 'proxy' 156 | 157 | config outbound 'direct' 158 | option alias 'direct' 159 | option protocol 'freedom' 160 | option tag 'direct' 161 | 162 | config outbound 'block' 163 | option alias 'block' 164 | option protocol 'blackhole' 165 | option tag 'block' 166 | 167 | config outbound 'dns_out' 168 | option alias 'dns_out' 169 | option protocol 'dns' 170 | option tag 'dns_out' 171 | 172 | config transparent_proxy 'main_transparent_proxy' 173 | option redirect_port '' 174 | option proxy_mode 'default' 175 | option apnic_delegated_mirror 'apnic' 176 | option gfwlist_mirror 'github' 177 | -------------------------------------------------------------------------------- /public/root/etc/firewall.v2ray: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2019-2020 Xingwang Liao 4 | # Licensed to the public under the MIT License. 5 | # 6 | 7 | test -s "/etc/init.d/v2ray" && /etc/init.d/v2ray reload 8 | -------------------------------------------------------------------------------- /public/root/etc/uci-defaults/40_luci-v2ray: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2019-2020 Xingwang Liao 4 | # Licensed to the public under the MIT License. 5 | # 6 | 7 | # v2ray main 8 | v2ray=$(uci -q get v2ray.main) 9 | if [ "x$v2ray" != "xv2ray" ] ; then 10 | uci -q batch <<-EOF >/dev/null 11 | add v2ray v2ray 12 | rename v2ray.@v2ray[-1]="main" 13 | set v2ray.main.enabled="0" 14 | commit v2ray 15 | EOF 16 | fi 17 | 18 | # dns 19 | dns=$(uci -q get v2ray.main_dns) 20 | if [ "x$dns" != "xdns" ] ; then 21 | uci -q batch <<-EOF >/dev/null 22 | add v2ray dns 23 | rename v2ray.@dns[-1]="main_dns" 24 | set v2ray.main_dns.enabled="0" 25 | commit v2ray 26 | EOF 27 | fi 28 | 29 | # routing 30 | routing=$(uci -q get v2ray.main_routing) 31 | if [ "x$routing" != "xrouting" ] ; then 32 | uci -q batch <<-EOF >/dev/null 33 | add v2ray routing 34 | rename v2ray.@routing[-1]="main_routing" 35 | set v2ray.main_routing.enabled="0" 36 | commit v2ray 37 | EOF 38 | fi 39 | 40 | # policy 41 | policy=$(uci -q get v2ray.main_policy) 42 | if [ "x$policy" != "xpolicy" ] ; then 43 | uci -q batch <<-EOF >/dev/null 44 | add v2ray policy 45 | rename v2ray.@policy[-1]="main_policy" 46 | set v2ray.main_policy.enabled="0" 47 | commit v2ray 48 | EOF 49 | fi 50 | 51 | # reverse 52 | reverse=$(uci -q get v2ray.main_reverse) 53 | if [ "x$reverse" != "xreverse" ] ; then 54 | uci -q batch <<-EOF >/dev/null 55 | add v2ray reverse 56 | rename v2ray.@reverse[-1]="main_reverse" 57 | set v2ray.main_reverse.enabled="0" 58 | commit v2ray 59 | EOF 60 | fi 61 | 62 | # transparent_proxy 63 | transparent_proxy=$(uci -q get v2ray.main_transparent_proxy) 64 | if [ "x$transparent_proxy" != "xtransparent_proxy" ] ; then 65 | uci -q batch <<-EOF >/dev/null 66 | add v2ray transparent_proxy 67 | rename v2ray.@transparent_proxy[-1]="main_transparent_proxy" 68 | set v2ray.main_transparent_proxy.redirect_port="" 69 | commit v2ray 70 | EOF 71 | fi 72 | 73 | uci -q batch <<-EOF >/dev/null 74 | delete ucitrack.@v2ray[-1] 75 | add ucitrack v2ray 76 | set ucitrack.@v2ray[-1].init=v2ray 77 | commit ucitrack 78 | delete firewall.v2ray 79 | set firewall.v2ray=include 80 | set firewall.v2ray.type=script 81 | set firewall.v2ray.path=/etc/firewall.v2ray 82 | set firewall.v2ray.family=any 83 | set firewall.v2ray.reload=1 84 | commit firewall 85 | EOF 86 | 87 | exit 0 88 | -------------------------------------------------------------------------------- /public/root/etc/v2ray/chnroute6.txt: -------------------------------------------------------------------------------- 1 | 2001:250::/35 2 | 2001:250:2000::/35 3 | 2001:250:4000::/34 4 | 2001:250:8000::/33 5 | 2001:251::/32 6 | 2001:252::/32 7 | 2001:254::/32 8 | 2001:256::/32 9 | 2001:4438::/32 10 | 2001:4510::/29 11 | 2400:1040::/32 12 | 2400:1340::/32 13 | 2400:1380::/32 14 | 2400:1640::/32 15 | 2400:1740::/32 16 | 2400:1840::/32 17 | 2400:1940::/32 18 | 2400:3040::/32 19 | 2400:3140::/32 20 | 2400:3200::/32 21 | 2400:3280::/32 22 | 2400:3340::/32 23 | 2400:3440::/32 24 | 2400:3540::/32 25 | 2400:3600::/32 26 | 2400:3640::/32 27 | 2400:4440::/32 28 | 2400:4540::/32 29 | 2400:4600::/32 30 | 2400:4640::/32 31 | 2400:4740::/32 32 | 2400:5080::/32 33 | 2400:5280::/32 34 | 2400:5400::/32 35 | 2400:5580::/32 36 | 2400:5600::/32 37 | 2400:5640::/32 38 | 2400:5840::/32 39 | 2400:6000::/32 40 | 2400:6040::/32 41 | 2400:6200::/32 42 | 2400:6600::/32 43 | 2400:6640::/32 44 | 2400:6740::/32 45 | 2400:6840::/32 46 | 2400:6940::/32 47 | 2400:7040::/32 48 | 2400:7100::/32 49 | 2400:7140::/32 50 | 2400:7200::/32 51 | 2400:7240::/32 52 | 2400:7340::/32 53 | 2400:7440::/32 54 | 2400:7540::/32 55 | 2400:7640::/32 56 | 2400:7680::/32 57 | 2400:7740::/32 58 | 2400:8080::/32 59 | 2400:8200::/32 60 | 2400:8580::/32 61 | 2400:8600::/32 62 | 2400:8780::/32 63 | 2400:8840::/32 64 | 2400:8980::/32 65 | 2400:9040::/32 66 | 2400:9340::/32 67 | 2400:9580::/32 68 | 2400:9600::/32 69 | 2401:80::/32 70 | 2401:140::/32 71 | 2401:540::/32 72 | 2401:780::/32 73 | 2401:1000::/32 74 | 2401:1200::/32 75 | 2401:1740::/32 76 | 2401:1940::/32 77 | 2401:2040::/32 78 | 2401:2080::/32 79 | 2401:2600::/32 80 | 2401:2780::/32 81 | 2401:2980::/32 82 | 2401:3100::/32 83 | 2401:3380::/32 84 | 2401:3440::/32 85 | 2401:3480::/32 86 | 2401:3640::/32 87 | 2401:3780::/32 88 | 2401:3800::/32 89 | 2401:3880::/32 90 | 2401:3980::/32 91 | 2401:4080::/32 92 | 2401:4180::/32 93 | 2401:4280::/32 94 | 2401:4380::/32 95 | 2401:4480::/32 96 | 2401:4580::/32 97 | 2401:4680::/32 98 | 2401:4780::/32 99 | 2401:4880::/32 100 | 2401:5180::/32 101 | 2401:5680::/32 102 | 2401:7180::/32 103 | 2401:7240::/32 104 | 2401:7340::/32 105 | 2401:7580::/32 106 | 2401:7680::/32 107 | 2401:7700::/32 108 | 2401:7780::/32 109 | 2401:7880::/32 110 | 2401:7980::/32 111 | 2401:8200::/32 112 | 2401:8380::/32 113 | 2401:8540::/32 114 | 2401:8600::/32 115 | 2401:8680::/32 116 | 2401:8840::/32 117 | 2401:9340::/32 118 | 2401:9380::/32 119 | 2401:9600::/32 120 | 2401:9740::/32 121 | 2402:440::/32 122 | 2402:840::/32 123 | 2402:880::/32 124 | 2402:1000::/32 125 | 2402:1440::/32 126 | 2402:1540::/32 127 | 2402:1600::/32 128 | 2402:1740::/32 129 | 2402:2000::/32 130 | 2402:2280::/32 131 | 2402:2440::/32 132 | 2402:2540::/32 133 | 2402:2640::/32 134 | 2402:2780::/32 135 | 2402:3040::/32 136 | 2402:3080::/32 137 | 2402:3140::/32 138 | 2402:3180::/32 139 | 2402:3240::/32 140 | 2402:4140::/32 141 | 2402:4340::/32 142 | 2402:4440::/32 143 | 2402:4500::/32 144 | 2402:4540::/32 145 | 2402:5140::/32 146 | 2402:5180::/32 147 | 2402:5240::/32 148 | 2402:5340::/32 149 | 2402:5880::/32 150 | 2402:5940::/32 151 | 2402:6280::/32 152 | 2402:6740::/32 153 | 2402:7040::/32 154 | 2402:7080::/32 155 | 2402:7140::/32 156 | 2402:7240::/32 157 | 2402:7540::/32 158 | 2402:7740::/32 159 | 2402:8180::/32 160 | 2402:8280::/32 161 | 2402:8300::/32 162 | 2402:8380::/32 163 | 2402:8800::/32 164 | 2402:8840::/32 165 | 2402:8900::/32 166 | 2402:8940::/32 167 | 2402:9240::/32 168 | 2402:9440::/32 169 | 2402:9480::/32 170 | 2402:9580::/32 171 | 2402:9680::/32 172 | 2402:9840::/32 173 | 2402:9940::/32 174 | 2403:600::/32 175 | 2403:700::/32 176 | 2403:800::/31 177 | 2403:980::/32 178 | 2403:1180::/32 179 | 2403:1340::/32 180 | 2403:1440::/32 181 | 2403:1580::/32 182 | 2403:1980::/32 183 | 2403:2040::/32 184 | 2403:2080::/32 185 | 2403:2180::/32 186 | 2403:2240::/32 187 | 2403:2280::/32 188 | 2403:2380::/32 189 | 2403:2440::/32 190 | 2403:2580::/32 191 | 2403:2680::/32 192 | 2403:2740::/32 193 | 2403:2780::/32 194 | 2403:2940::/32 195 | 2403:3040::/32 196 | 2403:3140::/32 197 | 2403:3280::/32 198 | 2403:3380::/32 199 | 2403:3480::/32 200 | 2403:3580::/32 201 | 2403:3640::/32 202 | 2403:3680::/32 203 | 2403:3740::/32 204 | 2403:3780::/32 205 | 2403:3840::/32 206 | 2403:3880::/32 207 | 2403:3940::/32 208 | 2403:3980::/32 209 | 2403:4080::/32 210 | 2403:4180::/32 211 | 2403:4240::/32 212 | 2403:4280::/32 213 | 2403:4300::/32 214 | 2403:4380::/32 215 | 2403:4580::/32 216 | 2403:4680::/32 217 | 2403:4840::/32 218 | 2403:4880::/32 219 | 2403:4980::/32 220 | 2403:5040::/32 221 | 2403:5080::/32 222 | 2403:5280::/32 223 | 2403:5380::/32 224 | 2403:5540::/32 225 | 2403:5580::/32 226 | 2403:5640::/32 227 | 2403:5780::/32 228 | 2403:5980::/32 229 | 2403:6080::/32 230 | 2403:6180::/32 231 | 2403:6280::/32 232 | 2403:6380::/32 233 | 2403:6580::/32 234 | 2403:6680::/32 235 | 2403:6740::/32 236 | 2403:6780::/32 237 | 2403:6880::/32 238 | 2403:6980::/32 239 | 2403:7040::/32 240 | 2403:7080::/32 241 | 2403:7180::/32 242 | 2403:7280::/32 243 | 2403:7380::/32 244 | 2403:7480::/32 245 | 2403:7540::/32 246 | 2403:7580::/32 247 | 2403:7700::/32 248 | 2403:7840::/32 249 | 2403:8080::/32 250 | 2403:8180::/32 251 | 2403:8280::/32 252 | 2403:8380::/32 253 | 2403:8480::/32 254 | 2403:8580::/32 255 | 2403:8880::/32 256 | 2403:8900::/32 257 | 2403:8980::/32 258 | 2403:9080::/32 259 | 2403:9180::/32 260 | 2403:9280::/32 261 | 2403:9380::/32 262 | 2403:9480::/32 263 | 2403:9580::/32 264 | 2403:9680::/32 265 | 2403:9780::/32 266 | 2403:9880::/32 267 | 2404:100::/32 268 | 2404:158::/32 269 | 2404:240::/32 270 | 2404:280::/32 271 | 2404:440::/32 272 | 2404:480::/32 273 | 2404:680::/32 274 | 2404:1080::/32 275 | 2404:1180::/32 276 | 2404:1880::/32 277 | 2404:3140::/32 278 | 2404:3240::/32 279 | 2404:3300::/32 280 | 2404:3340::/32 281 | 2404:3480::/32 282 | 2404:3640::/32 283 | 2404:3700::/32 284 | 2404:3740::/32 285 | 2404:3840::/32 286 | 2404:3940::/32 287 | 2404:4080::/32 288 | 2404:4540::/32 289 | 2404:4740::/32 290 | 2404:5640::/32 291 | 2404:6000::/32 292 | 2404:6100::/32 293 | 2404:6380::/32 294 | 2404:6500::/32 295 | 2404:7100::/32 296 | 2404:7180::/32 297 | 2404:7240::/32 298 | 2404:7600::/32 299 | 2404:7740::/32 300 | 2404:7940::/32 301 | 2404:8040::/32 302 | 2404:8140::/32 303 | 2404:8480::/32 304 | 2404:8580::/32 305 | 2404:8700::/32 306 | 2404:8880::/32 307 | 2404:9340::/32 308 | 2404:9880::/32 309 | 2405:80::/32 310 | 2405:480::/32 311 | 2405:580::/32 312 | 2405:680::/32 313 | 2405:780::/32 314 | 2405:880::/32 315 | 2405:940::/32 316 | 2405:980::/32 317 | 2405:1080::/32 318 | 2405:1180::/32 319 | 2405:1280::/32 320 | 2405:1380::/32 321 | 2405:1480::/32 322 | 2405:1580::/32 323 | 2405:1680::/32 324 | 2405:2080::/32 325 | 2405:2180::/32 326 | 2405:2280::/32 327 | 2405:2340::/32 328 | 2405:2380::/32 329 | 2405:2480::/32 330 | 2405:2580::/32 331 | 2405:2680::/32 332 | 2405:2780::/32 333 | 2405:2880::/32 334 | 2405:2980::/32 335 | 2405:3140::/32 336 | 2405:3880::/32 337 | 2405:3980::/32 338 | 2405:4080::/32 339 | 2405:4140::/32 340 | 2405:4180::/32 341 | 2405:4280::/32 342 | 2405:4380::/32 343 | 2405:4480::/32 344 | 2405:4540::/32 345 | 2405:4580::/32 346 | 2405:4680::/32 347 | 2405:4780::/32 348 | 2405:4880::/32 349 | 2405:4980::/32 350 | 2405:5080::/32 351 | 2405:5180::/32 352 | 2405:5240::/32 353 | 2405:5280::/32 354 | 2405:5380::/32 355 | 2405:5480::/32 356 | 2405:5580::/32 357 | 2405:5680::/32 358 | 2405:5780::/32 359 | 2405:5880::/32 360 | 2405:5980::/32 361 | 2405:6080::/32 362 | 2405:6180::/32 363 | 2405:6200::/32 364 | 2405:6880::/32 365 | 2405:6940::/32 366 | 2405:7080::/32 367 | 2405:7180::/32 368 | 2405:7280::/32 369 | 2405:7380::/32 370 | 2405:7480::/32 371 | 2405:7580::/32 372 | 2405:7680::/32 373 | 2405:7780::/32 374 | 2405:7880::/32 375 | 2405:7980::/32 376 | 2405:8280::/32 377 | 2405:8480::/32 378 | 2405:8580::/32 379 | 2405:8680::/32 380 | 2405:8780::/32 381 | 2405:8880::/32 382 | 2405:8980::/32 383 | 2405:9080::/32 384 | 2405:9180::/32 385 | 2405:9280::/32 386 | 2405:9300::/32 387 | 2405:9380::/32 388 | 2405:9480::/32 389 | 2405:9580::/32 390 | 2405:9680::/32 391 | 2405:9700::/32 392 | 2405:9780::/32 393 | 2405:9880::/32 394 | 2405:9900::/32 395 | 2405:9980::/32 396 | 2406:80::/32 397 | 2406:280::/32 398 | 2406:880::/32 399 | 2406:1080::/32 400 | 2406:1100::/32 401 | 2406:1180::/32 402 | 2406:1280::/32 403 | 2406:1380::/32 404 | 2406:1480::/32 405 | 2406:1580::/32 406 | 2406:1680::/32 407 | 2406:1780::/32 408 | 2406:1880::/32 409 | 2406:1980::/32 410 | 2406:2080::/32 411 | 2406:2580::/32 412 | 2406:2700::/32 413 | 2406:2780::/32 414 | 2406:2880::/32 415 | 2406:2980::/32 416 | 2406:3080::/32 417 | 2406:3180::/32 418 | 2406:3280::/32 419 | 2406:3300::/32 420 | 2406:3380::/32 421 | 2406:3480::/32 422 | 2406:3580::/32 423 | 2406:3680::/32 424 | 2406:3700::/32 425 | 2406:3780::/32 426 | 2406:3880::/32 427 | 2406:3980::/32 428 | 2406:4080::/32 429 | 2406:4180::/32 430 | 2406:4280::/32 431 | 2406:4380::/32 432 | 2406:4480::/32 433 | 2406:4500::/32 434 | 2406:4680::/32 435 | 2406:4980::/32 436 | 2406:5080::/32 437 | 2406:5180::/32 438 | 2406:5280::/32 439 | 2406:5380::/32 440 | 2406:5480::/32 441 | 2406:5580::/32 442 | 2406:5680::/32 443 | 2406:5780::/32 444 | 2406:5880::/32 445 | 2406:5980::/32 446 | 2406:6080::/32 447 | 2406:6100::/32 448 | 2406:6180::/32 449 | 2406:6280::/32 450 | 2406:6300::/32 451 | 2406:6380::/32 452 | 2406:6480::/32 453 | 2406:6500::/32 454 | 2406:6580::/32 455 | 2406:6680::/32 456 | 2406:6780::/32 457 | 2406:6880::/32 458 | 2406:6980::/32 459 | 2406:7080::/32 460 | 2406:7280::/32 461 | 2406:7380::/32 462 | 2406:7480::/32 463 | 2406:7580::/32 464 | 2406:7680::/32 465 | 2406:7780::/32 466 | 2406:7880::/32 467 | 2406:7980::/32 468 | 2406:8080::/32 469 | 2406:8180::/32 470 | 2406:8280::/32 471 | 2406:8380::/32 472 | 2406:8480::/32 473 | 2406:8500::/32 474 | 2406:8580::/32 475 | 2406:8780::/32 476 | 2406:8880::/32 477 | 2406:8980::/32 478 | 2406:9180::/32 479 | 2406:9200::/32 480 | 2406:9280::/32 481 | 2406:9380::/32 482 | 2406:9480::/32 483 | 2406:9780::/32 484 | 2407:480::/32 485 | 2407:580::/32 486 | 2407:1180::/32 487 | 2407:1900::/32 488 | 2407:2280::/32 489 | 2407:2380::/32 490 | 2407:2780::/32 491 | 2407:3700::/32 492 | 2407:3900::/32 493 | 2407:4580::/32 494 | 2407:4680::/32 495 | 2407:4880::/32 496 | 2407:4980::/32 497 | 2407:5380::/32 498 | 2407:5500::/32 499 | 2407:5780::/32 500 | 2407:6580::/32 501 | 2407:7680::/32 502 | 2407:7780::/32 503 | 2407:7880::/32 504 | 2407:7980::/32 505 | 2407:8880::/32 506 | 2407:9080::/32 507 | 2407:9180::/32 508 | 2407:9680::/32 509 | 2407:9980::/32 510 | 2408:4000::/22 511 | 2408:8000::/22 512 | 2408:8400::/22 513 | 2408:8800::/21 514 | 2409:8000::/20 515 | -------------------------------------------------------------------------------- /public/root/etc/v2ray/directlist.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuoruan/luci-app-v2ray/2cdc86337b9559ced164f6cc4ab08414c7641819/public/root/etc/v2ray/directlist.txt -------------------------------------------------------------------------------- /public/root/etc/v2ray/proxylist.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuoruan/luci-app-v2ray/2cdc86337b9559ced164f6cc4ab08414c7641819/public/root/etc/v2ray/proxylist.txt -------------------------------------------------------------------------------- /public/root/etc/v2ray/srcdirectlist.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuoruan/luci-app-v2ray/2cdc86337b9559ced164f6cc4ab08414c7641819/public/root/etc/v2ray/srcdirectlist.txt -------------------------------------------------------------------------------- /public/root/etc/v2ray/transport.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuoruan/luci-app-v2ray/2cdc86337b9559ced164f6cc4ab08414c7641819/public/root/etc/v2ray/transport.json -------------------------------------------------------------------------------- /public/root/usr/libexec/rpcd/luci.v2ray: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2020 Xingwang Liao 4 | # Licensed to the public under the MIT License. 5 | # 6 | 7 | . /usr/share/libubox/jshn.sh 8 | 9 | check_running_status() { 10 | local pid="$(cat /var/run/v2ray.main.pid 2>/dev/null)" 11 | 12 | if [ -z "$pid" ] ; then 13 | echo '{ "code": 1 }' 14 | return 15 | fi 16 | 17 | local file="$(uci -q get v2ray.main.v2ray_file)" 18 | 19 | if [ -z "$file" ] ; then 20 | echo '{ "code": 2 }' 21 | return 22 | fi 23 | 24 | local file_name="$(basename "$file")" 25 | 26 | if ( pidof "$file_name" 2>/dev/null | grep -q "$pid" ) ; then 27 | echo '{ "code": 0 }' 28 | else 29 | echo '{ "code": 1 }' 30 | fi 31 | } 32 | 33 | get_v2ray_version() { 34 | local file="$(uci -q get v2ray.main.v2ray_file)" 35 | 36 | if [ ! -s "$file" ] ; then 37 | echo '{ "code": 1 }' 38 | return 39 | fi 40 | 41 | test -x "$file" || chmod +x "$file" 42 | 43 | local version="$(sh -c "$file --version 2>/dev/null | head -n1")" 44 | 45 | if [ -n "$version" ] ; then 46 | printf '{ "code": 0, "version": "%s" }\n' "$version" 47 | else 48 | echo '{ "code": 1 }' 49 | fi 50 | } 51 | 52 | get_list_status() { 53 | local name="$1" 54 | 55 | if [ -z "$name" ] ; then 56 | echo '{ "code": 128 }' 57 | return 58 | fi 59 | 60 | local file="/etc/v2ray/${name}.txt" 61 | 62 | if [ ! -r "$file" ] ; then 63 | echo '{ "code": 2 }' 64 | return 65 | fi 66 | 67 | local count="$(grep -v '^$' "$file" | wc -l)" 68 | 69 | local lastModifyTime="$(date -r "$file" '+%Y/%m/%d %H:%M:%S %Z')" 70 | 71 | printf '{ "code": 0, "count": %d, "datetime": "%s" }\n' "$count" "$lastModifyTime" 72 | } 73 | 74 | case "$1" in 75 | list) 76 | json_set_namespace "v2ray_list" old_ns 77 | 78 | json_init 79 | 80 | json_add_object "runningStatus" 81 | json_close_object 82 | 83 | json_add_object "v2rayVersion" 84 | json_close_object 85 | 86 | json_add_object "listStatus" 87 | json_add_string "name" "String" 88 | json_close_object 89 | 90 | json_dump -i 91 | 92 | json_cleanup 93 | 94 | json_set_namespace "$old_ns" 95 | ;; 96 | call) 97 | case "$2" in 98 | runningStatus) 99 | check_running_status 100 | ;; 101 | v2rayVersion) 102 | get_v2ray_version 103 | ;; 104 | listStatus) 105 | read input; 106 | 107 | json_set_namespace "v2ray_listStatus" old_ns 108 | 109 | json_load "$input" 110 | json_get_var list_name "name" 111 | json_cleanup 112 | 113 | json_set_namespace "$old_ns" 114 | 115 | get_list_status "$list_name" 116 | ;; 117 | esac 118 | ;; 119 | esac 120 | -------------------------------------------------------------------------------- /public/root/usr/share/luci/menu.d/luci-app-v2ray.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/services/v2ray": { 3 | "title": "V2ray", 4 | "action": { 5 | "type": "alias", 6 | "path": "admin/services/v2ray/main" 7 | }, 8 | "depends": { 9 | "acl": [ 10 | "luci-app-v2ray" 11 | ], 12 | "uci": { 13 | "v2ray": true 14 | } 15 | } 16 | }, 17 | "admin/services/v2ray/main": { 18 | "title": "Global Settings", 19 | "order": 10, 20 | "action": { 21 | "type": "view", 22 | "path": "v2ray/main" 23 | } 24 | }, 25 | "admin/services/v2ray/inbound": { 26 | "title": "Inbound", 27 | "order": 20, 28 | "action": { 29 | "type": "view", 30 | "path": "v2ray/inbound" 31 | } 32 | }, 33 | "admin/services/v2ray/outbound": { 34 | "title": "Outbound", 35 | "order": 30, 36 | "action": { 37 | "type": "view", 38 | "path": "v2ray/outbound" 39 | } 40 | }, 41 | "admin/services/v2ray/dns": { 42 | "title": "DNS", 43 | "order": 40, 44 | "action": { 45 | "type": "view", 46 | "path": "v2ray/dns" 47 | } 48 | }, 49 | "admin/services/v2ray/routing": { 50 | "title": "Routing", 51 | "order": 50, 52 | "action": { 53 | "type": "view", 54 | "path": "v2ray/routing" 55 | } 56 | }, 57 | "admin/services/v2ray/policy": { 58 | "title": "Policy", 59 | "order": 60, 60 | "action": { 61 | "type": "view", 62 | "path": "v2ray/policy" 63 | } 64 | }, 65 | "admin/services/v2ray/reverse": { 66 | "title": "Reverse", 67 | "order": 70, 68 | "action": { 69 | "type": "view", 70 | "path": "v2ray/reverse" 71 | } 72 | }, 73 | "admin/services/v2ray/transparent-proxy": { 74 | "title": "Transparent Proxy", 75 | "order": 80, 76 | "action": { 77 | "type": "view", 78 | "path": "v2ray/transparent-proxy" 79 | } 80 | }, 81 | "admin/services/v2ray/about": { 82 | "title": "About", 83 | "order": 90, 84 | "action": { 85 | "type": "view", 86 | "path": "v2ray/about" 87 | } 88 | }, 89 | "admin/services/v2ray/request": { 90 | "action": { 91 | "type": "call", 92 | "module": "luci.controller.v2ray", 93 | "function": "action_request" 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /public/root/usr/share/rpcd/acl.d/luci-app-v2ray.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-v2ray": { 3 | "description": "Grant access to LuCI app V2ray", 4 | "read": { 5 | "ubus": { 6 | "luci.v2ray": [ 7 | "*" 8 | ] 9 | }, 10 | "uci": [ 11 | "v2ray" 12 | ], 13 | "file": { 14 | "/etc/v2ray/*": [ 15 | "read" 16 | ], 17 | "/var/etc/v2ray/*": [ 18 | "read" 19 | ], 20 | "/etc/init.d/v2ray": [ 21 | "exec" 22 | ] 23 | } 24 | }, 25 | "write": { 26 | "uci": [ 27 | "v2ray" 28 | ], 29 | "file": { 30 | "/etc/v2ray/*": [ 31 | "write" 32 | ] 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/i18n-scan.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use IPC::Open2; 6 | use POSIX; 7 | 8 | $ENV{'LC_ALL'} = 'C'; 9 | POSIX::setlocale(POSIX::LC_ALL, 'C'); 10 | 11 | @ARGV >= 1 || die "Usage: $0 \n"; 12 | 13 | 14 | my %keywords = ( 15 | '.js' => [ '_:1', '_:1,2c', 'N_:2,3', 'N_:2,3,4c' ], 16 | '.lua' => [ '_:1', '_:1,2c', 'translate:1', 'translate:1,2c', 'translatef:1', 'N_:2,3', 'N_:2,3,4c', 'ntranslate:2,3', 'ntranslate:2,3,4c' ], 17 | '.htm' => [ '_:1', '_:1,2c', 'translate:1', 'translate:1,2c', 'translatef:1', 'N_:2,3', 'N_:2,3,4c', 'ntranslate:2,3', 'ntranslate:2,3,4c' ], 18 | '.json' => [ '_:1', '_:1,2c' ] 19 | ); 20 | 21 | sub xgettext($@) { 22 | my $path = shift; 23 | my @keywords = @_; 24 | my ($ext) = $path =~ m!(\.\w+)$!; 25 | my @cmd = qw(xgettext --from-code=UTF-8 --no-wrap); 26 | 27 | if ($ext eq '.htm' || $ext eq '.lua') { 28 | push @cmd, '--language=Lua'; 29 | } 30 | elsif ($ext eq '.js' || $ext eq '.json') { 31 | push @cmd, '--language=JavaScript'; 32 | } 33 | 34 | push @cmd, map { "--keyword=$_" } (@{$keywords{$ext}}, @keywords); 35 | push @cmd, '-o', '-'; 36 | 37 | return @cmd; 38 | } 39 | 40 | sub whitespace_collapse($) { 41 | my $s = shift; 42 | my %r = ('n' => ' ', 't' => ' '); 43 | 44 | # Translate \t and \n to plain spaces, leave all other escape 45 | # sequences alone. Finally replace all consecutive spaces by 46 | # single ones and trim leading and trailing space. 47 | $s =~ s/\\(.)/$r{$1} || "\\$1"/eg; 48 | $s =~ s/ {2,}/ /g; 49 | $s =~ s/^ //; 50 | $s =~ s/ $//; 51 | 52 | return $s; 53 | } 54 | 55 | sub postprocess_pot($$) { 56 | my ($path, $source) = @_; 57 | my (@res, $msgid); 58 | my $skip = 1; 59 | 60 | $source =~ s/^#: (.+?)\n/join("\n", map { "#: $path:$_" } $1 =~ m!:(\d+)!g) . "\n"/emg; 61 | 62 | my @lines = split /\n/, $source; 63 | 64 | # Remove all header lines up to the first location comment 65 | while (@lines > 0 && $lines[0] !~ m!^#: !) { 66 | shift @lines; 67 | } 68 | 69 | while (@lines > 0) { 70 | my $line = shift @lines; 71 | 72 | # Concat multiline msgids and collapse whitespaces 73 | if ($line =~ m!^(msg\w+) "(.*)"$!) { 74 | my $kw = $1; 75 | my $kv = $2; 76 | 77 | while (@lines > 0 && $lines[0] =~ m!^"(.*)"$!) { 78 | $kv .= ' '. $1; 79 | shift @lines; 80 | } 81 | 82 | $kv = whitespace_collapse($kv); 83 | 84 | # Filter invalid empty msgids by popping all lines in @res 85 | # leading to this point and skip all subsequent lines in 86 | # @lines belonging to this faulty id. 87 | if ($kw ne 'msgstr' && $kv eq '') { 88 | while (@res > 0 && $res[-1] !~ m!^$!) { 89 | pop @res; 90 | } 91 | 92 | while (@lines > 0 && $lines[0] =~ m!^(?:msg\w+ )?"(.*)"$!) { 93 | shift @lines; 94 | } 95 | 96 | next; 97 | } 98 | 99 | push @res, sprintf '%s "%s"', $kw, $kv; 100 | } 101 | 102 | # Ignore any flags added by xgettext 103 | elsif ($line =~ m!^#, !) { 104 | next; 105 | } 106 | 107 | # Pass through other lines unmodified 108 | else { 109 | push @res, $line; 110 | } 111 | } 112 | 113 | return @res ? join("\n", '', @res, '') : ''; 114 | } 115 | 116 | sub uniq(@) { 117 | my %h = map { $_, 1 } @_; 118 | return sort keys %h; 119 | } 120 | 121 | sub preprocess_htm($$) { 122 | my ($path, $source) = @_; 123 | my $sub = { 124 | '=' => '(%s)', 125 | '_' => 'translate([==[%s]==])', 126 | ':' => 'translate([==[%s]==])', 127 | '+' => 'include([==[%s]==])', 128 | '#' => '--[==[%s]==]', 129 | '' => '%s' 130 | }; 131 | 132 | # Translate the .htm source into a valid Lua source using bracket quotes 133 | # to avoid the need for complex escaping. 134 | $source =~ s!<%-?([=_:+#]?)(.*?)-?%>! 135 | my $t = $1; 136 | my $s = $2; 137 | 138 | # Split translation expressions on first non-escaped pipe. 139 | if ($t eq ':' || $t eq '_') { 140 | $s =~ s/^((?:[^\|\\]|\\.)*)\|(.*)$/$1]==],[==[$2/; 141 | } 142 | 143 | sprintf "]==]; $sub->{$t}; [==[", $s 144 | !sge; 145 | 146 | # Discover expressions like "lng.translate(...)" or "luci.i18n.translate(...)" 147 | # and return them as extra keyword so that xgettext recognizes such expressions 148 | # as translate(...) calls. 149 | my @extra_function_keywords = 150 | map { ("$_:1", "$_:1,2c") } 151 | uniq($source =~ m!((?:\w+\.)+translatef?)[ \t\n]*\(!g); 152 | 153 | return ("[==[$source]==]", @extra_function_keywords); 154 | } 155 | 156 | sub preprocess_lua($$) { 157 | my ($path, $source) = @_; 158 | 159 | # Discover expressions like "lng.translate(...)" or "luci.i18n.translate(...)" 160 | # and return them as extra keyword so that xgettext recognizes such expressions 161 | # as translate(...) calls. 162 | my @extra_function_keywords = 163 | map { ("$_:1", "$_:1,2c") } 164 | uniq($source =~ m!((?:\w+\.)+translatef?)[ \t\n]*\(!g); 165 | 166 | return ($source, @extra_function_keywords); 167 | } 168 | 169 | sub preprocess_json($$) { 170 | my ($path, $source) = @_; 171 | my ($file) = $path =~ m!([^/]+)$!; 172 | 173 | $source =~ s/("(?:title|description)")\s*:\s*("(?:[^"\\]|\\.)*")/$1: _($2)/sg; 174 | 175 | return ($source); 176 | } 177 | 178 | 179 | my ($msguniq_in, $msguniq_out); 180 | my $msguniq_pid = open2($msguniq_out, $msguniq_in, 'msguniq', '-s'); 181 | 182 | print $msguniq_in "msgid \"\"\nmsgstr \"Content-Type: text/plain; charset=UTF-8\"\n"; 183 | 184 | if (open F, "find @ARGV -type f '(' -name '*.htm' -o -name '*.lua' -o -name '*.js' -o -path '*/menu.d/*.json' -o -path '*/acl.d/*.json' -o -path '*/statistics/plugins/*.json' ')' |") 185 | { 186 | while (defined( my $file = readline F)) 187 | { 188 | chomp $file; 189 | 190 | if (open S, '<', $file) 191 | { 192 | local $/ = undef; 193 | my $source = ; 194 | my @extra_function_keywords; 195 | 196 | if ($file =~ m!\.htm$!) 197 | { 198 | ($source, @extra_function_keywords) = preprocess_htm($file, $source); 199 | } 200 | elsif ($file =~ m!\.lua$!) 201 | { 202 | ($source, @extra_function_keywords) = preprocess_lua($file, $source); 203 | } 204 | elsif ($file =~ m!\.json$!) 205 | { 206 | ($source, @extra_function_keywords) = preprocess_json($file, $source); 207 | } 208 | 209 | my ($xgettext_in, $xgettext_out); 210 | my $pid = open2($xgettext_out, $xgettext_in, xgettext($file, @extra_function_keywords), '-'); 211 | 212 | print $xgettext_in $source; 213 | close $xgettext_in; 214 | 215 | my $pot = readline $xgettext_out; 216 | close $xgettext_out; 217 | 218 | waitpid $pid, 0; 219 | 220 | print $msguniq_in postprocess_pot($file, $pot); 221 | } 222 | } 223 | 224 | close F; 225 | } 226 | 227 | close $msguniq_in; 228 | 229 | my @pot = <$msguniq_out>; 230 | 231 | close $msguniq_out; 232 | waitpid $msguniq_pid, 0; 233 | 234 | while (@pot > 0) { 235 | my $line = shift @pot; 236 | 237 | # Reorder the location comments in a detemrinistic way to 238 | # reduce SCM noise when frequently updating templates. 239 | if ($line =~ m!^#: !) { 240 | my @locs = ($line); 241 | 242 | while (@pot > 0 && $pot[0] =~ m!^#: !) { 243 | push @locs, shift @pot; 244 | } 245 | 246 | print 247 | map { join(':', @$_) . "\n" } 248 | sort { ($a->[0] cmp $b->[0]) || ($a->[1] <=> $b->[1]) } 249 | map { [ /^(.+):(\d+)$/ ] } 250 | @locs 251 | ; 252 | 253 | next; 254 | } 255 | 256 | print $line; 257 | } 258 | -------------------------------------------------------------------------------- /scripts/i18n-update.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | @ARGV <= 2 || die "Usage: $0 [] []\n"; 4 | 5 | my $source = shift @ARGV; 6 | my $pattern = shift @ARGV || '*.po'; 7 | 8 | sub read_header 9 | { 10 | my $file = shift || return; 11 | local $/; 12 | 13 | open P, "< $file" || die "open(): $!"; 14 | my $data = readline P; 15 | close P; 16 | 17 | $data =~ / 18 | ^ ( 19 | msgid \s "" \n 20 | msgstr \s "" \n 21 | (?: " [^\n]+ " \n )+ 22 | \n ) 23 | /mx; 24 | 25 | return $1; 26 | } 27 | 28 | sub write_header 29 | { 30 | my $file = shift || return; 31 | my $head = shift || return; 32 | local $/; 33 | 34 | open P, "< $file" || die "open(): $!"; 35 | my $data = readline P; 36 | close P; 37 | 38 | $data =~ s/ 39 | ^ ( 40 | msgid \s "" \n 41 | msgstr \s "" \n 42 | (?: " [^\n]+ " \n )+ 43 | \n ) 44 | /$head/mx; 45 | 46 | open P, "> $file" || die "open(): $!"; 47 | print P $data; 48 | close P; 49 | } 50 | 51 | my @dirs; 52 | 53 | if( ! $source ) 54 | { 55 | @dirs = glob("./*/*/po/"); 56 | } 57 | else 58 | { 59 | @dirs = ( $source ); 60 | } 61 | 62 | foreach my $dir (@dirs) 63 | { 64 | if( open F, "find $dir -type f -name '$pattern' |" ) 65 | { 66 | while( chomp( my $file = readline F ) ) 67 | { 68 | my ( $basename ) = $file =~ m{.+/([^/]+)\.po$}; 69 | 70 | if( -f "$dir/templates/$basename.pot" ) 71 | { 72 | my $head = read_header($file); 73 | 74 | printf "Updating %-40s", $file; 75 | system("msgmerge", "-U", "-N", $file, "$dir/templates/$basename.pot"); 76 | 77 | write_header($file, $head); 78 | } 79 | } 80 | 81 | close F; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/typings/v2ray.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 Xingwang Liao 3 | * 4 | * Licensed to the public under the MIT License. 5 | */ 6 | 7 | type SectionItem = { 8 | caption: string; 9 | value: string; 10 | }; 11 | 12 | type CustomTextValueProperties = { 13 | filepath: string | null; 14 | isjson: boolean; 15 | required: boolean; 16 | }; 17 | 18 | type Vmess = { 19 | v: string; 20 | ps: string; 21 | add: string; 22 | port: string; 23 | id: string; 24 | aid: string; 25 | net: "tcp" | "kcp" | "mkcp" | "ws" | "http" | "h2" | "quic"; 26 | type: "none" | "http" | "srtp" | "utp" | "wechat-video"; 27 | host: string; 28 | path: string; 29 | tls: string; 30 | }; 31 | 32 | interface Custom extends LuCI.baseclass { 33 | TextValue: form.TextValue & CustomTextValueProperties; 34 | RunningStatus: form.AbstractValue; 35 | } 36 | 37 | interface V2Ray extends LuCI.baseclass { 38 | getLocalIPs(): Promise; 39 | getSections(type: string): Promise; 40 | getDokodemoDoorPorts(): Promise; 41 | } 42 | 43 | interface Base64 extends LuCI.baseclass { 44 | encode(str: string): string; 45 | decode(str: string): string; 46 | } 47 | 48 | interface Converters extends LuCI.baseclass { 49 | extractGFWList(gfwlist: string): string; 50 | extractCHNRoute(delegatedlist: string, ipv6?: boolean): string; 51 | vmessLinkToVmess(link: string): Vmess | null; 52 | } 53 | 54 | declare const base64: Base64; 55 | declare const converters: Converters; 56 | declare const custom: Custom; 57 | declare const v2ray: V2Ray; 58 | -------------------------------------------------------------------------------- /src/v2ray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | // "require baseclass"; 11 | "require fs"; 12 | "require network"; 13 | "require uci"; 14 | 15 | // @ts-ignore 16 | return L.Class.extend({ 17 | getLocalIPs: function (): Promise { 18 | return network.getNetworks().then(function (networks: network.Protocol[]) { 19 | const localIPs: string[] = ["127.0.0.1", "0.0.0.0", "::"]; 20 | 21 | for (const n of networks) { 22 | let IPv4 = n.getIPAddr(); 23 | let IPv6 = n.getIP6Addr(); 24 | 25 | if (IPv4 && (IPv4 = IPv4.split("/")[0]) && localIPs.indexOf(IPv4) < 0) { 26 | localIPs.push(IPv4); 27 | } 28 | 29 | if (IPv6 && (IPv6 = IPv6.split("/")[0]) && localIPs.indexOf(IPv6) < 0) { 30 | localIPs.push(IPv6); 31 | } 32 | } 33 | 34 | return localIPs.sort(); 35 | }); 36 | }, 37 | 38 | getSections: function ( 39 | type: string, 40 | captionKey: string = "alias" 41 | ): Promise { 42 | return uci.load("v2ray").then(function () { 43 | const sections: SectionItem[] = []; 44 | 45 | uci.sections("v2ray", type, function (s: uci.SectionObject) { 46 | let caption: string; 47 | if ((caption = s[captionKey])) { 48 | sections.push({ 49 | caption: caption, 50 | value: s[".name"], 51 | }); 52 | } 53 | }); 54 | return sections; 55 | }); 56 | }, 57 | 58 | getDokodemoDoorPorts: function (): Promise { 59 | return uci.load("v2ray").then(function () { 60 | const sections: SectionItem[] = []; 61 | 62 | uci.sections("v2ray", "inbound", function (s: uci.SectionObject) { 63 | let port: string; 64 | if (s["protocol"] == "dokodemo-door" && (port = s["port"])) { 65 | let alias: string; 66 | 67 | if ((alias = s["alias"])) { 68 | sections.push({ 69 | caption: "%s - %s".format(alias, port), 70 | value: port, 71 | }); 72 | } else { 73 | sections.push({ 74 | caption: "%s:%s".format(s["listen"], port), 75 | value: port, 76 | }); 77 | } 78 | } 79 | }); 80 | 81 | return sections; 82 | }); 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /src/view/v2ray/about.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require fs"; 11 | "require uci"; 12 | "require ui"; 13 | // "require view"; 14 | 15 | // @ts-ignore 16 | return L.view.extend<[string, string]>({ 17 | load: function () { 18 | return uci.load("v2ray").then(function () { 19 | let configFile = uci.get("v2ray", "main", "config_file"); 20 | 21 | if (!configFile) { 22 | configFile = "/var/etc/v2ray/v2ray.main.json"; 23 | } 24 | 25 | return Promise.all([ 26 | Promise.resolve(configFile), 27 | L.resolveDefault(fs.read(configFile), ""), 28 | ]); 29 | }); 30 | }, 31 | render: function ([configFile = "", configContent = ""] = []) { 32 | return E([ 33 | E("h2", "%s - %s".format(_("V2Ray"), _("About"))), 34 | E("p", _("LuCI support for V2Ray.")), 35 | E( 36 | "p", 37 | _("Version: %s").format( 38 | `${process.env.LUCI_VERSION}-${process.env.LUCI_RELEASE}` 39 | ) 40 | ), 41 | E("p", _("Author: %s").format("Xingwang Liao")), 42 | E( 43 | "p", 44 | _("Source: %s").format( 45 | 'https://github.com/kuoruan/luci-app-v2ray' 46 | ) 47 | ), 48 | E( 49 | "p", 50 | _("Latest: %s").format( 51 | 'https://github.com/kuoruan/luci-app-v2ray/releases/latest' 52 | ) 53 | ), 54 | E( 55 | "p", 56 | _("Report Bugs: %s").format( 57 | 'https://github.com/kuoruan/luci-app-v2ray/issues' 58 | ) 59 | ), 60 | E( 61 | "p", 62 | _("Donate: %s").format( 63 | 'https://blog.kuoruan.com/donate' 64 | ) 65 | ), 66 | E("p", _("Current Config File: %s").format(configFile)), 67 | E( 68 | "pre", 69 | { 70 | style: 71 | "-moz-tab-size: 4;-o-tab-size: 4;tab-size: 4;word-break: break-all;", 72 | }, 73 | configContent ? configContent : _("Failed to open file.") 74 | ), 75 | ]); 76 | }, 77 | handleReset: null, 78 | handleSave: null, 79 | handleSaveApply: null, 80 | }); 81 | -------------------------------------------------------------------------------- /src/view/v2ray/dns.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require v2ray"; 12 | // "require view"; 13 | 14 | // @ts-ignore 15 | return L.view.extend({ 16 | load: function () { 17 | return v2ray.getSections("dns_server"); 18 | }, 19 | render: function (dnsServers = []) { 20 | const m = new form.Map( 21 | "v2ray", 22 | "%s - %s".format(_("V2Ray"), _("DNS")), 23 | _("Details: %s").format( 24 | 'DnsObject' 25 | ) 26 | ); 27 | 28 | const s1 = m.section(form.NamedSection, "main_dns", "dns"); 29 | s1.anonymous = true; 30 | s1.addremove = false; 31 | 32 | let o; 33 | 34 | o = s1.option(form.Flag, "enabled", _("Enabled")); 35 | o.rmempty = false; 36 | 37 | o = s1.option(form.Value, "tag", _("Tag")); 38 | 39 | o = s1.option( 40 | form.Value, 41 | "client_ip", 42 | _("Client IP"), 43 | '%s'.format( 44 | _("Get my public IP address") 45 | ) 46 | ); 47 | o.datatype = "ipaddr"; 48 | 49 | o = s1.option( 50 | form.DynamicList, 51 | "hosts", 52 | _("Hosts"), 53 | _( 54 | "A list of static addresses, format: domain|address. eg: %s" 55 | ).format("google.com|127.0.0.1") 56 | ); 57 | 58 | o = s1.option( 59 | form.MultiValue, 60 | "servers", 61 | _("DNS Servers"), 62 | _("Select DNS servers to use") 63 | ); 64 | for (const d of dnsServers) { 65 | o.value(d.value, d.caption); 66 | } 67 | 68 | const s2 = m.section( 69 | form.GridSection, 70 | "dns_server", 71 | _("DNS server"), 72 | _("Add DNS servers here") 73 | ); 74 | s2.anonymous = true; 75 | s2.addremove = true; 76 | s2.nodescription = true; 77 | 78 | o = s2.option(form.Value, "alias", _("Alias")); 79 | o.rmempty = false; 80 | 81 | o = s2.option(form.Value, "address", _("Address")); 82 | 83 | o = s2.option(form.Value, "port", _("Port")); 84 | o.datatype = "port"; 85 | o.placeholder = "53"; 86 | 87 | o = s2.option(form.DynamicList, "domains", _("Domains")); 88 | o.modalonly = true; 89 | 90 | o = s2.option(form.DynamicList, "expect_ips", _("Expect IPs")); 91 | o.modalonly = true; 92 | 93 | return m.render(); 94 | }, 95 | }); 96 | -------------------------------------------------------------------------------- /src/view/v2ray/inbound.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require network"; 12 | "require uci"; 13 | "require v2ray"; 14 | // "require view"; 15 | 16 | // @ts-ignore 17 | return L.view.extend({ 18 | load: function () { 19 | return v2ray.getLocalIPs(); 20 | }, 21 | render: function (localIPs = []) { 22 | const m = new form.Map("v2ray", "%s - %s".format(_("V2Ray"), _("Inbound"))); 23 | 24 | const s = m.section(form.GridSection, "inbound"); 25 | s.anonymous = true; 26 | s.addremove = true; 27 | s.sortable = true; 28 | s.modaltitle = function (section_id: string) { 29 | const alias = uci.get("v2ray", section_id, "alias"); 30 | return `${_("Inbound")} » ${alias ?? _("Add")}`; 31 | }; 32 | s.nodescriptions = true; 33 | 34 | s.tab("general", _("General Settings")); 35 | s.tab("stream", _("Stream Settings")); 36 | s.tab("other", _("Other Settings")); 37 | 38 | let o; 39 | 40 | /** General settings */ 41 | o = s.taboption("general", form.Value, "alias", _("Alias")); 42 | o.rmempty = false; 43 | 44 | o = s.taboption("general", form.Value, "listen", _("Listen")); 45 | o.datatype = "ipaddr"; 46 | for (const IP of localIPs) { 47 | o.value(IP); 48 | } 49 | 50 | o = s.taboption("general", form.Value, "port", _("Port")); 51 | o.rmempty = false; 52 | o.datatype = "or(port, portrange)"; 53 | 54 | o = s.taboption("general", form.ListValue, "protocol", _("Protocol")); 55 | o.value("dokodemo-door", "Dokodemo-door"); 56 | o.value("http", "HTTP"); 57 | o.value("mtproto", "MTProto"); 58 | o.value("shadowsocks", "Shadowsocks"); 59 | o.value("socks", "Socks"); 60 | o.value("vmess", "VMess"); 61 | 62 | // Settings - Dokodemo-door 63 | o = s.taboption( 64 | "general", 65 | form.Value, 66 | "s_dokodemo_door_address", 67 | "%s - %s".format("Dokodemo-door", _("Address")), 68 | _("Address of the destination server.") 69 | ); 70 | o.modalonly = true; 71 | o.depends("protocol", "dokodemo-door"); 72 | o.datatype = "host"; 73 | 74 | o = s.taboption( 75 | "general", 76 | form.Value, 77 | "s_dokodemo_door_port", 78 | "%s - %s".format("Dokodemo-door", _("Port")), 79 | _("Port of the destination server.") 80 | ); 81 | o.modalonly = true; 82 | o.depends("protocol", "dokodemo-door"); 83 | o.datatype = "port"; 84 | 85 | o = s.taboption( 86 | "general", 87 | form.MultiValue, 88 | "s_dokodemo_door_network", 89 | "%s - %s".format("Dokodemo-door", _("Network")), 90 | _( 91 | "If transparent proxy enabled on current inbound, this option will be ignored." 92 | ) 93 | ); 94 | o.modalonly = true; 95 | o.depends("protocol", "dokodemo-door"); 96 | o.value("tcp"); 97 | o.value("udp"); 98 | o.default = "tcp"; 99 | 100 | o = s.taboption( 101 | "general", 102 | form.Value, 103 | "s_dokodemo_door_timeout", 104 | "%s - %s".format("Dokodemo-door", _("Timeout")), 105 | _("Time limit for inbound data(seconds)") 106 | ); 107 | o.modalonly = true; 108 | o.depends("protocol", "dokodemo-door"); 109 | o.datatype = "uinteger"; 110 | o.placeholder = "300"; 111 | 112 | o = s.taboption( 113 | "general", 114 | form.Flag, 115 | "s_dokodemo_door_follow_redirect", 116 | "%s - %s".format("Dokodemo-door", _("Follow redirect")), 117 | _( 118 | "If transparent proxy enabled on current inbound, this option will be ignored." 119 | ) 120 | ); 121 | o.modalonly = true; 122 | o.depends("protocol", "dokodemo-door"); 123 | 124 | o = s.taboption( 125 | "general", 126 | form.Value, 127 | "s_dokodemo_door_user_level", 128 | "%s - %s".format("Dokodemo-door", _("User level")), 129 | _("All connections share this level") 130 | ); 131 | o.modalonly = true; 132 | o.depends("protocol", "dokodemo-door"); 133 | o.datatype = "uinteger"; 134 | 135 | // Settings - HTTP 136 | o = s.taboption( 137 | "general", 138 | form.Value, 139 | "s_http_account_user", 140 | "%s - %s".format("HTTP", _("Account user")) 141 | ); 142 | o.modalonly = true; 143 | o.depends("protocol", "http"); 144 | 145 | o = s.taboption( 146 | "general", 147 | form.Value, 148 | "s_http_account_pass", 149 | "%s - %s".format("HTTP", _("Account password")) 150 | ); 151 | o.modalonly = true; 152 | o.depends("protocol", "http"); 153 | o.password = true; 154 | 155 | o = s.taboption( 156 | "general", 157 | form.Flag, 158 | "s_http_allow_transparent", 159 | "%s - %s".format("HTTP", _("Allow transparent")) 160 | ); 161 | o.modalonly = true; 162 | o.depends("protocol", "http"); 163 | 164 | o = s.taboption( 165 | "general", 166 | form.Value, 167 | "s_http_timeout", 168 | "%s - %s".format("HTTP", _("Timeout")), 169 | _("Time limit for inbound data(seconds)") 170 | ); 171 | o.modalonly = true; 172 | o.depends("protocol", "http"); 173 | o.datatype = "uinteger"; 174 | o.placeholder = "300"; 175 | 176 | o = s.taboption( 177 | "general", 178 | form.Value, 179 | "s_http_user_level", 180 | "%s - %s".format("HTTP", _("User level")), 181 | _("All connections share this level") 182 | ); 183 | o.modalonly = true; 184 | o.depends("protocol", "http"); 185 | o.datatype = "uinteger"; 186 | 187 | // Settings - MTProto 188 | o = s.taboption( 189 | "general", 190 | form.Value, 191 | "s_mtproto_user_email", 192 | "%s - %s".format("MTProto", _("User email")) 193 | ); 194 | o.modalonly = true; 195 | o.depends("protocol", "mtproto"); 196 | 197 | o = s.taboption( 198 | "general", 199 | form.Value, 200 | "s_mtproto_user_secret", 201 | "%s - %s".format("MTProto", _("User secret")) 202 | ); 203 | o.modalonly = true; 204 | o.depends("protocol", "mtproto"); 205 | o.password = true; 206 | 207 | o = s.taboption( 208 | "general", 209 | form.Value, 210 | "s_mtproto_user_level", 211 | "%s - %s".format("MTProto", _("User level")), 212 | _("All connections share this level") 213 | ); 214 | o.modalonly = true; 215 | o.depends("protocol", "mtproto"); 216 | o.datatype = "uinteger"; 217 | 218 | // Settings - Shadowsocks 219 | o = s.taboption( 220 | "general", 221 | form.Value, 222 | "s_shadowsocks_email", 223 | "%s - %s".format("Shadowsocks", _("Email")) 224 | ); 225 | o.modalonly = true; 226 | o.depends("protocol", "shadowsocks"); 227 | 228 | o = s.taboption( 229 | "general", 230 | form.ListValue, 231 | "s_shadowsocks_method", 232 | "%s - %s".format("Shadowsocks", _("Method")) 233 | ); 234 | o.modalonly = true; 235 | o.depends("protocol", "shadowsocks"); 236 | o.value(""); 237 | o.value("aes-256-cfb"); 238 | o.value("aes-128-cfb"); 239 | o.value("chacha20"); 240 | o.value("chacha20-ietf"); 241 | o.value("aes-256-gcm"); 242 | o.value("aes-128-gcm"); 243 | o.value("chacha20-poly1305"); 244 | o.value("chacha20-ietf-poly1305"); 245 | 246 | o = s.taboption( 247 | "general", 248 | form.Value, 249 | "s_shadowsocks_password", 250 | "%s - %s".format("Shadowsocks", _("Password")) 251 | ); 252 | o.modalonly = true; 253 | o.depends("protocol", "shadowsocks"); 254 | o.password = true; 255 | 256 | o = s.taboption( 257 | "general", 258 | form.Value, 259 | "s_shadowsocks_level", 260 | "%s - %s".format("Shadowsocks", _("User level")) 261 | ); 262 | o.modalonly = true; 263 | o.depends("protocol", "shadowsocks"); 264 | o.datatype = "uinteger"; 265 | 266 | o = s.taboption( 267 | "general", 268 | form.Flag, 269 | "s_shadowsocks_ota", 270 | "%s - %s".format("Shadowsocks", _("One Time Auth (OTA)")) 271 | ); 272 | o.modalonly = true; 273 | o.depends("protocol", "shadowsocks"); 274 | 275 | o = s.taboption( 276 | "general", 277 | form.MultiValue, 278 | "s_shadowsocks_network", 279 | "%s - %s".format("Shadowsocks", _("Network")) 280 | ); 281 | o.modalonly = true; 282 | o.depends("protocol", "shadowsocks"); 283 | o.value("tcp"); 284 | o.value("udp"); 285 | o.default = "tcp"; 286 | 287 | // Settings - Socks; 288 | o = s.taboption( 289 | "general", 290 | form.ListValue, 291 | "s_socks_auth", 292 | "%s - %s".format("Socks", _("Auth")) 293 | ); 294 | o.modalonly = true; 295 | o.depends("protocol", "socks"); 296 | o.value(""); 297 | o.value("noauth", _("No Auth")); 298 | o.value("password", _("Password")); 299 | o.default = "noauth"; 300 | 301 | o = s.taboption( 302 | "general", 303 | form.Value, 304 | "s_socks_account_user", 305 | "%s - %s".format("Socks", _("Account user")) 306 | ); 307 | o.modalonly = true; 308 | o.depends("s_socks_auth", "password"); 309 | 310 | o = s.taboption( 311 | "general", 312 | form.Value, 313 | "s_socks_account_pass", 314 | "%s - %s".format("Socks", _("Account password")) 315 | ); 316 | o.modalonly = true; 317 | o.depends("s_socks_auth", "password"); 318 | o.password = true; 319 | 320 | o = s.taboption( 321 | "general", 322 | form.Flag, 323 | "s_socks_udp", 324 | "%s - %s".format("Socks", _("UDP")) 325 | ); 326 | o.modalonly = true; 327 | o.depends("protocol", "socks"); 328 | 329 | o = s.taboption( 330 | "general", 331 | form.Value, 332 | "s_socks_ip", 333 | "%s - %s".format("Socks", _("IP")), 334 | _( 335 | "When UDP is enabled, V2Ray needs to know the IP address of current host." 336 | ) 337 | ); 338 | o.modalonly = true; 339 | o.depends("s_socks_udp", "1"); 340 | for (const IP of localIPs) { 341 | o.value(IP); 342 | } 343 | o.datatype = "host"; 344 | o.placeholder = "127.0.0.1"; 345 | 346 | o = s.taboption( 347 | "general", 348 | form.Value, 349 | "s_socks_user_level", 350 | "%s - %s".format("Socks", _("User level")), 351 | _("All connections share this level") 352 | ); 353 | o.modalonly = true; 354 | o.depends("protocol", "socks"); 355 | o.datatype = "uinteger"; 356 | 357 | // Settings - VMess 358 | o = s.taboption( 359 | "general", 360 | form.Value, 361 | "s_vmess_client_id", 362 | "%s - %s".format("VMess", _("Client ID")) 363 | ); 364 | o.modalonly = true; 365 | o.depends("protocol", "vmess"); 366 | 367 | o = s.taboption( 368 | "general", 369 | form.Value, 370 | "s_vmess_client_alter_id", 371 | "%s - %s".format("VMess", _("Client alter ID")) 372 | ); 373 | o.modalonly = true; 374 | o.depends("protocol", "vmess"); 375 | o.datatype = "and(min(0), max(65535))"; 376 | 377 | o = s.taboption( 378 | "general", 379 | form.Value, 380 | "s_vmess_client_email", 381 | "%s - %s".format("VMess", _("Client email")) 382 | ); 383 | o.modalonly = true; 384 | o.depends("protocol", "vmess"); 385 | 386 | o = s.taboption( 387 | "general", 388 | form.Value, 389 | "s_vmess_client_user_level", 390 | "%s - %s".format("VMess", _("Client User level")) 391 | ); 392 | o.modalonly = true; 393 | o.depends("protocol", "vmess"); 394 | o.datatype = "uinteger"; 395 | 396 | o = s.taboption( 397 | "general", 398 | form.Value, 399 | "s_vmess_default_alter_id", 400 | "%s - %s".format("VMess", _("Default alter ID")) 401 | ); 402 | o.modalonly = true; 403 | o.depends("protocol", "vmess"); 404 | o.datatype = "and(min(0), max(65535))"; 405 | 406 | o = s.taboption( 407 | "general", 408 | form.Value, 409 | "s_vmess_default_user_level", 410 | "%s - %s".format("VMess", _("Default user level")) 411 | ); 412 | o.modalonly = true; 413 | o.depends("protocol", "vmess"); 414 | o.datatype = "uinteger"; 415 | 416 | o = s.taboption( 417 | "general", 418 | form.Value, 419 | "s_vmess_detour_to", 420 | "%s - %s".format("VMess", _("Detour to")), 421 | _( 422 | "Optional feature to suggest client to take a detour. If specified, this inbound will instruct the outbound to use another inbound." 423 | ) 424 | ); 425 | o.modalonly = true; 426 | o.depends("protocol", "vmess"); 427 | 428 | o = s.taboption( 429 | "general", 430 | form.Flag, 431 | "s_vmess_disable_insecure_encryption", 432 | "%s - %s".format("VMess", _("Disable insecure encryption")) 433 | ); 434 | o.modalonly = true; 435 | o.depends("protocol", "vmess"); 436 | 437 | /** Stream Settings **/ 438 | o = s.taboption("stream", form.ListValue, "ss_network", _("Network")); 439 | o.value(""); 440 | o.value("tcp", "TCP"); 441 | o.value("kcp", "mKCP"); 442 | o.value("ws", "WebSocket"); 443 | o.value("http", "HTTP/2"); 444 | o.value("domainsocket", "Domain Socket"); 445 | o.value("quic", "QUIC"); 446 | 447 | o = s.taboption("stream", form.ListValue, "ss_security", _("Security")); 448 | o.modalonly = true; 449 | o.value(""); 450 | o.value("none", _("None")); 451 | o.value("tls", "TLS"); 452 | 453 | // Stream Settings - TLS 454 | o = s.taboption( 455 | "stream", 456 | form.Value, 457 | "ss_tls_server_name", 458 | "%s - %s".format("TLS", _("Server name")) 459 | ); 460 | o.modalonly = true; 461 | o.depends("ss_security", "tls"); 462 | o.datatype = "host"; 463 | 464 | o = s.taboption( 465 | "stream", 466 | form.Value, 467 | "ss_tls_alpn", 468 | "%s - %s".format("TLS", "ALPN") 469 | ); 470 | o.modalonly = true; 471 | o.depends("ss_security", "tls"); 472 | o.placeholder = "http/1.1"; 473 | 474 | o = s.taboption( 475 | "stream", 476 | form.Flag, 477 | "ss_tls_allow_insecure", 478 | "%s - %s".format("TLS", _("Allow insecure")) 479 | ); 480 | o.modalonly = true; 481 | o.depends("ss_security", "tls"); 482 | 483 | o = s.taboption( 484 | "stream", 485 | form.Flag, 486 | "ss_tls_allow_insecure_ciphers", 487 | "%s - %s".format("TLS", _("Allow insecure ciphers")) 488 | ); 489 | o.modalonly = true; 490 | o.depends("ss_security", "tls"); 491 | 492 | o = s.taboption( 493 | "stream", 494 | form.Flag, 495 | "ss_tls_disable_system_root", 496 | "%s - %s".format("TLS", _("Disable system root")) 497 | ); 498 | o.modalonly = true; 499 | o.depends("ss_security", "tls"); 500 | 501 | o = s.taboption( 502 | "stream", 503 | form.ListValue, 504 | "ss_tls_cert_usage", 505 | "%s - %s".format("TLS", _("Certificate usage")) 506 | ); 507 | o.modalonly = true; 508 | o.depends("ss_security", "tls"); 509 | o.value(""); 510 | o.value("encipherment"); 511 | o.value("verify"); 512 | o.value("issue"); 513 | 514 | o = s.taboption( 515 | "stream", 516 | form.Value, 517 | "ss_tls_cert_fiile", 518 | "%s - %s".format("TLS", _("Certificate file")) 519 | ); 520 | o.modalonly = true; 521 | o.depends("ss_security", "tls"); 522 | 523 | o = s.taboption( 524 | "stream", 525 | form.Value, 526 | "ss_tls_key_file", 527 | "%s - %s".format("TLS", _("Key file")) 528 | ); 529 | o.modalonly = true; 530 | o.depends("ss_security", "tls"); 531 | 532 | // Stream Settings - TCP 533 | o = s.taboption( 534 | "stream", 535 | form.ListValue, 536 | "ss_tcp_header_type", 537 | "%s - %s".format("TCP", _("Header type")) 538 | ); 539 | o.modalonly = true; 540 | o.depends("ss_network", "tcp"); 541 | o.value(""); 542 | o.value("none", _("None")); 543 | o.value("http", "HTTP"); 544 | 545 | o = s.taboption( 546 | "stream", 547 | form.Value, 548 | "ss_tcp_header_request_version", 549 | "%s - %s".format("TCP", _("HTTP request version")) 550 | ); 551 | o.modalonly = true; 552 | o.depends("ss_tcp_header_type", "http"); 553 | o.placeholder = "1.1"; 554 | 555 | o = s.taboption( 556 | "stream", 557 | form.ListValue, 558 | "ss_tcp_header_request_method", 559 | "%s - %s".format("TCP", _("HTTP request method")) 560 | ); 561 | o.modalonly = true; 562 | o.depends("ss_tcp_header_type", "http"); 563 | o.value(""); 564 | o.value("GET"); 565 | o.value("HEAD"); 566 | o.value("POST"); 567 | o.value("DELETE"); 568 | o.value("PUT"); 569 | o.value("PATCH"); 570 | o.value("OPTIONS"); 571 | o.default = "GET"; 572 | 573 | o = s.taboption( 574 | "stream", 575 | form.Value, 576 | "ss_tcp_header_request_path", 577 | "%s - %s".format("TCP", _("Request path")) 578 | ); 579 | o.modalonly = true; 580 | o.depends("ss_tcp_header_type", "http"); 581 | 582 | o = s.taboption( 583 | "stream", 584 | form.DynamicList, 585 | "ss_tcp_header_request_headers", 586 | "%s - %s".format("TCP", _("Request headers")), 587 | _( 588 | "A list of HTTP headers, format: header=value. eg: %s" 589 | ).format("Host=www.bing.com") 590 | ); 591 | o.modalonly = true; 592 | o.depends("ss_tcp_header_type", "http"); 593 | 594 | o = s.taboption( 595 | "stream", 596 | form.Value, 597 | "ss_tcp_header_response_version", 598 | "%s - %s".format("TCP", _("HTTP response version")) 599 | ); 600 | o.modalonly = true; 601 | o.depends("ss_tcp_header_type", "http"); 602 | o.placeholder = "1.1"; 603 | 604 | o = s.taboption( 605 | "stream", 606 | form.Value, 607 | "ss_tcp_header_response_status", 608 | "%s - %s".format("TCP", _("HTTP response status")) 609 | ); 610 | o.modalonly = true; 611 | o.depends("ss_tcp_header_type", "http"); 612 | o.placeholder = "200"; 613 | 614 | o = s.taboption( 615 | "stream", 616 | form.Value, 617 | "ss_tcp_header_response_reason", 618 | "%s - %s".format("TCP", _("HTTP response reason")) 619 | ); 620 | o.modalonly = true; 621 | o.depends("ss_tcp_header_type", "http"); 622 | o.placeholder = "OK"; 623 | 624 | o = s.taboption( 625 | "stream", 626 | form.DynamicList, 627 | "ss_tcp_header_response_headers", 628 | "%s - %s".format("TCP", _("Response headers")), 629 | _( 630 | "A list of HTTP headers, format: header=value. eg: %s" 631 | ).format("Host=www.bing.com") 632 | ); 633 | o.modalonly = true; 634 | o.depends("ss_tcp_header_type", "http"); 635 | 636 | // Stream Settings - KCP 637 | o = s.taboption( 638 | "stream", 639 | form.Value, 640 | "ss_kcp_mtu", 641 | "%s - %s".format("mKCP", _("Maximum transmission unit (MTU)")) 642 | ); 643 | o.modalonly = true; 644 | o.depends("ss_network", "kcp"); 645 | o.datatype = "and(min(576), max(1460))"; 646 | o.placeholder = "1350"; 647 | 648 | o = s.taboption( 649 | "stream", 650 | form.Value, 651 | "ss_kcp_tti", 652 | "%s - %s".format("mKCP", _("Transmission time interval (TTI)")) 653 | ); 654 | o.modalonly = true; 655 | o.depends("ss_network", "kcp"); 656 | o.datatype = "and(min(10), max(100))"; 657 | o.placeholder = "50"; 658 | 659 | o = s.taboption( 660 | "stream", 661 | form.Value, 662 | "ss_kcp_uplink_capacity", 663 | "%s - %s".format("mKCP", _("Uplink capacity")) 664 | ); 665 | o.modalonly = true; 666 | o.depends("ss_network", "kcp"); 667 | o.datatype = "uinteger"; 668 | o.placeholder = "5"; 669 | 670 | o = s.taboption( 671 | "stream", 672 | form.Value, 673 | "ss_kcp_downlink_capacity", 674 | "%s - %s".format("mKCP", _("Downlink capacity")) 675 | ); 676 | o.modalonly = true; 677 | o.depends("ss_network", "kcp"); 678 | o.datatype = "uinteger"; 679 | o.placeholder = "20"; 680 | 681 | o = s.taboption( 682 | "stream", 683 | form.Flag, 684 | "ss_kcp_congestion", 685 | "%s - %s".format("mKCP", _("Congestion enabled")) 686 | ); 687 | o.modalonly = true; 688 | o.depends("ss_network", "kcp"); 689 | 690 | o = s.taboption( 691 | "stream", 692 | form.Value, 693 | "ss_kcp_read_buffer_size", 694 | "%s - %s".format("mKCP", _("Read buffer size")) 695 | ); 696 | o.modalonly = true; 697 | o.depends("ss_network", "kcp"); 698 | o.datatype = "uinteger"; 699 | o.placeholder = "2"; 700 | 701 | o = s.taboption( 702 | "stream", 703 | form.Value, 704 | "ss_kcp_write_buffer_size", 705 | "%s - %s".format("mKCP", _("Write buffer size")) 706 | ); 707 | o.modalonly = true; 708 | o.depends("ss_network", "kcp"); 709 | o.datatype = "uinteger"; 710 | o.placeholder = "2"; 711 | 712 | o = s.taboption( 713 | "stream", 714 | form.ListValue, 715 | "ss_kcp_header_type", 716 | "%s - %s".format("mKCP", _("Header type")) 717 | ); 718 | o.modalonly = true; 719 | o.depends("ss_network", "kcp"); 720 | o.value(""); 721 | o.value("none", _("None")); 722 | o.value("srtp", "SRTP"); 723 | o.value("utp", "uTP"); 724 | o.value("wechat-video", _("Wechat Video")); 725 | o.value("dtls", "DTLS 1.2"); 726 | o.value("wireguard", "WireGuard"); 727 | 728 | // Stream Settings - WebSocket 729 | o = s.taboption( 730 | "stream", 731 | form.Value, 732 | "ss_websocket_path", 733 | "%s - %s".format("WebSocket", _("Path")) 734 | ); 735 | o.modalonly = true; 736 | o.depends("ss_network", "ws"); 737 | 738 | o = s.taboption( 739 | "stream", 740 | form.DynamicList, 741 | "ss_websocket_headers", 742 | "%s - %s".format("WebSocket", _("Headers")), 743 | _( 744 | "A list of HTTP headers, format: header=value. eg: %s" 745 | ).format("Host=www.bing.com") 746 | ); 747 | o.modalonly = true; 748 | o.depends("ss_network", "ws"); 749 | 750 | // Stream Settings - HTTP/2 751 | o = s.taboption( 752 | "stream", 753 | form.DynamicList, 754 | "ss_http_host", 755 | "%s - %s".format("HTTP/2", _("Host")) 756 | ); 757 | o.modalonly = true; 758 | o.depends("ss_network", "http"); 759 | 760 | o = s.taboption( 761 | "stream", 762 | form.Value, 763 | "ss_http_path", 764 | "%s - %s".format("HTTP/2", _("Path")) 765 | ); 766 | o.modalonly = true; 767 | o.depends("ss_network", "http"); 768 | o.placeholder = "/"; 769 | 770 | // Stream Settings - Domain Socket 771 | o = s.taboption( 772 | "stream", 773 | form.Value, 774 | "ss_domainsocket_path", 775 | "%s - %s".format("Domain Socket", _("Path")) 776 | ); 777 | o.modalonly = true; 778 | o.depends("ss_network", "domainsocket"); 779 | 780 | // Stream Settings - QUIC 781 | o = s.taboption( 782 | "stream", 783 | form.ListValue, 784 | "ss_quic_security", 785 | "%s - %s".format("QUIC", _("Security")) 786 | ); 787 | o.modalonly = true; 788 | o.depends("ss_network", "quic"); 789 | o.value(""); 790 | o.value("none", _("None")); 791 | o.value("aes-128-gcm"); 792 | o.value("chacha20-poly1305"); 793 | 794 | o = s.taboption( 795 | "stream", 796 | form.Value, 797 | "ss_quic_key", 798 | "%s - %s".format("QUIC", _("Key")) 799 | ); 800 | o.modalonly = true; 801 | o.depends("ss_quic_security", "aes-128-gcm"); 802 | o.depends("ss_quic_security", "chacha20-poly1305"); 803 | 804 | o = s.taboption( 805 | "stream", 806 | form.ListValue, 807 | "ss_quic_header_type", 808 | "%s - %s".format("QUIC", _("Header type")) 809 | ); 810 | o.modalonly = true; 811 | o.depends("ss_network", "quic"); 812 | o.value(""); 813 | o.value("none", _("None")); 814 | o.value("srtp", "SRTP"); 815 | o.value("utp", "uTP"); 816 | o.value("wechat-video", _("Wechat Video")); 817 | o.value("dtls", "DTLS 1.2"); 818 | o.value("wireguard", "WireGuard"); 819 | 820 | // Stream Settings - Socket Options 821 | o = s.taboption( 822 | "stream", 823 | form.ListValue, 824 | "ss_sockopt_tcp_fast_open", 825 | "%s - %s".format(_("Sockopt"), _("TCP fast open")) 826 | ); 827 | o.modalonly = true; 828 | o.value(""); 829 | o.value("0", _("False")); 830 | o.value("1", _("True")); 831 | 832 | o = s.taboption( 833 | "stream", 834 | form.ListValue, 835 | "ss_sockopt_tproxy", 836 | "%s - %s".format(_("Sockopt"), _("TProxy")), 837 | _( 838 | "If transparent proxy enabled on current inbound, this option will be ignored." 839 | ) 840 | ); 841 | o.modalonly = true; 842 | o.value(""); 843 | o.value("redirect", "Redirect"); 844 | o.value("tproxy", "TProxy"); 845 | o.value("off", _("Off")); 846 | 847 | /** Other Settings **/ 848 | o = s.taboption("other", form.Value, "tag", _("Tag")); 849 | 850 | o = s.taboption( 851 | "other", 852 | form.Flag, 853 | "sniffing_enabled", 854 | "%s - %s".format(_("Sniffing"), _("Enabled")) 855 | ); 856 | o.modalonly = true; 857 | 858 | o = s.taboption( 859 | "other", 860 | form.MultiValue, 861 | "sniffing_dest_override", 862 | "%s - %s".format(_("Sniffing"), _("Dest override")) 863 | ); 864 | o.modalonly = true; 865 | o.value("http"); 866 | o.value("tls"); 867 | 868 | o = s.taboption( 869 | "other", 870 | form.ListValue, 871 | "allocate_strategy", 872 | "%s - %s".format(_("Allocate"), _("Strategy")) 873 | ); 874 | o.modalonly = true; 875 | o.value(""); 876 | o.value("always"); 877 | o.value("random"); 878 | 879 | o = s.taboption( 880 | "other", 881 | form.Value, 882 | "allocate_refresh", 883 | "%s - %s".format(_("Allocate"), _("Refresh")) 884 | ); 885 | o.modalonly = true; 886 | o.datatype = "uinteger"; 887 | 888 | o = s.taboption( 889 | "other", 890 | form.Value, 891 | "allocate_concurrency", 892 | "%s - %s".format(_("Allocate"), _("Concurrency")) 893 | ); 894 | o.modalonly = true; 895 | o.datatype = "uinteger"; 896 | 897 | return m.render(); 898 | }, 899 | }); 900 | -------------------------------------------------------------------------------- /src/view/v2ray/include/custom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | // "require baseclass"; 11 | // "require dom"; 12 | "require form"; 13 | "require fs"; 14 | // "require poll"; 15 | "require rpc"; 16 | "require uci"; 17 | "require ui"; 18 | 19 | type ListStatus = { 20 | count: number; 21 | datetime: string; 22 | }; 23 | 24 | const callRunningStatus = rpc.declare<{ code: number }>({ 25 | object: "luci.v2ray", 26 | method: "runningStatus", 27 | params: [], 28 | expect: { "": { code: 1 } }, 29 | }); 30 | 31 | const callListStatus = rpc.declare({ 32 | object: "luci.v2ray", 33 | method: "listStatus", 34 | params: ["name"], 35 | expect: { "": { code: 1 } }, 36 | filter: function (data: any): ListStatus { 37 | if (data.code === 0) { 38 | return { 39 | count: data.count, 40 | datetime: data.datetime, 41 | }; 42 | } 43 | return { 44 | count: 0, 45 | datetime: _("Unknown"), 46 | }; 47 | }, 48 | }); 49 | 50 | const callV2RayVersion = rpc.declare({ 51 | object: "luci.v2ray", 52 | method: "v2rayVersion", 53 | params: [], 54 | expect: { "": { code: 1 } }, 55 | filter: function (data: any): string { 56 | return data.code ? "" : data.version; 57 | }, 58 | }); 59 | 60 | const CUSTOMTextValue = form.TextValue.extend< 61 | CustomTextValueProperties, 62 | form.TextValue 63 | >({ 64 | __name__: "CUSTOM.TextValue", 65 | filepath: null, 66 | isjson: false, 67 | required: false, 68 | cfgvalue: function () { 69 | if (!this.filepath) { 70 | return this.super("cfgvalue", L.toArray(arguments)); 71 | } 72 | 73 | return L.resolveDefault(fs.read(this.filepath), ""); 74 | }, 75 | write: function (__: string, value: string) { 76 | if (!this.filepath) { 77 | return this.super("write", L.toArray(arguments)); 78 | } 79 | 80 | const trimmed = value.trim().replace(/\r\n/g, "\n") + "\n"; 81 | return fs.write(this.filepath, trimmed); 82 | }, 83 | validate: function (section_id: string, value: string): string | boolean { 84 | if (this.required && !value) { 85 | const title = this.titleFn("title", section_id); 86 | return _("%s is required.").format(title); 87 | } 88 | 89 | if (this.isjson) { 90 | let obj; 91 | try { 92 | obj = JSON.parse(value); 93 | } catch (e) { 94 | obj = null; 95 | } 96 | 97 | if (!obj || typeof obj !== "object") { 98 | return _("Invalid JSON content."); 99 | } 100 | } 101 | 102 | return true; 103 | }, 104 | }); 105 | 106 | const CUSTOMListStatusValue = form.AbstractValue.extend({ 107 | __name__: "CUSTOM.ListStatusValue", 108 | listtype: null, 109 | onupdate: null, 110 | btnstyle: "button", 111 | btntitle: null, 112 | cfgvalue: function () { 113 | if (!this.listtype) { 114 | L.error("TypeError", _("Listtype is required")); 115 | } 116 | 117 | return L.resolveDefault(callListStatus(this.listtype), { 118 | count: 0, 119 | datetime: _("Unknown"), 120 | }); 121 | }, 122 | render: function (option_index: number, section_id: string) { 123 | return Promise.resolve(this.cfgvalue(section_id)).then( 124 | L.bind(function ({ count = 0, datetime = "" } = {}) { 125 | const title = this.titleFn("title", section_id); 126 | 127 | const config_name = 128 | this.uciconfig || this.section.uciconfig || this.map.config; 129 | const depend_list = this.transformDepList(section_id); 130 | 131 | const fieldChildren: HTMLDivElement[] = [ 132 | E("div", {}, [ 133 | E( 134 | "span", 135 | { 136 | style: "color: #ff8c00;margin-right: 5px;", 137 | }, 138 | _("Total: %s").format(count) 139 | ), 140 | _("Time: %s").format(datetime), 141 | E( 142 | "button", 143 | { 144 | style: "margin-left: 10px;", 145 | class: "cbi-button cbi-button-%s".format( 146 | this.btnstyle || "button" 147 | ), 148 | click: ui.createHandlerFn( 149 | this, 150 | function ( 151 | section_id: string, 152 | listtype: string, 153 | ev: MouseEvent 154 | ) { 155 | if (typeof this.onupdate === "function") { 156 | return this.onupdate(ev, section_id, listtype); 157 | } 158 | }, 159 | section_id, 160 | this.listtype 161 | ), 162 | }, 163 | this.titleFn("btntitle", section_id) || title 164 | ), 165 | ]), 166 | ]; 167 | 168 | if (typeof this.description === "string" && this.description !== "") { 169 | fieldChildren.push( 170 | E("div", { class: "cbi-value-description" }, this.description) 171 | ); 172 | } 173 | 174 | const optionEl = E( 175 | "div", 176 | { 177 | class: "cbi-value", 178 | id: "cbi-%s-%s-%s".format(config_name, section_id, this.option), 179 | "data-index": option_index, 180 | "data-depends": depend_list, 181 | "data-field": this.cbid(section_id), 182 | "data-name": this.option, 183 | "data-widget": this.__name__, 184 | }, 185 | [ 186 | E( 187 | "label", 188 | { 189 | class: "cbi-value-title", 190 | for: "widget.cbid.%s.%s.%s".format( 191 | config_name, 192 | section_id, 193 | this.option 194 | ), 195 | }, 196 | [title] 197 | ), 198 | E("div", { class: "cbi-value-field" }, fieldChildren), 199 | ] 200 | ); 201 | 202 | if (depend_list && depend_list.length) { 203 | optionEl.classList.add("hidden"); 204 | } 205 | 206 | optionEl.addEventListener( 207 | "widget-change", 208 | L.bind(this.map.checkDepends, this.map) 209 | ); 210 | 211 | L.dom.bindClassInstance(optionEl, this); 212 | 213 | return optionEl; 214 | }, this) 215 | ); 216 | }, 217 | remove: function () {}, 218 | write: function () {}, 219 | }); 220 | 221 | const CUSTOMRunningStatus = form.AbstractValue.extend({ 222 | __name__: "CUSTOM.RunningStatus", 223 | fetchVersion: function (node: HTMLElement) { 224 | L.resolveDefault(callV2RayVersion(), "").then(function (version: string) { 225 | L.dom.content( 226 | node, 227 | version 228 | ? _("Version: %s").format(version) 229 | : E("em", { style: "color: red;" }, _("Unable to get V2Ray version.")) 230 | ); 231 | }); 232 | }, 233 | pollStatus: function (node: HTMLElement) { 234 | const notRunning = E("em", { style: "color: red;" }, _("Not Running")); 235 | const running = E("em", { style: "color: green;" }, _("Running")); 236 | 237 | L.Poll.add(function () { 238 | L.resolveDefault(callRunningStatus(), { code: 0 }).then(function (res) { 239 | L.dom.content(node, res.code ? notRunning : running); 240 | }); 241 | }, 5); 242 | }, 243 | load: function () {}, 244 | cfgvalue: function () {}, 245 | render: function () { 246 | const status = E( 247 | "span", 248 | { 249 | style: "margin-left: 5px", 250 | }, 251 | E("em", {}, _("Collecting data...")) 252 | ); 253 | 254 | const version = E("span", {}, _("Getting...")); 255 | 256 | this.pollStatus(status); 257 | this.fetchVersion(version); 258 | 259 | return E("div", { class: "cbi-value" }, [status, " / ", version]); 260 | }, 261 | remove: function () {}, 262 | write: function () {}, 263 | }); 264 | 265 | // @ts-ignore 266 | return L.Class.extend({ 267 | TextValue: CUSTOMTextValue, 268 | ListStatusValue: CUSTOMListStatusValue, 269 | RunningStatus: CUSTOMRunningStatus, 270 | }); 271 | -------------------------------------------------------------------------------- /src/view/v2ray/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require fs"; 12 | "require ui"; 13 | "require v2ray"; 14 | // "require view"; 15 | 16 | "require view/v2ray/include/custom as custom"; 17 | 18 | // @ts-ignore 19 | return L.view.extend({ 20 | handleServiceReload: function (ev: MouseEvent) { 21 | return fs 22 | .exec("/etc/init.d/v2ray", ["reload"]) 23 | .then( 24 | L.bind( 25 | function (btn, res) { 26 | if (res.code !== 0) { 27 | ui.addNotification(null, [ 28 | E( 29 | "p", 30 | _("Reload service failed with code %d").format(res.code) 31 | ), 32 | res.stderr ? E("pre", {}, [res.stderr]) : "", 33 | ]); 34 | L.raise("Error", "Reload failed"); 35 | } 36 | }, 37 | this, 38 | ev.target 39 | ) 40 | ) 41 | .catch(function (e: Error) { 42 | ui.addNotification(null, E("p", e.message)); 43 | }); 44 | }, 45 | load: function () { 46 | return Promise.all([ 47 | v2ray.getSections("inbound"), 48 | v2ray.getSections("outbound"), 49 | ]); 50 | }, 51 | render: function ([inboundSections = [], outBoundSections = []] = []) { 52 | const m = new form.Map( 53 | "v2ray", 54 | "%s - %s".format(_("V2ray"), _("Global Settings")), 55 | "

%s

%s

".format( 56 | _("A platform for building proxies to bypass network restrictions."), 57 | _("For more information, please visit: %s").format( 58 | 'https://www.v2ray.com' 59 | ) 60 | ) 61 | ); 62 | 63 | const s = m.section(form.NamedSection, "main", "v2ray"); 64 | s.addremove = false; 65 | s.anonymous = true; 66 | 67 | s.option(custom.RunningStatus, "_status"); 68 | 69 | let o; 70 | 71 | o = s.option(form.Flag, "enabled", _("Enabled")); 72 | o.rmempty = false; 73 | 74 | o = s.option( 75 | form.Button, 76 | "_reload", 77 | _("Reload Service"), 78 | _("This will restart service when config file changes.") 79 | ); 80 | o.inputstyle = "action reload"; 81 | o.inputtitle = _("Reload"); 82 | o.onclick = L.bind(this.handleServiceReload, this); 83 | 84 | o = s.option( 85 | form.Value, 86 | "v2ray_file", 87 | _("V2Ray file"), 88 | _("Set the V2Ray executable file path.") 89 | ); 90 | o.datatype = "file"; 91 | o.placeholder = "/usr/bin/v2ray"; 92 | o.rmempty = false; 93 | 94 | o = s.option( 95 | form.Value, 96 | "asset_location", 97 | _("V2Ray asset location"), 98 | _( 99 | "Directory where geoip.dat and geosite.dat files are, default: same directory as V2Ray file." 100 | ) 101 | ); 102 | o.datatype = "directory"; 103 | o.placeholder = "/usr/bin"; 104 | 105 | o = s.option( 106 | form.Value, 107 | "mem_percentage", 108 | _("Memory percentage"), 109 | _("The maximum percentage of memory used by V2Ray.") 110 | ); 111 | o.datatype = "and(uinteger, max(100))"; 112 | o.placeholder = "80"; 113 | 114 | o = s.option( 115 | form.Value, 116 | "config_file", 117 | _("Config file"), 118 | _("Use custom config file.") 119 | ); 120 | o.datatype = "file"; 121 | o.value("", _("None")); 122 | 123 | o = s.option(form.Value, "access_log", _("Access log file")); 124 | o.depends("config_file", ""); 125 | o.value("/dev/null"); 126 | o.value("/var/log/v2ray-access.log"); 127 | 128 | o = s.option(form.ListValue, "loglevel", _("Log level")); 129 | o.depends("config_file", ""); 130 | o.value("debug", _("Debug")); 131 | o.value("info", _("Info")); 132 | o.value("warning", _("Warning")); 133 | o.value("error", _("Error")); 134 | o.value("none", _("None")); 135 | o.default = "warning"; 136 | 137 | o = s.option(form.Value, "error_log", _("Error log file")); 138 | o.value("/dev/null"); 139 | o.value("/var/log/v2ray-error.log"); 140 | o.depends("loglevel", "debug"); 141 | o.depends("loglevel", "info"); 142 | o.depends("loglevel", "warning"); 143 | o.depends("loglevel", "error"); 144 | 145 | o = s.option(form.MultiValue, "inbounds", _("Inbounds enabled")); 146 | o.depends("config_file", ""); 147 | for (const s of inboundSections) { 148 | o.value(s.value, s.caption); 149 | } 150 | 151 | o = s.option(form.MultiValue, "outbounds", _("Outbounds enabled")); 152 | o.depends("config_file", ""); 153 | for (const s of outBoundSections) { 154 | o.value(s.value, s.caption); 155 | } 156 | 157 | o = s.option( 158 | form.Flag, 159 | "stats_enabled", 160 | "%s - %s".format(_("Stats"), _("Enabled")) 161 | ); 162 | o.depends("config_file", ""); 163 | 164 | o = s.option( 165 | form.Flag, 166 | "transport_enabled", 167 | "%s - %s".format(_("Transport"), _("Enabled")) 168 | ); 169 | o.depends("config_file", ""); 170 | 171 | o = s.option( 172 | custom.TextValue, 173 | "_transport", 174 | "%s - %s".format(_("Transport"), _("Settings")), 175 | _("transport field in top level configuration, JSON string") 176 | ); 177 | o.depends("transport_enabled", "1"); 178 | o.wrap = "off"; 179 | o.rows = 5; 180 | o.datatype = "string"; 181 | o.filepath = "/etc/v2ray/transport.json"; 182 | o.required = true; 183 | o.isjson = true; 184 | 185 | return m.render(); 186 | }, 187 | }); 188 | -------------------------------------------------------------------------------- /src/view/v2ray/outbound.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require uci"; 12 | "require v2ray"; 13 | // "require view"; 14 | "require ui"; 15 | 16 | "require view/v2ray/include/custom as custom"; 17 | "require view/v2ray/tools/converters as converters"; 18 | 19 | // @ts-ignore 20 | return L.view.extend({ 21 | handleImportSave: function (val: string) { 22 | const links = val.split(/\r?\n/); 23 | 24 | let linksCount = 0; 25 | for (const link of links) { 26 | let vmess; 27 | if ( 28 | !link || 29 | !(vmess = converters.vmessLinkToVmess(link)) || 30 | vmess.v !== "2" 31 | ) { 32 | continue; 33 | } 34 | 35 | const sid = uci.add("v2ray", "outbound"); 36 | if (!sid) continue; 37 | 38 | const address = vmess.add || "0.0.0.0"; 39 | const port = vmess.port || "0"; 40 | const tls = vmess.tls || ""; 41 | 42 | const network = vmess.net || ""; 43 | const headerType = vmess.type || ""; 44 | const path = vmess.path || ""; 45 | 46 | const alias = vmess.ps || "%s:%s".format(address, port); 47 | 48 | uci.set("v2ray", sid, "alias", alias); 49 | uci.set("v2ray", sid, "protocol", "vmess"); 50 | uci.set("v2ray", sid, "s_vmess_address", address); 51 | uci.set("v2ray", sid, "s_vmess_port", port); 52 | uci.set("v2ray", sid, "s_vmess_user_id", vmess.id || ""); 53 | uci.set("v2ray", sid, "s_vmess_user_alter_id", vmess.aid || ""); 54 | uci.set("v2ray", sid, "ss_security", tls); 55 | 56 | let hosts: string[] = []; 57 | if (vmess.host) { 58 | hosts = vmess.host.split(","); 59 | } 60 | 61 | switch (network) { 62 | case "tcp": { 63 | uci.set("v2ray", sid, "ss_network", "tcp"); 64 | uci.set("v2ray", sid, "ss_tcp_header_type", headerType); 65 | 66 | if (headerType === "http" && hosts.length > 0) { 67 | uci.set("v2ray", sid, "ss_tcp_header_request_headers", [ 68 | "Host=%s".format(hosts[0]), 69 | ]); 70 | 71 | if (tls === "tls") { 72 | uci.set("v2ray", sid, "ss_tls_server_name", hosts[0]); 73 | } 74 | } 75 | break; 76 | } 77 | 78 | case "kcp": 79 | case "mkcp": { 80 | uci.set("v2ray", sid, "ss_network", "kcp"); 81 | uci.set("v2ray", sid, "ss_kcp_header_type", headerType); 82 | break; 83 | } 84 | 85 | case "ws": { 86 | uci.set("v2ray", sid, "ss_network", "ws"); 87 | uci.set("v2ray", sid, "ss_websocket_path", path); 88 | break; 89 | } 90 | 91 | case "http": 92 | case "h2": { 93 | uci.set("v2ray", sid, "ss_network", "http"); 94 | uci.set("v2ray", sid, "ss_http_path", path); 95 | 96 | if (hosts.length > 0) { 97 | uci.set("v2ray", sid, "ss_http_host", hosts); 98 | uci.set("v2ray", sid, "ss_tls_server_name", hosts[0]); 99 | } 100 | break; 101 | } 102 | 103 | case "quic": { 104 | uci.set("v2ray", sid, "ss_network", "quic"); 105 | uci.set("v2ray", sid, "ss_quic_header_type", headerType); 106 | uci.set("v2ray", sid, "ss_quic_key", path); 107 | 108 | if (hosts.length > 0) { 109 | uci.set("v2ray", sid, "ss_quic_security", hosts[0]); 110 | 111 | if (tls === "tls") { 112 | uci.set("v2ray", sid, "ss_tls_server_name", hosts[0]); 113 | } 114 | } 115 | 116 | break; 117 | } 118 | 119 | default: { 120 | uci.remove("v2ray", sid); 121 | continue; 122 | } 123 | } 124 | 125 | linksCount++; 126 | } 127 | 128 | if (linksCount > 0) { 129 | return uci.save().then(function () { 130 | ui.showModal(_("Outbound Import"), [ 131 | E("p", {}, _("Imported %d links.").format(linksCount)), 132 | E( 133 | "div", 134 | { class: "right" }, 135 | E( 136 | "button", 137 | { 138 | class: "btn", 139 | click: ui.createHandlerFn(this, function () { 140 | return uci.apply().then(function () { 141 | ui.hideModal(); 142 | 143 | window.location.reload(); 144 | }); 145 | }), 146 | }, 147 | _("OK") 148 | ) 149 | ), 150 | ]); 151 | }); 152 | } else { 153 | ui.showModal(_("Outbound Import"), [ 154 | E("p", {}, _("No links imported.")), 155 | E( 156 | "div", 157 | { class: "right" }, 158 | E( 159 | "button", 160 | { 161 | class: "btn", 162 | click: ui.hideModal, 163 | }, 164 | _("OK") 165 | ) 166 | ), 167 | ]); 168 | } 169 | }, 170 | handleImportClick: function () { 171 | const textarea = new ui.Textarea("", { 172 | rows: 10, 173 | placeholder: _("You can add multiple links at once, one link per line."), 174 | validate: function (val: string) { 175 | if (!val) { 176 | return _("Empty field."); 177 | } 178 | 179 | if (!/^(vmess:\/\/[a-zA-Z0-9/+=]+\s*)+$/i.test(val)) { 180 | return _("Invalid links."); 181 | } 182 | 183 | return true; 184 | }, 185 | }); 186 | 187 | ui.showModal(_("Import Vmess Links"), [ 188 | E("div", {}, [ 189 | E( 190 | "p", 191 | {}, 192 | _("Allowed link format: %s").format("vmess://xxxxx") 193 | ), 194 | textarea.render(), 195 | ]), 196 | E("div", { class: "right" }, [ 197 | E( 198 | "button", 199 | { 200 | class: "btn", 201 | click: ui.hideModal, 202 | }, 203 | _("Dismiss") 204 | ), 205 | " ", 206 | E( 207 | "button", 208 | { 209 | class: "cbi-button cbi-button-positive important", 210 | click: ui.createHandlerFn( 211 | this, 212 | function (area: ui.Textarea) { 213 | area.triggerValidation(); 214 | 215 | let val: string; 216 | if ( 217 | !area.isValid() || 218 | !(val = area.getValue()) || 219 | !(val = val.trim()) 220 | ) { 221 | return; 222 | } 223 | 224 | return this.handleImportSave(val); 225 | }, 226 | textarea 227 | ), 228 | }, 229 | _("Save") 230 | ), 231 | ]), 232 | ]); 233 | }, 234 | load: function () { 235 | return v2ray.getLocalIPs(); 236 | }, 237 | render: function (localIPs: string[] = []) { 238 | const m = new form.Map( 239 | "v2ray", 240 | "%s - %s".format(_("V2Ray"), _("Outbound")) 241 | ); 242 | 243 | const s = m.section(form.GridSection, "outbound"); 244 | s.anonymous = true; 245 | s.addremove = true; 246 | s.sortable = true; 247 | s.modaltitle = function (section_id: string) { 248 | const alias = uci.get("v2ray", section_id, "alias"); 249 | return `${_("Outbound")} » ${alias ?? _("Add")}`; 250 | }; 251 | s.nodescriptions = true; 252 | 253 | s.tab("general", _("General Settings")); 254 | s.tab("stream", _("Stream Settings")); 255 | s.tab("other", _("Other Settings")); 256 | 257 | let o; 258 | 259 | /** General Settings **/ 260 | o = s.taboption("general", form.Value, "alias", _("Alias")); 261 | o.rmempty = false; 262 | 263 | o = s.taboption("general", form.Value, "send_through", _("Send through")); 264 | o.datatype = "ipaddr"; 265 | for (const IP of localIPs) { 266 | o.value(IP); 267 | } 268 | 269 | o = s.taboption("general", form.ListValue, "protocol", _("Protocol")); 270 | o.value("blackhole", "Blackhole"); 271 | o.value("dns", "DNS"); 272 | o.value("freedom", "Freedom"); 273 | o.value("http", "HTTP/2"); 274 | o.value("mtproto", "MTProto"); 275 | o.value("shadowsocks", "Shadowsocks"); 276 | o.value("socks", "Socks"); 277 | o.value("vmess", "VMess"); 278 | 279 | // Settings Blackhole 280 | o = s.taboption( 281 | "general", 282 | form.ListValue, 283 | "s_blackhole_reponse_type", 284 | "%s - %s".format("Blackhole", _("Response type")) 285 | ); 286 | o.modalonly = true; 287 | o.depends("protocol", "blackhole"); 288 | o.value(""); 289 | o.value("none", _("None")); 290 | o.value("http", "HTTP"); 291 | 292 | // Settings DNS 293 | o = s.taboption( 294 | "general", 295 | form.ListValue, 296 | "s_dns_network", 297 | "%s - %s".format("DNS", _("Network")) 298 | ); 299 | o.modalonly = true; 300 | o.depends("protocol", "dns"); 301 | o.value(""); 302 | o.value("tcp", "TCP"); 303 | o.value("udp", "UDP"); 304 | 305 | o = s.taboption( 306 | "general", 307 | form.Value, 308 | "s_dns_address", 309 | "%s - %s".format("DNS", _("Address")) 310 | ); 311 | o.modalonly = true; 312 | o.depends("protocol", "dns"); 313 | 314 | o = s.taboption( 315 | "general", 316 | form.Value, 317 | "s_dns_port", 318 | "%s - %s".format("DNS", _("Port")) 319 | ); 320 | o.modalonly = true; 321 | o.depends("protocol", "dns"); 322 | o.datatype = "port"; 323 | 324 | // Settings Freedom 325 | o = s.taboption( 326 | "general", 327 | form.ListValue, 328 | "s_freedom_domain_strategy", 329 | "%s - %s".format("Freedom", _("Domain strategy")) 330 | ); 331 | o.modalonly = true; 332 | o.depends("protocol", "freedom"); 333 | o.value(""); 334 | o.value("AsIs"); 335 | o.value("UseIP"); 336 | o.value("UseIPv4"); 337 | o.value("UseIPv6"); 338 | 339 | o = s.taboption( 340 | "general", 341 | form.Value, 342 | "s_freedom_redirect", 343 | "%s - %s".format("Freedom", _("Redirect")) 344 | ); 345 | o.modalonly = true; 346 | o.depends("protocol", "freedom"); 347 | 348 | o = s.taboption( 349 | "general", 350 | form.Value, 351 | "s_freedom_user_level", 352 | "%s - %s".format("Freedom", _("User level")) 353 | ); 354 | o.modalonly = true; 355 | o.depends("protocol", "freedom"); 356 | o.datatype = "uinteger"; 357 | 358 | // Settings - HTTP 359 | o = s.taboption( 360 | "general", 361 | form.Value, 362 | "s_http_server_address", 363 | "%s - %s".format("HTTP", _("Server address")) 364 | ); 365 | o.modalonly = true; 366 | o.depends("protocol", "http"); 367 | o.datatype = "host"; 368 | 369 | o = s.taboption( 370 | "general", 371 | form.Value, 372 | "s_http_server_port", 373 | "%s - %s".format("HTTP", _("Server port")) 374 | ); 375 | o.modalonly = true; 376 | o.depends("protocol", "http"); 377 | o.datatype = "port"; 378 | 379 | o = s.taboption( 380 | "general", 381 | form.Value, 382 | "s_http_account_user", 383 | "%s - %s".format("HTTP", _("User")) 384 | ); 385 | o.modalonly = true; 386 | o.depends("protocol", "http"); 387 | 388 | o = s.taboption( 389 | "general", 390 | form.Value, 391 | "s_http_account_pass", 392 | "%s - %s".format("HTTP", _("Password")) 393 | ); 394 | o.modalonly = true; 395 | o.depends("protocol", "http"); 396 | o.password = true; 397 | 398 | // Settings - Shadowsocks 399 | o = s.taboption( 400 | "general", 401 | form.Value, 402 | "s_shadowsocks_email", 403 | "%s - %s".format("Shadowsocks", _("Email")) 404 | ); 405 | o.modalonly = true; 406 | o.depends("protocol", "shadowsocks"); 407 | 408 | o = s.taboption( 409 | "general", 410 | form.Value, 411 | "s_shadowsocks_address", 412 | "%s - %s".format("Shadowsocks", _("Address")) 413 | ); 414 | o.modalonly = true; 415 | o.depends("protocol", "shadowsocks"); 416 | o.datatype = "host"; 417 | 418 | o = s.taboption( 419 | "general", 420 | form.Value, 421 | "s_shadowsocks_port", 422 | "%s - %s".format("Shadowsocks", _("Port")) 423 | ); 424 | o.modalonly = true; 425 | o.depends("protocol", "shadowsocks"); 426 | o.datatype = "port"; 427 | 428 | o = s.taboption( 429 | "general", 430 | form.ListValue, 431 | "s_shadowsocks_method", 432 | "%s - %s".format("Shadowsocks", _("Method")) 433 | ); 434 | o.modalonly = true; 435 | o.depends("protocol", "shadowsocks"); 436 | o.value(""); 437 | o.value("aes-256-cfb"); 438 | o.value("aes-128-cfb"); 439 | o.value("chacha20"); 440 | o.value("chacha20-ietf"); 441 | o.value("aes-256-gcm"); 442 | o.value("aes-128-gcm"); 443 | o.value("chacha20-poly1305"); 444 | o.value("chacha20-ietf-poly1305"); 445 | 446 | o = s.taboption( 447 | "general", 448 | form.Value, 449 | "s_shadowsocks_password", 450 | "%s - %s".format("Shadowsocks", _("Password")) 451 | ); 452 | o.modalonly = true; 453 | o.depends("protocol", "shadowsocks"); 454 | o.password = true; 455 | 456 | o = s.taboption( 457 | "general", 458 | form.Value, 459 | "s_shadowsocks_level", 460 | "%s - %s".format("Shadowsocks", _("User level")) 461 | ); 462 | o.modalonly = true; 463 | o.depends("protocol", "shadowsocks"); 464 | o.datatype = "uinteger"; 465 | 466 | o = s.taboption( 467 | "general", 468 | form.Flag, 469 | "s_shadowsocks_ota", 470 | "%s - %s".format("Shadowsocks", _("OTA")) 471 | ); 472 | o.modalonly = true; 473 | o.depends("protocol", "shadowsocks"); 474 | 475 | // Settings - Socks 476 | o = s.taboption( 477 | "general", 478 | form.Value, 479 | "s_socks_server_address", 480 | "%s - %s".format("Socks", _("Server address")) 481 | ); 482 | o.modalonly = true; 483 | o.depends("protocol", "socks"); 484 | o.datatype = "host"; 485 | 486 | o = s.taboption( 487 | "general", 488 | form.Value, 489 | "s_socks_server_port", 490 | "%s - %s".format("Socks", _("Server port")) 491 | ); 492 | o.modalonly = true; 493 | o.depends("protocol", "socks"); 494 | o.datatype = "port"; 495 | 496 | o = s.taboption( 497 | "general", 498 | form.Value, 499 | "s_socks_account_user", 500 | "%s - %s".format("Socks", _("User")) 501 | ); 502 | o.modalonly = true; 503 | o.depends("protocol", "socks"); 504 | 505 | o = s.taboption( 506 | "general", 507 | form.Value, 508 | "s_socks_account_pass", 509 | "%s - %s".format("Socks", _("Password")) 510 | ); 511 | o.modalonly = true; 512 | o.depends("protocol", "socks"); 513 | o.password = true; 514 | 515 | o = s.taboption( 516 | "general", 517 | form.Value, 518 | "s_socks_user_level", 519 | "%s - %s".format("Socks", _("User level")) 520 | ); 521 | o.modalonly = true; 522 | o.depends("protocol", "socks"); 523 | o.datatype = "uinteger"; 524 | 525 | // Settings - VMess 526 | o = s.taboption( 527 | "general", 528 | form.Value, 529 | "s_vmess_address", 530 | "%s - %s".format("VMess", _("Address")) 531 | ); 532 | o.modalonly = true; 533 | o.depends("protocol", "vmess"); 534 | o.datatype = "host"; 535 | 536 | o = s.taboption( 537 | "general", 538 | form.Value, 539 | "s_vmess_port", 540 | "%s - %s".format("VMess", _("Port")) 541 | ); 542 | o.modalonly = true; 543 | o.depends("protocol", "vmess"); 544 | o.datatype = "port"; 545 | 546 | o = s.taboption( 547 | "general", 548 | form.Value, 549 | "s_vmess_user_id", 550 | "%s - %s".format("VMess", _("User ID")) 551 | ); 552 | o.modalonly = true; 553 | o.depends("protocol", "vmess"); 554 | 555 | o = s.taboption( 556 | "general", 557 | form.Value, 558 | "s_vmess_user_alter_id", 559 | "%s - %s".format("VMess", _("Alter ID")) 560 | ); 561 | o.modalonly = true; 562 | o.depends("protocol", "vmess"); 563 | o.datatype = "and(uinteger, max(65535))"; 564 | 565 | o = s.taboption( 566 | "general", 567 | form.ListValue, 568 | "s_vmess_user_security", 569 | "%s - %s".format("VMess", _("Security")) 570 | ); 571 | o.modalonly = true; 572 | o.depends("protocol", "vmess"); 573 | o.value(""); 574 | o.value("auto", _("Auto")); 575 | o.value("aes-128-gcm"); 576 | o.value("chacha20-poly1305"); 577 | o.value("none", _("None")); 578 | 579 | o = s.taboption( 580 | "general", 581 | form.Value, 582 | "s_vmess_user_level", 583 | "%s - %s".format("VMess", _("User level")) 584 | ); 585 | o.modalonly = true; 586 | o.depends("protocol", "vmess"); 587 | o.datatype = "uinteger"; 588 | 589 | /** Stream Settings **/ 590 | o = s.taboption("stream", form.ListValue, "ss_network", _("Network")); 591 | o.value(""); 592 | o.value("tcp", "TCP"); 593 | o.value("kcp", "mKCP"); 594 | o.value("ws", "WebSocket"); 595 | o.value("http", "HTTP/2"); 596 | o.value("domainsocket", "Domain Socket"); 597 | o.value("quic", "QUIC"); 598 | 599 | o = s.taboption("stream", form.ListValue, "ss_security", _("Security")); 600 | o.modalonly = true; 601 | o.value(""); 602 | o.value("none", _("None")); 603 | o.value("tls", "TLS"); 604 | 605 | // Stream Settings - TLS 606 | o = s.taboption( 607 | "stream", 608 | form.Value, 609 | "ss_tls_server_name", 610 | "%s - %s".format("TLS", _("Server name")) 611 | ); 612 | o.modalonly = true; 613 | o.depends("ss_security", "tls"); 614 | 615 | o = s.taboption( 616 | "stream", 617 | form.Value, 618 | "ss_tls_alpn", 619 | "%s - %s".format("TLS", "ALPN") 620 | ); 621 | o.modalonly = true; 622 | o.depends("ss_security", "tls"); 623 | o.placeholder = "http/1.1"; 624 | 625 | o = s.taboption( 626 | "stream", 627 | form.Flag, 628 | "ss_tls_allow_insecure", 629 | "%s - %s".format("TLS", _("Allow insecure")) 630 | ); 631 | o.modalonly = true; 632 | o.depends("ss_security", "tls"); 633 | 634 | o = s.taboption( 635 | "stream", 636 | form.Flag, 637 | "ss_tls_allow_insecure_ciphers", 638 | "%s - %s".format("TLS", _("Allow insecure ciphers")) 639 | ); 640 | o.modalonly = true; 641 | o.depends("ss_security", "tls"); 642 | 643 | o = s.taboption( 644 | "stream", 645 | form.Flag, 646 | "ss_tls_disable_system_root", 647 | "%s - %s".format("TLS", _("Disable system root")) 648 | ); 649 | o.modalonly = true; 650 | o.depends("ss_security", "tls"); 651 | 652 | o = s.taboption( 653 | "stream", 654 | form.ListValue, 655 | "ss_tls_cert_usage", 656 | "%s - %s".format("TLS", _("Certificate usage")) 657 | ); 658 | o.modalonly = true; 659 | o.depends("ss_security", "tls"); 660 | o.value(""); 661 | o.value("encipherment"); 662 | o.value("verify"); 663 | o.value("issue"); 664 | 665 | o = s.taboption( 666 | "stream", 667 | form.Value, 668 | "ss_tls_cert_fiile", 669 | "%s - %s".format("TLS", _("Certificate file")) 670 | ); 671 | o.modalonly = true; 672 | o.depends("ss_security", "tls"); 673 | 674 | o = s.taboption( 675 | "stream", 676 | form.Value, 677 | "ss_tls_key_file", 678 | "%s - %s".format("TLS", _("Key file")) 679 | ); 680 | o.modalonly = true; 681 | o.depends("ss_security", "tls"); 682 | 683 | // Stream Settings - TCP 684 | o = s.taboption( 685 | "stream", 686 | form.ListValue, 687 | "ss_tcp_header_type", 688 | "%s - %s".format("TCP", _("Header type")) 689 | ); 690 | o.modalonly = true; 691 | o.depends("ss_network", "tcp"); 692 | o.value(""); 693 | o.value("none", _("None")); 694 | o.value("http", "HTTP"); 695 | 696 | o = s.taboption( 697 | "stream", 698 | form.Value, 699 | "ss_tcp_header_request_version", 700 | "%s - %s".format("TCP", _("HTTP request version")) 701 | ); 702 | o.modalonly = true; 703 | o.depends("ss_tcp_header_type", "http"); 704 | 705 | o = s.taboption( 706 | "stream", 707 | form.ListValue, 708 | "ss_tcp_header_request_method", 709 | "%s - %s".format("TCP", _("HTTP request method")) 710 | ); 711 | o.modalonly = true; 712 | o.depends("ss_tcp_header_type", "http"); 713 | o.value(""); 714 | o.value("GET"); 715 | o.value("HEAD"); 716 | o.value("POST"); 717 | o.value("DELETE"); 718 | o.value("PUT"); 719 | o.value("PATCH"); 720 | o.value("OPTIONS"); 721 | 722 | o = s.taboption( 723 | "stream", 724 | form.Value, 725 | "ss_tcp_header_request_path", 726 | "%s - %s".format("TCP", _("Request path")) 727 | ); 728 | o.modalonly = true; 729 | o.depends("ss_tcp_header_type", "http"); 730 | 731 | o = s.taboption( 732 | "stream", 733 | form.DynamicList, 734 | "ss_tcp_header_request_headers", 735 | "%s - %s".format("TCP", _("Request headers")), 736 | _( 737 | "A list of HTTP headers, format: header=value. eg: %s" 738 | ).format("Host=www.bing.com") 739 | ); 740 | o.modalonly = true; 741 | o.depends("ss_tcp_header_type", "http"); 742 | 743 | o = s.taboption( 744 | "stream", 745 | form.Value, 746 | "ss_tcp_header_response_version", 747 | "%s - %s".format("TCP", _("HTTP response version")) 748 | ); 749 | o.modalonly = true; 750 | o.depends("ss_tcp_header_type", "http"); 751 | 752 | o = s.taboption( 753 | "stream", 754 | form.Value, 755 | "ss_tcp_header_response_status", 756 | "%s - %s".format("TCP", _("HTTP response status")) 757 | ); 758 | o.modalonly = true; 759 | o.depends("ss_tcp_header_type", "http"); 760 | 761 | o = s.taboption( 762 | "stream", 763 | form.Value, 764 | "ss_tcp_header_response_reason", 765 | "%s - %s".format("TCP", _("HTTP response reason")) 766 | ); 767 | o.modalonly = true; 768 | o.depends("ss_tcp_header_type", "http"); 769 | 770 | o = s.taboption( 771 | "stream", 772 | form.DynamicList, 773 | "ss_tcp_header_response_headers", 774 | "%s - %s".format("TCP", _("Response headers")), 775 | _( 776 | "A list of HTTP headers, format: header=value. eg: %s" 777 | ).format("Host=www.bing.com") 778 | ); 779 | o.modalonly = true; 780 | o.depends("ss_tcp_header_type", "http"); 781 | 782 | // Stream Settings - KCP 783 | o = s.taboption( 784 | "stream", 785 | form.Value, 786 | "ss_kcp_mtu", 787 | "%s - %s".format("mKCP", _("Maximum transmission unit (MTU)")) 788 | ); 789 | o.modalonly = true; 790 | o.depends("ss_network", "kcp"); 791 | o.datatype = "and(min(576), max(1460))"; 792 | o.placeholder = "1350"; 793 | 794 | o = s.taboption( 795 | "stream", 796 | form.Value, 797 | "ss_kcp_tti", 798 | "%s - %s".format("mKCP", _("Transmission time interval (TTI)")) 799 | ); 800 | o.modalonly = true; 801 | o.depends("ss_network", "kcp"); 802 | o.datatype = "and(min(10), max(100))"; 803 | o.placeholder = "50"; 804 | 805 | o = s.taboption( 806 | "stream", 807 | form.Value, 808 | "ss_kcp_uplink_capacity", 809 | "%s - %s".format("mKCP", _("Uplink capacity")) 810 | ); 811 | o.modalonly = true; 812 | o.depends("ss_network", "kcp"); 813 | o.datatype = "uinteger"; 814 | o.placeholder = "5"; 815 | 816 | o = s.taboption( 817 | "stream", 818 | form.Value, 819 | "ss_kcp_downlink_capacity", 820 | "%s - %s".format("mKCP", _("Downlink capacity")) 821 | ); 822 | o.modalonly = true; 823 | o.depends("ss_network", "kcp"); 824 | o.datatype = "uinteger"; 825 | o.placeholder = "20"; 826 | 827 | o = s.taboption( 828 | "stream", 829 | form.Flag, 830 | "ss_kcp_congestion", 831 | "%s - %s".format("mKCP", _("Congestion enabled")) 832 | ); 833 | o.modalonly = true; 834 | o.depends("ss_network", "kcp"); 835 | 836 | o = s.taboption( 837 | "stream", 838 | form.Value, 839 | "ss_kcp_read_buffer_size", 840 | "%s - %s".format("mKCP", _("Read buffer size")) 841 | ); 842 | o.modalonly = true; 843 | o.depends("ss_network", "kcp"); 844 | o.datatype = "uinteger"; 845 | o.placeholder = "2"; 846 | 847 | o = s.taboption( 848 | "stream", 849 | form.Value, 850 | "ss_kcp_write_buffer_size", 851 | "%s - %s".format("mKCP", _("Write buffer size")) 852 | ); 853 | o.modalonly = true; 854 | o.depends("ss_network", "kcp"); 855 | o.datatype = "uinteger"; 856 | o.placeholder = "2"; 857 | 858 | o = s.taboption( 859 | "stream", 860 | form.ListValue, 861 | "ss_kcp_header_type", 862 | "%s - %s".format("mKCP", _("Header type")) 863 | ); 864 | o.modalonly = true; 865 | o.depends("ss_network", "kcp"); 866 | o.value(""); 867 | o.value("none", _("None")); 868 | o.value("srtp", "SRTP"); 869 | o.value("utp", "uTP"); 870 | o.value("wechat-video", _("Wechat Video")); 871 | o.value("dtls", "DTLS 1.2"); 872 | o.value("wireguard", "WireGuard"); 873 | 874 | // Stream Settings - WebSocket 875 | o = s.taboption( 876 | "stream", 877 | form.Value, 878 | "ss_websocket_path", 879 | "%s - %s".format("WebSocket", _("Path")) 880 | ); 881 | o.modalonly = true; 882 | o.depends("ss_network", "ws"); 883 | 884 | o = s.taboption( 885 | "stream", 886 | form.DynamicList, 887 | "ss_websocket_headers", 888 | "%s - %s".format("WebSocket", _("Headers")), 889 | _( 890 | "A list of HTTP headers, format: header=value. eg: %s" 891 | ).format("Host=www.bing.com") 892 | ); 893 | o.modalonly = true; 894 | o.depends("ss_network", "ws"); 895 | 896 | // Stream Settings - HTTP/2 897 | o = s.taboption( 898 | "stream", 899 | form.DynamicList, 900 | "ss_http_host", 901 | "%s - %s".format("HTTP/2", _("Host")) 902 | ); 903 | o.modalonly = true; 904 | o.depends("ss_network", "http"); 905 | 906 | o = s.taboption( 907 | "stream", 908 | form.Value, 909 | "ss_http_path", 910 | "%s - %s".format("HTTP/2", _("Path")) 911 | ); 912 | o.modalonly = true; 913 | o.depends("ss_network", "http"); 914 | o.placeholder = "/"; 915 | 916 | // Stream Settings - Domain Socket 917 | o = s.taboption( 918 | "stream", 919 | form.Value, 920 | "ss_domainsocket_path", 921 | "%s - %s".format("Domain Socket", _("Path")) 922 | ); 923 | o.modalonly = true; 924 | o.depends("ss_network", "domainsocket"); 925 | 926 | // Stream Settings - QUIC 927 | o = s.taboption( 928 | "stream", 929 | form.ListValue, 930 | "ss_quic_security", 931 | "%s - %s".format("QUIC", _("Security")) 932 | ); 933 | o.modalonly = true; 934 | o.depends("ss_network", "quic"); 935 | o.value(""); 936 | o.value("none", _("None")); 937 | o.value("aes-128-gcm"); 938 | o.value("chacha20-poly1305"); 939 | 940 | o = s.taboption( 941 | "stream", 942 | form.Value, 943 | "ss_quic_key", 944 | "%s - %s".format("QUIC", _("Key")) 945 | ); 946 | o.modalonly = true; 947 | o.depends("ss_quic_security", "aes-128-gcm"); 948 | o.depends("ss_quic_security", "chacha20-poly1305"); 949 | 950 | o = s.taboption( 951 | "stream", 952 | form.ListValue, 953 | "ss_quic_header_type", 954 | "%s - %s".format("QUIC", _("Header type")) 955 | ); 956 | o.modalonly = true; 957 | o.depends("ss_network", "quic"); 958 | o.value(""); 959 | o.value("none", _("None")); 960 | o.value("srtp", "SRTP"); 961 | o.value("utp", "uTP"); 962 | o.value("wechat-video", _("Wechat Video")); 963 | o.value("dtls", "DTLS 1.2"); 964 | o.value("wireguard", "WireGuard"); 965 | 966 | // Stream Settings - Socket Options 967 | o = s.taboption( 968 | "stream", 969 | form.Value, 970 | "ss_sockopt_mark", 971 | "%s - %s".format(_("Sockopt"), _("Mark")), 972 | _( 973 | "If transparent proxy is enabled, this option is ignored and will be set to 255." 974 | ) 975 | ); 976 | o.modalonly = true; 977 | o.placeholder = "255"; 978 | 979 | o = s.taboption( 980 | "stream", 981 | form.ListValue, 982 | "ss_sockopt_tcp_fast_open", 983 | "%s - %s".format(_("Sockopt"), _("TCP fast open")) 984 | ); 985 | o.modalonly = true; 986 | o.value(""); 987 | o.value("0", _("False")); 988 | o.value("1", _("True")); 989 | 990 | /** Other Settings **/ 991 | o = s.taboption("general", form.Value, "tag", _("Tag")); 992 | 993 | o = s.taboption( 994 | "general", 995 | form.Value, 996 | "proxy_settings_tag", 997 | "%s - %s".format(_("Proxy settings"), _("Tag")) 998 | ); 999 | o.modalonly = true; 1000 | o = s.taboption( 1001 | "other", 1002 | form.Flag, 1003 | "mux_enabled", 1004 | "%s - %s".format(_("Mux"), _("Enabled")) 1005 | ); 1006 | o.modalonly = true; 1007 | 1008 | o = s.taboption( 1009 | "other", 1010 | form.Value, 1011 | "mux_concurrency", 1012 | "%s - %s".format(_("Mux"), _("Concurrency")) 1013 | ); 1014 | o.modalonly = true; 1015 | o.datatype = "uinteger"; 1016 | o.placeholder = "8"; 1017 | 1018 | const self = this; 1019 | return m.render().then(function (node: Node) { 1020 | const container = m.findElement("id", "cbi-v2ray-outbound"); 1021 | 1022 | const importButton = E( 1023 | "div", 1024 | { 1025 | class: "cbi-section-create cbi-tblsection-create", 1026 | }, 1027 | E( 1028 | "button", 1029 | { 1030 | class: "cbi-button cbi-button-neutral", 1031 | title: _("Import"), 1032 | click: L.bind(self.handleImportClick, self), 1033 | }, 1034 | _("Import") 1035 | ) 1036 | ); 1037 | 1038 | L.dom.append(container, importButton); 1039 | 1040 | return node; 1041 | }); 1042 | }, 1043 | }); 1044 | -------------------------------------------------------------------------------- /src/view/v2ray/policy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require uci"; 12 | "require v2ray"; 13 | // "require view"; 14 | 15 | // @ts-ignore 16 | return L.view.extend({ 17 | load: function () { 18 | return v2ray.getSections("policy_level", "level"); 19 | }, 20 | render: function (policyLevels = []) { 21 | const m = new form.Map( 22 | "v2ray", 23 | "%s - %s".format(_("V2Ray"), _("Policy")), 24 | _("Details: %s").format( 25 | 'PolicyObject' 26 | ) 27 | ); 28 | 29 | const s1 = m.section(form.NamedSection, "main_policy", "policy"); 30 | s1.anonymous = true; 31 | s1.addremove = false; 32 | 33 | let o; 34 | o = s1.option(form.Flag, "enabled", _("Enabled")); 35 | o.rmempty = false; 36 | 37 | o = s1.option( 38 | form.MultiValue, 39 | "levels", 40 | _("Levels"), 41 | _("Select policy levels") 42 | ); 43 | for (const s of policyLevels) { 44 | o.value(s.value, s.caption); 45 | } 46 | 47 | o = s1.option( 48 | form.Flag, 49 | "system_stats_inbound_uplink", 50 | "%s - %s".format(_("System"), _("Stats inbound uplink")) 51 | ); 52 | 53 | o = s1.option( 54 | form.Flag, 55 | "system_stats_inbound_downlink", 56 | "%s - %s".format(_("System"), _("Stats inbound downlink")) 57 | ); 58 | 59 | const s2 = m.section( 60 | form.GridSection, 61 | "policy_level", 62 | _("Policy Level"), 63 | _("Add policy levels here") 64 | ); 65 | s2.anonymous = true; 66 | s2.addremove = true; 67 | s2.sortable = true; 68 | s2.nodescription = true; 69 | 70 | o = s2.option(form.Value, "level", _("Level")); 71 | o.rmempty = false; 72 | o.datatype = "uinteger"; 73 | 74 | o = s2.option(form.Value, "handshake", _("Handshake")); 75 | o.datatype = "uinteger"; 76 | o.placeholder = "4"; 77 | 78 | o = s2.option(form.Value, "conn_idle", _("Connection idle")); 79 | o.datatype = "uinteger"; 80 | o.placeholder = "300"; 81 | 82 | o = s2.option(form.Value, "uplink_only", _("Uplink only")); 83 | o.modalonly = true; 84 | o.datatype = "uinteger"; 85 | o.placeholder = "2"; 86 | 87 | o = s2.option(form.Value, "downlink_only", _("Downlink only")); 88 | o.modalonly = true; 89 | o.datatype = "uinteger"; 90 | o.placeholder = "5"; 91 | 92 | o = s2.option(form.Flag, "stats_user_uplink", _("Stats user uplink")); 93 | o.modalonly = true; 94 | 95 | o = s2.option(form.Flag, "stats_user_downlink", _("Stats user downlink")); 96 | o.modalonly = true; 97 | 98 | o = s2.option(form.Value, "buffer_size", _("Buffer size")); 99 | o.datatype = "uinteger"; 100 | 101 | return m.render(); 102 | }, 103 | }); 104 | -------------------------------------------------------------------------------- /src/view/v2ray/reverse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | // "require view"; 12 | 13 | // @ts-ignore 14 | return L.view.extend({ 15 | render: function () { 16 | const m = new form.Map( 17 | "v2ray", 18 | "%s - %s".format(_("V2Ray"), _("Reverse")), 19 | _("Details: %s").format( 20 | 'ReverseObject' 21 | ) 22 | ); 23 | 24 | const s = m.section(form.NamedSection, "main_reverse", "reverse"); 25 | s.addremove = false; 26 | 27 | let o; 28 | o = s.option(form.Flag, "enabled", _("Enabled")); 29 | o.rmempty = false; 30 | 31 | o = s.option( 32 | form.DynamicList, 33 | "bridges", 34 | _("Bridges"), 35 | _("A list of bridges, format: tag|domain. eg: %s").format( 36 | "bridge|test.v2ray.com" 37 | ) 38 | ); 39 | 40 | o = s.option( 41 | form.DynamicList, 42 | "portals", 43 | _("Portals"), 44 | _("A list of portals, format: tag|domain. eg: %s").format( 45 | "portal|test.v2ray.com" 46 | ) 47 | ); 48 | 49 | return m.render(); 50 | }, 51 | }); 52 | -------------------------------------------------------------------------------- /src/view/v2ray/routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require uci"; 12 | "require v2ray"; 13 | // "require view"; 14 | 15 | // @ts-ignore 16 | return L.view.extend({ 17 | load: function () { 18 | return Promise.all([ 19 | v2ray.getSections("routing_rule"), 20 | v2ray.getSections("routing_balancer", "tag"), 21 | ]); 22 | }, 23 | render: function ([routingRules = [], routingBalancers = []] = []) { 24 | const m = new form.Map( 25 | "v2ray", 26 | "%s - %s".format(_("V2Ray"), _("Routing")), 27 | _("Details: %s").format( 28 | 'RoutingObject' 29 | ) 30 | ); 31 | 32 | const s1 = m.section(form.NamedSection, "main_routing", "routing"); 33 | s1.anonymous = true; 34 | s1.addremove = false; 35 | 36 | let o; 37 | o = s1.option(form.Flag, "enabled", _("Enabled")); 38 | 39 | o = s1.option( 40 | form.ListValue, 41 | "domain_strategy", 42 | _("Domain resolution strategy") 43 | ); 44 | o.value(""); 45 | o.value("AsIs"); 46 | o.value("IPIfNonMatch"); 47 | o.value("IPOnDemand"); 48 | 49 | o = s1.option( 50 | form.MultiValue, 51 | "rules", 52 | _("Rules"), 53 | _("Select routing rules to use") 54 | ); 55 | for (const s of routingRules) { 56 | o.value(s.value, s.caption); 57 | } 58 | 59 | o = s1.option( 60 | form.MultiValue, 61 | "balancers", 62 | _("Balancers"), 63 | _("Select routing balancers to use") 64 | ); 65 | for (const s of routingBalancers) { 66 | o.value(s.value, s.caption); 67 | } 68 | 69 | const s2 = m.section( 70 | form.GridSection, 71 | "routing_rule", 72 | _("Routing Rule"), 73 | _("Add routing rules here") 74 | ); 75 | s2.anonymous = true; 76 | s2.addremove = true; 77 | s2.sortable = true; 78 | s2.nodescription = true; 79 | 80 | o = s2.option(form.Value, "alias", _("Alias")); 81 | o.rmempty = false; 82 | 83 | o = s2.option(form.ListValue, "type", _("Type")); 84 | o.value("field"); 85 | 86 | o = s2.option(form.DynamicList, "domain", _("Domain")); 87 | o.modalonly = true; 88 | 89 | o = s2.option(form.DynamicList, "ip", _("IP")); 90 | o.modalonly = true; 91 | 92 | o = s2.option(form.DynamicList, "port", _("Port")); 93 | o.modalonly = true; 94 | o.datatype = "or(port, portrange)"; 95 | 96 | o = s2.option(form.MultiValue, "network", _("Network")); 97 | o.value("tcp"); 98 | o.value("udp"); 99 | 100 | o = s2.option(form.DynamicList, "source", _("Source")); 101 | o.modalonly = true; 102 | 103 | o = s2.option(form.DynamicList, "user", _("User")); 104 | o.modalonly = true; 105 | 106 | o = s2.option(form.DynamicList, "inbound_tag", _("Inbound tag")); 107 | 108 | o = s2.option(form.MultiValue, "protocol", _("Protocol")); 109 | o.modalonly = true; 110 | o.value("http"); 111 | o.value("tls"); 112 | o.value("bittorrent"); 113 | 114 | o = s2.option(form.Value, "attrs", _("Attrs")); 115 | o.modalonly = true; 116 | 117 | o = s2.option(form.Value, "outbound_tag", _("Outbound tag")); 118 | 119 | o = s2.option(form.Value, "balancer_tag", _("Balancer tag")); 120 | o.modalonly = true; 121 | o.depends("outbound_tag", ""); 122 | 123 | const s3 = m.section( 124 | form.TypedSection, 125 | "routing_balancer", 126 | _("Routing Balancer", _("Add routing balancers here")) 127 | ); 128 | s3.anonymous = true; 129 | s3.addremove = true; 130 | 131 | o = s3.option(form.Value, "tag", _("Tag")); 132 | o.rmempty = false; 133 | 134 | o = s3.option(form.DynamicList, "selector", _("Selector")); 135 | 136 | return m.render(); 137 | }, 138 | }); 139 | -------------------------------------------------------------------------------- /src/view/v2ray/tools/base64.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 11 | const b64re = /^(?:[A-Za-z\d+\\/]{4})*?(?:[A-Za-z\d+\\/]{2}(?:==)?|[A-Za-z\d+\\/]{3}=?)?$/; 12 | 13 | // @ts-ignore 14 | return L.Class.extend({ 15 | decode: function (encoded: string) { 16 | if (typeof atob === "function") { 17 | return atob(encoded); 18 | } 19 | 20 | // atob can work with strings with whitespaces, even inside the encoded part, 21 | // but only \t, \n, \f, \r and ' ', which can be stripped. 22 | encoded = String(encoded).replace(/[\t\n\f\r ]+/g, ""); 23 | if (!b64re.test(encoded)) 24 | throw new TypeError( 25 | "Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded." 26 | ); 27 | 28 | // Adding the padding if missing, for semplicity 29 | encoded += "==".slice(2 - (encoded.length & 3)); 30 | let bitmap, 31 | result = "", 32 | r1, 33 | r2, 34 | i = 0; 35 | for (; i < encoded.length; ) { 36 | bitmap = 37 | (b64.indexOf(encoded.charAt(i++)) << 18) | 38 | (b64.indexOf(encoded.charAt(i++)) << 12) | 39 | ((r1 = b64.indexOf(encoded.charAt(i++))) << 6) | 40 | (r2 = b64.indexOf(encoded.charAt(i++))); 41 | 42 | result += 43 | r1 === 64 44 | ? String.fromCharCode((bitmap >> 16) & 255) 45 | : r2 === 64 46 | ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255) 47 | : String.fromCharCode( 48 | (bitmap >> 16) & 255, 49 | (bitmap >> 8) & 255, 50 | bitmap & 255 51 | ); 52 | } 53 | return result; 54 | }, 55 | 56 | encode: function (str: string) { 57 | if (typeof btoa === "function") { 58 | return btoa(str); 59 | } 60 | 61 | str = String(str); 62 | 63 | let bitmap, 64 | a, 65 | b, 66 | c, 67 | result = "", 68 | i = 0; 69 | const rest = str.length % 3; // To determine the final padding 70 | 71 | for (; i < str.length; ) { 72 | if ( 73 | (a = str.charCodeAt(i++)) > 255 || 74 | (b = str.charCodeAt(i++)) > 255 || 75 | (c = str.charCodeAt(i++)) > 255 76 | ) 77 | throw new TypeError( 78 | "Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range." 79 | ); 80 | 81 | bitmap = (a << 16) | (b << 8) | c; 82 | result += 83 | b64.charAt((bitmap >> 18) & 63) + 84 | b64.charAt((bitmap >> 12) & 63) + 85 | b64.charAt((bitmap >> 6) & 63) + 86 | b64.charAt(bitmap & 63); 87 | } 88 | 89 | // If there's need of padding, replace the last 'A's with equal signs 90 | return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result; 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /src/view/v2ray/tools/converters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require view/v2ray/tools/base64 as base64"; 11 | 12 | // @ts-ignore 13 | return L.Class.extend({ 14 | extractGFWList: function (gfwlist: string): string { 15 | let decoded: string; 16 | try { 17 | decoded = base64.decode(gfwlist.replace(/\r?\n/g, "")); 18 | } catch (e) { 19 | decoded = ""; 20 | } 21 | 22 | if (!decoded) return ""; 23 | 24 | const gfwlistLines = decoded.split(/\r?\n/); 25 | 26 | const domainList: { [key: string]: boolean } = Object.create(null); 27 | 28 | for (const line of gfwlistLines) { 29 | if (!line || /^[![@]/.test(line) || /(\d+\.){3}\d+/.test(line)) { 30 | continue; 31 | } 32 | 33 | const matches = line.match(/\w[\w-]*\.\w[\w\-.]+/); 34 | 35 | let domain: string; 36 | if (matches && (domain = matches[0])) { 37 | domainList[domain] = true; 38 | } 39 | } 40 | 41 | return Object.keys(domainList).sort().join("\n") + "\n"; 42 | }, 43 | 44 | extractCHNRoute: function ( 45 | delegatedlist: string, 46 | ipv6: boolean = false 47 | ): string { 48 | const delegatedLines = delegatedlist.split(/\r?\n/); 49 | 50 | const ipList: string[] = []; 51 | 52 | const regex = ipv6 53 | ? /CN\|ipv6\|([0-9a-zA-Z:]+)\|(\d+)/ 54 | : /CN\|ipv4\|([\d.]+)\|(\d+)/; 55 | 56 | for (const line of delegatedLines) { 57 | if (!line || line.indexOf("#") === 0) { 58 | continue; 59 | } 60 | 61 | const matches = line.match(regex); 62 | if (matches && matches.length >= 3) { 63 | const [, ip, value] = matches; 64 | 65 | if (ipv6) { 66 | ipList.push(`${ip}/${value}`); 67 | } else { 68 | // base log 69 | const mask = 32 - Math.log(+value) / Math.log(2); 70 | 71 | ipList.push(`${ip}/${mask}`); 72 | } 73 | } 74 | } 75 | 76 | return ipList.join("\n") + "\n"; 77 | }, 78 | 79 | vmessLinkToVmess(link: string): Vmess | null { 80 | let matches; 81 | if ( 82 | !link || 83 | !(link = link.trim()) || 84 | !(matches = link.match(/^vmess:\/\/([a-zA-Z0-9/+]+={0,2})$/i)) || 85 | matches.length < 2 86 | ) { 87 | return null; 88 | } 89 | 90 | let decoded: string; 91 | try { 92 | decoded = base64.decode(matches[1]); 93 | } catch (e) { 94 | decoded = ""; 95 | } 96 | 97 | if (!decoded) return null; 98 | 99 | let vmess: Vmess | null; 100 | try { 101 | vmess = JSON.parse(decoded); 102 | } catch (e) { 103 | vmess = null; 104 | } 105 | 106 | return vmess; 107 | }, 108 | }); 109 | -------------------------------------------------------------------------------- /src/view/v2ray/transparent-proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2020 Xingwang Liao 4 | * 5 | * Licensed to the public under the MIT License. 6 | */ 7 | 8 | "use strict"; 9 | 10 | "require form"; 11 | "require fs"; 12 | // "require request"; 13 | "require rpc"; 14 | "require uci"; 15 | "require ui"; 16 | "require v2ray"; 17 | // "require view"; 18 | 19 | "require tools/widgets as widgets"; 20 | 21 | "require view/v2ray/include/custom as custom"; 22 | "require view/v2ray/tools/converters as converters"; 23 | 24 | const gfwlistUrls = { 25 | github: 26 | "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt", 27 | gitlab: "https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt", 28 | pagure: "https://pagure.io/gfwlist/raw/master/f/gfwlist.txt", 29 | bitbucket: "https://bitbucket.org/gfwlist/gfwlist/raw/HEAD/gfwlist.txt", 30 | }; 31 | 32 | const apnicDelegatedUrls = { 33 | apnic: "https://ftp.apnic.net/stats/apnic/delegated-apnic-latest", 34 | arin: "https://ftp.arin.net/pub/stats/apnic/delegated-apnic-latest", 35 | ripe: "https://ftp.ripe.net/pub/stats/apnic/delegated-apnic-latest", 36 | iana: "https://ftp.iana.org/pub/mirror/rirstats/apnic/delegated-apnic-latest", 37 | }; 38 | 39 | // @ts-ignore 40 | return L.view.extend<[SectionItem[], SectionItem[]]>({ 41 | handleListUpdate(ev: MouseEvent, section_id: string, listtype: string) { 42 | const hideModal = function () { 43 | ui.hideModal(); 44 | 45 | window.location.reload(); 46 | }; 47 | 48 | switch (listtype) { 49 | case "gfwlist": { 50 | const gfwlistMirror = 51 | uci.get("v2ray", section_id, "gfwlist_mirror") || "github"; 52 | const url = gfwlistUrls[gfwlistMirror]; 53 | 54 | return L.Request.request(L.url("admin/services/v2ray/request"), { 55 | method: "post", 56 | timeout: 50 * 1000, 57 | query: { 58 | url: url, 59 | token: L.env.token, 60 | sessionid: L.env.sessionid, 61 | }, 62 | }) 63 | .then(function (res: LuCI.response) { 64 | let data; 65 | if (res.status === 200 && (data = res.json())) { 66 | let content; 67 | if (!data.code && (content = data.content)) { 68 | const gfwlistDomains = converters.extractGFWList(content); 69 | if (gfwlistDomains) { 70 | fs.write("/etc/v2ray/gfwlist.txt", gfwlistDomains) 71 | .then(function () { 72 | ui.showModal(_("List Update"), [ 73 | E("p", _("GFWList updated.")), 74 | E( 75 | "div", 76 | { class: "right" }, 77 | E( 78 | "button", 79 | { 80 | class: "btn", 81 | click: hideModal, 82 | }, 83 | _("OK") 84 | ) 85 | ), 86 | ]); 87 | }) 88 | .catch(L.raise); 89 | } else { 90 | L.raise("Error", _("Failed to decode GFWList.")); 91 | } 92 | } else { 93 | L.raise("Error", data.message || _("Failed to fetch GFWList.")); 94 | } 95 | } else { 96 | L.raise("Error", res.statusText); 97 | } 98 | }) 99 | .catch(function (e) { 100 | ui.addNotification(null, E("p", e.message)); 101 | }); 102 | } 103 | case "chnroute": 104 | case "chnroute6": { 105 | const delegatedMirror = 106 | uci.get("v2ray", section_id, "apnic_delegated_mirror") || 107 | "apnic"; 108 | 109 | const url = apnicDelegatedUrls[delegatedMirror]; 110 | 111 | return L.Request.request(L.url("admin/services/v2ray/request"), { 112 | method: "post", 113 | timeout: 50 * 1000, 114 | query: { 115 | url: url, 116 | token: L.env.token, 117 | sessionid: L.env.sessionid, 118 | }, 119 | }) 120 | .then(function (res: LuCI.response) { 121 | let data; 122 | if (res.status === 200 && (data = res.json())) { 123 | let content; 124 | if ((content = data.content)) { 125 | const ipList = converters.extractCHNRoute( 126 | content, 127 | listtype === "chnroute6" 128 | ); 129 | 130 | fs.write(`/etc/v2ray/${listtype}.txt`, ipList) 131 | .then(function () { 132 | ui.showModal(_("List Update"), [ 133 | E("p", _("CHNRoute list updated.")), 134 | E( 135 | "div", 136 | { class: "right" }, 137 | E( 138 | "button", 139 | { 140 | class: "btn", 141 | click: hideModal, 142 | }, 143 | _("OK") 144 | ) 145 | ), 146 | ]); 147 | }) 148 | .catch(L.raise); 149 | } else { 150 | L.raise( 151 | "Error", 152 | data.message || _("Failed to fetch CHNRoute list.") 153 | ); 154 | } 155 | } else { 156 | L.raise("Error", res.statusText); 157 | } 158 | }) 159 | .catch(function (e) { 160 | ui.addNotification(null, E("p", e.message)); 161 | }); 162 | } 163 | 164 | default: { 165 | ui.addNotification(null, _("Unexpected error.")); 166 | } 167 | } 168 | }, 169 | load: function () { 170 | return v2ray.getDokodemoDoorPorts(); 171 | }, 172 | render: function (dokodemoDoorPorts = []) { 173 | const m = new form.Map( 174 | "v2ray", 175 | "%s - %s".format(_("V2Ray"), _("Transparent Proxy")) 176 | ); 177 | 178 | const s = m.section( 179 | form.NamedSection, 180 | "main_transparent_proxy", 181 | "transparent_proxy" 182 | ); 183 | 184 | let o; 185 | 186 | o = s.option( 187 | form.Value, 188 | "redirect_port", 189 | _("Redirect port"), 190 | _("Enable transparent proxy on Dokodemo-door port.") 191 | ); 192 | o.value("", _("None")); 193 | for (const p of dokodemoDoorPorts) { 194 | o.value(p.value, p.caption); 195 | } 196 | o.datatype = "port"; 197 | 198 | o = s.option( 199 | widgets.NetworkSelect, 200 | "lan_ifaces", 201 | _("LAN interfaces"), 202 | _("Enable proxy on selected interfaces.") 203 | ); 204 | o.multiple = true; 205 | o.nocreate = true; 206 | o.filter = function (section_id: string, value: string) { 207 | return value.indexOf("wan") < 0; 208 | }; 209 | o.rmempty = false; 210 | 211 | o = s.option( 212 | form.Flag, 213 | "use_tproxy", 214 | _("Use TProxy"), 215 | _("Setup redirect rules with TProxy.") 216 | ); 217 | 218 | o = s.option( 219 | form.Flag, 220 | "only_privileged_ports", 221 | _("Only privileged ports"), 222 | _("Only redirect traffic on ports below 1024.") 223 | ); 224 | 225 | o = s.option( 226 | form.Flag, 227 | "redirect_udp", 228 | _("Redirect UDP"), 229 | _("Redirect UDP traffic to V2Ray.") 230 | ); 231 | 232 | o = s.option( 233 | form.Flag, 234 | "redirect_dns", 235 | _("Redirect DNS"), 236 | _("Redirect DNS traffic to V2Ray.") 237 | ); 238 | o.depends("redirect_udp", ""); 239 | o.depends("redirect_udp", "0"); 240 | 241 | o = s.option( 242 | form.ListValue, 243 | "proxy_mode", 244 | _("Proxy mode"), 245 | _( 246 | "If enabled, iptables rules will be added to pre-filter traffic and then sent to V2Ray." 247 | ) 248 | ); 249 | o.value("default", _("Default")); 250 | o.value("cn_direct", _("CN Direct")); 251 | o.value("cn_proxy", _("CN Proxy")); 252 | o.value("gfwlist_proxy", _("GFWList Proxy")); 253 | 254 | o = s.option( 255 | form.ListValue, 256 | "apnic_delegated_mirror", 257 | _("APNIC delegated mirror") 258 | ); 259 | o.value("apnic", "APNIC"); 260 | o.value("arin", "ARIN"); 261 | o.value("ripe", "RIPE"); 262 | o.value("iana", "IANA"); 263 | 264 | o = s.option(custom.ListStatusValue, "_chnroutelist", _("CHNRoute")); 265 | o.listtype = "chnroute"; 266 | o.btntitle = _("Update"); 267 | o.btnstyle = "apply"; 268 | o.onupdate = L.bind(this.handleListUpdate, this); 269 | 270 | o = s.option(form.ListValue, "gfwlist_mirror", _("GFWList mirror")); 271 | o.value("github", "GitHub"); 272 | o.value("gitlab", "GitLab"); 273 | o.value("bitbucket", "Bitbucket"); 274 | o.value("pagure", "Pagure"); 275 | 276 | o = s.option(custom.ListStatusValue, "_gfwlist", _("GFWList")); 277 | o.listtype = "gfwlist"; 278 | o.btntitle = _("Update"); 279 | o.btnstyle = "apply"; 280 | o.onupdate = L.bind(this.handleListUpdate, this); 281 | 282 | o = s.option( 283 | custom.TextValue, 284 | "_proxy_list", 285 | _("Extra proxy list"), 286 | _( 287 | "One address per line. Allow types: DOMAIN, IP, CIDR. eg: %s, %s, %s" 288 | ).format("www.google.com", "1.1.1.1", "192.168.0.0/16") 289 | ); 290 | o.wrap = "off"; 291 | o.rows = 5; 292 | o.datatype = "string"; 293 | o.filepath = "/etc/v2ray/proxylist.txt"; 294 | 295 | o = s.option( 296 | custom.TextValue, 297 | "_direct_list", 298 | _("Extra direct list"), 299 | _( 300 | "One address per line. Allow types: DOMAIN, IP, CIDR. eg: %s, %s, %s" 301 | ).format("www.google.com", "1.1.1.1", "192.168.0.0/16") 302 | ); 303 | o.wrap = "off"; 304 | o.rows = 5; 305 | o.datatype = "string"; 306 | o.filepath = "/etc/v2ray/directlist.txt"; 307 | 308 | o = s.option( 309 | form.Value, 310 | "proxy_list_dns", 311 | _("Proxy list DNS"), 312 | _( 313 | "DNS used for domains in proxy list, format: ip#port. eg: %s" 314 | ).format("1.1.1.1#53") 315 | ); 316 | 317 | o = s.option( 318 | form.Value, 319 | "direct_list_dns", 320 | _("Direct list DNS"), 321 | _( 322 | "DNS used for domains in direct list, format: ip#port. eg: %s" 323 | ).format("114.114.114.114#53") 324 | ); 325 | 326 | o = s.option( 327 | custom.TextValue, 328 | "_src_direct_list", 329 | _("Local devices direct outbound list"), 330 | _("One address per line. Allow types: IP, CIDR. eg: %s, %s").format( 331 | "192.168.0.19", 332 | "192.168.0.0/16" 333 | ) 334 | ); 335 | o.wrap = "off"; 336 | o.rows = 3; 337 | o.datatype = "string"; 338 | o.filepath = "/etc/v2ray/srcdirectlist.txt"; 339 | 340 | return m.render(); 341 | }, 342 | }); 343 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": [ 8 | "dom", 9 | "dom.iterable", 10 | "esnext", 11 | "es2015.promise", 12 | ], /* Specify library files to be included in the compilation. */ 13 | // "allowJs": true, /* Allow javascript files to be compiled. */ 14 | // "checkJs": true, /* Report errors in .js files. */ 15 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 16 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 17 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 18 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 19 | // "outFile": "./", /* Concatenate and emit output to single file. */ 20 | // "outDir": "./", /* Redirect output structure to the directory. */ 21 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 22 | // "composite": true, /* Enable project compilation */ 23 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 24 | // "removeComments": true, /* Do not emit comments to output. */ 25 | // "noEmit": true, /* Do not emit outputs. */ 26 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 27 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 28 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 29 | 30 | /* Strict Type-Checking Options */ 31 | "strict": true, /* Enable all strict type-checking options. */ 32 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 33 | // "strictNullChecks": true, /* Enable strict null checks. */ 34 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 35 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 36 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 37 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 38 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 39 | 40 | /* Additional Checks */ 41 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 42 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 43 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 44 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | "typeRoots": [ 52 | "src/typings", 53 | "node_modules/@types", 54 | ], /* List of folders to include type definitions from. */ 55 | // "types": [], /* Type declaration files to be included in compilation. */ 56 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 57 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 58 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 59 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 60 | 61 | /* Source Map Options */ 62 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 63 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 64 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 65 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 66 | 67 | /* Experimental Options */ 68 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 69 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 70 | 71 | /* Advanced Options */ 72 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 73 | }, 74 | "include": [ 75 | "src/**/*.ts", 76 | "node_modules/@kuoruan/luci-types/index.d.ts", 77 | ] 78 | } 79 | --------------------------------------------------------------------------------