diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index 53becc1d..00000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Docker - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - workflow_dispatch: #github页面手动触发 - push: - tags: [ 'v*' ] - -env: - # github.repository as / - IMAGE_NAME_V2RAY: jrohy/v2ray - IMAGE_NAME_XRAY: jrohy/xray - PLATFORMS: linux/amd64,linux/arm64/v8,linux/386 - -jobs: - buildx: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: Get latest tag - uses: oprypin/find-latest-tag@v1 - id: octokit # The step ID to refer to later. - with: - repository: ${{ github.repository }} # The repository to scan. - - # https://github.com/docker/login-action - - name: Login - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - # https://github.com/docker/build-push-action - - name: Build & Push v2ray - uses: docker/build-push-action@v2 - with: - context: ./docker/v2ray/ - file: ./docker/v2ray/Dockerfile - push: true - platforms: ${{ env.PLATFORMS }} - tags: | - ${{ env.IMAGE_NAME_V2RAY }}:${{ steps.octokit.outputs.tag }} - ${{ env.IMAGE_NAME_V2RAY }}:latest - - - # https://github.com/docker/build-push-action - - name: Build & Push xray - uses: docker/build-push-action@v2 - with: - context: ./docker/xray/ - file: ./docker/xray/Dockerfile - push: true - platforms: ${{ env.PLATFORMS }} - tags: | - ${{ env.IMAGE_NAME_XRAY }}:${{ steps.octokit.outputs.tag }} - ${{ env.IMAGE_NAME_XRAY }}:latest diff --git a/.gitignore b/.gitignore index dbe9c82b..1d74e219 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.vscode/ \ No newline at end of file +.vscode/ diff --git a/Changelog.md b/Changelog.md deleted file mode 100644 index ad9d5749..00000000 --- a/Changelog.md +++ /dev/null @@ -1,131 +0,0 @@ -# Change Log -## [v3.7.7](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.7)(2020-01-11) -- fix [#279](https://github.com/Jrohy/multi-v2ray/issues/279): 生成随机端口时进行端口检测是否占用, 同时不再用lsof来检测端口占用(去掉依赖), 换成python原生socket来检测 - -## [v3.7.6](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.6)(2020-01-03) -- 添加 docker 容器内update.sh命令支持 - -## [v3.7.5](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.5)(2019-11-29) -- 加入生成随机邮箱 [#253](https://github.com/Jrohy/multi-v2ray/issues/253) -- fix [#255](https://github.com/Jrohy/multi-v2ray/issues/255): 纯IPV6的vps获取SSL证书失败,acme.sh无法安装 -- fix [#256](https://github.com/Jrohy/multi-v2ray/issues/256) - -## [v3.7.4](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.4)(2019-11-28) -- 测试支持纯ipv6 vps, 安装脚本路径更换 - -## [v3.7.3](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.3)(2019-11-17) -- 添加完整的docker功能, 包括实现容器内iptables流量统计、证书申请、命令补全 - 实现iptables流量统计起容器时必须加入--privileged - -## [v3.7.2](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.2)(2019-11-15) -- 优化脚本输入体验, 单个字符输入免回车 - -## [v3.7.1](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.1)(2019-11-12) -- fix [#244](https://github.com/Jrohy/multi-v2ray/issues/244), 修复v2ray流量统计bug -- 修改v2ray流量统计命令行为`v2ray stats`, iptables流量统计为`v2ray iptables` - -## [v3.7.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.7.0)(2019-11-07) -- 添加更多Cloudcflare cdn 端口, 端口详见: [Identifying network ports compatible with Cloudflare's proxy -](https://support.cloudflare.com/hc/en-us/articles/200169156-Identifying-network-ports-compatible-with-Cloudflare-s-proxy) - -## [v3.6.8](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.8)(2019-11-05) -- 优化v2ray流量统计查看 - -## [v3.6.7](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.7)(2019-10-03) -- 选择组时输入的字母不再区分大小写. - -## [v3.6.6](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.6)(2019-10-03) -- fixed: [#231](https://github.com/Jrohy/multi-v2ray/issues/231) - -## [v3.6.5](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.5)(2019-09-28) -- 优化docker平台服务管理 - -## [v3.6.4](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.4)(2019-09-28) -- 添加`v2ray update.sh`命令来更新multi-v2ray脚本 - -## [v3.6.3](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.3)(2019-09-21) -- Added: 80端口cdn关闭(去掉域名) -- Added: 随机生成kcp header时增加wireguard header -- Changed: service命令变为systemctl - -## [v3.6.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.6.0)(2019-09-21) -- Fixed: #218 -- Fixed: v2ray重启失败时提示重启成功 -- Fixed: 没有修改配置文件却触发v2ray重启 -- Added: cdn 命令行传参(v2ray cdn) - -## [v3.5.2](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.5.2)(2019-09-19) -- 支持80端口和443端口的cdn模式 -- 支持域名按group存储 - -## [v3.2.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.2.0)(2019-04-12) -- 修复单独pip安装时因为翻译'_'模块无法使用的问题 -- 支持v2rayN quic vmess分享格式 - -## [v3.1.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.1.0)(2019-03-22) -- 修复翻译错误, 流量统计乱码 - -## [v3.0.7](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.0.7)(2019-03-22) -- support chinese && english switch -- check v2ray log -- [#163](https://github.com/Jrohy/multi-v2ray/issues/163) -- [#168](https://github.com/Jrohy/multi-v2ray/issues/168) - -## [v3.0.6](https://github.com/Jrohy/multi-v2ray/releases/tag/v3.0.6)(2019-03-17) -- Change to pip install -- [#152](https://github.com/Jrohy/multi-v2ray/issues/152) -- [#155](https://github.com/Jrohy/multi-v2ray/issues/155) - -## [v2.6.3.1](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.6.3.1)(2019-01-10) -- fix some bug - -## [v2.6.3](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.6.3)(2018-12-25) -- [#107](https://github.com/Jrohy/multi-v2ray/issues/107) -- [#108](https://github.com/Jrohy/multi-v2ray/issues/108) -- support zsh - -## [v2.6.2](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.6.2)(2018-12-23) -- [#106](https://github.com/Jrohy/multi-v2ray/issues/106) - -## [v2.6.1](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.6.1)(2018-12-21) -- [#99](https://github.com/Jrohy/multi-v2ray/issues/99) -- Fix del mtproto port - -## [v2.6.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.6.0)(2018-12-17) -- [#95](https://github.com/Jrohy/multi-v2ray/issues/95) -- [#96](https://github.com/Jrohy/multi-v2ray/issues/96) -- 增加iptables端口流量统计 - -## [v2.5.3](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.5.3)(2018-12-08) -- [#82](https://github.com/Jrohy/multi-v2ray/issues/82) -- Fix force update bug - -## [v2.5.2](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.5.2)(2018-12-06) -- [#78](https://github.com/Jrohy/multi-v2ray/issues/78) -- [#80](https://github.com/Jrohy/multi-v2ray/issues/80) - -## [v2.5.1](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.5.1)(2018-12-03) -- 加入更新脚本特定版本指令 - -## [v2.5.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.5.0)(2018-12-02) -- 更新策略更改, 只用最新的Release版本更新, 亦可指定版本更新(回退) -- 增加版本信息显示 - -## [v2.4](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.4)(2018-11-29) -- 支持Quic -- 加入更新v2ray到特定版本的指令 - -## [v2.3](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.3)(2018-11-27) -- Add Flask web接口 -- 精简json模板 - -## [v2.2](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.2)(2018-11-20) -- 加入禁止BT - -## [v2.1](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.1)(2018-11-19) -- 代码重构,加入json文件缓存 - -## [v2.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v2.0)(2018-10-09) -- 代码重构,加入json文件缓存 - -## [v1.0](https://github.com/Jrohy/multi-v2ray/releases/tag/v1.0)(2018-09-23) \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 5c59e416..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -include README.md -include v2ray_util/util_core/util.dat -include v2ray_util/util_core/util.cfg -include v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.mo -include v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.mo -recursive-include v2ray_util/json_template *.json -recursive-include v2ray_util/global_setting *.sh \ No newline at end of file diff --git a/README.md b/README.md index f5843005..ab6c0cb4 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,48 @@ -# multi-v2ray -V2ray/Xray多用户管理脚本,向导式管理[新增|删除|修改]传输协议 -![](https://img.shields.io/pypi/v/v2ray-util.svg) -[![Downloads](https://pepy.tech/badge/v2ray-util)](https://pepy.tech/project/v2ray-util) -[![Downloads](https://pepy.tech/badge/v2ray-util/month)](https://pepy.tech/project/v2ray-util) -![](https://img.shields.io/docker/pulls/jrohy/v2ray.svg) -![](https://img.shields.io/github/license/Jrohy/multi-v2ray.svg) -## [中文](README.md) [English](README_EN.md) + + +* [multi-v2ray](#multi-v2ray) + * [特色](#特色) + * [功能](#功能) + * [安装命令](#安装命令) + * [升级命令](#升级命令) + * [卸载命令](#卸载命令) + * [命令行参数](#命令行参数) + * [截图](#截图) + * [系统要求](#系统要求) + * [更新日志](#更新日志) + * [特别说明](#特别说明) + * [感谢](#感谢) + + + +# multi-v2ray +V2ray多用户管理脚本,向导式管理[新增|删除|修改]传输协议,享受V2ray的乐趣~ ## 特色 -- [x] 支持Xray管理, v2ray和xray相互独立, 不同命令(v2ray/xray)进入不同的core管理 -- [x] 调用v2ray官方api进行流量统计 -- [x] **多用户, 多端口管理**, 混合传输协议管理不再是梦 -- [x] 首次安装时产生随机端口,默认配置mkcp + 随机一种 (srtp | wechat-video | utp | dtls | wireguard) header伪装; -  安装完成显示配置信息; -- [x] 查看配置信息显示vmess/vless字符串(v2rayN的分享链接格式) -- [x] 生成**Telegram**的socks5/MTProto分享链接, 支持socks5 + tls组合 -- [x] 支持http/2, 随机生成伪装h2 path -- [x] 开启关闭tcpFastOpen -- [x] 直接开启[CDN](https://github.com/Jrohy/multi-v2ray/wiki/CloudFlare-cdn%E4%BB%A3%E7%90%86v2ray%E6%B5%81%E9%87%8F) -- [x] 开启关闭动态端口 -- [x] 定时更新v2ray(需手动开启) -- [x] 支持新版v2ray配置文件格式(v4.1+) -- [x] 支持范围端口修改 -- [x] 支持程序和**命令行参数**管理控制 -- [x] 支持docker部署 -- [x] 支持VLESS和Trojan以及XTLS(v4.31.0+) -- [x] 支持纯ipv6 vps -- [x] 禁止BT +- 调用v2ray官方api进行流量统计 +- **多用户, 多端口管理**, 混合传输协议管理不再是梦 +- 首次安装时产生随机端口,默认配置mkcp + 随机一种 (srtp | wechat-video | utp | dtls) header伪装; +  安装完成显示配置信息; **脚本跑完即可放心食用!** +- 每天**北京时间**早上3点自动升级重启v2ray核心,降低v2ray因内存小被kill几率。可关闭开启此功能。 +- 查看配置信息显示vmess字符串(v2rayN的分享链接格式) +- 生成**Telegram**的socks5/MTProto分享链接, 支持socks5 + tls组合 +- 支持http/2, 随机生成伪装h2 path +- 开启关闭tcpFastOpen +- 开启关闭动态端口 +- 禁止BT +- 支持新版v2ray配置文件格式(v4.1+) +- 支持范围端口修改 +- 支持程序和**命令行参数**管理控制 ## 功能 - 一键 启动 / 停止 / 重启 V2ray 服务端 -- 流量统计(v2ray && iptables) +- 流量统计 - 命令行模式管理v2ray - 支持多用户, 多端口管理 - 开启关闭动态端口 - bittorrent的禁止与放行 - 单端口, 范围端口的修改 -- 直接走Cloudcflare cdn - 开启关闭tcpFastOpen - 快速查看服务器连接信息, 常规配置修改 - 自由更改**传输配置**: @@ -54,87 +59,167 @@ V2ray/Xray多用户管理脚本,向导式管理[新增|删除|修改]传输协 - Socks5 - MTProto - Shadowsocks - - Quic - - VLESS_TCP - - VLESS_TLS - - VLESS_WS - - VLESS_REALITY - - Trojan + +**WebSocket和HTTP/2不包括Nginx分流,请自行安装Nginx来分流。** ## 安装命令 -``` -source <(curl -sL https://multi.netlify.app/v2ray.sh) --zh -``` -## 升级命令(保留配置文件更新) +```bash +source <(curl -sL https://git.io/fNgqx) ``` -source <(curl -sL https://multi.netlify.app/v2ray.sh) -k + +## 升级命令(保留配置文件,升级失败请全新安装) +```bash +source <(curl -sL https://git.io/fNgqx) -k ``` ## 卸载命令 -``` -source <(curl -sL https://multi.netlify.app/v2ray.sh) --remove +```bash +source <(curl -sL https://git.io/fNgqx) --remove ``` -## 命令行参数 +## 命令行参数 +所有命令行参数支持**Tab**补全 ```bash -v2ray/xray [-h|help] [options] - -h, help 查看帮助 - -v, version 查看版本号 - start 启动 V2Ray - stop 停止 V2Ray - restart 重启 V2Ray - status 查看 V2Ray 运行状态 - new 重建新的v2ray json配置文件 - update 更新 V2Ray 到最新Release版本 - update [version] 更新 V2Ray 到指定版本 - update.sh 更新 multi-v2ray 到最新版本 - add 新增端口组 - add [protocol] 新增一种协议的组, 端口随机, 如 v2ray add utp 为新增utp协议 - del 删除端口组 - info 查看配置 - port 修改端口 - tls 修改tls - tfo 修改tcpFastOpen - stream 修改传输协议 - cdn 走cdn - stats v2ray流量统计 - iptables iptables流量统计 - clean 清理日志 - log 查看日志 - rm 卸载core + v2ray -h 查看帮助 + v2ray start 启动 V2Ray + v2ray stop 停止 V2Ray + v2ray restart 重启 V2Ray + v2ray status 查看 V2Ray 运行状态 + v2ray log 查看 V2Ray 运行日志 + v2ray update 更新 V2Ray + v2ray update.sh 更新 multi-v2ray脚本 + v2ray add 新增mkcp + 随机一种 (srtp | wechat-video | utp) header伪装的端口(Group) + v2ray add [wechat|utp|srtp|dtls|wireguard|socks|mtproto|ss] 新增一种协议的组,端口随机,如 v2ray add utp 为新增utp协议 + v2ray del 删除端口组 + v2ray info 查看配置 + v2ray port 修改端口 + v2ray tls 修改tls + v2ray tfo 修改tcpFastOpen + v2ray stream 修改传输协议 + v2ray stats 流量统计 + v2ray clean 清理日志 ``` +更多命令行参数请参考 [multi-v2ray wiki](https://github.com/Jrohy/multi-v2ray/wiki) -## Docker运行 +## 截图 -默认创建mkcp + 随机一种伪装头配置文件(**如果使用xray则换成镜像jrohy/xray**): -``` -docker run -d --name v2ray --privileged --restart always --network host jrohy/v2ray -``` +![1](fun_img/1.png) -自定义v2ray配置文件: -``` -docker run -d --name v2ray --privileged -v /path/config.json:/etc/v2ray/config.json --restart always --network host jrohy/v2ray -``` +![2](fun_img/2.png) -查看v2ray配置: -``` -docker exec v2ray bash -c "v2ray info" -``` +![3](fun_img/3.png) -**warning**: 如果用centos,需要先关闭防火墙 -``` -systemctl stop firewalld.service -systemctl disable firewalld.service -``` +![4](fun_img/4.png) + +## 系统要求 + +- Debian 7 +- Debian 8 +- **Debian 9(推荐)** +- Ubuntu 14 +- Ubuntu 16 +- Ubuntu 18 +- CentOS 7 + +**不支持Centos 6** + +## 更新日志 +**2018.11.25** +加入Flask Web接口 + +**2018.11.19** +加入禁止BT + +**2018.11.18** +支持新版v2ray配置文件格式(v4.1+), 升级脚本自动转换格式为新版 +支持范围端口修改 + +**2018.10.9** +面向对象 来重构代码 +加入json文件缓存(利用序列化实现) + +**2018.9.15** +tcpFastOpen的配置 +命令行参数加入Tab补全 + +**2018.9.1** +增加 WireGuard header type + +**2018.8.26** +脚本加入pip安装 +tls设置支持自定义证书路径 + +**2018.7.29** +支持Shadowsocks + +**2018.7.28** +项目改名为multi-v2ray +重构安装脚本, 仅留一个multi-v2ray.sh, 支持指令 +管理程序v2ray支持命令行参数 +增加wiki + +**2018.7.27** +支持MTProto + +**2018.7.15** +增加清理日志,更新脚本菜单 + +**2018.6.26** +支持Socks5 + +**2018.6.21** +加入流量统计 + +**2018.6.16** +代码完全重构, 多用户多端口管理初版 + +**2018.5.31** +增加DTLS header type + +**2018.5.14** +加入自动升级v2ray功能, 每天北京时间早上3点自动升级重启v2ray核心,降低v2ray因内存小被kill几率 + +**2018.5.13** +安装和升级脚本合二为一 + +**2018.5.6** +支持http/2一键开启关闭, 项目本身转为独立项目 + +**2018.5.3** +支持开启动态端口 + +**2018.3.23** +安装完脚本即显示完整的v2ray配置信息,进一步提高v2ray部署速度 + +**2018.3.8** +优化菜单显示 +升级v2ray.fun保留配置文件 +首次安装默认配置改为mkcp+随机三种伪装type之一, 用于快速部署v2ray + +**2018.3.6** +增加配置文件vmess字符串生成显示,增加修改alterId的菜单 + +**2018.3.5** +第一次安装产生随机端口 + +**2017.10.16** +新增TLS功能,自动获取证书。 + +**2017.9.4** +第一版通过测试发布。 + +## 特别说明 + +有任何问题或者新功能想法欢迎提交 Issue。 + +本程序遵循 GPL v3协议发布,请Fork保留源项目地址,谢谢! + +由于官方统计方式的限制, v2ray core重启就会重置统计流量数据! + + +## 感谢 -## 建议 -安装完v2ray后强烈建议开启BBR等加速: [one_click_script](https://github.com/jinwyp/one_click_script) -使用Trojan和VLESS协议建议自行安装个nginx, 能让v2ray顺利Fallback到默认的80端口 +V2ray: [https://v2ray.com](https://v2ray.com) -## 依赖 -v2ray docker: https://hub.docker.com/r/jrohy/v2ray -xray docker: https://hub.docker.com/r/jrohy/xray -pip: https://pypi.org/project/v2ray-util/ -python3: https://github.com/Jrohy/python3-install -acme: https://github.com/acmesh-official/acme.sh +雨落无声的v2ray.fun: [YLWS-4617](https://github.com/YLWS-4617) diff --git a/README_EN.md b/README_EN.md deleted file mode 100644 index 6fc48234..00000000 --- a/README_EN.md +++ /dev/null @@ -1,117 +0,0 @@ -# multi-v2ray -a tool to manage v2ray/xray config json, support multiple user && group manage -![](https://img.shields.io/pypi/v/v2ray-util.svg) -[![Downloads](https://pepy.tech/badge/v2ray-util)](https://pepy.tech/project/v2ray-util) -[![Downloads](https://pepy.tech/badge/v2ray-util/month)](https://pepy.tech/project/v2ray-util) -![](https://img.shields.io/docker/pulls/jrohy/v2ray.svg) -![](https://img.shields.io/github/license/Jrohy/multi-v2ray.svg) - -## [中文](README.md) [English](README_EN.md) - -## Feature -- Support Xray manage, different commands (v2ray/xray) enter different core management -- V2ray && Iptables Traffic Statistics -- Command line to manage -- Multiple user && port manage -- Cloudcflare cdn mode -- Support pure ipv6 VPS -- Support Docker -- Dynamic port -- Ban bittorrent -- Range port -- TcpFastOpen -- Vmess/VLESS/Socks5/MTproto share link -- Support protocol modify: - - TCP - - Fake http - - WebSocket - - mkcp - - mKCP + srtp - - mKCP + utp - - mKCP + wechat-video - - mKCP + dtls - - mKCP + wireguard - - HTTP/2 - - Socks5 - - MTProto - - Shadowsocks - - Quic - - VLESS_TCP - - VLESS_TLS - - VLESS_WS - - VLESS_REALITY - - Trojan - -## How To Use -new install -``` -source <(curl -sL https://multi.netlify.app/v2ray.sh) -``` - -keep profile to update -``` -source <(curl -sL https://multi.netlify.app/v2ray.sh) -k -``` - -uninstall -``` -source <(curl -sL https://multi.netlify.app/v2ray.sh) --remove -``` - -## Command Line -```bash -v2ray/xray [-h|help] [options] - -h, help get help - -v, version get version - start start V2Ray - stop stop V2Ray - restart restart V2Ray - status check V2Ray status - new create new json profile - update update v2ray to latest - update [version] update v2ray to special version - update.sh update multi-v2ray to latest - add add new group - add [protocol] create special protocol, random new port - del delete port group - info check v2ray profile - port modify port - tls modify tls - tfo modify tcpFastOpen - stream modify protocol - cdn cdn mode - stats v2ray traffic statistics - iptables iptables traffic statistics - clean clean v2ray log - log check v2ray log - rm uninstall core -``` - -## Docker Run -default will create random port + random header(srtp | wechat-video | utp | dtls) kcp profile(**if use xray replace image to jrohy/xray**) -``` -docker run -d --name v2ray --privileged --restart always --network host jrohy/v2ray -``` - -custom v2ray config.json: -``` -docker run -d --name v2ray --privileged -v /path/config.json:/etc/v2ray/config.json --restart always --network host jrohy/v2ray -``` - -check v2ray profile: -``` -docker exec v2ray bash -c "v2ray info" -``` - -**warning**: if u run with centos, u should close firewall first -``` -systemctl stop firewalld.service -systemctl disable firewalld.service -``` - -## Dependent -v2ray docker: https://hub.docker.com/r/jrohy/v2ray -xray docker: https://hub.docker.com/r/jrohy/xray -pip: https://pypi.org/project/v2ray-util/ -python3: https://github.com/Jrohy/python3-install -acme: https://github.com/Neilpang/acme.sh diff --git a/app.py b/app.py new file mode 100644 index 00000000..da85e677 --- /dev/null +++ b/app.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import json +import os +from flask import request, jsonify, Blueprint + +from loader import Loader +from utils import ss_method, stream_list, StreamType +from writer import NodeWriter, GroupWriter, ClientWriter, GlobalWriter, StreamWriter + +func_router = Blueprint('func_router', __name__) + +loader = Loader() + +class ResponseJson: + def __init__(self, success=True, msg="success", data=None): + self.success = success + self.msg = msg + self.data = data + +def find_client(group_list, client_index): + find, group = False, None + for single_group in group_list: + if find: + break + for index, node in enumerate(single_group.node_list): + if node.user_number == client_index: + client_index = index + group = single_group + find = True + break + return group, client_index + +@func_router.route('/list', methods=['GET']) +def node_list(): + loader.load_profile() + return json.dumps(loader.profile, default=lambda x: x.__dict__, ensure_ascii=False) + +@func_router.route('/collection/', methods=['GET']) +def collection(ctype): + success, msg, result = True, "get {} success!!!".format(ctype), None + try: + if ctype == 'ss_method': + result = ss_method() + elif ctype == 'new_port': + result = [x.value for x in stream_list()] + elif ctype == 'stream': + result = [x.value for x in StreamType] + else: + raise ValueError("{} ctype not found".format(ctype)) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg, result).__dict__) + +@func_router.route('/manage/', methods=['POST']) +def manage(action): + success, msg, result = True, "{} v2ray success!!!".format(action), None + try: + if action in ['start', 'stop', 'restart']: + os.system("service v2ray {}".format(action)) + else: + raise ValueError("{} action not found".format(action)) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg, result).__dict__) + +@func_router.route('/user', methods = ['POST']) +def add_user(): + success, msg, kw = True, "add user success!!!", dict() + try: + json_request = json.loads(request.get_data()) + group_tag = json_request['group_tag'] + kw['email'] = json_request['email'] + loader.load_profile() + group_list = loader.profile.group_list + group = list(filter(lambda group:group.tag == group_tag, group_list))[0] + nw = NodeWriter(group.tag, group.index) + nw.create_new_user(**kw) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/user/', methods = ['DELETE']) +def del_user(client_index): + success, msg = True, "del user {} success!!!".format(client_index) + try: + loader.load_profile() + group_list = loader.profile.group_list + group, client_index = find_client(group_list, client_index) + nw = NodeWriter() + nw.del_user(group, client_index) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/group', methods = ['POST']) +def add_group(): + success, msg, kw, stream_type = True, "add group {} success!!!", dict(), "" + try: + json_request = json.loads(request.get_data()) + port = int(json_request['port']) + stream_type = json_request['stream_type'] + + from utils import stream_list + stream_list = stream_list() + if stream_type not in [x.value for x in stream_list]: + raise ValueError("stream_type {} not found".format(stream_type)) + if "data" in json_request: + kw = json_request['data'] + + stream = list(filter(lambda stream:stream.value == stream_type, stream_list))[0] + nw = NodeWriter() + nw.create_new_port(port, stream, **kw) + msg = msg.format(stream_type) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/group/', methods = ['DELETE']) +def del_group(group_tag): + success, msg = True, "del group {} success!!!".format(group_tag) + try: + loader.load_profile() + group_list = loader.profile.group_list + group = list(filter(lambda group:group.tag == group_tag, group_list))[0] + nw = NodeWriter() + nw.del_port(group) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/stream/', methods = ['PUT']) +def modify_stream(group_tag): + success, msg, kw = True, "modify group {} success!!!".format(group_tag), dict() + try: + json_request = json.loads(request.get_data()) + stream_type = json_request['stream_type'] + if stream_type not in [x.value for x in StreamType]: + raise ValueError("stream_type {} not found".format(stream_type)) + if "data" in json_request: + kw = json_request['data'] + stream = list(filter(lambda stream:stream.value == stream_type, StreamType))[0] + + loader.load_profile() + group_list = loader.profile.group_list + group = list(filter(lambda group:group.tag == group_tag, group_list))[0] + sw = StreamWriter(group.tag, group.index, stream) + sw.write(**kw) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/group/', methods = ['PUT']) +def modify_group(group_tag): + success, msg = True, "modify group {} success!!!".format(group_tag) + try: + json_request = json.loads(request.get_data()) + modify_type = json_request['modify_type'] + value = json_request['value'] + loader.load_profile() + group_list = loader.profile.group_list + group = list(filter(lambda group:group.tag == group_tag, group_list))[0] + gw = GroupWriter(group.tag, group.index) + method = 'write_' + modify_type + if hasattr(gw, method): + func = getattr(gw, method) + if modify_type == "dyp": + func(bool(value['status']), int(value['aid'])) + else: + func(value) + else: + raise RuntimeError("{} method not found".format(method)) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/user/', methods = ['PUT']) +def modify_user(client_index): + success, msg, modify_type = True, "modify user {} success!!!".format(client_index), "" + try: + json_request = json.loads(request.get_data()) + modify_type = json_request['modify_type'] + value = json_request['value'] + loader.load_profile() + group_list = loader.profile.group_list + group, client_index = find_client(group_list, client_index) + cw = ClientWriter(group.tag, group.index, client_index) + method = 'write_' + modify_type + if hasattr(cw, method): + func = getattr(cw, method) + func(value) + else: + raise RuntimeError("{} method not found".format(method)) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) + +@func_router.route('/global', methods = ['PUT']) +def modify_global(): + success, msg, modify_type = True, "modify global {} success!!!", "" + try: + json_request = json.loads(request.get_data()) + modify_type = json_request['modify_type'] + value = json_request['value'] + loader.load_profile() + group_list = loader.profile.group_list + gw = GlobalWriter(group_list) + method = 'write_' + modify_type + if hasattr(gw, method): + func = getattr(gw, method) + func(value) + else: + raise RuntimeError("{} method not found".format(method)) + msg = msg.format(modify_type) + except Exception as e: + success = False + msg = str(e) + return jsonify(ResponseJson(success, msg).__dict__) \ No newline at end of file diff --git a/client.py b/client.py new file mode 100644 index 00000000..ce46fccd --- /dev/null +++ b/client.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import os +import json + +from config import Config +from utils import get_ip +from group import Vmess, Socks, SS, Mtproto +from selector import ClientSelector + +class ClientWriter: + def __init__(self, group, client_index): + self.config_factory = Config() + with open(self.config_factory.get_path("config_path"), 'r') as json_file: + self.config = json.load(json_file) + + self.write_path = self.config_factory.get_path("write_client_path") + self.template_path = self.config_factory.get_path("template_path") + self.group = group + self.client_index = client_index + self.node = group.node_list[client_index] + + def load_template(self, template_name): + ''' + load special template + ''' + with open(self.template_path + "/" + template_name, 'r') as stream_file: + template = json.load(stream_file) + return template + + def transform(self): + user_json = None + if type(self.node) == Vmess: + self.client_config = self.load_template('client.json') + user_json = self.client_config["outbounds"][0]["settings"]["vnext"][0] + user_json["users"][0]["id"] = self.node.password + user_json["users"][0]["alterId"] = self.node.alter_id + + elif type(self.node) == Socks: + self.client_config = self.load_template('client_socks.json') + user_json = self.client_config["outbounds"][0]["settings"]["servers"][0] + user_json["users"][0]["user"] = self.node.user_info + user_json["users"][0]["pass"] = self.node.password + + elif type(self.node) == SS: + self.client_config = self.load_template('client_ss.json') + user_json = self.client_config["outbounds"][0]["settings"]["servers"][0] + user_json["method"] = self.node.method + user_json["password"] = self.node.password + + elif type(self.node) == Mtproto: + print("\nMTProto协议只支持Telegram通信, 所以无法生成配置文件!\n") + exit(-1) + + user_json["port"] = int(self.group.port) + + if type(self.node) != SS: + self.client_config["outbounds"][0]["streamSettings"] = self.config["inbounds"][group.index]["streamSettings"] + + if group.tls == 'tls': + content = self.config_factory.get_data("domain") + user_json["address"] = str(content) + self.client_config["outbounds"][0]["streamSettings"]["tlsSettings"] = {} + else: + user_json["address"] = str(get_ip()) + + def write(self): + ''' + 写客户端配置文件函数 + ''' + json_dump = json.dumps(self.client_config,indent=1) + with open(self.write_path, 'w') as write_json_file: + write_json_file.writelines(json_dump) + + print("保存成功!({})\n".format(self.write_path)) + + +if __name__ == '__main__': + cs = ClientSelector('生成客户端json') + client_index = cs.client_index + group = cs.group + + if group == None: + exit(-1) + else: + cw = ClientWriter(group, client_index) + cw.transform() + cw.write() \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 00000000..e56f2652 --- /dev/null +++ b/config.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import configparser + +# ENV = 'dev' +ENV = 'prod' + +DEV_FILE_PATH = 'multi-v2ray.conf' +PROD_FILE_PATH = '/usr/local/multi-v2ray/multi-v2ray.conf' + +class Config: + + def __init__(self): + #读取配置文件 + self.config_path = eval('{}_FILE_PATH'.format(ENV.upper())) + self.config = configparser.ConfigParser() + self.config.read(self.config_path) + + def get_path(self, key): + return self.config.get('{}-path'.format(ENV), key) + + def get_web(self, key): + return self.config.get('web', key) + + def get_data(self, key): + return self.config.get('data', key) + + def set_data(self, key, value): + self.config.set('data', key, value) + self.config.write(open(self.config_path),"w") \ No newline at end of file diff --git a/config_modify/alterid.py b/config_modify/alterid.py new file mode 100644 index 00000000..265adc6b --- /dev/null +++ b/config_modify/alterid.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from group import Vmess +from writer import ClientWriter +from selector import ClientSelector + +cs = ClientSelector('修改alterId') +client_index = cs.client_index +group = cs.group + +if group == None: + exit(-1) +else: + if type(group.node_list[client_index]) == Vmess: + print("当前节点alterID: {}".format(group.node_list[client_index].alter_id)) + new_alterid = input("请输入新的alterID: ") + if (new_alterid.isnumeric()): + cw = ClientWriter(group.tag, group.index, client_index) + cw.write_aid(int(new_alterid)) + print("alterID修改成功!") + else: + print ("输入错误,请检查是否为数字") + else: + print("只有vmess协议才能修改alterId!") \ No newline at end of file diff --git a/config_modify/dyn_port.py b/config_modify/dyn_port.py new file mode 100644 index 00000000..63d93c8d --- /dev/null +++ b/config_modify/dyn_port.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from writer import GroupWriter +from selector import GroupSelector + +gs = GroupSelector('修改动态端口') +group = gs.group + +if group == None: + exit() +else: + print('当前组的动态端口状态:{}'.format(group.dyp)) + gw = GroupWriter(group.tag, group.index) + + choice = input("是否开启动态端口(y/n): ").lower() + + if choice == 'y': + newAlterId = input("请为动态端口设置alterID(默认32): ") + newAlterId = '32' if newAlterId == '' else newAlterId + if (newAlterId.isdecimal()): + gw.write_dyp(True, newAlterId) + print("\n成功开启动态端口!") + else: + print ("\n输入错误,请检查是否为数字") + elif choice == 'n': + gw.write_dyp(False) + print("\n成功关闭动态端口!") + else: + print ("\n输入错误,请检查重新输入") \ No newline at end of file diff --git a/config_modify/n_email.py b/config_modify/n_email.py new file mode 100644 index 00000000..0bfc0da9 --- /dev/null +++ b/config_modify/n_email.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from group import Socks +from writer import ClientWriter +from selector import ClientSelector +from utils import is_email + +cs = ClientSelector('修改email') +client_index = cs.client_index +group = cs.group +group_list = cs.group_list + +if group == None: + exit(-1) +elif type(group.node_list[0]) == Socks: + print("Socks5节点 不支持写入email!") + exit(-1) +else: + print ("当前节点email为:{}".format(group.node_list[client_index].user_info)) + email = "" + while True: + is_duplicate_email=False + email = input("请输入新的email地址: ") + if email == "": + break + if not is_email(email): + print("不是合格的email格式,请重新输入") + continue + + for loop_group in group_list: + for node in loop_group.node_list: + if node.user_info == None or node.user_info == '': + continue + elif node.user_info == email: + print("已经有重复的email, 请重新输入") + is_duplicate_email = True + break + if not is_duplicate_email: + break + + if email != "": + cw = ClientWriter(group.tag, group.index, client_index) + cw.write_email(email) + print("修改email成功!") \ No newline at end of file diff --git a/config_modify/n_uuid.py b/config_modify/n_uuid.py new file mode 100644 index 00000000..a71026d7 --- /dev/null +++ b/config_modify/n_uuid.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import uuid + +from group import Vmess +from writer import ClientWriter +from selector import ClientSelector + +cs = ClientSelector('修改uuid') +client_index = cs.client_index +group = cs.group + +if group == None: + exit(-1) +else: + if type(group.node_list[client_index]) == Vmess: + print("当前节点UUID为:{}".format(group.node_list[client_index].password)) + choice = input("是否要随机生成一个新的UUID (y/n):").lower() + if choice == "y": + new_uuid = uuid.uuid1() + print("新的UUID为:{}".format(new_uuid)) + cw = ClientWriter(group.tag, group.index, client_index) + cw.write_uuid(new_uuid) + print("UUID修改成功!") + else: + print("已取消生成新的UUID,未执行任何操作") + else: + print("只有vmess协议才能修改uuid!") \ No newline at end of file diff --git a/config_modify/port.py b/config_modify/port.py new file mode 100644 index 00000000..6be31f95 --- /dev/null +++ b/config_modify/port.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import re +from writer import GroupWriter +from selector import GroupSelector + +gs = GroupSelector('修改port') +group = gs.group + +if group == None: + exit(-1) +else: + if group.end_port: + port_info = "{0}-{1}".format(group.port, group.end_port) + else: + port_info = group.port + print('当前组的端口信息为:{}'.format(port_info)) + + port_strategy="always" + new_port_info = input("请输入新端口(支持输入端口范围, 用'-'隔开, 表示该范围的全部端口生效):") + if new_port_info.isdecimal() or re.match(r'^\d+\-\d+$', new_port_info): + gw = GroupWriter(group.tag, group.index) + gw.write_port(new_port_info) + print('端口修改成功!') + else: + print("输入错误!") \ No newline at end of file diff --git a/config_modify/ss.py b/config_modify/ss.py new file mode 100644 index 00000000..7bc4e2f4 --- /dev/null +++ b/config_modify/ss.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import random +import string +import sys + +from group import SS +from writer import GroupWriter +from selector import GroupSelector +from utils import ss_method + +class SSFactory: + def __init__(self): + self.method_tuple = ss_method() + + def get_method(self): + print ("请选择shadowsocks的加密方式:") + for index, method in enumerate(self.method_tuple): + print ("{}.{}".format(index + 1, method)) + choice = input() + choice = int(choice) + if choice < 0 or choice > len(self.method_tuple): + print("输入错误,请检查是否符合范围中") + exit(-1) + else: + return self.method_tuple[choice - 1] + + def get_password(self): + random_pass = ''.join(random.sample(string.ascii_letters + string.digits, 16)) + new_pass =input("产生随机密码 {} , 回车直接使用该密码, 否则输入自定义密码: ".format(random_pass)) + if not new_pass: + new_pass = random_pass + return new_pass + +if __name__ == '__main__': + + # 外部传参来决定修改哪种, 默认修改method + choice = "method" + correct_way = ("method", "password") + + if len(sys.argv) > 1: + choice = sys.argv[1] + if choice not in correct_way: + print("传参有误!") + exit(-1) + else: + print("请传以下参数来修改ss配置: {}". format(correct_way)) + exit(-1) + + gs = GroupSelector('修改SS') + group = gs.group + + if group == None: + exit(-1) + elif type(group.node_list[0]) != SS: + print("\n当前选择组不是Shadowsocks协议!\n") + exit(-1) + else: + sm = SSFactory() + gw = GroupWriter(group.tag, group.index) + if choice == correct_way[0]: + gw.write_ss_method(sm.get_method()) + elif choice == correct_way[1]: + gw.write_ss_password(sm.get_password()) + print("修改Shadowsocks {}成功!\n".format(choice)) \ No newline at end of file diff --git a/config_modify/stream.py b/config_modify/stream.py new file mode 100644 index 00000000..dba4b3d8 --- /dev/null +++ b/config_modify/stream.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import random + +from writer import StreamWriter +from selector import GroupSelector +from group import Mtproto, SS +from utils import StreamType + +from config_modify.ss import SSFactory + +class StreamModifier: + def __init__(self, group_tag='A', group_index=-1): + self.stream_type = [ + (StreamType.TCP, "普通TCP"), + (StreamType.TCP_HOST, "HTTP伪装"), + (StreamType.WS, "WebSocket流量"), + (StreamType.KCP, "普通mKCP"), + (StreamType.KCP_SRTP, "mKCP + srtp"), + (StreamType.KCP_UTP, "mKCP + utp"), + (StreamType.KCP_WECHAT, "mKCP + wechat-video"), + (StreamType.KCP_DTLS, "mKCP + dtls"), + (StreamType.KCP_WG, "mKCP + wireguard"), + (StreamType.H2, "HTTP/2"), + (StreamType.SOCKS, "Socks5"), + (StreamType.MTPROTO, "MTProto"), + (StreamType.SS, "Shadowsocks") + ] + self.group_tag = group_tag + self.group_index = group_index + + def select(self, index): + sw = StreamWriter(self.group_tag, self.group_index, self.stream_type[index][0]) + kw = {} + if index == 0 or (index >= 3 and index <= 9) or index == 11: + pass + elif index == 1 or index == 2: + host = input("请输入你想要为伪装的域名(不不不需要http):") + kw['host'] = host + elif index == 10: + user = input("请输入socks的用户名: ") + password = input("请输入socks的密码: ") + if user == "" or password == "": + print("socks的用户名或者密码不能为空") + exit(-1) + kw = {'user': user, 'pass': password} + elif index == 12: + sf = SSFactory() + kw = {"method": sf.get_method(), "password": sf.get_password()} + sw.write(**kw) + + def random_kcp(self): + kcp_list = ('mKCP + srtp', 'mKCP + utp', 'mKCP + wechat-video', 'mKCP + dtls') + choice = random.randint(4, 7) + print("随机一种 (srtp | wechat-video | utp | dtls) header伪装, 当前生成 {} \n".format(kcp_list[choice - 4])) + self.select(choice) + +if __name__ == '__main__': + + gs = GroupSelector('修改传输方式') + group = gs.group + + if group == None: + exit(-1) + else: + sm = StreamModifier(group.tag, group.index) + + print("当前组的传输方式为:{}".format(group.node_list[0].stream())) + print ("") + for index, stream_type in enumerate(sm.stream_type): + print("{0}.{1}".format(index + 1, stream_type[1])) + + choice = input() + + if not choice.isdecimal(): + print("请输入数字!") + else: + choice = int(choice) + if choice > 0 and choice <= len(sm.stream_type): + if (sm.stream_type[choice - 1][1] == "MTProto" or sm.stream_type[choice - 1][1] == "Shadowsocks") and group.tls == 'tls': + print("v2ray MTProto/Shadowsocks不支持https, 关闭tls成功!") + sm.select(choice - 1) + print("传输模式修改成功!") + else: + print("请输入符合范围的数字!") \ No newline at end of file diff --git a/config_modify/tfo.py b/config_modify/tfo.py new file mode 100644 index 00000000..1b40f010 --- /dev/null +++ b/config_modify/tfo.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from group import Mtproto, SS +from writer import GroupWriter +from selector import GroupSelector + +gs = GroupSelector('修改tcpFastOpen') +group = gs.group + +if group == None: + exit(-1) +else: + if type(group.node_list[0]) == Mtproto or type(group.node_list[0]) == SS: + print("\nv2ray MTProto/Shadowsocks协议不支持配置tcpFastOpen!!!\n") + exit(-1) + + print('当前组的tcpFastOpen状态:{}'.format(group.tfo)) + print("") + print("1.开启TFO(强制开启)") + print("2.关闭TFO(强制关闭)") + print("3.删除TFO(使用系统默认设置)") + choice = input("请输入数字选择功能:") + + gw = GroupWriter(group.tag, group.index) + if choice == "1": + gw.write_tfo('on') + elif choice == "2": + gw.write_tfo('off') + elif choice == "3": + gw.write_tfo('del') + else: + print("输入错误,请重试!") \ No newline at end of file diff --git a/config_modify/tls.py b/config_modify/tls.py new file mode 100644 index 00000000..e427f444 --- /dev/null +++ b/config_modify/tls.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import socket +import os + +from writer import GroupWriter +from group import Mtproto, SS +from selector import GroupSelector + +from utils import get_ip, get_domain_by_crt_file, gen_cert + +class TLSModifier: + def __init__(self, group_tag, group_index): + self.writer = GroupWriter(group_tag, group_index) + + def turn_on(self): + print("1. Let’s Encrypt 生成证书(准备域名)") + print("2. 自定义已存在的其他路径的证书(准备证书文件路径)\n") + choice=input("请选择使用证书方式: ") + if choice == "1": + print("\n请将您的域名解析到本VPS的IP地址,否则程序会出错!!\n") + local_ip = get_ip() + print("本机器IP地址为:" + local_ip + "\n") + input_domain=input("请输入您绑定的域名:") + try: + input_ip = socket.gethostbyname(input_domain) + except Exception: + print("\n域名检测错误!!!\n") + return + if input_ip != local_ip: + print("\n输入的域名与本机ip不符!!!\n") + return + + print("") + print("正在获取SSL证书,请稍等。。。") + gen_cert(input_domain) + crt_file = "/root/.acme.sh/" + input_domain +"_ecc"+ "/fullchain.cer" + key_file = "/root/.acme.sh/" + input_domain +"_ecc"+ "/"+ input_domain +".key" + + self.writer.write_tls(True, crt_file=crt_file, key_file=key_file, domain=input_domain) + + elif choice == "2": + crt_file = input("请输入证书cert文件路径: ") + key_file = input("请输入证书key文件路径: ") + if not os.path.exists(crt_file) or not os.path.exists(key_file): + print("证书crt文件或者key文件指定路径不存在!\n") + return + domain = get_domain_by_crt_file(crt_file) + if not domain: + print("证书文件有误!\n") + return + self.writer.write_tls(True, crt_file=crt_file, key_file=key_file, domain=domain) + else: + print("输入有误!\n") + + def turn_off(self): + self.writer.write_tls(False) + +if __name__ == '__main__': + gs = GroupSelector('修改TLS') + group = gs.group + + if group == None: + exit(-1) + else: + if type(group.node_list[0]) == Mtproto or type(group.node_list[0]) == SS: + print("\nv2ray MTProto/Shadowsocks协议不支持配置https!!!\n") + exit(-1) + tm = TLSModifier(group.tag, group.index) + tls_status = '开启' if group.tls == 'tls' else '关闭' + print("当前选择组节点状态:{}\n".format(tls_status)) + print("") + print("1.开启TLS") + print("2.关闭TLS") + choice = input("请输入数字选择功能:") + if choice == '1': + tm.turn_on() + elif choice == '2': + tm.turn_off() + else: + print("输入错误,请重试!") \ No newline at end of file diff --git a/converter.py b/converter.py new file mode 100644 index 00000000..b15f9575 --- /dev/null +++ b/converter.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import json + +config_path = "/etc/v2ray/config.json" + +class ConfigConverter: + def __init__(self): + self.config = self.load() + + def load(self): + ''' + load v2ray profile + ''' + with open(config_path, 'r') as json_file: + config = json.load(json_file) + return config + + def save(self): + ''' + save v2ray config.json + ''' + json_dump=json.dumps(self.config, indent=2) + with open(config_path, 'w') as writer: + writer.writelines(json_dump) + + def transform(self): + inbound_list, outbound_list = [], [] + if "inbound" in self.config: + inbound_list.append(self.config["inbound"]) + del self.config["inbound"] + if "inboundDetour" in self.config and self.config["inboundDetour"]: + inbound_list.extend([ x for x in self.config["inboundDetour"] ]) + del self.config["inboundDetour"] + if inbound_list: + self.config["inbounds"] = inbound_list + + if "outbound" in self.config: + outbound_list.append(self.config["outbound"]) + del self.config["outbound"] + if "outboundDetour" in self.config and self.config["outboundDetour"]: + outbound_list.extend([ x for x in self.config["outboundDetour"] ]) + del self.config["outboundDetour"] + if outbound_list: + self.config["outbounds"] = outbound_list + + # 转换路由 + if "routing" in self.config and "settings" in self.config["routing"]: + self.config["routing"]["rules"] = self.config["routing"]["settings"]["rules"] + del self.config["routing"]["strategy"] + del self.config["routing"]["settings"] + + self.save() + +if __name__ == '__main__': + print("转换配置文件格式中..") + ConfigConverter().transform() \ No newline at end of file diff --git a/docker/v2ray/Dockerfile b/docker/v2ray/Dockerfile deleted file mode 100644 index 1292fba6..00000000 --- a/docker/v2ray/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM --platform=$TARGETPLATFORM centos:7 as builder - -RUN curl -L -s https://multi.netlify.app/go.sh|bash - -FROM --platform=$TARGETPLATFORM alpine:latest - -LABEL maintainer "Jrohy " - -ENV COMPLETION_FILE "/usr/share/bash-completion/completions/v2ray" - -ENV SOURCE_COMPLETION_FILE "https://multi.netlify.app/v2ray" - -ENV VERSION_LIST "https://api.github.com/repos/Jrohy/multi-v2ray/tags" - -COPY --from=builder /usr/bin/v2ray/v2ray /usr/bin/v2ray/ -COPY --from=builder /usr/bin/v2ray/geoip.dat /usr/bin/v2ray/ -COPY --from=builder /usr/bin/v2ray/geosite.dat /usr/bin/v2ray/ -COPY run.sh /root - -WORKDIR /root - -RUN apk --no-cache add python3 bash bash-completion ca-certificates curl socat openssl iptables ip6tables && \ - python3 -m ensurepip && \ - rm -r /usr/lib/python*/ensurepip && \ - ln -s $(which pip3) /usr/local/bin/pip && \ - LATEST_VERSION=`curl -s $VERSION_LIST|grep name|grep -o "[0-9].*[0-9]"|head -n 1` && \ - pip install v2ray-util==$LATEST_VERSION && \ - curl $SOURCE_COMPLETION_FILE > $COMPLETION_FILE && \ - mkdir /var/log/v2ray/ && \ - chmod +x /usr/bin/v2ray/v2ray && \ - chmod +x /root/run.sh && \ - chmod +x $COMPLETION_FILE && \ - echo "source $COMPLETION_FILE" > /root/.bashrc && \ - ln -s $(which v2ray-util) /usr/local/bin/v2ray && \ - rm -r /root/.cache - -ENTRYPOINT ["./run.sh"] \ No newline at end of file diff --git a/docker/v2ray/run.sh b/docker/v2ray/run.sh deleted file mode 100644 index dc3d582c..00000000 --- a/docker/v2ray/run.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -uptime=`cat /proc/uptime |awk '{print $1}'` -if [[ `echo "$uptime < 100"|bc` -eq 1 ]];then - local_ip=`curl -s http://api.ipify.org 2>/dev/null` - if [[ -e /root/.iptables ]];then - [[ `echo $local_ip|grep :` ]] && iptable_way="ip6tables" || iptable_way="iptables" - $iptable_way-restore -c < /root/.iptables - fi -fi - -if [[ ! -e /etc/v2ray ]];then - mkdir /etc/v2ray - v2ray new >/dev/null 2>&1 -fi - -touch /.run.log -/usr/bin/v2ray/v2ray run -c /etc/v2ray/config.json > /.run.log & - -tail -f /.run.log diff --git a/docker/xray/Dockerfile b/docker/xray/Dockerfile deleted file mode 100644 index 5ae1b78c..00000000 --- a/docker/xray/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM --platform=$TARGETPLATFORM centos:7 as builder - -RUN curl -L -s https://multi.netlify.app/go.sh -o go.sh && bash go.sh -x - -FROM --platform=$TARGETPLATFORM alpine:latest - -LABEL maintainer "Jrohy " - -ENV COMPLETION_FILE "/usr/share/bash-completion/completions/v2ray" - -ENV SOURCE_COMPLETION_FILE "https://multi.netlify.app/v2ray" - -ENV VERSION_LIST "https://api.github.com/repos/Jrohy/multi-v2ray/tags" - -COPY --from=builder /usr/bin/xray/xray /usr/bin/xray/ -COPY --from=builder /usr/bin/xray/geoip.dat /usr/bin/xray/ -COPY --from=builder /usr/bin/xray/geosite.dat /usr/bin/xray/ -COPY run.sh /root - -WORKDIR /root - -RUN apk --no-cache add python3 bash bash-completion ca-certificates curl socat openssl iptables ip6tables && \ - python3 -m ensurepip && \ - rm -r /usr/lib/python*/ensurepip && \ - ln -s $(which pip3) /usr/local/bin/pip && \ - LATEST_VERSION=`curl -s $VERSION_LIST|grep name|grep -o "[0-9].*[0-9]"|head -n 1` && \ - pip install v2ray-util==$LATEST_VERSION && \ - curl $SOURCE_COMPLETION_FILE > $COMPLETION_FILE && \ - mkdir /var/log/xray/ && \ - chmod +x /usr/bin/xray/xray && \ - chmod +x /root/run.sh && \ - chmod +x $COMPLETION_FILE && \ - echo "source $COMPLETION_FILE" > /root/.bashrc && \ - ln -s $(which v2ray-util) /usr/local/bin/xray && \ - rm -r /root/.cache - -ENTRYPOINT ["./run.sh"] \ No newline at end of file diff --git a/docker/xray/run.sh b/docker/xray/run.sh deleted file mode 100644 index 65c02505..00000000 --- a/docker/xray/run.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -uptime=`cat /proc/uptime |awk '{print $1}'` -if [[ `echo "$uptime < 100"|bc` -eq 1 ]];then - local_ip=`curl -s http://api.ipify.org 2>/dev/null` - if [[ -e /root/.iptables ]];then - [[ `echo $local_ip|grep :` ]] && iptable_way="ip6tables" || iptable_way="iptables" - $iptable_way-restore -c < /root/.iptables - fi -fi - -if [[ ! -e /etc/xray ]];then - mkdir /etc/xray - xray new >/dev/null 2>&1 -fi - -touch /.run.log -/usr/bin/xray/xray run -c /etc/xray/config.json > /.run.log & - -tail -f /.run.log diff --git a/fun_img/1.png b/fun_img/1.png new file mode 100644 index 00000000..84e07da3 Binary files /dev/null and b/fun_img/1.png differ diff --git a/fun_img/2.png b/fun_img/2.png new file mode 100644 index 00000000..7110e878 Binary files /dev/null and b/fun_img/2.png differ diff --git a/fun_img/3.png b/fun_img/3.png new file mode 100644 index 00000000..748d27b6 Binary files /dev/null and b/fun_img/3.png differ diff --git a/fun_img/4.png b/fun_img/4.png new file mode 100644 index 00000000..ff66d4ef Binary files /dev/null and b/fun_img/4.png differ diff --git a/global_setting/ban_bt.py b/global_setting/ban_bt.py new file mode 100644 index 00000000..3762fa6e --- /dev/null +++ b/global_setting/ban_bt.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from loader import Loader +from writer import GlobalWriter + +if __name__ == '__main__': + loader = Loader() + + profile = loader.profile + + print("当前禁止BT状态: {}".format(profile.ban_bt)) + + choice = input("是否禁止BT(y/n):").lower() + + ban_bt = True if choice == 'y' else False + + gw = GlobalWriter(profile.group_list) + + gw.write_ban_bittorrent(ban_bt) + + print("修改成功!") \ No newline at end of file diff --git a/global_setting/stats_ctr.py b/global_setting/stats_ctr.py new file mode 100644 index 00000000..27afe917 --- /dev/null +++ b/global_setting/stats_ctr.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import os +import re + +from loader import Loader +from writer import GlobalWriter +from utils import bytes_2_human_readable, color_str, Color + +class StatsFactory: + def __init__(self, door_port): + self.door_port = door_port + self.downlink_value = 0 + self.uplink_value = 0 + + def get_stats(self, meta_info, is_reset=False, is_group=False): + is_reset = "true" if is_reset else "false" + + stats_cmd = "cd /usr/bin/v2ray && ./v2ctl api --server=127.0.0.1:%s StatsService.GetStats 'name: \"%s>>>%s>>>traffic>>>%s\" reset: %s'" + type_tag = ("inbound" if is_group else "user") + + stats_real_cmd = stats_cmd % (str(self.door_port), type_tag, meta_info, "downlink", is_reset) + downlink_result = os.popen(stats_real_cmd).readlines() + if downlink_result and len(downlink_result) == 5: + re_result = re.findall(r"\d+", downlink_result[2]) + if not re_result: + print("当前无流量数据,请使用流量片刻再来查看统计!") + return + self.downlink_value = int(re_result[0]) + + stats_real_cmd = stats_cmd % (str(self.door_port), type_tag, meta_info, "uplink", is_reset) + uplink_result = os.popen(stats_real_cmd).readlines() + if uplink_result and len(uplink_result) == 5: + re_result = re.findall(r"\d+", uplink_result[2]) + self.uplink_value = int(re_result[0]) + + def print_stats(self): + print(''' +downlink: {0} +uplink: {1} +total: {2} + '''.format(color_str(Color.CYAN, bytes_2_human_readable(self.downlink_value, 2)), + color_str(Color.CYAN, bytes_2_human_readable(self.uplink_value, 2)), + color_str(Color.CYAN, bytes_2_human_readable(self.downlink_value + self.uplink_value, 2))) + ) + + +if __name__ == '__main__': + + RESTART_CMD = "service v2ray restart" + + FIND_V2RAY_CRONTAB_CMD = "crontab -l|grep v2ray" + + DEL_UPDATE_TIMER_CMD = "crontab -l|sed '/SHELL=/d;/v2ray/d' > crontab.txt && crontab crontab.txt >/dev/null 2>&1 && rm -f crontab.txt >/dev/null 2>&1" + + while True: + loader = Loader() + + profile = loader.profile + + group_list = profile.group_list + + print("当前流量统计状态: {}".format(profile.stats.status)) + + print("") + print("1.开启流量统计\n") + print("2.关闭流量统计\n") + print("3.查看流量统计\n") + print("4.重置流量统计\n") + print("tip: 具有有效email节点才会统计, 重启v2ray会重置流量统计!!!\n") + + choice = input("请输入数字选择功能:") + if choice == "1": + if os.popen(FIND_V2RAY_CRONTAB_CMD).readlines(): + rchoice = input("开启流量统计会关闭定时更新v2ray服务, 是否继续y/n: ") + if rchoice == "y" or rchoice == "Y": + #关闭定时更新v2ray服务 + os.system(DEL_UPDATE_TIMER_CMD) + else: + print("撤销开启流量统计!!") + continue + gw = GlobalWriter(group_list) + gw.write_stats(True) + os.system(RESTART_CMD) + print("开启流量统计成功!\n") + + elif choice == "2": + gw = GlobalWriter(group_list) + gw.write_stats(False) + os.system(RESTART_CMD) + print("关闭流量统计成功!\n") + elif choice == "3" or choice == "4": + is_reset = (False if choice == "3" else True) + action_info = ("查看" if choice == "3" else "重置") + if not profile.stats.status: + print("流量统计开启状态才能{}统计\n".format(action_info)) + continue + + if len(group_list) > 1: + print(profile) + schoice = input("请输入所需要{}流量的组别(字母)或者序号(数字): ".format(action_info)) + else: + schoice = "A" + + sf = StatsFactory(profile.stats.door_port) + schoice=schoice.upper() + if schoice.isnumeric() : + schoice = int(schoice) + if schoice > 0 and schoice <= group_list[-1].node_list[-1].user_number: + find = False + for group in group_list: + if find: + break + for index, node in enumerate(group.node_list): + if node.user_number == schoice: + if node.user_info: + sf.get_stats(node.user_info, is_reset) + sf.print_stats() + else: + print("无有效邮箱,无法统计!!!\n") + find = True + break + elif schoice.isalpha() and schoice <= group_list[-1].tag: + sf.get_stats(schoice, is_reset, True) + sf.print_stats() + else: + print("输入有误! 请重新输入\n") + else: + break \ No newline at end of file diff --git a/global_setting/update_timer.sh b/global_setting/update_timer.sh new file mode 100644 index 00000000..ede9ab1a --- /dev/null +++ b/global_setting/update_timer.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +#定时任务北京执行时间(0~23) +BEIJING_UPDATE_TIME=3 + +#######color code######## +RED="31m" # Error message +GREEN="32m" # Success message +YELLOW="33m" # Warning message +BLUE="36m" # Info message + +########################## +colorEcho(){ + COLOR=$1 + echo -e "\033[${COLOR}${@:2}\033[0m" +} + +#检查是否为Root +[ $(id -u) != "0" ] && { colorEcho ${RED} "Error: You must be root to run this script"; exit 1; } + +#检查系统信息 +if [ -f /etc/redhat-release ];then + OS='CentOS' + elif [ ! -z "`cat /etc/issue | grep bian`" ];then + OS='Debian' + elif [ ! -z "`cat /etc/issue | grep Ubuntu`" ];then + OS='Ubuntu' + else + colorEcho ${RED} "Not support OS, Please reinstall OS and retry!" + exit 1 +fi + +#设置定时升级任务 +planUpdate(){ + #计算北京时间早上3点时VPS的实际时间 + ORIGIN_TIME_ZONE=$(date -R|awk '{printf"%d",$6}') + LOCAL_TIME_ZONE=${ORIGIN_TIME_ZONE%00} + BEIJING_ZONE=8 + DIFF_ZONE=$[$BEIJING_ZONE-$LOCAL_TIME_ZONE] + LOCAL_TIME=$[$BEIJING_UPDATE_TIME-$DIFF_ZONE] + if [ $LOCAL_TIME -lt 0 ];then + LOCAL_TIME=$[24+$LOCAL_TIME] + elif [ $LOCAL_TIME -ge 24 ];then + LOCAL_TIME=$[$LOCAL_TIME-24] + fi + colorEcho ${BLUE} "北京时间${BEIJING_UPDATE_TIME}点,VPS时间为${LOCAL_TIME}点\n" + + OLD_CRONTAB=$(crontab -l) + echo "SHELL=/bin/bash" >> crontab.txt + echo "${OLD_CRONTAB}" >> crontab.txt + echo "0 ${LOCAL_TIME} * * * bash <(curl -L -s https://install.direct/go.sh) | tee -a /root/v2rayUpdate.log && service v2ray restart" >> crontab.txt + crontab crontab.txt + sleep 1 + if [[ "${OS}" == "CentOS" ]];then + service crond restart + else + service cron restart + fi + rm -f crontab.txt + colorEcho ${GREEN} "成功配置每天北京时间${BEIJING_UPDATE_TIME}点自动升级V2ray内核任务\n" +} + +[[ -z $(crontab -l|grep v2ray) ]] && IS_OPEN="关闭" || IS_OPEN="开启" + +echo -e "当前定时更新任务状态: ${IS_OPEN}\n" + +echo -e "" +echo -e "1.开启定时更新任务\n" +echo -e "2.关闭定时更新任务\n" +echo -e "Tip: 开启定时更新v2ray的更新时间为每天北京时间3:00更新" + +while :; do echo + read -n1 -p "请选择: " CHOICE + if [[ ! $CHOICE =~ ^[1-2]$ ]]; then + if [[ -z ${CHOICE} ]];then + bash /usr/local/bin/v2ray + exit 0 + fi + colorEcho ${RED} "输入错误! 请输入正确的数字!" + else + echo -e "\n" + break + fi +done + +if [[ ${CHOICE} == 1 ]]; then + if [[ ${IS_OPEN} == "开启" ]]; then + colorEcho ${YELLOW} "当前定时更新已开启,无需重复操作!\n" + bash /usr/local/bin/v2ray + exit 0 + fi + #设置定时任务 + planUpdate +else + #删除v2ray定时更新任务 + crontab -l|sed '/SHELL=/d;/v2ray/d' > crontab.txt + crontab crontab.txt >/dev/null 2>&1 + rm -f crontab.txt >/dev/null 2>&1 + if [[ "${OS}" == "CentOS" ]];then + service crond restart >/dev/null 2>&1 + else + service cron restart >/dev/null 2>&1 + fi + colorEcho ${GREEN} "成功关闭定时更新任务\n" +fi \ No newline at end of file diff --git a/go.sh b/go.sh deleted file mode 100644 index 77fd4a4b..00000000 --- a/go.sh +++ /dev/null @@ -1,559 +0,0 @@ -#!/bin/bash - -# This file is accessible as https://multi.netlify.app/go.sh - -# If not specify, default meaning of return value: -# 0: Success -# 1: System error -# 2: Application error -# 3: Network error - -# CLI arguments -proxy='' -help='' -force='' -check='' -remove='' -version='' -vsrc_root='/tmp/v2ray' -extract_only='' -local='' -local_install='' -error_if_uptodate='' - -cur_ver="" -new_ver="" -zipfile="/tmp/v2ray/v2ray.zip" -v2ray_running=0 - -cmd_install="" -cmd_update="" -software_updated=0 -key="V2Ray" -key_lower="v2ray" -repos="v2fly/v2ray-core" - -systemctl_cmd=$(command -v systemctl 2>/dev/null) - -#######color code######## -red="31m" # Error message -green="32m" # Success message -yellow="33m" # Warning message -blue="36m" # Info message - -xray_set(){ - key="Xray" - key_lower="xray" - repos="XTLS/Xray-core" - vsrc_root='/tmp/xray' - zipfile="/tmp/xray/xray.zip" -} - -######################### -while [[ $# > 0 ]]; do - case "$1" in - -p|--proxy) - proxy="-x ${2}" - shift # past argument - ;; - -h|--help) - help="1" - ;; - -f|--force) - force="1" - ;; - -c|--check) - check="1" - ;; - -x|--xray) - xray_set - ;; - --remove) - remove="1" - ;; - --version) - version="$2" - shift - ;; - --extract) - vsrc_root="$2" - shift - ;; - --extractonly) - extract_only="1" - ;; - -l|--local) - local="$2" - local_install="1" - shift - ;; - --errifuptodate) - error_if_uptodate="1" - ;; - *) - # unknown option - ;; - esac - shift # past argument or value -done - -############################### -colorEcho(){ - echo -e "\033[${1}${@:2}\033[0m" 1>& 2 -} - -archAffix(){ - case "$(uname -m)" in - 'i386' | 'i686') - machine='32' - ;; - 'amd64' | 'x86_64') - machine='64' - ;; - 'armv5tel') - machine='arm32-v5' - ;; - 'armv6l') - machine='arm32-v6' - grep Features /proc/cpuinfo | grep -qw 'vfp' || machine='arm32-v5' - ;; - 'armv7' | 'armv7l') - machine='arm32-v7a' - grep Features /proc/cpuinfo | grep -qw 'vfp' || machine='arm32-v5' - ;; - 'armv8' | 'aarch64') - machine='arm64-v8a' - ;; - 'mips') - machine='mips32' - ;; - 'mipsle') - machine='mips32le' - ;; - 'mips64') - machine='mips64' - ;; - 'mips64le') - machine='mips64le' - ;; - 'ppc64') - machine='ppc64' - ;; - 'ppc64le') - machine='ppc64le' - ;; - 'riscv64') - machine='riscv64' - ;; - 's390x') - machine='s390x' - ;; - *) - echo "error: The architecture is not supported." - exit 1 - ;; - esac - - return 0 -} - -zipRoot() { - unzip -lqq "$1" | awk -e ' - NR == 1 { - prefix = $4; - } - NR != 1 { - prefix_len = length(prefix); - cur_len = length($4); - - for (len = prefix_len < cur_len ? prefix_len : cur_len; len >= 1; len -= 1) { - sub_prefix = substr(prefix, 1, len); - sub_cur = substr($4, 1, len); - - if (sub_prefix == sub_cur) { - prefix = sub_prefix; - break; - } - } - - if (len == 0) { - prefix = ""; - nextfile; - } - } - END { - print prefix; - } - ' -} - -downloadV2Ray(){ - rm -rf /tmp/$key_lower - mkdir -p /tmp/$key_lower - local pack_name=$key_lower - [[ $key == "Xray" ]] && pack_name=$key - download_link="https://github.com/$repos/releases/download/${new_ver}/${pack_name}-linux-${machine}.zip" - colorEcho ${blue} "Downloading $key: ${download_link}" - curl ${proxy} -L -H "Cache-Control: no-cache" -o ${zipfile} ${download_link} - if [ $? != 0 ];then - colorEcho ${red} "Failed to download! Please check your network or try again." - return 3 - fi - return 0 -} - -installSoftware(){ - component=$1 - if [[ -n `command -v $component` ]]; then - return 0 - fi - - getPMT - if [[ $? -eq 1 ]]; then - colorEcho ${red} "The system package manager tool isn't APT or YUM, please install ${component} manually." - return 1 - fi - if [[ $software_updated -eq 0 ]]; then - colorEcho ${blue} "Updating software repo" - $cmd_update - software_updated=1 - fi - - colorEcho ${blue} "Installing ${component}" - $cmd_install $component - if [[ $? -ne 0 ]]; then - colorEcho ${red} "Failed to install ${component}. Please install it manually." - return 1 - fi - return 0 -} - -# return 1: not apt, yum, or zypper -getPMT(){ - if [[ -n `command -v apt-get` ]];then - cmd_install="apt-get -y -qq install" - cmd_update="apt-get -qq update" - elif [[ -n `command -v yum` ]]; then - cmd_install="yum -y -q install" - cmd_update="yum -q makecache" - elif [[ -n `command -v zypper` ]]; then - cmd_install="zypper -y install" - cmd_update="zypper ref" - else - return 1 - fi - return 0 -} - -normalizeVersion() { - if [ -n "$1" ]; then - case "$1" in - v*) - echo "$1" - ;; - *) - echo "v$1" - ;; - esac - else - echo "" - fi -} - -# 1: new V2Ray. 0: no. 2: not installed. 3: check failed. 4: don't check. -getVersion(){ - if [[ -n "$version" ]]; then - new_ver="$(normalizeVersion "$version")" - return 4 - else - ver="$(/usr/bin/$key_lower/$key_lower -version 2>/dev/null)" - [[ -z $ver ]] && ver="$(/usr/bin/$key_lower/$key_lower version 2>/dev/null)" - retval=$? - cur_ver="$(normalizeVersion "$(echo "$ver" | head -n 1 | cut -d " " -f2)")" - tag_url="https://api.github.com/repos/$repos/releases/latest" - new_ver="$(normalizeVersion "$(curl ${proxy} -H "Accept: application/json" -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0" -s "${tag_url}" --connect-timeout 10| grep 'tag_name' | cut -d\" -f4)")" - - if [[ $? -ne 0 ]] || [[ $new_ver == "" ]]; then - colorEcho ${red} "Failed to fetch release information. Please check your network or try again." - return 3 - elif [[ $retval -ne 0 ]];then - return 2 - elif [[ $new_ver != $cur_ver ]];then - return 1 - fi - return 0 - fi -} - -stopV2ray(){ - colorEcho ${blue} "Shutting down $key service." - if [[ -n "${systemctl_cmd}" ]] || [[ -f "/lib/systemd/system/$key_lower.service" ]] || [[ -f "/etc/systemd/system/$key_lower.service" ]]; then - ${systemctl_cmd} stop $key_lower - fi - if [[ $? -ne 0 ]]; then - colorEcho ${yellow} "Failed to shutdown $key service." - return 2 - fi - return 0 -} - -startV2ray(){ - if [ -n "${systemctl_cmd}" ] && [[ -f "/lib/systemd/system/$key_lower.service" || -f "/etc/systemd/system/$key_lower.service" ]]; then - ${systemctl_cmd} start $key_lower - fi - if [[ $? -ne 0 ]]; then - colorEcho ${yellow} "Failed to start $key service." - return 2 - fi - return 0 -} - -installV2Ray(){ - # Install $key binary to /usr/bin/$key_lower - if [[ $key == "V2Ray" && `unzip -l $1|grep v2ctl` ]];then - unzip_param="$2v2ctl" - chmod_param="/usr/bin/$key_lower/v2ctl" - fi - mkdir -p /etc/$key_lower /var/log/$key_lower && \ - unzip -oj "$1" "$2${key_lower}" "$2geoip.dat" "$2geosite.dat" $unzip_param -d /usr/bin/$key_lower && \ - chmod +x /usr/bin/$key_lower/$key_lower $chmod_param || { - colorEcho ${red} "Failed to copy $key binary and resources." - return 1 - } - - # Install V2Ray server config to /etc/v2ray - if [ ! -f /etc/$key_lower/config.json ]; then - local port="$(($RANDOM + 10000))" - local uuid="$(cat '/proc/sys/kernel/random/uuid')" - - if [[ $key == "Xray" ]];then - cat > /etc/$key_lower/config.json < \ - /etc/$key_lower/config.json || { - colorEcho ${yellow} "Failed to create $key configuration file. Please create it manually." - return 1 - } - fi - - colorEcho ${blue} "port:${port}" - colorEcho ${blue} "uuid:${uuid}" - fi -} - - -installInitScript(){ - if [[ -e /.dockerenv ]]; then - if [[ $key_lower == "v2ray" ]];then - if [[ ${new_ver} =~ "v4" ]];then - sed -i "s/run -c/-config/g" /root/run.sh - else - sed -i "s/-config/run -c/g" /root/run.sh - fi - fi - return - fi - if [[ ! -f "/etc/systemd/system/$key_lower.service" && ! -f "/lib/systemd/system/$key_lower.service" ]]; then - cat > /etc/systemd/system/$key_lower.service <& 2 << EOF -./go.sh [-h] [-c] [--remove] [-p proxy] [-f] [--version vx.y.z] [-l file] [-x] - -h, --help Show help - -p, --proxy To download through a proxy server, use -p socks5://127.0.0.1:1080 or -p http://127.0.0.1:3128 etc - -f, --force Force install - --version Install a particular version, use --version v3.15 - -l, --local Install from a local file - --remove Remove installed V2Ray/Xray - -x, --xray Xray mod - -c, --check Check for update -EOF -} - -remove(){ - if [[ -n "${systemctl_cmd}" ]] && [[ -f "/etc/systemd/system/$key_lower.service" ]];then - if pgrep "$key_lower" > /dev/null ; then - stopV2ray - fi - systemctl disable $key_lower.service - rm -rf "/usr/bin/$key_lower" "/etc/systemd/system/$key_lower.service" - if [[ $? -ne 0 ]]; then - colorEcho ${red} "Failed to remove $key." - return 0 - else - colorEcho ${green} "Removed $key successfully." - colorEcho ${blue} "If necessary, please remove configuration file and log file manually." - return 0 - fi - elif [[ -n "${systemctl_cmd}" ]] && [[ -f "/lib/systemd/system/$key_lower.service" ]];then - if pgrep "$key_lower" > /dev/null ; then - stopV2ray - fi - systemctl disable $key_lower.service - rm -rf "/usr/bin/$key_lower" "/lib/systemd/system/$key_lower.service" - if [[ $? -ne 0 ]]; then - colorEcho ${red} "Failed to remove $key." - return 0 - else - colorEcho ${green} "Removed $key successfully." - colorEcho ${blue} "If necessary, please remove configuration file and log file manually." - return 0 - fi - else - colorEcho ${yellow} "$key not found." - return 0 - fi -} - -checkUpdate(){ - echo "Checking for update." - version="" - getVersion - retval="$?" - if [[ $retval -eq 1 ]]; then - colorEcho ${blue} "Found new version ${new_ver} for $key.(Current version:$cur_ver)" - elif [[ $retval -eq 0 ]]; then - colorEcho ${blue} "No new version. Current version is ${new_ver}." - elif [[ $retval -eq 2 ]]; then - colorEcho ${yellow} "No $key installed." - colorEcho ${blue} "The newest version for $key is ${new_ver}." - fi - return 0 -} - -main(){ - #helping information - [[ "$help" == "1" ]] && Help && return - [[ "$check" == "1" ]] && checkUpdate && return - [[ "$remove" == "1" ]] && remove && return - - local arch=$(uname -m) - archAffix - - # extract local file - if [[ $local_install -eq 1 ]]; then - colorEcho ${yellow} "Installing $key via local file. Please make sure the file is a valid $key package, as we are not able to determine that." - new_ver=local - rm -rf /tmp/$key_lower - zipfile="$local" - else - # download via network and extract - installSoftware "curl" || return $? - getVersion - retval="$?" - if [[ $retval == 0 ]] && [[ "$force" != "1" ]]; then - colorEcho ${blue} "Latest version ${cur_ver} is already installed." - if [ -n "${error_if_uptodate}" ]; then - return 10 - fi - return - elif [[ $retval == 3 ]]; then - return 3 - else - colorEcho ${blue} "Installing $key ${new_ver} on ${arch}" - downloadV2Ray || return $? - fi - fi - - local ziproot="$(zipRoot "${zipfile}")" - installSoftware unzip || return $? - - if [ -n "${extract_only}" ]; then - colorEcho ${blue} "Extracting $key package to ${vsrc_root}." - - if unzip -o "${zipfile}" -d ${vsrc_root}; then - colorEcho ${green} "$key extracted to ${vsrc_root%/}${ziproot:+/${ziproot%/}}, and exiting..." - return 0 - else - colorEcho ${red} "Failed to extract $key." - return 2 - fi - fi - - if pgrep "$key_lower" > /dev/null ; then - v2ray_running=1 - stopV2ray - fi - installV2Ray "${zipfile}" "${ziproot}" || return $? - installInitScript "${zipfile}" "${ziproot}" || return $? - if [[ ${v2ray_running} -eq 1 ]];then - colorEcho ${blue} "Restarting $key service." - startV2ray - fi - colorEcho ${green} "$key ${new_ver} is installed." - rm -rf /tmp/$key_lower - return 0 -} - -main diff --git a/group.py b/group.py new file mode 100644 index 00000000..6d65801d --- /dev/null +++ b/group.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import json +import base64 + +from utils import color_str, Color + +__author__ = 'Jrohy' + +class Dyport: + def __init__(self, status=False, aid=0): + self.status = status + self.aid = aid + + def __str__(self): + return "开启,alterId为{}".format(self.aid) if self.status else "关闭" + +class User: + def __init__(self, user_number, password, user_info=None): + """ + user_info可能是email, 也可能user_name, 具体取决于group的protocol + """ + self.__password = password + self.user_info = user_info + self.user_number = user_number + + @property + def password(self): + return self.__password + +class SS(User): + def __init__(self, user_number, password, method, user_info): + super(SS, self).__init__(user_number, password, user_info) + self.method = method + + def __str__(self): + if self.user_info: + return "Email: {self.user_info}\nMethod: {self.method}\nPassword: {password}\n".format(self=self, password=self.password) + else: + return "Method: {self.method}\nPassword: {password}\n".format(self=self, password=self.password) + + def link(self, ip, port, tls): + ss_origin_url = "{0}:{1}@{2}:{3}".format(self.method, self.password, ip, port) + return color_str(Color.GREEN, "ss://{}".format(bytes.decode(base64.b64encode(bytes(ss_origin_url, 'utf-8'))))) + + def stream(self): + return "shadowsocks" + +class Mtproto(User): + def __str__(self): + if self.user_info: + return "Email: {}\nSecret: {}\n".format(self.user_info, self.password) + else: + return "Secret: {}\n".format(self.password) + + def link(self, ip, port, tls): + return color_str(Color.GREEN, "tg://proxy?server={0}&port={1}&secret={2}".format(ip, port, self.password)) + + def stream(self): + return "mtproto" + +class Socks(User): + def __str__(self): + return "User: {0}\nPass: {1}\nUDP: true\n".format(self.user_info, self.password) + + def link(self, ip, port, tls): + if tls == "tls": + return color_str(Color.RED, "HTTPS的Socks5不支持tg的分享连接. 请自行配合设置BifrostV等软件使用") + else: + return color_str(Color.GREEN, "tg://socks?server={0}&port={1}&user={2}&pass={3}".format(ip, port, self.user_info, self.password)) + + def stream(self): + return "socks" + +class Vmess(User): + def __init__(self, uuid, alter_id: int, network: str, user_number, *, path=None, host=None, header=None, email=None): + super(Vmess, self).__init__(user_number, uuid, email) + self.alter_id = alter_id + self.network = network + self.path = path + self.host = host + self.header = header + + def stream(self): + if self.network == "h2": + network = "HTTP/2 path: {}".format(self.path) + elif self.network == "ws": + network = "WebSocket host: {0}, path: {1}".format(self.host, self.path) + elif self.network == "tcp": + if self.host: + network = "tcp host: {0}".format(self.host) + else: + network = "tcp" + else: + if self.header and self.header != 'none': + network = "{} {}".format(self.network, self.header) + else: + network = "{}".format(self.network) + return network + + def __str__(self): + email = "" + if self.user_info: + email = "Email: {}".format(self.user_info) + result = ''' +{email} +UUID: {uuid} +Alter ID: {self.alter_id} +Network: {network} +'''.format(self=self, uuid=self.password, email=email, network=self.stream()).strip() + "\n" + return result + + def link(self, ip, port, tls): + json_dict = { + "v": "2", + "ps": "", + "add": ip, + "port": port, + "aid": self.alter_id, + "type": self.header, + "net": self.network, + "path": self.path, + "host": self.host, + "id": self.password, + "tls": tls + } + json_data = json.dumps(json_dict) + return color_str(Color.GREEN, "vmess://{}".format(bytes.decode(base64.b64encode(bytes(json_data, 'utf-8'))))) + +class Group: + def __init__(self, ip, port, *, end_port=None, tfo=None, tls="none", dyp=Dyport(), index=0, tag='A'): + self.ip = ip + self.port = port + self.end_port = end_port + self.tag = tag + self.node_list = [] + self.tfo = tfo + self.tls = tls + self.dyp = dyp + self.protocol = None + self.index = index + + def show_node(self, index): + tls = "开启" if self.tls == "tls" else "关闭" + tfo = "TcpFastOpen: {}".format(self.tfo) if self.tfo != None else "" + dyp = "DynamicPort: {}".format(self.dyp) if self.dyp.status else "" + port_way = "-{}".format(self.end_port) if self.end_port else "" + node = self.node_list[index] + result = ''' +{node.user_number}. +Group: {self.tag} +IP: {color_ip} +Port: {self.port}{port_way} +TLS: {tls} +{node}{tfo} +{dyp} +{link} + '''.format(self=self, color_ip=color_str(Color.FUCHSIA, self.ip), port_way=port_way, node=node,tfo=tfo, dyp=dyp,tls=tls, link=node.link(self.ip, int(self.port), self.tls)) + return result + + # print一个实例打印的字符串 + def __str__(self): + tls = "开启" if self.tls == "tls" else "关闭" + tfo = "TcpFastOpen: {}".format(self.tfo) if self.tfo != None else "" + dyp = "DynamicPort: {}".format(self.dyp) if self.dyp.status else "" + port_way = "-{}".format(self.end_port) if self.end_port else "" + result = "" + for node in self.node_list: + temp = ''' +{node.user_number}. +Group: {self.tag} +IP: {color_ip} +Port: {self.port}{port_way} +TLS: {tls} +{node}{tfo} +{dyp} + '''.format(self=self, color_ip=color_str(Color.FUCHSIA, self.ip), node=node,tfo=tfo,dyp=dyp,tls=tls, port_way=port_way) + result = "{0}{1}\n\n{2}\n\n".format(result, temp.strip(), node.link(self.ip, int(self.port), self.tls)) + return result + + # 直接调用实例和打印一个实例显示的字符串一样 + def __repr__ (self): + return self.__str__ \ No newline at end of file diff --git a/json_template/client.json b/json_template/client.json new file mode 100644 index 00000000..2365df19 --- /dev/null +++ b/json_template/client.json @@ -0,0 +1,87 @@ +{ + "log": { + "access": "", + "error": "", + "loglevel": "info" + }, + "inbounds": [ + { + "port": 1080, + "listen": "0.0.0.0", + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "ip": "127.0.0.1", + "clients": null + }, + "streamSettings": null + } + ], + "outbounds": [ + { + "protocol": "vmess", + "settings": { + "vnext": [ + { + "address": "123.ocm", + "port": 1234, + "users": [ + { + "id": "cc4f8d5b-967b-4557-a4b6-bde92965bc27", + "alterId": 100, + "security": "aes-128-gcm" + } + ] + } + ] + }, + "streamSettings": { + "security": "", + "tlsSettings": {}, + "wsSettings": null, + "httpSettings": null, + "network": "tcp", + "kcpSettings": null, + "tcpSettings": null + }, + "mux": { + "enabled": true + } + }, + { + "protocol": "freedom", + "settings": { + "response": null + }, + "tag": "direct" + } + ], + "dns": { + "servers": [ + "8.8.8.8", + "8.8.4.4", + "localhost" + ] + }, + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "ip": ["geoip:private"], + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geosite:cn"], + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geoip:cn"], + "outboundTag": "direct" + } + ] + } +} diff --git a/json_template/client_socks.json b/json_template/client_socks.json new file mode 100644 index 00000000..986b3e9b --- /dev/null +++ b/json_template/client_socks.json @@ -0,0 +1,80 @@ +{ + "log": { + "access": "", + "error": "", + "loglevel": "info" + }, + "inbounds": [ + { + "port": 1080, + "listen": "0.0.0.0", + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "ip": "127.0.0.1", + "clients": null + }, + "streamSettings": null + } + ], + "outbounds": + [ + { + "protocol": "socks", + "settings": { + "servers": [ + { + "address": "123.ocm", + "port": 1234, + "users": [ + { + "user": "hello", + "pass": "3.1415" + } + ] + } + ] + }, + "streamSettings": { + "security": "", + "tlsSettings": {}, + "wsSettings": null, + "httpSettings": null, + "network": "tcp", + "kcpSettings": null, + "tcpSettings": null + }, + "mux": { + "enabled": true + } + }, + { + "protocol": "freedom", + "settings": { + "response": null + }, + "tag": "direct" + } + ], + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "ip": ["geoip:private"], + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geosite:cn"], + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geoip:cn"], + "outboundTag": "direct" + } + ] + } +} diff --git a/json_template/client_ss.json b/json_template/client_ss.json new file mode 100644 index 00000000..621e0a00 --- /dev/null +++ b/json_template/client_ss.json @@ -0,0 +1,64 @@ +{ + "log": { + "access": "", + "error": "", + "loglevel": "info" + }, + "inbounds": [ + { + "port": 1080, + "listen": "0.0.0.0", + "protocol": "socks", + "settings": { + "auth": "noauth", + "udp": true, + "ip": "127.0.0.1", + "clients": null + }, + "streamSettings": null + } + ], + "outbounds": [ + { + "protocol": "shadowsocks", + "settings": { + "servers": [ + { + "address": "serveraddr.com", + "method": "aes-128-gcm", + "ota": false, + "password": "sspasswd", + "port": 1024 + } + ] + } + }, + { + "protocol": "freedom", + "settings": { + "response": null + }, + "tag": "direct" + } + ], + "routing": { + "domainStrategy": "IPIfNonMatch", + "rules": [ + { + "type": "field", + "ip": ["geoip:private"], + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geosite:cn"], + "outboundTag": "direct" + }, + { + "type": "field", + "domain": ["geoip:cn"], + "outboundTag": "direct" + } + ] + } +} \ No newline at end of file diff --git a/v2ray_util/json_template/dyn_port.json b/json_template/dyn_port.json similarity index 100% rename from v2ray_util/json_template/dyn_port.json rename to json_template/dyn_port.json diff --git a/v2ray_util/json_template/http.json b/json_template/http.json similarity index 92% rename from v2ray_util/json_template/http.json rename to json_template/http.json index aa4e1559..4ef4331a 100644 --- a/v2ray_util/json_template/http.json +++ b/json_template/http.json @@ -2,8 +2,9 @@ "network": "tcp", "security": "none", "tlsSettings": {}, - "httpSettings": {}, + "httpSettings": null, "tcpSettings": { + "connectionReuse": true, "header": { "type": "http", "request": { @@ -49,7 +50,6 @@ } } }, - "kcpSettings": {}, - "wsSettings": {}, - "quicSettings": {} + "kcpSettings": null, + "wsSettings": null } \ No newline at end of file diff --git a/v2ray_util/json_template/http2.json b/json_template/http2.json similarity index 56% rename from v2ray_util/json_template/http2.json rename to json_template/http2.json index bf4738fa..1d94a479 100644 --- a/v2ray_util/json_template/http2.json +++ b/json_template/http2.json @@ -2,11 +2,10 @@ "network": "h2", "security": "tls", "tlsSettings": {}, - "tcpSettings": {}, + "tcpSettings": null, "httpSettings": { "path": "/ray/" }, - "kcpSettings": {}, - "wsSettings": {}, - "quicSettings": {} + "kcpSettings": null, + "wsSettings": null } \ No newline at end of file diff --git a/json_template/kcp.json b/json_template/kcp.json new file mode 100644 index 00000000..6518b511 --- /dev/null +++ b/json_template/kcp.json @@ -0,0 +1,22 @@ +{ + "network": "kcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": null, + "httpSettings": null, + "kcpSettings": { + "mtu": 1350, + "tti": 50, + "uplinkCapacity": 100, + "downlinkCapacity": 100, + "congestion": false, + "readBufferSize": 2, + "writeBufferSize": 2, + "header": { + "type": "none", + "request": null, + "response": null + } + }, + "wsSettings": null +} \ No newline at end of file diff --git a/json_template/kcp_dtls.json b/json_template/kcp_dtls.json new file mode 100644 index 00000000..e82e5326 --- /dev/null +++ b/json_template/kcp_dtls.json @@ -0,0 +1,22 @@ +{ + "network": "kcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": null, + "httpSettings": null, + "kcpSettings": { + "mtu": 1350, + "tti": 50, + "uplinkCapacity": 100, + "downlinkCapacity": 100, + "congestion": false, + "readBufferSize": 2, + "writeBufferSize": 2, + "header": { + "type": "dtls", + "request": null, + "response": null + } + }, + "wsSettings": null +} \ No newline at end of file diff --git a/json_template/kcp_srtp.json b/json_template/kcp_srtp.json new file mode 100644 index 00000000..6dad54a1 --- /dev/null +++ b/json_template/kcp_srtp.json @@ -0,0 +1,22 @@ +{ + "network": "kcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": null, + "httpSettings": null, + "kcpSettings": { + "mtu": 1350, + "tti": 50, + "uplinkCapacity": 100, + "downlinkCapacity": 100, + "congestion": false, + "readBufferSize": 2, + "writeBufferSize": 2, + "header": { + "type": "srtp", + "request": null, + "response": null + } + }, + "wsSettings": null +} \ No newline at end of file diff --git a/v2ray_util/json_template/kcp.json b/json_template/kcp_utp.json similarity index 67% rename from v2ray_util/json_template/kcp.json rename to json_template/kcp_utp.json index 82151a98..d493275a 100644 --- a/v2ray_util/json_template/kcp.json +++ b/json_template/kcp_utp.json @@ -2,8 +2,8 @@ "network": "kcp", "security": "none", "tlsSettings": {}, - "tcpSettings": {}, - "httpSettings": {}, + "tcpSettings": null, + "httpSettings": null, "kcpSettings": { "mtu": 1350, "tti": 50, @@ -13,9 +13,10 @@ "readBufferSize": 2, "writeBufferSize": 2, "header": { - "type": "none" + "type": "utp", + "request": null, + "response": null } }, - "wsSettings": {}, - "quicSettings": {} + "wsSettings": null } \ No newline at end of file diff --git a/json_template/kcp_wechat.json b/json_template/kcp_wechat.json new file mode 100644 index 00000000..efa86bfe --- /dev/null +++ b/json_template/kcp_wechat.json @@ -0,0 +1,22 @@ +{ + "network": "kcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": null, + "httpSettings": null, + "kcpSettings": { + "mtu": 1350, + "tti": 50, + "uplinkCapacity": 100, + "downlinkCapacity": 100, + "congestion": false, + "readBufferSize": 2, + "writeBufferSize": 2, + "header": { + "type": "wechat-video", + "request": null, + "response": null + } + }, + "wsSettings": null +} \ No newline at end of file diff --git a/json_template/kcp_wireguard.json b/json_template/kcp_wireguard.json new file mode 100644 index 00000000..5a1bd1f1 --- /dev/null +++ b/json_template/kcp_wireguard.json @@ -0,0 +1,22 @@ +{ + "network": "kcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": null, + "httpSettings": null, + "kcpSettings": { + "mtu": 1350, + "tti": 50, + "uplinkCapacity": 100, + "downlinkCapacity": 100, + "congestion": false, + "readBufferSize": 2, + "writeBufferSize": 2, + "header": { + "type": "wireguard", + "request": null, + "response": null + } + }, + "wsSettings": null +} \ No newline at end of file diff --git a/v2ray_util/json_template/mtproto.json b/json_template/mtproto.json similarity index 100% rename from v2ray_util/json_template/mtproto.json rename to json_template/mtproto.json diff --git a/v2ray_util/json_template/server.json b/json_template/server.json similarity index 80% rename from v2ray_util/json_template/server.json rename to json_template/server.json index 191cc2ef..2d2c98ca 100644 --- a/v2ray_util/json_template/server.json +++ b/json_template/server.json @@ -1,67 +1,65 @@ -{ - "log": { - "access": "/var/log/v2ray/access.log", - "error": "/var/log/v2ray/error.log", - "loglevel": "info" - }, - "inbounds": [ - { - "port": 999999999, - "protocol": "vmess", - "settings": { - "clients": [ - { - "id": "cc4f8d5b-967b-4557-a4b6-bde92965bc27", - "alterId": 0 - } - ] - }, - "streamSettings": { - "security": "none", - "tlsSettings": {}, - "wsSettings": {}, - "httpSettings": {}, - "network": "", - "kcpSettings": {}, - "tcpSettings": {}, - "quicSettings": {}, - "grpcSettings": {} - } - } - ], - "outbounds": [ - { - "protocol": "freedom", - "settings": {} - }, - { - "protocol": "blackhole", - "settings": {}, - "tag": "block" - } - ], - "routing": { - "rules": [ - { - "type": "field", - "ip": [ - "0.0.0.0/8", - "10.0.0.0/8", - "100.64.0.0/10", - "169.254.0.0/16", - "172.16.0.0/12", - "192.0.0.0/24", - "192.0.2.0/24", - "192.168.0.0/16", - "198.18.0.0/15", - "198.51.100.0/24", - "203.0.113.0/24", - "::1/128", - "fc00::/7", - "fe80::/10" - ], - "outboundTag": "block" - } - ] - } -} +{ + "log": { + "access": "/var/log/v2ray/access.log", + "error": "/var/log/v2ray/error.log", + "loglevel": "info" + }, + "inbounds": [ + { + "port": 999999999, + "protocol": "vmess", + "settings": { + "clients": [ + { + "id": "cc4f8d5b-967b-4557-a4b6-bde92965bc27", + "alterId": 64 + } + ] + }, + "streamSettings": { + "security": "none", + "tlsSettings": {}, + "wsSettings": null, + "httpSettings": null, + "network": "", + "kcpSettings": null, + "tcpSettings": null + } + } + ], + "outbounds": [ + { + "protocol": "freedom", + "settings": {} + }, + { + "protocol": "blackhole", + "settings": {}, + "tag": "blocked" + } + ], + "routing": { + "rules": [ + { + "type": "field", + "ip": [ + "0.0.0.0/8", + "10.0.0.0/8", + "100.64.0.0/10", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.0.2.0/24", + "192.168.0.0/16", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "::1/128", + "fc00::/7", + "fe80::/10" + ], + "outboundTag": "blocked" + } + ] + } +} diff --git a/v2ray_util/json_template/socks.json b/json_template/socks.json similarity index 59% rename from v2ray_util/json_template/socks.json rename to json_template/socks.json index d577d3fb..f3ed4231 100644 --- a/v2ray_util/json_template/socks.json +++ b/json_template/socks.json @@ -6,5 +6,8 @@ "pass": "socks" } ], - "udp": true + "udp": true, + "ip": "127.0.0.1", + "timeout": 0, + "userLevel": 0 } \ No newline at end of file diff --git a/v2ray_util/json_template/ss.json b/json_template/ss.json similarity index 83% rename from v2ray_util/json_template/ss.json rename to json_template/ss.json index ff900768..a06e88c9 100644 --- a/v2ray_util/json_template/ss.json +++ b/json_template/ss.json @@ -4,6 +4,6 @@ "settings": { "method": "aes-128-gcm", "password": "password", - "network":"tcp,udp" + "network":"tcp, udp" } } \ No newline at end of file diff --git a/v2ray_util/json_template/stats_settings.json b/json_template/stats_settings.json similarity index 100% rename from v2ray_util/json_template/stats_settings.json rename to json_template/stats_settings.json diff --git a/json_template/tcp.json b/json_template/tcp.json new file mode 100644 index 00000000..e2e4da9a --- /dev/null +++ b/json_template/tcp.json @@ -0,0 +1,9 @@ +{ + "network": "tcp", + "security": "none", + "tlsSettings": {}, + "tcpSettings": null, + "kcpSettings": null, + "wsSettings": null, + "httpSettings": null +} \ No newline at end of file diff --git a/v2ray_util/json_template/ws.json b/json_template/ws.json similarity index 57% rename from v2ray_util/json_template/ws.json rename to json_template/ws.json index 98297a1a..4f76404e 100644 --- a/v2ray_util/json_template/ws.json +++ b/json_template/ws.json @@ -2,14 +2,14 @@ "network": "ws", "security": "none", "tlsSettings": {}, - "tcpSettings": {}, - "kcpSettings": {}, - "httpSettings": {}, + "tcpSettings": null, + "kcpSettings": null, + "httpSettings": null, "wsSettings": { + "connectionReuse": true, "path": "", "headers": { "Host": "" } - }, - "quicSettings": {} + } } \ No newline at end of file diff --git a/v2ray_util/util_core/loader.py b/loader.py similarity index 81% rename from v2ray_util/util_core/loader.py rename to loader.py index dd60ee5d..e8e31301 100644 --- a/v2ray_util/util_core/loader.py +++ b/loader.py @@ -3,14 +3,14 @@ import os import pickle -from .config import Config -from .profile import Profile +from config import Config +from profile import Profile class Loader: def __init__(self): config = Config() self.config_path = config.get_path("config_path") - self.path = config.data_path + self.path = config.get_path("data_path") self.profile = None self.load_profile() @@ -21,8 +21,6 @@ def load_profile(self): self.profile = pickle.load(reader) if os.path.getmtime(self.profile.path) != self.profile.modify_time: raise ValueError - if not hasattr(self.profile, "network"): - raise ValueError else: raise FileNotFoundError except Exception: diff --git a/multi-v2ray.conf b/multi-v2ray.conf new file mode 100644 index 00000000..dde605d6 --- /dev/null +++ b/multi-v2ray.conf @@ -0,0 +1,20 @@ +[prod-path] +config_path=/etc/v2ray/config.json +write_client_path=/root/config.json +data_path=/usr/local/multi-v2ray/multi-v2ray.dat +template_path=/usr/local/multi-v2ray/json_template + +[dev-path] +config_path=test_server.json +write_client_path=test_client.json +data_path=test.dat +template_path=json_template + +[web] +port=5000 +user=user +pass=pass +index_page=index.html + +[data] +domain=xxoox.fun \ No newline at end of file diff --git a/multi-v2ray.sh b/multi-v2ray.sh new file mode 100644 index 00000000..c9af8327 --- /dev/null +++ b/multi-v2ray.sh @@ -0,0 +1,322 @@ +#!/bin/bash +# Author: Jrohy +# github: https://github.com/Jrohy/multi-v2ray + +#定时任务北京执行时间(0~23) +BEIJING_UPDATE_TIME=3 + +#记录最开始运行脚本的路径 +BEGIN_PATH=$(pwd) + +#安装方式0: 全新安装, 1:保留配置更新 , 2:仅更新multi-v2ray源码 +INSTARLL_WAY=0 + +#定义操作变量, 0为否, 1为是 +DEV_MODE=0 + +HELP=0 + +REMOVE=0 + +APP_PATH="/usr/local/multi-v2ray" + +#Centos 临时取消别名 +[ -f /etc/redhat-release ] && unalias -a + +#######color code######## +RED="31m" # Error message +GREEN="32m" # Success message +YELLOW="33m" # Warning message +BLUE="36m" # Info message + +colorEcho(){ + COLOR=$1 + echo -e "\033[${COLOR}${@:2}\033[0m" +} + +#######get params######### +while [[ $# > 0 ]];do + key="$1" + case $key in + --remove) + REMOVE=1 + ;; + -h|--help) + HELP=1 + ;; + -k|--keep) + INSTARLL_WAY=1 + colorEcho ${BLUE} "当前以keep保留配置文件形式更新, 若失败请用全新安装\n" + ;; + -c|--code) + INSTARLL_WAY=2 + colorEcho ${BLUE} "当前仅更新multi-v2ray源码\n" + ;; + -d|--dev) + DEV_MODE=1 + colorEcho ${BLUE} "当前为开发模式, 用dev分支来更新\n" + ;; + *) + # unknown option + ;; + esac + shift # past argument or value +done +############################# + +help(){ + echo "source multi-v2ray.sh [-h|--help] [-k|--keep] [-d|--dev][-c|--code][--remove]" + echo " -h, --help Show help" + echo " -k, --keep keep the v2ray config.json to update" + echo " -d, --dev update from dev branch" + echo " -c, --code only update multi-v2ray code" + echo " --remove remove v2ray && multi-v2ray" + echo " no params to new install" + return 0 +} + +removeV2Ray() { + #卸载V2ray官方脚本 + systemctl stop v2ray >/dev/null 2>&1 + systemctl disable v2ray >/dev/null 2>&1 + service v2ray stop >/dev/null 2>&1 + update-rc.d -f v2ray remove >/dev/null 2>&1 + rm -rf /etc/v2ray/ >/dev/null 2>&1 + rm -rf /usr/bin/v2ray >/dev/null 2>&1 + rm -rf /var/log/v2ray/ >/dev/null 2>&1 + rm -rf /lib/systemd/system/v2ray.service >/dev/null 2>&1 + rm -rf /etc/init.d/v2ray >/dev/null 2>&1 + + #卸载multi-v2ray + rm -rf $APP_PATH >/dev/null 2>&1 + rm -rf /etc/bash_completion.d/v2ray.bash >/dev/null 2>&1 + rm -rf /usr/local/bin/v2ray >/dev/null 2>&1 + + #删除v2ray定时更新任务 + crontab -l|sed '/SHELL=/d;/v2ray/d' > crontab.txt + crontab crontab.txt >/dev/null 2>&1 + rm -f crontab.txt >/dev/null 2>&1 + + if [[ ${OS} == "CentOS" ]];then + service crond restart >/dev/null 2>&1 + else + service cron restart >/dev/null 2>&1 + fi + + #删除multi-v2ray环境变量 + sed -i '/v2ray/d' ~/.bashrc + source ~/.bashrc + + colorEcho ${GREEN} "卸载完成!" +} + +closeSELinux() { + #禁用SELinux + if [ -s /etc/selinux/config ] && grep 'SELINUX=enforcing' /etc/selinux/config; then + sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config + setenforce 0 + fi +} + +checkSys() { + #检查是否为Root + [ $(id -u) != "0" ] && { colorEcho ${RED} "Error: You must be root to run this script"; exit 1; } + + #检查系统信息 + if [ -f /etc/redhat-release ];then + OS='CentOS' + elif [ ! -z "`cat /etc/issue | grep bian`" ];then + OS='Debian' + elif [ ! -z "`cat /etc/issue | grep Ubuntu`" ];then + OS='Ubuntu' + else + colorEcho ${RED} "Not support OS, Please reinstall OS and retry!" + exit 1 + fi +} + +#安装依赖 +installDependent(){ + if [[ ${OS} == 'CentOS' ]];then + yum install epel-release curl wget unzip git ntp ntpdate socat crontabs lsof -y + [[ -z $(rpm -qa|grep python3) ]] && yum install python34 -y + else + apt-get update + apt-get install curl unzip git ntp wget ntpdate socat cron lsof -y + [[ -z $(dpkg -l|grep python3) ]] && apt-get install python3 -y + fi + + # 安装 pip依赖 + python3 <(curl -sL https://bootstrap.pypa.io/get-pip.py) + pip3 install pyopenssl +} + +#设置定时升级任务 +planUpdate(){ + #计算北京时间早上3点时VPS的实际时间 + ORIGIN_TIME_ZONE=$(date -R|awk '{printf"%d",$6}') + LOCAL_TIME_ZONE=${ORIGIN_TIME_ZONE%00} + BEIJING_ZONE=8 + DIFF_ZONE=$[$BEIJING_ZONE-$LOCAL_TIME_ZONE] + LOCAL_TIME=$[$BEIJING_UPDATE_TIME-$DIFF_ZONE] + if [ $LOCAL_TIME -lt 0 ];then + LOCAL_TIME=$[24+$LOCAL_TIME] + elif [ $LOCAL_TIME -ge 24 ];then + LOCAL_TIME=$[$LOCAL_TIME-24] + fi + colorEcho ${BLUE} "北京时间${BEIJING_UPDATE_TIME}点,VPS时间为${LOCAL_TIME}点\n" + + OLD_CRONTAB=$(crontab -l) + echo "SHELL=/bin/bash" >> crontab.txt + echo "${OLD_CRONTAB}" >> crontab.txt + echo "0 ${LOCAL_TIME} * * * bash <(curl -L -s https://install.direct/go.sh) | tee -a /root/v2rayUpdate.log && service v2ray restart" >> crontab.txt + crontab crontab.txt + sleep 1 + if [[ ${OS} == "CentOS" ]];then + service crond restart + else + service cron restart + fi + rm -f crontab.txt + colorEcho ${GREEN} "成功配置每天北京时间${BEIJING_UPDATE_TIME}点自动升级V2ray内核任务\n" +} + +updateProject() { + local DOMAIN="" + + if [[ -e $APP_PATH/my_domain ]];then + DOMAIN=$(cat $APP_PATH/my_domain|awk 'NR==1') + elif [[ -e $APP_PATH/multi-v2ray.conf ]];then + TEMP_VALUE=$(cat $APP_PATH/multi-v2ray.conf|grep domain|awk 'NR==1') + DOMAIN=${TEMP_VALUE/*=} + fi + + cd /usr/local/ + if [[ -e multi-v2ray && -e multi-v2ray/.git ]];then + cd multi-v2ray + + git reset --hard && git clean -d -f + if [[ $DEV_MODE == 1 ]];then + git checkout dev + else + git checkout master + fi + git pull + else + [[ $DEV_MODE == 1 ]] && BRANCH="dev" || BRANCH="master" + git clone -b $BRANCH https://github.com/Jrohy/multi-v2ray + fi + + [[ ! -z $DOMAIN ]] && sed -i "s/^domain.*/domain=${DOMAIN}/g" $APP_PATH/multi-v2ray.conf + + #更新v2ray bash_completion脚本 + cp -f $APP_PATH/v2ray.bash /etc/bash_completion.d/ + source /etc/bash_completion.d/v2ray.bash + + #安装/更新V2ray主程序 + [[ ${INSTARLL_WAY} != 2 ]] && bash <(curl -L -s https://install.direct/go.sh) +} + +#时间同步 +timeSync() { + if [[ ${INSTARLL_WAY} == 0 ]];then + systemctl stop ntp &>/dev/null + echo -e "${Info} 正在进行时间同步 ${Font}" + ntpdate time.nist.gov + if [[ $? -eq 0 ]];then + echo -e "${OK} 时间同步成功 ${Font}" + echo -e "${OK} 当前系统时间 `date -R`${Font}" + sleep 1 + else + echo -e "${Error} 时间同步失败,可以手动执行命令同步:${Font}${Yellow}ntpdate time.nist.gov${Font}" + fi + fi +} + + +profileInit() { + #配置V2ray初始环境 + cp $APP_PATH/v2ray /usr/local/bin + chmod +x /usr/local/bin/v2ray + + #加入multi-v2ray模块搜索路径 + [[ -z $(grep multi-v2ray ~/.bashrc) ]] && echo "export PYTHONPATH=$PYTHONPATH:$APP_PATH" >> ~/.bashrc && source ~/.bashrc + + # 加入v2ray tab补全环境变量 + [[ -z $(grep v2ray.bash ~/.bashrc) ]] && echo "source /etc/bash_completion.d/v2ray.bash" >> ~/.bashrc && source ~/.bashrc + + #解决Python3中文显示问题 + [[ -z $(grep PYTHONIOENCODING=utf-8 ~/.bashrc) ]] && echo "export PYTHONIOENCODING=utf-8" >> ~/.bashrc && source ~/.bashrc + + #全新安装的新配置 + if [[ ${INSTARLL_WAY} == 0 ]];then + rm -rf /etc/v2ray/config.json + cp $APP_PATH/json_template/server.json /etc/v2ray/config.json + + #产生随机uuid + UUID=$(cat /proc/sys/kernel/random/uuid) + sed -i "s/cc4f8d5b-967b-4557-a4b6-bde92965bc27/${UUID}/g" /etc/v2ray/config.json + + #产生随机端口 + D_PORT=$(shuf -i 1000-65535 -n 1) + sed -i "s/999999999/${D_PORT}/g" /etc/v2ray/config.json + + #产生默认配置mkcp+随机3种伪装类型type + python3 -c "from config_modify import stream; stream.StreamModifier().random_kcp();" + + python3 $APP_PATH/client.py + python3 -c "from utils import open_port; open_port();" + else + python3 $APP_PATH/converter.py + fi +} + +installFinish() { + #回到原点 + cd ${BEGIN_PATH} + + [[ ${INSTARLL_WAY} == 0 ]] && way="安装" || way="更新" + colorEcho ${GREEN} "multi-v2ray ${way}成功!\n" + + clear + + echo "V2ray配置信息:" + #安装完后显示v2ray的配置信息,用于快速部署 + python3 -c "from loader import Loader; print(Loader().profile);" + + echo -e "输入 v2ray 回车即可进行服务管理\n" +} + + +main() { + + [[ ${HELP} == 1 ]] && help && return + + [[ ${REMOVE} == 1 ]] && removeV2Ray && return + + [[ ${INSTARLL_WAY} == 0 ]] && colorEcho ${BLUE} "当前为全新安装\n" + + if [[ ${INSTARLL_WAY} != 2 ]];then + checkSys + + installDependent + + closeSELinux + + timeSync + + #设置定时任务 + [[ -z $(crontab -l|grep v2ray) ]] && planUpdate + + fi + + updateProject + + profileInit + + service v2ray restart + + installFinish +} + +main \ No newline at end of file diff --git a/package.bat b/package.bat deleted file mode 100644 index 2c55735f..00000000 --- a/package.bat +++ /dev/null @@ -1,17 +0,0 @@ -@echo off -echo update... -pip install -U setuptools wheel twine -echo package... -python setup.py sdist bdist_wheel -echo upload... -twine upload dist/* -echo clean.. -if exist dist ( - rd /s /Q build - rd /s /Q dist - rd /s /Q v2ray_util.egg-info - rd /s /Q v2ray_util\__pycache__ - rd /s /Q v2ray_util\util_core\__pycache__ -) -echo finish! -pause \ No newline at end of file diff --git a/v2ray_util/util_core/profile.py b/profile.py similarity index 58% rename from v2ray_util/util_core/profile.py rename to profile.py index 3c9794ad..49712cdf 100644 --- a/v2ray_util/util_core/profile.py +++ b/profile.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import json +import time import os +import urllib.request -from v2ray_util import run_type -from .config import Config -from .utils import ColorStr, get_ip -from .group import SS, Socks, Vmess, Vless, Mtproto, Quic, Group, Dyport, Trojan +from config import Config +from group import SS, Socks, Vmess,Mtproto, Group, Dyport class Stats: def __init__(self, status=False, door_port=0): @@ -14,7 +14,7 @@ def __init__(self, status=False, door_port=0): self.door_port = door_port def __str__(self): - return "open" if self.status else "close" + return "开启" if self.status else "关闭" class Profile: def __init__(self): @@ -23,7 +23,6 @@ def __init__(self): self.stats = None self.ban_bt = False self.user_number = 0 - self.network = "ipv4" self.modify_time = os.path.getmtime(self.path) self.read_json() @@ -31,7 +30,7 @@ def __str__(self): result = "" for group in self.group_list: result = "{}{}".format(result, group) - result = result + _("Tip: The same group's node protocol, port, tls are the same.") + result = result + "Tip: 同一Group的节点传输方式,端口配置,TLS等设置相同\n" return result def read_json(self): @@ -56,29 +55,22 @@ def read_json(self): if "protocol" in rule and "bittorrent" in rule["protocol"]: self.ban_bt = True - local_ip = get_ip() - - if ":" in local_ip: - self.network = "ipv6" + #获取本机IP地址 + my_ip = urllib.request.urlopen('http://api.ipify.org').read() + local_ip = bytes.decode(my_ip) group_ascii = 64 # before 'A' ascii code for index, json_part in enumerate(conf_inbounds): group = self.parse_group(json_part, index, local_ip) if group != None: group_ascii = group_ascii + 1 - if group_ascii > 90: - group.tag = str(group_ascii) - else: - group.tag = chr(group_ascii) + group.tag = chr(group_ascii) self.group_list.append(group) - if len(self.group_list) == 0: - print("{} json no streamSettings item, please run {} to recreate {} json!".format(run_type, ColorStr.cyan("{} new".format(run_type)), run_type)) - del self.config def parse_group(self, part_json, group_index, local_ip): - dyp, quic, end_port, tfo, header, tls, path, host, conf_ip, serviceName, mode, serverName, privateKey, shortId = Dyport(), None, None, None, "", "", "", "", local_ip, "", "gun", "", "", "" + dyp, end_port, header, tfo, tls, path, host, conf_ip = Dyport(), None, None, None, "", "", "", local_ip protocol = part_json["protocol"] @@ -89,9 +81,6 @@ def parse_group(self, part_json, group_index, local_ip): port_info = str(part_json["port"]).split("-", 2) - if "domain" in part_json and part_json["domain"]: - conf_ip = part_json["domain"] - if len(port_info) == 2: port, end_port = port_info else: @@ -105,39 +94,26 @@ def parse_group(self, part_json, group_index, local_ip): dyp.status = True break - if protocol in ("vmess", "vless", "socks", "trojan"): + if protocol == "vmess" or protocol == "socks": conf_stream = part_json["streamSettings"] tls = conf_stream["security"] - if tls == "reality" and conf_stream["realitySettings"]: - serverName = conf_stream["realitySettings"]["serverNames"][0] - privateKey = conf_stream["realitySettings"]["privateKey"] - shortId = conf_stream["realitySettings"]["shortIds"][0] - if "sockopt" in conf_stream and "tcpFastOpen" in conf_stream["sockopt"]: - tfo = "open" if conf_stream["sockopt"]["tcpFastOpen"] else "close" + tfo = "开启" if conf_stream["sockopt"]["tcpFastOpen"] else "关闭" - if "httpSettings" in conf_stream and conf_stream["httpSettings"]: + if conf_stream["httpSettings"] != None: path = conf_stream["httpSettings"]["path"] - elif "wsSettings" in conf_stream and conf_stream["wsSettings"]: + elif conf_stream["wsSettings"] != None: host = conf_stream["wsSettings"]["headers"]["Host"] path = conf_stream["wsSettings"]["path"] - elif "tcpSettings" in conf_stream and conf_stream["tcpSettings"]: + elif conf_stream["tcpSettings"] != None: host = conf_stream["tcpSettings"]["header"]["request"]["headers"]["Host"] - header = "http" + + if (tls == "tls"): + conf_ip = Config().get_data('domain') if conf_stream["network"] == "kcp" and "header" in conf_stream["kcpSettings"]: header = conf_stream["kcpSettings"]["header"]["type"] - if "seed" in conf_stream["kcpSettings"]: - path = conf_stream["kcpSettings"]["seed"] - - if conf_stream["network"] == "quic" and conf_stream["quicSettings"]: - quic_settings = conf_stream["quicSettings"] - quic = Quic(quic_settings["security"], quic_settings["key"], quic_settings["header"]["type"]) - if conf_stream["network"] == "grpc" and conf_stream["grpcSettings"]: - serviceName = conf_stream["grpcSettings"]["serviceName"] - if "multiMode" in conf_stream["grpcSettings"] and conf_stream["grpcSettings"]["multiMode"]: - mode = "multi" group = Group(conf_ip, port, end_port=end_port, tls=tls, tfo=tfo, dyp=dyp, index=group_index) @@ -148,7 +124,7 @@ def parse_group(self, part_json, group_index, local_ip): group.node_list.append(ss) group.protocol = ss.__class__.__name__ return group - elif protocol in ("vmess", "vless", "trojan"): + elif protocol == "vmess": clients=conf_settings["clients"] elif protocol == "socks": clients=conf_settings["accounts"] @@ -156,30 +132,19 @@ def parse_group(self, part_json, group_index, local_ip): clients=conf_settings["users"] for client in clients: - email, node, flow = "", None, "" + email, node = "", None self.user_number = self.user_number + 1 - if "email" in client and client["email"]: + if "email" in client and client["email"] != None: email = client["email"] if protocol == "vmess": - if serviceName: - path = serviceName - header = mode - node = Vmess(client["id"], client["alterId"], conf_stream["network"], self.user_number, path=path, host=host, header=header, email=email, quic=quic) + node = Vmess(client["id"], client["alterId"], conf_stream["network"], self.user_number, path=path, host=host, header=header, email=email) elif protocol == "socks": node = Socks(self.user_number, client["pass"], user_info=client["user"]) elif protocol == "mtproto": node = Mtproto(self.user_number, client["secret"], user_info=email) - - elif protocol == "vless": - if "flow" in client: - flow = client["flow"] - node = Vless(client["id"], self.user_number, conf_settings["decryption"], email, conf_stream["network"], path, host, header, flow, serviceName, mode, serverName, privateKey, shortId) - - elif protocol == "trojan": - node = Trojan(self.user_number, client["password"], email) if not group.protocol: group.protocol = node.__class__.__name__ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..436abd5b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask_HTTPAuth==3.2.4 +Flask==1.0.2 +pyOpenSSL==18.0.0 diff --git a/selector.py b/selector.py new file mode 100644 index 00000000..43ba0aa8 --- /dev/null +++ b/selector.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from loader import Loader +from utils import color_str, Color + +class Selector: + def __init__(self, action): + loader = Loader() + self.profile = loader.profile + self.group_list = loader.profile.group_list + self.action = action + +class ClientSelector(Selector): + def __init__(self, action): + super(ClientSelector, self).__init__(action) + self.list_size = self.group_list[-1].node_list[-1].user_number + self.group = self.group_list[0] + self.client_index = 0 + if self.list_size > 1: + self.select_client() + + def select_client(self): + print(self.profile) + self.group = None + choice = input("请输入要{}的节点序号数字: ".format(self.action)) + + if not choice.isnumeric(): + print(color_str(Color.RED, '输入错误,请检查是否为数字')) + return + + choice = int(choice) + if choice < 1 or choice > self.list_size: + print(color_str(Color.RED, '输入错误,请检查是否符合范围中')) + else: + find = False + for group in self.group_list: + if find: + break + for index, node in enumerate(group.node_list): + if node.user_number == choice: + self.client_index = index + self.group = group + find = True + break + +class GroupSelector(Selector): + def __init__(self, action): + super(GroupSelector, self).__init__(action) + self.group = self.group_list[0] + if len(self.group_list) > 1: + self.select_group() + + def select_group(self): + print(self.profile) + choice = input("请输入要{}的节点Group字母: ".format(self.action)) + group_list = [x for x in self.group_list if x.tag == choice] + if len(group_list) == 0: + print(color_str(Color.RED, '输入有误,请检查 {} Group是否存在'.format(choice))) + self.group = None + else: + self.group = group_list[0] \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index d7060f27..00000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from setuptools import setup, find_packages - -import v2ray_util - -with open("README.md", "r", encoding='UTF-8') as fh: - long_description = fh.read() - -setup( - name='v2ray-util', - version=v2ray_util.__version__, - description="a tool to manage v2ray config json", - long_description=long_description, - long_description_content_type="text/markdown", - keywords='python v2ray multi-v2ray vmess socks5 vless trojan xray xtls reality', - author='Jrohy', - author_email='euvkzx@gmail.com', - url='https://github.com/Jrohy/multi-v2ray', - license='GPL', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - python_requires='>=3', - entry_points={ - 'console_scripts': [ - 'v2ray-util = v2ray_util.main:menu' - ] - }, - classifiers=[ - 'Topic :: Utilities', - 'Development Status :: 5 - Production/Stable', - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", - "Natural Language :: English", - 'Natural Language :: Chinese (Simplified)', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - ] -) \ No newline at end of file diff --git a/user_manage/create_new_port.py b/user_manage/create_new_port.py new file mode 100644 index 00000000..9f68ad80 --- /dev/null +++ b/user_manage/create_new_port.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import random +import sys + +from config_modify.ss import SSFactory +from utils import StreamType, stream_list +from writer import NodeWriter + +info = dict() + +if len(sys.argv) > 1: + stream_list, param = stream_list(), sys.argv[1] + + if param not in [x.value for x in stream_list]: + print("输入的参数无效! 输入-h 或者--help查看帮助") + exit(-1) + + stream = list(filter(lambda stream:stream.value == param, stream_list))[0] + + if stream == StreamType.SOCKS: + user = input("请输入socks的用户名: ") + password = input("请输入socks的密码: ") + if user == "" or password == "": + print("socks的用户名或者密码不能为空") + exit(-1) + info = {"user":user, "pass": password} + elif stream == StreamType.SS: + sf = SSFactory() + info = {"method": sf.get_method(), "password": sf.get_password()} +else: + salt_stream = [StreamType.KCP_DTLS, StreamType.KCP_WECHAT, StreamType.KCP_UTP, StreamType.KCP_SRTP] + random.shuffle(salt_stream) + stream = salt_stream[0] + print("随机一种 (srtp | wechat-video | utp | dtls) header伪装, 当前生成 {} \n".format(stream.value)) + +random_port = random.randint(1000, 65535) +new_port = input("产生随机端口{}, 回车直接以该端口新建Group, 否则输入自定义端口: ".format(random_port)) + +if not new_port: + new_port = str(random_port) + +if new_port.isnumeric(): + print("\n新端口为: {} \n".format(new_port)) + nw = NodeWriter() + nw.create_new_port(int(new_port), stream, **info) +else: + print ("\n输入错误,请检查是否为数字") \ No newline at end of file diff --git a/user_manage/create_new_user.py b/user_manage/create_new_user.py new file mode 100644 index 00000000..c694a2e9 --- /dev/null +++ b/user_manage/create_new_user.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from utils import is_email +from group import Vmess, Socks, Mtproto, SS +from writer import GroupWriter, NodeWriter +from selector import GroupSelector + +gs = GroupSelector('user数量') +group = gs.group +group_list = gs.group_list + +if group == None: + exit(-1) +else: + email = "" + if type(group.node_list[0]) == Vmess: + while True: + is_duplicate_email=False + + email = input("是否输入email来新建用户, 回车直接跳过: ") + if email == "": + break + if not is_email(email): + print("不是合格的email格式,请重新输入") + continue + + for loop_group in group_list: + for node in loop_group.node_list: + if node.user_info == None or node.user_info == '': + continue + elif node.user_info == email: + print("已经有重复的email, 请重新输入") + is_duplicate_email = True + break + if not is_duplicate_email: + break + + nw = NodeWriter(group.tag, group.index) + info = {'email': email} + nw.create_new_user(**info) + + elif type(group.node_list[0]) == Socks: + print("当前组为socks组, 请输入用户密码创建新的socks用户\n") + user = input("请输入socks的用户名: ") + password = input("请输入socks的密码: ") + if user == "" or password == "": + print("socks的用户名或者密码不能为空") + exit(-1) + info = {"user":user, "pass": password} + nw = NodeWriter(group.tag, group.index) + nw.create_new_user(**info) + + elif type(group.node_list[0]) == Mtproto: + print("\n当前选择的组为MTProto协议, V2ray只支持该协议同组的第一个用户生效, 所以没必要新增用户!") + + elif type(group.node_list[0]) == SS: + print("\n当前选择的组为Shadowsocks协议, V2ray只支持ss协议一个用户一个端口, 想多用户请新增端口!") \ No newline at end of file diff --git a/user_manage/del_port.py b/user_manage/del_port.py new file mode 100644 index 00000000..fcb7dda3 --- /dev/null +++ b/user_manage/del_port.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from writer import NodeWriter +from selector import GroupSelector + +gs = GroupSelector('删除port') +group = gs.group + +if group == None: + exit(-1) +else: + print("你要删除的Group组所有节点信息: ") + print(group) + choice = input("是否删除y/n:").lower() + if choice == 'y': + nw = NodeWriter() + nw.del_port(group) + else: + print("撤销删除") \ No newline at end of file diff --git a/user_manage/del_user.py b/user_manage/del_user.py new file mode 100644 index 00000000..9a214681 --- /dev/null +++ b/user_manage/del_user.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from writer import NodeWriter +from selector import ClientSelector + +cs = ClientSelector('删除user') +client_index = cs.client_index +group = cs.group + +if group == None: + exit(-1) +else: + print("你选择的user信息:") + print(group.show_node(client_index)) + choice = input("是否删除y/n:").lower() + if choice == 'y': + nw = NodeWriter() + nw.del_user(group, client_index) + else: + print("撤销删除") \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..48eb40e3 --- /dev/null +++ b/utils.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import urllib.request +import os +import re +from enum import Enum, unique +from OpenSSL import crypto + +@unique +class Color(Enum): + """ + 终端显示颜色 枚举类 + """ + # 显示格式: \033[显示方式;前景色;背景色m + # 只写一个字段表示前景色,背景色默认 + RED = '\033[31m' # 红色 + GREEN = '\033[32m' # 绿色 + YELLOW = '\033[33m' # 黄色 + BLUE = '\033[34m' # 蓝色 + FUCHSIA = '\033[35m' # 紫红色 + CYAN = '\033[36m' # 青蓝色 + WHITE = '\033[37m' # 白色 + #: no color + RESET = '\033[0m' # 终端默认颜色 + +@unique +class StreamType(Enum): + TCP = 'tcp' + TCP_HOST = 'tcp_host' + SOCKS = 'socks' + SS = 'ss' + MTPROTO = 'mtproto' + H2 = 'h2' + WS = 'ws' + KCP = 'kcp' + KCP_UTP = 'utp' + KCP_SRTP = 'srtp' + KCP_DTLS = 'dtls' + KCP_WECHAT = 'wechat' + KCP_WG = 'wireguard' + +def stream_list(): + return [ + StreamType.KCP_WG, + StreamType.KCP_DTLS, + StreamType.KCP_WECHAT, + StreamType.KCP_UTP, + StreamType.KCP_SRTP, + StreamType.MTPROTO, + StreamType.SOCKS, + StreamType.SS + ] + +def ss_method(): + return ("aes-256-cfb", "aes-128-cfb", "chacha20", + "chacha20-ietf", "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305") + +def color_str(color: Color, str: str) -> str: + """ + 返回有色字符串 + """ + return '{}{}{}'.format( + color.value, + str, + Color.RESET.value + ) + +def is_number(s): + """ + 判断是否为数字的函数 + """ + try: + float(s) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(s) + return True + except (TypeError, ValueError): + pass + + return False + +def get_ip(): + """ + 获取本地ip + """ + my_ip = urllib.request.urlopen('http://api.ipify.org').read() + return bytes.decode(my_ip) + +def port_is_use(port): + """ + 判断端口是否占用 + """ + cmd = "lsof -i:" + str(port) + result = os.popen(cmd).readlines() + return result != [] + +def is_email(email): + """ + 判断是否是邮箱格式 + """ + str=r'^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}$' + return re.match(str, email) + +def get_domain_by_crt_file(crt_path): + """ + 通过证书文件获取域名, 证书文件有误或不存在则返回空 + """ + try: + cert = crypto.load_certificate(crypto.FILETYPE_PEM, open(crt_path).read()) + except: + return + return cert.get_subject().CN + +def bytes_2_human_readable(number_of_bytes, precision=1): + """ + 流量bytes转换为kb, mb, gb等单位 + """ + if number_of_bytes < 0: + raise ValueError("!!! number_of_bytes can't be smaller than 0 !!!") + + step_to_greater_unit = 1024. + + number_of_bytes = float(number_of_bytes) + unit = 'bytes' + + if (number_of_bytes / step_to_greater_unit) >= 1: + number_of_bytes /= step_to_greater_unit + unit = 'KB' + + if (number_of_bytes / step_to_greater_unit) >= 1: + number_of_bytes /= step_to_greater_unit + unit = 'MB' + + if (number_of_bytes / step_to_greater_unit) >= 1: + number_of_bytes /= step_to_greater_unit + unit = 'GB' + + if (number_of_bytes / step_to_greater_unit) >= 1: + number_of_bytes /= step_to_greater_unit + unit = 'TB' + + number_of_bytes = round(number_of_bytes, precision) + + return str(number_of_bytes) + ' ' + unit + +def gen_cert(domain): + service_name = ["v2ray", "nginx", "httpd", "apache2"] + start_cmd = "service {} start >/dev/null 2>&1" + stop_cmd = "service {} stop >/dev/null 2>&1" + + if not os.path.exists("/root/.acme.sh/acme.sh"): + os.system("curl https://get.acme.sh | sh") + + get_ssl_cmd = "bash /root/.acme.sh/acme.sh --issue -d " + domain + " --standalone --keylength ec-256" + + for name in service_name: + os.system(stop_cmd.format(name)) + os.system(get_ssl_cmd) + for name in service_name: + os.system(start_cmd.format(name)) + +def open_port(): + + from loader import Loader + + group_list = Loader().profile.group_list + + port_set = set([group.port for group in group_list]) + + for port in port_set: + cmd1="iptables -I INPUT -m state --state NEW -m tcp -p tcp --dport " + str(port) +" -j ACCEPT" + cmd2="iptables -I INPUT -m state --state NEW -m udp -p udp --dport " + str(port) +" -j ACCEPT" + os.system(cmd1) + os.system(cmd2) \ No newline at end of file diff --git a/v2ray b/v2ray old mode 100644 new mode 100755 index 863bf042..f11e43cb --- a/v2ray +++ b/v2ray @@ -1,31 +1,399 @@ -# bash completion for v2ray/xray -function _auto_tab() { - local options_array=("start" "stop" "restart" "status" "update" "update.sh" "add" "del" "info" "port" "tls" "tfo" "stream" "iptables" "cdn" "stats" "clean" "log" "new" "rm" "-h" "-v" "help" "version") - local add_array=("wechat" "utp" "srtp" "dtls" "grpc" "wireguard" "socks" "mtproto" "ss" "vless_tcp" "vless_tls" "vless_ws" "vless_reality" "trojan" "ss" "quic" "h2" "tcp" "tcp_host" "ws" "vless_kcp" "vless_utp" "vless_srtp" "vless_dtls" "vless_wechat" "vless_wireguard" "vless_grpc") - local log_array=("access" "a" "error" "e") - local cur prev - - compreply=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - case $prev in - 'v2ray') - compreply=( $(compgen -W "${options_array[*]}" -- $cur) ) ;; - 'xray') - compreply=( $(compgen -W "${options_array[*]}" -- $cur) ) ;; - 'add') - compreply=( $(compgen -W "${add_array[*]}" -- $cur) );; - 'log') - compreply=( $(compgen -W "${log_array[*]}" -- $cur) );; - 'stats') - compreply=( $(compgen -W "group user" -- $cur) );; - 'iptables') - compreply=( $(compgen -W "show" -- $cur) );; - '*') - compreply=( $(compgen -d -f ${cur}) ) ;; - esac - - return 0 +#!/bin/bash + +#######color code######## +RED="31m" # Error message +GREEN="32m" # Success message +YELLOW="33m" # Warning message +BLUE="36m" # Info message + +APP_PATH="/usr/local/multi-v2ray" + +colorEcho(){ + COLOR=$1 + echo -e "\033[${COLOR}${@:2}\033[0m" } -complete -F _auto_tab v2ray xray + +cleanLog() { + cat /dev/null > /var/log/v2ray/access.log + cat /dev/null > /var/log/v2ray/error.log + colorEcho ${GREEN} "清理v2ray日志成功!\n" +} + +#Check Root +[ $(id -u) != "0" ] && { colorEcho ${RED} "Error: You must be root to run this script"; exit 1; } + +help(){ + echo "v2ray [-h|--help] [options]" + echo " -h, --help 查看帮助" + echo " start 启动 V2Ray" + echo " stop 停止 V2Ray" + echo " restart 重启 V2Ray" + echo " status 查看 V2Ray 运行状态" + echo " log 查看 V2Ray 运行日志" + echo " update 更新 V2Ray" + echo " update.sh 更新 multi-v2ray脚本" + echo " add 新增mkcp + 随机一种 (srtp | wechat-video | utp | dtls) header伪装的端口(Group)" + echo " add [wechat|utp|srtp|dtls|wireguard|socks|mtproto|ss] 新增一种协议的组,端口随机,如 v2ray add utp 为新增utp协议" + echo " del 删除端口组" + echo " info 查看配置" + echo " port 修改端口" + echo " tls 修改tls" + echo " tfo 修改tcpFastOpen" + echo " stream 修改传输协议" + echo " stats 流量统计" + echo " clean 清理日志" + echo " 更多功能 请输入 v2ray 回车进入服务管理程序" + return 0 +} + +PARAMS_NUM=$# + +#######get params######### +while [[ $# > 0 ]];do + key="$1" + case $key in + -h|--help) + help + ;; + start) + service v2ray start + colorEcho ${GREEN} "v2ray启动成功!" + ;; + stop) + service v2ray stop + colorEcho ${GREEN} "v2ray已停止!" + ;; + restart) + service v2ray restart + colorEcho ${GREEN} "v2ray重启成功!" + ;; + status) + service v2ray status + ;; + log) + colorEcho ${BLUE} "按Ctrl + C 可以退出日志功能\n" + tail -f /var/log/v2ray/access.log + ;; + update) + bash <(curl -L -s https://install.direct/go.sh) + ;; + update.sh) + source <(curl -sL https://git.io/fNgqx) -k + ;; + info) + python3 -c "from loader import Loader; print(Loader().profile);" + ;; + port) + python3 $APP_PATH/config_modify/port.py + python3 -c "from utils import open_port; open_port();" + + service v2ray restart + ;; + tls) + python3 $APP_PATH/config_modify/tls.py + service v2ray restart + ;; + tfo) + python3 $APP_PATH/config_modify/tfo.py + service v2ray restart + ;; + stream) + python3 $APP_PATH/config_modify/stream.py + service v2ray restart + ;; + stats) + python3 $APP_PATH/global_setting/stats_ctr.py + ;; + clean) + cleanLog + ;; + del) + python3 $APP_PATH/user_manage/del_port.py + service v2ray restart + ;; + add) + EXTRA="$2" + python3 $APP_PATH/user_manage/create_new_port.py $EXTRA + python3 -c "from utils import open_port; open_port();" + service v2ray restart + shift + ;; + *) + colorEcho ${YELLOW} "传参有误! 输入-h 或者--help查看帮助" # unknown option + ;; + esac + shift # past argument or value +done +############################# + +##有参数输入操作一波后就结束 +[[ $PARAMS_NUM > 0 ]] && exit + +echo "" +colorEcho ${BLUE} "欢迎使用 multi-v2ray 管理程序" +echo -e "" +echo -e "1.服务管理 2.用户管理\n" +echo -e "3.更改配置 4.查看配置\n" +echo -e "5.全局功能 6.更新V2Ray\n" +echo -e "7.生成客户端配置文件" + +while :; do echo + read -n1 -p "请输入数字选择功能(按回车键退出): " CHOICE + if [[ ! $CHOICE =~ ^[1-7]$ ]]; then + if [[ -z ${CHOICE} ]];then + exit 0 + fi + colorEcho ${RED} "输入错误! 请输入正确的数字!" + else + echo -e "\n" + break + fi +done + +if [[ ${CHOICE} == 1 ]]; then + echo -e "1.启动服务" + echo -e "2.停止服务" + echo -e "3.重启服务" + echo -e "4.运行状态" + echo -e "5.运行日志" + while :; do echo + read -n1 -p "请选择: " SERVICE_CHOICE + if [[ ! $SERVICE_CHOICE =~ ^[1-5]$ ]]; then + if [[ -z ${SERVICE_CHOICE} ]];then + bash /usr/local/bin/v2ray + exit 0 + fi + colorEcho ${RED} "输入错误! 请输入正确的数字!" + else + echo -e "\n" + break + fi + done + if [[ ${SERVICE_CHOICE} == 1 ]]; then + service v2ray start + python3 -c "from utils import open_port; open_port();" + echo "服务启动成功!" + bash /usr/local/bin/v2ray + + elif [[ ${SERVICE_CHOICE} == 2 ]]; then + service v2ray stop + echo "服务停止成功!" + bash /usr/local/bin/v2ray + + elif [[ ${SERVICE_CHOICE} == 3 ]]; then + service v2ray restart + python3 -c "from utils import open_port; open_port();" + echo "服务已重启!" + bash /usr/local/bin/v2ray + + elif [[ ${SERVICE_CHOICE} == 4 ]]; then + service v2ray status + bash /usr/local/bin/v2ray + + elif [[ ${SERVICE_CHOICE} == 5 ]]; then + echo "按Ctrl + C 可以退出日志功能" + echo "" + tail -f /var/log/v2ray/access.log + bash /usr/local/bin/v2ray + fi +fi + +if [[ ${CHOICE} == 2 ]]; then + echo -e "1.新增用户\n" + echo -e "2.新增端口\n" + echo -e "3.删除用户\n" + echo -e "4.删除端口\n" + while :; do echo + read -n1 -p "请输入数字选择功能: " USER_CHOICE + if [[ ! $USER_CHOICE =~ ^[1-4]$ ]]; then + if [[ -z ${USER_CHOICE} ]];then + bash /usr/local/bin/v2ray + exit 0 + fi + colorEcho ${RED} "输入错误! 请输入正确的数字!" + else + echo -e "\n" + break + fi + done + + if [[ ${USER_CHOICE} == 1 ]]; then + python3 $APP_PATH/user_manage/create_new_user.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${USER_CHOICE} == 2 ]]; then + python3 $APP_PATH/user_manage/create_new_port.py + python3 -c "from utils import open_port; open_port();" + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${USER_CHOICE} == 3 ]]; then + python3 $APP_PATH/user_manage/del_user.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${USER_CHOICE} == 4 ]]; then + python3 $APP_PATH/user_manage/del_port.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + fi +fi + +if [[ ${CHOICE} == 3 ]]; then + echo "" + echo -e "1.更改email\n" + echo -e "2.更改UUID\n" + echo -e "3.更改alterID\n" + echo -e "4.更改主端口\n" + echo -e "5.更改传输方式\n" + echo -e "6.更改TLS设置\n" + echo -e "7.更改tcpFastOpen设置\n" + echo -e "8.更改动态端口\n" + echo -e "9.更改Shadowsocks加密方式\n" + echo -e "10.更改Shadowsocks密码\n" + + while :; do echo + read -n1 -p "请输入数字选择功能: " CONFIG_CHOICE + if [[ ! $CONFIG_CHOICE =~ (^[1-9]$|10) ]]; then + if [[ -z ${CONFIG_CHOICE} ]];then + bash /usr/local/bin/v2ray + exit 0 + fi + colorEcho ${RED} "输入错误! 请输入正确的数字!" + else + echo -e "\n" + break + fi + done + + if [[ ${CONFIG_CHOICE} == 1 ]]; then + python3 $APP_PATH/config_modify/n_email.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 2 ]]; then + python3 $APP_PATH/config_modify/n_uuid.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 3 ]]; then + python3 $APP_PATH/config_modify/alterid.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 4 ]]; then + python3 $APP_PATH/config_modify/port.py + python3 -c "from utils import open_port; open_port();" + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 5 ]]; then + python3 $APP_PATH/config_modify/stream.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 6 ]]; then + python3 $APP_PATH/config_modify/tls.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 7 ]]; then + python3 $APP_PATH/config_modify/tfo.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 8 ]]; then + python3 $APP_PATH/config_modify/dyn_port.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 9 ]]; then + python3 $APP_PATH/config_modify/ss.py method + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${CONFIG_CHOICE} == 10 ]]; then + python3 $APP_PATH/config_modify/ss.py password + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + fi +fi + +if [[ ${CHOICE} == 4 ]]; then + python3 -c "from loader import Loader; print(Loader().profile);" + bash /usr/local/bin/v2ray +fi + +if [[ ${CHOICE} == 5 ]]; then + echo -e "" + echo -e "1.流量统计\n" + echo -e "2.禁止bittorrent\n" + echo -e "3.定时更新V2ray\n" + echo -e "4.清理v2ray日志\n" + echo -e "5.脚本升级\n" + echo -e "请输入数字选择功能" + + while :; do echo + read -n1 -p "请选择: " EXTRA_CHOICE + if [[ ! $EXTRA_CHOICE =~ ^[1-5]$ ]]; then + if [[ -z ${EXTRA_CHOICE} ]];then + bash /usr/local/bin/v2ray + exit 0 + fi + colorEcho ${RED} "输入错误! 请输入正确的数字!" + else + echo -e "\n" + break + fi + done + + if [[ ${EXTRA_CHOICE} == 1 ]]; then + python3 $APP_PATH/global_setting/stats_ctr.py + bash /usr/local/bin/v2ray + + elif [[ ${EXTRA_CHOICE} == 2 ]]; then + python3 $APP_PATH/global_setting/ban_bt.py + service v2ray restart + echo "" + bash /usr/local/bin/v2ray + + elif [[ ${EXTRA_CHOICE} == 3 ]]; then + bash $APP_PATH/global_setting/update_timer.sh + bash /usr/local/bin/v2ray + + elif [[ ${EXTRA_CHOICE} == 4 ]]; then + cleanLog + bash /usr/local/bin/v2ray + + elif [[ ${EXTRA_CHOICE} == 5 ]]; then + echo -e "脚本正在以保留配置文件形式升级, 若失败请自行手动全新安装..\n" + source <(curl -sL https://git.io/fNgqx) -k + echo -e "\n脚本升级成功!!\n" + bash /usr/local/bin/v2ray + fi +fi + +if [[ ${CHOICE} == 6 ]]; then + bash <(curl -L -s https://install.direct/go.sh) + bash /usr/local/bin/v2ray +fi + +if [[ ${CHOICE} == 7 ]]; then + python3 $APP_PATH/client.py + bash /usr/local/bin/v2ray +fi \ No newline at end of file diff --git a/v2ray.bash b/v2ray.bash new file mode 100644 index 00000000..66c61ea4 --- /dev/null +++ b/v2ray.bash @@ -0,0 +1,22 @@ +# bash completion for v2ray -*- shell-script -*- +function _auto_tab() { + local options_array=("start" "stop" "restart" "status" "log" "update" "update.sh" "add" "del" "info" "port" "tls" "tfo" "stream" "stats" "clean" "-h") + local add_array=("wechat" "utp" "srtp" "dtls" "wireguard" "socks" "mtproto" "ss") + local cur pre + + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + case $prev in + 'v2ray') + COMPREPLY=( $(compgen -W "${options_array[*]}" -- $cur) ) ;; + 'add') + COMPREPLY=( $(compgen -W "${add_array[*]}" -- $cur) );; + '*') + COMPREPLY=( $(compgen -d -f ${cur}) ) ;; + esac + + return 0 +} +complete -F _auto_tab v2ray \ No newline at end of file diff --git a/v2ray.sh b/v2ray.sh deleted file mode 100644 index c28e0d90..00000000 --- a/v2ray.sh +++ /dev/null @@ -1,301 +0,0 @@ -#!/bin/bash -# Author: Jrohy -# github: https://github.com/Jrohy/multi-v2ray - -#记录最开始运行脚本的路径 -begin_path=$(pwd) - -#安装方式, 0为全新安装, 1为保留v2ray配置更新 -install_way=0 - -#定义操作变量, 0为否, 1为是 -help=0 - -remove=0 - -chinese=0 - -base_source_path="https://multi.netlify.app" - -util_path="/etc/v2ray_util/util.cfg" - -util_cfg="$base_source_path/v2ray_util/util_core/util.cfg" - -bash_completion_shell="$base_source_path/v2ray" - -clean_iptables_shell="$base_source_path/v2ray_util/global_setting/clean_iptables.sh" - -#Centos 临时取消别名 -[[ -f /etc/redhat-release && -z $(echo $SHELL|grep zsh) ]] && unalias -a - -[[ -z $(echo $SHELL|grep zsh) ]] && env_file=".bashrc" || env_file=".zshrc" - -#######color code######## -red="31m" -green="32m" -yellow="33m" -blue="36m" -fuchsia="35m" - -colorEcho(){ - color=$1 - echo -e "\033[${color}${@:2}\033[0m" -} - -#######get params######### -while [[ $# > 0 ]];do - key="$1" - case $key in - --remove) - remove=1 - ;; - -h|--help) - help=1 - ;; - -k|--keep) - install_way=1 - colorEcho ${blue} "keep config to update\n" - ;; - --zh) - chinese=1 - colorEcho ${blue} "安装中文版..\n" - ;; - *) - # unknown option - ;; - esac - shift # past argument or value -done -############################# - -help(){ - echo "bash v2ray.sh [-h|--help] [-k|--keep] [--remove]" - echo " -h, --help Show help" - echo " -k, --keep keep the config.json to update" - echo " --remove remove v2ray,xray && multi-v2ray" - echo " no params to new install" - return 0 -} - -removeV2Ray() { - #卸载V2ray脚本 - bash <(curl -L -s https://multi.netlify.app/go.sh) --remove >/dev/null 2>&1 - rm -rf /etc/v2ray >/dev/null 2>&1 - rm -rf /var/log/v2ray >/dev/null 2>&1 - - #卸载Xray脚本 - bash <(curl -L -s https://multi.netlify.app/go.sh) --remove -x >/dev/null 2>&1 - rm -rf /etc/xray >/dev/null 2>&1 - rm -rf /var/log/xray >/dev/null 2>&1 - - #清理v2ray相关iptable规则 - bash <(curl -L -s $clean_iptables_shell) - - #卸载multi-v2ray - pip uninstall v2ray_util -y - rm -rf /usr/share/bash-completion/completions/v2ray.bash >/dev/null 2>&1 - rm -rf /usr/share/bash-completion/completions/v2ray >/dev/null 2>&1 - rm -rf /usr/share/bash-completion/completions/xray >/dev/null 2>&1 - rm -rf /etc/bash_completion.d/v2ray.bash >/dev/null 2>&1 - rm -rf /usr/local/bin/v2ray >/dev/null 2>&1 - rm -rf /etc/v2ray_util >/dev/null 2>&1 - rm -rf /etc/profile.d/iptables.sh >/dev/null 2>&1 - rm -rf /root/.iptables >/dev/null 2>&1 - - #删除v2ray定时更新任务 - crontab -l|sed '/SHELL=/d;/v2ray/d'|sed '/SHELL=/d;/xray/d' > crontab.txt - crontab crontab.txt >/dev/null 2>&1 - rm -f crontab.txt >/dev/null 2>&1 - - if [[ ${package_manager} == 'dnf' || ${package_manager} == 'yum' ]];then - systemctl restart crond >/dev/null 2>&1 - else - systemctl restart cron >/dev/null 2>&1 - fi - - #删除multi-v2ray环境变量 - sed -i '/v2ray/d' ~/$env_file - sed -i '/xray/d' ~/$env_file - source ~/$env_file - - rc_service=`systemctl status rc-local|grep loaded|egrep -o "[A-Za-z/]+/rc-local.service"` - - rc_file=`cat $rc_service|grep ExecStart|awk '{print $1}'|cut -d = -f2` - - sed -i '/iptables/d' ~/$rc_file - - colorEcho ${green} "uninstall success!" -} - -closeSELinux() { - #禁用SELinux - if [ -s /etc/selinux/config ] && grep 'SELINUX=enforcing' /etc/selinux/config; then - sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config - setenforce 0 - fi -} - -checkSys() { - #检查是否为Root - [ $(id -u) != "0" ] && { colorEcho ${red} "Error: You must be root to run this script"; exit 1; } - - if [[ `command -v apt-get` ]];then - package_manager='apt-get' - elif [[ `command -v dnf` ]];then - package_manager='dnf' - elif [[ `command -v yum` ]];then - package_manager='yum' - else - colorEcho $red "Not support OS!" - exit 1 - fi -} - -#安装依赖 -installDependent(){ - if [[ ${package_manager} == 'dnf' || ${package_manager} == 'yum' ]];then - ${package_manager} install socat crontabs bash-completion which -y - else - ${package_manager} update - ${package_manager} install socat cron bash-completion ntpdate gawk -y - fi - - #install python3 & pip - source <(curl -sL https://python3.netlify.app/install.sh) -} - -updateProject() { - [[ ! $(type pip 2>/dev/null) ]] && colorEcho $red "pip no install!" && exit 1 - - [[ -e /etc/profile.d/iptables.sh ]] && rm -f /etc/profile.d/iptables.sh - - rc_service=`systemctl status rc-local|grep loaded|egrep -o "[A-Za-z/]+/rc-local.service"` - - rc_file=`cat $rc_service|grep ExecStart|awk '{print $1}'|cut -d = -f2` - - if [[ ! -e $rc_file || -z `cat $rc_file|grep iptables` ]];then - local_ip=`curl -s http://api.ipify.org 2>/dev/null` - [[ `echo $local_ip|grep :` ]] && iptable_way="ip6tables" || iptable_way="iptables" - if [[ ! -e $rc_file || -z `cat $rc_file|grep "/bin/bash"` ]];then - echo "#!/bin/bash" >> $rc_file - fi - if [[ -z `cat $rc_service|grep "\[Install\]"` ]];then - cat >> $rc_service << EOF - -[Install] -WantedBy=multi-user.target -EOF - systemctl daemon-reload - fi - echo "[[ -e /root/.iptables ]] && $iptable_way-restore -c < /root/.iptables" >> $rc_file - chmod +x $rc_file - systemctl restart rc-local - systemctl enable rc-local - - $iptable_way-save -c > /root/.iptables - fi - - pip install -U v2ray_util - - if [[ -e $util_path ]];then - [[ -z $(cat $util_path|grep lang) ]] && echo "lang=en" >> $util_path - else - mkdir -p /etc/v2ray_util - curl $util_cfg > $util_path - fi - - [[ $chinese == 1 ]] && sed -i "s/lang=en/lang=zh/g" $util_path - - rm -f /usr/local/bin/v2ray >/dev/null 2>&1 - ln -s $(which v2ray-util) /usr/local/bin/v2ray - rm -f /usr/local/bin/xray >/dev/null 2>&1 - ln -s $(which v2ray-util) /usr/local/bin/xray - - #移除旧的v2ray bash_completion脚本 - [[ -e /etc/bash_completion.d/v2ray.bash ]] && rm -f /etc/bash_completion.d/v2ray.bash - [[ -e /usr/share/bash-completion/completions/v2ray.bash ]] && rm -f /usr/share/bash-completion/completions/v2ray.bash - - #更新v2ray bash_completion脚本 - curl $bash_completion_shell > /usr/share/bash-completion/completions/v2ray - curl $bash_completion_shell > /usr/share/bash-completion/completions/xray - if [[ -z $(echo $SHELL|grep zsh) ]];then - source /usr/share/bash-completion/completions/v2ray - source /usr/share/bash-completion/completions/xray - fi - - #安装V2ray主程序 - [[ ${install_way} == 0 ]] && bash <(curl -L -s https://multi.netlify.app/go.sh) -} - -#时间同步 -timeSync() { - if [[ ${install_way} == 0 ]];then - echo -e "Time Synchronizing.. " - if [[ `command -v ntpdate` ]];then - ntpdate pool.ntp.org - elif [[ `command -v chronyc` ]];then - chronyc -a makestep - fi - - if [[ $? -eq 0 ]];then - colorEcho $green "Time Sync Success" - colorEcho $blue "now: `date -R`" - fi - fi -} - -profileInit() { - - #清理v2ray模块环境变量 - [[ $(grep v2ray ~/$env_file) ]] && sed -i '/v2ray/d' ~/$env_file && source ~/$env_file - - #解决Python3中文显示问题 - [[ -z $(grep PYTHONIOENCODING=utf-8 ~/$env_file) ]] && echo "export PYTHONIOENCODING=utf-8" >> ~/$env_file && source ~/$env_file - - #全新安装的新配置 - [[ ${install_way} == 0 ]] && v2ray new - - echo "" -} - -installFinish() { - #回到原点 - cd ${begin_path} - - [[ ${install_way} == 0 ]] && WAY="install" || WAY="update" - colorEcho ${green} "multi-v2ray ${WAY} success!\n" - - if [[ ${install_way} == 0 ]]; then - clear - - v2ray info - - echo -e "please input 'v2ray' command to manage v2ray\n" - fi -} - - -main() { - - [[ ${help} == 1 ]] && help && return - - [[ ${remove} == 1 ]] && removeV2Ray && return - - [[ ${install_way} == 0 ]] && colorEcho ${blue} "new install\n" - - checkSys - - installDependent - - closeSELinux - - timeSync - - updateProject - - profileInit - - installFinish -} - -main diff --git a/v2ray_util/__init__.py b/v2ray_util/__init__.py deleted file mode 100644 index 6af3fca4..00000000 --- a/v2ray_util/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -__version__ = '3.11.4' - -import sys -if "xray" in sys.argv[0]: - run_type = 'xray' -else: - run_type = 'v2ray' - -from .util_core.trans import _ \ No newline at end of file diff --git a/v2ray_util/config_modify/__init__.py b/v2ray_util/config_modify/__init__.py deleted file mode 100644 index 5a2b0120..00000000 --- a/v2ray_util/config_modify/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ..util_core.trans import _ \ No newline at end of file diff --git a/v2ray_util/config_modify/base.py b/v2ray_util/config_modify/base.py deleted file mode 100644 index 5125ba4f..00000000 --- a/v2ray_util/config_modify/base.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from v2ray_util import run_type - -from ..util_core.v2ray import restart -from ..util_core.utils import readchar, random_email, ColorStr -from ..util_core.group import Vless, Vmess, Socks, Mtproto, SS -from ..util_core.writer import ClientWriter, GroupWriter -from ..util_core.selector import ClientSelector, GroupSelector - -@restart() -def alterid(): - cs = ClientSelector(_('modify alterId')) - group = cs.group - - if group == None: - pass - else: - client_index = cs.client_index - if type(group.node_list[client_index]) == Vmess: - print("{}: {}".format(_("node alterID"), group.node_list[client_index].alter_id)) - new_alterid = input(_("please input new alterId: ")) - if (new_alterid.isnumeric()): - cw = ClientWriter(group.tag, group.index, client_index) - cw.write_aid(int(new_alterid)) - print(_("alterId modify success!")) - return True - else: - print(_("input error, please check is number")) - else: - print(_("only vmess protocol can modify alterId!")) - -@restart() -def dyn_port(): - gs = GroupSelector(_('modify dyn_port')) - group = gs.group - - if group == None: - pass - else: - print('{}: {}'.format(_("dyn_port status"), group.dyp)) - gw = GroupWriter(group.tag, group.index) - choice = readchar(_("open/close dyn_port(y/n): ")).lower() - - if choice == 'y': - newAlterId = input(_("please input dyn_port alterID(default 32): ")) - newAlterId = '32' if newAlterId == '' else newAlterId - if (newAlterId.isdecimal()): - gw.write_dyp(True, newAlterId) - print(_("open dyn_port success!")) - return True - else: - print(_("input error, please check is number")) - elif choice == 'n': - gw.write_dyp(False) - print(_("close dyn_port success!")) - return True - else: - print(_("input error, please input again")) - -@restart() -def new_email(): - cs = ClientSelector(_('modify email')) - group = cs.group - - if group == None: - pass - elif type(group.node_list[0]) == Socks: - print(_("Socks5 don't support email!")) - else: - client_index = cs.client_index - group_list = cs.group_list - print("{}: {}".format(_("node email"), group.node_list[client_index].user_info)) - email = "" - while True: - is_duplicate_email=False - remail = random_email() - tip = _("create random email:") + ColorStr.cyan(remail) + _(", enter to use it or input new email: ") - email = input(tip) - if email == "": - email = remail - from ..util_core.utils import is_email - if not is_email(email): - print(_("not email, please input again")) - continue - - for loop_group in group_list: - for node in loop_group.node_list: - if node.user_info == None or node.user_info == '': - continue - elif node.user_info == email: - print(_("have same email, please input other")) - is_duplicate_email = True - break - if not is_duplicate_email: - break - - if email != "": - cw = ClientWriter(group.tag, group.index, client_index) - cw.write_email(email) - print(_("modify email success!!")) - return True - -@restart() -def new_uuid(): - cs = ClientSelector(_('modify uuid')) - group = cs.group - - if group == None: - pass - else: - client_index = cs.client_index - if type(group.node_list[client_index]) in (Vmess, Vless): - print("{}: {}".format(_("node UUID"), group.node_list[client_index].password)) - choice = readchar(_("get new UUID?(y/n): ")).lower() - if choice == "y": - import uuid - new_uuid = uuid.uuid4() - print("{}: {}".format(_("new UUID"),new_uuid)) - cw = ClientWriter(group.tag, group.index, client_index) - cw.write_uuid(new_uuid) - print(_("UUID modify success!")) - return True - else: - print(_("undo modify")) - else: - print(_("only vmess/VLESS protocol can modify uuid!")) - -@restart(True) -def port(): - gs = GroupSelector(_('modify port')) - group = gs.group - - if group == None: - pass - else: - if group.end_port: - port_info = "{0}-{1}".format(group.port, group.end_port) - else: - port_info = group.port - print('{}: {}'.format(_("group port"), port_info)) - new_port_info = input(_("please input new port(support range port(use '-' as separator), all range port can effect):")) - import re - if new_port_info.isdecimal() or re.match(r'^\d+\-\d+$', new_port_info): - gw = GroupWriter(group.tag, group.index) - gw.write_port(new_port_info) - print(_('port modify success!')) - return True - else: - print(_("input error!")) - -@restart() -def tfo(): - gs = GroupSelector(_('modify tcpFastOpen')) - group = gs.group - - if group == None: - pass - else: - if type(group.node_list[0]) == Mtproto or type(group.node_list[0]) == SS: - print(_("{} MTProto/Shadowsocks don't support tcpFastOpen!!!".format(run_type.capitalize()))) - print("") - return - - print('{}: {}'.format(_("group tcpFastOpen"), group.tfo)) - print("") - print(_("1.open TFO(force open)")) - print(_("2.close TFO(force close)")) - print(_("3.delete TFO(use system default profile)")) - choice = readchar(_("please select: ")) - if not choice: - return - if not choice in ("1", "2", "3"): - print(_("input error, please input again")) - return - - gw = GroupWriter(group.tag, group.index) - if choice == "1": - gw.write_tfo('on') - elif choice == "2": - gw.write_tfo('off') - elif choice == "3": - gw.write_tfo('del') - - return True \ No newline at end of file diff --git a/v2ray_util/config_modify/cdn.py b/v2ray_util/config_modify/cdn.py deleted file mode 100644 index 715fffed..00000000 --- a/v2ray_util/config_modify/cdn.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import socket - -from .tls import TLSModifier -from ..util_core.v2ray import restart -from ..util_core.loader import Loader -from ..util_core.writer import StreamWriter, NodeWriter -from ..util_core.utils import StreamType, ColorStr, get_ip, loop_input_choice_number, check_ip, is_ipv4, port_is_use - -# https://support.cloudflare.com/hc/en-us/articles/200169156-Identifying-network-ports-compatible-with-Cloudflare-s-proxy -class CDNModifier: - def __init__(self, domain='', cType=0): - ''' - cType 0: vmess_ws, 1: vless_ws - ''' - self.domain = domain - self.cType = cType - - @restart(True) - def open(self, port=443): - ''' - cloudflare cdn proxy https port(443, 2053, 2083, 2087, 2096, 8443) - ''' - nw = NodeWriter() - nw.create_new_port(int(port)) - reload_data = Loader() - new_group_list = reload_data.profile.group_list - group = new_group_list[-1] - TLSModifier(group.tag, group.index, self.domain).turn_on(False) - if self.cType == 0: - StreamWriter(group.tag, group.index, StreamType.WS).write() - elif self.cType == 1: - StreamWriter(group.tag, group.index, StreamType.VLESS_WS).write() - return True - -def modify(): - port_choice = "" - - https_list=(443, 2053, 2083, 2087, 2096, 8443) - - cdn_protocol_list=('vmess_ws', 'vless_ws') - - domain = input(_("please input run cdn mode domain: ")) - if not domain: - print(ColorStr.yellow(_("domain is empty!"))) - return - - local_ip = get_ip() - try: - if is_ipv4(local_ip): - input_ip = socket.gethostbyname(domain) - else: - input_ip = socket.getaddrinfo(domain, None, socket.AF_INET6)[0][4][0] - except Exception: - print(_("domain check error!!!")) - print("") - return - - print("") - print(_("local vps ip address: ") + local_ip + "\n") - - if input_ip != local_ip: - print(_("domain can't analysis to local ip!!!")) - print(_("must be close cdn proxy!")) - print("") - return - - for index, text in enumerate(https_list): - print("{}.{}".format(index + 1, text)) - port_choice = loop_input_choice_number(_("please select https port to cdn: "), len(https_list)) - if not port_choice: - return - new_port = https_list[port_choice - 1] - if port_is_use(new_port): - print("{} port is use!".format(new_port)) - return - print("") - for index, text in enumerate(cdn_protocol_list): - print("{}.{}".format(index + 1, text)) - cdn_choice = loop_input_choice_number(_("please select protocol to cdn: "), len(cdn_protocol_list)) - if not cdn_choice: - return - CDNModifier(domain, cdn_choice - 1).open(new_port) \ No newline at end of file diff --git a/v2ray_util/config_modify/multiple.py b/v2ray_util/config_modify/multiple.py deleted file mode 100644 index d05c77c3..00000000 --- a/v2ray_util/config_modify/multiple.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import random -import string - -from ..util_core.v2ray import restart -from ..util_core.loader import Loader -from ..util_core.writer import NodeWriter -from ..util_core.group import Vmess, Socks, Mtproto, SS, Vless, Trojan -from ..util_core.selector import GroupSelector, ClientSelector, CommonSelector -from ..util_core.utils import is_email, clean_iptables, random_email, ColorStr, readchar, random_port, port_is_use, xtls_flow, StreamType - -@restart(True) -def new_port(new_stream=None): - if new_stream != None and new_stream not in StreamType._value2member_map_: - print(ColorStr.red("{} not support!".format(new_stream))) - return - new_port = "" - while True: - new_random_port = random_port(1000, 65535) - new_port = input("{0} {1}, {2}: ".format(_("random generate port"), ColorStr.green(str(new_random_port)), _("enter to use, or input customize port"))) - - if not new_port: - new_port = str(new_random_port) - else: - if not new_port.isnumeric(): - print(_("input error, please input again")) - continue - elif port_is_use(new_port): - print(_("port is use, please input other port!")) - continue - break - - print("") - print("{}: {}".format(_("new port"), new_port)) - print("") - nw = NodeWriter() - nw.create_new_port(int(new_port)) - - reload_data = Loader() - new_group_list = reload_data.profile.group_list - from .stream import modify - modify(new_group_list[-1], new_stream) - return True - -@restart() -def new_user(): - gs = GroupSelector(_('add user')) - group = gs.group - group_list = gs.group_list - - if group == None: - pass - else: - email = "" - if type(group.node_list[0]) in (Vmess, Vless, Trojan): - while True: - is_duplicate_email=False - remail = random_email() - tip = _("create random email:") + ColorStr.cyan(remail) + _(", enter to use it or input new email: ") - email = input(tip) - if email == "": - email = remail - if not is_email(email): - print(_("not email, please input again")) - continue - - for loop_group in group_list: - for node in loop_group.node_list: - if node.user_info == None or node.user_info == '': - continue - elif node.user_info == email: - print(_("have same email, please input other")) - is_duplicate_email = True - break - if not is_duplicate_email: - break - - nw = NodeWriter(group.tag, group.index) - info = {'email': email} - if type(group.node_list[0]) == Trojan: - random_pass = ''.join(random.sample(string.digits + string.ascii_letters, 8)) - tip = _("create random trojan user password:") + ColorStr.cyan(random_pass) + _(", enter to use or input new password: ") - password = input(tip) - if password == "": - password = random_pass - info['password'] = password - elif type(group.node_list[0]) == Vless and group.tls == "reality": - info['flow'] = xtls_flow()[0] - nw.create_new_user(**info) - return True - - elif type(group.node_list[0]) == Socks: - print(_("local group is socks, please input user and password to create user")) - print("") - user = input(_("please input socks user: ")) - password = input(_("please input socks password: ")) - if user == "" or password == "": - print(_("socks user or password is null!!")) - exit(-1) - info = {"user":user, "pass": password} - nw = NodeWriter(group.tag, group.index) - nw.create_new_user(**info) - return True - - elif type(group.node_list[0]) == Mtproto: - print("") - print(_("Mtproto protocol only support one user!!")) - - elif type(group.node_list[0]) == SS: - print("") - print(_("Shadowsocks protocol only support one user, u can add new port to multiple SS!")) - -@restart() -def del_port(): - gs = GroupSelector(_('del port')) - group = gs.group - - if group == None: - pass - else: - print(_("del group info: ")) - print(group) - choice = readchar(_("delete?(y/n): ")).lower() - if choice == 'y': - nw = NodeWriter() - nw.del_port(group) - clean_iptables(group.port) - return True - else: - print(_("undo delete")) - -@restart() -def del_user(): - cs = ClientSelector(_('del user')) - group = cs.group - - if group == None: - pass - else: - client_index = cs.client_index - print(_("del user info:")) - print(group.show_node(client_index)) - choice = readchar(_("delete?(y/n): ")).lower() - if choice == 'y': - if len(group.node_list) == 1: - clean_iptables(group.port) - nw = NodeWriter() - nw.del_user(group, client_index) - return True - else: - print(_("undo delete")) \ No newline at end of file diff --git a/v2ray_util/config_modify/ss.py b/v2ray_util/config_modify/ss.py deleted file mode 100644 index 71f92738..00000000 --- a/v2ray_util/config_modify/ss.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import random -import string - -from ..util_core.group import SS -from ..util_core.v2ray import restart -from ..util_core.writer import GroupWriter -from ..util_core.selector import GroupSelector -from ..util_core.utils import ss_method, ColorStr, readchar - -class SSFactory: - def __init__(self): - self.method_tuple = ss_method() - - def get_method(self): - for index, method in enumerate(self.method_tuple): - print ("{}.{}".format(index + 1, method)) - choice = readchar(_("please select shadowsocks method:")) - if choice: - choice = int(choice) - if choice < 0 or choice > len(self.method_tuple): - print(_("input out of range!!")) - exit(-1) - else: - return self.method_tuple[choice - 1] - else: - exit(-1) - - def get_password(self): - random_pass = ''.join(random.sample(string.ascii_letters + string.digits, 16)) - new_pass =input("{} {}, {}".format(_("random generate password"), ColorStr.green(random_pass), _("enter to use, or input customize password: "))) - if not new_pass: - new_pass = random_pass - return new_pass - -@restart() -def modify(alter_type='method'): - # 外部传参来决定修改哪种, 默认修改method - correct_way = ("method", "password") - - if alter_type not in correct_way: - print(_("input error!")) - exit(-1) - - gs = GroupSelector(_('modify SS')) - group = gs.group - - if group == None: - exit(-1) - elif type(group.node_list[0]) != SS: - print("") - print(_("local group not Shadowsocks protocol!")) - print("") - exit(-1) - else: - sm = SSFactory() - gw = GroupWriter(group.tag, group.index) - if alter_type == correct_way[0]: - gw.write_ss_method(sm.get_method()) - elif alter_type == correct_way[1]: - gw.write_ss_password(sm.get_password()) - print("{0} {1} {2}\n".format(_("modify Shadowsocks"),alter_type, _("success"))) - return True \ No newline at end of file diff --git a/v2ray_util/config_modify/stream.py b/v2ray_util/config_modify/stream.py deleted file mode 100644 index a6b1365c..00000000 --- a/v2ray_util/config_modify/stream.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import random -import string - -from v2ray_util import run_type -from ..util_core.v2ray import restart -from ..util_core.writer import StreamWriter, GroupWriter -from ..util_core.selector import GroupSelector, CommonSelector -from ..util_core.utils import StreamType, header_type_list, ColorStr, all_port, xtls_flow, readchar - -from .ss import SSFactory - -class StreamModifier: - def __init__(self, group_tag='A', group_index=-1): - self.stream_type = [ - (StreamType.TCP, "TCP"), - (StreamType.TCP_HOST, "Fake HTTP"), - (StreamType.WS, "WebSocket"), - (StreamType.KCP, "mKCP"), - (StreamType.KCP_SRTP, "mKCP + srtp"), - (StreamType.KCP_UTP, "mKCP + utp"), - (StreamType.KCP_WECHAT, "mKCP + wechat-video"), - (StreamType.KCP_DTLS, "mKCP + dtls"), - (StreamType.KCP_WG, "mKCP + wireguard"), - (StreamType.H2, "HTTP/2"), - (StreamType.SOCKS, "Socks5"), - (StreamType.MTPROTO, "MTProto"), - (StreamType.SS, "Shadowsocks"), - (StreamType.QUIC, "Quic"), - (StreamType.GRPC, "gRPC"), - (StreamType.VLESS_KCP, "VLESS + mkcp"), - (StreamType.VLESS_UTP, "VLESS + mKCP + utp"), - (StreamType.VLESS_SRTP, "VLESS + mKCP + srtp"), - (StreamType.VLESS_WECHAT, "VLESS + mKCP + wechat-video"), - (StreamType.VLESS_DTLS, "VLESS + mKCP + dtls"), - (StreamType.VLESS_WG, "VLESS + mKCP + wireguard"), - (StreamType.VLESS_TCP, "VLESS_TCP"), - (StreamType.VLESS_TLS, "VLESS_TLS"), - (StreamType.VLESS_WS, "VLESS_WS"), - (StreamType.VLESS_REALITY, "VLESS_REALITY"), - (StreamType.VLESS_GRPC, "VLESS_GRPC"), - (StreamType.TROJAN, "Trojan"), - ] - self.group_tag = group_tag - self.group_index = group_index - - def select(self, sType): - sw = StreamWriter(self.group_tag, self.group_index, sType) - kw = {} - if sType in (StreamType.TCP_HOST, StreamType.WS): - host = input(_("please input fake domain: ")) - kw['host'] = host - elif sType == StreamType.SOCKS: - user = input(_("please input socks user: ")) - password = input(_("please input socks password: ")) - if user == "" or password == "": - print(_("socks user or password is null!!")) - exit(-1) - kw = {'user': user, 'pass': password} - elif sType == StreamType.SS: - sf = SSFactory() - kw = {"method": sf.get_method(), "password": sf.get_password()} - elif sType == StreamType.QUIC: - key = "" - security_list = ('none', "aes-128-gcm", "chacha20-poly1305") - print("") - security = CommonSelector(security_list, _("please select ss method: ")).select() - if security != "none": - key = ''.join(random.sample(string.ascii_letters + string.digits, 8)) - new_pass = input('{} {}, {}'.format(_("random generate password"), key, _("enter to use, or input customize password: "))) - if new_pass: - key = new_pass - - print("") - header = CommonSelector(header_type_list(), _("please select fake header: ")).select() - kw = {'security': security, 'key': key, 'header': header} - elif sType in (StreamType.VLESS_TLS, StreamType.VLESS_WS, StreamType.VLESS_REALITY, StreamType.VLESS_GRPC): - port_set = all_port() - if not "443" in port_set and sType == StreamType.VLESS_TLS: - print() - print(ColorStr.yellow(_("auto switch 443 port.."))) - gw = GroupWriter(self.group_tag, self.group_index) - gw.write_port(443) - sw = StreamWriter(self.group_tag, self.group_index, sType) - if sType == StreamType.VLESS_WS: - host = input(_("please input fake domain: ")) - kw['host'] = host - elif sType == StreamType.VLESS_TLS: - kw = {'flow': xtls_flow()[0]} - elif sType == StreamType.VLESS_REALITY: - serverName = input(_("please input reality serverName(domain): ")) - kw = {'flow': xtls_flow()[0]} - kw['serverNames'] = [serverName] - elif sType == StreamType.VLESS_GRPC and run_type == "xray": - choice = readchar(_("open xray grpc multiMode?(y/n): ")).lower() - if choice == 'y': - kw = {'mode': 'multi'} - - elif sType == StreamType.GRPC: - choice = readchar(_("open xray grpc multiMode?(y/n): ")).lower() - if choice == 'y': - kw = {'mode': 'multi'} - - elif sType == StreamType.TROJAN: - port_set = all_port() - if not "443" in port_set: - print() - print(ColorStr.yellow(_("auto switch 443 port.."))) - gw = GroupWriter(self.group_tag, self.group_index) - gw.write_port(443) - sw = StreamWriter(self.group_tag, self.group_index, sType) - random_pass = ''.join(random.sample(string.digits + string.ascii_letters, 8)) - tip = _("create random trojan user password:") + ColorStr.cyan(random_pass) + _(", enter to use or input new password: ") - password = input(tip) - if password == "": - password = random_pass - kw['password'] = password - sw.write(**kw) - - def random_kcp(self): - kcp_list = (StreamType.KCP_SRTP, StreamType.KCP_UTP, StreamType.KCP_WECHAT, StreamType.KCP_DTLS, StreamType.KCP_WG) - choice = random.randint(0, 4) - print("{}: {} \n".format(_("random generate (srtp | wechat-video | utp | dtls | wireguard) fake header, new protocol"), ColorStr.green(kcp_list[choice].value))) - self.select(kcp_list[choice]) - -@restart() -def modify(group=None, sType=None): - need_restart = False - if group == None: - need_restart = True - gs = GroupSelector(_('modify protocol')) - group = gs.group - - if group == None: - pass - else: - sm = StreamModifier(group.tag, group.index) - - if sType != None: - sm.select([v for v in StreamType if v.value == sType][0]) - print(_("modify protocol success")) - return - - if need_restart: - print("") - print("{}: {}".format(_("group protocol"), group.node_list[0].stream())) - - print("") - for index, stream_type in enumerate(sm.stream_type): - print("{0}.{1}".format(index + 1, stream_type[1])) - - print("") - choice = input(_("please select new protocol: ")) - - if not choice.isdecimal(): - print(_("please input number!")) - else: - choice = int(choice) - if choice > 0 and choice <= len(sm.stream_type): - if sm.stream_type[choice - 1][1] in ("MTProto", "Shadowsocks") and group.tls in ('tls', 'reality'): - print(_("{} MTProto/Shadowsocks not support https, close tls success!".format(run_type.capitalize()))) - sm.select(sm.stream_type[choice - 1][0]) - print(_("modify protocol success")) - if need_restart: - return True \ No newline at end of file diff --git a/v2ray_util/config_modify/tls.py b/v2ray_util/config_modify/tls.py deleted file mode 100644 index 6f43aedc..00000000 --- a/v2ray_util/config_modify/tls.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import socket -import os - -from ..util_core.v2ray import restart, V2ray -from ..util_core.writer import GroupWriter -from ..util_core.group import Mtproto, SS -from ..util_core.selector import GroupSelector -from ..util_core.utils import get_ip, gen_cert, readchar, is_ipv4, is_email - -class TLSModifier: - def __init__(self, group_tag, group_index, domain='', alpn=None): - self.domain = domain - self.alpn = alpn - self.writer = GroupWriter(group_tag, group_index) - - @restart(True) - def turn_on(self, need_restart=True): - cert_list=["letsencrypt", "zerossl", "buypass"] - print("") - print(_("1. Let's Encrypt certificate")) - print(_("2. ZeroSSL certificate")) - print(_("3. BuyPass certificate")) - print(_("4. Customize certificate file path")) - print("") - choice = readchar(_("please select: ")) - input_domain, input_email = self.domain, "" - if choice in ("1", "2", "3"): - if not input_domain: - local_ip = get_ip() - input_domain = input(_("please input your vps domain: ")) - try: - if is_ipv4(local_ip): - socket.gethostbyname(input_domain) - else: - socket.getaddrinfo(input_domain, None, socket.AF_INET6)[0][4][0] - except Exception: - print(_("domain check error!!!")) - print("") - return - - if choice in ("2", "3"): - while True: - input_email = input(_("please input your email to apply for a certificate: ")) - if not input_email: - print("email is null!") - return - elif not is_email(input_email): - print(_("not valid email, please input again!")) - continue - break - - print(_("auto generate SSL certificate, please wait..")) - V2ray.stop() - gen_cert(input_domain, cert_list[int(choice) - 1], input_email) - crt_file = "/root/.acme.sh/" + input_domain +"_ecc"+ "/fullchain.cer" - key_file = "/root/.acme.sh/" + input_domain +"_ecc"+ "/"+ input_domain +".key" - - self.writer.write_tls(True, crt_file=crt_file, key_file=key_file, domain=input_domain, alpn=self.alpn) - - elif choice == "4": - crt_file = input(_("please input certificate cert file path: ")) - key_file = input(_("please input certificate key file path: ")) - if not os.path.exists(crt_file) or not os.path.exists(key_file): - print(_("certificate cert or key not exist!")) - return - if not input_domain: - input_domain = input(_("please input the certificate cert file domain: ")) - if not input_domain: - print(_("domain is null!")) - return - self.writer.write_tls(True, crt_file=crt_file, key_file=key_file, domain=input_domain, alpn=self.alpn) - else: - print(_("input error!")) - return - return need_restart - - @restart() - def turn_off(self): - self.writer.write_tls(False) - return True - -def modify(): - gs = GroupSelector(_('modify tls')) - group = gs.group - - if group == None: - pass - else: - if type(group.node_list[0]) == Mtproto or type(group.node_list[0]) == SS: - print(_("MTProto/Shadowsocks protocol not support https!!!")) - print("") - return - tm = TLSModifier(group.tag, group.index) - tls_status = 'open' if group.tls == 'tls' else 'close' - print("{}: {}\n".format(_("group tls status"), tls_status)) - print(_("1.open TLS")) - print(_("2.close TLS")) - choice = readchar(_("please select: ")) - if not choice: - return - if not choice in ("1", "2"): - print(_("input error, please input again")) - return - - if choice == '1': - tm.turn_on() - elif choice == '2': - tm.turn_off() \ No newline at end of file diff --git a/v2ray_util/global_setting/__init__.py b/v2ray_util/global_setting/__init__.py deleted file mode 100644 index 5a2b0120..00000000 --- a/v2ray_util/global_setting/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ..util_core.trans import _ \ No newline at end of file diff --git a/v2ray_util/global_setting/ban_bt.py b/v2ray_util/global_setting/ban_bt.py deleted file mode 100644 index 4a5ed9ef..00000000 --- a/v2ray_util/global_setting/ban_bt.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from ..util_core.loader import Loader -from ..util_core.v2ray import restart -from ..util_core.writer import GlobalWriter -from ..util_core.utils import readchar - -@restart() -def manage(): - loader = Loader() - - profile = loader.profile - - print("{}: {}".format(_("Ban BT status"), profile.ban_bt)) - - choice = readchar(_("Ban BT?(y/n): ")).lower() - - if not choice: - return - - ban_bt = True if choice == 'y' else False - - gw = GlobalWriter(profile.group_list) - - gw.write_ban_bittorrent(ban_bt) - - print(_("modify success!")) - - return True \ No newline at end of file diff --git a/v2ray_util/global_setting/calcul_traffic.sh b/v2ray_util/global_setting/calcul_traffic.sh deleted file mode 100644 index b9170a09..00000000 --- a/v2ray_util/global_setting/calcul_traffic.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -port=$1 - -network=$2 - -[[ $# == 0 ]] && exit 1 - -if [[ $network ]];then - input_traffic=$(ip6tables -nvL INPUT -x 2>/dev/null|grep $port|awk '{sum += $2};END {printf("%.0f\n",sum)}') - - output_traffic=$(ip6tables -nvL OUTPUT -x 2>/dev/null|grep $port|awk '{sum += $2};END {printf("%.0f\n",sum)}') -else - input_traffic=$(iptables -nvL INPUT -x 2>/dev/null|grep $port|awk '{sum += $2};END {printf("%.0f\n",sum)}') - - output_traffic=$(iptables -nvL OUTPUT -x 2>/dev/null|grep $port|awk '{sum += $2};END {printf("%.0f\n",sum)}') -fi - -if [[ $input_traffic && $output_traffic ]]; then - total_traffic=`expr $input_traffic + $output_traffic` - echo "$input_traffic $output_traffic $total_traffic" -fi diff --git a/v2ray_util/global_setting/clean_iptables.sh b/v2ray_util/global_setting/clean_iptables.sh deleted file mode 100644 index 0ee4b4bb..00000000 --- a/v2ray_util/global_setting/clean_iptables.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -clean_iptables(){ - local type=$1 - if [[ $network == 1 ]];then - result=$(ip6tables -nvL $type --line-number 2>/dev/null|grep :|awk '{printf "%s %s\n",$1,$NF}'|sed 's/dpt://g'|sed 's/spt://g'|sort -n -k1 -r) - else - result=$(iptables -nvL $type --line-number 2>/dev/null|grep :|awk -F ':' '{print $2" " $1}'|awk '{print $2" "$1}'|sort -n -k1 -r) - fi - echo "$result" | while read line - do - line_array=($line) - if [[ ${line_array[1]} && -z $(netstat -tunlp|grep -w ${line_array[1]}) ]];then - [[ $network == 1 ]] && ip6tables -D $type ${line_array[0]} || iptables -D $type ${line_array[0]} - fi - done -} - -clean_iptables INPUT -clean_iptables OUTPUT diff --git a/v2ray_util/global_setting/clean_traffic.sh b/v2ray_util/global_setting/clean_traffic.sh deleted file mode 100644 index cedc5dd9..00000000 --- a/v2ray_util/global_setting/clean_traffic.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -port=$1 - -network=$2 - -[[ $# == 0 ]] && exit 1 - -clean_traffic(){ - local type=$1 - if [[ $network ]];then - result=$(ip6tables -nvL $type --line-numbers 2>/dev/null|grep -w "$port"|awk '{print $1}') - else - result=$(iptables -nvL $type --line-numbers 2>/dev/null|grep -w "$port"|awk '{print $1}') - fi - echo "$result" | while read line - do - if [[ ${line} ]];then - [[ $network ]] && ip6tables -Z $type $line || iptables -Z $type $line - fi - done -} - -clean_traffic INPUT -clean_traffic OUTPUT diff --git a/v2ray_util/global_setting/iptables_ctr.py b/v2ray_util/global_setting/iptables_ctr.py deleted file mode 100644 index 3dff86ae..00000000 --- a/v2ray_util/global_setting/iptables_ctr.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import subprocess -import pkg_resources - -from ..util_core.loader import Loader -from ..util_core.utils import ColorStr, calcul_iptables_traffic, readchar - -def manage(iptables_type=''): - - loader = Loader() - - profile = loader.profile - - group_list = profile.group_list - - while True: - if iptables_type == 'show': - choice = "1" - else: - print("") - print(_("Iptables Traffic Statistics")) - print("") - print(_("1.check statistics result")) - print("") - print(_("2.reset special port statistics")) - print("") - - choice = readchar(_("please select: ")) - if choice == "1": - print("") - ipv6 = True if profile.network == "ipv6" else False - for group in group_list: - print(calcul_iptables_traffic(group.port, ipv6)) - print("") - if iptables_type: - break - - elif choice == "2": - port = input(_("please input reset port:")) - if port and port.isnumeric(): - subprocess.call("bash {0} {1}".format(pkg_resources.resource_filename(__name__, "clean_traffic.sh"), str(port)), shell=True) - print(ColorStr.green(_("reset success!"))) - else: - print(ColorStr.red(_("input error!"))) - else: - break \ No newline at end of file diff --git a/v2ray_util/global_setting/stats_ctr.py b/v2ray_util/global_setting/stats_ctr.py deleted file mode 100644 index 1241c24c..00000000 --- a/v2ray_util/global_setting/stats_ctr.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import re -import subprocess - -from v2ray_util import run_type -from ..util_core.v2ray import V2ray -from ..util_core.loader import Loader -from ..util_core.writer import GlobalWriter -from ..util_core.utils import bytes_2_human_readable, ColorStr, readchar - -class StatsFactory: - def __init__(self, door_port): - self.door_port = door_port - self.downlink_value = 0 - self.uplink_value = 0 - - def __run_command(self, command): - value = 0 - result = bytes.decode(subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.strip()) - result_list = re.findall(r"\d+", result) - if len(result_list) > 0: - value = int(result_list[-1]) - return value - - def get_stats(self, meta_info, is_reset=False, is_group=False): - is_reset = "true" if is_reset else "false" - type_tag = ("inbound" if is_group else "user") - - if run_type == "xray": - stats_cmd = "cd /usr/bin/xray && ./xray api stats --server=127.0.0.1:{} -name \"{}>>>{}>>>traffic>>>{}\"" - if is_reset == "true": - stats_cmd = stats_cmd + " -reset" - else: - stats_cmd = "cd /usr/bin/v2ray && ./v2ctl api --server=127.0.0.1:{} StatsService.GetStats 'name: \"{}>>>{}>>>traffic>>>{}\"" + " reset: {}'".format(is_reset) - - stats_real_cmd = stats_cmd.format(str(self.door_port), type_tag, meta_info, "downlink") - self.downlink_value = self.__run_command(stats_real_cmd) - - stats_real_cmd = stats_cmd.format(str(self.door_port), type_tag, meta_info, "uplink") - self.uplink_value = self.__run_command(stats_real_cmd) - - def print_stats(self, horizontal=False): - if horizontal: - print("downlink: {0} uplink: {1} total: {2}".format( - ColorStr.cyan(bytes_2_human_readable(self.downlink_value, 2)), - ColorStr.cyan(bytes_2_human_readable(self.uplink_value, 2)), - ColorStr.cyan(bytes_2_human_readable(self.downlink_value + self.uplink_value, 2))) - ) - else: - print(''' -downlink: {0} -uplink: {1} -total: {2} - '''.format(ColorStr.cyan(bytes_2_human_readable(self.downlink_value, 2)), - ColorStr.cyan(bytes_2_human_readable(self.uplink_value, 2)), - ColorStr.cyan(bytes_2_human_readable(self.downlink_value + self.uplink_value, 2))) - ) - -def manage(stat_type=''): - - FIND_V2RAY_CRONTAB_CMD = "crontab -l|grep {}".format(run_type) - - DEL_UPDATE_TIMER_CMD = "crontab -l|sed '/SHELL=/d;/{}/d' > crontab.txt && crontab crontab.txt >/dev/null 2>&1 && rm -f crontab.txt >/dev/null 2>&1".format(run_type) - - while True: - loader = Loader() - - profile = loader.profile - - group_list = profile.group_list - - if stat_type == 'group': - choice = "4" - elif stat_type == 'user': - choice = "3" - else: - print("{}: {}".format(_("{} Traffic Statistics Status".format(run_type.capitalize())), profile.stats.status)) - - print("") - print(_("1.open statistics")) - print("") - print(_("2.close statistics")) - print("") - print(_("3.check user statistics result")) - print("") - print(_("4.check group statistics result")) - print("") - print(_("5.reset statistics")) - print("") - print(_("tip: restart {} will reset traffic statistics!!!".format(run_type))) - print("") - - choice = readchar(_("please select: ")) - - if choice in ("3", "4", "5") and not profile.stats.status: - print(_("only open traffic statistics to operate")) - print("") - if stat_type: - break - else: - continue - - if choice == "1": - if os.popen(FIND_V2RAY_CRONTAB_CMD).readlines(): - rchoice = readchar(_("open traffic statistics will close schedule update {}, continue?(y/n): ".format(run_type))) - if rchoice == "y" or rchoice == "Y": - #关闭定时更新v2ray服务 - os.system(DEL_UPDATE_TIMER_CMD) - else: - print(_("undo open traffic statistics!!")) - continue - gw = GlobalWriter(group_list) - gw.write_stats(True) - V2ray.restart() - print(_("open traffic statistics success!")) - print("") - - elif choice == "2": - gw = GlobalWriter(group_list) - gw.write_stats(False) - V2ray.restart() - print(_("close traffic statistics success!")) - print("") - - elif choice == "3": - sf = StatsFactory(profile.stats.door_port) - print("") - for group in group_list: - port_way = "-{}".format(group.end_port) if group.end_port else "" - for node in group.node_list: - print(''' -Group: {group.tag} -IP: {color_ip} -Port: {group.port}{port_way} -{node} - '''.format(group=group, color_ip=ColorStr.fuchsia(group.ip), node=node, port_way=port_way).strip()) - if node.user_info: - sf.get_stats(node.user_info, False) - sf.print_stats(horizontal=True) - else: - print(ColorStr.yellow(_("no effective email!!!"))) - print("") - if stat_type: - break - - elif choice == "4": - sf = StatsFactory(profile.stats.door_port) - print("") - for group in group_list: - tls = _("open") if group.tls == "tls" else _("close") - port_way = "-{}".format(group.end_port) if group.end_port else "" - print(''' -Group: {group.tag} -IP: {color_ip} -Port: {group.port}{port_way} -TLS: {tls} - '''.format(group=group, color_ip=ColorStr.fuchsia(group.ip), tls=tls, port_way=port_way).strip()) - sf.get_stats(group.tag, False, True) - sf.print_stats(horizontal=True) - print("") - if stat_type: - break - - elif choice == "5": - if group_list[-1].node_list[-1].user_number > 1: - print(profile) - schoice = input(_("please input number or group alphabet to reset: ")) - else: - schoice = "A" - - sf = StatsFactory(profile.stats.door_port) - schoice=schoice.upper() - if schoice.isnumeric() : - schoice = int(schoice) - if schoice > 0 and schoice <= group_list[-1].node_list[-1].user_number: - find = False - for group in group_list: - if find: - break - for node in group.node_list: - if node.user_number == schoice: - if node.user_info: - sf.get_stats(node.user_info, True) - sf.print_stats() - else: - print(_("no effective email!!!")) - print("") - find = True - break - elif schoice.isalpha() and schoice <= group_list[-1].tag: - sf.get_stats(schoice, True, True) - sf.print_stats() - else: - print(_("input error, please input again")) - print("") - else: - break \ No newline at end of file diff --git a/v2ray_util/global_setting/update_timer.py b/v2ray_util/global_setting/update_timer.py deleted file mode 100644 index a2aecabf..00000000 --- a/v2ray_util/global_setting/update_timer.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import time - -from v2ray_util import run_type -from ..util_core.config import Config -from ..util_core.loader import Loader -from ..util_core.utils import ColorStr, readchar - -def restartCron(): - IS_CENTOS = True if os.popen("command -v yum").readlines() else False - if os.path.exists("/.dockerenv"): - pass - elif IS_CENTOS: - os.system("systemctl restart crond >/dev/null 2>&1") - else: - os.system("systemctl restart cron >/dev/null 2>&1") - -def planUpdate(): - if Loader().profile.network == "ipv6": - print(ColorStr.yellow("ipv6 not support!")) - return - if Config().get_data("lang") == "zh": - origin_time_zone = int(time.strftime("%z", time.gmtime())[0:-2]) - beijing_time_zone, beijing_update_time = 8, 3 - diff_zone = beijing_time_zone - origin_time_zone - local_time = beijing_update_time - diff_zone - if local_time < 0: - local_time = 24 + local_time - elif local_time >= 24: - local_time = local_time - 24 - ColorStr.cyan("{}: {}".format(_("Beijing time: 3, VPS time"), local_time)) - else: - local_time = 3 - os.system('echo "SHELL=/bin/bash" >> crontab.txt && echo "$(crontab -l)" >> crontab.txt') - os.system('echo "0 {} * * * bash <(curl -L -s https://multi.netlify.app/go.sh) {}| tee -a /root/{}Update.log" >> crontab.txt'.format(local_time,"-x" if run_type == "xray" else "",run_type)) - os.system("crontab crontab.txt && rm -f crontab.txt") - restartCron() - print(ColorStr.green(_("success open schedule update task!"))) - -def manage(): - check_result = os.popen("crontab -l|grep {}".format(run_type)).readlines() - - status = _("open") if check_result else _("close") - - print("{}: {}".format(_("schedule update {} task".format(run_type)), status)) - - print("") - print(_("1.open schedule task")) - print("") - print(_("2.close schedule task")) - print("") - print(_("Tip: open schedule update {} at 3:00".format(run_type))) - - choice = readchar(_("please select: ")) - - if choice == "1": - if check_result: - print(ColorStr.yellow(_("have open schedule!"))) - return - else: - planUpdate() - elif choice == "2": - os.system("crontab -l|sed '/SHELL=/d;/{}/d' > crontab.txt && crontab crontab.txt && rm -f crontab.txt".format(run_type)) - print(ColorStr.green(_("close shedule task success"))) - restartCron() \ No newline at end of file diff --git a/v2ray_util/json_template/quic.json b/v2ray_util/json_template/quic.json deleted file mode 100644 index fcefa448..00000000 --- a/v2ray_util/json_template/quic.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "network": "quic", - "security": "none", - "tlsSettings": {}, - "tcpSettings": {}, - "kcpSettings": {}, - "httpSettings": {}, - "wsSettings": {}, - "quicSettings": { - "security": "none", - "key": "", - "header": { - "type": "none" - } - } -} \ No newline at end of file diff --git a/v2ray_util/json_template/reality.json b/v2ray_util/json_template/reality.json deleted file mode 100644 index c0a94fc8..00000000 --- a/v2ray_util/json_template/reality.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "show": false, - "dest": "www.yahoo.com:443", - "serverNames": ["www.yahoo.com", "news.yahoo.com"], - "privateKey": "kOsBHSgxhAfCeQIQyJvupiXTmQrMmsqi6y6Wc5OQZXc", - "shortIds": ["d49d578f280fd83a"] -} \ No newline at end of file diff --git a/v2ray_util/json_template/tcp.json b/v2ray_util/json_template/tcp.json deleted file mode 100644 index 81f1ca6f..00000000 --- a/v2ray_util/json_template/tcp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "network": "tcp", - "security": "none", - "tlsSettings": {}, - "tcpSettings": {}, - "kcpSettings": {}, - "wsSettings": {}, - "httpSettings": {}, - "quicSettings": {}, - "grpcSettings": {} -} \ No newline at end of file diff --git a/v2ray_util/json_template/vless.json b/v2ray_util/json_template/vless.json deleted file mode 100644 index 3da56100..00000000 --- a/v2ray_util/json_template/vless.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "clients": [ - { - "id": "d4e321ea-e118-11ea-a265-42010a8c0002" - } - ], - "decryption": "none", - "fallbacks": [ - { - "dest": 80 - } - ] -} \ No newline at end of file diff --git a/v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.mo b/v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.mo deleted file mode 100644 index 1095ea30..00000000 Binary files a/v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.mo and /dev/null differ diff --git a/v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.po b/v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.po deleted file mode 100644 index 419edbda..00000000 --- a/v2ray_util/locale_i18n/en_US/LC_MESSAGES/lang.po +++ /dev/null @@ -1,21 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2019-03-19 22:21+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" - -msgid "Change Language" -msgstr "切换到中文" - -msgid "please run again to become effective!" -msgstr "请重新运行来生效!" \ No newline at end of file diff --git a/v2ray_util/locale_i18n/en_US/LC_MESSAGES/msgfmt.py b/v2ray_util/locale_i18n/en_US/LC_MESSAGES/msgfmt.py deleted file mode 100644 index 63d52d1f..00000000 --- a/v2ray_util/locale_i18n/en_US/LC_MESSAGES/msgfmt.py +++ /dev/null @@ -1,238 +0,0 @@ -#! /usr/bin/env python3 -# Written by Martin v. Löwis - -"""Generate binary message catalog from textual translation description. - -This program converts a textual Uniforum-style message catalog (.po file) into -a binary GNU catalog (.mo file). This is essentially the same function as the -GNU msgfmt program, however, it is a simpler implementation. - -Usage: msgfmt.py [OPTIONS] filename.po - -Options: - -o file - --output-file=file - Specify the output file to write to. If omitted, output will go to a - file named filename.mo (based off the input file name). - - -h - --help - Print this message and exit. - - -V - --version - Display version information and exit. -""" - -import os -import sys -import ast -import getopt -import struct -import array -from email.parser import HeaderParser - -__version__ = "1.1" - -MESSAGES = {} - - - -def usage(code, msg=''): - print(__doc__, file=sys.stderr) - if msg: - print(msg, file=sys.stderr) - sys.exit(code) - - - -def add(id, str, fuzzy): - "Add a non-fuzzy translation to the dictionary." - global MESSAGES - if not fuzzy and str: - MESSAGES[id] = str - - - -def generate(): - "Return the generated output." - global MESSAGES - # the keys are sorted in the .mo file - keys = sorted(MESSAGES.keys()) - offsets = [] - ids = strs = b'' - for id in keys: - # For each string, we need size and file offset. Each string is NUL - # terminated; the NUL does not count into the size. - offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) - ids += id + b'\0' - strs += MESSAGES[id] + b'\0' - output = '' - # The header is 7 32-bit unsigned integers. We don't use hash tables, so - # the keys start right after the index tables. - # translated string. - keystart = 7*4+16*len(keys) - # and the values start after the keys - valuestart = keystart + len(ids) - koffsets = [] - voffsets = [] - # The string table first has the list of keys, then the list of values. - # Each entry has first the size of the string, then the file offset. - for o1, l1, o2, l2 in offsets: - koffsets += [l1, o1+keystart] - voffsets += [l2, o2+valuestart] - offsets = koffsets + voffsets - output = struct.pack("Iiiiiii", - 0x950412de, # Magic - 0, # Version - len(keys), # # of entries - 7*4, # start of key index - 7*4+len(keys)*8, # start of value index - 0, 0) # size and offset of hash table - output += array.array("i", offsets).tobytes() - output += ids - output += strs - return output - - - -def make(filename, outfile): - ID = 1 - STR = 2 - - # Compute .mo name from .po name and arguments - if filename.endswith('.po'): - infile = filename - else: - infile = filename + '.po' - if outfile is None: - outfile = os.path.splitext(infile)[0] + '.mo' - - try: - with open(infile, 'rb') as f: - lines = f.readlines() - except IOError as msg: - print(msg, file=sys.stderr) - sys.exit(1) - - section = None - fuzzy = 0 - - # Start off assuming Latin-1, so everything decodes without failure, - # until we know the exact encoding - encoding = 'latin-1' - - # Parse the catalog - lno = 0 - for l in lines: - l = l.decode(encoding) - lno += 1 - # If we get a comment line after a msgstr, this is a new entry - if l[0] == '#' and section == STR: - add(msgid, msgstr, fuzzy) - section = None - fuzzy = 0 - # Record a fuzzy mark - if l[:2] == '#,' and 'fuzzy' in l: - fuzzy = 1 - # Skip comments - if l[0] == '#': - continue - # Now we are in a msgid section, output previous section - if l.startswith('msgid') and not l.startswith('msgid_plural'): - if section == STR: - add(msgid, msgstr, fuzzy) - if not msgid: - # See whether there is an encoding declaration - p = HeaderParser() - charset = p.parsestr(msgstr.decode(encoding)).get_content_charset() - if charset: - encoding = charset - section = ID - l = l[5:] - msgid = msgstr = b'' - is_plural = False - # This is a message with plural forms - elif l.startswith('msgid_plural'): - if section != ID: - print('msgid_plural not preceded by msgid on %s:%d' % (infile, lno), - file=sys.stderr) - sys.exit(1) - l = l[12:] - msgid += b'\0' # separator of singular and plural - is_plural = True - # Now we are in a msgstr section - elif l.startswith('msgstr'): - section = STR - if l.startswith('msgstr['): - if not is_plural: - print('plural without msgid_plural on %s:%d' % (infile, lno), - file=sys.stderr) - sys.exit(1) - l = l.split(']', 1)[1] - if msgstr: - msgstr += b'\0' # Separator of the various plural forms - else: - if is_plural: - print('indexed msgstr required for plural on %s:%d' % (infile, lno), - file=sys.stderr) - sys.exit(1) - l = l[6:] - # Skip empty lines - l = l.strip() - if not l: - continue - l = ast.literal_eval(l) - if section == ID: - msgid += l.encode(encoding) - elif section == STR: - msgstr += l.encode(encoding) - else: - print('Syntax error on %s:%d' % (infile, lno), \ - 'before:', file=sys.stderr) - print(l, file=sys.stderr) - sys.exit(1) - # Add last entry - if section == STR: - add(msgid, msgstr, fuzzy) - - # Compute output - output = generate() - - try: - with open(outfile,"wb") as f: - f.write(output) - except IOError as msg: - print(msg, file=sys.stderr) - - - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hVo:', - ['help', 'version', 'output-file=']) - except getopt.error as msg: - usage(1, msg) - - outfile = None - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-V', '--version'): - print("msgfmt.py", __version__) - sys.exit(0) - elif opt in ('-o', '--output-file'): - outfile = arg - # do it - if not args: - print('No input file given', file=sys.stderr) - print("Try `msgfmt --help' for more information.", file=sys.stderr) - return - - for filename in args: - make(filename, outfile) - - -if __name__ == '__main__': - main() diff --git a/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.mo b/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.mo deleted file mode 100644 index 362158bf..00000000 Binary files a/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.mo and /dev/null differ diff --git a/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.po b/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.po deleted file mode 100644 index f94d7537..00000000 --- a/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/lang.po +++ /dev/null @@ -1,729 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2019-03-19 22:21+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: pygettext.py 1.5\n" - -### 通用翻译 start ### -msgid "please select: " -msgstr "请选择: " - -msgid "input error, please input again" -msgstr "输入有误, 请重新输入" - -msgid "input error, please check is number" -msgstr "输入有误, 请检查是否为数字" - -msgid "input error!" -msgstr "输入有误!" - -msgid "modify success!" -msgstr "修改成功!" - -### 通用翻译 end ### - -### main.py start ### -msgid "Welcome to v2ray manager" -msgstr "欢迎使用 v2ray 管理程序" - -msgid "Welcome to xray manager" -msgstr "欢迎使用 xray 管理程序" - -msgid "1.V2ray Manage" -msgstr "1.服务管理" - -msgid "1.Xray Manage" -msgstr "1.服务管理" - -msgid "2.Group Manage" -msgstr "2.用户管理" - -msgid "3.Modify Config" -msgstr "3.更改配置" - -msgid "4.Check Config" -msgstr "4.查看配置" - -msgid "5.Global Setting" -msgstr "5.全局功能" - -msgid "6.Update V2ray" -msgstr "6.更新V2ray" - -msgid "6.Update Xray" -msgstr "6.更新Xray" - -msgid "7.Generate Client Json" -msgstr "7.生成客户端配置文件" - -msgid "start v2ray" -msgstr "启动服务" - -msgid "start xray" -msgstr "启动服务" - -msgid "stop v2ray" -msgstr "停止服务" - -msgid "stop xray" -msgstr "停止服务" - -msgid "restart v2ray" -msgstr "重启服务" - -msgid "restart xray" -msgstr "重启服务" - -msgid "v2ray status" -msgstr "运行状态" - -msgid "xray status" -msgstr "运行状态" - -msgid "v2ray log" -msgstr "运行日志" - -msgid "xray log" -msgstr "运行日志" - -msgid "add user" -msgstr "新增用户" - -msgid "add port" -msgstr "新增端口" - -msgid "del user" -msgstr "删除用户" - -msgid "del port" -msgstr "删除端口" - -msgid "modify email" -msgstr "更改email" - -msgid "modify UUID" -msgstr "更改UUID" - -msgid "modify alterID" -msgstr "更改alterID" - -msgid "modify port" -msgstr "更改主端口" - -msgid "modify stream" -msgstr "更改传输方式" - -msgid "modify tls" -msgstr "更改TLS设置" - -msgid "modify tcpFastOpen" -msgstr "更改tcpFastOpen设置" - -msgid "modify dyn_port" -msgstr "更改动态端口" - -msgid "modify shadowsocks method" -msgstr "更改Shadowsocks加密方式" - -msgid "modify shadowsocks password" -msgstr "更改Shadowsocks密码" - -msgid "CDN mode(need domain)" -msgstr "走CDN(需要域名)" - -msgid "V2ray Traffic Statistics" -msgstr "流量统计(v2ray)" - -msgid "Xray Traffic Statistics" -msgstr "流量统计(xray)" - -msgid "Iptables Traffic Statistics" -msgstr "流量统计(iptables)" - -msgid "Ban Bittorrent" -msgstr "禁止bittorrent" - -msgid "Schedule Update V2ray" -msgstr "定时更新V2ray" - -msgid "Schedule Update Xray" -msgstr "定时更新Xray" - -msgid "Clean V2ray Log" -msgstr "清理v2ray日志" - -msgid "Clean Xray Log" -msgstr "清理xray日志" - -msgid "Change Language" -msgstr "Switch to English" - -### main.py end ### - -### group.py start ### - -msgid "HTTPS Socks5 don't support telegram share link" -msgstr "HTTPS的Socks5不支持tg的分享连接" - -### group.py end ### - -### profile.py start ### - -msgid "Tip: The same group's node protocol, port, tls are the same." -msgstr "Tip: 同一Group的节点传输方式,端口配置,TLS等设置相同" - -### profile.py end ### - -### selector.py start ### - -msgid "del" -msgstr "删除" - -msgid "last node can't delete!!!" -msgstr "仅剩最后一个节点无法删除!!!" - -msgid "please input number to" -msgstr "请输入数字来" - -msgid "last group can't delete!!!" -msgstr "仅剩最后一个端口无法删除!!!" - -msgid "please input group to" -msgstr "请输入节点Group来" - -msgid "input error, please check group" -msgstr "输入有误,请检查组" - -msgid "exist" -msgstr "是否存在" - -### selector.py end ### - -### v2ray.py start ### - -msgid "clean v2ray log success!" -msgstr "清理v2ray日志成功!" - -msgid "clean xray log success!" -msgstr "清理xray日志成功!" - -msgid "ipv6 network not support update v2ray online, please manual donwload v2ray to update!" -msgstr "ipv6 网络不支持v2ray的在线更新, 请手动下载v2ray来更新!" - -msgid "ipv6 network not support update xray online, please manual donwload xray to update!" -msgstr "ipv6 网络不支持xray的在线更新, 请手动下载xray来更新!" - -msgid "download v2ray-linux-xx.zip and run 'bash <(curl -L -s https://multi.netlify.app/go.sh) -l v2ray-linux-xx.zip' to update" -msgstr "下载v2ray-linux-xx.zip包然后运行 'bash <(curl -L -s https://multi.netlify.app/go.sh) -l v2ray-linux-xx.zip' 来更新" - -msgid "download Xray-linux-xx.zip and run 'bash <(curl -L -s https://multi.netlify.app/go.sh) -l Xray-linux-xx.zip -x' to update" -msgstr "下载Xray-linux-xx.zip包然后运行 'bash <(curl -L -s https://multi.netlify.app/go.sh) -l Xray-linux-xx.zip -x' 来更新" - -msgid "check v2ray no install, auto install v2ray.." -msgstr "检测到本机没安装v2ray, 正在自动安装.." - -msgid "check xray no install, auto install xray.." -msgstr "检测到本机没安装xray, 正在自动安装.." - -### v2ray.py end ### - -### writer.py start ### - -msgid "close tls will also close HTTP/2!" -msgstr "关闭tls同时也会关闭HTTP/2!" - -msgid "already reset protocol to origin kcp" -msgstr "已重置为kcp传输方式, 若要其他方式请自行切换" - -msgid "add port group success!" -msgstr "新增端口组成功!" - -msgid "add socks5 user success!" -msgstr "新建Socks5用户成功!" - -msgid "add trojan user success!" -msgstr "新建trojan用户成功!" - -msgid "add user success!" -msgstr "新建用户成功!" - -msgid "del user success!" -msgstr "删除用户成功!" - -msgid "del port success!" -msgstr "删除端口成功!" - -### writer.py end ### - -### ban_bt.py start ### - -msgid "Ban BT status" -msgstr "当前禁止BT状态" - -msgid "Ban BT?(y/n): " -msgstr "是否禁止BT(y/n):" - -### ban_bt.py end ### - -### iptables_ctr.py start ### - -msgid "Iptables Port Traffic Statistics" -msgstr "Iptables 端口流量统计" - -msgid "1.check statistics result" -msgstr "1.查看流量统计" - -msgid "2.reset special port statistics" -msgstr "2.重置流量统计" - -msgid "please input reset port:" -msgstr "请输入要重置流量的端口:" - -msgid "reset success!" -msgstr "重置成功!" - -### iptables_ctr.py end ### - -### stats_ctr.py start ### - -msgid "no data traffic now!" -msgstr "当前无流量数据, 请使用流量片刻再来查看统计!" - -msgid "V2ray Traffic Statistics Status" -msgstr "当前V2ray流量统计状态" - -msgid "Xray Traffic Statistics Status" -msgstr "当前Xray流量统计状态" - -msgid "1.open statistics" -msgstr "1.开启流量统计" - -msgid "2.close statistics" -msgstr "2.关闭流量统计" - -msgid "3.check user statistics result" -msgstr "3.查看用户流量统计" - -msgid "4.check group statistics result" -msgstr "4.查看组流量统计" - -msgid "5.reset statistics" -msgstr "5.重置流量统计" - -msgid "tip: restart v2ray will reset traffic statistics!!!" -msgstr "tip: 重启v2ray会重置流量统计!!!" - -msgid "tip: restart xray will reset traffic statistics!!!" -msgstr "tip: 重启xray会重置流量统计!!!" - -msgid "open traffic statistics will close schedule update v2ray, continue?(y/n): " -msgstr "开启流量统计会关闭定时更新v2ray服务, 是否继续y/n: " - -msgid "open traffic statistics will close schedule update xray, continue?(y/n): " -msgstr "开启流量统计会关闭定时更新xray服务, 是否继续y/n: " - -msgid "undo open traffic statistics!!" -msgstr "撤销开启流量统计!!" - -msgid "open traffic statistics success!" -msgstr "开启流量统计成功!" - -msgid "close traffic statistics success!" -msgstr "关闭流量统计成功!" - -msgid "only open traffic statistics to operate" -msgstr "流量统计开启状态才能操作" - -msgid "please input number or group alphabet to reset: " -msgstr "请输入流量的组别(字母)或者序号(数字)来重置: " - -msgid "no effective email!!!" -msgstr "无有效邮箱,无法统计!!!" - -### stats_ctr.py end ### - -### base.py start ### - -msgid "modify alterId" -msgstr "修改 alterId" - -msgid "node alterID" -msgstr "当前节点alterID" - -msgid "please input new alterId: " -msgstr "请输入新的alterID: " - -msgid "alterId modify success!" -msgstr "alterID修改成功!" - -msgid "only vmess protocol can modify alterId!" -msgstr "只有vmess协议才能修改alterId!" - -msgid "modify dyn_port" -msgstr "修改动态端口" - -msgid "dyn_port status" -msgstr "当前组的动态端口状态" - -msgid "open/close dyn_port(y/n): " -msgstr "是否开启动态端口(y/n): " - -msgid "please input dyn_port alterID(default 32): " -msgstr "请为动态端口设置alterID(默认32): " - -msgid "open dyn_port success!" -msgstr "成功开启动态端口!" - -msgid "close dyn_port success!" -msgstr "成功关闭动态端口!" - -msgid "modify email" -msgstr "修改email" - -msgid "Socks5 don't support email!" -msgstr "Socks5节点 不支持写入email!" - -msgid "node email" -msgstr "当前节点email为" - -msgid "please input new email: " -msgstr "请输入新的email地址: " - -msgid "not email, please input again" -msgstr "不是合格的email格式,请重新输入" - -msgid "have same email, please input other" -msgstr "已经有重复的email, 请重新输入" - -msgid "modify email success!" -msgstr "修改email成功!" - -msgid "modify uuid" -msgstr "修改uuid" - -msgid "node UUID" -msgstr "当前节点UUID为" - -msgid "get new UUID?(y/n): " -msgstr "是否要随机生成一个新的UUID (y/n):" - -msgid "new UUID" -msgstr "新的UUID为" - -msgid "UUID modify success!" -msgstr "UUID修改成功!" - -msgid "undo modify" -msgstr "已取消生成新的UUID,未执行任何操作" - -msgid "only vmess/VLESS protocol can modify uuid!" -msgstr "只有vmess/VLESS协议才能修改uuid!" - -msgid "modify port" -msgstr "修改port" - -msgid "group port" -msgstr "当前组的端口信息为" - -msgid "please input new port(support range port(use '-' as separator), all range port can effect):" -msgstr "请输入新端口(支持输入端口范围, 用'-'隔开, 表示该范围的全部端口生效):" - -msgid "port modify success!" -msgstr "端口修改成功!" - -msgid "modify tcpFastOpen" -msgstr "修改tcpFastOpen" - -msgid "V2ray MTProto/Shadowsocks don't support tcpFastOpen!!!" -msgstr "V2ray MTProto/Shadowsocks协议不支持配置tcpFastOpen!!!" - -msgid "Xray MTProto/Shadowsocks don't support tcpFastOpen!!!" -msgstr "Xray MTProto/Shadowsocks协议不支持配置tcpFastOpen!!!" - -msgid "group tcpFastOpen" -msgstr "当前组的tcpFastOpen状态" - -msgid "1.open TFO(force open)" -msgstr "1.开启TFO(强制开启)" - -msgid "2.close TFO(force close)" -msgstr "2.关闭TFO(强制关闭)" - -msgid "3.delete TFO(use system default profile)" -msgstr "3.删除TFO(使用系统默认设置)" - -### base.py end ### - -### multiple.py start ### - -msgid "input error! input -h or --help to get help" -msgstr "输入的参数无效! 输入-h 或者--help查看帮助" - -msgid "please input socks user: " -msgstr "请输入socks的用户名: " - -msgid "please input socks password: " -msgstr "请输入socks的密码: " - -msgid "socks user or password is null!!" -msgstr "socks的用户名或者密码不能为空" - -msgid "random generate (srtp | wechat-video | utp | dtls) fake header, new protocol" -msgstr "随机一种 (srtp | wechat-video | utp | dtls) header伪装, 当前生成" - -msgid "random generate port" -msgstr "产生随机端口" - -msgid "enter to use, or input customize port" -msgstr "回车直接以该端口新建Group, 否则输入自定义端口" - -msgid "port is use, please input other port!" -msgstr "端口已经被使用, 请输入其他端口!" - -msgid "new port" -msgstr "新端口为" - -msgid "add user" -msgstr "新增用户" - -msgid "create random email:" -msgstr "生成随机邮箱:" - -msgid ", enter to use it or input new email: " -msgstr ", 回车直接使用或者输入自定义邮箱: " - -msgid "local group is socks, please input user and password to create user" -msgstr "当前组为socks组, 请输入用户密码创建新的socks用户" - -msgid "Mtproto protocol only support one user!!" -msgstr "当前选择的组为MTProto协议, 该协议只支持同组的第一个用户生效, 所以没必要新增用户!" - -msgid "Shadowsocks protocol only support one user, u can add new port to multiple SS!" -msgstr "当前选择的组为Shadowsocks协议, ss协议只支持一个用户一个端口, 想多用户请新增端口!" - -msgid "del group info: " -msgstr "你要删除的Group组所有节点信息: " - -msgid "delete?(y/n): " -msgstr "是否删除y/n:" - -msgid "undo delete" -msgstr "撤销删除" - -msgid "del user info:" -msgstr "你选择的user信息:" - -msgid "create random trojan user password:" -msgstr "生成随机trojan用户密码:" - -msgid ", enter to use or input new password: " -msgstr ", 回车直接使用或者输入自定义密码: " - -### multiple.py end ### - -### ss.py start ### - -msgid "please select shadowsocks method:" -msgstr "请选择shadowsocks的加密方式:" - -msgid "input out of range!!" -msgstr "输入错误,请检查是否符合范围中" - -msgid "random generate password" -msgstr "产生随机密码" - -msgid "enter to use, or input customize password: " -msgstr "回车直接使用该密码, 否则输入自定义密码: " - -msgid "modify SS" -msgstr "修改SS" - -msgid "local group not Shadowsocks protocol!" -msgstr "当前选择组不是Shadowsocks协议!" - -msgid "modify Shadowsocks" -msgstr "修改Shadowsocks" - -msgid "success" -msgstr "成功" - -### ss.py end ### - -### stream.py start ### - -msgid "please input fake domain: " -msgstr "请输入伪装域名(不需要直接回车):" - -msgid "please select ss method: " -msgstr "请输入序号选择加密方法: " - -msgid "please input reality serverName(domain): " -msgstr "请输入客户端访问的域名(serverName): " - -msgid "open xray grpc multiMode?(y/n): " -msgstr "是否开启grpc multiMode(y/n): " - -msgid "please select fake header: " -msgstr "请输入序号选择伪装协议: " - -msgid "modify protocol" -msgstr "修改传输方式" - -msgid "group protocol" -msgstr "当前组的传输方式为" - -msgid "please select new protocol: " -msgstr "请选择新的传输方式: " - -msgid "please input number!" -msgstr "请输入数字!" - -msgid "V2ray MTProto/Shadowsocks not support https, close tls success!" -msgstr "V2ray MTProto/Shadowsocks不支持https, 关闭tls成功!" - -msgid "Xray MTProto/Shadowsocks not support https, close tls success!" -msgstr "Xray MTProto/Shadowsocks不支持https, 关闭tls成功!" - -msgid "modify protocol success" -msgstr "传输模式修改成功!" - -msgid "auto switch 443 port.." -msgstr "自动切换到443端口..." - -### stream.py end ### - -### tls.py start ### - -msgid "1. Let's Encrypt certificate" -msgstr "1. Let’s Encrypt 证书" - -msgid "2. ZeroSSL certificate" -msgstr "2. ZeroSSL 证书" - -msgid "3. BuyPass certificate" -msgstr "3. BuyPass 证书" - -msgid "4. Customize certificate file path" -msgstr "4. 自定义已存在的其他路径的证书" - -msgid "local vps ip address: " -msgstr "本机器IP地址为:" - -msgid "please input your vps domain: " -msgstr "请输入您绑定的域名:" - -msgid "domain check error!!!" -msgstr "域名检测错误!!!" - -msgid "domain can't analysis to local ip!!!" -msgstr "输入的域名与本机ip不符!!!" - -msgid "please input your email to apply for a certificate: " -msgstr "请输入用于申请域名的邮箱:" - -msgid "not valid email, please input again!" -msgstr "不是有效的邮箱, 请重新输入!" - -msgid "auto generate SSL certificate, please wait.." -msgstr "正在获取SSL证书,请稍等.." - -msgid "please input certificate cert file path: " -msgstr "请输入证书cert文件路径: " - -msgid "please input certificate key file path: " -msgstr "请输入证书key文件路径: " - -msgid "certificate cert or key not exist!" -msgstr "证书crt文件或者key文件指定路径不存在!" - -msgid "please input the certificate cert file domain: " -msgstr "请输入证书的域名: " - -msgid "domain is null!" -msgstr "证书域名为空!" - -msgid "group tls status" -msgstr "当前选择组tls状态" - -msgid "1.open TLS" -msgstr "1.开启 TLS" - -msgid "2.close TLS" -msgstr "2.关闭 TLS" - -msgid "MTProto/Shadowsocks protocol not support https!!!" -msgstr "MTProto/Shadowsocks协议不支持配置https!!!" - -### tls.py end ### - -### cdn.py start ### -msgid "modify cdn" -msgstr "修改cdn" - -msgid "please input run cdn mode domain: " -msgstr "请输入走cdn的域名: " - -msgid "domain is empty!" -msgstr "域名为空!" - -msgid "this group domain {}, enter to use it or input other domain: " -msgstr "当前组域名为: {}, 回车继续使用或者输入新的域名: " - -msgid "must be close cdn proxy!" -msgstr "必须先关闭cdn的代理!" - -msgid "please select https port to cdn: " -msgstr "请选择走https cdn端口的序号: " - -msgid "please select protocol to cdn: " -msgstr "请选择走cdn的协议: " - -### update_timer.py start ### - -msgid "open" -msgstr "开启" - -msgid "close" -msgstr "关闭" - -msgid "schedule update v2ray task" -msgstr "当前定时更新任务状态" - -msgid "schedule update xray task" -msgstr "当前定时更新任务状态" - -msgid "1.open schedule task" -msgstr "1.开启定时更新任务" - -msgid "2.close schedule task" -msgstr "2.关闭定时更新任务" - -msgid "Tip: open schedule update v2ray at 3:00" -msgstr "Tip: 开启定时更新v2ray的更新时间为每天北京时间3:00更新" - -msgid "Tip: open schedule update xray at 3:00" -msgstr "Tip: 开启定时更新xray的更新时间为每天北京时间3:00更新" - -msgid "have open schedule!" -msgstr "当前定时更新已开启,无需重复操作!" - -msgid "close shedule task success" -msgstr "成功关闭定时更新任务" - -msgid "Beijing time: 3, VPS time" -msgstr "北京时间: 3, VPS 时间" - -msgid "success open schedule update task!" -msgstr "成功设置定时更新任务!" - -### update_timer.py end ### \ No newline at end of file diff --git a/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/msgfmt.py b/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/msgfmt.py deleted file mode 100644 index 63d52d1f..00000000 --- a/v2ray_util/locale_i18n/zh_CH/LC_MESSAGES/msgfmt.py +++ /dev/null @@ -1,238 +0,0 @@ -#! /usr/bin/env python3 -# Written by Martin v. Löwis - -"""Generate binary message catalog from textual translation description. - -This program converts a textual Uniforum-style message catalog (.po file) into -a binary GNU catalog (.mo file). This is essentially the same function as the -GNU msgfmt program, however, it is a simpler implementation. - -Usage: msgfmt.py [OPTIONS] filename.po - -Options: - -o file - --output-file=file - Specify the output file to write to. If omitted, output will go to a - file named filename.mo (based off the input file name). - - -h - --help - Print this message and exit. - - -V - --version - Display version information and exit. -""" - -import os -import sys -import ast -import getopt -import struct -import array -from email.parser import HeaderParser - -__version__ = "1.1" - -MESSAGES = {} - - - -def usage(code, msg=''): - print(__doc__, file=sys.stderr) - if msg: - print(msg, file=sys.stderr) - sys.exit(code) - - - -def add(id, str, fuzzy): - "Add a non-fuzzy translation to the dictionary." - global MESSAGES - if not fuzzy and str: - MESSAGES[id] = str - - - -def generate(): - "Return the generated output." - global MESSAGES - # the keys are sorted in the .mo file - keys = sorted(MESSAGES.keys()) - offsets = [] - ids = strs = b'' - for id in keys: - # For each string, we need size and file offset. Each string is NUL - # terminated; the NUL does not count into the size. - offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) - ids += id + b'\0' - strs += MESSAGES[id] + b'\0' - output = '' - # The header is 7 32-bit unsigned integers. We don't use hash tables, so - # the keys start right after the index tables. - # translated string. - keystart = 7*4+16*len(keys) - # and the values start after the keys - valuestart = keystart + len(ids) - koffsets = [] - voffsets = [] - # The string table first has the list of keys, then the list of values. - # Each entry has first the size of the string, then the file offset. - for o1, l1, o2, l2 in offsets: - koffsets += [l1, o1+keystart] - voffsets += [l2, o2+valuestart] - offsets = koffsets + voffsets - output = struct.pack("Iiiiiii", - 0x950412de, # Magic - 0, # Version - len(keys), # # of entries - 7*4, # start of key index - 7*4+len(keys)*8, # start of value index - 0, 0) # size and offset of hash table - output += array.array("i", offsets).tobytes() - output += ids - output += strs - return output - - - -def make(filename, outfile): - ID = 1 - STR = 2 - - # Compute .mo name from .po name and arguments - if filename.endswith('.po'): - infile = filename - else: - infile = filename + '.po' - if outfile is None: - outfile = os.path.splitext(infile)[0] + '.mo' - - try: - with open(infile, 'rb') as f: - lines = f.readlines() - except IOError as msg: - print(msg, file=sys.stderr) - sys.exit(1) - - section = None - fuzzy = 0 - - # Start off assuming Latin-1, so everything decodes without failure, - # until we know the exact encoding - encoding = 'latin-1' - - # Parse the catalog - lno = 0 - for l in lines: - l = l.decode(encoding) - lno += 1 - # If we get a comment line after a msgstr, this is a new entry - if l[0] == '#' and section == STR: - add(msgid, msgstr, fuzzy) - section = None - fuzzy = 0 - # Record a fuzzy mark - if l[:2] == '#,' and 'fuzzy' in l: - fuzzy = 1 - # Skip comments - if l[0] == '#': - continue - # Now we are in a msgid section, output previous section - if l.startswith('msgid') and not l.startswith('msgid_plural'): - if section == STR: - add(msgid, msgstr, fuzzy) - if not msgid: - # See whether there is an encoding declaration - p = HeaderParser() - charset = p.parsestr(msgstr.decode(encoding)).get_content_charset() - if charset: - encoding = charset - section = ID - l = l[5:] - msgid = msgstr = b'' - is_plural = False - # This is a message with plural forms - elif l.startswith('msgid_plural'): - if section != ID: - print('msgid_plural not preceded by msgid on %s:%d' % (infile, lno), - file=sys.stderr) - sys.exit(1) - l = l[12:] - msgid += b'\0' # separator of singular and plural - is_plural = True - # Now we are in a msgstr section - elif l.startswith('msgstr'): - section = STR - if l.startswith('msgstr['): - if not is_plural: - print('plural without msgid_plural on %s:%d' % (infile, lno), - file=sys.stderr) - sys.exit(1) - l = l.split(']', 1)[1] - if msgstr: - msgstr += b'\0' # Separator of the various plural forms - else: - if is_plural: - print('indexed msgstr required for plural on %s:%d' % (infile, lno), - file=sys.stderr) - sys.exit(1) - l = l[6:] - # Skip empty lines - l = l.strip() - if not l: - continue - l = ast.literal_eval(l) - if section == ID: - msgid += l.encode(encoding) - elif section == STR: - msgstr += l.encode(encoding) - else: - print('Syntax error on %s:%d' % (infile, lno), \ - 'before:', file=sys.stderr) - print(l, file=sys.stderr) - sys.exit(1) - # Add last entry - if section == STR: - add(msgid, msgstr, fuzzy) - - # Compute output - output = generate() - - try: - with open(outfile,"wb") as f: - f.write(output) - except IOError as msg: - print(msg, file=sys.stderr) - - - -def main(): - try: - opts, args = getopt.getopt(sys.argv[1:], 'hVo:', - ['help', 'version', 'output-file=']) - except getopt.error as msg: - usage(1, msg) - - outfile = None - # parse options - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-V', '--version'): - print("msgfmt.py", __version__) - sys.exit(0) - elif opt in ('-o', '--output-file'): - outfile = arg - # do it - if not args: - print('No input file given', file=sys.stderr) - print("Try `msgfmt --help' for more information.", file=sys.stderr) - return - - for filename in args: - make(filename, outfile) - - -if __name__ == '__main__': - main() diff --git a/v2ray_util/main.py b/v2ray_util/main.py deleted file mode 100644 index deb3bb8e..00000000 --- a/v2ray_util/main.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import sys -import subprocess - -from v2ray_util import run_type -from .util_core.v2ray import V2ray -from .util_core.utils import ColorStr, open_port, loop_input_choice_number -from .global_setting import stats_ctr, iptables_ctr, ban_bt, update_timer -from .config_modify import base, multiple, ss, stream, tls, cdn - -def help(): - exec_name = sys.argv[0] - from .util_core.config import Config - lang = Config().get_data('lang') - if lang == 'zh': - print(""" -{0} [-h|help] [options] - -h, help 查看帮助 - -v, version 查看版本号 - start 启动 {bin} - stop 停止 {bin} - restart 重启 {bin} - status 查看 {bin} 运行状态 - new 重建新的{bin} json配置文件 - update 更新 {bin} 到最新Release版本 - update [version] 更新 {bin} 到指定版本 - update.sh 更新 multi-v2ray 到最新版本 - add 新增端口组 - add [protocol] 新增一种协议的组, 端口随机, 如 {bin} add utp 为新增utp协议 - del 删除端口组 - info 查看配置 - port 修改端口 - tls 修改tls - tfo 修改tcpFastOpen - stream 修改传输协议 - cdn 走cdn - stats {bin}流量统计 - iptables iptables流量统计 - clean 清理日志 - log 查看日志 - rm 卸载{bin} - """.format(exec_name[exec_name.rfind("/") + 1:], bin=run_type)) - else: - print(""" -{0} [-h|help] [options] - -h, help get help - -v, version get version - start start {bin} - stop stop {bin} - restart restart {bin} - status check {bin} status - new create new json profile - update update {bin} to latest - update [version] update {bin} to special version - update.sh update multi-v2ray to latest - add add new group - add [protocol] create special protocol, random new port - del delete port group - info check {bin} profile - port modify port - tls modify tls - tfo modify tcpFastOpen - stream modify protocol - cdn cdn mode - stats {bin} traffic statistics - iptables iptables traffic statistics - clean clean {bin} log - log check {bin} log - rm uninstall {bin} - """.format(exec_name[exec_name.rfind("/") + 1:], bin=run_type)) - -def updateSh(): - if os.path.exists("/.dockerenv"): - subprocess.Popen("pip install -U v2ray_util", shell=True).wait() - else: - subprocess.Popen("curl -Ls https://multi.netlify.app/v2ray.sh -o temp.sh", shell=True).wait() - subprocess.Popen("bash temp.sh -k && rm -f temp.sh", shell=True).wait() - -def parse_arg(): - if len(sys.argv) == 1: - return - elif len(sys.argv) == 2: - if sys.argv[1] == "start": - V2ray.start() - elif sys.argv[1] == "stop": - V2ray.stop() - elif sys.argv[1] == "restart": - V2ray.restart() - elif sys.argv[1] in ("-h", "help"): - help() - elif sys.argv[1] in ("-v", "version"): - V2ray.version() - elif sys.argv[1] == "status": - V2ray.status() - elif sys.argv[1] == "info": - V2ray.info() - elif sys.argv[1] == "port": - base.port() - elif sys.argv[1] == "tls": - tls.modify() - elif sys.argv[1] == "tfo": - base.tfo() - elif sys.argv[1] == "stream": - stream.modify() - elif sys.argv[1] == "stats": - stats_ctr.manage() - elif sys.argv[1] == "iptables": - iptables_ctr.manage() - elif sys.argv[1] == "clean": - V2ray.cleanLog() - elif sys.argv[1] == "del": - multiple.del_port() - elif sys.argv[1] == "add": - multiple.new_port() - elif sys.argv[1] == "update": - V2ray.update() - elif sys.argv[1] == "update.sh": - updateSh() - elif sys.argv[1] == "new": - V2ray.new() - elif sys.argv[1] == "log": - V2ray.log() - elif sys.argv[1] == "cdn": - cdn.modify() - elif sys.argv[1] == "rm": - V2ray.remove() - else: - if sys.argv[1] == "add": - multiple.new_port(sys.argv[2]) - elif sys.argv[1] == "update": - V2ray.update(sys.argv[2]) - elif sys.argv[1] == "iptables": - iptables_ctr.manage(sys.argv[2]) - elif sys.argv[1] == "stats": - stats_ctr.manage(sys.argv[2]) - elif sys.argv[1] == "log": - if sys.argv[2] in ("error", "e"): - V2ray.log(True) - elif sys.argv[2] in ("access", "a"): - V2ray.log() - sys.exit(0) - -def service_manage(): - show_text = (_("start {}".format(run_type)), _("stop {}".format(run_type)), _("restart {}".format(run_type)), _("{} status".format(run_type)), _("{} log".format(run_type))) - print("") - for index, text in enumerate(show_text): - print("{}.{}".format(index + 1, text)) - choice = loop_input_choice_number(_("please select: "), len(show_text)) - if choice == 1: - V2ray.start() - elif choice == 2: - V2ray.stop() - elif choice == 3: - V2ray.restart() - elif choice == 4: - V2ray.status() - elif choice == 5: - V2ray.log() - -def user_manage(): - show_text = (_("add user"), _("add port"), _("del user"), _("del port")) - print("") - for index, text in enumerate(show_text): - print("{}.{}".format(index + 1, text)) - choice = loop_input_choice_number(_("please select: "), len(show_text)) - if not choice: - return - elif choice == 1: - multiple.new_user() - elif choice == 2: - multiple.new_port() - open_port() - elif choice == 3: - multiple.del_user() - elif choice == 4: - multiple.del_port() - -def profile_alter(): - show_text = (_("modify email"), _("modify UUID"), _("modify alterID"), _("modify port"), _("modify stream"), _("modify tls"), - _("modify tcpFastOpen"), _("modify dyn_port"), _("modify shadowsocks method"), _("modify shadowsocks password"), _("CDN mode(need domain)")) - print("") - for index, text in enumerate(show_text): - print("{}.{}".format(index + 1, text)) - choice = loop_input_choice_number(_("please select: "), len(show_text)) - if not choice: - return - elif choice == 1: - base.new_email() - elif choice == 2: - base.new_uuid() - elif choice == 3: - base.alterid() - elif choice == 4: - base.port() - elif choice == 5: - stream.modify() - elif choice == 6: - tls.modify() - elif choice == 7: - base.tfo() - elif choice == 8: - base.dyn_port() - elif choice == 9: - ss.modify('method') - elif choice == 10: - ss.modify('password') - elif choice == 11: - cdn.modify() - -def global_setting(): - show_text = (_("{} Traffic Statistics".format(run_type.capitalize())), _("Iptables Traffic Statistics"), _("Ban Bittorrent"), _("Schedule Update {}".format(run_type.capitalize())), _("Clean {} Log".format(run_type.capitalize())), _("Change Language")) - print("") - for index, text in enumerate(show_text): - print("{}.{}".format(index + 1, text)) - choice = loop_input_choice_number(_("please select: "), len(show_text)) - if choice == 1: - stats_ctr.manage() - elif choice == 2: - iptables_ctr.manage() - elif choice == 3: - ban_bt.manage() - elif choice == 4: - update_timer.manage() - elif choice == 5: - V2ray.cleanLog() - elif choice == 6: - from .util_core.config import Config - config = Config() - lang = config.get_data("lang") - config.set_data("lang", "zh" if lang == "en" else "en") - print(ColorStr.yellow(_("please run again to become effective!"))) - sys.exit(0) - -def menu(): - V2ray.check() - parse_arg() - while True: - print("") - print(ColorStr.cyan(_("Welcome to {} manager".format(run_type)))) - print("") - show_text = (_("1.{} Manage".format(run_type.capitalize())), _("2.Group Manage"), _("3.Modify Config"), _("4.Check Config"), _("5.Global Setting"), _("6.Update {}".format(run_type.capitalize()))) - for index, text in enumerate(show_text): - if index % 2 == 0: - print('{:<20}'.format(text), end="") - else: - print(text) - print("") - choice = loop_input_choice_number(_("please select: "), len(show_text)) - if choice == 1: - service_manage() - elif choice == 2: - user_manage() - elif choice == 3: - profile_alter() - elif choice == 4: - V2ray.info() - elif choice == 5: - global_setting() - elif choice == 6: - V2ray.update() - else: - break - -if __name__ == "__main__": - menu() \ No newline at end of file diff --git a/v2ray_util/util_core/__init__.py b/v2ray_util/util_core/__init__.py deleted file mode 100644 index 67745b83..00000000 --- a/v2ray_util/util_core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .trans import _ \ No newline at end of file diff --git a/v2ray_util/util_core/config.py b/v2ray_util/util_core/config.py deleted file mode 100644 index 54662160..00000000 --- a/v2ray_util/util_core/config.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import pkg_resources -import configparser -from v2ray_util import run_type - -CONF_FILE = '/etc/v2ray_util/util.cfg' -DATA_FILE = 'util.dat' if run_type == "v2ray" else 'xray.dat' - -class Config: - - def __init__(self): - self.config = configparser.ConfigParser() - self.config_path = CONF_FILE - self.data_path = pkg_resources.resource_filename('v2ray_util', DATA_FILE) - self.json_path = pkg_resources.resource_filename('v2ray_util', "json_template") - self.config.read(self.config_path) - - def get_path(self, key): - if key == 'config_path' and run_type == 'xray': - return '/etc/xray/config.json' - return self.config.get('path', key) - - def get_data(self, key): - return self.config.get('data', key) - - def set_data(self, key, value): - self.config.set('data', key, value) - self.config.write(open(self.config_path, "w")) \ No newline at end of file diff --git a/v2ray_util/util_core/group.py b/v2ray_util/util_core/group.py deleted file mode 100644 index 962684ab..00000000 --- a/v2ray_util/util_core/group.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import json -import base64 -from urllib.parse import quote -from .utils import ColorStr, x25519_key - -__author__ = 'Jrohy' - -class Dyport: - def __init__(self, status=False, aid=0): - self.status = status - self.aid = aid - - def __str__(self): - return "{}, alterId:{}".format(_("open"), self.aid) if self.status else _("close") - -class Quic: - def __init__(self, security="none", key="", header="none"): - self.security = security - self.key = key - self.header = header - - def __str__(self): - return "Security: {0}\nKey: {1}\nHeader: {2}".format(self.security, self.key, self.header) - -class User: - def __init__(self, user_number, password, user_info=None): - """ - user_info可能是email, 也可能user_name, 具体取决于group的protocol - """ - self.__password = password - self.user_info = user_info - self.user_number = user_number - - @property - def password(self): - return self.__password - -class SS(User): - def __init__(self, user_number, password, method, user_info): - super(SS, self).__init__(user_number, password, user_info) - self.method = method - - def __str__(self): - if self.user_info: - return "Email: {self.user_info}\nMethod: {self.method}\nPassword: {password}\n".format(self=self, password=self.password) - else: - return "Method: {self.method}\nPassword: {password}\n".format(self=self, password=self.password) - - def link(self, ip, port, tls): - ss_origin_url = "{0}:{1}@{2}:{3}".format(self.method, self.password, ip, port) - return ColorStr.green("ss://{}".format(bytes.decode(base64.b64encode(bytes(ss_origin_url, 'utf-8'))))) - - def stream(self): - return "shadowsocks" - -class Trojan(User): - def __init__(self, user_number, password, email): - super(Trojan, self).__init__(user_number, password, email) - - def __str__(self): - if self.user_info: - return "Email: {self.user_info}\nPassword: {password}\n".format(self=self, password=self.password) - else: - return "Password: {password}\n".format(password=self.password) - - def link(self, ip, port, tls): - return ColorStr.green("trojan://{0}@{1}:{2}#{1}:{2}".format(self.password, ip, port)) - - def stream(self): - return "trojan" - -class Mtproto(User): - def __str__(self): - if self.user_info: - return "Email: {}\nSecret: {}\n".format(self.user_info, self.password) - else: - return "Secret: {}\n".format(self.password) - - def link(self, ip, port, tls): - return ColorStr.green("tg://proxy?server={0}&port={1}&secret={2}".format(ip, port, self.password)) - - def stream(self): - return "mtproto" - -class Socks(User): - def __str__(self): - return "User: {0}\nPass: {1}\nUDP: true\n".format(self.user_info, self.password) - - def link(self, ip, port, tls): - if tls == "tls": - return ColorStr.red(_("HTTPS Socks5 don't support telegram share link")) - else: - return ColorStr.green("tg://socks?server={0}&port={1}&user={2}&pass={3}".format(ip, port, self.user_info, self.password)) - - def stream(self): - return "socks" - -class Vless(User): - def __init__(self, uuid, user_number, encryption=None, email=None, network=None, path=None, host=None, header=None, flow="", serviceName="", mode="", serverName="", privateKey="", shortId=""): - super(Vless, self).__init__(user_number, uuid, email) - self.encryption = encryption - self.path = path - self.host = host - self.header = header - self.network = network - self.flow = flow - self.serviceName = serviceName - self.mode = mode - self.serverName = serverName - self.privateKey = privateKey - self.shortId = shortId - - def __str__(self): - email = "" - if self.user_info: - email = "Email: {}".format(self.user_info) - result = ''' -{email} -ID: {password} -Encryption: {self.encryption} -Network: {network} -'''.format(self=self, password=self.password, email=email, network=self.stream()).strip() + "\n" - return result - - def stream(self): - if self.network == "ws": - return "WebSocket host: {0}, path: {1}".format(self.host, self.path) - elif self.network == "tcp": - return "tcp" - elif self.network == "grpc": - return "grpc serviceName: {}, mode: {}".format(self.serviceName, self.mode) - elif self.network == "kcp": - result = "kcp" - if self.header and self.header != 'none': - result = "{} {}".format(result, self.header) - if self.path != "": - result = "{} seed: {}".format(result, self.path) - return result - - def link(self, ip, port, tls): - result_link = "vless://{s.password}@{ip}:{port}?encryption={s.encryption}".format(s=self, ip=ip, port=port) - if tls == "tls": - result_link += "&security=tls" - if self.flow: - result_link += "&flow={}".format(self.flow) - elif tls == "reality": - result_link += "&security=reality&fp=chrome&flow={}&sni={}&pbk={}&sid={}".format(self.flow, self.serverName, x25519_key(self.privateKey)[1], self.shortId) - if self.network == "ws": - result_link += "&type=ws&host={0}&path={1}".format(self.host, quote(self.path)) - elif self.network == "tcp": - result_link += "&type=tcp" - elif self.network == "grpc": - result_link += "&type=grpc&serviceName={}&mode={}".format(self.serviceName, self.mode) - elif self.network == "kcp": - result_link += "&type=kcp&headerType={0}&seed={1}".format(self.header, self.path) - result_link += "#{}".format(quote("{}:{}".format(ip, port))) - return ColorStr.green(result_link) - -class Vmess(User): - def __init__(self, uuid, alter_id: int, network: str, user_number, *, path=None, host=None, header=None, email=None, quic=None): - super(Vmess, self).__init__(user_number, uuid, email) - self.alter_id = alter_id - self.network = network - self.path = path - self.host = host - self.header = header - self.quic = quic - if quic: - self.header = quic.header - self.host = quic.security - self.path = quic.key - - def stream(self): - network = "" - if self.network == "quic": - network = "Quic\n{}".format(self.quic) - elif self.network == "h2": - network = "HTTP/2 path: {}".format(self.path) - elif self.network == "ws": - network = "WebSocket host: {0}, path: {1}".format(self.host, self.path) - elif self.network == "grpc": - network = "grpc serviceName: {}, mode: {}".format(self.path, self.header) - elif self.network == "tcp": - if self.host: - network = "tcp host: {0}".format(self.host) - else: - network = "tcp" - elif self.network == "kcp": - network = "kcp" - if self.header and self.header != 'none': - network = "{} {}".format(network, self.header) - if self.path != "": - network = "{} seed: {}".format(network, self.path) - return network - - def __str__(self): - email = "" - if self.user_info: - email = "Email: {}".format(self.user_info) - result = ''' -{email} -UUID: {uuid} -Alter ID: {self.alter_id} -Network: {network} -'''.format(self=self, uuid=self.password, email=email, network=self.stream()).strip() + "\n" - return result - - def link(self, ip, port, tls): - json_dict = { - "v": "2", - "ps": "{}:{}".format(ip, port), - "add": ip, - "port": port, - "aid": self.alter_id, - "type": self.header, - "net": self.network, - "path": self.path, - "host": self.host, - "id": self.password, - "tls": tls - } - json_data = json.dumps(json_dict) - result_link = "vmess://{}".format(bytes.decode(base64.b64encode(bytes(json_data, 'utf-8')))) - return ColorStr.green(result_link) - -class Group: - def __init__(self, ip, port, *, end_port=None, tfo=None, tls="none", dyp=Dyport(), index=0, tag='A'): - self.ip = ip - self.port = port - self.end_port = end_port - self.tag = tag - self.node_list = [] - self.tfo = tfo - self.tls = tls - self.dyp = dyp - self.protocol = None - self.index = index - - def show_node(self, index): - tfo = "TcpFastOpen: {}".format(self.tfo) if self.tfo != None else "" - dyp = "DynamicPort: {}".format(self.dyp) if self.dyp.status else "" - port_way = "-{}".format(self.end_port) if self.end_port else "" - node = self.node_list[index] - if self.tls == "tls": - tls = _("open") - elif self.tls == "reality": - tls = "serverName: {0}, flow: {1}, publicKey: {2}, shortId: {3}".format(node.serverName, node.flow, x25519_key(node.privateKey)[1], node.shortId) - else: - tls = _("close") - result = ''' -{node.user_number}. -Group: {self.tag} -IP: {color_ip} -Port: {self.port}{port_way} -TLS: {tls} -{node}{tfo} -{dyp}'''.format(self=self, color_ip=ColorStr.fuchsia(self.ip), port_way=port_way, node=node,tfo=tfo, dyp=dyp,tls=tls) - link = node.link(self.ip, int(self.port), self.tls) - if link: - result += "{}\n\n".format(link) - return result - - # print一个实例打印的字符串 - def __str__(self): - tfo = "TcpFastOpen: {}".format(self.tfo) if self.tfo != None else "" - dyp = "DynamicPort: {}".format(self.dyp) if self.dyp.status else "" - port_way = "-{}".format(self.end_port) if self.end_port else "" - if self.tls == "tls": - tls = _("open") - if hasattr(self.node_list[0], "flow"): - tls += " flow: {}".format(self.node_list[0].flow) - elif self.tls == "reality": - tls = "reality serverName: {0}, flow: {1}, publicKey: {2}, shortId: {3}".format(self.node_list[0].serverName, self.node_list[0].flow, x25519_key(self.node_list[0].privateKey)[1], self.node_list[0].shortId) - else: - tls = _("close") - result = "" - for node in self.node_list: - temp = ''' -{node.user_number}. -Group: {self.tag} -IP: {color_ip} -Port: {self.port}{port_way} -TLS: {tls} -{node}{tfo} -{dyp} - '''.format(self=self, color_ip=ColorStr.fuchsia(self.ip), node=node,tfo=tfo,dyp=dyp,tls=tls, port_way=port_way) - link = node.link(self.ip, int(self.port), self.tls) - result = "{0}{1}\n\n".format(result, temp.strip()) - if link: - result += "{}\n\n".format(link) - return result - - # 直接调用实例和打印一个实例显示的字符串一样 - def __repr__ (self): - return self.__str__ \ No newline at end of file diff --git a/v2ray_util/util_core/selector.py b/v2ray_util/util_core/selector.py deleted file mode 100644 index 4d437302..00000000 --- a/v2ray_util/util_core/selector.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from .loader import Loader -from .utils import ColorStr, readchar - -class CommonSelector: - def __init__(self, collection, msg): - from collections.abc import Iterable - if isinstance(collection, Iterable): - self.collection = collection - else: - raise ValueError("{} object can't iterate".format(collection)) - self.msg = msg - - def select(self): - for index, element in enumerate(self.collection): - print("{0}.{1}".format(index + 1, element)) - - if len(self.collection) < 10: - choice = readchar(self.msg) - else: - choice = input(self.msg) - - if not choice: - print("use {}".format(self.collection[0])) - return self.collection[0] - - if not choice.isnumeric(): - raise RuntimeError(_('input error, please check is number')) - - choice = int(choice) - if choice < 1 or choice > len(self.collection): - raise RuntimeError(_('input error, input index out of range')) - else: - return self.collection[choice - 1] - -class Selector: - def __init__(self, action): - loader = Loader() - self.profile = loader.profile - self.group_list = loader.profile.group_list - self.action = action - -class ClientSelector(Selector): - def __init__(self, action): - super(ClientSelector, self).__init__(action) - self.list_size = self.group_list[-1].node_list[-1].user_number - if _("del") in action and self.list_size == 1: - print(ColorStr.red(_("last node can't delete!!!"))) - self.group = None - elif self.list_size > 1: - self.select_client() - else: - self.group = self.group_list[0] - self.client_index = 0 - - def select_client(self): - print(self.profile) - self.group = None - if self.list_size < 10: - choice = readchar("{} {}: ".format(_("please input number to"), self.action)) - else: - choice = input("{} {}: ".format(_("please input number to"), self.action)) - - if not choice: - return - if not choice.isnumeric(): - print(ColorStr.red(_('input error, please check is number'))) - return - - choice = int(choice) - if choice < 1 or choice > self.list_size: - print(ColorStr.red(_('input out of range!!'))) - else: - find = False - for group in self.group_list: - if find: - break - for index, node in enumerate(group.node_list): - if node.user_number == choice: - self.client_index = index - self.group = group - find = True - break - -class GroupSelector(Selector): - def __init__(self, action): - super(GroupSelector, self).__init__(action) - if _("del") in action and len(self.group_list) == 1: - print(ColorStr.red(_("last group can't delete!!!"))) - self.group = None - elif len(self.group_list) > 1: - self.select_group() - else: - self.group = self.group_list[0] - - def select_group(self): - print(self.profile) - if len(self.group_list[-1].tag) == 1: - choice = readchar("{} {}: ".format(_("please input group to"), self.action)) - else: - choice = input("{} {}: ".format(_("please input group to"), self.action)) - group_list = [x for x in self.group_list if x.tag == str.upper(choice)] - if len(group_list) == 0: - print(ColorStr.red('{0} {1} {2}'.format(_("input error, please check group"), choice, _("exist")))) - self.group = None - else: - self.group = group_list[0] \ No newline at end of file diff --git a/v2ray_util/util_core/trans.py b/v2ray_util/util_core/trans.py deleted file mode 100644 index f5b5ec3a..00000000 --- a/v2ray_util/util_core/trans.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import gettext -import pkg_resources - -lang = 'en' -if os.path.exists('/etc/v2ray_util/util.cfg'): - from .config import Config - lang = Config().get_data('lang') -if lang == 'zh': - trans = gettext.translation('lang', pkg_resources.resource_filename('v2ray_util', 'locale_i18n'), languages=['zh_CH']) -else: - trans = gettext.translation('lang', pkg_resources.resource_filename('v2ray_util', 'locale_i18n'), languages=['en_US']) -trans.install() -_ = trans.gettext \ No newline at end of file diff --git a/v2ray_util/util_core/util.cfg b/v2ray_util/util_core/util.cfg deleted file mode 100644 index 1ca672a7..00000000 --- a/v2ray_util/util_core/util.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[path] -config_path=/etc/v2ray/config.json -write_client_path=/root/config.json - -[data] -lang=en \ No newline at end of file diff --git a/v2ray_util/util_core/utils.py b/v2ray_util/util_core/utils.py deleted file mode 100644 index 06bbd5d0..00000000 --- a/v2ray_util/util_core/utils.py +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import re -import sys -import tty -import socket -import string -import random -import termios -import pkg_resources -import urllib.request -from enum import Enum, unique - -class ColorStr: - RED = '\033[31m' # 红色 - GREEN = '\033[32m' # 绿色 - YELLOW = '\033[33m' # 黄色 - BLUE = '\033[34m' # 蓝色 - FUCHSIA = '\033[35m' # 紫红色 - CYAN = '\033[36m' # 青蓝色 - WHITE = '\033[37m' # 白色 - #: no color - RESET = '\033[0m' # 终端默认颜色 - - @classmethod - def red(cls, s): - return cls.RED + s + cls.RESET - - @classmethod - def green(cls, s): - return cls.GREEN + s + cls.RESET - - @classmethod - def yellow(cls, s): - return cls.YELLOW + s + cls.RESET - - @classmethod - def blue(cls, s): - return cls.BLUE + s + cls.RESET - - @classmethod - def cyan(cls, s): - return cls.CYAN + s + cls.RESET - - @classmethod - def fuchsia(cls, s): - return cls.FUCHSIA + s + cls.RESET - - @classmethod - def white(cls, s): - return cls.WHITE + s + cls.RESET - -@unique -class StreamType(Enum): - TCP = 'tcp' - TCP_HOST = 'tcp_host' - SOCKS = 'socks' - SS = 'ss' - MTPROTO = 'mtproto' - H2 = 'h2' - WS = 'ws' - GRPC = 'grpc' - QUIC = 'quic' - KCP = 'kcp' - KCP_UTP = 'utp' - KCP_SRTP = 'srtp' - KCP_DTLS = 'dtls' - KCP_WECHAT = 'wechat' - KCP_WG = 'wireguard' - VLESS_KCP = 'vless_kcp' - VLESS_UTP = 'vless_utp' - VLESS_SRTP = 'vless_srtp' - VLESS_DTLS = 'vless_dtls' - VLESS_WECHAT = 'vless_wechat' - VLESS_WG = 'vless_wireguard' - VLESS_TCP = 'vless_tcp' - VLESS_TLS = 'vless_tls' - VLESS_WS = 'vless_ws' - VLESS_GRPC = 'vless_grpc' - VLESS_REALITY = 'vless_reality' - TROJAN = 'trojan' - -def header_type_list(): - return ("none", "srtp", "utp", "wechat-video", "dtls", "wireguard") - -def ss_method(): - return ("aes-256-gcm", "aes-128-gcm", "chacha20-poly1305") - -def xtls_flow(): - return ("xtls-rprx-vision", "none") - -def get_ip(): - """ - 获取本地ip - """ - my_ip = "" - try: - my_ip = urllib.request.urlopen('http://api.ipify.org').read() - except Exception: - my_ip = urllib.request.urlopen('http://icanhazip.com').read() - return bytes.decode(my_ip).strip() - -def port_is_use(port): - """ - 判断端口是否占用 - """ - tcp_use, udp_use = False, False - s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) - u = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) - s.settimeout(3) - tcp_use = s.connect_ex(('127.0.0.1', int(port))) == 0 - try: - u.bind(('127.0.0.1', int(port))) - except: - udp_use = True - finally: - u.close() - return tcp_use or udp_use - -def random_port(start_port, end_port): - while True: - random_port = random.randint(start_port, end_port) - if not port_is_use(random_port): - return random_port - -def is_email(email): - """ - 判断是否是邮箱格式 - """ - str = r'^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+){0,4}$' - return re.match(str, email) - -def is_ipv4(ip): - try: - socket.inet_pton(socket.AF_INET, ip) - except AttributeError: # no inet_pton here, sorry - try: - socket.inet_aton(ip) - except socket.error: - return False - return ip.count('.') == 3 - except socket.error: # not a valid ip - return False - return True - -def is_ipv6(ip): - try: - socket.inet_pton(socket.AF_INET6, ip) - except socket.error: # not a valid ip - return False - return True - -def check_ip(ip): - return is_ipv4(ip) or is_ipv6(ip) - -def bytes_2_human_readable(number_of_bytes, precision=1): - """ - 流量bytes转换为kb, mb, gb等单位 - """ - if number_of_bytes < 0: - raise ValueError("!!! number_of_bytes can't be smaller than 0 !!!") - - step_to_greater_unit = 1024. - - number_of_bytes = float(number_of_bytes) - unit = 'bytes' - - if (number_of_bytes / step_to_greater_unit) >= 1: - number_of_bytes /= step_to_greater_unit - unit = 'KB' - - if (number_of_bytes / step_to_greater_unit) >= 1: - number_of_bytes /= step_to_greater_unit - unit = 'MB' - - if (number_of_bytes / step_to_greater_unit) >= 1: - number_of_bytes /= step_to_greater_unit - unit = 'GB' - - if (number_of_bytes / step_to_greater_unit) >= 1: - number_of_bytes /= step_to_greater_unit - unit = 'TB' - - number_of_bytes = round(number_of_bytes, precision) - - return str(number_of_bytes) + ' ' + unit - -def gen_cert(domain, cert_type, email=""): - local_ip = get_ip() - service_name = ["nginx", "httpd", "apache2"] - start_cmd = "systemctl start {} >/dev/null 2>&1" - stop_cmd = "systemctl stop {} >/dev/null 2>&1" - - if not os.path.exists("/root/.acme.sh/acme.sh"): - if ":" in local_ip: - if not os.path.exists("/root/.acme.sh/"): - os.makedirs("/root/.acme.sh") - os.system("curl https://acme-install.netlify.app/acme.sh -o /root/.acme.sh/acme.sh") - else: - os.system("curl https://get.acme.sh | sh") - - open_port(80) - if int(os.popen("/root/.acme.sh/acme.sh -v|tr -cd '[0-9]'").read()) < 300: - os.system("/root/.acme.sh/acme.sh --upgrade") - if cert_type in ("buypass", "zerossl"): - os.system("bash /root/.acme.sh/acme.sh --server {} --register-account -m {}".format(cert_type, email)) - get_ssl_cmd = "bash /root/.acme.sh/acme.sh --issue -d " + domain + " --debug --standalone --keylength ec-256 --server " + cert_type - if len(os.popen("/root/.acme.sh/acme.sh --list|grep {}|grep -i {}".format(domain, cert_type)).readlines()) == 0: - get_ssl_cmd += " --force" - if ":" in local_ip: - get_ssl_cmd += " --listen-v6" - if cert_type == "buypass": - get_ssl_cmd += " --days 170" - - if not os.path.exists("/.dockerenv"): - for name in service_name: - os.system(stop_cmd.format(name)) - os.system(get_ssl_cmd) - if not os.path.exists("/.dockerenv"): - for name in service_name: - os.system(start_cmd.format(name)) - -def calcul_iptables_traffic(port, ipv6=False): - network = "1" if ipv6 else "" - traffic_result = os.popen("bash {0} {1} {2}".format(pkg_resources.resource_filename("v2ray_util", "global_setting/calcul_traffic.sh"), str(port), network)).readlines() - if traffic_result: - traffic_list = traffic_result[0].split() - upload_traffic = bytes_2_human_readable(int(traffic_list[0]), 2) - download_traffic = bytes_2_human_readable(int(traffic_list[1]), 2) - total_traffic = bytes_2_human_readable(int(traffic_list[2]), 2) - return "{0}: upload:{1} download:{2} total:{3}".format(ColorStr.green(str(port)), - ColorStr.cyan(upload_traffic), ColorStr.cyan(download_traffic), ColorStr.cyan(total_traffic)) - -def clean_iptables(port): - import platform - from .loader import Loader - - iptable_way = "iptables" if Loader().profile.network == "ipv4" else "ip6tables" - - clean_cmd = "{} -D {} {}" - check_cmd = "%s -nvL %s --line-number 2>/dev/null|grep -w \"%s\"|awk '{print $1}'|sort -r" - firewall_clean_cmd = "firewall-cmd --zone=public --remove-port={}/tcp --remove-port={}/udp --permanent >/dev/null 2>&1" - - if "centos-8" in platform.platform(): - os.system("{}-save -c > /etc/sysconfig/iptables 2>/dev/null".format(iptable_way)) - os.system(firewall_clean_cmd.format(str(port), str(port))) - os.system("firewall-cmd --reload >/dev/null 2>&1") - os.system("{}-restore -c < /etc/sysconfig/iptables".format(iptable_way)) - input_result = os.popen(check_cmd % (iptable_way, "INPUT", str(port))).readlines() - for line in input_result: - os.system(clean_cmd.format(iptable_way, "INPUT", str(line))) - - output_result = os.popen(check_cmd % (iptable_way, "OUTPUT", str(port))).readlines() - for line in output_result: - os.system(clean_cmd.format(iptable_way, "OUTPUT", str(line))) - -def x25519_key(private_key=None): - gen_cmd="/usr/bin/xray/xray x25519" - if private_key: - gen_cmd = "{} -i '{}'".format(gen_cmd, private_key) - gen_result = os.popen(gen_cmd + "|awk -F ':' '{print $2}'|sed 's/ //g'").readlines() - return list(map(lambda x: x.strip(), gen_result)) - -def all_port(): - from .loader import Loader - profile = Loader().profile - group_list = profile.group_list - return set([group.port for group in group_list]) - -def iptables_open(iptable_way, port): - check_cmd = "{} -nvL --line-number 2>/dev/null|grep -w \"{}\"" - input_cmd = "{} -I INPUT -p {} --dport {} -j ACCEPT" - output_cmd = "{} -I OUTPUT -p {} --sport {}" - if len(os.popen(check_cmd.format(iptable_way, port)).readlines()) > 0: - return - os.system(input_cmd.format(iptable_way, "tcp", port)) - os.system(input_cmd.format(iptable_way, "udp", port)) - os.system(output_cmd.format(iptable_way, "tcp", port)) - os.system(output_cmd.format(iptable_way, "udp", port)) - -def open_port(openport=-1): - import platform - from .loader import Loader - - iptable_way = "iptables" if Loader().profile.network == "ipv4" else "ip6tables" - - is_centos8 = True if "centos-8" in platform.platform() else False - firewall_open_cmd = "firewall-cmd --zone=public --add-port={}/tcp --add-port={}/udp --permanent >/dev/null 2>&1" - - port_set = all_port() - - if openport != -1: - port_str = str(openport) - if is_centos8: - os.system("{}-save -c > /etc/sysconfig/iptables 2>/dev/null".format(iptable_way)) - os.system(firewall_open_cmd.format(port_str, port_str)) - os.system("firewall-cmd --reload >/dev/null 2>&1") - os.system("{}-restore -c < /etc/sysconfig/iptables".format(iptable_way)) - else: - iptables_open(iptable_way, port_str) - else: - if is_centos8: - os.system("{}-save -c > /etc/sysconfig/iptables 2>/dev/null".format(iptable_way)) - for port in port_set: - os.system(firewall_open_cmd.format(str(port), str(port))) - os.system("firewall-cmd --reload >/dev/null 2>&1") - os.system("{}-restore -c < /etc/sysconfig/iptables".format(iptable_way)) - for port in port_set: - iptables_open(iptable_way, str(port)) - os.system("{}-save -c > /root/.iptables 2>/dev/null".format(iptable_way)) - -def random_email(): - domain = ['163', 'qq', 'sina', '126', 'gmail', 'outlook', 'icloud'] - core_email = "@{}.com".format(random.choice(domain)) - return ''.join(random.sample(string.ascii_letters + string.digits, 8)) + core_email - -def loop_input_choice_number(input_tip, length): - """ - 循环输入选择的序号,直到符合规定为止 - """ - while True: - print("") - if length >= 10: - choice = input(input_tip) - else: - choice = readchar(input_tip) - if not choice: - return - if choice.isnumeric(): - choice = int(choice) - else: - print(ColorStr.red(_("input error, please input again"))) - continue - if (choice <= length and choice > 0): - return choice - else: - print(ColorStr.red(_("input error, please input again"))) - -def readchar(prompt=""): - if prompt: - sys.stdout.write(prompt) - sys.stdout.flush() - - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - ch = sys.stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - - print(ch) - return ch.strip() \ No newline at end of file diff --git a/v2ray_util/util_core/v2ray.py b/v2ray_util/util_core/v2ray.py deleted file mode 100644 index 6b755b39..00000000 --- a/v2ray_util/util_core/v2ray.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -import os -import sys -import uuid -import time -import subprocess -import pkg_resources -from functools import wraps -from v2ray_util import run_type -from .utils import ColorStr, open_port, get_ip, is_ipv6, random_port - -def restart(port_open=False): - """ - 运行函数/方法后重启v2ray的装饰器 - """ - def decorate(func): - @wraps(func) - def wrapper(*args, **kw): - result = func(*args, **kw) - if port_open: - open_port() - if result: - V2ray.restart() - return wrapper - return decorate - -class V2ray: - - @staticmethod - def docker_run(command, keyword): - subprocess.run(command, shell=True) - print("{}ing {}...".format(keyword, run_type)) - time.sleep(1) - if V2ray.docker_status() or keyword == "stop": - print(ColorStr.green("{} {} success !".format(run_type, keyword))) - else: - print(ColorStr.red("{} {} fail !".format(run_type, keyword))) - - @staticmethod - def run(command, keyword): - try: - subprocess.check_output(command, shell=True) - print("{}ing {}...".format(keyword, run_type)) - time.sleep(2) - if subprocess.check_output("systemctl is-active {}|grep active".format(run_type), shell=True) or keyword == "stop": - print(ColorStr.green("{} {} success !".format(run_type, keyword))) - else: - raise subprocess.CalledProcessError - except subprocess.CalledProcessError: - print(ColorStr.red("{} {} fail !".format(run_type, keyword))) - - @staticmethod - def docker_status(): - is_running = True - failed = bytes.decode(subprocess.run('cat /.run.log|grep failed', shell=True, stdout=subprocess.PIPE).stdout) - running = bytes.decode(subprocess.run('ps aux|grep /etc/{}/config.json'.format(run_type), shell=True, stdout=subprocess.PIPE).stdout) - if failed or "/usr/bin/{bin}/{bin}".format(bin=run_type) not in running: - is_running = False - return is_running - - @staticmethod - def status(): - if os.path.exists("/.dockerenv"): - if V2ray.docker_status(): - print(ColorStr.green("{} running..".format(run_type))) - else: - print(bytes.decode(subprocess.run('cat /.run.log', shell=True, stdout=subprocess.PIPE).stdout)) - print(ColorStr.yellow("{} stoped..".format(run_type))) - else: - subprocess.call("systemctl status {}".format(run_type), shell=True) - - @staticmethod - def version(): - v2ray_version = bytes.decode(subprocess.check_output("/usr/bin/{bin}/{bin}".format(bin=run_type) + " -version 2>/dev/null | head -n 1 | awk '{print $2}'", shell=True)) - if not v2ray_version: - v2ray_version = bytes.decode(subprocess.check_output("/usr/bin/{bin}/{bin}".format(bin=run_type) + " version 2>/dev/null | head -n 1 | awk '{print $2}'", shell=True)) - import v2ray_util - print("{}: {}".format(run_type, ColorStr.green(v2ray_version))) - print("v2ray_util: {}".format(ColorStr.green(v2ray_util.__version__))) - - @staticmethod - def info(): - from .loader import Loader - print(Loader().profile) - - @staticmethod - def update(version=None): - if is_ipv6(get_ip()): - print(ColorStr.yellow(_("ipv6 network not support update {soft} online, please manual donwload {soft} to update!".format(soft=run_type)))) - if run_type == "xray": - print(ColorStr.fuchsia(_("download Xray-linux-xx.zip and run 'bash <(curl -L -s https://multi.netlify.app/go.sh) -l Xray-linux-xx.zip -x' to update"))) - else: - print(ColorStr.fuchsia(_("download v2ray-linux-xx.zip and run 'bash <(curl -L -s https://multi.netlify.app/go.sh) -l v2ray-linux-xx.zip' to update"))) - sys.exit(0) - if os.path.exists("/.dockerenv"): - V2ray.stop() - subprocess.Popen("curl -Ls https://multi.netlify.app/go.sh -o temp.sh", shell=True).wait() - subprocess.Popen("bash temp.sh {} {} && rm -f temp.sh".format("-x" if run_type == "xray" else "", "--version {}".format(version) if version else ""), shell=True).wait() - if os.path.exists("/.dockerenv"): - V2ray.start() - - @staticmethod - def cleanLog(): - subprocess.call("cat /dev/null > /var/log/{}/access.log".format(run_type), shell=True) - subprocess.call("cat /dev/null > /var/log/{}/error.log".format(run_type), shell=True) - print(ColorStr.green(_("clean {} log success!".format(run_type)))) - print("") - - @staticmethod - def log(error_log=False): - f = subprocess.Popen(['tail','-f', '-n', '100', '/var/log/{}/{}.log'.format(run_type, "error" if error_log else "access")], - stdout=subprocess.PIPE,stderr=subprocess.PIPE) - try: - while True: - print(bytes.decode(f.stdout.readline().strip())) - except BaseException: - print() - - @classmethod - def restart(cls): - if os.path.exists("/.dockerenv"): - V2ray.stop() - V2ray.start() - else: - cls.run("systemctl restart {}".format(run_type), "restart") - - @classmethod - def start(cls): - if os.path.exists("/.dockerenv"): - try: - subprocess.check_output("/usr/bin/{bin}/{bin}".format(bin=run_type) + " -version 2>/dev/null", shell=True) - cls.docker_run("/usr/bin/{bin}/{bin} -config /etc/{bin}/config.json > /.run.log &".format(bin=run_type), "start") - except: - cls.docker_run("/usr/bin/{bin}/{bin} run -c /etc/{bin}/config.json > /.run.log &".format(bin=run_type), "start") - else: - cls.run("systemctl start {}".format(run_type), "start") - - @classmethod - def stop(cls): - if os.path.exists("/.dockerenv"): - cls.docker_run("ps aux|grep /usr/bin/{bin}/{bin}".format(bin=run_type) + "|awk '{print $1}'|xargs -r kill -9 2>/dev/null", "stop") - else: - cls.run("systemctl stop {}".format(run_type), "stop") - - @classmethod - def check(cls): - if not os.path.exists("/etc/v2ray_util/util.cfg"): - subprocess.call("mkdir -p /etc/v2ray_util && cp -f {} /etc/v2ray_util/".format(pkg_resources.resource_filename(__name__, 'util.cfg')), shell=True) - if not os.path.exists("/usr/bin/{bin}/{bin}".format(bin=run_type)): - print(ColorStr.yellow(_("check {soft} no install, auto install {soft}..".format(soft=run_type)))) - cls.update() - cls.new() - - @classmethod - def remove(cls): - if os.path.exists("/.dockerenv"): - print(ColorStr.yellow("docker run don't support remove {}!".format(run_type))) - return - cls.stop() - subprocess.call("systemctl disable {}.service".format(run_type), shell=True) - subprocess.call("rm -rf /usr/bin/{bin} /etc/systemd/system/{bin}.service".format(bin=run_type), shell=True) - print(ColorStr.green("Removed {} successfully.".format(run_type))) - print(ColorStr.blue("If necessary, please remove configuration file and log file manually.")) - - @classmethod - def new(cls): - subprocess.call("rm -rf /etc/{soft}/config.json && cp {package_path}/server.json /etc/{soft}/config.json".format(soft=run_type, package_path=pkg_resources.resource_filename('v2ray_util', "json_template")), shell=True) - new_uuid = uuid.uuid4() - print("new UUID: {}".format(ColorStr.green(str(new_uuid)))) - new_port = random_port(1000, 65535) - print("new port: {}".format(ColorStr.green(str(new_port)))) - subprocess.call("sed -i \"s/cc4f8d5b-967b-4557-a4b6-bde92965bc27/{uuid}/g\" /etc/{soft}/config.json && sed -i \"s/999999999/{port}/g\" /etc/{soft}/config.json".format(uuid=new_uuid, port=new_port, soft=run_type), shell=True) - if run_type == "xray": - subprocess.call("sed -i \"s/v2ray/xray/g\" /etc/xray/config.json", shell=True) - from ..config_modify import stream - stream.StreamModifier().random_kcp() - open_port() - cls.restart() \ No newline at end of file diff --git a/web.py b/web.py new file mode 100644 index 00000000..76feec1b --- /dev/null +++ b/web.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from flask import Flask, request, jsonify, render_template +from flask_httpauth import HTTPBasicAuth + +from app import func_router +from config import Config + +config = Config() +app = Flask(__name__, static_url_path='/static') +app.register_blueprint(func_router) + +auth = HTTPBasicAuth() + +@auth.get_password +def get_pw(username): + return config.get_web('pass') + +@app.route('/') +@auth.login_required +def index(): + return render_template(config.get_web('index_page')) + +if __name__ == '__main__': + app.run(debug=True, port=config.get_web('port')) \ No newline at end of file diff --git a/v2ray_util/util_core/writer.py b/writer.py similarity index 53% rename from v2ray_util/util_core/writer.py rename to writer.py index 3bb2c738..219610e2 100644 --- a/v2ray_util/util_core/writer.py +++ b/writer.py @@ -1,38 +1,39 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import os import json import random import string import uuid -from .config import Config -from .utils import StreamType, random_port, x25519_key -from .group import Mtproto, Vmess, Socks, Vless, Trojan +from config import Config +from utils import port_is_use, StreamType +from loader import Loader +from group import Mtproto, Vmess, Socks def clean_mtproto_tag(config, group_index): ''' 清理mtproto 协议减少时无用的tag ''' - if "tag" in config["inbounds"][group_index]: - tag = config["inbounds"][group_index]["tag"] - - rules = config["routing"]["rules"] - - for index, rule in enumerate(rules): - if rule["outboundTag"] != "tg-out": - continue - if len(rule["inboundTag"]) == 1: - del rules[index] - for out_index, oubound_mtproto in enumerate(config["outbounds"]): - if oubound_mtproto["protocol"] == "mtproto": - del config["outbounds"][out_index] - break - else: - for tag_index, rule_tag in enumerate(rule["inboundTag"]): - if rule_tag == tag: - del rule["inboundTag"][tag_index] - break - break + rules = config["routing"]["rules"] + + tag = config["inbounds"][group_index]["tag"] + + for index, rule in enumerate(rules): + if rule["outboundTag"] != "tg-out": + continue + if len(rule["inboundTag"]) == 1: + del rules[index] + for out_index, oubound_mtproto in enumerate(config["outbounds"]): + if oubound_mtproto["protocol"] == "mtproto": + del config["outbounds"][out_index] + break + else: + for tag_index, rule_tag in enumerate(rule["inboundTag"]): + if rule_tag == tag: + del rule["inboundTag"][tag_index] + break + break class Writer: def __init__(self, group_tag = 'A', group_index=0): @@ -40,7 +41,7 @@ def __init__(self, group_tag = 'A', group_index=0): self.group_index = group_index self.group_tag = group_tag self.path = self.multi_config.get_path('config_path') - self.template_path = self.multi_config.json_path + self.template_path = self.multi_config.get_path('template_path') self.config = self.load(self.path) self.part_json = self.config["inbounds"][self.group_index] @@ -79,8 +80,6 @@ def to_mtproto(self, template_json): mtproto_in["tag"] = self.group_tag if "allocate" in self.part_json: mtproto_in["allocate"] = self.part_json["allocate"] - if "sniffing" in self.part_json: - mtproto_in["sniffing"] = self.part_json["sniffing"] salt = "abcdef" + string.digits secret = ''.join([random.choice(salt) for _ in range(32)]) mtproto_in["settings"]["users"][0]["secret"] = secret @@ -109,24 +108,8 @@ def to_mtproto(self, template_json): routing_bind["inboundTag"][0] = self.group_tag rules.append(routing_bind) - def to_kcp(self, header_type): - self.part_json["streamSettings"] = self.load_template('kcp.json') - type_str = "none" - if "utp" in header_type: - type_str = "utp" - elif "srtp" in header_type: - type_str = "srtp" - elif "dtls" in header_type: - type_str = "dtls" - elif "wechat" in header_type: - type_str = "wechat-video" - elif "wireguard" in header_type: - type_str = "wireguard" - self.part_json["streamSettings"]["kcpSettings"]["header"]["type"] = type_str - def write(self, **kw): - security_backup, tls_settings_backup, origin_protocol, domain = "", "", None, "" - no_tls_group = (StreamType.MTPROTO, StreamType.SS) + security_backup, tls_settings_backup, origin_protocol = "", "", None if self.part_json['protocol'] == 'shadowsocks': origin_protocol = StreamType.SS @@ -134,43 +117,40 @@ def write(self, **kw): origin_protocol = StreamType.MTPROTO elif self.part_json['protocol'] == 'socks': origin_protocol = StreamType.SOCKS - elif self.part_json['protocol'] == 'vless': - if self.part_json["streamSettings"]["network"] == "grpc": - origin_protocol = StreamType.VLESS_GRPC - elif self.part_json["streamSettings"]["security"] == "reality": - origin_protocol = StreamType.VLESS_REALITY - elif self.part_json["streamSettings"]["security"] == "tls": - origin_protocol = StreamType.VLESS_TLS - else: - origin_protocol = StreamType.VLESS_TCP - elif self.part_json['protocol'] == 'trojan': - origin_protocol = StreamType.TROJAN - if origin_protocol not in no_tls_group: + if origin_protocol != StreamType.MTPROTO and origin_protocol != StreamType.SS: security_backup = self.part_json["streamSettings"]["security"] - if origin_protocol == StreamType.VLESS_REALITY: - tls_settings_backup = self.part_json["streamSettings"]["realitySettings"] - else: - tls_settings_backup = self.part_json["streamSettings"]["tlsSettings"] - if "domain" in self.part_json: - domain = self.part_json["domain"] + tls_settings_backup = self.part_json["streamSettings"]["tlsSettings"] #mtproto换成其他协议时, 减少mtproto int和out的路由绑定 if origin_protocol == StreamType.MTPROTO and origin_protocol != self.stream_type: clean_mtproto_tag(self.config, self.group_index) - server = self.load_template('server.json') - server["inbounds"][0]["port"] = self.part_json["port"] - server["inbounds"][0]["settings"]["clients"][0]["id"] = str(uuid.uuid4()) - if "allocate" in self.part_json: - server["inbounds"][0]["allocate"] = self.part_json["allocate"] - if "sniffing" in self.part_json: - server["inbounds"][0]["sniffing"] = self.part_json["sniffing"] - self.part_json = server["inbounds"][0] - self.config["inbounds"][self.group_index] = self.part_json + #原来是socks/mtproto/shadowsocks协议 则先切换为标准的inbound + if origin_protocol == StreamType.MTPROTO or origin_protocol == StreamType.SOCKS or origin_protocol == StreamType.SS: + vmess = self.load_template('server.json') + vmess["inbounds"][0]["port"] = self.part_json["port"] + if "allocate" in self.part_json: + vmess["inbounds"][0]["allocate"] = self.part_json["allocate"] + self.part_json = vmess["inbounds"][0] + + if self.stream_type == StreamType.KCP: + self.part_json["streamSettings"] = self.load_template('kcp.json') + + elif self.stream_type == StreamType.KCP_UTP: + self.part_json["streamSettings"] = self.load_template('kcp_utp.json') + + elif self.stream_type == StreamType.KCP_SRTP: + self.part_json["streamSettings"] = self.load_template('kcp_srtp.json') - if self.stream_type.name.startswith('KCP'): - self.to_kcp(self.stream_type.value) + elif self.stream_type == StreamType.KCP_WECHAT: + self.part_json["streamSettings"] = self.load_template('kcp_wechat.json') + + elif self.stream_type == StreamType.KCP_DTLS: + self.part_json["streamSettings"] = self.load_template('kcp_dtls.json') + + elif self.stream_type == StreamType.KCP_WG: + self.part_json["streamSettings"] = self.load_template('kcp_wireguard.json') elif self.stream_type == StreamType.TCP: self.part_json["streamSettings"] = self.load_template('tcp.json') @@ -184,13 +164,6 @@ def write(self, **kw): mtproto = self.load_template('mtproto.json') self.to_mtproto(mtproto) - elif self.stream_type == StreamType.QUIC: - quic = self.load_template('quic.json') - quic["quicSettings"]["security"] = kw["security"] - quic["quicSettings"]["key"] = kw["key"] - quic["quicSettings"]["header"]["type"] = kw["header"] - self.part_json["streamSettings"] = quic - elif self.stream_type == StreamType.SOCKS: socks = self.load_template('socks.json') tcp = self.load_template('tcp.json') @@ -205,8 +178,6 @@ def write(self, **kw): ss["port"] = self.part_json["port"] if "allocate" in self.part_json: ss["allocate"] = self.part_json["allocate"] - if "sniffing" in self.part_json: - ss["sniffing"] = self.part_json["sniffing"] ss["settings"]["method"] = kw["method"] ss["settings"]["password"] = kw["password"] self.part_json = ss @@ -215,103 +186,9 @@ def write(self, **kw): ws = self.load_template('ws.json') salt = '/' + ''.join(random.sample(string.ascii_letters + string.digits, 8)) + '/' ws["wsSettings"]["path"] = salt - if "host" in kw: - ws["wsSettings"]["headers"]["Host"] = kw['host'] + ws["wsSettings"]["headers"]["Host"] = kw['host'] self.part_json["streamSettings"] = ws - elif self.stream_type == StreamType.GRPC: - alpn = ["h2"] - self.part_json["streamSettings"] = self.load_template('tcp.json') - self.part_json["streamSettings"]["network"] = "grpc" - self.part_json["streamSettings"]["grpcSettings"]["serviceName"] = ''.join(random.sample(string.ascii_letters + string.digits, 8)) - if "mode" in kw and kw["mode"] == "multi": - self.part_json["streamSettings"]["grpcSettings"]["multiMode"] = True - if "fallbacks" in self.part_json["settings"]: - del self.part_json["settings"]["fallbacks"] - self.save() - if not "certificates" in tls_settings_backup: - from ..config_modify.tls import TLSModifier - tm = TLSModifier(self.group_tag, self.group_index, alpn=alpn) - tm.turn_on(False) - return - tls_settings_backup["alpn"] = alpn - - elif "vless" in self.stream_type.value: - alpn = ["http/1.1"] - vless = self.load_template('vless.json') - vless["clients"][0]["id"] = str(uuid.uuid4()) - if self.stream_type in (StreamType.VLESS_TLS, StreamType.VLESS_REALITY): - vless["clients"][0]["flow"] = kw["flow"] - if self.stream_type in (StreamType.VLESS_WS, StreamType.VLESS_REALITY): - del vless["fallbacks"] - self.part_json['protocol'] = "vless" - self.part_json["settings"] = vless - if self.stream_type == StreamType.VLESS_WS: - ws = self.load_template('ws.json') - ws["wsSettings"]["path"] = '/' + ''.join(random.sample(string.ascii_letters + string.digits, 8)) + '/' - if "host" in kw: - ws["wsSettings"]["headers"]["Host"] = kw['host'] - self.part_json["streamSettings"] = ws - elif self.stream_type in (StreamType.VLESS_KCP, StreamType.VLESS_UTP, StreamType.VLESS_SRTP, StreamType.VLESS_DTLS, StreamType.VLESS_WECHAT, StreamType.VLESS_WG): - self.to_kcp(self.stream_type.value) - self.part_json["streamSettings"]["kcpSettings"]["seed"] = ''.join(random.sample(string.ascii_letters + string.digits, 8)) - else: - self.part_json["streamSettings"] = self.load_template('tcp.json') - if self.stream_type == StreamType.VLESS_GRPC: - alpn = ["h2"] - self.part_json["streamSettings"]["network"] = "grpc" - self.part_json["streamSettings"]["grpcSettings"]["serviceName"] = ''.join(random.sample(string.ascii_letters + string.digits, 8)) - if "mode" in kw and kw["mode"] == "multi": - self.part_json["streamSettings"]["grpcSettings"]["multiMode"] = True - if "fallbacks" in self.part_json["settings"]: - del self.part_json["settings"]["fallbacks"] - elif self.stream_type == StreamType.VLESS_REALITY: - short_id = ''.join(random.sample('0123456789abcdef', 16)) - reality_settings = self.load_template('reality.json') - reality_settings["serverNames"] = kw['serverNames'] - reality_settings["dest"] = "{}:443".format(kw['serverNames'][0]) - reality_settings["privateKey"] = x25519_key()[0] - reality_settings["shortIds"] = [short_id] - self.part_json["streamSettings"]["realitySettings"] = reality_settings - self.part_json["streamSettings"]["security"] = "reality" - if "tlsSettings" in self.part_json["streamSettings"]: - del self.part_json["streamSettings"]["tlsSettings"] - self.save() - # tls的设置 - if self.stream_type in (StreamType.VLESS_WS, StreamType.VLESS_TLS, StreamType.VLESS_GRPC): - if not "certificates" in tls_settings_backup: - from ..config_modify.tls import TLSModifier - tm = TLSModifier(self.group_tag, self.group_index, alpn=alpn) - tm.turn_on(False) - return - tls_settings_backup["alpn"] = alpn - - elif self.stream_type == StreamType.TROJAN: - self.part_json['protocol'] = "trojan" - self.part_json["settings"] = { - "clients": [ - { - "password": kw["password"] - } - ], - "fallbacks": [ - { - "dest": 80 - } - ] - } - self.part_json["streamSettings"] = self.load_template('tcp.json') - self.save() - alpn = ["http/1.1"] - # tls的设置 - if not "certificates" in tls_settings_backup: - from ..config_modify.tls import TLSModifier - tm = TLSModifier(self.group_tag, self.group_index, alpn=alpn) - tm.turn_on(False) - return - elif not "alpn" in tls_settings_backup: - tls_settings_backup["alpn"] = alpn - elif self.stream_type == StreamType.H2: http2 = self.load_template('http2.json') salt = '/' + ''.join(random.sample(string.ascii_letters + string.digits, 8)) + '/' @@ -320,25 +197,16 @@ def write(self, **kw): self.save() # http2 tls的设置 - if not "certificates" in tls_settings_backup: - from ..config_modify.tls import TLSModifier - tm = TLSModifier(self.group_tag, self.group_index) - tm.turn_on(False) + if security_backup != "tls" or not "certificates" in tls_settings_backup: + from config_modify import tls + tm = tls.TLSModifier(self.group_tag, self.group_index) + tm.turn_on() return - if self.stream_type not in no_tls_group and origin_protocol not in no_tls_group and self.stream_type != StreamType.VLESS_REALITY: + if (self.stream_type != StreamType.MTPROTO and origin_protocol != StreamType.MTPROTO + and self.stream_type != StreamType.SS and origin_protocol != StreamType.SS): self.part_json["streamSettings"]["security"] = security_backup - if security_backup == "reality": - self.part_json["streamSettings"]["realitySettings"] = tls_settings_backup - else: - self.part_json["streamSettings"]["tlsSettings"] = tls_settings_backup - - if domain and self.stream_type not in no_tls_group: - self.part_json["domain"] = domain - - apln_list = (StreamType.VLESS_TLS, StreamType.TROJAN, StreamType.VLESS_REALITY, StreamType.VLESS_GRPC) - if self.stream_type not in apln_list and "streamSettings" in self.part_json and "tlsSettings" in self.part_json["streamSettings"] and "alpn" in self.part_json["streamSettings"]["tlsSettings"]: - del self.part_json["streamSettings"]["tlsSettings"]["alpn"] + self.part_json["streamSettings"]["tlsSettings"] = tls_settings_backup self.config["inbounds"][self.group_index] = self.part_json self.save() @@ -368,7 +236,7 @@ def write_ss_email(self, email): def write_dyp(self, status = False, aid = '32'): if status: - short_uuid = str(uuid.uuid4())[0:7] + short_uuid = str(uuid.uuid1())[0:7] dynamic_port_tag = "dynamicPort" + short_uuid self.part_json["settings"].update({"detour":{"to":dynamic_port_tag}}) dyn_json = self.load_template('dyn_port.json') @@ -385,32 +253,26 @@ def write_dyp(self, status = False, aid = '32'): del self.part_json["settings"]["detour"] self.save() - def write_tls(self, status = False, * , crt_file=None, key_file=None, domain=None, alpn=None): + def write_tls(self, status = False, *, crt_file=None, key_file=None, domain=None): if status: tls_settings = {"certificates": [ { "certificateFile": crt_file, "keyFile": key_file } - ]} - if alpn: - tls_settings["alpn"] = alpn - self.part_json["domain"] = domain + ]} + self.part_json["streamSettings"]["security"] = "tls" self.part_json["streamSettings"]["tlsSettings"] = tls_settings - self.part_json["streamSettings"]["security"] = "tls" - self.save() + Config().set_data("domain", domain) else: if self.part_json["streamSettings"]["network"] == StreamType.H2.value: - print(_("close tls will also close HTTP/2!")) - print("") - print(_("already reset protocol to origin kcp")) - self.part_json["streamSettings"] = self.load_template('kcp.json') + print("关闭tls同时也会关闭HTTP/2!\n") + print("已重置为kcp utp传输方式, 若要其他方式请自行切换") + self.part_json["streamSettings"] = self.load_template('kcp_utp.json') else: self.part_json["streamSettings"]["security"] = "none" self.part_json["streamSettings"]["tlsSettings"] = {} - if "domain" in self.part_json: - del self.part_json["domain"] - self.save() + self.save() def write_tfo(self, action = 'del'): if action == "del": @@ -430,7 +292,7 @@ class ClientWriter(Writer): def __init__(self, group_tag = 'A', group_index = 0, client_index = 0): super(ClientWriter, self).__init__(group_tag, group_index) self.client_index = client_index - self.client_str = "clients" if self.part_json["protocol"] in ("vmess", "vless", "trojan") else "users" + self.client_str = "clients" if self.part_json["protocol"] == "vmess" else "users" def write_aid(self, aid = 32): self.part_json["settings"][self.client_str][self.client_index]["alterId"] = int(aid) @@ -441,10 +303,10 @@ def write_uuid(self, new_uuid): self.save() def write_email(self, email): - if self.part_json["protocol"] == "shadowsocks": - self.part_json["settings"].update({"email": email}) - else: + if not "email" in self.part_json: self.part_json["settings"][self.client_str][self.client_index].update({"email": email}) + else: + self.part_json["settings"][self.client_str][self.client_index]["email"] = email self.save() class GlobalWriter(Writer): @@ -514,7 +376,11 @@ def write_stats(self, status = False): dokodemo_door = stats_json["dokodemoDoor"] del stats_json["dokodemoDoor"] #产生随机dokodemo_door的连接端口 - dokodemo_door["port"] = random_port(1000, 65535) + while True: + random_port = random.randint(1000, 65535) + if not port_is_use(random_port): + break + dokodemo_door["port"] = random_port has_door = False for inbound in self.config["inbounds"]: @@ -553,25 +419,21 @@ def write_stats(self, status = False): self.save() class NodeWriter(Writer): - def create_new_port(self, newPort): + def create_new_port(self, newPort, protocol, **kw): # init new inbound server = self.load_template('server.json') new_inbound = server["inbounds"][0] new_inbound["port"] = int(newPort) - new_inbound["settings"]["clients"][0]["id"] = str(uuid.uuid4()) - for rule in self.config["routing"]["rules"]: - if "protocol" in rule and "bittorrent" in rule["protocol"]: - new_inbound.update({ - "sniffing": { - "enabled": True, - "destOverride": ["http", "tls"] - } - }) - break + new_inbound["settings"]["clients"][0]["id"] = str(uuid.uuid1()) self.config["inbounds"].append(new_inbound) - print(_("add port group success!")) + print("新增端口组成功!") self.save() + reload_data = Loader() + new_group_list = reload_data.profile.group_list + stream_writer = StreamWriter(new_group_list[-1].tag, new_group_list[-1].index, protocol) + stream_writer.write(**kw) + def create_new_user(self, **kw): ''' 初始化时需传入group_tag, group_index参数, 自动调用父构造器来初始化 @@ -579,22 +441,13 @@ def create_new_user(self, **kw): if self.part_json['protocol'] == 'socks': user = {"user": kw["user"], "pass": kw["pass"]} self.part_json["settings"]["accounts"].append(user) - print("{0} user: {1}, pass: {2}".format(_("add socks5 user success!"), kw["user"], kw["pass"])) - - elif self.part_json['protocol'] == 'trojan': - user = {"password": kw["password"]} - email_info = "" - if "email" in kw and kw["email"] != "": - user.update({"email": kw["email"]}) - email_info = ", email: " + kw["email"] - self.part_json["settings"]["clients"].append(user) - print("{0} pass: {1}{2}".format(_("add trojan user success!"), kw["password"], email_info)) + print("新建Socks5用户成功! user: %s, pass: %s" % (kw["user"], kw["pass"])) - elif self.part_json['protocol'] == 'vmess': - new_uuid = uuid.uuid4() + elif self.part_json['protocol'] == 'vmess' : + new_uuid = uuid.uuid1() email_info = "" user = { - "alterId": 0, + "alterId": 32, "id": "ae1bc6ce-e575-4ee2-85f1-350a0aa506cb" } if "email" in kw and kw["email"] != "": @@ -602,22 +455,7 @@ def create_new_user(self, **kw): email_info = ", email: " + kw["email"] user["id"]=str(new_uuid) self.part_json["settings"]["clients"].append(user) - print("{0} uuid: {1}, alterId: 32{2}".format(_("add user success!"), str(new_uuid), email_info)) - - elif self.part_json['protocol'] == 'vless': - new_uuid = uuid.uuid4() - info = "" - user = { - "id": str(new_uuid) - } - if "email" in kw and kw["email"] != "": - user.update({"email":kw["email"]}) - info = ", email: " + kw["email"] - if self.part_json["streamSettings"]["security"] in ("reality", "tls"): - user["flow"] = kw["flow"] - info += ", flow: " + kw["flow"] - self.part_json["settings"]["clients"].append(user) - print("{0} id: {1}{2}".format(_("add user success!"), str(new_uuid), info)) + print("新建用户成功! uuid: %s, alterId: 32%s" % (str(new_uuid), email_info)) self.save() @@ -627,16 +465,16 @@ def del_user(self, group, client_index): if type(node) == Mtproto: clean_mtproto_tag(self.config, group.index) del self.config["inbounds"][group.index] - elif type(node) in (Vmess, Socks, Vless, Trojan): - client_str = 'accounts' if type(node) == Socks else 'clients' + elif type(node) == Vmess or type(node) == Socks: + client_str = 'clients' if type(node) == Vmess else 'accounts' del self.config["inbounds"][group.index]["settings"][client_str][client_index] - print(_("del user success!")) + print("删除用户成功!") self.save() def del_port(self, group): if type(group.node_list[0]) == Mtproto: clean_mtproto_tag(self.config, group.index) del self.config["inbounds"][group.index] - print(_("del port success!")) + print("删除端口成功!") self.save() \ No newline at end of file