diff --git a/app/bot.php b/app/bot.php index 607e1ef..95a7125 100644 --- a/app/bot.php +++ b/app/bot.php @@ -143,7 +143,7 @@ class Bot case preg_match('~^/menu (?Paddpeer) (?P(?:-)?\d+)$~', $this->input['callback'], $m): case preg_match('~^/menu (?Pwg) (?P(?:-)?\d+)$~', $this->input['callback'], $m): case preg_match('~^/menu (?Pclient) (?P\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m): - case preg_match('~^/menu (?Ppac|adguard|config|ss|lang|oc|naive|mirror|update)$~', $this->input['callback'], $m): + case preg_match('~^/menu (?Ppac|adguard|config|ss|lang|oc|naive|mirror|update|hy)$~', $this->input['callback'], $m): $this->menu(type: $m['type'] ?? false, arg: $m['arg'] ?? false); break; case preg_match('~^/changeWG (\d+)$~', $this->input['callback'], $m): @@ -413,6 +413,9 @@ class Bot case preg_match('~^/changeNaivePass$~', $this->input['callback'], $m): $this->changeNaivePass(); break; + case preg_match('~^/changeHysteriaPass$~', $this->input['callback'], $m): + $this->changeHysteriaPass(); + break; case preg_match('~^/changeOcDns$~', $this->input['callback'], $m): $this->changeOcDns(); break; @@ -1051,6 +1054,22 @@ class Bot ]; } + public function changeHysteriaPass() + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} enter password", + $this->input['message_id'], + reply: 'enter password', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'start_callback' => $this->input['callback_id'], + 'callback' => 'chhypass', + 'args' => [], + ]; + } + public function addOcUser() { $r = $this->send( @@ -1145,6 +1164,18 @@ class Bot } } + public function restartHysteria() + { + $pac = $this->getPacConf(); + $this->ssh('pkill hysteria', 'hy'); + $c = yaml_parse_file('/config/hysteria.yaml'); + $c['auth']['password'] = $pac['hysteria_pass']; + yaml_emit_file('/config/hysteria.yaml', $c); + if (!empty($pac['hysteria_pass'])) { + $this->ssh('hysteria server -c /config/hysteria.yaml', 'hy', false); + } + } + public function chocdns($dns) { $c = file_get_contents('/config/ocserv.conf'); @@ -1199,6 +1230,15 @@ class Bot $this->menu('naive'); } + public function chhypass($pass) + { + $pac = $this->getPacConf(); + $pac['hysteria_pass'] = $pass; + $this->setPacConf($pac); + $this->restartHysteria(); + $this->menu('hy'); + } + public function chockey($pass) { $c = file_get_contents('/config/ocserv.conf'); @@ -1794,6 +1834,7 @@ class Bot 'mtproto' => file_get_contents('/config/mtprotosecret'), 'mtprotodomain' => file_get_contents('/config/mtprotodomain'), 'xray' => $this->getXray(), + 'hy' => yaml_parse_file('/config/hysteria.yaml'), 'oc' => file_get_contents('/config/ocserv.conf'), 'ocu' => file_get_contents('/config/ocserv.passwd'), 'ss' => $this->getSSConfig(), @@ -1931,9 +1972,17 @@ class Bot file_put_contents('/config/ocserv.passwd', $json['ocu']); $this->restartOcserv($json['oc']); } + // hysteria + if (!empty($json['hy'])) { + $out[] = 'update hysteria'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + yaml_emit_file('/config/hysteria.yaml', $json['hy']); + $this->restartHysteria(); + } if (!empty($json['pac']['domain'])) { $this->setUpstreamDomainOcserv($json['pac']['domain']); $this->setUpstreamDomainNaive($json['pac']['domain']); + $this->setUpstreamDomainHysteria($json['pac']['domain']); } // nginx $out[] = 'reset nginx'; @@ -2351,7 +2400,8 @@ class Bot $adguardClient = $conf['adguardkey'] ? "-d {$conf['adguardkey']}.{$conf['domain']}" : ''; $oc = $this->getHashSubdomain('oc'); $np = $this->getHashSubdomain('np'); - exec("certbot certonly --force-renew --preferred-chain 'ISRG Root X1' -n --agree-tos --email mail@{$conf['domain']} -d {$conf['domain']} -d $oc.{$conf['domain']} -d $np.{$conf['domain']} $adguardClient --webroot -w /certs/ --logs-dir /logs --max-log-backups 0 2>&1", $out, $code); + $hy = $this->getHashSubdomain('hy'); + exec("certbot certonly --force-renew --preferred-chain 'ISRG Root X1' -n --agree-tos --email mail@{$conf['domain']} -d {$conf['domain']} -d $oc.{$conf['domain']} -d $np.{$conf['domain']} -d $hy.{$conf['domain']} $adguardClient --webroot -w /certs/ --logs-dir /logs --max-log-backups 0 2>&1", $out, $code); if ($code > 0) { $this->send($this->input['chat'], "ERROR\n" . implode("\n", $out)); break; @@ -4685,6 +4735,7 @@ DNS-over-HTTPS with IP: $main[] = ''; $oc = $this->getHashSubdomain('oc'); $np = $this->getHashSubdomain('np'); + $hy = $this->getHashSubdomain('hy'); if (!empty($conf['domain'])) { $ssl_expiry = $this->expireCert(); $certs = $this->domainsCert() ?: []; @@ -4694,6 +4745,7 @@ DNS-over-HTTPS with IP: $main[] = $conf['domain'] . (in_array($conf['domain'], $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); $main[] = 'naive ' . "$np.{$conf['domain']}" . (in_array("$np.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); $main[] = 'openconnect ' . "$oc.{$conf['domain']}" . (in_array("$oc.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); + $main[] = 'hysteria ' . "$hy.{$conf['domain']}" . (in_array("$hy.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); if (!empty($conf['adguardkey'])) { $main[] = "{$conf['adguardkey']}.{$conf['domain']}" . (in_array("{$conf['adguardkey']}.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : '') . ' adguard DOT';; } @@ -4712,6 +4764,7 @@ DNS-over-HTTPS with IP: $this->i18n($this->ssh('pgrep xray', 'xr') ? 'on' : 'off') . ' ' . $this->i18n('xray'), $this->i18n($this->ssh('pgrep caddy', 'np') ? 'on' : 'off') . ' ' . $this->i18n('naive'), $this->i18n($this->ssh('pgrep ocserv', 'oc') ? 'on' : 'off') . ' ' . $this->i18n('ocserv'), + $this->i18n($this->ssh('pgrep hysteria', 'hy') ? 'on' : 'off') . ' ' . $this->i18n('hysteria'), $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'), @@ -4724,6 +4777,7 @@ DNS-over-HTTPS with IP: $this->i18n('on') . ' 443', $this->i18n('on') . ' 443', $this->i18n('on') . ' 443', + $this->i18n('on') . ' 443', $this->i18n($c['tg'] ? 'on' : 'off') . ' ' . getenv('TGPORT'), $this->i18n($c['ad'] ? 'on' : 'off') . ' 853', $this->i18n($c['ss'] ? 'on' : 'off') . ' ' . getenv('SSPORT'), @@ -4803,7 +4857,11 @@ DNS-over-HTTPS with IP: ], [ [ - 'text' => $this->i18n('dnstt'), + 'text' => $this->i18n('Hysteria'), + 'callback_data' => "/menu hy", + ], + [ + 'text' => $this->i18n('DNSTT'), 'callback_data' => "/dnstt", ], ], @@ -4837,6 +4895,7 @@ DNS-over-HTTPS with IP: 'lang' => $type == 'lang' ? $this->menuLang() : false, 'oc' => $type == 'oc' ? $this->ocMenu() : false, 'naive' => $type == 'naive' ? $this->naiveMenu() : false, + 'hy' => $type == 'hy' ? $this->hysteriaMenu() : false, 'mirror' => $type == 'mirror' ? $this->mirrorMenu() : false, 'update' => $type == 'update' ? $this->updatebot() : false, ]; @@ -5855,6 +5914,32 @@ DNS-over-HTTPS with IP: ]; } + public function hysteriaMenu() + { + $pac = $this->getPacConf(); + $domain = $this->getDomain(); + $text[] = "Menu -> Hysteria"; + $hy = $this->getHashSubdomain('hy'); + $text[] = "server: $hy.$domain"; + $text[] = "passwd: {$pac['hysteria_pass']}"; + $data[] = [ + [ + 'text' => $this->i18n('change password'), + 'callback_data' => "/changeHysteriaPass", + ], + ]; + $data[] = [ + [ + 'text' => $this->i18n('back'), + 'callback_data' => "/menu", + ], + ]; + return [ + 'text' => implode("\n", $text), + 'data' => $data, + ]; + } + public function mirrorMenu() { $ip = $this->getPacConf()['domain'] ?: $this->ip; @@ -7846,6 +7931,7 @@ DNS-over-HTTPS with IP: file_put_contents('/config/upstream.conf', $t); $this->ssh("nginx -s reload 2>&1", 'up'); } + public function setUpstreamDomainOcserv($domain) { $sub = $this->getHashSubdomain('oc'); @@ -7854,6 +7940,7 @@ DNS-over-HTTPS with IP: file_put_contents('/config/upstream.conf', $t); $this->ssh("nginx -s reload 2>&1", 'up'); } + public function setUpstreamDomainNaive($domain) { $sub = $this->getHashSubdomain('np'); @@ -7863,6 +7950,15 @@ DNS-over-HTTPS with IP: $this->ssh("nginx -s reload 2>&1", 'up'); } + public function setUpstreamDomainHysteria($domain) + { + $sub = $this->getHashSubdomain('hy'); + $nginx = file_get_contents('/config/upstream.conf'); + $t = preg_replace('~#hysteria.+#hysteria~s', $domain ? "#hysteria\n$sub.$domain hysteria;\n#hysteria" : "#hysteria\n#$sub.\$domain hysteria;\n#hysteria", $nginx); + file_put_contents('/config/upstream.conf', $t); + $this->ssh("nginx -s reload 2>&1", 'up'); + } + public function getHashBot($notset = false) { $p = $this->getPacConf(); @@ -8214,6 +8310,7 @@ DNS-over-HTTPS with IP: $conf = $this->getPacConf(); $oc = $this->getHashSubdomain('oc'); $np = $this->getHashSubdomain('np'); + $hy = $this->getHashSubdomain('hy'); if (!empty($conf['domain'])) { $ssl_expiry = $this->expireCert(); $certs = $this->domainsCert() ?: []; @@ -8223,6 +8320,7 @@ DNS-over-HTTPS with IP: $text[] = $conf['domain'] . (in_array($conf['domain'], $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); $text[] = 'naive ' . "$np.{$conf['domain']}" . (in_array("$np.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); $text[] = 'openconnect ' . "$oc.{$conf['domain']}" . (in_array("$oc.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); + $text[] = 'hysteria ' . "$hy.{$conf['domain']}" . (in_array("$hy.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : ''); if (!empty($conf['adguardkey'])) { $text[] = "{$conf['adguardkey']}.{$conf['domain']}" . (in_array("{$conf['adguardkey']}.{$conf['domain']}", $certs) ? ' (ssl: ' . date('Y-m-d H:i:s', $ssl_expiry) . ')' : '') . ' adguard DOT';; } diff --git a/app/service.php b/app/service.php index d787a6a..7232025 100644 --- a/app/service.php +++ b/app/service.php @@ -25,3 +25,8 @@ $bot->cloakNginx(); $bot->syncDeny(); $bot->cleanDocker(); $bot->dnsttStart(); +$c = $bot->getPacConf(); +$bot->setUpstreamDomain($c['pac']['transport'] != 'Reality' ? 't' : ($c['pac']['reality']['domain'] ?: $c['xray']['inbounds'][0]['streamSettings']['realitySettings']['serverNames'][0])); +$bot->setUpstreamDomainNaive($c['domain']); +$bot->setUpstreamDomainOcserv($c['domain']); +$bot->setUpstreamDomainHysteria($c['domain']); diff --git a/config/hysteria.yaml b/config/hysteria.yaml new file mode 100644 index 0000000..b841936 --- /dev/null +++ b/config/hysteria.yaml @@ -0,0 +1,9 @@ +listen: :443 +proxy_protocol: true + +tls: + cert: /certs/cert_public + key: /certs/cert_private +auth: + type: password + password: diff --git a/config/upstream.conf b/config/upstream.conf index 8e74430..604baae 100644 --- a/config/upstream.conf +++ b/config/upstream.conf @@ -32,6 +32,10 @@ stream { server np:443; } + upstream hysteria { + server hy:443; + } + map_hash_bucket_size 128; map $ssl_preread_server_name $sni_name { #domain @@ -45,6 +49,10 @@ stream { #naive #np.domain naive; #naive + + #hysteria + #hy.domain hysteria; + #hysteria default other; } diff --git a/docker-compose.yml b/docker-compose.yml index 2b2a298..0b6e90e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,6 +54,8 @@ services: condition: service_started np: condition: service_started + hy: + condition: service_started env_file: - path: ./.env required: true # default @@ -212,6 +214,7 @@ services: - proxy - ss - dnstt + - hy wg: image: mercurykd/vpnbot-wg:1.1 build: @@ -566,3 +569,32 @@ services: default: ipv4_address: 10.10.0.16 logging: *default-logging + hy: + image: mercurykd/vpnbot-hysteria:1.0 + build: + dockerfile: dockerfile/hysteria.dockerfile + args: + image: ${IMAGE} + volumes: + - ./config/.profile:/root/.ashrc:ro + - ./ssh:/ssh + - ./config/sshd_config:/etc/ssh/sshd_config + - ./certs:/certs + - ./config:/config + - ./scripts/start_hysteria.sh:/start_hysteria.sh + hostname: hysteria + container_name: hysteria-${VER} + depends_on: + php: + condition: service_healthy + env_file: + - path: ./.env + required: true # default + - path: ./override.env + required: false + stop_grace_period: 1s + command: ["/bin/sh", "/start_hysteria.sh"] + networks: + default: + ipv4_address: 10.10.0.17 + logging: *default-logging diff --git a/dockerfile/dnstt.dockerfile b/dockerfile/dnstt.dockerfile index 34d8781..f696ae7 100644 --- a/dockerfile/dnstt.dockerfile +++ b/dockerfile/dnstt.dockerfile @@ -8,6 +8,6 @@ RUN apk add --no-cache 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 \ +RUN apk add --no-cache openssh \ && mkdir -p /root/.ssh ENV ENV="/root/.ashrc" \ No newline at end of file diff --git a/dockerfile/hysteria.dockerfile b/dockerfile/hysteria.dockerfile new file mode 100644 index 0000000..040fc78 --- /dev/null +++ b/dockerfile/hysteria.dockerfile @@ -0,0 +1,5 @@ +FROM tobyxdd/hysteria +RUN apk add --no-cache openssh \ + && mkdir -p /root/.ssh +ENV ENV="/root/.ashrc" +ENTRYPOINT [] \ No newline at end of file diff --git a/makefile b/makefile index c602dfc..932924e 100644 --- a/makefile +++ b/makefile @@ -39,6 +39,8 @@ tg: # консоль сервиса docker compose exec tg /bin/sh dnstt: # консоль сервиса docker compose exec dnstt /bin/sh +hy: # консоль сервиса + docker compose exec hy /bin/sh xr: # консоль сервиса docker compose exec xr /bin/sh oc: # консоль сервиса diff --git a/scripts/start_hysteria.sh b/scripts/start_hysteria.sh new file mode 100644 index 0000000..546d204 --- /dev/null +++ b/scripts/start_hysteria.sh @@ -0,0 +1,5 @@ +cat /ssh/key.pub > /root/.ssh/authorized_keys +ssh-keygen -A +exec /usr/sbin/sshd -D -e "$@" & +hysteria server -c /config/hysteria.yaml & +tail -f /dev/null