diff --git a/.gitignore b/.gitignore index 41428ba..5fd76d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -config_*.json -config_*.json.backup +figs/*.kra diff --git a/HiddifyNG.en.md b/HiddifyNG.en.md new file mode 100644 index 0000000..cab1bd4 --- /dev/null +++ b/HiddifyNG.en.md @@ -0,0 +1,14 @@ +[HiddifyNG in Google Play](https://play.google.com/store/apps/details?id=ang.hiddify.com&pcampaignid=web_share) + +Copy config in a form of a link. Open Hiddify app, click Clipboard button, enter any name for a group (e.g. xray), press Confirm. Then click +Configs (on the bottom), you should see "easy-xray" with your server ip. Go to Settings, Custom rules, direct URL or IP section, and paste +the content of `misc/customgeo4hiddify.txt` as plain text. Save, then click to connect/start. + +![](figs/hiddify-1.jpg) + +![](figs/hiddify-2.jpg) + +![](figs/hiddify-3.jpg) + +![](figs/hiddify-4.jpg) + diff --git a/Nekoray.ru.md b/Nekoray.ru.md new file mode 100644 index 0000000..7b2d4bf --- /dev/null +++ b/Nekoray.ru.md @@ -0,0 +1,16 @@ +[Релизы Nekoray (см. Assets)](https://github.com/MatsuriDayo/nekoray/releases) + +Выбираем ядро xray, добавляем профиль (конфиг-ссылка), настраиваем маршруты (копируем из `misc/customgeo4nekoray.txt`), выбираем режим +системного прокси, и запускаем! + +![](figs/nekoray-1.jpg) + +![](figs/nekoray-2.jpg) + +![](figs/nekoray-3.jpg) + +![](figs/nekoray-4.jpg) + +![](figs/nekoray-5.jpg) + +![](figs/nekoray-6.jpg) diff --git a/README.md b/README.md index 9d6c17f..a83bbea 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,140 @@ # easy-xray -*Script for Linux which makes XRay installation and configuration easy* +*Script for Linux which makes XRay management easy* -(todo) [Readme in Russian](README.ru.md) +[Readme in Russian](README.ru.md) [(todo) Readme in Chinese](README.cn.md) -(todo) [Readme in Chinese](README.cn.md) +[XRay (aka ProjectX)](https://xtls.github.io/en/) is a frontier solution to circumvent the internet censorship. XRay allows to guide traffic +through a server (VPS) outside the region of censorship as a proxie, but connection to xray server looks for authorities as a typical +connection to a regular website. Attempts to detect VPN such as [active probing](https://ensa.fi/active-probing/) or blocking by the rule +"all except https" are eliminated by XRay. Also, XRay server can be configured to open only foreign websites, thus preventing detection by +the code on domestic websites. As a proxy, XRay has no need to encrypt already encrypted https traffic, hence CPU load is low. XRay doesn't +need to keep the connection alive, and users don't need to manually reconnect to it time-to-time. Also users don't need to turn a client off +to go to most domestic websites. -[XRay (aka ProjectX)](https://xtls.github.io/en/) is a frontier solution to surpass the internet censorship. It can work as a server and as -a client allowing to guide traffic through a VPS outside the region of censorship. XRay configuration can be confusing for a newcomer, -so, here is a script which helps to +![xray-schematic: traffic to foreign websites goes through vps, traffic to domestic sites goes directly from pc](figs/xray-schematic.png) -- install/upgrade/remove XRay and geodata +Besides of its plusses, configuration and management of XRay server is quite sophisticated. So, here is a script which helps to do it. It +can + +- install/upgrade/remove XRay - generate credentials and server/client configs - add/delete users to the configs +- and more + +### How to use on VPS + +First you need a Linux server (VPS) with [jq](https://jqlang.github.io/jq/) and `openssl` installed, they can be found in repositories of +almost all popular Linux distributions. Then download whole `easy-xray` folder to the VPS, make the script `ex.sh` executable, and run a +desired command with it. Use `./ex.sh help` to see the list of all available commands and `./ex.sh install` to start interactive prompt +that installs and configures XRay. -First, make the script `ex.sh` executable, then run a desired command with it. Use `./ex.sh help` to see the list of all available commands -and `./ex.sh install` to start interactive prompt which installs and configures XRay. ``` -$ chmod +x ex.sh -$ ./ex.sh help -$ sudo ./ex.sh install +chmod +x ex.sh +./ex.sh help +sudo ./ex.sh install ``` -### Prerequisites +Now you have `conf` folder with server and client configs and some user configs. Server config is properly installed and XRay is running. +Time to share configs or *links* with users! To generate config in the link form, use `./ex.sh link user_config_file.json`. -For manipulations with configs, [jq](https://jqlang.github.io/jq/) is needed, it can be found in repositories of almost all popular Linux -distributions. +### Clients -### How it works +#### Linux -With current configs, XRay creates a tunnel between the client (your laptop, phone etc.) and the server (your VPS). The tunnel uses VLESS -[Reality](https://github.com/XTLS/REALITY/blob/main/README.en.md) protocol that obfuscates traffic and in our case imitates -[grpc](https://en.wikipedia.org/wiki/GRPC). For the censor the tunnel looks like a usual connection to a site. The server responds to https -requests as some popular site thus it is not suspicious for an active probing. On the client side *XRay* creates http/https and socks -proxies which can be used by your Telegram or web browser like this: +XRay itself can be a client, besides plenty of GUI clients that are available for other popular operating systems (see below). You can +manually install XRay with [official script](https://github.com/XTLS/Xray-install) and manually copy `customgeo.dat` to +`/usr/local/share/xray/` or just install them both with `sudo ./ex.sh install` command. Then, copy client config from the server and run: -![browser proxy: http/https proxy 127.0.0.1 at port 801, socks v5 host 127.0.0.1 at port 800](browser-proxy-settings.png) +``` +sudo cp config_client_username.json /usr/local/etc/xray/config.json +sudo systemctl start xray +``` -Note that there is no additional encryption layer in VLESS; using it you rely on the encryption that the browser (Telegram app etc.) makes. -Note also that for current client config, traffic to .cn, .ru, .by and .ir sites goes directly from the client, see -[here](https://github.com/EvgenyNerush/coherence-grabber) for details. This makes your server much less attention-grabbing and suspicious, -but your connection less anonymous. +or -### Other clients +``` +sudo xray run -c config_client_username.json +``` -For Windows, MacOS or Android you can try Nekobox, v2rayNG or ? (TODO): tests and config generation for them. +In the current configuration, on the client side XRay creates http/https and socks5 proxies on your PC which can be used by your Telegram +app or Web browser like this: -### Futher reading +![browser proxy: http/https proxy 127.0.0.1 at port 801, socks v5 host 127.0.0.1 at port 800](figs/browser-proxy-settings.png) -Template configs contain comments and links and are good start to find another interesting Xray configuration options. +To check that traffic to domestic and foreing sites goes by different ways, visit, for example, +[whatismyip.com](https://www.whatismyip.com/) and [2ip.ru](https://2ip.ru/). They should show different IP addressess. -### Acknowledgements +#### Windows -[This article (in Russian)](https://habr.com/ru/articles/731608/) helped me to install *XRay* for the first time. +Use [Nekoray (Nekobox)](https://github.com/MatsuriDayo/nekoray) client that releases can be found on [this +page](https://github.com/MatsuriDayo/nekoray/releases). Choose one of Assets, for instance `nekoray-3.26-2023-12-09-windows64.zip`, download +then unzip it and run Nekoray. The following configuration is [quite easy (RU)](Nekoray.ru.md). + +#### MacOS + +Use XRay: + +``` +brew install xray +cp customgeo.dat /usr/local/share/xray/ # not yet tested +sudo xray -config=config_client_username.json +``` + +#### Android + +For many mobile applications it is enough to paste a client config in a link form from the buffer, and add customgeo in an appropriate form +(see `misc` dir) to somethere like `Settings/Routing/Custom rules/Direct URL`. Tested applications are listed below. + +Use [V2RayNG](https://play.google.com/store/apps/details?id=com.v2ray.ang&pcampaignid=web_share), +[HiddifyNG](https://play.google.com/store/apps/details?id=ang.hiddify.com&pcampaignid=web_share) or [Hiddify +Next](https://play.google.com/store/apps/details?id=app.hiddify.com&pcampaignid=web_share). They are very similar to each other, here are +some instructions for [V2RayNG (RU)](V2RayNG.ru.md) and [HiddifyNG (EN)](HiddifyNG.en.md). + +#### iOS + +Use [Straisand](https://apps.apple.com/us/app/streisand/id6450534064). Its configuration is very similar to that of V2Ray and Hiddify (see +above). Manual copy-paste from json config file is also possible. (customgeo not yet tested) + +#### Others + +[Here](https://github.com/xtls/xray-core) you can find an additional list of clients. + +### Tor + +Most of GUI clients are based on xray core, but do not fully support its configuration, that is crutial for Tor. To use +[TorBrowser](https://www.torproject.org/download/) in this case, use bridges. To get a bridge, send a letter to bridges@torproject.org, then +copy symbols after `obfs4` and paste them to TorBrowser bridge settings. + +### Bittorrent + +Bittorent protocol is blocked in the current configuration. Using bittorent on a VPS can lead to a ban from VPS provider. + +### What else + +#### Hints + +To choose a good `serverName` (your VPS will mimic this website), you can use [RealiTLScanner](https://github.com/XTLS/RealiTLScanner). This +tool scans ip addresses near your server (do it not from your VPS!) and show names found at port 443. With `nmap -T4 hostname` you can check +that only ports 80 and 443 are open on hosts of the found websites (as in the configurations of template configs). It is also good to move +ssh on your VPS to a port beyond 1024. It can be done in `/etc/ssh/sshd_config`. Check twice that VPS is available at your new port before +you comment Port 22! + +``` +#Port 22 +Port 43210 +``` + +#### Links + +Template configs contain comments and links and are a good start to find another interesting Xray configuration options. + +See [this link](https://github.com/EvgenyNerush/coherence-grabber) for details on how `customgeo` files are generated. + +[This article (in Russian)](https://habr.com/ru/articles/731608/) helped me to install XRay for the first time. + +The template configs are based on these [gRPC](https://github.com/XTLS/Xray-examples/tree/main/VLESS-gRPC-REALITY) +and [XTLS](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision-REALITY) examples. [XRay config reference](https://xtls.github.io/en/config/) is brilliant and helped me much. -Configs for [gRPC](https://github.com/XTLS/Xray-examples/tree/main/VLESS-gRPC-REALITY) -and [XTLS](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision-REALITY) on that the template configs are based. - diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..1113700 --- /dev/null +++ b/README.ru.md @@ -0,0 +1,148 @@ +# easy-xray + +*XRay - это просто* + +[(EN)](README.md) [(CN(todo))](README.cn.md) + +[XRay (aka ProjectX)](https://xtls.github.io/en/) - современное решение для обхода интернет-цензуры. Он - как прокси - позволяет проводить +трафик через сервер (VPS) за пределами региона с цензурой, при этом подключение к серверу XRay выглядит как подключение к обычному сайту. +Попытки обнаружения VPN, такие как активное зондирование [(active probing)](https://ensa.fi/active-probing/) или блокировка по правилу +«блокируем все протоколы, кроме https», для XRay не страшны. Кроме того, сервер XRay можно настроить на открытие только "зарубежных" +веб-сайтов, что предотвращает попытки обнаружения туннеля сайтами "домашнего" региона. XRay (в случае использования протокола +VLESS-Reality) не шифрует уже зашифрованный https-трафик, поэтому нагрузка на процессор невелика. XRay не нужно поддерживать соединение, и +пользователям не нужно время от времени повторно подключаться к нему вручную. При правильной настройке клиента пользователям не нужно +отключаться от сервера, чтобы перейти на большинство веб-сайтов "домашнего" региона. + +![схема: трафик до зарубежных сайтов идёт через сервер, а до домашних сайтов - напрямую с ПК](figs/xray-schematic.png) + +Помимо плюсов, у XRay есть и недостатки - например, сложная настройка и администрирование, что стало мотивацией для создания этого +репозитория и скрипта `ex.sh`. Этот скрипт поможет + +- установить/обновить/удалить XRay +- генерировать учетные данные и конфигурации сервера/клиента +- добавлять/удалять пользователей +- и т. д. + +### Как использовать на VPS + +Для начала нужно арендовать VPS сервер вне региона с цензурой (или попросить друзей заграницей сделать это). После на сервер нужно +установить [jq](https://jqlang.github.io/jq/) и `openssl` - их можно найти в репозиторияx практически всех популярных Linux дистрибутивов. +Затем скачайте `easy-xray` (всю директорию целиком), сделайте исполняемым файл `ex.sh` и запустите нужную команду. Используйте `./ex.sh +help` для получения списка всех команд и короткой справки по ним. Для установки и настройки xray используйте `./ex.sh install` - эта команда +предложит вам ввести ip-адрес вашего сервера, адрес сайта, под который ваш сервер будет маскироваться, и имена пользователей. + +``` +chmod +x ex.sh +./ex.sh help +sudo ./ex.sh install +``` + +В результате у вас появится директория `conf` с серверным конфигом, основным клиентским конфигом и конфигами пользователей. Серверный конфиг +будет должным образом установлен, а xray - запущен с ним. Настало время раздать конфиги пользователям! Для многих графических клиентов +удобно использовать конфиги в виде ссылок, которые можно сгенерировать командой `./ex.sh link user_config_file.json`. + +### Клиенты + +#### Linux + +Для подключения к XRay-серверу существуют клиенты с графическим интерфейсом, но о них чуть позже. На Linux удобно использовать сам XRay в +качестве клиента. XRay можно установить вручную, используя [официальный скрипт](https://github.com/XTLS/Xray-install), после чего +скопировать `customgeo.dat` в `/usr/local/share/xray/`. Другой вариант установить XRay и `customgeo.dat` - воспользоваться командой `sudo +./ex.sh install`. Затем нужно скопировать клиентский конфиг с сервера и выполнить + +``` +sudo cp config_client_username.json /usr/local/etc/xray/config.json +sudo systemctl start xray +``` + +или + +``` +sudo xray run -c config_client_username.json +``` + +XRay с текущим клиентским конфигом создаёт локальные http/https и socks5 прокси, которые могут быть прописаны в настройки Телеграма или +браузера примерно так: + +![прокси в браузере: http/https прокси 127.0.0.1 порт 801, socks5 хост 127.0.0.1 порт 800](figs/browser-proxy-settings.png) + +Чтобы проверить, что трафик до "зарубежных" и "домашних" сайтов идёт разными путями, зайдите на +[whatismyip.com](https://www.whatismyip.com/) и [2ip.ru](https://2ip.ru/). Они должны показать разные ip-адреса и локации. + +#### Windows + +Используйте [Nekoray (Nekobox)](https://github.com/MatsuriDayo/nekoray) - клиент с графическим интерфейсом. Релизы для Windows можно найти +на [этой странице](https://github.com/MatsuriDayo/nekoray/releases) в разделе Assets. Выберите и скачайте, например, +`nekoray-3.26-2023-12-09-windows64.zip`, затем распакуйте и запустите. Дальнейшее конфигурирование [описано здесь](Nekoray.ru.md). + +#### MacOS + +Используйте сам XRay: + +``` +brew install xray +cp customgeo.dat /usr/local/share/xray/ # not yet tested +sudo xray -config=config_client_username.json +``` + +#### Android + +Для многих мобильных клиентов вся настройка заключается в том, что нужно вставить конфиг-ссылку из буфера обмена, а после добавить сайты из +customgeo в подходящей форме (см. директорию `misc`) в "прямые" маршруты (в `Settings/Routing/Custom rules/Direct URL` или что-то подобное). +Приложения, которые были протестированы, перечислены ниже. + +Скачайте из Google Play [V2RayNG](https://play.google.com/store/apps/details?id=com.v2ray.ang&pcampaignid=web_share), +[HiddifyNG](https://play.google.com/store/apps/details?id=ang.hiddify.com&pcampaignid=web_share) или [Hiddify +Next](https://play.google.com/store/apps/details?id=app.hiddify.com&pcampaignid=web_share). Они очень похожи друг на друга, здесь есть +инструкции для [V2RayNG](V2RayNG.ru.md) и [HiddifyNG (EN)](HiddifyNG.en.md). + +#### iOS + +Используйте [Straisand](https://apps.apple.com/us/app/streisand/id6450534064). Конфигурирование аналогично тому, что описано для V2Ray или +Hiddify (см. выше). Customgeo - ещё не протестирован. + +#### Другие + +[Здесь](https://github.com/xtls/xray-core) можно найти дополнительный список клиентов. + +### Tor + +В случае использования XRay в качестве клиента с текущей конфигурацией не должно возникнуть проблем. Однако большинство графических +клиентов, хотя в качестве ядра используют xray, не дают полного доступа к его конфигурации. Из-за этого +[Torbrowser](https://www.torproject.org/download/) не всегда может работать "из коробки". Если подключиться к сети Tor не удаётся, +попробуйте использовать мост (специальный промежуточный сервер между вами и сетью Tor). Чтобы получить мост, отправьте письмо с любым +текстом на bridges@torproject.org, затем вставьте из ответного письма символы после `obfs4` в настройки моста в Torbrowser. + +### Bittorrent + +Bittorent протокол блокируется в текущих настройках - использование bittorrent может привести к бану со стороны VPS провайдера. + +### Что ещё? + +#### Советы + +Для того, чтобы выбрать сайт, который будет видеть на вашем сервере цензор в случае атаки active probing (`serverName` в конфигах), можно +воспользоваться утилитой [RealiTLScanner](https://github.com/XTLS/RealiTLScanner). Она может просканировать адреса, близкие к вашему серверу +(делайте это с домашнего компьютера, а не с сервера!), и показать имена сайтов на них. С помощью команды `nmap -T4 hostname` можно +проверить, что на выбранном вами сервере открыты только порты 80 и 443 (как в настройках xray здесь). Также неплохо выбрать далёкий порт для +ssh вместо 22-го, если на выбранном вами сайте этот порт не открыт. Дважды проверьте, что по выбранному вами порту ssh вы можете зайти на +сервер - только после этого закрывайте порт 22. + +``` +#Port 22 +Port 43210 +``` + +#### Ссылки + +Шаблонные конфиг-файлы для сервера и клиента (`template_config_*.json`) содержат много комментариев, которые могут помочь в создании своей +собственной конфигурации клиента и сервера. + +Шаблонные конфиги сделаны на основе этих [двух](https://github.com/XTLS/Xray-examples/tree/main/VLESS-gRPC-REALITY) [примеров](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision-REALITY). + +[Описание всех возможных полей XRay конфигов (EN)](https://xtls.github.io/en/config/) очень подробное и хорошо помогает. + +[Здесь](https://github.com/EvgenyNerush/coherence-grabber) детали того, как собраны сайты для доступа напрямую, прописанные в `customgeo`. + +[Эта статья](https://habr.com/ru/articles/731608/) помогла мне установить XRay в первый раз. + diff --git a/V2RayNG.ru.md b/V2RayNG.ru.md new file mode 100644 index 0000000..5caf3c8 --- /dev/null +++ b/V2RayNG.ru.md @@ -0,0 +1,11 @@ +[V2RayNG в Google Play](https://play.google.com/store/apps/details?id=com.v2ray.ang&pcampaignid=web_share) + +Конфиг-ссылку нужно скопировать и добавить в приложение ("+" справа вверху, далее импорт профиля из буфера обмена). +Для нормальной работы российских сайтов нужно скопировать содержимое `misc/customgeo4hiddify.txt` в пользовательские правила (прямые). + +![](figs/v2ray-1.jpg) + +![](figs/v2ray-2.jpg) + +![](figs/v2ray-3.jpg) + diff --git a/browser-proxy-settings.png b/browser-proxy-settings.png deleted file mode 100644 index 92e631c..0000000 Binary files a/browser-proxy-settings.png and /dev/null differ diff --git a/customgeo.dat b/customgeo.dat index e79f698..7cbdfa7 100644 --- a/customgeo.dat +++ b/customgeo.dat @@ -1,30 +1,53 @@ - -COHERENCE-EXTRA lenta.com + +COHERENCE-EXTRA habr.comhabrastorage.org lenta.com rugldn.net ru yastatic.net ru yandex.net rumycdn.me ruavito.st -ru gismeteo.st -ru gismeteo.net -ruozonusercontent.com +ru gismeteo.st +ru +by  gismeteo.net +ru +byozonusercontent.com ru mradx.net ru aestatic.net -ru -alicdn.com -ru!rambler.pushwoosh.com -ruvk.com +ru! pushwoosh.com +ru +byvk.com +ru +by userapi.com ru otm-r.com ru2gis.com ruchampionat.com ru rus-tv.su -ru kpcdn.net -rukp.house -rukp.kg -rukaspersky-labs.com -ruqq.com +ru kpcdn.net +ru +bykp.house +ru +bykp.kg +ru +bykaspersky-labs.com +ru yadro.com +ru boosty.to +ru eaglecdn.com +rulivejournal.com +rulivejournal.net +ruedc.sale +ru +by"stellarlabs.ai +ru +by myminsk.com +by#sputniknews.com +ru +by$sputnikglobe.com +ru +by digikala.com +irbale.ai +ru +irqq.com cn gtimg.com cn zhihu.com cn geetest.com diff --git a/ex.sh b/ex.sh index ccfa055..419496d 100755 --- a/ex.sh +++ b/ex.sh @@ -1,13 +1,19 @@ #!/usr/bin/env bash +# stdout styles bold='\033[0;1m' +italic='\033[0;3m' underl='\033[0;4m' red='\033[0;31m' green='\033[0;32m' yellow='\033[0;33m' normal='\033[0m' -# strip lines with comments from jsonC +################# +### Functions ### +################# + +# delete lines with comments from jsonC jsonc2json () { if [ ! -v $1 ] then @@ -19,33 +25,353 @@ jsonc2json () { fi } +# convert string with number of bytes to pretty form +bytes2MB () { + if [ -v $1 ] + then + echo "" + else + bytes=$1 + length=${#bytes} + if [ $length -gt 9 ] + then + head=${bytes::-9} + tail=${bytes: -9} + echo "${head}.${tail::2} GB" + elif [ $length -gt 6 ] + then + head=${bytes::-6} + tail=${bytes: -6} + echo "${head}.${tail::2} MB" + elif [ $length -gt 3 ] + then + head=${bytes::-3} + tail=${bytes: -3} + echo "${head}.${tail::2} kB" + else + echo "$bytes bytes" + fi + fi +} -export PATH=$PATH:/usr/local/bin/ # for sudo user this can be not in PATH -if command -v xray > /dev/null -then - xray_version=$(xray --version | head -n 1 | cut -c 6-10) - echo -e "${green}xray ${xray_version} detected${normal}" -fi +# drop quotes (") at the start and at the end of a string +strip_quotes () { + if [ -v $1 ] || [ ${#1} -lt 2 ] + then + echo "" + else + s=$1 + s=${s: 1} # from 1 to the end + s=${s:: -1} # from 0 to that is before the last one + echo $s + fi +} -if command -v jq > /dev/null -then - jq_installed=true - echo -e "${green}jq found${normal}" -else - jq_installed=false - echo -e "${yellow}Warning: jq not installed but needed for operations with configs${normal}" -fi +# convert json string with statistics to pretty form; +# use with pipe | to deal with multiline strings correctly! +pretty_stats () { + read stats + if [ -v "$stats" ] + then + echo "" + else + bytes=$(echo $stats | jq ".stat.value") + echo "$(bytes2MB $(strip_quotes $bytes))" + fi +} -if [ $(id -u) -eq 0 ] -then - is_root=true - echo -e "${green}running as root${normal}" -else - is_root=false - echo -e "${yellow}Warning: you should be root to install xray${normal}" -fi +# check if the mandatory command exists +check_command () { + cmd=$1 + cmd_aim=$2 + comment=$3 + if command -v $cmd > /dev/null + then + echo -e "${green}${cmd} found${normal}" + else + echo -e "${red}${cmd} not found; ${cmd_aim}${normal}" + echo -e "${comment}" + exit 1 + fi +} -command="help" +# make directory `dir`; if it already exists, first move it to dir.backup; +# if dir.backup already exists, first move it to dir.backup.backup; if it exists, +# first delete it +unsafe_mkdir () { + dir=$1 + if [ -d "$dir" ] + then + if [ -d "${dir}.backup" ] + then + if [ -d "${dir}.backup.backup" ] + then + rm -r "${dir}.backup.backup" + fi + mv "${dir}.backup" "${dir}.backup.backup" + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" "${dir}.backup.backup" + fi + mv "$dir" "${dir}.backup" + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" "${dir}.backup" + fi + mkdir "$dir" + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" "${dir}" +} + +# copy file to file.backup with the same logic as in `unsafe_mkdir` +cp_to_backup () { + file=$1 + if [ -f "${file}.backup" ] + then + cp "${file}.backup" "${file}.backup.backup" + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" "${file}.backup.backup" + fi + cp "$file" "${file}.backup" + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" "${file}.backup" +} + +# the main part of `./ex.sh conf` command, generates config files for server and clients +conf () { + export PATH=$PATH:/usr/local/bin/ # brings xray to the path for sudo user + check_command xray "needed for config generation" "to install xray, try: sudo ./ex.sh install" + check_command jq "needed for operations with configs" + check_command openssl "needed for strong random numbers excluding some types of attacks" + # + echo -e "Enter IPv4 or IPv6 address of your xray server, or its domain name:" + read address + if [ -v $address ] + then + echo -e "${red}no address given${normal}" + exit 1 + fi + id=$(xray uuid) # random uuid for VLESS + keys=$(xray x25519) # string "Private key: Abc... Public key: Xyz..." + private_key=$(echo $keys | cut -d " " -f 3) # get 3rd field of fields delimited by spaces + public_key=$(echo $keys | cut -d " " -f 6) # get 6th field + short_id=$(openssl rand -hex 8) # random short_id for REALITY + # + echo -e "Choose a fake site to mimic. +Better if it is quite popular and not blocked in your country: +(1) www.youtube.com (default) +(2) www.microsoft.com +(3) www.google.com +(4) www.bing.com +(5) www.yahoo.com +(6) www.adobe.com +(7) aws.amazon.com +(8) discord.com +(9) your variant" + read number + default_fake_site="www.youtube.com" + if [ -v $number ] + then + fake_site=$default_fake_site + else + if [ $number -eq 2 ] + then + fake_site="www.microsoft.com" + elif [ $number -eq 3 ] + then + fake_site="www.google.com" + elif [ $number -eq 4 ] + then + fake_site="www.bing.com" + elif [ $number -eq 5 ] + then + fake_site="www.yahoo.com" + elif [ $number -eq 6 ] + then + fake_site="www.adobe.com" + elif [ $number -eq 7 ] + then + fake_site="aws.amazon.com" + elif [ $number -eq 8 ] + then + fake_site="discord.com" + elif [ $number -eq 9 ] + then + echo -e "type your variant:" + read fake_site + if [ -v $fake_site ] + then + fake_site=$default_fake_site + fi + else + fake_site=$default_fake_site + fi + fi + echo -e "${green}mimic ${fake_site}${normal}" + server_names="[ \"$fake_site\" ]" + email="love@xray.com" + # + unsafe_mkdir conf + ## Make server config ## + jsonc2json template_config_server.jsonc \ + | jq ".inbounds[1].settings.clients[0].id=\"${id}\" + | .inbounds[2].settings.clients[0].id=\"${id}\" + | .inbounds[1].settings.clients[0].email=\"${email}\" + | .inbounds[2].settings.clients[0].email=\"${email}\" + | .inbounds[1].streamSettings.realitySettings.dest=\"${fake_site}:443\" + | .inbounds[2].streamSettings.realitySettings.dest=\"${fake_site}:80\" + | .inbounds[1].streamSettings.realitySettings.serverNames=${server_names} + | .inbounds[2].streamSettings.realitySettings.serverNames=${server_names} + | .inbounds[1].streamSettings.realitySettings.privateKey=\"${private_key}\" + | .inbounds[2].streamSettings.realitySettings.privateKey=\"${private_key}\" + | .inbounds[1].streamSettings.realitySettings.shortIds=[ \"${short_id}\" ] + | .inbounds[2].streamSettings.realitySettings.shortIds=[ \"${short_id}\" ]" \ + > ./conf/config_server.json + # make the user (not root) the owner of the file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ./conf/config_server.json + vnext=" [ + { + \"address\": \"${address}\", + \"port\": 443, + \"users\": [ + { + \"id\": \"${id}\", + \"email\": \"${email}\", + \"encryption\": \"none\", + \"flow\": \"xtls-rprx-vision\" + } + ] + } + ]" + clientRealitySettings=" { + \"fingerprint\": \"chrome\", + \"serverName\": \"${fake_site}\", + \"show\": false, + \"publicKey\": \"${public_key}\", + \"shortId\": \"${short_id}\", + }" + ## Make main client config ## + jsonc2json template_config_client.jsonc \ + | jq ".outbounds + |= map(if .settings.vnext then .settings.vnext=${vnext} else . end) + | .outbounds + |= map(if .streamSettings.realitySettings then .streamSettings.realitySettings=${clientRealitySettings} else . end)" \ + > ./conf/config_client.json + # make the user (not root) an owner of a file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ./conf/config_client.json + if [ -f "./conf/config_client.json" ] && [ -f "./conf/config_server.json" ] + then + echo -e "${green}config files are generated${normal}" + else + echo -e "${red}config files are not generated${normal}" + exit 1 + fi +} + +# the main part of `./ex.sh add` command, adds config for given users and updates server config +add () { + export PATH=$PATH:/usr/local/bin/ # brings xray to the path for sudo user + check_command xray "needed for config generation" "to install xray, try: sudo ./ex.sh install" + check_command jq "needed for operations with configs" + check_command openssl "needed for strong random numbers excluding some types of attacks" + if [ ! -f "./conf/config_client.json" ] || [ ! -f "./conf/config_server.json" ] + then + echo -e "${red}server config and config for default user are needed +but not present; to generate them, try + ./ex.sh conf${normal}" + exit 1 + fi + if [ -v $1 ] + then + echo -e "${red}usernames not set${normal} +For default user, use config_client.json generated +by ${underl}install${normal} command. Otherwise use non-void usernames, +preferably of letters and digits only." + exit 1 + fi + # backup server config + cp_to_backup ./conf/config_server.json + # loop over usernames + for username in "$@" + do + username_exists=false + client_emails=$(jq ".inbounds[1].settings.clients[].email" ./conf/config_server.json) + for email in ${client_emails[@]} + do + # convert "name@example.com" to name + name=$(echo $email | cut -d "@" -f 1 | cut -c 2-) + if [ $username = $name ] + then + username_exists=true + fi + done + if $username_exists + then + echo -e "${yellow}username ${username} already exists, no new config created fot it${normal}" + else + id=$(xray uuid) # generate random uuid for vless + # generate random short_id for grpc-reality + short_id=$(openssl rand -hex 8) + # make new user config from default user config + ok1=$(cat ./conf/config_client.json | jq ".outbounds[0].settings.vnext[0].users[0].id=\"${id}\" | .outbounds[0].settings.vnext[0].users[0].email=\"${username}@example.com\" | .outbounds[0].streamSettings.realitySettings.shortId=\"${short_id}\"" > ./conf/config_client_${username}.json) + # then make the user (not root) an owner of a file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ./conf/config_client_${username}.json + # update server config + client=" + { + \"id\": \"${id}\", + \"email\": \"${username}@example.com\", + \"flow\": \"xtls-rprx-vision\" + } + " + # update server config + cp ./conf/config_server.json ./conf/tmpconfig.json + ok2=$(cat ./conf/tmpconfig.json | jq ".inbounds[1].settings.clients += [${client}] | .inbounds[1].streamSettings.realitySettings.shortIds += [\"${short_id}\"]" > ./conf/config_server.json) + rm ./conf/tmpconfig.json + # then make the user (not root) an owner of a file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ./conf/config_server.json + if $ok1 && $ok2 + then + echo -e "${green}config_client_${username}.json is written, config_server.json is updated${normal}" + else + echo -e "${yellow}something went wrong with username ${username}${normal}" + fi + fi + done +} + +# `./ex.sh push` command, copies config to xray's dir and restarts xray +push () { + if [ $(id -u) -ne 0 ] # not root + then + echo -e "${red}you should have root privileges for that, try +sudo ./ex.sh push${normal}" + exit 1 + fi + echo -e "Which config to use, server/client/other? (S/c/o)" + read answer + if [ ! -v $answer ] && [ ${answer::1} = "c" ] + then + # use main client config + config="config_client.json" + elif [ ! -v $answer ] && [ ${answer::1} = "o" ] + then + # use config of some other user + echo -e "Which config from ./conf/ to use? (write the filename)" + read answer + config="$answer" + else + # use server config + config="config_server.json" + fi + if $(cp ./conf/${config} /usr/local/etc/xray/config.json && systemctl restart xray) + then + sleep 1s # gives time to xray restart + journalctl -u xray | tail -n 5 # message about xray start + else + echo -e "${red}can't copy config or start xray, try +sudo xray run -c ./conf/${config}${normal}" + fi +} + +############# +### MAIN #### +############# + +command="help" # default if [ ! -v $1 ] then command=$1 @@ -53,276 +379,283 @@ fi if [ $command = "install" ] then - - echo -e "${bold}Download and install xray?${normal} (Y/n)" - read answer_di - if [ -v $answer_di ] || [ $(echo $answer_di | cut -c 1) != "n" ] + if [ $(id -u) -ne 0 ] # not root then - install_xray=true - if command -v xray > /dev/null + echo -e "${red}you should have root privileges to install xray, try +sudo ./ex.sh install${normal}" + exit 1 + fi + # + if command -v xray > /dev/null # xray already installed + then + echo -e "${yellow}xray ${version} detected, install anyway?${normal} (y/N)" + read answer + # default answer, answer not set or it's first letter is not `y` or `Y` + if [ -v $answer ] || ([ ${answer::1} != "y" ] && [ ${answer::1} != "Y" ]) then - echo -e "xray ${version} detected, install anyway? (y/N)" - read answer_ia - if [ -v $answer_ia ] || ([ $(echo $answer_ia | cut -c 1) != "y" ] && [ $(echo $answer_ia | cut -c 1) != "Y" ]) - then - install_xray=false - fi - fi - if $install_xray - then - if $is_root - then - if bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install - then - dat_dir="/usr/local/share/xray/" - mkdir -p $dat_dir - cp customgeo.dat ${dat_dir} - echo -e "${green}xray installed${normal}" - else - echo -e "${red}xray not installed, something goes wrong${normal}" - fi - else - echo -e "${red}You should be root, or run this script with sudo -to install xray${normal}" - exit 1 - fi + exit 1 fi fi - - echo -e "${bold}Generate configs?${normal} (Y/n)" - read answer_gc - if [ -v $answer_gc ] || [ $(echo $answer_gc | cut -c 1) != "n" ] + # + if bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install then - if ! $(command -v xray > /dev/null) + echo -e "${green}xray installed${normal}" + dat_dir="/usr/local/share/xray/" + mkdir -p $dat_dir + if cp customgeo.dat ${dat_dir} then - echo -e "${red}xray not installed, can't generate configs" - exit 1 - fi - if ! $jq_installed - then - echo -e "${red}jq not installed, can't generate configs" - exit 1 + echo -e "${green}customgeo.dat copied to ${dat_dir}${normal}" else - echo -e "${bold}Enter IPv4 or IPv6 address of your xray server, or its domain name:${normal}" - read address - id=$(xray uuid) # generate random uuid for vless - keys=$(xray x25519) - private_key=$(echo $keys | cut -d " " -f 3) - public_key=$(echo $keys | cut -d " " -f 6) - # generate random short_id for grpc-reality - if command -v openssl > /dev/null - then - short_id=$(openssl rand -hex 8) - else - echo -e "Enter a random (up to) 16-digit hex number, -containing only digits 0-9 and letters a-f, for instance -1234567890abcdef" - read short_id - if [ -v $short_id ] - then - echo -e "${red}short id not set${normal}" - exit 1 - fi - fi - echo -e "${bold}Choose a fake site to mimic.${normal} -Better if it is: hosted by your VPS provider, -in the same country, it is popular, -and have only ports 80 (http) and 443 (https) open -(can check with `nmap -T4 hostname`) -(1) www.youtube.com (default) -(2) www.microsoft.com -(3) www.google.com -(4) www.bing.com -(5) www.yahoo.com -(6) your variant" - read number - default_fake_site="www.youtube.com" - if [ ! -v $number ] - then - if [ $number -eq 2 ] - then - fake_site="www.microsoft.com" - elif [ $number -eq 3 ] - then - fake_site="www.google.com" - elif [ $number -eq 4 ] - then - fake_site="www.bing.com" - elif [ $number -eq 5 ] - then - fake_site="www.yahoo.com" - elif [ $number -eq 6 ] - then - echo -e "type your variant:" - read fake_site - if [ -v $fake_site ] - then - fake_site=$default_fake_site - fi - else - fake_site=$default_fake_site - fi - else - fake_site=$default_fake_site - fi - server_names="[ \"$fake_site\" ]" - echo -e "${green}mimic ${fake_site}${normal}" - email="love@xray.com" - # make server config - jsonc2json template_config_server.jsonc \ - | jq ".inbounds[].settings.clients[0].id=\"${id}\" - | .inbounds[].settings.clients[0].email=\"${email}\" - | .inbounds[0].streamSettings.realitySettings.dest=\"${fake_site}:443\" - | .inbounds[1].streamSettings.realitySettings.dest=\"${fake_site}:80\" - | .inbounds[].streamSettings.realitySettings.serverNames=${server_names} - | .inbounds[].streamSettings.realitySettings.privateKey=\"${private_key}\" - | .inbounds[].streamSettings.realitySettings.shortIds=[ \"${short_id}\" ]" \ - > config_server.json - # then make the user (not root) the owner of the file - [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" config_server.json - vnext=" [ - { - \"address\": \"${address}\", - \"port\": 443, - \"users\": [ - { - \"id\": \"${id}\", - \"email\": \"${email}\", - \"encryption\": \"none\", - \"flow\": \"xtls-rprx-vision\" - } - ] - } - ]" - clientRealitySettings=" { - \"fingerprint\": \"chrome\", - \"serverName\": \"${fake_site}\", - \"show\": false, - \"publicKey\": \"${public_key}\", - \"shortId\": \"${short_id}\", - }" - # make main client config - jsonc2json template_config_client.jsonc | jq ".outbounds |= map(if .settings.vnext then .settings.vnext=${vnext} else . end) | .outbounds |= map(if .streamSettings.realitySettings then .streamSettings.realitySettings=${clientRealitySettings} else . end)" > config_client.json - # then make the user (not root) an owner of a file - [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" config_client.json + echo -e "${red}customgeo.dat not copied to ${dat_dir}${normal}" + exit 1 fi + else + echo -e "${red}xray not installed, something goes wrong${normal}" + exit 1 + fi + # + echo -e "Generate configs? (Y/n)" + read answer + if [ ! -v $answer ] && [ ${answer::1} = "n" ] + then + # config generation is not requested + echo -e "If you have a config file for xray, you can manually +start xray with the following commands: + sudo cp yourconfig.json /usr/local/etc/xray/config.json + sudo systemctl start xray +or + sudo xray run -c yourconfig.json" + exit 0 + else + # config generation is requested + conf + fi + # + echo -e "Add other users? (Y/n)" + read answer + if [ -v $answer ] || [ ${answer::1} != "n" ] + then + echo -e "Enter usernames separated by spaces" + read usernames + add $usernames + fi + # + echo -e "Copy config to xray's dir and restart xray? (Y/n)" + read answer + if [ -v $answer ] || [ ${answer::1} != "n" ] + then + push + fi + +elif [ $command = "conf" ] +then + conf + # + echo -e "Add other users? (Y/n)" + read answer + if [ -v $answer ] || [ ${answer::1} != "n" ] + then + echo -e "Enter usernames separated by spaces" + read usernames + add $usernames + fi + # + echo -e "Copy config to xray's dir and restart xray? (Y/n)" + read answer + if [ -v $answer ] || [ ${answer::1} != "n" ] + then + push fi elif [ $command = "add" ] then - echo -e "${bold}add${normal}" - if ! $(command -v xray > /dev/null) + add "${@:2}" + # + echo -e "Copy config to xray's dir and restart xray? (Y/n)" + read answer + if [ -v $answer ] || [ ${answer::1} != "n" ] then - echo -e "${red}xray needed but not installed${normal}" - exit 1 - fi - if ! $jq_installed - then - echo -e "${red}jq needed but not installed${normal}" - exit 1 - fi - if [ ! -f "config_client.json" ] || [ ! -r "config_server.json" ] - then - echo -e "${red}server config and config for default user are needed but not present, -first generate them with ${normal}install${red} command.${normal}" - exit 1 - fi - if [ -v $2 ] - then - echo -e "${red}username not set${normal} -For default user, use config_client.json generated -by ${underl}install${normal} command. Otherwise use non-void username, -preferably of letters and digits only." - exit 1 - else - username=$2 - fi - configs=(config_client_*.json) - username_exists=false - for c in ${configs[@]} - do - if [ -f $c ] - then - email=$(jq '.outbounds[0].settings.vnext[0].users[0].email' $c) - name=$(echo $email | cut -d "@" -f 1 | cut -c 2-) - if [ $username = $name ] - then - username_exists=true - fi - fi - done - if $username_exists - then - echo -e "${red}username ${username} already exists, try another one${normal}" - exit 1 - else - id=$(xray uuid) # generate random uuid for vless - # generate random short_id for grpc-reality - if command -v openssl > /dev/null - then - short_id=$(openssl rand -hex 8) - else - echo -e "Enter a random (up to) 16-digit hex number, -containing only digits 0-9 and letters a-f, for instance -1234567890abcdef" - read short_id - if [ -v $short_id ] - then - echo -e "${red}short id not set${normal}" - exit 1 - fi - fi - # make new user config from default user config - cat config_client.json | jq ".outbounds[0].settings.vnext[0].users[0].id=\"${id}\" | .outbounds[0].settings.vnext[0].users[0].email=\"${username}@example.com\" | .outbounds[0].streamSettings.realitySettings.shortId=\"${short_id}\"" > config_client_${username}.json - # then make the user (not root) an owner of a file - [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" config_client.json - # update server config - client=" - { - \"id\": \"${id}\", - \"email\": \"${username}@example.com\", - \"flow\": \"xtls-rprx-vision\" - } - " - cp config_server.json config_server.json.backup - # update server config - cat config_server.json.backup | jq ".inbounds[0].settings.clients += [${client}] | .inbounds[0].streamSettings.realitySettings.shortIds += [\"${short_id}\"]" > config_server.json - # then make the user (not root) an owner of a file - [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" config_server.json - echo -e "${green}config_client_${username}.json is written, -config_server.json is updated${normal}" + push fi elif [ $command = "del" ] then - echo -e "${bold}del${normal}" - if ! $jq_installed - then - echo -e "${red}jq needed but not installed${normal}" - exit 1 - fi if [ -v $2 ] then - echo -e "${red}username not set${normal}" + echo -e "${red}usernames not set${normal}" exit 1 - else - username=$2 fi - config="config_client_${username}.json" - if [ ! -f $config ] + check_command jq "needed for operations with configs" + if [ ! -f "./conf/config_server.json" ] then - echo -e "${red}no config for user ${username}${normal}" + echo -e "${red}server config not found" exit 1 fi - short_id=$(jq ".outbounds[0].streamSettings.realitySettings.shortId" $config) - cp config_server.json config_server.json.backup - # update server config - cat config_server.json.backup | jq "del(.inbounds[0].settings.clients[] | select(.email == \"${username}@example.com\")) | del(.inbounds[0].streamSettings.realitySettings.shortIds[] | select(. == ${short_id}))" > config_server.json - # then make the user (not root) an owner of a file - [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" config_server.json - rm config_client_${username}.json - echo -e "${green}config_client_${username}.json is deleted, + # backup server config + cp_to_backup ./conf/config_server.json + # loop over usernames + for username in "${@:2}" + do + config="./conf/config_client_${username}.json" + if [ ! -f $config ] + then + echo -e "${yellow}no config for user ${username}${normal}" + else + short_id=$(jq ".outbounds[0].streamSettings.realitySettings.shortId" $config) + cp ./conf/config_server.json ./conf/tmpconfig.json + # update server config + ok1=$(cat ./conf/tmpconfig.json | jq "del(.inbounds[1].settings.clients[] | select(.email == \"${username}@example.com\")) | del(.inbounds[1].streamSettings.realitySettings.shortIds[] | select(. == ${short_id}))" > ./conf/config_server.json) + rm ./conf/tmpconfig.json + # then make the user (not root) an owner of a file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ./conf/config_server.json + ok2=$(rm ./conf/config_client_${username}.json) + if $ok1 && $ok2 + then + echo -e "${green}config_client_${username}.json is deleted, config_server.json is updated${normal}" + else + echo -e "${red}something went wrong with username ${username}${normal}" + fi + fi + done + echo -e "Copy config to xray's dir and restart xray? (Y/n)" + read answer + if [ -v $answer ] || [ ${answer::1} != "n" ] + then + push + fi + +elif [ $command = "push" ] +then + push + +elif [ $command = "link" ] +then + conf_file=$2 + if [ -v $conf_file ] + then + echo -e "${red}no config is given${normal}" + exit 1 + fi + id=$(strip_quotes $(jq ".outbounds[0].settings.vnext[0].users[0].id" $conf_file)) + address=$(strip_quotes $(jq ".outbounds[0].settings.vnext[0].address" $conf_file)) + port=$(jq ".outbounds[0].settings.vnext[0].port" $conf_file) + public_key=$(strip_quotes $(jq ".outbounds[0].streamSettings.realitySettings.publicKey" $conf_file)) + server_name=$(strip_quotes $(jq ".outbounds[0].streamSettings.realitySettings.serverName" $conf_file)) + short_id=$(strip_quotes $(jq ".outbounds[0].streamSettings.realitySettings.shortId" $conf_file)) + link="vless://${id}@${address}:${port}?fragment=&security=reality&encryption=none&pbk=${public_key}&fp=chrome&type=tcp&flow=xtls-rprx-vision&sni=${server_name}&sid=${short_id}#easy-xray+%F0%9F%97%BD" + echo -e "${yellow}don't forget to share misc/customgeo4hiddify.txt as well +${green}here is your link:${normal}" + echo $link + +elif [ $command = "stats" ] +then + client_stats_proxy_down=$(xray api stats -server=127.0.0.1:8080 -name "outbound>>>proxy>>>traffic>>>downlink" 2> /dev/null) + server_stats_direct_down=$(xray api stats -server=127.0.0.1:8080 -name "outbound>>>direct>>>traffic>>>downlink" 2> /dev/null) + if [ ! -z "$client_stats_proxy_down" ] # output is not a zero string, hence script is running on a client + then + ## Client statistics ## + echo "Downloaded via server: $(echo $client_stats_proxy_down | pretty_stats)" + # + client_stats_proxy_up=$(xray api stats -server=127.0.0.1:8080 -name "outbound>>>proxy>>>traffic>>>uplink" 2> /dev/null) + echo "Uploaded via server: $(echo $client_stats_proxy_up | pretty_stats)" + # + client_stats_direct_down=$(xray api stats -server=127.0.0.1:8080 -name "outbound>>>direct>>>traffic>>>downlink" 2> /dev/null) + echo "Downloaded via client directly: $(echo $client_stats_direct_down | pretty_stats)" + # + client_stats_direct_up=$(xray api stats -server=127.0.0.1:8080 -name "outbound>>>direct>>>traffic>>>uplink" 2> /dev/null) + echo "Uploaded via client directly: $(echo $client_stats_direct_up | pretty_stats)" + elif [ ! -z "$server_stats_direct_down" ] # output is not a zero string, hence script is running on a server + then + ## Server statistics ## + echo "Downloaded in total: $(echo $server_stats_direct_down | pretty_stats)" + # + server_stats_direct_up=$(xray api stats -server=127.0.0.1:8080 -name "outbound>>>direct>>>traffic>>>uplink" 2> /dev/null) + echo "Uploaded in total: $(echo $server_stats_direct_up | pretty_stats)" + # + # Per user statistics + conf_file="./conf/config_server.json" # assuming xray is running with this config + qemails=$(cat $conf_file | jq ".inbounds[1].settings.clients[].email") + for qemail in ${qemails[@]} + do + echo "" + email=$(strip_quotes $qemail) + user_stats_down=$(xray api stats -server=127.0.0.1:8080 -name "user>>>${email}>>>traffic>>>downlink" 2> /dev/null) + echo "Downloaded by ${email}: $(echo $user_stats_down | pretty_stats)" + user_stats_up=$(xray api stats -server=127.0.0.1:8080 -name "user>>>${email}>>>traffic>>>uplink" 2> /dev/null) + echo "Uploaded by ${email}: $(echo $user_stats_up | pretty_stats)" + done + else + echo -e "${red}xray should be running to aquire or reset statistics${normal}" + exit 1 + fi + # + if [ ! -v $2 ] && [ $2 = "reset" ] + then + echo "" + xray api statsquery -server=127.0.0.1:8080 -reset > /dev/null \ + && echo -e "${green}statistics reset successfully${normal}" \ + || echo -e "${red}statistics reset failed${normal}" + fi + +elif [ $command = "import" ] +then + if [ -v $2 ] || [ -v $3 ] + then + echo -e "${red}both directories (from and to) should be set${normal}" + exit 1 + fi + from=$2 + to=$3 + # backup the server and the main client configs + cp_to_backup ${to}/config_server.json + # + configs="${from}/config_client_*.json" + for c in $configs + do + if [ -f $c ] + then + uname_with_json=$(echo $c | cut -d "_" -f 3-) # remove "config_client_" + uname_from_filename=${uname_with_json:: -5} # remove ".json" + email=$(strip_quotes $(jq ".outbounds[0].settings.vnext[0].users[0].email" $c)) + uname_from_email=${email%@*} # remove "@example.com" + if [ $uname_from_filename != $uname_from_email ] + then + echo -e "${yellow}username ${uname_from_filename} (from filename) inconsistent with username ${uname_from_email} (from email), + continue with name from email${normal}" + fi + if [ -f ${to}/config_client_${uname_from_email}.json ] # username already exists + then + echo -e "${yellow}username ${uname_from_email} already exists in ${to}, no new config created fot it${normal}" + else + id=$(strip_quotes $(jq ".outbounds[0].settings.vnext[0].users[0].id" $c)) + short_id=$(strip_quotes $(jq ".outbounds[0].streamSettings.realitySettings.shortId" $c)) + # make new user config + ok1=$(cat ${to}/config_client.json | jq ".outbounds[0].settings.vnext[0].users[0].id=\"${id}\" | .outbounds[0].settings.vnext[0].users[0].email=\"${uname_from_email}@example.com\" | .outbounds[0].streamSettings.realitySettings.shortId=\"${short_id}\"" > ${to}/config_client_${uname_from_email}.json) + # then make the user (not root) an owner of a file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ${to}/config_client_${uname_from_email}.json + # update server config + client=" + { + \"id\": \"${id}\", + \"email\": \"${uname_from_email}@example.com\", + \"flow\": \"xtls-rprx-vision\" + } + " + cp ${to}/config_server.json ${to}/tmpconfig.json + ok2=$(cat ${to}/tmpconfig.json | jq ".inbounds[1].settings.clients += [${client}] | .inbounds[1].streamSettings.realitySettings.shortIds += [\"${short_id}\"]" > ${to}/config_server.json) + rm ${to}/tmpconfig.json + # then make the user (not root) an owner of a file + [[ $SUDO_USER ]] && chown "$SUDO_USER:$SUDO_USER" ${to}/config_server.json + if $ok1 && $ok2 + then + echo -e "${green}${to}/config_client_${uname_from_email}.json is written, ${to}/config_server.json is updated${normal}" + else + echo -e "${yellow}something went wrong with username ${uname_from_email}${normal}" + fi + fi + fi + done elif [ $command = "upgrade" ] then @@ -336,57 +669,52 @@ then elif [ $command = "remove" ] then echo -e "Remove xray? (y/N)" - read answer_rx - if [ ! -v $answer_rx ] && ([ $(echo $answer_rx | cut -c 1) = "y" ] || [ $(echo $answer_rx | cut -c 1) = "Y" ]) + read answer + if [ ! -v $answer ] && [ ${answer::1} = "y" ] then - echo -e "${red}Please type YES to remove${normal}" - read answer_y - if [ ! -v $answer_y ] && ([ $answer_y = "YES" ] || [ $answer_y = "yes" ]) + if [ $(id -u) -ne 0 ] # not root then - if $is_root - then - if bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ remove --purge - then - echo -e "${green}xray removed${normal}" - else - echo -e "${red}xray not removed${normal}" - fi - else - echo -e "${red}You should be root, or run this script with sudo -to remove xray${normal}" - exit 1 - fi + echo -e "${red}you should have root privileges for that, try +sudo ./ex.sh push${normal}" + exit 1 + fi + if bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ remove --purge + then + echo -e "${green}xray removed${normal}" + else + echo -e "${red}xray not removed${normal}" fi fi else # "help", default echo -e " -${green}**** Hi, there! How to use: ****${normal} +${green}**** Hi, there! ****${normal} + +The aim of ${italic}easy-xray${normal} is to help to administrate an xray server. +It's all about ${italic}conf${normal} directory that contains the server config +${italic}config_server.json${normal}, the main client config ${italic}config_client.json${normal} and +configs for other users ${italic}config_client_*.json${normal}. How to use it: ${bold}./ex.sh ${underl}command${normal} -Here is the list of all available commands: +Here is a list of all the commands available: ${bold}help${normal} show this message (default) - ${bold}install${normal} run interactive prompt, which asks to download and install - XRay and generate configs for server and client - ${bold}add ${underl}username${normal} add user with (any, fake) username to configs - ${bold}del ${underl}username${normal} delete user with given username from configs + ${bold}install${normal} run interactive prompt, that asks to download and + install XRay, and to generate configs + ${bold}conf${normal} generate config files for server and clients + ${bold}add ${underl}usernames${normal} add users with given usernames to configs, + usernames should by separated by spaces + ${bold}del ${underl}usernames${normal} delete users with given usernames from configs + ${bold}push${normal} copy config to xray's dir and restart xray + ${bold}link ${underl}config${normal} convert user config to a link acceptable by + client applications such as Hiddify or V2ray + ${bold}stats${normal} print some traffic statistics + ${bold}stats reset${normal} print statistics then set them to zero + ${bold}import ${underl}from${normal} ${underl}to${normal} import users from one directory that contains + user configs to another directory that contains + server config and the main client config ${bold}upgrade${normal} upgrade xray, do not touch configs ${bold}remove${normal} remove xray" fi -echo -e " -Command is done. - -${bold}Important:${normal} It is assumed that configs are stored and updated -locally as config_server.json, config_client.json or -config_client_username.json files. You should manually -start XRay with one of configs, depending -which role - server or client - XRay should play: - sudo cp config_(role).json /usr/local/etc/xray/config.json - sudo systemctl start xray -or - sudo xray run -c config_(role).json -" - diff --git a/figs/browser-proxy-settings.png b/figs/browser-proxy-settings.png new file mode 100644 index 0000000..6901bd4 Binary files /dev/null and b/figs/browser-proxy-settings.png differ diff --git a/figs/hiddify-1.jpg b/figs/hiddify-1.jpg new file mode 100644 index 0000000..151d95e Binary files /dev/null and b/figs/hiddify-1.jpg differ diff --git a/figs/hiddify-2.jpg b/figs/hiddify-2.jpg new file mode 100644 index 0000000..9cbbe38 Binary files /dev/null and b/figs/hiddify-2.jpg differ diff --git a/figs/hiddify-3.jpg b/figs/hiddify-3.jpg new file mode 100644 index 0000000..61d3476 Binary files /dev/null and b/figs/hiddify-3.jpg differ diff --git a/figs/hiddify-4.jpg b/figs/hiddify-4.jpg new file mode 100644 index 0000000..89484a7 Binary files /dev/null and b/figs/hiddify-4.jpg differ diff --git a/figs/nekoray-1.jpg b/figs/nekoray-1.jpg new file mode 100644 index 0000000..395a2ab Binary files /dev/null and b/figs/nekoray-1.jpg differ diff --git a/figs/nekoray-2.jpg b/figs/nekoray-2.jpg new file mode 100644 index 0000000..3619f2a Binary files /dev/null and b/figs/nekoray-2.jpg differ diff --git a/figs/nekoray-3.jpg b/figs/nekoray-3.jpg new file mode 100644 index 0000000..ae4a5cf Binary files /dev/null and b/figs/nekoray-3.jpg differ diff --git a/figs/nekoray-5.jpg b/figs/nekoray-5.jpg new file mode 100644 index 0000000..36a07e1 Binary files /dev/null and b/figs/nekoray-5.jpg differ diff --git a/figs/nekoray-6.jpg b/figs/nekoray-6.jpg new file mode 100644 index 0000000..131309b Binary files /dev/null and b/figs/nekoray-6.jpg differ diff --git a/figs/v2ray-1.jpg b/figs/v2ray-1.jpg new file mode 100644 index 0000000..a1161fd Binary files /dev/null and b/figs/v2ray-1.jpg differ diff --git a/figs/v2ray-2.jpg b/figs/v2ray-2.jpg new file mode 100644 index 0000000..580b36e Binary files /dev/null and b/figs/v2ray-2.jpg differ diff --git a/figs/v2ray-3.jpg b/figs/v2ray-3.jpg new file mode 100644 index 0000000..f043357 Binary files /dev/null and b/figs/v2ray-3.jpg differ diff --git a/figs/xray-schematic.png b/figs/xray-schematic.png new file mode 100644 index 0000000..e14bc4d Binary files /dev/null and b/figs/xray-schematic.png differ diff --git a/misc/customgeo4hiddify.txt b/misc/customgeo4hiddify.txt new file mode 100644 index 0000000..5151aea --- /dev/null +++ b/misc/customgeo4hiddify.txt @@ -0,0 +1,89 @@ +geosite:cn, +domain:cn, +domain:xn--fiqs8s, +domain:xn--fiqz9s, +domain:xn--55qx5d, +domain:xn--io0a7i, +domain:ru, +domain:xn--p1ai, +domain:by, +domain:xn--90ais, +domain:ir, +domain:habr.com, +domain:habrastorage.org, +domain:lenta.com, +domain:gldn.net, +domain:yastatic.net, +domain:yandex.net, +domain:mycdn.me, +domain:avito.st, +domain:gismeteo.st, +domain:gismeteo.net, +domain:ozonusercontent.com, +domain:mradx.net, +domain:aestatic.net, +domain:pushwoosh.com, +domain:vk.com, +domain:userapi.com, +domain:otm-r.com, +domain:2gis.com, +domain:championat.com, +domain:rus-tv.su, +domain:kpcdn.net, +domain:kp.house, +domain:kp.kg, +domain:kaspersky-labs.com, +domain:yadro.com, +domain:boosty.to, +domain:eaglecdn.com, +domain:livejournal.com, +domain:livejournal.net, +domain:edc.sale, +domain:stellarlabs.ai, +domain:myminsk.com, +domain:sputniknews.com, +domain:sputnikglobe.com, +domain:digikala.com, +domain:bale.ai, +domain:qq.com, +domain:gtimg.com, +domain:zhihu.com, +domain:geetest.com, +domain:zhimg.com, +domain:126.net, +domain:163.com, +domain:25ku.com, +domain:sohu.com, +domain:bcebos.com, +domain:ifengimg.com, +domain:taobao.com, +domain:bdstatic.com, +domain:ad-survey.com, +domain:ifeng.com, +domain:alicdn.com, +domain:jd.com, +domain:aliapp.org, +domain:aliyun.com, +domain:iqiyi.com, +domain:mgtv.com, +domain:iqiyipic.com, +domain:hdslb.com, +domain:360buyimg.com, +domain:bilibili.com, +domain:alibaba.com, +domain:xiami.com, +domain:yinyuetai.com, +domain:ctrip.com, +domain:tripcdn.com, +domain:c-ctrip.com, +domain:fliggy.com, +domain:qyerstatic.com, +domain:baidu.com, +domain:mafengwo.net, +domain:meituan.com, +domain:dianping.com, +domain:bdimg.com, +domain:chuimg.com, +domain:autonavi.com, +domain:amap.com, +domain:mmstat.com, diff --git a/misc/customgeo4nekoray.txt b/misc/customgeo4nekoray.txt new file mode 100644 index 0000000..3fdab3e --- /dev/null +++ b/misc/customgeo4nekoray.txt @@ -0,0 +1,89 @@ +geosite:cn +domain:cn +domain:xn--fiqs8s +domain:xn--fiqz9s +domain:xn--55qx5d +domain:xn--io0a7i +domain:ru +domain:xn--p1ai +domain:by +domain:xn--90ais +domain:ir +domain:habr.com +domain:habrastorage.org +domain:lenta.com +domain:gldn.net +domain:yastatic.net +domain:yandex.net +domain:mycdn.me +domain:avito.st +domain:gismeteo.st +domain:gismeteo.net +domain:ozonusercontent.com +domain:mradx.net +domain:aestatic.net +domain:pushwoosh.com +domain:vk.com +domain:userapi.com +domain:otm-r.com +domain:2gis.com +domain:championat.com +domain:rus-tv.su +domain:kpcdn.net +domain:kp.house +domain:kp.kg +domain:kaspersky-labs.com +domain:yadro.com +domain:boosty.to +domain:eaglecdn.com +domain:livejournal.com +domain:livejournal.net +domain:edc.sale +domain:stellarlabs.ai +domain:myminsk.com +domain:sputniknews.com +domain:sputnikglobe.com +domain:digikala.com +domain:bale.ai +domain:qq.com +domain:gtimg.com +domain:zhihu.com +domain:geetest.com +domain:zhimg.com +domain:126.net +domain:163.com +domain:25ku.com +domain:sohu.com +domain:bcebos.com +domain:ifengimg.com +domain:taobao.com +domain:bdstatic.com +domain:ad-survey.com +domain:ifeng.com +domain:alicdn.com +domain:jd.com +domain:aliapp.org +domain:aliyun.com +domain:iqiyi.com +domain:mgtv.com +domain:iqiyipic.com +domain:hdslb.com +domain:360buyimg.com +domain:bilibili.com +domain:alibaba.com +domain:xiami.com +domain:yinyuetai.com +domain:ctrip.com +domain:tripcdn.com +domain:c-ctrip.com +domain:fliggy.com +domain:qyerstatic.com +domain:baidu.com +domain:mafengwo.net +domain:meituan.com +domain:dianping.com +domain:bdimg.com +domain:chuimg.com +domain:autonavi.com +domain:amap.com +domain:mmstat.com diff --git a/template_config_client.jsonc b/template_config_client.jsonc index c2b2a8c..d9f1f24 100644 --- a/template_config_client.jsonc +++ b/template_config_client.jsonc @@ -7,8 +7,42 @@ "loglevel": "warning", "dnsLog": false }, + // Turns on traffic statistics, see https://xtls.github.io/en/config/stats.html#statsobject + // and https://xtls.github.io/en/config/policy.html#policyobject + // and special "api" tag below + "stats": { + }, + "policy": { + "levels": { + // default level + "0": { + "statsUserUplink": true, + "statsUserDownlink": true + } + }, + "system": { + "statsOutboundUplink": true, + "statsOutboundDownlink": true + } + }, + // enables API interface https://xtls.github.io/en/config/api.html#apiobject + "api": { + "tag": "api", + "services": [ "StatsService" ] + }, // client-side inbound configuration "inbounds": [ + // gRPC API inbound, used to get statistics + { + "listen": "127.0.0.1", + "port": 8080, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "api" + }, + // socks proxy { "tag": "socks", "port": 800, @@ -20,14 +54,16 @@ "destOverride": [ "http", "tls" - ] + ], + "routeOnly": true }, - // settings of inbound `protocol` (see above) + // settings of inbound "protocol" (see above) "settings": { "auth": "noauth", "udp": true } }, + // http/https proxy { "tag": "http", "port": 801, @@ -40,9 +76,9 @@ "http", "tls" ], - "routeOnly": false + "routeOnly": true }, - // settings of inbound `protocol` (see above) + // settings of inbound "protocol" (see above) "settings": { "auth": "noauth", "udp": true @@ -105,6 +141,13 @@ "routing": { "domainStrategy": "AsIs", "rules": [ + { + "type": "field", + "inboundTag": [ + "api" + ], + "outboundTag": "api" + }, { "type": "field", "domain": [ diff --git a/template_config_server.jsonc b/template_config_server.jsonc index 0d540fe..8fb66be 100644 --- a/template_config_server.jsonc +++ b/template_config_server.jsonc @@ -7,11 +7,41 @@ "loglevel": "warning", "dnsLog": false }, + // Turns on traffic statistics, see https://xtls.github.io/en/config/stats.html#statsobject + // and https://xtls.github.io/en/config/policy.html#policyobject + // and special "api" tag below + "stats": { + }, + "policy": { + "levels": { + // default level + "0": { + "statsUserUplink": true, + "statsUserDownlink": true + } + }, + "system": { + "statsOutboundUplink": true, + "statsOutboundDownlink": true + } + }, + // enables API interface https://xtls.github.io/en/config/api.html#apiobject + "api": { + "tag": "api", + "services": [ "StatsService" ] + }, // Forward each inbound connections to corresponding `outboundTag`. If no rules match, // the traffic is sent out by the first outbound in `outbounds` section. "routing": { "domainStrategy": "IPIfNonMatch", "rules": [ + { + "type": "field", + "inboundTag": [ + "api" + ], + "outboundTag": "api" + }, { "type": "field", "port": "80", @@ -64,6 +94,16 @@ }, // server-side inbound configuration "inbounds": [ + // gRPC API inbound, used to get statistics + { + "listen": "127.0.0.1", + "port": 8080, + "protocol": "dokodemo-door", + "settings": { + "address": "127.0.0.1" + }, + "tag": "api" + }, // main inbound, clients connect to it { "listen": "0.0.0.0", @@ -117,9 +157,9 @@ "enabled": true, "destOverride": [ "http", - "tls", - "quic" - ] + "tls" + ], + "routeOnly": true } }, // extra inbound; its main purpose is to get fallback to "dest" at port 80. Many regular websites @@ -176,9 +216,9 @@ "enabled": true, "destOverride": [ "http", - "tls", - "quic" - ] + "tls" + ], + "routeOnly": true } } ],