Контейнер с VPN для Mikrotik, претендующий на универсальность

Знаете, скажем так, прошлая моя попытка к чему-то подобному (xray/tunsocks/byedpi) ни к чему рабочему не привела, особенно проблемно когда всё это настраиваешь в первый раз, да ещё и приходится на микротике, а не на нормальном сервере с линуксом.

Тут припёрло, пошел искать опять информацию, ну и наткнулся на эту тему, где очень подробная инструкция. И она сработала. И теперь стало хоть понятно, кто что где и зачем :slight_smile:

Ваш докерхаб тоже попадался, может и где-то темы где-то тоже попадались, но вот решил остановиться и попробовать эту объемную, где всё расписано уж совсем для дураков (для меня).

Если нравится xray, в режиме туннеля будет более быстрым сборка с тегом wiktorbgu/vless-hev-socks5-tunnel-mikrotik:redirect

Т.е. просто создаю контейнер с переменными, вешаю на него интерфейс и на него делаю маршут для необходимых адресов/DNS?

согласен, в чем-то разобраться и сделать самому всегда приятно =)

Да, просто роутите на шлюз т.е. на контейнер, туннель там уже везде встроен и плюсом где-то есть socks, в описании есть информация

Рад что кому-то пригодилось. Есть даже немного долполнить)
В последнее время ГРЦЧ начали как-то шибко задорно ломать траффик, в результате чего wireproxy стал достаточно регулярно и занимательно крашиться. В связи с этим добавил ещё несколько скриптов, которые пытаются автоматом чинить это безобразие. Первые два для микрота - мониторят логи на предмет ошибок wireguard и если их за минуту становится больше 7 - рестартят контейнер

XRAYmonitor
:local errlimit 7;
:global lasterrorXray;

:local currentBuf [ :toarray [ /log find where topics=wireguard,info ]];
:local buflen [ :len $currentBuf ]

:if ( :typeof $lasterrorXray = nil ) do={
	:set lasterrorXray ( $currentBuf->($buflen -1 ));
};
:local i 1;
while ( ($currentBuf->($buflen - $i ) != $lasterrorXray) and ($i < $buflen)) do={
			:set i ( $i +1 );
};
if (( $i > 1 ) and ($i < errlimit )) do={
	:log warning message="Xray PLUS $i errors for last minute";
};
if (( $i > $errlimit ) and ($i < $buflen)) do={
	/system/script/run restart_xray
	:log warning message="Xray PLUS container restarted";
};
set lasterrorXray ( $currentBuf->($buflen - 1));
restart_xray
/container stop  [/container/find comment="Xray-core PLUS"];
:while ([/container/find comment="Xray-core PLUS" status=stopping]) do={
	:delay 500ms
}
/container start  [/container/find comment="Xray-core PLUS"];

Ну и добавляем в шедуллер

/system schedule add interval=1m name=XRAYmonitor on-event=“/system/script/run XRAYmonitor” policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon start-time=startup

Ну и второй - на замену entrypoint.sh в контейнере

entrypoint.sh
#!/bin/busybox sh
#setting envs
export MYIP=$(ip -4 addr show dev eth0 | grep inet | awk -F' ' '{split($2, a, "/");print a[1]}') #because busybox needs some hacks
if hostname | grep -iq mikrotik; then MYHOST=$MYIP; else MYHOST=$(hostname); fi #if container hostname is not set, it will be router name
if [ ! "$WP_PORT" ]; then export WP_PORT=9998; fi #setting wireproxy port
if [ ! "$BDPI_PORT" ]; then export BDPI_PORT=9999; fi #setting byedpi port
if [ ! "$WG_WPIN_PRIVKEY" ]; then echo "$MYHOST: WG_WPIN_PRIVKEY is not set. Exiting"; exit 0 ; fi
if [ ! "$WG_WPIN_PUBKEY" ]; then echo "$MYHOST: WG_WPIN_PUBKEY is not set. Exiting"; exit 0 ; fi
if [ ! "$WG_BDPIIN_PRIVKEY" ]; then echo "$MYHOST: WG_BDPIIN_PRIVKEY is not set. Exiting"; exit 0 ; fi
if [ ! "$WG_BDPIIN_PUBKEY" ]; then echo "$MYHOST: WG_BDPIIN_PUBKEY is not set. Exiting"; exit 0 ; fi

#generating xray base conf from template
xray_conf_gen() {
	mkfifo /opt/config/00_base_dyn.json
	eval "cat <<EOF
$(cat /opt/xray-core/00_base.json)
EOF
" > /opt/config/00_base_dyn.json &
}
xray_conf_gen
#generating config for wireproxy with socks block
wireproxy_conf_gen() {
	mkfifo /opt/awg.conf
	eval "cat <<EOF
$(find /opt/config/ -maxdepth 1 -type f -name "*.conf" -exec cat {} \; -quit)
[Socks5]
BindAddress = $MYIP:$WP_PORT
EOF
" > /opt/awg.conf &
}
wireproxy_conf_gen
#starting all processes
log() { while read line; do echo "$MYHOST: ${1}: ${line}"; done; }
run_wireproxy() {
	if [ ! "$LOGFILTER" ]; then #wireproxy produce too much logs. We can filter them
		echo "Filеring logs for wireproxy disabled"
		/opt/wireproxy -c /opt/awg.conf 2>&1 | log "WireproxyAWG" &
	else
		/opt/wireproxy -c /opt/awg.conf 2>&1 | grep -Eiv "$LOGFILTER" | log "WireproxyAWG" &
	fi
}
run_wireproxy
/opt/ciadpi --ip $MYIP --port $BDPI_PORT $BDPI_ARGS 2>&1 | log "ByeDpi" &
/opt/xray-core/xray run -confdir /opt/config 2>&1 | log "Xray-core" &

while true; do
	sleep 5
	if [ `ps aux | grep wireproxy | grep -v grep | wc -l` -ne 1 ]; then
		pkill -f wireproxy
		wireproxy_conf_gen
		echo "FATAL! Restarted on crash" | log "WireproxyAWG"
		run_wireproxy
	fi
	if [ `ps aux | grep ciadpi | grep -v grep | wc -l` -ne 1 ]; then
		pkill -f ciadpi
		echo "FATAL! Restarted on crash" | log "ByeDpi"
		/opt/ciadpi --ip $MYIP --port $BDPI_PORT $BDPI_ARGS 2>&1 | log "ByeDpi" &
	fi
	if [ `ps aux | grep xray-core | grep -v grep | wc -l` -ne 1 ]; then
		pkill -f xray-core
		xray_conf_gen
		echo "FATAL! Restarted on crash" | log "Xray-core"
		
		/opt/xray-core/xray run -confdir /opt/config 2>&1 | log "Xray-core" &
	fi
done

Там соотв. добавлен мониторинг наличия всех трёх процессов и если какой-то падает - скрипт его рестартит. Пока тестирую, но вроде пока что всё самостоятельно и достаточно стабильно восстаёт из пепла в случае крашей.

у себя заметил, что иногда xray в контейнере падает. из-за этого, естественно, падает весь контейнер. Пока что просто в шедулере проверяю, запущен ли, и, если нет, запускаю. Посмотрим, насколько это будет часто происходить.

После обновления ROS на 7.20.2 сей велосипед перестал работать, т.к. внутри контейнера интерфейс теперь зовётся не eth0, а также, как и снаружи.

Соотв. в entrypoint.sh нужно поправить с

export MYIP=$(ip -4 addr show dev eth0 | grep inet | awk -F' ' '{split($2, a, "/");print a[1]}')

На

export MYIP=$(ip -4 addr show scope global | grep inet | awk -F' ' '{split($2, a, "/");print a[1]}')

Надо было сразу так сделать, но кто ж знал))

Исправленный скрипт для сборки контейнера

build.sh
#!/bin/bash
DISTR_XRAY="https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip"
#DISTR_XRAY="https://github.com/XTLS/Xray-core/releases/download/v24.10.31/Xray-linux-arm64-v8a.zip"
DISTR_BDPI="https://github.com/hufrea/byedpi/releases/download/v0.17.3/byedpi-17.3-aarch64.tar.gz"
DISTR_WIREPROXY_AWG="https://github.com/artem-russkikh/wireproxy-awg/releases/latest/download/wireproxy_linux_arm64.tar.gz"
GEOSITE_RU_BLOCKED="https://github.com/runetfreedom/russia-blocked-geosite/releases/latest/download/geosite-ru-only.dat"
GEOIP_RU="https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/geoip.dat"
#-------------------------------------------------
download() {
	if [ $download_enabled -eq 1 ]; then
		rm -rf temp/*
		wget ${1} -O temp/${2}
		case `echo ${2} | awk -F. '{print $NF}'` in 
			"zip")
				unzip temp/${2} -d temp/ ;;
			*)
				tar -xf temp/${2} -C temp/ ;;
		esac
	fi
}
precheck() {
	echo "1. Checking installation..."
	if [ ! `which docker` ]; then
		echo "Install docker first"
		exit 0;
	fi
	if [ ! `which wget` ]; then
		echo "Install wget first"
		exit 0;
	fi
	if [ ! `which unzip` ]; then
		echo "Install unzip first"
		exit 0;
	fi
	
	rm -rf temp prepare
	mkdir -p temp
	mkdir -p prepare/opt/xray-core/data
	mkdir -p prepare/opt/config/
	mkdir -p prepare/usr/share
	echo "Installation OK"
	download_enabled=1
}
#--------------------
xray_baseconf_gen() {
cat > prepare/opt/xray-core/00_base.json << EOF
{
  "log": 
  {
    "access": "none",
    "dnsLog": false,
    "loglevel": "warning"
  },
  "routing": 
  {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "type": "field",
        "ip": [
          "geoip:private",
          "192.168.88.0/24"
        ],
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "domain": "geosite:ru-available-only-inside",
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "protocol": "bittorrent",
        "outboundTag": "direct"
      },
      {
        "type": "field",
        "ip": "geoip:ru",
        "outboundTag": "bdpi"
      },
      {
        "type": "field",
        "inboundTag": "inbound-wg-bdpi:3124",
        "outboundTag": "bdpi"
      },
      {
        "type": "field",
        "inboundTag": "inbound-wg-awg:3125",
        "outboundTag": "awg"
      }
    ]
  },
  "inbounds": 
  [
    {
      "listen": "\$MYIP",
      "port": 3124,
      "protocol": "wireguard",
      "settings": {
        "mtu": 1420,
        "secretKey": "\$WG_BDPIIN_PRIVKEY",
        "peers": [
          {
            "publicKey": "\$WG_BDPIIN_PUBKEY",
            "allowedIPs": [
              "0.0.0.0/0",
              "::/0"
            ],
            "keepAlive": 0
          }
        ],
        "noKernelTun": true
      },
      "streamSettings": null,
      "tag": "inbound-wg-bdpi:3124",
      "sniffing": {
        "enabled": true,
        "destOverride": "fakedns+others",
        "metadataOnly": false
      }
    },
    {
      "listen": "\$MYIP",
      "port": 3125,
      "protocol": "wireguard",
      "settings": {
        "mtu": 1420,
        "secretKey": "\$WG_WPIN_PRIVKEY",
        "peers": [
          {
            "publicKey": "\$WG_WPIN_PUBKEY",
            "allowedIPs": [
              "0.0.0.0/0",
              "::/0"
            ],
            "keepAlive": 0
          }
        ],
        "noKernelTun": true
      },
      "streamSettings": null,
      "tag": "inbound-wg-awg:3125",
      "sniffing": {
        "enabled": true,
        "destOverride": "fakedns+others",
        "metadataOnly": false
      }
    }
  ],
  "outbounds": 
  [
    {
      "tag": "direct",
      "protocol": "freedom",
      "settings": {
        "domainStrategy": "AsIs"
      }
    },
    {
      "tag": "blocked",
      "protocol": "blackhole",
      "settings": {}
    },
    {
      "tag": "awg",
      "protocol": "socks",
      "settings": {
        "servers": [
          {
            "address": "\$MYIP",
            "port": \$WP_PORT
          }
        ]
      }
    },
    {
      "tag": "bdpi",
      "protocol": "socks",
      "settings": {
        "servers": [
          {
            "address": "\$MYIP",
            "port": \$BDPI_PORT
          }
        ]
      }
    }
  ]
}
EOF
}
xray_prepare() {
	echo "2. Preparing xray-core"
	download $DISTR_XRAY xray.zip
	mv temp/xray prepare/opt/xray-core/
	chmod +x prepare/opt/xray-core/xray
	cp geoip-ru.dat prepare/opt/xray-core/data/geoip.dat
	wget $GEOSITE_RU_BLOCKED -O prepare/opt/xray-core/data/geosite.dat
#	wget $GEOIP_RU -O prepare/opt/xray-core/data/geoip.dat
	
	wget 
	ln -s /opt/xray-core/data prepare/usr/share/xray
	if [ -e prepare/opt/xray-core/xray ]; then
		echo "xray-core OK"
	else
		echo "Unable to prepare xray-core. Exiting"
		exit 0
	fi
}
#--------------------
bdpi_prepare(){
	echo "3. Preparing byedpi"
	download $DISTR_BDPI bdpi.tar.gz
	tar -xf temp/bdpi.tar.gz -C temp/
	mv temp/ciadpi-aarch64 prepare/opt/ciadpi
	chmod +x prepare/opt/ciadpi
	if [ -e prepare/opt/ciadpi ]; then
		echo "byedpi OK"
	else
		echo "Unable to prepare byedpi. Exiting"
		exit 0
	fi
}
#--------------------
awg_prepare(){
	echo "4. Preparing AWG"
	download $DISTR_WIREPROXY_AWG awg.tar.gz
	mv temp/wireproxy prepare/opt/
	chmod +x prepare/opt/wireproxy
	if [ -e prepare/opt/wireproxy ]; then
		echo "AWG OK"
	else
		echo "Unable to prepare AWG. Exiting"
		exit 0
	fi
}
#--------------------
entrypoint_gen() {
cat > prepare/opt/entrypoint.sh << EOF2
#!/bin/busybox sh
#setting envs
export MYIP=\$(ip -4 addr show scope global | grep inet | awk -F' ' '{split(\$2, a, "/");print a[1]}') #because busybox needs some hacks
if hostname | grep -iq mikrotik; then MYHOST=\$MYIP; else MYHOST=\$(hostname); fi #if container hostname is not set, it will be router name
if [ ! "\$WP_PORT" ]; then export WP_PORT=9998; fi #setting wireproxy port
if [ ! "\$BDPI_PORT" ]; then export BDPI_PORT=9999; fi #setting byedpi port
if [ ! "\$WG_WPIN_PRIVKEY" ]; then echo "\$MYHOST: WG_WPIN_PRIVKEY is not set. Exiting"; exit 0 ; fi
if [ ! "\$WG_WPIN_PUBKEY" ]; then echo "\$MYHOST: WG_WPIN_PUBKEY is not set. Exiting"; exit 0 ; fi
if [ ! "\$WG_BDPIIN_PRIVKEY" ]; then echo "\$MYHOST: WG_BDPIIN_PRIVKEY is not set. Exiting"; exit 0 ; fi
if [ ! "\$WG_BDPIIN_PUBKEY" ]; then echo "\$MYHOST: WG_BDPIIN_PUBKEY is not set. Exiting"; exit 0 ; fi
#making container stop smoothly
trap 'echo wireproxy ciadpi xray | xargs killall; exit 0' SIGINT
trap 'echo wireproxy ciadpi xray | xargs killall; exit 0' SIGTERM
trap 'echo wireproxy ciadpi xray | xargs killall; exi t0' SIGHUP
#generating xray base conf from template
xray_conf_gen() {
	mkfifo /opt/config/00_base_dyn.json
	eval "cat <<EOF
\$(cat /opt/xray-core/00_base.json)
EOF
" > /opt/config/00_base_dyn.json &
}
xray_conf_gen
#generating config for wireproxy with socks block
wireproxy_conf_gen() {
	mkfifo /opt/awg.conf
	eval "cat <<EOF
\$(find /opt/config/ -maxdepth 1 -type f -name "*.conf" -exec cat {} \; -quit)
[Socks5]
BindAddress = \$MYIP:\$WP_PORT
EOF
" > /opt/awg.conf &
}
wireproxy_conf_gen
#starting all processes
log() { while read line; do echo "\$MYHOST: \${1}: \${line}"; done; }
run_wireproxy() {
	if [ ! "\$LOGFILTER" ]; then #wireproxy produce too much logs. We can filter them
		echo "Filеring logs for wireproxy disabled"
		/opt/wireproxy -c /opt/awg.conf 2>&1 | log "WireproxyAWG" &
	else
		/opt/wireproxy -c /opt/awg.conf 2>&1 | grep -Eiv "\$LOGFILTER" | log "WireproxyAWG" &
	fi
}
run_wireproxy
/opt/ciadpi --ip \$MYIP --port \$BDPI_PORT \$BDPI_ARGS 2>&1 | log "ByeDpi" &
/opt/xray-core/xray run -confdir /opt/config 2>&1 | log "Xray-core" &

while true; do
	sleep 5
	if [ \$(ps aux | grep wireproxy | grep -v grep | wc -l) -ne 1 ]; then
		pkill -f wireproxy
		wireproxy_conf_gen
		echo "FATAL! Restarted on crash" | log "WireproxyAWG"
		run_wireproxy
	fi
	if [ \$(ps aux | grep ciadpi | grep -v grep | wc -l) -ne 1 ]; then
		pkill -f ciadpi
		echo "FATAL! Restarted on crash" | log "ByeDpi"
		/opt/ciadpi --ip \$MYIP --port \$BDPI_PORT \$BDPI_ARGS 2>&1 | log "ByeDpi" &
	fi
	if [ \$(ps aux | grep xray-core | grep -v grep | wc -l) -ne 1 ]; then
		pkill -f xray-core
		xray_conf_gen
		echo "FATAL! Restarted on crash" | log "Xray-core"
		
		/opt/xray-core/xray run -confdir /opt/config 2>&1 | log "Xray-core" &
	fi
done
EOF2
}
dockerbuild () {
	echo "5. Building docker image"
	#cp -R /usr/share/ca-certificates prepare/usr/share
	#cp -R /usr/share/zoneinfo prepare/usr/share
	#echo 'FROM --platform=linux/arm64 alpine:3
	echo 'FROM --platform=linux/arm64 busybox:stable
WORKDIR /opt/xray-core/data
COPY ./prepare/ /
ENTRYPOINT ["/opt/entrypoint.sh"]
' > Dockerfile
	chmod +x prepare/opt/entrypoint.sh
	docker build --rm -t xray_full_mikro .
	docker save xray_full_mikro:latest -o xray_full_mikro.tar
	docker image rm xray_full_mikro:latest
}
precheck
xray_prepare
xray_baseconf_gen
bdpi_prepare
awg_prepare
entrypoint_gen
dockerbuild

И обновлённый предсобранный образ

UPD. Маленькое обновление. Добален мониторинг и авторестарт дочерних процессов, сделана нормальная остановка по SIGHUP, SIGINT, SIGTERM