From 75fb0bbc6c9d7f1b194227f01038541b51eb24e0 Mon Sep 17 00:00:00 2001 From: mercury Date: Fri, 20 Jan 2023 00:50:25 +0400 Subject: [PATCH] pre release v2 --- .env | 4 +- .gitignore | 14 +- app/bot.php | 1565 ++++++++++++++++++++---- app/debug.php | 6 +- app/index.php | 38 +- app/updatepac.php | 320 +++++ logs/debug => app/zapretlists/.gitkeep | 0 logs/error_log => certs/.gitkeep | 0 config/AdGuardHome.yaml | 145 +++ config/clients.json | 1 + config/nginx.conf | 177 +++ config/pac.json | 99 ++ config/php.ini | 8 + config/sockd.conf | 16 - config/sslocal.json | 15 + config/ssserver.json | 13 + config/unit.json | 30 +- docker-compose.yml | 126 +- dockerfile/adguard.dockerfile | 8 + dockerfile/doh.dockerfile | 6 + dockerfile/php.dockerfile | 16 +- dockerfile/proxy.dockerfile | 11 - dockerfile/shadowsocks.dockerfile | 11 + dockerfile/wireguard.dockerfile | 13 +- logs/{input => .gitkeep} | 0 makefile | 16 +- scripts/init.sh | 2 +- scripts/start_ad.sh | 4 + scripts/start_ng.sh | 5 + scripts/start_proxy.sh | 4 + scripts/start_ss.sh | 4 + scripts/start_unit.sh | 5 + scripts/start_wg.sh | 4 +- ssh/.gitkeep | 0 34 files changed, 2360 insertions(+), 326 deletions(-) create mode 100644 app/updatepac.php rename logs/debug => app/zapretlists/.gitkeep (100%) rename logs/error_log => certs/.gitkeep (100%) create mode 100644 config/AdGuardHome.yaml create mode 100644 config/nginx.conf create mode 100644 config/pac.json delete mode 100644 config/sockd.conf create mode 100644 config/sslocal.json create mode 100644 config/ssserver.json create mode 100644 dockerfile/adguard.dockerfile create mode 100644 dockerfile/doh.dockerfile delete mode 100644 dockerfile/proxy.dockerfile create mode 100644 dockerfile/shadowsocks.dockerfile rename logs/{input => .gitkeep} (100%) create mode 100644 scripts/start_ad.sh create mode 100644 scripts/start_ng.sh create mode 100644 scripts/start_proxy.sh create mode 100644 scripts/start_ss.sh create mode 100644 scripts/start_unit.sh create mode 100644 ssh/.gitkeep diff --git a/.env b/.env index 2eb53e8..4e2f45d 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ TZ=Europe/Samara -PORT=51820 -ADDRESS=10.0.1.1/24 +WGPORT=51820 +WGADDRESS=10.0.1.1/24 diff --git a/.gitignore b/.gitignore index 1c16f24..71cdd29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,15 @@ /app/config.php -/cert/* -!/cert/.gitkeep +/certs/* +!/certs/.gitkeep +/logs/* +!/logs/.gitkeep +/ssh/* +!/ssh/.gitkeep +/app/zaprelists/* +!/app/zaprelists/.gitkeep /sftp-config.json /tg.pyr +/config/wg0.conf +/todo.todo +/app/zapretlists/* +!/app/zapretlists/.gitkeep diff --git a/app/bot.php b/app/bot.php index acffed3..f7fd305 100644 --- a/app/bot.php +++ b/app/bot.php @@ -9,7 +9,8 @@ class Bot $this->key = $key; $this->api = "https://api.telegram.org/bot$key/"; $this->file = "https://api.telegram.org/file/bot$key/"; - $this->clients = __DIR__ . '/clients.json'; + $this->clients = '/config/clients.json'; + $this->pac = '/config/pac.json'; } public function input() @@ -46,7 +47,7 @@ class Bot public function callbackCheck() { if (empty($this->callback) && !empty($this->input['callback_id'])) { - $this->answer($this->input['callback_id']); + $this->answer($this->input['callback_id'], $GLOBALS['debug'] ? $this->input['callback']: false); } } @@ -64,13 +65,15 @@ class Bot } } - public function sd($var, $log = false, $json = false) + public function sd($var, $log = false, $json = false, $raw = false) { if ($log) { if ($json) { - file_put_contents(__DIR__ . '/logs/input', json_encode($var, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + file_put_contents('/logs/debug', json_encode($var, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } elseif ($raw) { + file_put_contents('/logs/debug', $var); } else { - file_put_contents(__DIR__ . '/logs/input', var_export($var, true)); + file_put_contents('/logs/debug', var_export($var, true)); } } $this->send($this->input['chat'], var_export($var, true), $this->input['message_id']); @@ -82,24 +85,85 @@ class Bot // смена айпи сервера case preg_match('~^/menu$~', $this->input['message'], $m): case preg_match('~^/menu$~', $this->input['callback'], $m): - case preg_match('~^/client (\d+)$~', $this->input['callback'], $m): - $this->menu($m[1] ?? false); + 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)$~', $this->input['callback'], $m): + case preg_match('~^/menu (?Padguard)$~', $this->input['callback'], $m): + case preg_match('~^/menu (?Pconfig)$~', $this->input['callback'], $m): + case preg_match('~^/menu (?Pss)$~', $this->input['callback'], $m): + case preg_match('~^/menu (?Psubzoneslist|reverselist|includelist|excludelist) (?P(?:-)?\d+)$~', $this->input['callback'], $m): + $this->menu(type: $m['type'] ?? false, arg: $m['arg'] ?? false); + break; + case preg_match('~^/selfssl$~', $this->input['callback'], $m): + $this->selfssl(); + break; + case preg_match('~^/sspswd$~', $this->input['callback'], $m): + $this->sspswd(); + break; + case preg_match('~^/v2ray$~', $this->input['callback'], $m): + $this->v2ray(); + break; + case preg_match('~^/checkdns$~', $this->input['callback'], $m): + $this->checkdns(); + break; + case preg_match('~^/adguardpsswd$~', $this->input['callback'], $m): + $this->adguardpsswd(); + break; + case preg_match('~^/addupstream$~', $this->input['callback'], $m): + $this->addupstream(); + break; + case preg_match('~^/checkurl$~', $this->input['callback'], $m): + $this->checkurl(); + break; + case preg_match('~^/setSSL (\w+)$~', $this->input['callback'], $m): + $this->setSSL($m[1]); + break; + case preg_match('~^/deletessl$~', $this->input['callback'], $m): + $this->deleteSSL(); break; case preg_match('~^/download (\d+)$~', $this->input['callback'], $m): $this->downloadPeer($m[1]); break; - case preg_match('~^/delete (\d+)$~', $this->input['callback'], $m): - $this->deletePeer($m[1]); + case preg_match('~^/delupstream (\d+)$~', $this->input['callback'], $m): + $this->delupstream($m[1]); + break; + case preg_match('~^/delete (?P\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m): + $this->deletePeer(...explode('_', $m['arg'])); + break; + case preg_match('~^/deldomain$~', $this->input['callback'], $m): + $this->delDomain(); + break; + case preg_match('~^/(?Pchange|delete)(?Psubzoneslist|reverselist|includelist|excludelist) (?P[^\s]+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m): + $this->listPacChange($m['typelist'], $m['action'], ...explode('_', $m['arg'])); + break; + case preg_match('~^/paczapret$~', $this->input['callback'], $m): + $this->pacZapret(); + break; + case preg_match('~^/pacupdate$~', $this->input['callback'], $m): + $this->pacUpdate(); break; case preg_match('~^/add$~', $this->input['callback'], $m): $this->addPeer(); // добавление клиента "весь траффик" break; - case preg_match('~^/showadd$~', $this->input['callback'], $m): - $this->showaddclient(); // меню добавления клиента - break; case preg_match('~^/add_ips$~', $this->input['callback'], $m): $this->addips(); // ответ с предложением ввести список подсетей break; + case preg_match('~^/domain$~', $this->input['callback'], $m): + $this->domain(); + break; + case preg_match('~^/include (\d+)$~', $this->input['callback'], $m): + $this->include($m[1]); + break; + case preg_match('~^/exclude (\d+)$~', $this->input['callback'], $m): + $this->exclude($m[1]); + break; + case preg_match('~^/reverse (\d+)$~', $this->input['callback'], $m): + $this->reverse($m[1]); + break; + case preg_match('~^/subzones (\d+)$~', $this->input['callback'], $m): + $this->subzones($m[1]); + break; case preg_match('~^/showreset$~', $this->input['callback'], $m): $this->showreset(); break; @@ -118,8 +182,8 @@ class Bot case preg_match('~^/import$~', $this->input['callback'], $m): $this->import(); break; - case preg_match('~^/rename (.+)$~', $this->input['callback'], $m): - $this->rename($m[1]); + case preg_match('~^/rename (?P\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m): + $this->rename(...explode('_', $m['arg'])); break; case !empty($this->input['reply']): $this->reply(); @@ -127,7 +191,101 @@ class Bot } } - public function rename(int $client) + public function checkurl() + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} enter url", + $this->input['message_id'], + reply: 'enter url', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'start_callback' => $this->input['callback_id'], + 'callback' => 'urlcheck', + 'args' => [], + ]; + } + + public function urlcheck($url) + { + if (file_exists(__DIR__ . '/zapretlists/mpac')) { + $domains = explode("\n", file_get_contents(__DIR__ . '/zapretlists/mpac')); + foreach ($domains as $k => $v) { + if (preg_match("~$v~", $url)) { + $flag = 1; + break; + } + } + if ($flag) { + $text = "$url\nmatch"; + } else { + $text = "$url\nnot match"; + } + } else { + $text = 'no file, update pac'; + } + $this->update($this->input['chat'], $this->input['message_id'], $text); + sleep(3); + $this->menu('pac'); + } + + public function sspswd() + { + $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' => 'sspwdch', + 'args' => [], + ]; + } + + public function sspwdch($pass) + { + $this->ssh('pkill ssserver', 'ss'); + $c = $this->getSSConfig(); + $c['password'] = $pass; + file_put_contents('/config/ssserver.json', json_encode($c, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->ssh('/ssserver -v -d -c /config.json', 'ss'); + $this->menu('ss'); + } + + public function v2ray() + { + $this->ssh('pkill sslocal', 'proxy'); + $this->ssh('pkill ssserver', 'ss'); + $c = $this->getSSConfig(); + $l = $this->getSSLocalConfig(); + $domain = $this->getPacConf()['domain']; + if ($c['plugin']) { + unset($c['plugin']); + unset($c['plugin_opts']); + unset($l['plugin']); + unset($l['plugin_opts']); + $l['server'] = 'ss'; + $l['server_port'] = 8388; + } else { + $c['plugin'] = 'v2ray-plugin'; + $c['plugin_opts'] = 'server;loglevel=none'; + $l['server'] = 'ng'; + $l['server_port'] = 443; + $l['plugin'] = 'v2ray-plugin'; + $l['plugin_opts'] = "tls;fast-open;path=/v2ray;host=$domain"; + } + file_put_contents('/config/ssserver.json', json_encode($c, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + file_put_contents('/config/sslocal.json', json_encode($l, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->ssh('/ssserver -v -d -c /config.json', 'ss'); + $this->ssh('/sslocal -v -d -c /config.json', 'proxy'); + $this->menu('ss'); + } + + public function rename(int $client, $page) { $r = $this->send( $this->input['chat'], @@ -139,7 +297,7 @@ class Bot 'start_message' => $this->input['message_id'], 'start_callback' => $this->input['callback_id'], 'callback' => 'renameClient', - 'args' => [$client], + 'args' => [$client, $page], ]; } @@ -155,6 +313,7 @@ class Bot } } $this->restartWG($this->createConfig($server)); + $this->menu('client', implode('_', $_SESSION['reply'][$this->input['reply']]['args'])); } public function readClients():array @@ -164,12 +323,23 @@ class Bot public function export() { - $conf = $this->readConfig(); - $export = [ - 'server' => $this->readConfig(), - 'clients' => json_decode(file_get_contents($this->clients), true) ?: [], + $conf = [ + 'wg' => [ + 'server' => $this->readConfig(), + 'clients' => json_decode(file_get_contents($this->clients), true) ?: [], + ], + 'ss' => $this->getSSConfig(), + 'sl' => $this->getSSLocalConfig(), + 'ad' => yaml_parse_file('/config/adguard/AdGuardHome.yaml'), + 'pac' => $this->getPacConf(), + 'ssl' => [ + 'private' => file_exists('/certs/cert_private') ? file_get_contents('/certs/cert_private') : false, + 'public' => file_exists('/certs/cert_public') ? file_get_contents('/certs/cert_public') : false, + ], + 'nginx' => file_get_contents('/config/nginx.conf') + ]; - $this->upload(date('d_m_Y_H_i') . '.json', json_encode($export, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->upload(date('d_m_Y_H_i') . '.json', json_encode($conf, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } public function import() @@ -195,8 +365,61 @@ class Bot if (empty($json) || !is_array($json)) { $this->answer($this->input['callback_id'], 'error', true); } else { - $this->saveClients($json['clients']); - $this->restartWG($this->createConfig($json['server'])); + // wg + if (!empty($json['wg'])) { + $out[] = 'update wireguard'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $this->saveClients($json['wg']['clients']); + $this->restartWG($this->createConfig($json['wg']['server'])); + } + // pac + if (!empty($json['pac'])) { + $out[] = 'update pac'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $this->setPacConf($json['pac']); + } + // ad + if (!empty($json['ad'])) { + $out[] = 'update adguard'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $this->ssh("/AdGuardHome/AdGuardHome -s stop 2>&1", 'ad'); + yaml_emit_file('/config/adguard/AdGuardHome.yaml', $json['ad']); + $this->ssh("/AdGuardHome/AdGuardHome -s start 2>&1", 'ad'); + } + // ss + if (!empty($json['ss'])) { + $out[] = 'update shadowsocks server'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $this->ssh('pkill ssserver', 'ss'); + file_put_contents('/config/ssserver.json', json_encode($json['ss'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->ssh('/ssserver -v -d -c /config.json', 'ss'); + } + // sl + if (!empty($json['sl'])) { + $out[] = 'update shadowsocks proxy'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $this->ssh('pkill sslocal', 'proxy'); + file_put_contents('/config/sslocal.json', json_encode($json['sl'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->ssh('/sslocal -v -d -c /config.json', 'proxy'); + } + // certs + if (!empty($json['ssl'])) { + $out[] = 'update certificates'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + file_put_contents('/certs/cert_private', $json['ssl']['private']); + file_put_contents('/certs/cert_public', $json['ssl']['public']); + } + // nginx + if (!empty($json['nginx'])) { + $out[] = 'reload nginx'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + file_put_contents('/config/nginx.conf', $json['nginx']); + $this->ssh("nginx -s reload 2>&1", 'ng'); + } + $out[] = 'end import'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + sleep(3); + $this->menu(); } } @@ -210,7 +433,7 @@ class Bot public function upload($name, $code) { - $path = __DIR__ . "/logs/$name"; + $path = "/logs/$name"; file_put_contents($path, $code); $this->sendFile( $this->input['chat'], @@ -219,48 +442,10 @@ class Bot unlink($path); } - public function pac($domains) - { - $domains = implode(' || ', array_map(fn($el) => 'shExpMatch(url, "*' . trim($el) . '*")', explode(PHP_EOL, $domains))); - $proxy = trim($this->ssh("getent hosts proxy | awk '{ print $1 }'")); - $pac = <<send( - $this->input['chat'], - "$pac", - $this->input['message_id'], - ); - } - - public function showpac() - { - $r = $this->send( - $this->input['chat'], - "@{$this->input['username']} list the domains (each domain on a new line, you can part of the domain):", - $this->input['message_id'], - reply: 'list the domains (each domain on a new line, you can part of the domain):', - ); - $_SESSION['reply'][$r['result']['message_id']] = [ - 'start_message' => $this->input['message_id'], - 'start_callback' => $this->input['callback_id'], - 'callback' => 'pac', - 'args' => [], - ]; - } - public function proxy() { $proxy = trim($this->ssh("getent hosts proxy | awk '{ print $1 }'")); $this->createPeer("$proxy/32", 'proxy'); - $this->menu(); } public function change_server_ip($ip) @@ -274,30 +459,159 @@ class Bot { if (!empty($_SESSION['reply'][$this->input['reply']])) { $this->delete($this->input['chat'], $this->input['reply']); + $this->delete($this->input['chat'], $this->input['message_id']); $callback = $_SESSION['reply'][$this->input['reply']]['callback']; + $this->input['message_id'] = $this->input['callback_id'] = $_SESSION['reply'][$this->input['reply']]['start_message']; $this->{$callback}($this->input['message'], ...$_SESSION['reply'][$this->input['reply']]['args']); - switch ($callback) { - case 'createPeer': - case 'importFile': - $this->delete($this->input['chat'], $this->input['message_id']); - $this->input['message_id'] = $this->input['callback_id'] = $_SESSION['reply'][$this->input['reply']]['start_message']; - $this->menu(); - $this->answer($_SESSION['reply'][$this->input['reply']]['start_message']); - break; - case 'pac': - $this->answer($_SESSION['reply'][$this->input['reply']]['start_callback']); - break; - case 'renameClient': - $this->delete($this->input['chat'], $this->input['message_id']); - $this->input['message_id'] = $this->input['callback_id'] = $_SESSION['reply'][$this->input['reply']]['start_message']; - $this->menu(); - $this->answer($_SESSION['reply'][$this->input['reply']]['start_message']); - break; - } + $this->answer($_SESSION['reply'][$this->input['reply']]['start_message']); unset($_SESSION['reply'][$this->input['reply']]); } } + public function addDomain($domain) + { + $domain = trim($domain); + if (!empty($domain)) { + $conf = $this->getPacConf(); + $conf['domain'] = idn_to_ascii($domain); + $this->setPacConf($conf); + } + $this->menu('config'); + } + + public function comment($text) + { + $text = explode("\n", $text); + foreach ($text as $k => $v) { + if (preg_match('~##domain~', $v)) { + $text[$k] = '#-domain'; + continue; + } + $text[$k] = "#$v"; + } + return implode("\n", $text); + } + + public function uncomment($text) + { + $text = explode("\n", $text); + foreach ($text as $k => $v) { + if (preg_match('~#-domain~', $v)) { + $text[$k] = '##domain'; + continue; + } + $text[$k] = trim($v, '#'); + } + return implode("\n", $text); + } + + public function deleteSSL() + { + $nginx = file_get_contents('/config/nginx.conf'); + $t = preg_replace("/#~[^\s]+/", '#~', $nginx); + preg_match_all('~##domain.+?##domain~s', $t, $m); + foreach ($m[0] as $k => $v) { + $t = preg_replace('~##domain.+?##domain~s', $this->comment($v), $t, 1); + } + file_put_contents('/config/nginx.conf', $t); + $u = $this->ssh("nginx -t 2>&1", 'ng'); + $this->update($this->input['chat'], $this->input['message_id'], $u); + if (preg_match('~test is successful~', $u)) { + $u .= $this->ssh("nginx -s reload 2>&1", 'ng'); + $this->update($this->input['chat'], $this->input['message_id'], $u); + unlink('/certs/cert_private'); + unlink('/certs/cert_public'); + sleep(3); + } else { + file_put_contents('/config/nginx.conf', $nginx); + } + $this->menu('config'); + } + + public function updateUnitInitConfig() + { + $unit = $this->controlUnit('config'); + file_put_contents('/config/unit.json', $unit); + } + + public function setSSL($name) + { + $conf = $this->getPacConf(); + switch ($name) { + case 'letsencrypt': + $out[] = 'Install certificate:'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + exec("certbot certonly -n --agree-tos --email mail@{$conf['domain']} -d {$conf['domain']} --webroot -w /certs/ 2>&1", $out, $code); + if ($code > 0) { + $this->send($this->input['chat'], "ERROR\n" . implode("\n", $out)); + break; + } + $out[] = 'Generate bundle'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $bundle = file_get_contents("/etc/letsencrypt/live/{$conf['domain']}/privkey.pem") . file_get_contents("/etc/letsencrypt/live/{$conf['domain']}/fullchain.pem"); + break; + case 'self': + $r = $this->request('getFile', ['file_id' => $this->input['file_id']]); + $bundle = file_get_contents($this->file . $r['result']['file_path']); + break; + } + if (preg_match('~[^\s]+BEGIN PRIVATE KEY.+?END PRIVATE KEY[^\s]+~s', $bundle, $m)) { + file_put_contents('/certs/cert_private', $m[0]); + file_put_contents('/certs/cert_public', preg_replace('~[^\s]+BEGIN PRIVATE KEY.+?END PRIVATE KEY[^\s]+~s', '', $bundle)); + $nginx = file_get_contents('/config/nginx.conf'); + $t = preg_replace('/#~([^\n]+)?/', "#~$name", $nginx); + $t = preg_replace('/server_name([^\n]+)?/', "server_name {$conf['domain']};", $t); + preg_match_all('~#-domain.+?#-domain~s', $t, $m); + foreach ($m[0] as $k => $v) { + $t = preg_replace('~#-domain.+?#-domain~s', $this->uncomment($v), $t, 1); + } + file_put_contents('/config/nginx.conf', $t); + $u = $this->ssh("nginx -t 2>&1", 'ng'); + $out[] = $u; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + if (preg_match('~test is successful~', $u)) { + $out[] = $this->ssh("nginx -s reload 2>&1", 'ng'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + } else { + file_put_contents('/config/nginx.conf', $nginx); + } + } else { + $this->update($this->input['chat'], $this->input['message_id'], "wrong format key"); + } + sleep(3); + $this->menu('config'); + } + + public function controlUnit($url, $method = 'GET', $json = false, $bundle = false) + { + $ch = curl_init(); + $opt = [ + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_URL => "http://localhost/$url", + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_UNIX_SOCKET_PATH => '/var/run/control.unit.sock', + CURLOPT_TIMEOUT => 10, + ]; + if ($json) { + $opt[CURLOPT_POSTFIELDS] = $json; + } + if ($bundle) { + $opt[CURLOPT_POSTFIELDS] = ['file' => new CURLStringFile($bundle, 'bundle.pem', 'text/plain')]; + } + curl_setopt_array($ch, $opt); + $r = curl_exec($ch); + curl_close($ch); + return $r ?: 'lost connect to unit'; + } + + public function delDomain() + { + $conf = $this->getPacConf(); + unset($conf['domain']); + $this->setPacConf($conf); + $this->deleteSSL(); + } + public function addips() { $r = $this->send( @@ -309,45 +623,292 @@ class Bot $_SESSION['reply'][$r['result']['message_id']] = [ 'start_message' => $this->input['message_id'], 'callback' => 'createPeer', - 'args' => ['подсеть'], + 'args' => ['subnet'], ]; } - public function showaddclient() + public function getPacConf() { - [$text, $data] = $this->menu(return: true); - $data = [ - [ - [ - 'text' => "all traffic", - 'callback_data' => "/add", - ], - ], - [ - [ - 'text' => "subnet", - 'callback_data' => "/add_ips", - ], - ], - [ - [ - 'text' => "proxy", - 'callback_data' => "/proxy", - ], - ], - [ - [ - 'text' => "back", - 'callback_data' => "/menu", - ], - ], - ]; - $this->update( + return json_decode(file_get_contents($this->pac), true); + } + + public function setPacConf(array $conf) + { + return file_put_contents($this->pac, json_encode($conf, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } + + public function domain() + { + $r = $this->send( $this->input['chat'], + "@{$this->input['username']} enter domain", $this->input['message_id'], - $text, - $data, + reply: 'enter domain', ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'addDomain', + 'args' => [], + ]; + } + + public function selfssl() + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} send file with your certificate chain and private key cat key.pem ca.pem cert.pem", + $this->input['message_id'], + reply: 'send file with your certificate chain and private key', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'selfsslInstall', + 'args' => [], + ]; + } + + public function adguardpsswd() + { + $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'], + 'callback' => 'chpsswd', + 'args' => [], + ]; + } + + public function chpsswd($pass) + { + $out[] = 'Restart Adguard Home'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $out[] = $this->ssh("/AdGuardHome/AdGuardHome -s stop 2>&1", 'ad'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $c = yaml_parse_file('/config/adguard/AdGuardHome.yaml'); + $c['users'][0]['password'] = password_hash($pass, PASSWORD_DEFAULT); + yaml_emit_file('/config/adguard/AdGuardHome.yaml', $c); + $out[] = $this->ssh("/AdGuardHome/AdGuardHome -s start 2>&1", 'ad'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + sleep(3); + $this->menu('adguard'); + } + + public function checkdns() + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} enter dns address +Plain DNS: +example.org 94.140.14.14 +DNS-over-TLS: +example.org tls://dns.adguard.com +DNS-over-TLS with IP: +example.org tls://dns.adguard.com 94.140.14.14 +DNS-over-HTTPS with HTTP/2: +example.org https://dns.adguard.com/dns-query +DNS-over-HTTPS forcing HTTP/3 only: +example.org h3://dns.google/dns-query +DNS-over-HTTPS with IP: +example.org https://dns.adguard.com/dns-query 94.140.14.14", + $this->input['message_id'], + reply: 'enter command', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'dnscheck', + 'args' => [], + ]; + } + + public function dnscheck($dns) + { + exec("JSON=1 dnslookup $dns", $out, $code); + if ($code) { + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out), mode: false); + } else { + $this->send($this->input['chat'], "JSON=1 dnslookup $dns\n" . implode("\n", $out), mode: false); + } + sleep(3); + $this->menu('adguard'); + } + + public function addupstream() + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} enter address upstream", + $this->input['message_id'], + reply: 'enter address upstream', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'upstream', + 'args' => [], + ]; + } + + public function upstream($url) + { + $out[] = 'Restart Adguard Home'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $out[] = $this->ssh("/AdGuardHome/AdGuardHome -s stop 2>&1", 'ad'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $c = yaml_parse_file('/config/adguard/AdGuardHome.yaml'); + $c['dns']['upstream_dns'][] = $url; + yaml_emit_file('/config/adguard/AdGuardHome.yaml', $c); + $out[] = $this->ssh("/AdGuardHome/AdGuardHome -s start 2>&1", 'ad'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + sleep(3); + $this->menu('adguard'); + } + + public function delupstream($k) + { + $out[] = 'Restart Adguard Home'; + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $out[] = $this->ssh("/AdGuardHome/AdGuardHome -s stop 2>&1", 'ad'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + $c = yaml_parse_file('/config/adguard/AdGuardHome.yaml'); + unset($c['dns']['upstream_dns'][$k]); + yaml_emit_file('/config/adguard/AdGuardHome.yaml', $c); + $out[] = $this->ssh("/AdGuardHome/AdGuardHome -s start 2>&1", 'ad'); + $this->update($this->input['chat'], $this->input['message_id'], implode("\n", $out)); + sleep(3); + $this->menu('adguard'); + } + + public function selfsslInstall() + { + $this->setSSL('self'); + } + + public function include(int $count) + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} list domains separated by commas", + $this->input['message_id'], + reply: 'list domains separated by commas', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'addInclude', + 'args' => [$count], + ]; + } + + public function addInclude(string $domains, int $count) + { + $domains = explode(',', $domains); + $domains = array_filter($domains, fn($x) => !empty(trim($x))); + if (!empty($domains)) { + $conf = $this->getPacConf(); + foreach ($domains as $k => $v) { + $conf['includelist'][idn_to_ascii(trim($v))] = true; + } + ksort($conf['includelist']); + $this->setPacConf($conf); + $page = (int) floor(array_search($v, array_keys($conf['includelist'])) / $count); + } + $page = $page ?: -2; + $this->menu('includelist', $page); + } + + public function reverse(int $count) + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} list domains separated by commas", + $this->input['message_id'], + reply: 'list domains separated by commas', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'addReverse', + 'args' => [$count], + ]; + } + + public function addReverse(string $domains, int $count) + { + $domains = explode(',', $domains); + $domains = array_filter($domains, fn($x) => !empty(trim($x))); + if (!empty($domains)) { + $conf = $this->getPacConf(); + foreach ($domains as $k => $v) { + $conf['reverselist'][idn_to_ascii(trim($v))] = true; + } + ksort($conf['reverselist']); + $this->setPacConf($conf); + $page = (int) floor(array_search($v, array_keys($conf['reverselist'])) / $count); + } + $page = $page ?: -2; + $this->menu('reverselist', $page); + } + + public function subzones(int $count) + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} list subdomains separated by commas", + $this->input['message_id'], + reply: 'list subdomains separated by commas', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'addSubzones', + 'args' => [$count], + ]; + } + + public function addSubzones(string $domains, int $count) + { + $domains = explode(',', $domains); + $domains = array_filter($domains, fn($x) => !empty(trim($x))); + if (!empty($domains)) { + $conf = $this->getPacConf(); + foreach ($domains as $k => $v) { + $conf['subzoneslist'][idn_to_ascii(trim($v))] = true; + } + ksort($conf['subzoneslist']); + $this->setPacConf($conf); + $page = (int) floor(array_search($v, array_keys($conf['subzoneslist'])) / $count); + } + $page = $page ?: -2; + $this->menu('subzoneslist', $page); + } + + public function exclude(int $count) + { + $r = $this->send( + $this->input['chat'], + "@{$this->input['username']} enter regular expression", + $this->input['message_id'], + reply: 'enter regular expression', + ); + $_SESSION['reply'][$r['result']['message_id']] = [ + 'start_message' => $this->input['message_id'], + 'callback' => 'addExclude', + 'args' => [$count], + ]; + } + + public function addExclude(string $reg, int $count) + { + $reg = trim($reg); + if (!empty($reg)) { + $conf = $this->getPacConf(); + $conf['excludelist'][$reg] = true; + ksort($conf['excludelist']); + $this->setPacConf($conf); + $page = (int) floor(array_search($reg, array_keys($conf['excludelist'])) / $count); + } + $page = $page ?: -2; + $this->menu('excludelist', $page); } public function showreset() @@ -374,8 +935,10 @@ class Bot public function reset() { - $conf = $this->readConfig(); - $r = $this->ssh("/bin/sh /reset_wg.sh {$_SERVER['ADDRESS']} {$_SERVER['PORT_WG']}"); + $conf = $this->readConfig(); + $address = getenv('ADDRESS'); + $port = getenv('PORT_WG'); + $r = $this->ssh("/bin/sh /reset_wg.sh $address $port"); file_put_contents($this->clients, ''); $this->menu(); } @@ -383,7 +946,6 @@ class Bot public function addPeer() { $this->createPeer(name: 'all traffic'); - $this->menu(); } public function config() @@ -405,116 +967,519 @@ class Bot ); } - public function deletePeer($client) + public function deletePeer($client, $page) { $conf = $this->readConfig(); $this->deleteClient($client); unset($conf['peers'][$client]); $this->restartWG($this->createConfig($conf)); - $this->menu(); + $this->menu('wg', $page); } - public function menu($client = false, $return = false) + public function statusWg(int $page = 0) + { + $conf = $this->readConfig(); + $status = $this->readStatus(); + $text[] = 'Server:'; + $text[] = " address: {$conf['interface']['Address']}"; + $text[] = " port: {$status['interface']['listening port']}"; + $text[] = " publickey: {$status['interface']['public key']}"; + $text[] = "\nPeers:"; + foreach ($conf['peers'] as $k => $v) { + foreach ($clients as $cl) { + if ($cl['interface']['Address'] == $v['AllowedIPs']) { + $allowed_ips = $cl['peers'][0]['AllowedIPs']; + } + } + $conf['peers'][$k]['status'] = $this->getStatusPeer($v['PublicKey'], $status['peers']); + $conf['peers'][$k]['online'] = preg_match('~^(\d+ seconds|[12] minute)~', $conf['peers'][$k]['status']['latest handshake']) ? $conf['peers'][$k]['status']['endpoint'] : 'OFFLINE'; + } + usort($conf['peers'], fn($a, $b) => ($a['online'] == 'OFFLINE') <=> ($b['online'] == 'OFFLINE')); + foreach ($conf['peers'] as $k => $v) { + preg_match_all('~([0-9.]+\.?)\s(\w+)~', $v['status']['transfer'], $m); + $text[] = ($v['online'] == 'OFFLINE' ? '(OFFLINE) ' : '') + . "{$this->getName($v)} " + . ($v['online'] != 'OFFLINE' ? "({$v['online']})" : '') + . ($m[0] ? " {$m[1][0]}↑ {$m[2][0]} / {$m[1][1]}↓ {$m[2][1]}" : '') + . "\n" + ; + } + $text = "Menu -> Wireguard\n\n" . implode(PHP_EOL, $text) . ''; + $data = [ + [[ + 'text' => "update status", + 'callback_data' => "/menu wg", + ]], + [[ + 'text' => "add peer", + 'callback_data' => "/menu addpeer $page", + ]], + ]; + if ($clients = $this->getClients($page)) { + $data = array_merge($data, $clients); + } + $data[] = [[ + 'text' => 'back', + 'callback_data' => "/menu", + ]]; + return [ + 'text' => $text, + 'data' => $data, + ]; + } + + public function getClient($client, $page) { $clients = $this->readClients(); - if ($client !== false) { + if ($clients) { $name = $this->getName($clients[$client]['interface']); - $text = "{$this->createConfig($clients[$client])}\n\n$name"; - $data = [ - [ + return [ + 'text' => "{$this->createConfig($clients[$client])}\n\n$name", + 'data' => [ [ - 'text' => "rename", - 'callback_data' => "/rename $client", + [ + 'text' => "rename", + 'callback_data' => "/rename {$client}_$page", + ], ], + [ + [ + 'text' => "download", + 'callback_data' => "/download $client", + ], + ], + [ + [ + 'text' => "delete", + 'callback_data' => "/delete {$client}_$page", + ], + ], + [ + [ + 'text' => "back", + 'callback_data' => "/menu wg $page", + ], + ], + ] + ]; + } + return [ + 'text' => "no clients", + 'data' => false + ]; + } + + public function getClients(int $page, int $count = 5) + { + $clients = $this->readClients(); + if (!empty($clients)) { + $all = (int) ceil(count($clients) / $count); + $page = min($page, $all - 1); + $page = $page == -2 ? $all - 1 : $page; + $clients = $page != -1 ? array_slice($clients, $page * $count, $count, true) : $clients; + foreach ($clients as $k => $v) { + $data[] = [[ + 'text' => $this->getName($v['interface']), + 'callback_data' => "/menu client {$k}_$page", + ]]; + } + if ($page != -1 && $all > 1) { + $data[] = [ + [ + 'text' => '<<', + 'callback_data' => "/menu wg " . ($page - 1 >= 0 ? $page - 1 : $all - 1), + ], + [ + 'text' => 'all', + 'callback_data' => "/menu wg -1", + ], + [ + 'text' => '>>', + 'callback_data' => "/menu wg " . ($page < $all - 1 ? $page + 1 : 0), + ] + ]; + } + } + return $data; + } + + public function sizeFormat($bytes) + { + if (floor($bytes / 1024 ** 2) > 0) { + $r = round($bytes / 1024 ** 2, 2) . 'MB'; + } elseif (floor($bytes / 1024) > 0) { + $r = round($bytes / 1024, 2) . 'KB'; + } else { + $r = $bytes . 'B'; + } + return $r; + } + + public function pacMenu() + { + $rmpac = stat(__DIR__ . '/zapretlists/rmpac'); + $rpac = stat(__DIR__ . '/zapretlists/rpac'); + $mpac = stat(__DIR__ . '/zapretlists/mpac'); + $pac = stat(__DIR__ . '/zapretlists/pac'); + $conf = $this->getPacConf(); + $zapret = $conf['zapret'] ? 'ON' : 'OFF'; + $ip = $conf['domain'] ?: file_get_contents('https://ipinfo.io/ip'); + $hash = substr(md5($this->key), 0, 8); + $scheme = empty($this->nginxGetTypeCert()) ? 'http' : 'https'; + $text = << pac + + RU blacklist: https://github.com/zapret-info/z-i + + self lists - domains that will work through a proxy + + reverse lists - domains that will work without a proxy, all others through a proxy + text; + if ($pac) { + $pac['time'] = date('d.m.Y H:i:s', $pac['mtime']); + $pac['sz'] = $this->sizeFormat($pac['size']); + $text .= <<PAC ({$pac['time']} / {$pac['sz']}): + $scheme://$ip/pac?h=$hash&a=127.0.0.1&p=1080 + text; + $urls[] = [ + 'text' => "PAC", + 'url' => "$scheme://$ip/pac?h=$hash&a=127.0.0.1&p=1080", + ]; + $urls[] = [ + 'text' => "PAC Wireguard proxy", + 'url' => "$scheme://$ip/pac?h=$hash&a=10.10.0.3&p=1080", + ]; + } + if ($mpac) { + $mpac['time'] = date('d.m.Y H:i:s', $mpac['mtime']); + $mpac['sz'] = $this->sizeFormat($mpac['size']); + $text .= <<Shadowsocks-android PAC ({$mpac['time']} / {$mpac['sz']}): + $scheme://$ip/pac?h=$hash&t=mpac + text; + $urls[] = [ + 'text' => "PAC ShadowSocks(Android)", + 'url' => "$scheme://$ip/pac?h=$hash&t=mpac", + ]; + } + if ($rpac) { + $rpac['time'] = date('d.m.Y H:i:s', $rpac['mtime']); + $rpac['sz'] = $this->sizeFormat($rpac['size']); + $text .= <<Reverse PAC ({$rpac['time']} / {$rpac['sz']}): + $scheme://$ip/pac?h=$hash&t=rpac&a=127.0.0.1&p=1080 + text; + $urls[] = [ + 'text' => "Reverse PAC", + 'url' => "$scheme://$ip/pac?h=$hash&t=rpac", + ]; + $urls[] = [ + 'text' => "Reverse PAC Wireguard proxy", + 'url' => "$scheme://$ip/pac?h=$hash&t=rpac&a=10.10.0.3", + ]; + } + if ($rmpac) { + $rmpac['time'] = date('d.m.Y H:i:s', $rmpac['mtime']); + $rmpac['sz'] = $this->sizeFormat($rmpac['size']); + $text .= <<Reverse shadowsocks-android PAC ({$rmpac['time']} / {$rmpac['sz']}): + $scheme://$ip/pac?h=$hash&t=rmpac + text; + $urls[] = [ + 'text' => "Reverse PAC SS(Android)", + 'url' => "$scheme://$ip/pac?h=$hash&t=rmpac", + ]; + } + if ($urls) { + $data = array_chunk($urls, 2); + } + if ($conf['zapret']) { + $data[] = [ + [ + 'text' => "RU blacklist : $zapret", + 'callback_data' => "/paczapret", ], [ - [ - 'text' => "download", - 'callback_data' => "/download $client", - ], - ], - [ - [ - 'text' => "delete", - 'callback_data' => "/delete $client", - ], - ], - [ - [ - 'text' => "back", - 'callback_data' => "/menu", - ], + 'text' => 'blacklist exclude', + 'callback_data' => "/menu excludelist 0", ], ]; } else { $data[] = [ [ - 'text' => "update status", - 'callback_data' => "/menu", + 'text' => "RU blacklist : $zapret", + 'callback_data' => "/paczapret", ], ]; - if (!empty($clients)) { - foreach ($clients as $k => $v) { - $data[] = [[ - 'text' => $this->getName($v['interface']), - 'callback_data' => "/client $k", - ]]; - } - } - $data[] = [ - [ - 'text' => "add peer", - 'callback_data' => "/showadd", - ], - [ - 'text' => "PAC script", - 'callback_data' => "/showpac", - ], - ]; - $data[] = [ - [ - 'text' => "export", - 'callback_data' => "/export", - ], - [ - 'text' => "import", - 'callback_data' => "/import", - ], - [ - 'text' => "reset", - 'callback_data' => "/showreset", - ], - ]; - $conf = $this->readConfig(); - $status = $this->readStatus(); - $text[] = 'Server:'; - $text[] = " address: {$conf['interface']['Address']}"; - $text[] = " port: {$status['interface']['listening port']}"; - $text[] = " publickey: {$status['interface']['public key']}"; - $text[] = "\nPeers:"; - foreach ($conf['peers'] as $k => $v) { - foreach ($clients as $cl) { - if ($cl['interface']['Address'] == $v['AllowedIPs']) { - $allowed_ips = $cl['peers'][0]['AllowedIPs']; - } - } - $peer = $this->getStatusPeer($v['PublicKey'], $status['peers']); - $text[] = " {$this->getName($v)}: " . (preg_match('~^(\d+ seconds|[12] minute)~', $peer['latest handshake']) ? 'ONLINE' : 'OFFLINE') . ($peer['transfer'] ? " {$peer['transfer']}": ''); - $text[] = " address: {$peer['allowed ips']}"; - $text[] = " allowed ips: $allowed_ips"; - $text[] = " publickey: {$peer['peer']}"; - if ($peer['latest handshake']) { - $text[] = " endpoint: {$peer['endpoint']}"; - $text[] = " handshake: {$peer['latest handshake']}"; - } - $text[] = ''; - } - $text = '' . implode(PHP_EOL, $text) . ''; } - $data[] = [[ - 'text' => 'donate', - 'url' => "https://yoomoney.ru/to/410011827900450", - ]]; + $data[] = [ + [ + 'text' => 'self list', + 'callback_data' => "/menu includelist 0", + ], + [ + 'text' => 'subzones', + 'callback_data' => "/menu subzoneslist 0", + ], + [ + 'text' => "reverse list", + 'callback_data' => "/menu reverselist 0", + ], + ]; + if ($conf['zapret'] || !empty($conf['includelist'])) { + $data[] = [ + [ + 'text' => 'update PAC', + 'callback_data' => "/pacupdate", + ], + [ + 'text' => 'check url', + 'callback_data' => "/checkurl", + ], + ]; + } + $data[] = [ + [ + 'text' => 'back', + 'callback_data' => "/menu", + ], + ]; + return [ + 'text' => $text, + 'data' => $data, + ]; + } + + public function pacList($type, int $page, int $count = 5) + { + $name = str_replace('list', '', $type); + $text = "Menu -> pac -> {$name}list\n\n"; + $data[] = [ + [ + 'text' => 'add', + 'callback_data' => "/$name $count", + ], + ]; + $domains = $this->getPacConf()[$type]; + if (!empty($domains)) { + ksort($domains); + $all = (int) ceil(count($domains) / $count); + $page = min($page, $all - 1); + $page = $page == -2 ? $all - 1 : $page; + $domains = $page != -1 ? array_slice($domains, $page * $count, $count, true) : $domains; + foreach ($domains as $k => $v) { + $data[] = [ + [ + 'text' => '(' . ($v ? 'ON' : 'OFF') . ') ' . ($type == 'includelist' ? idn_to_utf8($k) : $k), + 'callback_data' => "/change{$name}list {$k}_$count", + ], + [ + 'text' => 'delete', + 'callback_data' => "/delete{$name}list {$k}_$count", + ], + ]; + } + if ($page != -1 && $all > 1) { + $data[] = [ + [ + 'text' => '<<', + 'callback_data' => "/menu $type " . ($page - 1 >= 0 ? $page - 1 : $all - 1), + ], + // [ + // 'text' => 'all', + // 'callback_data' => "/menu $type -1", + // ], + [ + 'text' => '>>', + 'callback_data' => "/menu $type " . ($page < $all - 1 ? $page + 1 : 0), + ] + ]; + } + } + $data[] = [ + [ + 'text' => 'back', + 'callback_data' => "/menu pac", + ], + ]; + return [ + 'text' => $text, + 'data' => $data, + ]; + } + + public function listPacChange($type, $action, $key, int $count) + { + $conf = $this->getPacConf(); + ksort($conf[$type]); + $page = (int) floor(array_search($key, array_keys($conf[$type])) / $count); + switch ($action) { + case 'change': + $conf[$type][$key] = !$conf[$type][$key]; + $page = (int) floor(array_search($key, array_keys($conf[$type])) / $count); + break; + case 'delete': + unset($conf[$type][$key]); + break; + } + $this->setPacConf($conf); + $this->menu($type, $page); + } + + public function pacZapret() + { + $conf = $this->getPacConf(); + $conf['zapret'] = !$conf['zapret']; + $this->setPacConf($conf); + $this->menu('pac'); + } + + public function pacUpdate() + { + exec("php updatepac.php start {$this->input['chat']} {$this->input['message_id']} {$this->input['callback_id']} > /dev/null &"); + } + + public function getSSConfig() + { + return json_decode(file_get_contents('/config/ssserver.json'), true); + } + + public function getSSLocalConfig() + { + return json_decode(file_get_contents('/config/sslocal.json'), true); + } + + public function menuSS() + { + $conf = $this->getPacConf(); + $ip = file_get_contents('https://ipinfo.io/ip'); + $domain = $conf['domain'] ?: $ip; + $scheme = empty($ssl = $this->nginxGetTypeCert()) ? 'http' : 'https'; + $ss = $this->getSSConfig(); + $v2ray = !empty($ss['plugin']) ? 'ON' : 'OFF'; + $port = !empty($ssl) && !empty($ss['plugin']) ? 443 : 8388; + $options = !empty($ssl) && !empty($ss['plugin']) ? "tls;fast-open;path=/v2ray;host=$domain" : "path=/v2ray;host=$domain"; + + $text = "Menu -> ShadowSocks"; + $data[] = [ + [ + 'text' => 'change password', + 'callback_data' => "/sspswd", + ], + ]; + $text .= "\n\nserver: $domain:$port"; + $text .= "\n\nmethod: {$ss['method']}"; + $text .= "\n\nnameserver: 10.10.0.5"; + if ($ss['plugin']) { + $text .= "\n\nplugin: v2ray-plugin_windows_amd64"; + $text .= "\n\nv2ray options: $options"; + } + $data[] = [ + [ + 'text' => "v2ray: $v2ray", + 'callback_data' => "/v2ray", + ], + ]; + $data[] = [ + [ + 'text' => 'back', + 'callback_data' => "/menu", + ], + ]; + return [ + 'text' => $text, + 'data' => $data, + ]; + } + + public function menu($type = false, $arg = false, $return = false) + { + $menu = [ + 'main' => [ + 'text' => "Menu", + 'data' => [ + [ + [ + 'text' => "Wireguard", + 'callback_data' => "/menu wg 0", + ], + [ + 'text' => "Shadowsocks", + 'callback_data' => "/menu ss", + ], + ], + [ + [ + 'text' => "Adguard", + 'callback_data' => "/menu adguard", + ], + [ + 'text' => "PAC", + 'callback_data' => "/menu pac", + ], + ], + [ + [ + 'text' => "config", + 'callback_data' => "/menu config", + ] + ], + [ + [ + 'text' => 'discussion group', + 'url' => "https://t.me/vpnbot_group", + ], + [ + 'text' => 'donate for a new laptop', + 'url' => "https://yoomoney.ru/to/410011827900450", + ], + ] + ], + ], + 'wg' => $type == 'wg' ? $this->statusWg($arg) : false, + 'client' => $type == 'client' ? $this->getClient(...explode('_', $arg)) : false, + 'addpeer' => [ + 'text' => "Menu -> Wireguard -> Add peer\n\n", + 'data' => [ + [[ + 'text' => "all traffic", + 'callback_data' => "/add", + ]], + [[ + 'text' => "subnet", + 'callback_data' => "/add_ips", + ]], + [[ + 'text' => "proxy ip", + 'callback_data' => "/proxy", + ]], + [[ + 'text' => "back", + 'callback_data' => "/menu wg $arg", + ]], + ], + ], + 'pac' => $type == 'pac' ? $this->pacMenu() : false, + 'adguard' => $type == 'adguard' ? $this->adguardMenu() : false, + 'includelist' => $type == 'includelist' ? $this->pacList($type, $arg) : false, + 'excludelist' => $type == 'excludelist' ? $this->pacList($type, $arg) : false, + 'reverselist' => $type == 'reverselist' ? $this->pacList($type, $arg) : false, + 'subzoneslist' => $type == 'subzoneslist' ? $this->pacList($type, $arg) : false, + 'config' => $type == 'config' ? $this->configMenu() : false, + 'ss' => $type == 'ss' ? $this->menuSS() : false, + ]; + + $text = $menu[$type ?: 'main' ]['text']; + $data = $menu[$type ?: 'main' ]['data']; if ($return) { return [$text, $data]; @@ -525,18 +1490,154 @@ class Bot $this->input['chat'], $this->input['message_id'], $text, - $data, + $data ?: false, ); } else { $this->send( $this->input['chat'], $text, $this->input['message_id'], - $data, + $data ?: false, ); } } + public function adguardMenu() + { + $conf = $this->getPacConf(); + $ip = file_get_contents('https://ipinfo.io/ip'); + $domain = $conf['domain'] ?: $ip; + $scheme = empty($ssl = $this->nginxGetTypeCert()) ? 'http' : 'https'; + + $text = "Menu -> Adguard Home\n\nDNS server:\n$ip\n\n"; + if ($ssl) { + $text .= "DNS over HTTPS:\n$ip\n$scheme://$domain/dns-query\n\n"; + $text .= "DNS over TLS:\n$ip:853"; + } + $data = [ + [ + [ + 'text' => 'Adguard Home', + 'url' => "$scheme://$domain/adguard", + ], + [ + 'text' => 'change password', + 'callback_data' => "/adguardpsswd", + ], + ], + ]; + $data[] = [ + [ + 'text' => 'add upstream', + 'callback_data' => "/addupstream", + ], + ]; + $upstreams = yaml_parse_file('/config/adguard/AdGuardHome.yaml')['dns']['upstream_dns']; + if (!empty($upstreams)) { + foreach ($upstreams as $k => $v) { + $data[] = [ + [ + 'text' => $v, + 'callback_data' => "/menu adguard", + ], + [ + 'text' => 'delete', + 'callback_data' => "/delupstream $k", + ], + ]; + } + } + $data[] = [ + [ + 'text' => 'check DNS', + 'callback_data' => "/checkdns", + ], + ]; + $data[] = [ + [ + 'text' => 'back', + 'callback_data' => "/menu", + ], + ]; + return [ + 'text' => $text, + 'data' => $data, + ]; + } + + public function configMenu() + { + $conf = $this->getPacConf(); + $text = "Menu -> Config\n\nSome clients require a valid certificate when connecting, such as windows 11 DoH or ShadowSocks Android (PAC url), this requires a domain"; + $data = [ + [ + [ + 'text' => $conf['domain'] ? "delete {$conf['domain']}" : 'install domain', + 'callback_data' => $conf['domain'] ? '/deldomain' : '/domain', + ], + ], + ]; + if ($conf['domain']) { + if ($cert = $this->nginxGetTypeCert()) { + switch ($cert) { + case 'letsencrypt': + $data[] = [ + [ + 'text' => 'renew SSL', + 'callback_data' => "/setSSL letsencrypt", + ], + [ + 'text' => 'delete SSL', + 'callback_data' => "/deletessl", + ], + ]; + break; + case 'self': + $data[] = [ + [ + 'text' => 'delete SSL', + 'callback_data' => "/deletessl", + ], + ]; + break; + } + } else { + $data[] = [ + [ + 'text' => 'Letsencrypt SSL', + 'callback_data' => "/setSSL letsencrypt", + ], + [ + 'text' => 'Self SSL', + 'callback_data' => "/selfssl", + ], + ]; + } + } + $data[] = [ + [ + 'text' => 'import', + 'callback_data' => "/import", + ], + ]; + $data[] = [ + [ + 'text' => 'export', + 'callback_data' => "/export", + ], + ]; + $data[] = [ + [ + 'text' => 'back', + 'callback_data' => "/menu", + ], + ]; + return [ + 'text' => $text, + 'data' => $data, + ]; + } + public function getStatusPeer(string $publickey, array $peers) { foreach ($peers as $k => $v) { @@ -577,6 +1678,13 @@ class Bot return $d; } + public function nginxGetTypeCert() + { + $conf = $this->ssh('cat /etc/nginx/nginx.conf', 'ng'); + preg_match("/#~([^\s]+)/", $conf, $m); + return $m[1]; + } + public function readStatus() { $r = $this->ssh('wg'); @@ -672,19 +1780,20 @@ class Bot 'PrivateKey' => $private_peer_key, 'Address' => "$client_ip/32", 'MTU' => 1350, - 'DNS' => '8.8.8.8', + 'DNS' => '10.10.0.5', ], 'peers' => [ [ 'PublicKey' => $public_server_key, - 'Endpoint' => "{$_SERVER['HTTP_HOST']}:{$_SERVER['PORT_WG']}", + 'Endpoint' => "{$_SERVER['HTTP_HOST']}:" . getenv('PORT_WG'), 'AllowedIPs' => $ips_user ?: "0.0.0.0/0", 'PersistentKeepalive' => 20, ] ] ]; - $this->saveClient($client_conf); + $k = $this->saveClient($client_conf); $this->restartWG($this->createConfig($conf)); + $this->menu('client', "{$k}_-2"); } public function deleteClient(int $client) @@ -696,7 +1805,9 @@ class Bot public function saveClient(array $client) { - $this->saveClients(array_merge($this->readClients(), [$client])); + $r = array_merge($this->readClients(), [$client]); + $this->saveClients($r); + return count($r) - 1; } public function saveClients(array $clients) @@ -712,12 +1823,26 @@ class Bot return true; } - public function ssh($cmd) + public function disconnect(...$args) + { + $this->send($this->input['chat'], "disconnect: \n" . var_export($args, true) . "\n", $this->input['message_id']); + } + + public function ssh($cmd, $service = 'wg') { try { - $c = ssh2_connect('wg', 22); - ssh2_auth_pubkey_file($c, 'root', '/ssh/key.pub', '/ssh/key'); + $c = ssh2_connect($service, 22); + if (empty($c)) { + throw new Exception("no connection to $service: \n$cmd\n" . var_export($c, true)); + } + $a = ssh2_auth_pubkey_file($c, 'root', '/ssh/key.pub', '/ssh/key'); + if (empty($a)) { + throw new Exception("auth fail: \n$cmd\n" . var_export($a, true)); + } $s = ssh2_exec($c, $cmd); + if (empty($s)) { + throw new Exception("exec fail: \n$cmd\n" . var_export($s, true)); + } stream_set_blocking($s, true); $data = ""; while ($buf = fread($s, 4096)) { @@ -726,7 +1851,7 @@ class Bot fclose($s); ssh2_disconnect($c); } catch (Exception | Error $e) { - $this->send($this->input['chat'], 'no connection to wg', $this->input['message_id']); + $this->send($this->input['chat'], $e->getMessage(), $this->input['message_id']); die(); } return $data; @@ -756,8 +1881,8 @@ class Bot } echo "$ip\n"; var_dump($this->request('setWebhook', [ - 'url' => "https://$ip/{$this->key}", - 'certificate' => curl_file_create('/cert/nginx_public.pem'), + 'url' => "https://$ip/tlgrm?k={$this->key}", + 'certificate' => curl_file_create('/certs/self_public'), ])); } @@ -774,7 +1899,7 @@ class Bot var_dump($this->request('setMyCommands', json_encode($data), 1)); } - public function send($chat, $text, ?int $to = 0, $button = false, $reply = false) + public function send($chat, $text, ?int $to = 0, $button = false, $reply = false, $mode = 'HTML') { if ($button) { $extra = ['inline_keyboard' => $button]; @@ -793,7 +1918,7 @@ class Bot $data = [ 'chat_id' => $chat, 'text' => "$v\n", - 'parse_mode' => 'HTML', + 'parse_mode' => $mode, // 'disable_web_page_preview' => true, // 'disable_notification' => !empty($to) && 0 == $k, 'reply_to_message_id' => 0 == $k && $to > 0 ? $to : false, @@ -809,7 +1934,7 @@ class Bot $data = [ 'chat_id' => $chat, 'text' => $text, - 'parse_mode' => 'HTML', + 'parse_mode' => $mode, // 'disable_web_page_preview' => true, // 'disable_notification' => !empty($to), 'reply_to_message_id' => $to, @@ -864,7 +1989,7 @@ class Bot ]); } - public function update($chat, $message_id, $text, $button = false, $reply = false) + public function update($chat, $message_id, $text, $button = false, $reply = false, $mode = 'HTML') { if ($button) { $extra = ['inline_keyboard' => $button]; @@ -879,7 +2004,7 @@ class Bot 'chat_id' => $chat, 'message_id' => $message_id, 'text' => $text, - 'parse_mode' => 'HTML', + 'parse_mode' => $mode, 'disable_web_page_preview' => true, ]; if (!empty($extra)) { diff --git a/app/debug.php b/app/debug.php index 12b3d5f..25ea8d1 100644 --- a/app/debug.php +++ b/app/debug.php @@ -1,9 +1,9 @@ file_get_contents('php://input'), @@ -17,5 +17,5 @@ function exit_log($debug) { $output = ob_get_contents(); $debug['response'] = json_decode($output, true) ?: $output; - file_put_contents(__DIR__ . '/debug', "\n" . date('Y-m-d H:i:s') . "\n" . var_export($debug, true) . "\n", FILE_APPEND); + file_put_contents('/logs/requests', "\n" . date('Y-m-d H:i:s') . "\n" . var_export($debug, true) . "\n", FILE_APPEND); } diff --git a/app/index.php b/app/index.php index a350281..c231152 100644 --- a/app/index.php +++ b/app/index.php @@ -1,14 +1,36 @@ input(); die(); } -$bot = new Bot($key); -$bot->input(); +// pac +$type = $_GET['t'] ?? 'pac'; +$address = $_GET['a'] ?: '127.0.0.1'; +$port = $_GET['p'] ?: '1080'; +$hash = $_GET['h']; +if ($hash == substr(md5($key), 0, 8)) { + if (file_exists($file = __DIR__ . "/zapretlists/$type")) { + $pac = file_get_contents($file); + header("Content-Type: text/plain"); + echo str_replace([ + '~address~', + '~port~', + ], [ + $address, + $port + ], $pac); + die(); + } +} + +header('500', true, 500); +die(); diff --git a/app/updatepac.php b/app/updatepac.php new file mode 100644 index 0000000..b194af6 --- /dev/null +++ b/app/updatepac.php @@ -0,0 +1,320 @@ + $v) { + $curr ++; + $percent = (int) ceil($curr * 100 / $size); + $rotate = $curr != $size ? $load[$j % count($load)] : ''; + $tail = << 0.1) { + update($text . $tail); + $time = microtime(true); + $j++; + } + switch (true) { + case $last == $k: + $r .= strlen($phrase) > 1 ? mb_chr($keys[$phrase]) : $v; + break; + case !empty($keys[$phrase . $str[$k + 1]]): + $phrase .= $str[$k + 1]; + break; + default: + $r .= strlen($phrase) > 1 ? mb_chr($keys[$phrase]) : $v; + $keys[$phrase . $str[$k + 1]] = $code; + $code++; + $phrase = $str[$k + 1]; + } + } + return $r; +} + +function dlzw($text) +{ + $text = mb_str_split($text); + $code = 256; + $r = $phrase = ''; + $keys = []; + $last = count($text) - 1; + foreach ($text as $k => $v) { + $cc = mb_ord($v); + $r .= $cc < 256 ? $v : $keys[$cc]; + if ($last == $k) { + break; + } + if (!empty($phrase)) { + $keys[$code] = $phrase . ($cc < 256 ? $v : $keys[$cc][0]); + $code++; + } + $phrase = $cc < 256 ? $v : $keys[$cc]; + } + return $r; +} + +function getSize($url) +{ + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_HEADER => 1, + CURLOPT_NOBODY => 1, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_TIMEOUT => 10, + ]); + $r = curl_exec($ch); + curl_close($ch); + preg_match('~content-length.+?(\d+)~ius', $r, $m); + return (int) $m[1]; +} + +function update($text) +{ + global $bot; + $bot->update($bot->input['chat'], $bot->input['message_id'], $text); +} + +function start() +{ + global $bot, $source, $res, $load, $text; + + $conf = $bot->getPacConf(); + + $subzones = array_keys(array_filter($conf['subzoneslist'], fn($x) => $x == true)); + + // check && exclude + $include = array_filter($conf['includelist'], fn($x) => $x == true); + if ($conf['zapret']) { + foreach ($source as $k => $v) { + $size = getSize($v['source']); + if ($size > 0) { + if (file_exists($v['dest'])) { + unlink($v['dest']); + } + touch($v['dest']); + $percent = 0; + $curr = 0; + exec("php updatepac.php download $k > /dev/null &"); + $i = $j = 0; + while ($curr <= $size) { + $rotate = $curr != $size ? $load[$j % count($load)] : ''; + $tail = << 50) { + break; + } + usleep(50000); + clearstatcache(); + if ($curr == filesize($v['dest'])) { + $i++; + } + $curr = filesize($v['dest']); + $percent = (int) ceil($curr * 100 / $size); + $j++; + } + if ($curr != $size) { + $text .= "\nError downloading {$v['source']}\nabort script"; + endScript($text); + } + } else { + $text .= "\nError size {$v['source']}\nabort script"; + endScript($text); + } + $text .= $tail ? "$tail\n" : ''; + } + // prepare + $reg = '~^([^.*]+\.(?:(?:' . implode('|', $subzones) . ')\.)?[^.]+)$~'; + + foreach ($source as $k => $v) { + $size = filesize($v['dest']); + $curr = $j = 0; + $time = microtime(true); + $name = basename($v['dest']); + $f = fopen($v['dest'], 'r'); + while (($s = fgets($f)) !== false) { + $curr += strlen($s); + $percent = (int) ceil($curr * 100 / $size); + $rotate = $curr != $size ? $load[$j % count($load)] : ''; + $tail = << 0.1) { + update($text . $tail); + $time = microtime(true); + $j++; + } + if ($name == 'dump.csv') { + if (!preg_match('~;.*;~', $s)) { + continue; + } + $t = explode(';', iconv('CP1251', 'utf-8', $s)); + if (empty($t[1])) { + continue; + } + if (preg_match($reg, idn_to_ascii(trim($t[1])), $m)) { + $domains[$m[1]] = 0; + } + } else { + if (preg_match($reg, idn_to_ascii(iconv('CP1251', 'utf-8', trim($s))), $m)) { + $domains[$m[1]] = 0; + } + } + } + fclose($f); + $text .= $tail ? "$tail\n" : ''; + } + $exclude = array_keys(array_filter($conf['excludelist'], fn($x) => $x == true)); + foreach ($include as $k => $v) { + $domains[$k] = 0; + } + } else { + $domains = $include; + } + if (empty($domains)) { + $text = "Empty domains. Delete pac files"; + unlink(__DIR__ . '/zapretlists/mpac'); + unlink(__DIR__ . '/zapretlists/pac'); + } else { + $domains = array_keys($domains); + + $size = count($domains); + $curr = $j = 0; + $time = microtime(true); + $f = fopen(__DIR__ . '/zapretlists/mpac', 'w'); + $t = []; + foreach ($domains as $v) { + $curr ++; + $percent = (int) ceil($curr * 100 / $size); + $rotate = $curr != $size ? $load[$j % count($load)] : ''; + $tail = << 0.1) { + update($text . $tail); + $time = microtime(true); + $j++; + } + if ($exclude && preg_match('~' . implode('|', $exclude) . '~', $v)) { + continue; + } + fwrite($f, preg_quote($v) . "\n"); + + preg_match('~(.+)\.([^.]+)$~', $v, $m); + $t[$m[2]][strlen($m[1])][] = $m[1]; + } + fclose($f); + $text .= $tail ? "$tail\n" : ''; + + // create pac + $domains = elzw(json_encode($t)); + $pac = 'function FindProxyForURL(t,e){"indexOf"in Array.prototype||(Array.prototype.indexOf=function(t,e){void 0===e&&(e=0),e<0&&(e+=this.length),e<0&&(e=0);for(var n=this.length;e0&&(keys[code]=phrase+(cc<256?text[e]:keys[cc][0]),code++),phrase=cc<256?text[e]:keys[cc]}return o});var n=JSON.parse(\'' . $domains . '\'.dlzw()),r=(/\.(' . implode('|', $subzones) . ')\.[^.]+$/.test(e)?e.replace(/(.+)\.([^.]+\.[^.]+\.[^.]+$)/,"$2"):e.replace(/(.+)\.([^.]+\.[^.]+$)/,"$2")).replace(/^www\.(.+)/,"$1").match(/(.*)\.([^.]+$)/);if(console.log(n,r),!r||!r[2])return"DIRECT";var o=r[1],i=r[2],a=[];if(n.hasOwnProperty(i)&&n[i].hasOwnProperty(o.length)){if("string"==typeof n[i][o.length]){var l=RegExp(".{"+o.length.toString()+"}","g");n[i][o.length]=n[i][o.length].match(l)}a=n[i][o.length]}return -1!==a.indexOf(o)?"SOCKS5 ~address~:~port~; DIRECT":"DIRECT"}'; + file_put_contents(__DIR__ . '/zapretlists/pac', $pac); + $text .= "Create minified pac 100%\n"; + } + + // create reverse PAC + $domains = array_filter($conf['reverselist'], fn($x) => $x == true); + if (empty($domains)) { + $text .= "Empty reverse domains. Delete reverse pac files"; + unlink(__DIR__ . '/zapretlists/rmpac'); + unlink(__DIR__ . '/zapretlists/rpac'); + } else { + $domains = array_keys($domains); + + // $size = count($domains); + // $curr = $j = 0; + // $time = microtime(true); + // $f = fopen(__DIR__ . '/zapretlists/rmpac', 'w'); + // $t = []; + // foreach ($domains as $v) { + // $curr ++; + // $percent = (int) ceil($curr * 100 / $size); + // $rotate = $curr != $size ? $load[$j % count($load)] : ''; + // $tail = << 0.1) { + // update($text . $tail); + // $time = microtime(true); + // $j++; + // } + // fwrite($f, '!(' . preg_quote($v) . ')' . "\n"); + + // preg_match('~(.+)\.([^.]+)$~', $v, $m); + // $t[$m[2]][strlen($m[1])][] = $m[1]; + // } + // fclose($f); + $text .= $tail ? "$tail\n" : ''; + + $domains = elzw(json_encode($t)); + $pac = 'function FindProxyForURL(t,e){"indexOf"in Array.prototype||(Array.prototype.indexOf=function(t,e){void 0===e&&(e=0),e<0&&(e+=this.length),e<0&&(e=0);for(var n=this.length;e0&&(keys[code]=phrase+(cc<256?text[e]:keys[cc][0]),code++),phrase=cc<256?text[e]:keys[cc]}return o});var n=JSON.parse(\'' . $domains . '\'.dlzw()),r=(/\.(' . implode('|', $subzones) . ')\.[^.]+$/.test(e)?e.replace(/(.+)\.([^.]+\.[^.]+\.[^.]+$)/,"$2"):e.replace(/(.+)\.([^.]+\.[^.]+$)/,"$2")).replace(/^www\.(.+)/,"$1").match(/(.*)\.([^.]+$)/);if(console.log(n,r),!r||!r[2])return"DIRECT";var o=r[1],i=r[2],a=[];if(n.hasOwnProperty(i)&&n[i].hasOwnProperty(o.length)){if("string"==typeof n[i][o.length]){var l=RegExp(".{"+o.length.toString()+"}","g");n[i][o.length]=n[i][o.length].match(l)}a=n[i][o.length]}return -1!==a.indexOf(o)?"DIRECT":"SOCKS5 ~address~:~port~"}'; + file_put_contents(__DIR__ . '/zapretlists/rpac', $pac); + $text .= 'Create minified reverse pac 100%'; + } + endScript($text); +} + +function endScript($text) +{ + global $bot; + update($text); + sleep(2); + $bot->menu('pac'); + die(); +} + +function download($index) +{ + global $source; + file_put_contents($source[$index]['dest'], file_get_contents($source[$index]['source'])); +} + +require __DIR__ . '/debug.php'; +require __DIR__ . '/bot.php'; +require __DIR__ . '/config.php'; +$source = [ + [ + 'source' => 'https://raw.githubusercontent.com/zapret-info/z-i/master/nxdomain.txt', + 'dest' => __DIR__ . '/zapretlists/nxdomain.txt', + ], + [ + 'source' => 'https://raw.githubusercontent.com/zapret-info/z-i/master/dump.csv', + 'dest' => __DIR__ . '/zapretlists/dump.csv', + ], +]; + +$res = __DIR__ . '/zapretlists/result'; + +$text = ''; +$load = [ + '/', + '-', + '\\', +]; + +switch ($_SERVER['argv'][1]) { + case 'start': + $bot = new Bot($key); + $bot->input['chat'] = $_SERVER['argv'][2]; + $bot->input['message_id'] = $_SERVER['argv'][3]; + $bot->input['callback_id'] = $_SERVER['argv'][4]; + start(); + break; + case 'download': + download($_SERVER['argv'][2]); + break; +} diff --git a/logs/debug b/app/zapretlists/.gitkeep similarity index 100% rename from logs/debug rename to app/zapretlists/.gitkeep diff --git a/logs/error_log b/certs/.gitkeep similarity index 100% rename from logs/error_log rename to certs/.gitkeep diff --git a/config/AdGuardHome.yaml b/config/AdGuardHome.yaml new file mode 100644 index 0000000..d1ec808 --- /dev/null +++ b/config/AdGuardHome.yaml @@ -0,0 +1,145 @@ +bind_host: 0.0.0.0 +bind_port: 80 +beta_bind_port: 0 +users: + - name: admin + password: +auth_attempts: 5 +block_auth_min: 15 +http_proxy: "" +language: "" +debug_pprof: false +web_session_ttl: 720 +dns: + bind_hosts: + - 0.0.0.0 + port: 53 + statistics_interval: 1 + querylog_enabled: true + querylog_file_enabled: true + querylog_interval: 2160h + querylog_size_memory: 1000 + anonymize_client_ip: false + protection_enabled: true + blocking_mode: default + blocking_ipv4: "" + blocking_ipv6: "" + blocked_response_ttl: 10 + parental_block_host: family-block.dns.adguard.com + safebrowsing_block_host: standard-block.dns.adguard.com + ratelimit: 20 + ratelimit_whitelist: [] + refuse_any: true + upstream_dns: + - https://dns10.quad9.net/dns-query + upstream_dns_file: "" + bootstrap_dns: + - 9.9.9.10 + - 149.112.112.10 + - 2620:fe::10 + - 2620:fe::fe:10 + all_servers: false + fastest_addr: false + fastest_timeout: 1s + allowed_clients: [] + disallowed_clients: [] + blocked_hosts: + - version.bind + - id.server + - hostname.bind + trusted_proxies: + - 10.10.0.0/24 + - 127.0.0.0/8 + - ::1/128 + cache_size: 4194304 + cache_ttl_min: 0 + cache_ttl_max: 0 + cache_optimistic: false + bogus_nxdomain: [] + aaaa_disabled: false + enable_dnssec: false + edns_client_subnet: false + max_goroutines: 300 + handle_ddr: true + ipset: [] + ipset_file: "" + filtering_enabled: true + filters_update_interval: 24 + parental_enabled: false + safesearch_enabled: false + safebrowsing_enabled: false + safebrowsing_cache_size: 1048576 + safesearch_cache_size: 1048576 + parental_cache_size: 1048576 + cache_time: 30 + rewrites: [] + blocked_services: [] + upstream_timeout: 10s + private_networks: [] + use_private_ptr_resolvers: true + local_ptr_upstreams: [] + serve_http3: false + use_http3_upstreams: false +tls: + enabled: false + server_name: "" + force_https: false + port_https: 443 + port_dns_over_tls: 853 + port_dns_over_quic: 853 + port_dnscrypt: 0 + dnscrypt_config_file: "" + allow_unencrypted_doh: false + certificate_chain: "" + private_key: "" + certificate_path: "" + private_key_path: "" + strict_sni_check: false +filters: + - enabled: true + url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt + name: AdGuard DNS filter + id: 1 + - enabled: false + url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt + name: AdAway Default Blocklist + id: 2 +whitelist_filters: [] +user_rules: [] +dhcp: + enabled: false + interface_name: "" + local_domain_name: lan + dhcpv4: + gateway_ip: "" + subnet_mask: "" + range_start: "" + range_end: "" + lease_duration: 86400 + icmp_timeout_msec: 1000 + options: [] + dhcpv6: + range_start: "" + lease_duration: 86400 + ra_slaac_only: false + ra_allow_slaac: false +clients: + runtime_sources: + whois: true + arp: true + rdns: true + dhcp: true + hosts: true + persistent: [] +log_file: "" +log_max_backups: 0 +log_max_size: 100 +log_max_age: 3 +log_compress: false +log_localtime: false +verbose: false +os: + group: "" + user: "" + rlimit_nofile: 0 +schema_version: 14 diff --git a/config/clients.json b/config/clients.json index e69de29..fe51488 100644 --- a/config/clients.json +++ b/config/clients.json @@ -0,0 +1 @@ +[] diff --git a/config/nginx.conf b/config/nginx.conf new file mode 100644 index 0000000..aa739ac --- /dev/null +++ b/config/nginx.conf @@ -0,0 +1,177 @@ +user nginx; +worker_processes auto; + +load_module modules/ngx_stream_js_module.so; + +error_log /logs/nginx_error; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Proxy Cache storage - so we can cache the DoH response from the upstream + proxy_cache_path /var/cache/nginx/doh_cache levels=1:2 keys_zone=doh_cache:10m; + + server { + listen 80 default_server; + listen 443 ssl http2 default_server; + ssl_certificate /certs/self_public; + ssl_certificate_key /certs/self_private; + + access_log /logs/nginx_default_access; + + location / { + return 444; + } + location /adguard/ { + access_log /logs/nginx_adguard_access; + proxy_pass http://ad:80/; + proxy_redirect / /adguard/; + proxy_cookie_path / /adguard/; + } + location /pac { + access_log /logs/nginx_pac_access; + proxy_pass http://unit; + } + location /tlgrm { + access_log /logs/nginx_tlgrm_access; + proxy_pass http://unit; + } + location /v2ray { + access_log /logs/nginx_v2ray_access; + proxy_redirect off; + proxy_buffering off; + proxy_http_version 1.1; + proxy_pass http://ss:8388/; + proxy_set_header Host $http_host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + location ~\.well-known { + access_log /logs/nginx_certbot_access; + root /certs/; + try_files $uri =404; + } + } + + #~ + + #~domain + # server { + # listen 443 ssl http2; + # server_name ; + # ssl_certificate /certs/cert_public; + # ssl_certificate_key /certs/cert_private; + + # access_log /logs/nginx_domain_access; + + # location / { + # return 444; + # } + # location /adguard/ { + # access_log /logs/nginx_adguard_access; + # proxy_pass http://ad:80/; + # proxy_redirect / /adguard/; + # proxy_cookie_path / /adguard/; + # } + # location /pac { + # access_log /logs/nginx_pac_access; + # proxy_pass http://unit; + # } + # location ~\.well-known { + # access_log /logs/nginx_certbot_access; + # root /certs/; + # try_files $uri =404; + # } + # location /v2ray { + # access_log /logs/nginx_v2ray_access; + # proxy_redirect off; + # proxy_buffering off; + # proxy_http_version 1.1; + # proxy_pass http://ss:8388/; + # proxy_set_header Host $http_host; + # proxy_set_header Upgrade $http_upgrade; + # proxy_set_header Connection "upgrade"; + # } + + # # The DoH server block + # location /dns-query { + # access_log /logs/nginx_doh_access; + # # Proxy HTTP/1.1, clear the connection header to enable Keep-Alive + # proxy_http_version 1.1; + # proxy_set_header Connection ""; + + # # Enable Cache, and set the cache_key to include the request_body + # proxy_cache doh_cache; + # proxy_cache_key $scheme$proxy_host$uri$is_args$args$request_body; + + # # proxy pass to the dohloop upstream + # proxy_pass http://dohloop; + # } + # } + #~domain + + # This upstream connects to a local Stream service which converts HTTP -> DNS + upstream dohloop { + zone dohloop 64k; + server 127.0.0.1:8053; + keepalive_timeout 60s; + keepalive_requests 100; + keepalive 10; + } + +} + +# DNS Stream Services +stream { + # DNS logging + log_format dns '$remote_addr [$time_local] $protocol "$dns_qname"'; + access_log /logs/nginx_dns_access dns; + + # Import the NJS module + js_import /etc/nginx/njs.d/dns/dns.js; + + # The $dns_qname variable can be populated by preread calls, and can be used for DNS routing + js_set $dns_qname dns.get_qname; + + # DNS upstream pool. + upstream dns { + zone dns 64k; + server ad:53; + } + + # DNS(TCP) and DNS over TLS (DoT) Server + # Terminate DoT and DNS TCP, and proxy onto standard DNS + #~domain + # server { + # listen 53; + # listen 853 ssl; + # ssl_certificate /certs/cert_public; + # ssl_certificate_key /certs/cert_private; + # js_preread dns.preread_dns_request; + # proxy_pass dns; + # } + #~domain + + # DNS(UDP) Server + # DNS UDP proxy onto DNS UDP + server { + listen 53 udp; + proxy_responses 1; + js_preread dns.preread_dns_request; + proxy_pass dns; + } + + # DNS over HTTPS (gateway) Service + # Upstream can be either DNS(TCP) or DoT. If upstream is DNS, proxy_ssl should be off. + server { + listen 8053; + js_filter dns.filter_doh_request; + proxy_pass dns; + } +} diff --git a/config/pac.json b/config/pac.json new file mode 100644 index 0000000..1a24005 --- /dev/null +++ b/config/pac.json @@ -0,0 +1,99 @@ +{ + "zapret": false, + "excludelist": { + "(?:v|w)ul(?:c|k)an": true, + "^\\d": true, + "^a\\w-": true, + "^admiral": true, + "^avtomaty": true, + "^azart": true, + "^azimob": true, + "^baltplay": true, + "a(?:s|z)ino": true, + "adrenalin": true, + "alco": true, + "bet": true, + "prostitut": true, + "zenit": true, + "zerkalo": true, + "777": true + }, + "includelist": {}, + "reverselist": {}, + "subzoneslist": { + "3dn": false, + "3nx": true, + "akadns": true, + "appspot": true, + "azurewebsites": true, + "beget": true, + "berlogovo": true, + "biz": true, + "brightcove": true, + "cc": true, + "cloudfront": true, + "co": true, + "com": true, + "cu": true, + "ddns": true, + "deviantart": true, + "dn": true, + "dp": true, + "dyndns": true, + "edgecastcdn": true, + "edu": true, + "eu": true, + "fastly": true, + "force": true, + "github": true, + "google": true, + "googleusercontent": true, + "gov": true, + "herokuapp": true, + "hldns": true, + "ho": true, + "hopto": true, + "hwcdn": true, + "i": true, + "iboards": true, + "in": true, + "info": true, + "int": true, + "itch": true, + "keenetic": true, + "kiev": true, + "kirov": true, + "linode": true, + "livejournal": true, + "maryno": true, + "mil": true, + "msk": true, + "my1": true, + "mybb2": true, + "ne": true, + "net": true, + "netdna-ssl": true, + "nnov": true, + "notion": true, + "nov": true, + "od": true, + "org": true, + "pp": true, + "pximg": true, + "pythonanywhere": true, + "ru": true, + "scaleway": true, + "sl": true, + "sl-reverse": true, + "spb": true, + "tilda": true, + "trafficmanager": true, + "tut": true, + "u-stream": true, + "ucoz": true, + "v": true, + "vercel": true, + "wix": true, + "wixmp": true + } +} diff --git a/config/php.ini b/config/php.ini index a3b757a..8e508f0 100644 --- a/config/php.ini +++ b/config/php.ini @@ -1949,3 +1949,11 @@ ldap.max_links = -1 ; List of headers files to preload, wildcard patterns allowed. ;ffi.preload= extension=ssh2 +extension=yaml +zend_extension=opcache +opcache.enable=1 +opcache.jit_buffer_size=128M +opcache.enable_cli=1 +pcre.jit=1 + + diff --git a/config/sockd.conf b/config/sockd.conf deleted file mode 100644 index 8c16683..0000000 --- a/config/sockd.conf +++ /dev/null @@ -1,16 +0,0 @@ -logoutput: stderr -internal: eth0 port = 1080 -external: eth0 -socksmethod: none -user.privileged: root -user.unprivileged: nobody - -client pass { - from: 0/0 to: 0/0 - log: connect disconnect error ioop -} - -socks pass { - from: 0/0 to: 0/0 - log: connect disconnect error ioop -} diff --git a/config/sslocal.json b/config/sslocal.json new file mode 100644 index 0000000..4c854d4 --- /dev/null +++ b/config/sslocal.json @@ -0,0 +1,15 @@ +{ + "server": "ss", + "server_port": 8388, + "local_address": "0.0.0.0", + "local_port": 1080, + "password": "test", + "timeout": 120, + "method": "chacha20-ietf-poly1305", + "no_delay": true, + "fast_open": true, + "reuse_port": true, + "workers": 1, + "nameserver": "10.10.0.5", + "mode": "tcp_and_udp" +} diff --git a/config/ssserver.json b/config/ssserver.json new file mode 100644 index 0000000..e25df79 --- /dev/null +++ b/config/ssserver.json @@ -0,0 +1,13 @@ +{ + "server": "0.0.0.0", + "server_port": 8388, + "password": "test", + "timeout": 120, + "method": "chacha20-ietf-poly1305", + "no_delay": true, + "fast_open": true, + "reuse_port": true, + "workers": 1, + "nameserver": "10.10.0.5", + "mode": "tcp_and_udp" +} diff --git a/config/unit.json b/config/unit.json index cc3c686..3acb6a6 100644 --- a/config/unit.json +++ b/config/unit.json @@ -1,17 +1,17 @@ { - "listeners":{ - "*:443":{ - "pass": "applications/php", - "tls": { - "certificate": "cert" - } - } - }, - "applications":{ - "php":{ - "type":"php", - "root":"/app", - "script":"index.php" - } - } + "listeners": { + "*:80": { + "pass": "applications/php" + } + }, + "applications": { + "php": { + "type": "php", + "root": "/app", + "script": "index.php", + "user": "root", + "group": "root" + } + }, + "access_log": "/logs/unit_access" } diff --git a/docker-compose.yml b/docker-compose.yml index b3fd686..708de50 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,55 +1,90 @@ version: "3.7" -volumes: - sshkey: - networks: default: ipam: config: - subnet: 10.10.0.0/24 +volumes: + adguard: services: + ng: + build: + context: dockerfile + dockerfile: doh.dockerfile + volumes: + - ./config/.profile:/root/.bashrc:ro + - ./config/nginx.conf:/etc/nginx/nginx.conf + - ./scripts/start_ng.sh:/start_ng.sh + - ./certs/:/certs/ + - ./ssh/:/ssh/ + - ./logs/:/logs/ + ports: + - 80:80 + - 443:443 + hostname: nginx + environment: + TZ: ${TZ} + stop_grace_period: 1s + command: ["/bin/sh", "/start_ng.sh"] + networks: + default: + ipv4_address: 10.10.0.2 unit: build: dockerfile: dockerfile/php.dockerfile args: IP: ${IP} - ports: - - 443:443 volumes: - - ./config/unit.json:/docker-entrypoint.d/config.json - - ./config/php.ini:/usr/local/etc/php/php.ini - ./config/.profile:/root/.bashrc:ro - - ./config/clients.json:/app/clients.json - - ./app:/app - - ./logs:/app/logs/ + - ./config/php.ini:/usr/local/etc/php/php.ini + - ./config/clients.json:/config/clients.json + - ./config/pac.json:/config/pac.json + - ./config/unit.json:/config/unit.json + - ./config/nginx.conf:/config/nginx.conf + - ./config/ssserver.json:/config/ssserver.json + - ./config/sslocal.json:/config/sslocal.json + - ./certs/:/certs/ - type: volume - target: /ssh - source: sshkey + target: /config/adguard + source: adguard + - ./ssh/:/ssh/ + - ./app:/app + - ./logs/:/logs/ + - ./scripts/start_unit.sh:/start_unit.sh environment: TZ: ${TZ} - ADDRESS: ${ADDRESS} - PORT_WG: ${PORT} + ADDRESS: ${WGADDRESS} + PORT_WG: ${WGPORT} hostname: unit restart: unless-stopped - depends_on: - - wg stop_grace_period: 1s - command: bash -c 'chown -R www-data:www-data /app/logs && chown www-data:www-data /app/clients.json && php init.php && /usr/local/bin/docker-entrypoint.sh unitd --no-daemon' + command: ["/bin/sh", "/start_unit.sh"] working_dir: /app + depends_on: + - ng + networks: + default: + ipv4_address: 10.10.0.7 proxy: build: - dockerfile: dockerfile/proxy.dockerfile + dockerfile: dockerfile/shadowsocks.dockerfile + ports: + - 1080:1080 volumes: - ./config/.profile:/root/.bashrc:ro - - ./config/sockd.conf:/etc/sockd.conf + - ./config/sslocal.json:/config.json + - ./ssh:/ssh + - ./scripts/start_proxy.sh:/start_proxy.sh hostname: proxy networks: default: ipv4_address: 10.10.0.3 environment: TZ: ${TZ} + stop_grace_period: 1s + command: ["/bin/sh", "/start_proxy.sh"] wg: build: dockerfile: dockerfile/wireguard.dockerfile @@ -58,16 +93,57 @@ services: - ./config/wg0.conf:/etc/wireguard/wg0.conf - ./scripts/start_wg.sh:/start_wg.sh - ./scripts/reset_wg.sh:/reset_wg.sh - - type: volume - target: /ssh - source: sshkey + - ./ssh:/ssh hostname: wireguard ports: - - ${PORT}:${PORT}/udp + - ${WGPORT}:${WGPORT}/udp environment: TZ: ${TZ} - PORT_WG: ${PORT} - ADDRESS: ${ADDRESS} + PORT_WG: ${WGPORT} + ADDRESS: ${WGADDRESS} cap_add: - NET_ADMIN stop_grace_period: 1s + command: ["/bin/sh", "/start_wg.sh"] + networks: + default: + ipv4_address: 10.10.0.4 + ad: + build: + dockerfile: dockerfile/adguard.dockerfile + volumes: + - ./config/.profile:/root/.bashrc:ro + - type: volume + target: /opt/adguardhome + source: adguard + - ./ssh:/ssh + - ./logs/:/logs/ + - ./scripts/start_ad.sh:/start_ad.sh + hostname: adguard + environment: + TZ: ${TZ} + stop_grace_period: 1s + networks: + default: + ipv4_address: 10.10.0.5 + command: ["/bin/sh", "/start_ad.sh"] + ss: + build: + dockerfile: dockerfile/shadowsocks.dockerfile + volumes: + - ./config/.profile:/root/.bashrc:ro + - ./config/ssserver.json:/config.json + - ./ssh:/ssh + - ./scripts/start_ss.sh:/start_ss.sh + hostname: shadowsocks + ports: + - 8388:8388/udp + - 8388:8388/tcp + environment: + TZ: ${TZ} + stop_grace_period: 1s + command: ["/bin/sh", "/start_ss.sh"] + networks: + default: + ipv4_address: 10.10.0.6 + diff --git a/dockerfile/adguard.dockerfile b/dockerfile/adguard.dockerfile new file mode 100644 index 0000000..96adec1 --- /dev/null +++ b/dockerfile/adguard.dockerfile @@ -0,0 +1,8 @@ +from nginx:stable +run apt update && \ +apt install -y git net-tools lsof ssh wget && \ +wget https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.107.21/AdGuardHome_linux_amd64.tar.gz && \ +tar -xf AdGuardHome_linux_amd64.tar.gz && \ +mkdir -p /opt/adguardhome && \ +mkdir /root/.ssh +copy config/AdGuardHome.yaml /opt/adguardhome/AdGuardHome.yaml diff --git a/dockerfile/doh.dockerfile b/dockerfile/doh.dockerfile new file mode 100644 index 0000000..b8b8ea3 --- /dev/null +++ b/dockerfile/doh.dockerfile @@ -0,0 +1,6 @@ +from nginx:stable +run apt update && \ +apt install -y git net-tools lsof ssh && \ +git clone https://github.com/TuxInvader/nginx-dns.git && \ +cp -r ./nginx-dns/njs.d /etc/nginx/ && \ +mkdir /root/.ssh diff --git a/dockerfile/php.dockerfile b/dockerfile/php.dockerfile index b2bff42..b56047f 100644 --- a/dockerfile/php.dockerfile +++ b/dockerfile/php.dockerfile @@ -1,9 +1,9 @@ from nginx/unit:1.29.0-php8.1 -run apt update && apt install -y libssh2-1-dev && pecl install https://pecl.php.net/get/ssh2-1.3.1.tgz -ARG IP -run mkdir /cert && \ -openssl req -newkey rsa:2048 -sha256 -nodes \ --keyout /cert/nginx_private.key -x509 -days 365 -out /cert/nginx_public.pem \ --subj "/C=US/ST=New York/L=Brooklyn/O=Example Brooklyn Company/CN=$IP" && \ -cat /cert/nginx_private.key /cert/nginx_public.pem > /docker-entrypoint.d/cert.pem -workdir /app +arg IP +run apt update && apt install -y wget libssh2-1-dev ssh libicu-dev libyaml-dev certbot && \ +pecl install https://pecl.php.net/get/ssh2-1.3.1.tgz && \ +pecl install https://pecl.php.net/get/yaml-2.2.2.tgz && \ +docker-php-ext-install intl && \ +wget https://github.com/ameshkov/dnslookup/releases/download/v1.8.1/dnslookup-linux-amd64-v1.8.1.tar.gz && \ +tar -xf dnslookup-linux-amd64-v1.8.1.tar.gz +env PATH="$PATH:/linux-amd64" diff --git a/dockerfile/proxy.dockerfile b/dockerfile/proxy.dockerfile deleted file mode 100644 index 1580896..0000000 --- a/dockerfile/proxy.dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -from ubuntu:18.04 -run apt update && \ -apt install -y build-essential gcc make wget && \ -wget https://www.inet.no/dante/files/dante-1.4.3.tar.gz && \ -tar -xf dante-1.4.3.tar.gz && \ -cd dante-1.4.3 && \ -./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --disable-client --without-libwrap --without-bsdauth --without-gssapi --without-krb5 --without-upnp --without-pam && \ -make && \ -make install -expose 1080 -cmd ["sockd"] diff --git a/dockerfile/shadowsocks.dockerfile b/dockerfile/shadowsocks.dockerfile new file mode 100644 index 0000000..4f9f658 --- /dev/null +++ b/dockerfile/shadowsocks.dockerfile @@ -0,0 +1,11 @@ +from ubuntu:18.04 +run apt update && \ +apt install -y ssh git net-tools +run mkdir /root/.ssh && \ +mkdir /ssh && \ +touch /root/.ssh/authorized_keys && \ +wget https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.15.1/shadowsocks-v1.15.1.x86_64-unknown-linux-gnu.tar.xz && \ +tar -xf shadowsocks-v1.15.1.x86_64-unknown-linux-gnu.tar.xz && \ +wget https://github.com/teddysun/v2ray-plugin/releases/download/v5.1.0/v2ray-plugin-linux-amd64-v5.1.0.tar.gz && \ +tar xf v2ray-plugin-linux-amd64-v5.1.0.tar.gz && \ +mv v2ray-plugin_linux_amd64 /usr/local/bin/v2ray-plugin diff --git a/dockerfile/wireguard.dockerfile b/dockerfile/wireguard.dockerfile index 1a7d046..702aac2 100644 --- a/dockerfile/wireguard.dockerfile +++ b/dockerfile/wireguard.dockerfile @@ -3,15 +3,8 @@ run apt update && \ apt install -y wireguard \ iproute2 \ net-tools \ +lsof \ iptables \ linux-headers-$(uname -r) \ -ssh -run mkdir /root/.ssh && \ -mkdir /ssh && \ -touch /root/.ssh/authorized_keys && \ -ssh-keygen -t rsa -f /ssh/key -N '' && \ -chmod 644 /ssh/key && \ -wg genkey > /etc/wireguard/privatekey -copy ./scripts/start_wg.sh /start_wg.sh -copy ./scripts/reset_wg.sh /reset_wg.sh -cmd ["/bin/sh", "/start_wg.sh"] +ssh && \ +mkdir /root/.ssh diff --git a/logs/input b/logs/.gitkeep similarity index 100% rename from logs/input rename to logs/.gitkeep diff --git a/makefile b/makefile index 323913b..f5cb468 100644 --- a/makefile +++ b/makefile @@ -6,10 +6,10 @@ unhosts: sed -i '/test.ru/d' /mnt/c/Windows/System32/drivers/etc/hosts u: # запуск контейнеров IP=$(shell curl https://ipinfo.io/ip) docker compose up -d --build --force-recreate - sleep 1 - docker compose logs wg unit proxy +# sleep 1 +# docker compose logs unit wg ss proxy d: # остановка контейнеров - docker compose down -v + docker compose down ps: # список контейнеров docker compose ps l: # логи из контейнеров @@ -20,3 +20,13 @@ proxy: # консоль сервиса docker compose exec proxy bash wg: # консоль сервиса docker compose exec wg bash +ss: # консоль сервиса + docker compose exec ss bash +ng: # консоль сервиса + docker compose exec ng bash +doh: # консоль сервиса + docker compose exec doh bash +ad: # консоль сервиса + docker compose exec ad bash +proxy: # консоль сервиса + docker compose exec proxy bash diff --git a/scripts/init.sh b/scripts/init.sh index a44a0b6..05242ec 100644 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -7,7 +7,7 @@ apt install -y \ make \ git mkdir -p /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null diff --git a/scripts/start_ad.sh b/scripts/start_ad.sh new file mode 100644 index 0000000..706467d --- /dev/null +++ b/scripts/start_ad.sh @@ -0,0 +1,4 @@ +cat /ssh/key.pub > /root/.ssh/authorized_keys +service ssh start +/AdGuardHome/AdGuardHome -s install -c /opt/adguardhome/AdGuardHome.yaml -h 0.0.0.0 -w /opt/adguardhome/ +tail -f /dev/null diff --git a/scripts/start_ng.sh b/scripts/start_ng.sh new file mode 100644 index 0000000..a25f81c --- /dev/null +++ b/scripts/start_ng.sh @@ -0,0 +1,5 @@ +openssl req -newkey rsa:2048 -sha256 -nodes -x509 -days 365 -keyout /certs/self_private -out /certs/self_public -subj "/C=NN/ST=N/L=N/O=N/CN=$(curl https://ipinfo.io/ip)" +ssh-keygen -m PEM -t rsa -f /ssh/key -N '' +cat /ssh/key.pub > /root/.ssh/authorized_keys +service ssh start +nginx -g "daemon off;" diff --git a/scripts/start_proxy.sh b/scripts/start_proxy.sh new file mode 100644 index 0000000..86591b4 --- /dev/null +++ b/scripts/start_proxy.sh @@ -0,0 +1,4 @@ +cat /ssh/key.pub > /root/.ssh/authorized_keys +service ssh start +/sslocal -v -d -c /config.json +tail -f /dev/null diff --git a/scripts/start_ss.sh b/scripts/start_ss.sh new file mode 100644 index 0000000..5b8df30 --- /dev/null +++ b/scripts/start_ss.sh @@ -0,0 +1,4 @@ +cat /ssh/key.pub > /root/.ssh/authorized_keys +service ssh start +/ssserver -v -d -c /config.json +tail -f /dev/null diff --git a/scripts/start_unit.sh b/scripts/start_unit.sh new file mode 100644 index 0000000..ea93f0b --- /dev/null +++ b/scripts/start_unit.sh @@ -0,0 +1,5 @@ +php init.php +unitd --log /logs/unit_error +curl -X PUT --data-binary @/config/unit.json --unix-socket /var/run/control.unit.sock http://localhost/config +kill -TERM $(/bin/cat /var/run/unit.pid) +unitd --no-daemon --log /logs/unit_error diff --git a/scripts/start_wg.sh b/scripts/start_wg.sh index 3b8c661..e0263c3 100644 --- a/scripts/start_wg.sh +++ b/scripts/start_wg.sh @@ -1,7 +1,7 @@ -INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') -PRIVATEKEY=$(cat /etc/wireguard/privatekey) if [ $(cat /etc/wireguard/wg0.conf | wc -c) -eq 0 ] then + PRIVATEKEY=$(wg genkey | tee /etc/wireguard/privatekey) + INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') echo "[Interface]" > /etc/wireguard/wg0.conf echo "PrivateKey = $PRIVATEKEY" >> /etc/wireguard/wg0.conf echo "Address = $ADDRESS" >> /etc/wireguard/wg0.conf diff --git a/ssh/.gitkeep b/ssh/.gitkeep new file mode 100644 index 0000000..e69de29