diff --git a/app/bot.php b/app/bot.php index 55c5969..607e1ef 100644 --- a/app/bot.php +++ b/app/bot.php @@ -317,20 +317,23 @@ class Bot case preg_match('~^/logs$~', $this->input['callback'], $m): $this->logs(); break; - case preg_match('~^/iodine$~', $this->input['callback'], $m): - $this->iodine(); + case preg_match('~^/dnstt$~', $this->input['callback'], $m): + $this->dnstt(); break; - case preg_match('~^/iodineDomain$~', $this->input['callback'], $m): - $this->iodineDomain(); + case preg_match('~^/dnsttDownload$~', $this->input['callback'], $m): + $this->dnsttDownload(); break; - case preg_match('~^/iodinePassword$~', $this->input['callback'], $m): - $this->iodinePassword(); + case preg_match('~^/dnsttDomain$~', $this->input['callback'], $m): + $this->dnsttDomain(); break; - case preg_match('~^/setIodineDomain (\w+)$~', $this->input['callback'], $m): - $this->setIodineDomain($m[1]); + case preg_match('~^/dnsttPassword$~', $this->input['callback'], $m): + $this->dnsttPassword(); break; - case preg_match('~^/setIodinePassword (\w+)$~', $this->input['callback'], $m): - $this->setIodinePassword($m[1]); + case preg_match('~^/setdnsttDomain (\w+)$~', $this->input['callback'], $m): + $this->setdnsttDomain($m[1]); + break; + case preg_match('~^/setdnsttPassword (\w+)$~', $this->input['callback'], $m): + $this->setdnsttPassword($m[1]); break; case preg_match('~^/getLog (?P\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m): $this->getLog(...explode('_', $m['arg'])); @@ -1138,7 +1141,7 @@ class Bot $t = preg_replace('~^(\t+)?basic_auth[^\n]+~sm', '$1basic_auth ' . ($pac['naive']['user'] ?? '_') . ' ' . ($pac['naive']['pass'] ?? '__'), $c); file_put_contents('/config/Caddyfile', $t); if (!empty($pac['naive']['pass'])) { - $this->ssh('caddy run -c /config/Caddyfile > /dev/null 2>&1 &', 'np', false); + $this->ssh('caddy run -c /config/Caddyfile', 'np', false); } } @@ -2829,7 +2832,7 @@ DNS-over-HTTPS with IP: public function startAd() { - return $this->ssh('/opt/adguardhome/AdGuardHome --no-check-update --pidfile /opt/adguardhome/pid -c /config/AdGuardHome.yaml -h 0.0.0.0 -w /opt/adguardhome/work > /dev/null 2>&1 &', 'ad', false); + return $this->ssh('/opt/adguardhome/AdGuardHome --no-check-update --pidfile /opt/adguardhome/pid -c /config/AdGuardHome.yaml -h 0.0.0.0 -w /opt/adguardhome/work', 'ad', false); } public function stopAd() @@ -4540,7 +4543,7 @@ DNS-over-HTTPS with IP: return implode("\n", $result); } - public function iodineDomain() + public function dnsttDomain() { $r = $this->send( $this->input['chat'], @@ -4550,12 +4553,12 @@ DNS-over-HTTPS with IP: ); $_SESSION['reply'][$r['result']['message_id']] = [ 'start_message' => $this->input['message_id'], - 'callback' => 'setIodineDomain', + 'callback' => 'setdnsttDomain', 'args' => [], ]; } - public function iodinePassword() + public function dnsttPassword() { $r = $this->send( $this->input['chat'], @@ -4565,63 +4568,82 @@ DNS-over-HTTPS with IP: ); $_SESSION['reply'][$r['result']['message_id']] = [ 'start_message' => $this->input['message_id'], - 'callback' => 'setIodinePassword', + 'callback' => 'setdnsttPassword', 'args' => [], ]; } - public function setIodinePassword($text) + public function setdnsttPassword($text) { $c = $this->getPacConf(); if ($text) { - $c['iodinePassword'] = $text; + $c['dnsttPassword'] = $text; } else { - unset($c['iodinePassword']); + unset($c['dnsttPassword']); } $this->setPacConf($c); - $this->iodineRestart(); - $this->iodine(); + $this->dnsttStart(); + $this->dnstt(); } - public function setIodineDomain($text) + public function setdnsttDomain($text) { $c = $this->getPacConf(); if ($text) { - $c['iodineDomain'] = $text; + $c['dnsttDomain'] = $text; } else { - unset($c['iodineDomain']); + unset($c['dnsttDomain']); } $this->setPacConf($c); - $this->iodineRestart(); - $this->iodine(); + $this->dnsttStart(); + $this->dnstt(); } - public function iodineRestart() + public function dnsttStart() { $c = $this->getPacConf(); - $this->ssh('pkill iodine', 'io'); - if (!empty($c['iodineDomain']) && !empty($c['iodinePassword'])) { - $this->ssh("iodined -c -P {$c['iodinePassword']} 10.0.0.1 {$c['iodineDomain']}", 'io'); + $this->ssh('pkill dnstt', 'dnstt'); + if (!empty($c['dnsttDomain']) && !empty($c['dnsttPassword'])) { + $this->ssh("adduser -D -s /bin/sh vpnbot", 'dnstt'); + $this->ssh("echo 'vpnbot:{$c['dnsttPassword']}' | chpasswd", 'dnstt'); + if (!file_exists('/config/dnstt/server.key')) { + $this->ssh("dnstt-server -gen-key -privkey-file /dnstt/server.key -pubkey-file /dnstt/server.pub", 'dnstt'); + } + $this->ssh("dnstt-server -udp :53 -privkey-file /dnstt/server.key {$c['dnsttDomain']} 127.0.0.1:22", 'dnstt' , false); } } - public function iodine() + public function dnsttDownload() + { + $this->sendFile($this->input['from'], curl_file_create('/config/dnstt/server.pub')); + } + + public function dnstt() { $c = $this->getPacConf(); - $text[] = 'Iodine'; - $text[] = "domain: {$c['iodineDomain']}"; - $text[] = "password: {$c['iodinePassword']}"; + $pubkey = file_get_contents('/config/dnstt/server.pub'); + $text[] = "dnstt"; + $text[] = "
set the NS record for {$c['dnsttDomain']}: tns.{$c['domain']}\nset A record for tns.{$c['domain']}: {$this->ip}
"; + $text[] = "domain: {$c['dnsttDomain']}"; + $text[] = "ssh: vpnbot:{$c['dnsttPassword']}"; + $text[] = "pubkey: $pubkey"; + $data[] = [ + [ + 'text' => $this->i18n('download pubkey'), + 'callback_data' => "/dnsttDownload", + ], + ]; $data[] = [ [ 'text' => $this->i18n('set domain'), - 'callback_data' => "/iodineDomain", + 'callback_data' => "/dnsttDomain", ], ]; $data[] = [ [ 'text' => $this->i18n('set password'), - 'callback_data' => "/iodinePassword", + 'callback_data' => "/dnsttPassword", ], ]; $data[] = [ @@ -4693,7 +4715,7 @@ DNS-over-HTTPS with IP: $this->i18n($this->ssh('pgrep mtproto-proxy', 'tg') ? 'on' : 'off') . ' ' . $this->i18n('mtproto'), $this->i18n(exec("JSON=1 timeout 2 dnslookup google.com ad") ? 'on' : 'off') . ' ' . $this->i18n('ad_title'), $this->i18n($this->ssh('pgrep ssserver', 'ss') ? 'on' : 'off') . ' ' . $this->i18n('sh_title'), - $this->i18n($this->ssh('pgrep iodine', 'io') ? 'on' : 'off') . ' ' . $this->i18n('Iodine'), + $this->i18n($this->ssh('pgrep dnstt', 'dnstt') ? 'on' : 'off') . ' ' . $this->i18n('dnstt'), $this->i18n($this->warpStatus()) . ' ' . $this->i18n('warp'), ], [ @@ -4705,7 +4727,7 @@ DNS-over-HTTPS with IP: $this->i18n($c['tg'] ? 'on' : 'off') . ' ' . getenv('TGPORT'), $this->i18n($c['ad'] ? 'on' : 'off') . ' 853', $this->i18n($c['ss'] ? 'on' : 'off') . ' ' . getenv('SSPORT'), - $this->i18n($c['io'] ? 'on' : 'off') . ' 53', + $this->i18n($c['dnstt'] ? 'on' : 'off') . ' 53', '', ], ]); @@ -4781,8 +4803,8 @@ DNS-over-HTTPS with IP: ], [ [ - 'text' => $this->i18n('Iodine'), - 'callback_data' => "/iodine", + 'text' => $this->i18n('dnstt'), + 'callback_data' => "/dnstt", ], ], [ @@ -8380,8 +8402,8 @@ DNS-over-HTTPS with IP: 'callback_data' => "/hidePort ss", ]], [[ - 'text' => $this->i18n($c['io'] ? 'on' : 'off') . ' 53 Iodine', - 'callback_data' => "/hidePort io", + 'text' => $this->i18n($c['dnstt'] ? 'on' : 'off') . ' 53 dnstt', + 'callback_data' => "/hidePort dnstt", ]], ]; if (!empty($pac['restart'])) { @@ -8414,7 +8436,7 @@ DNS-over-HTTPS with IP: 'tg' => getenv('TGPORT') . ':' . getenv('TGPORT'), 'ad' => '853:853', 'ss' => '8388:8388', - 'io' => '53:53/udp', + 'dnstt' => '53:53/udp', ]; $f = '/docker/compose'; $c = yaml_parse_file($f); @@ -9091,15 +9113,33 @@ DNS-over-HTTPS with IP: if (empty($a)) { throw new Exception("auth fail: \n$cmd\n" . var_export($a, true)); } + + // Оборачиваем команду для выполнения в фоновом режиме + if (!$wait) { + // nohup запускает процесс независимо от SSH-сессии + // & переносит процесс в фон + // /dev/null 2>&1 перенаправляет все потоки ввода-вывода + $cmd = "nohup $cmd /dev/null 2>&1 &"; + } + $s = ssh2_exec($c, $cmd); if (empty($s)) { throw new Exception("exec fail: \n$cmd\n" . var_export($s, true)); } - stream_set_blocking($s, $wait); + $data = ""; - while ($buf = fread($s, 4096)) { - $data .= $buf; + if ($wait) { + // Только для синхронных команд читаем вывод + stream_set_blocking($s, true); + while ($buf = fread($s, 4096)) { + $data .= $buf; + } + } else { + // Для фоновых команд просто даем время запуститься + stream_set_blocking($s, false); + usleep(100000); // 100ms для запуска процесса } + fclose($s); ssh2_disconnect($c); } catch (Exception | Error $e) { diff --git a/app/service.php b/app/service.php index cb578fa..d787a6a 100644 --- a/app/service.php +++ b/app/service.php @@ -24,4 +24,4 @@ $bot->adguardSync(); $bot->cloakNginx(); $bot->syncDeny(); $bot->cleanDocker(); -$bot->iodineRestart(); +$bot->dnsttStart(); diff --git a/config/sshd_config b/config/sshd_config index e5a5eca..76388d1 100644 --- a/config/sshd_config +++ b/config/sshd_config @@ -83,9 +83,9 @@ AuthorizedKeysFile .ssh/authorized_keys #AllowAgentForwarding yes # Feel free to re-enable these if your use case requires them. -AllowTcpForwarding no -GatewayPorts no -X11Forwarding no +# AllowTcpForwarding no +# GatewayPorts no +# X11Forwarding no #X11DisplayOffset 10 #X11UseLocalhost yes #PermitTTY yes @@ -117,4 +117,8 @@ Subsystem sftp internal-sftp # ForceCommand cvs server HostKeyAlgorithms +ssh-rsa PubkeyAcceptedKeyTypes +ssh-rsa -PasswordAuthentication no +PasswordAuthentication yes +AllowTcpForwarding yes +PermitTunnel yes +GatewayPorts yes +X11Forwarding yes \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2fc8007..2b2a298 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -211,6 +211,7 @@ services: - np - proxy - ss + - dnstt wg: image: mercurykd/vpnbot-wg:1.1 build: @@ -537,21 +538,20 @@ services: default: ipv4_address: 10.10.0.6 logging: *default-logging - io: - image: mercurykd/vpnbot-io:1.0 + dnstt: + image: mercurykd/vpnbot-dnstt:1.0 build: - dockerfile: dockerfile/iodine.dockerfile + dockerfile: dockerfile/dnstt.dockerfile + args: + image: ${IMAGE} volumes: - ./config/.profile:/root/.ashrc:ro - ./ssh:/ssh - ./config/sshd_config:/etc/ssh/sshd_config - - ./scripts/start_io.sh:/start_io.sh - hostname: iodine - cap_add: - - NET_ADMIN - devices: - - /dev/net/tun:/dev/net/tun - container_name: iodine-${VER} + - ./config/dnstt:/dnstt + - ./scripts/start_dnstt.sh:/start_dnstt.sh + hostname: dnstt + container_name: dnstt-${VER} depends_on: php: condition: service_healthy @@ -561,7 +561,7 @@ services: - path: ./override.env required: false stop_grace_period: 1s - command: ["/bin/sh", "/start_io.sh"] + command: ["/bin/sh", "/start_dnstt.sh"] networks: default: ipv4_address: 10.10.0.16 diff --git a/dockerfile/dnstt.dockerfile b/dockerfile/dnstt.dockerfile new file mode 100644 index 0000000..34d8781 --- /dev/null +++ b/dockerfile/dnstt.dockerfile @@ -0,0 +1,13 @@ +ARG image +FROM golang:alpine AS go +RUN apk add --no-cache git \ + && git clone https://www.bamsoftware.com/git/dnstt.git \ + && cd /go/dnstt/dnstt-server \ + && go build -ldflags="-s -w" -trimpath \ + && rm -rf /go/dnstt/.git \ + && apk del git +FROM $image +COPY --from=go /go/dnstt/dnstt-server/dnstt-server /usr/local/bin/ +RUN apk add --no-cache openssh iptables \ + && mkdir -p /root/.ssh +ENV ENV="/root/.ashrc" \ No newline at end of file diff --git a/dockerfile/iodine.dockerfile b/dockerfile/iodine.dockerfile deleted file mode 100644 index 0f999d7..0000000 --- a/dockerfile/iodine.dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM alpine:3.22 -RUN apk add iodine openssh iptables\ - && mkdir /root/.ssh -ENV ENV="/root/.ashrc" diff --git a/makefile b/makefile index 67cb519..da88850 100644 --- a/makefile +++ b/makefile @@ -37,8 +37,8 @@ proxy: # консоль сервиса docker compose exec proxy /bin/sh tg: # консоль сервиса docker compose exec tg /bin/sh -io: # консоль сервиса - docker compose exec io /bin/sh +dnstt: # консоль сервиса + docker compose exec dnstt /bin/sh xr: # консоль сервиса docker compose exec xr /bin/sh oc: # консоль сервиса diff --git a/scripts/start_dnstt.sh b/scripts/start_dnstt.sh new file mode 100644 index 0000000..3d56de1 --- /dev/null +++ b/scripts/start_dnstt.sh @@ -0,0 +1,4 @@ +cat /ssh/key.pub > /root/.ssh/authorized_keys +ssh-keygen -A +exec /usr/sbin/sshd -D -e "$@" & +tail -f /dev/null diff --git a/scripts/start_io.sh b/scripts/start_io.sh deleted file mode 100644 index cb44fe9..0000000 --- a/scripts/start_io.sh +++ /dev/null @@ -1,7 +0,0 @@ -cat /ssh/key.pub > /root/.ssh/authorized_keys -ssh-keygen -A -exec /usr/sbin/sshd -D -e "$@" & -iptables -A FORWARD -i dns0 -o eth0 -j ACCEPT -iptables -A FORWARD -i eth0 -o dns0 -m state --state RELATED,ESTABLISHED -j ACCEPT -iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE -tail -f /dev/null diff --git a/scripts/start_wp.sh b/scripts/start_wp.sh index 73293d1..8969add 100755 --- a/scripts/start_wp.sh +++ b/scripts/start_wp.sh @@ -1,6 +1,4 @@ cat /ssh/key.pub > /root/.ssh/authorized_keys -echo 'HostKeyAlgorithms +ssh-rsa' >> /etc/ssh/sshd_config -echo 'PubkeyAcceptedKeyTypes +ssh-rsa' >> /etc/ssh/sshd_config service ssh start off=$(cat /config/pac.json | jq -r .warpoff) key=$(cat /config/pac.json | jq -r .warp)