выпилил obsolete скрипты

This commit is contained in:
kirillius 2026-01-31 01:13:56 +03:00
parent a0a7794cb0
commit 3c59c10a7c
64 changed files with 1 additions and 1817 deletions

View File

@ -1,18 +1,5 @@
# Protected Resources # Protected Resources
Список ресурсов для настройки родительского контроля чтобы заблокировать доступ детям до запрещённых в РФ вражеских сервисов. Список ресурсов для настройки родительского контроля чтобы заблокировать доступ детям до запрещённых в РФ вражеских сервисов.
## networks ## resources
Каталог с файлами сервисов и их подсетей Каталог с файлами сервисов и их подсетей
### Установка
```shell
apk add php php-session php-curl jq git
cd /opt
git clone https://git.kirillius.ru/kirillius/protected-resources-list.git
cd /opt/protected-resources-list/
chmod -R +x ./bin
ln -s /opt/protected-resources-list/bin/webui /etc/init.d/webui
cp config.json.example config.json
rc-update add webui
/etc/init.d/webui start
```

View File

@ -1,79 +0,0 @@
import {JSONRPC} from "./jrpc.js";
const App = {
config: {},
networks: {},
RPC: JSONRPC
};
App.auth = async function () {
let authorized = await JSONRPC.__invoke("auth");
if (!authorized) {
do {
let pass = prompt("Password");
authorized = await JSONRPC.__invoke("auth", {
"password": pass
});
if (!authorized) {
alert("Wrong password");
}
} while (!authorized);
}
this.config = await JSONRPC.__invoke("getConfig");
this.networks = await JSONRPC.__invoke("getNetworks");
for (const key of this.config.plugins) {
$("body").append("<" + "script type='module' src='/plugins/" + key + "/plugin.js'><" + "/script>");
}
$("#loading").hide();
this.fillNetworks();
$("#panel").show();
let invalidNetworks = await JSONRPC.__invoke("getInvalidNetworks");
if (invalidNetworks.length > 0) {
$("body").append(`<span>There are invalid networks <BR>` + invalidNetworks.join("<BR>") + ` <BR></span>`);
}
}
App.fillNetworks = function () {
const that = this;
let proto = $("#net-table tr");
proto.detach();
for (const net in this.networks) {
let item = proto.clone();
item.find("input").prop('checked', this.config.networks.indexOf(net) !== -1).change(function () {
if ($(this).prop('checked')) {
that.config.networks.push(net);
} else {
that.config.networks = that.config.networks.filter(e => e !== net);
}
});
item.find("span").text(net);
$("#net-table").append(item);
}
}
App.render = async function () {
await this.auth();
$("#save").click(function () {
const self = $(this);
self.prop("disabled", true);
(async function () {
await JSONRPC.__invoke("setConfig", App.config);
alert("Config saved!");
self.prop("disabled", false);
})();
});
}
export {App};

View File

@ -1,36 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Routing config</title>
<script src="jquery-3.7.1.min.js" type="text/javascript"></script>
</head>
<body>
<span id="loading">Loading...</span>
<div style="display: none" id="panel">
Selected networks:
<table id="net-table">
<tr>
<td><input type="checkbox"></td>
<td><span>Network name</span></td>
</tr>
</table>
<div id="buttons">
<button id="save">Save</button>
</div>
</div>
<script type="module">
import {App} from "./App.js";
$(document).ready(function () {
App.render();
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,59 +0,0 @@
export const JSONRPC = {
url: "/rpc",
__id: 1,
/**
*
* @param method
* @param params
* @returns Object
* @private
*/
__invoke: async function (method, params) {
if(params === undefined){
params = {};
}
const request = await JSONRPC.__performRequest(method, params);
if (!request.success) {
console.error(request.result);
throw new Error("Failed to invoke method " + method + " with params " + JSON.stringify(params));
}
return request.result;
},
__performRequest: async function (method, params) {
const __this = this;
const resp = await fetch(
__this.url,
{
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
redirect: "follow",
referrerPolicy: "no-referrer",
body: JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: __this.__id++
})
}
);
const success = resp.status === 200;
const result = (success ? (await resp.json()).result : {
"error": true,
"code": resp.status,
"status": resp.statusText,
"body": await resp.text()
});
return {
"result": result,
"success": success
};
}
};

View File

@ -1,29 +0,0 @@
#!/usr/bin/php
<?php
require_once dirname(__DIR__) . "/loader.php";
require_once dirname(__DIR__) . "/plugins/openvpn/Openvpn.php";
try {
if (!isset($argv[1])) {
throw new RuntimeException("Output config is not set");
}
$rpc = new StaticRPC();
$instance = $rpc->getPlugins()["openvpn"] ?? null;
if ($instance === null) {
throw new RuntimeException("Plugin is not enabled");
}
/**
* @var Custom $instance
*/
$config = $instance->getRoutingConfig();
$outfile = $argv[1];
file_put_contents($outfile, implode("\n", $config));
} catch (Exception $e) {
echo "\nError:" . $e->getMessage() . "\n";
exit(1);
}
exit(0);

View File

@ -1,27 +0,0 @@
#!/usr/bin/php
<?php
require_once dirname(__DIR__) . "/loader.php";
require_once dirname(__DIR__) . "/plugins/netsync/Netsync.php";
try {
$rpc = new StaticRPC();
$instance = $rpc->getPlugins()["netsync"] ?? null;
if ($instance === null) {
throw new RuntimeException("Plugin is not enabled");
}
/**
* @var Netsync $instance
*/
$config = $instance->sync();
var_dump($config);
} catch (Exception $e) {
echo "\nError:" . $e->getMessage() . "\n";
exit(1);
}
exit(0);

View File

@ -1,4 +0,0 @@
#!/sbin/openrc-run
command="/opt/protected-resources-list/bin/webui-server"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

View File

@ -1,17 +0,0 @@
#!/bin/sh
SELFDIR=`dirname $0`
ROOT=`realpath $SELFDIR/..`
CFGFILE=$ROOT/config.json
if ! test -f "$CFGFILE"; then
echo Config file $CFGFILE not found
exit 1
fi
HOST=`jq -r .web.host $CFGFILE`
PORT=`jq -r .web.port $CFGFILE`
#TODO FIXME !!!!
killall php
cd $ROOT
php $ROOT/loader.php --init
php -S $HOST:$PORT $ROOT/server.php

View File

@ -1,57 +0,0 @@
<?php
class Config implements ArrayAccess
{
private string $path;
public function asArray(): array
{
return $this->data;
}
public function fromArray($a)
{
$this->data = $a;
}
public function __construct()
{
$this->path = dirname(__DIR__) . "/config.json";
}
public function read(): void
{
$this->data = @json_decode(@file_get_contents($this->path), true);
if ($this->data == null) {
throw new RuntimeException("Failed to read or parse config file");
}
}
public function save(): void
{
file_put_contents($this->path, json_encode($this->data,JSON_PRETTY_PRINT));
}
private mixed $data = [];
public function offsetExists(mixed $offset): bool
{
return isset($this->data[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->data[$offset];
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->data[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->data[$offset]);
}
}

View File

@ -1,8 +0,0 @@
<?php
interface IPluggable
{
public function onServerStarted();
public function onInit(PluginContext $context);
public function onSync(array $remote_config);
}

View File

@ -1,46 +0,0 @@
<?php
class IPv4Subnet
{
private string $address;
private int $prefix;
/**
* @param string $subnetAddress
* @param int $prefix
*/
public function __construct(string $subnetAddress, int $prefix)
{
$this->address = $subnetAddress;
if (ip2long($subnetAddress) === false) {
throw new RuntimeException("Invalid subnet address: " . $subnetAddress);
}
$this->prefix = $prefix;
if ($prefix < 0 or $prefix > 32) {
throw new RuntimeException("Invalid subnet prefix: " . $prefix);
}
}
public function getFirstAddress()
{
$a = ip2long($this->address);
$mask = ip2long($this->getNetMask());
return long2ip($a & $mask);
}
public function getLastAddress()
{
return long2ip(ip2long($this->getFirstAddress()) + $this->getAddressCount() - 1);
}
public function getAddressCount()
{
return pow(2, 32 - $this->prefix);
}
public function getNetMask()
{
return long2ip(-1 << (32 - $this->prefix));
}
}

View File

@ -1,33 +0,0 @@
<?php
class NetworkConfigReader
{
private array $configs = [];
public function __construct()
{
$path = dirname(__DIR__) . "/networks";
foreach (new IteratorIterator(new DirectoryIterator($path)) as $file) {
/**
* @var SplFileInfo $file
*/
if ($file->getExtension() === "json") {
$key = $file->getBasename(".json");
$value = @json_decode(@file_get_contents($file->getPathname()), true);
if ($value === null) {
throw new RuntimeException("Network file " . $file->getBasename() . " is invalid or cannot be read");
}
$this->configs[$key] = $value;
}
}
}
public function getConfigs(): array
{
return $this->configs;
}
}

View File

@ -1,49 +0,0 @@
<?php
abstract class Plugin implements IPluggable
{
protected PluginContext $context;
protected array $config;
public function onServerStarted()
{
}
public function onSync($remote_config)
{
}
public function onInit(PluginContext $context): void
{
$this->context = $context;
$this->checkConfig();
$this->config = $this->context->getConfig()[$context->getName()];
}
protected function checkConfig(): void
{
$config = $this->context->getConfig();
$defaults = $this->context->getMetadata()["config"];
$name = $this->context->getName();
if (!isset($config[$name])) {
$config[$name] = $defaults;
return;
}
foreach ($defaults as $key => $value) {
if (!isset($config[$name][$key])) {
$config[$name][$key] = $value;
}
}
}
protected function saveConfig()
{
$wrapper = $this->context->getConfig();
$wrapper[$this->context->getName()] = $this->config;
$wrapper->save();
}
}

View File

@ -1,43 +0,0 @@
<?php
class PluginContext
{
private RPC $RPC;
private Config $config;
private array $metadata;
private string $name;
public function getName(): string
{
return $this->name;
}
/**
* @param RPC $RPC
* @param Config $config
* @param array $metadata
* @param string $name
*/
public function __construct(RPC $RPC, Config $config, array $metadata, string $name)
{
$this->name = $name;
$this->RPC = $RPC;
$this->config = $config;
$this->metadata = $metadata;
}
public function getRPC(): RPC
{
return $this->RPC;
}
public function getConfig(): Config
{
return $this->config;
}
public function getMetadata(): array
{
return $this->metadata;
}
}

View File

@ -1,167 +0,0 @@
<?php
class RPC
{
protected Config $config;
protected array $plugins = [];
public function getPlugins(): array
{
return $this->plugins;
}
public function __construct()
{
$this->config = new Config();
$this->config->read();
foreach ($this->config["plugins"] as $plugin) {
try {
$meta = $this->getPluginMetadata($plugin);
$inst = $this->loadPlugin($plugin, $meta["class"]);
$inst->onInit(new PluginContext($this, $this->config, $meta, $plugin));
} catch (Error $e) {
continue;
}
}
}
private function getPluginMetadata($name): array
{
$root = dirname(__DIR__) . "/plugins/" . $name;
if (!file_exists($root)) {
throw new RuntimeException("Plugin $name dir not found");
}
$metafile = $root . "/metadata.json";
if (!file_exists($metafile)) {
throw new RuntimeException("Plugin $name metadata not found");
}
$meta = @json_decode(@file_get_contents($metafile), true);
if ($meta === null) {
throw new RuntimeException("Unable to parse $name plugin metadata");
}
return $meta;
}
private function loadPlugin($name, $classname): IPluggable
{
$file = dirname(__DIR__) . "/plugins/" . $name . "/" . $classname . ".php";
if (!file_exists($file)) {
throw new RuntimeException("Plugin $name class $classname not found");
}
require_once $file;
$instance = new $classname();
if (!($instance instanceof IPluggable)) {
throw new RuntimeException("Class $classname have to implement IPluggable");
}
return $this->plugins[$name] = $instance;
}
public function getConfig(): array
{
$this->checkAuth();
return $this->config->asArray();
}
public function setConfig($config): bool
{
$this->config->fromArray($config);
$this->config->save();
return true;
}
private function checkAuth(): void
{
$auth = $_SESSION["auth"] ?? false;
if (!$auth) {
throw new RuntimeException("Unauthorized");
}
}
public function getNetworks(): array
{
$this->checkAuth();
return (new NetworkConfigReader())->getConfigs();
}
public function getInvalidNetworks(): array
{
$this->checkAuth();
$invalid = [];
foreach ((new NetworkConfigReader())->getConfigs() as $config) {
foreach ($config["networks"] as $network) {
if (!RouteUtil::validateSubnet($network)) {
$invalid[] = $network;
}
}
}
return $invalid;
}
public function logout(): void
{
$_SESSION["auth"] = false;
}
public function auth($params): bool
{
if (isset($params["password"])) {
if ($this->comparePassword($params["password"])) {
return $_SESSION["auth"] = true;
} else {
return false;
}
}
return $_SESSION["auth"] ?? false;
}
private function comparePassword($passwd): bool
{
$pass = $this->config["password"];
if ($pass["type"] == "plaintext") {
return $pass["data"] == $passwd;
} else if ($pass["type"] == "hash") {
return $this->hash($passwd) == $pass["data"];
}
return false;
}
private function hash($what): string
{
return md5(sha1($what) . md5($what));
}
private function isInternalMethod($name)
{
$cls = new ReflectionClass(IPluggable::class);
return $cls->hasMethod($name);
}
public function __invoke($method, $args)
{
$parts = explode("::", $method);
$isPlugin = count($parts) > 1;
if ($isPlugin) {
$this->checkAuth();
$plugin = $this->plugins[$parts[0]];
$methodname = $parts[1];
if ($this->isInternalMethod($methodname)) {
throw new RuntimeException("Unable to invoke internal methods");
}
return call_user_func([$plugin, $methodname], $args);
} else {
$cls = new ReflectionClass(__CLASS__);
$method = $cls->getMethod($method);
if (!$method or !$method->isPublic()) {
throw new RuntimeException("Unable to find method");
}
return $method->invoke($this, $args);
}
}
}

View File

@ -1,18 +0,0 @@
<?php
class RouteUtil
{
public static function validateSubnet($subnet)
{
$parts = explode("/", $subnet);
if (count($parts) != 2) {
return false;
}
try {
$ipv4subnet = new IPv4Subnet($parts[0], $parts[1]);
return $parts[0] == $ipv4subnet->getFirstAddress();
} catch (Exception $e) {
return false;
}
}
}

View File

@ -1,39 +0,0 @@
<?php
class RoutingTableReader
{
private $routes = [];
/**
* @return array
*/
public function getRoutes(): array
{
return $this->routes;
}
public function __construct()
{
$result = @shell_exec("ip --json route show");
if (!$result) {
throw new RuntimeException("Failed to read routing table");
}
$this->routes = @json_decode($result, true);
if ($this->routes === null) {
throw new RuntimeException("Failed to parse json output");
}
foreach ($this->routes as $key => &$route) {
if ($route["dst"] === "default") {
$route["dst"] = "0.0.0.0/0";
} elseif (!str_contains($route["dst"], "/")) {
$route["dst"] .= "/32";
}
}
}
}

View File

@ -1,15 +0,0 @@
<?php
class StaticRPC extends RPC
{
public function __construct()
{
parent::__construct();
}
public function getPlugins(): array
{
return $this->plugins;
}
}

View File

@ -1,85 +0,0 @@
<?php
class WebRouter
{
private string $requestBody;
private string $requestedFile;
private string $URI;
public function __construct()
{
$this->requestBody = @file_get_contents('php://input');
$this->requestedFile = dirname(__DIR__) . "/" . $_SERVER["REQUEST_URI"];
$this->URI = $_SERVER["REQUEST_URI"];
}
public function handleRequest(): void
{
@session_start();
try {
if (str_starts_with($this->URI, "/assets") or str_starts_with($this->URI, "/plugins") and str_ends_with($this->URI, ".js")) {
$this->handleAsset();
} elseif (!str_starts_with($this->URI, "/rpc")) {
$this->redirect("/assets/index.html");
} else {
$this->handleJRPC();
}
} finally {
session_write_close();
}
}
private function handleJRPC(): void
{
try {
$request = @json_decode($this->requestBody, true);
if ($request === null) {
throw new RuntimeException("Failed to parse JRPC");
}
foreach (["id", "jsonrpc", "method", "params"] as $param) {
if (!isset($request[$param])) {
throw new RuntimeException("Bad JRPC structure");
}
}
$rpc = new RPC();
$response = $rpc($request["method"], $request["params"]);
header("content-type: application/json");
echo json_encode([
"jsonrpc" => "2.0",
"id" => $request["id"] ?? 0,
"result" => $response
]);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode([
"jsonrpc" => "2.0",
"id" => $request["id"],
"error" => $e->getMessage()
]);
}
}
private function handleAsset(): void
{
if (!file_exists($this->requestedFile)) {
http_response_code(404);
echo "File not found: " . $this->URI;
} else {
header("content-type: " . mime_content_type($this->requestedFile));
echo file_get_contents($this->requestedFile);
}
}
private function redirect($where): void
{
http_response_code(302);
header("location: " . $where);
}
}

View File

@ -1,74 +0,0 @@
<?php
if (!function_exists('mime_content_type')) {
function mime_content_type($filename): bool|string
{
$mime_types = array(
'txt' => 'text/plain',
'htm' => 'text/html',
'html' => 'text/html',
'php' => 'text/html',
'css' => 'text/css',
'js' => 'application/javascript',
'json' => 'application/json',
'xml' => 'application/xml',
'swf' => 'application/x-shockwave-flash',
'flv' => 'video/x-flv',
// images
'png' => 'image/png',
'jpe' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'ico' => 'image/vnd.microsoft.icon',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'svg' => 'image/svg+xml',
'svgz' => 'image/svg+xml',
// archives
'zip' => 'application/zip',
'rar' => 'application/x-rar-compressed',
'exe' => 'application/x-msdownload',
'msi' => 'application/x-msdownload',
'cab' => 'application/vnd.ms-cab-compressed',
// audio/video
'mp3' => 'audio/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
// adobe
'pdf' => 'application/pdf',
'psd' => 'image/vnd.adobe.photoshop',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
// ms office
'doc' => 'application/msword',
'rtf' => 'application/rtf',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
// open office
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
);
$parts = explode('.', $filename);
$ext = strtolower(array_pop($parts));
if (array_key_exists($ext, $mime_types)) {
return $mime_types[$ext];
} elseif (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME);
$mimetype = finfo_file($finfo, $filename);
finfo_close($finfo);
return $mimetype;
} else {
return 'application/octet-stream';
}
}
}

View File

@ -1,14 +0,0 @@
{
"password": {
"type": "plaintext",
"data": "admin"
},
"networks": [
"google"
],
"web": {
"port":8000,
"host":"0.0.0.0"
},
"plugins":["updates"]
}

View File

@ -1,15 +0,0 @@
#!/usr/bin/php
<?php
require_once __DIR__ . "/common.inc.php";
spl_autoload_register(function ($classname) {
require_once __DIR__ . "/classes/" . $classname . ".php";
});
if (isset($argv) and in_array("--init", $argv)) {
$rpc = new StaticRPC();
foreach ($rpc->getPlugins() as $plugin => $instance) {
/**
* @var IPluggable $instance
*/
$instance->onServerStarted();
}
}

View File

@ -1,35 +0,0 @@
{
"description": "facebook, instagram, oculus",
"domains": [],
"networks": [
"213.102.128.0/24",
"204.15.20.0/22",
"199.201.0.0/16",
"185.89.0.0/16",
"185.60.216.0/21",
"179.60.0.0/16",
"173.252.0.0/16",
"173.194.10.0/24",
"164.163.191.64/26",
"163.70.0.0/16",
"157.240.0.0/16",
"147.75.0.0/16",
"142.250.0.0/15",
"129.134.0.0/16",
"103.4.0.0/16",
"102.221.0.0/16",
"102.132.0.0/16",
"99.84.0.0/16",
"87.245.208.0/24",
"77.240.43.0/24",
"74.119.0.0/16",
"69.171.0.0/16",
"69.63.0.0/16",
"66.220.0.0/16",
"57.141.0.0/16",
"57.144.222.0/24",
"45.64.0.0/16",
"45.130.4.0/24",
"31.13.0.0/16"
]
}

View File

@ -1,16 +0,0 @@
{
"description": "broadcom.com",
"domains": ["broadcom.com","omnissa.com"],
"networks": [
"172.66.0.165/32",
"162.159.140.167/32",
"54.68.22.26/32",
"50.112.202.115/32",
"52.13.171.212/32",
"2.19.183.16/32",
"2.19.183.47/32",
"129.153.117.201/32",
"23.73.4.0/24",
"184.50.200.7/32"
]
}

View File

@ -1,21 +0,0 @@
{
"description": "cloudflare",
"domains": [],
"networks": [
"173.245.48.0/20",
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"141.101.64.0/18",
"108.162.192.0/18",
"190.93.240.0/20",
"188.114.96.0/20",
"197.234.240.0/22",
"198.41.128.0/17",
"162.158.0.0/15",
"104.16.0.0/13",
"104.24.0.0/14",
"172.64.0.0/13",
"131.0.72.0/22"
]
}

View File

@ -1,40 +0,0 @@
{
"description": "amazon cloudfront",
"domains": [],
"networks": [
"108.138.0.0/15",
"108.156.0.0/14",
"111.13.0.0/16",
"116.129.226.0/24",
"118.193.97.0/24",
"119.147.182.0/24",
"120.232.0.0/16",
"120.52.0.0/16",
"13.0.0.0/8",
"130.176.0.0/16",
"143.204.0.0/16",
"144.220.0.0/16",
"15.128.0.0/9",
"18.0.0.0/8",
"180.163.57.0/24",
"204.246.0.0/16",
"205.251.0.0/16",
"216.137.32.0/19",
"3.0.0.0/8",
"34.128.0.0/9",
"35.0.0.0/8",
"36.103.232.0/24",
"43.218.56.0/24",
"44.0.0.0/8",
"47.129.0.0/16",
"52.0.0.0/8",
"54.0.0.0/8",
"58.254.138.0/24",
"64.252.0.0/16",
"65.8.0.0/16",
"65.9.0.0/16",
"70.132.0.0/18",
"71.152.0.0/17",
"99.0.0.0/8"
]
}

View File

@ -1,9 +0,0 @@
{
"description": "cura ultimaker",
"domains": [
"ultimaker.com"
],
"networks": [
"188.114.98.0/23"
]
}

View File

@ -1,23 +0,0 @@
{
"description": "discord",
"domains": [],
"networks": [
"104.16.0.0/12",
"108.177.14.207/32",
"138.128.140.240/28",
"142.250.150.207/32",
"142.251.1.207/32",
"162.159.128.0/20",
"172.64.0.0/14",
"173.194.0.0/16",
"18.165.140.0/25",
"188.114.0.0/16",
"204.11.56.48/32",
"209.85.233.207/32",
"23.227.38.74/32",
"34.0.0.0/7",
"64.233.160.0/21",
"66.22.192.0/18",
"74.125.128.0/17"
]
}

View File

@ -1,9 +0,0 @@
{
"description": "flibusta",
"domains": [
"flibusta.is"
],
"networks": [
"179.43.150.83/32"
]
}

View File

@ -1,7 +0,0 @@
{
"description": "facebook, instagram, oculus",
"domains": ["githubusercontent.com", "github.com"],
"networks": [
"185.199.0.0/16"
]
}

View File

@ -1,103 +0,0 @@
{
"description": "google services (youtube, mail)",
"domains": [
"youtube.com",
"googlevideo.com",
"ytimg.com",
"youtu.be",
"ggpht.com",
"nhacmp3youtube.com",
"googleusercontent.com",
"googleapis.com",
"gstatic.com",
"google.ac", "google.ad", "google.ae", "google.al", "google.am", "google.as", "google.at", "google.az",
"google.ba", "google.be", "google.bf", "google.bg", "google.bi", "google.bj", "google.bs", "google.bt",
"google.by", "google.ca", "google.cat", "google.cd", "google.cf", "google.cg", "google.ch", "google.ci",
"google.cl", "google.cm", "google.cn", "google.co.ao", "google.co.bw", "google.co.ck", "google.co.cr",
"google.co.id", "google.co.il", "google.co.in", "google.co.jp", "google.co.ke", "google.co.kr", "google.co.ls",
"google.co.ma", "google.co.mz", "google.co.nz", "google.co.th", "google.co.tz", "google.co.ug", "google.co.uk",
"google.co.uz", "google.co.ve", "google.co.vi", "google.co.za", "google.co.zm", "google.co.zw", "google.com",
"google.com.af", "google.com.ag", "google.com.ai", "google.com.ar", "google.com.au", "google.com.bd", "google.com.bh",
"google.com.bn", "google.com.bo", "google.com.br", "google.com.bz", "google.com.co", "google.com.cu", "google.com.cy",
"google.com.do", "google.com.ec", "google.com.eg", "google.com.et", "google.com.fj", "google.com.gh", "google.com.gi",
"google.com.gt", "google.com.hk", "google.com.jm", "google.com.jo", "google.com.kh", "google.com.kw", "google.com.lb",
"google.com.ly", "google.com.mm", "google.com.mt", "google.com.mx", "google.com.my", "google.com.na", "google.com.ng",
"google.com.ni", "google.com.np", "google.com.om", "google.com.pa", "google.com.pe", "google.com.pg", "google.com.ph",
"google.com.pk", "google.com.pr", "google.com.py", "google.com.qa", "google.com.sa", "google.com.sb", "google.com.sg",
"google.com.sl", "google.com.sv", "google.com.tj", "google.com.tr", "google.com.tw", "google.com.ua", "google.com.uy",
"google.com.vc", "google.com.vn", "google.cv", "google.cz", "google.de", "google.dj", "google.dk", "google.dm",
"google.dz", "google.ee", "google.es", "google.fi", "google.fm", "google.fr", "google.ga", "google.ge", "google.gg",
"google.gl", "google.gm", "google.gp", "google.gr", "google.gy", "google.hn", "google.hr", "google.ht", "google.hu",
"google.ie", "google.im", "google.iq", "google.is", "google.it", "google.je", "google.jo", "google.kg", "google.ki",
"google.kz", "google.la", "google.li", "google.lk", "google.lt", "google.lu", "google.lv", "google.md", "google.me",
"google.mg", "google.mk", "google.ml", "google.mn", "google.ms", "google.mu", "google.mv", "google.mw", "google.ne",
"google.nl", "google.no", "google.nr", "google.nu", "google.pl", "google.pn", "google.ps", "google.pt", "google.ro",
"google.rs", "google.ru", "google.rw", "google.sc", "google.se", "google.sh", "google.si", "google.sk", "google.sn",
"google.so", "google.sr", "google.st", "google.td", "google.tg", "google.tl", "google.tm", "google.tn", "google.to",
"google.tt", "google.vg", "google.vu", "google.ws"
],
"networks": [
"188.43.61.0/24",
"157.240.252.0/23",
"87.245.216.0/24",
"85.249.244.0/23",
"213.221.56.0/27",
"104.154.0.0/15",
"104.196.0.0/14",
"104.237.160.0/19",
"104.237.160.0/19",
"107.167.160.0/19",
"107.178.192.0/18",
"108.170.192.0/18",
"108.177.0.0/17",
"108.59.80.0/20",
"130.211.0.0/16",
"136.124.0.0/15",
"136.22.0.0/16",
"142.250.0.0/15",
"146.148.0.0/17",
"152.65.0.0/16",
"162.120.128.0/17",
"162.216.148.0/22",
"162.222.176.0/21",
"172.110.32.0/21",
"172.217.0.0/16",
"172.253.0.0/16",
"173.194.0.0/16",
"173.255.112.0/20",
"178.66.83.0/24",
"192.158.28.0/22",
"192.178.0.0/15",
"193.186.4.0/24",
"195.95.178.0/24",
"199.192.112.0/22",
"199.223.232.0/21",
"199.36.154.0/23",
"199.36.156.0/24",
"207.223.160.0/20",
"208.117.224.0/19",
"208.65.152.0/22",
"208.68.108.0/22",
"208.81.188.0/22",
"209.85.0.0/16",
"216.239.32.0/19",
"216.58.192.0/19",
"216.73.80.0/20",
"23.236.48.0/20",
"23.251.128.0/19",
"34.0.0.0/7",
"57.140.192.0/18",
"64.15.112.0/20",
"64.233.0.0/16",
"66.102.0.0/20",
"66.22.228.0/23",
"66.249.64.0/19",
"70.32.128.0/19",
"72.14.192.0/18",
"74.125.0.0/16",
"8.34.208.0/20",
"8.35.192.0/20",
"8.8.4.0/24",
"8.8.8.0/24"
]
}

View File

@ -1,9 +0,0 @@
{
"description": "habrahabr",
"domains": [
"habr.com"
],
"networks": [
"178.248.237.68/32"
]
}

View File

@ -1,9 +0,0 @@
{
"description": "intel website",
"domains": [
"intel.com"
],
"networks": [
"23.42.171.108/32"
]
}

View File

@ -1,11 +0,0 @@
{
"description": "jetbrains market",
"domains": ["jetbrains.com"],
"networks": [
"108.157.229.0/24",
"18.245.46.0/24",
"18.238.243.0/24",
"52.85.49.0/24",
"3.160.150.0/24"
]
}

View File

@ -1,7 +0,0 @@
{
"description": "lostfilm",
"domains": [],
"networks": [
"104.21.0.0/17"
]
}

View File

@ -1,11 +0,0 @@
{
"description": "notion",
"domains": [
"notion.so"
],
"networks": [
"104.18.39.102/32",
"172.64.148.154/32",
"208.103.161.0/30"
]
}

View File

@ -1,10 +0,0 @@
{
"description": "google & cloudflare public dns",
"domains": [],
"networks": [
"1.1.1.1/32",
"1.0.0.1/32",
"8.8.8.8/32",
"8.8.4.4/32"
]
}

View File

@ -1,7 +0,0 @@
{
"description": "rutor",
"domains": ["rutor.info"],
"networks": [
"193.46.255.29/32"
]
}

View File

@ -1,17 +0,0 @@
{
"description": "rutracker site & trackers",
"domains": [
"rutracker.org",
"rutracker.cc",
"t-ru.org"
],
"networks": [
"104.21.32.39/32",
"172.67.182.196/32",
"188.114.97.0/24",
"188.114.96.0/24",
"104.21.50.150/32",
"172.67.163.237/32",
"188.186.154.0/24"
]
}

View File

@ -1,8 +0,0 @@
{
"description": "www.snapeda.com",
"domains": ["snapeda.com"],
"networks": [
"104.20.16.0/20",
"172.66.144.0/20"
]
}

View File

@ -1,32 +0,0 @@
<?php
class API extends Plugin
{
public function onInit(PluginContext $context): void
{
parent::onInit($context);
if (!isset($this->config["key"])) {
$this->generateNewKey();
}
$headers = getallheaders();
if ($headers and isset($headers["X-Auth"]) and $headers["X-Auth"] == md5($this->config["key"])) {
$_SESSION["auth"] = true;
}
}
public function generateNewKey(): string
{
$this->config["key"] = sha1(rand() . uniqid());
$this->saveConfig();
return $this->config["key"];
}
public function getKey(): string
{
return $this->config["key"];
}
}

View File

@ -1,5 +0,0 @@
{
"class": "API",
"config": {
}
}

View File

@ -1,7 +0,0 @@
import {App} from "/assets/App.js";
(async function () {
let key = await App.RPC.__invoke("api::getKey");
$("body").append("<span>API Key: " + key + "</span>")
})();

View File

@ -1,35 +0,0 @@
#!/usr/bin/php
<?php
class Custom extends Plugin
{
public function onSync($remote_config)
{
if (!isset($remote_config["custom"])) {
return;
}
$this->config = $remote_config["custom"];
$this->saveConfig();
$this->updateConfigFile();
}
public function updateConfigFile()
{
$path = dirname(__DIR__, 2) . "/networks/custom.json";
$current_config = @file_get_contents($path);
$new_config = json_encode($this->config);
if($current_config !== $new_config) {
file_put_contents($path, $new_config);
}
}
public function onInit(PluginContext $context): void
{
parent::onInit($context);
$this->updateConfigFile();
}
}

View File

@ -1,10 +0,0 @@
{
"class": "Custom",
"config": {
"description": "Custom routes",
"domains": [
],
"networks": [
]
}
}

View File

@ -1,5 +0,0 @@
import {App} from "/assets/App.js";
(async function(){
await App.RPC.__invoke("custom::updateConfigFile")
})();

View File

@ -1,57 +0,0 @@
<?php
class BindPlugin extends Plugin
{
public function restart(): string|bool|null
{
$selectedDomains = [];
$networks = (new NetworkConfigReader())->getConfigs();
//add new routes
foreach ($this->context->getConfig()["networks"] as $key) {
if (isset($networks[$key])) {
foreach ($networks[$key]["domains"] as $domain) {
$selectedDomains[] = $domain;
}
}
}
$selectedDomains = array_unique($selectedDomains);
$data = [];
foreach ($selectedDomains as $domain) {
$data[] = $this->createForwardRecord($domain);
}
file_put_contents($this->config["file"], implode("\n", $data));
return shell_exec($this->config["restart_cmd"]);
}
private function createForwardRecord($domain)
{
$fwd = implode(";", $this->config["forwarders"]) . ";";
return <<<TXT
zone "{$domain}" IN {
type forward;
forward only;
forwarders{{$fwd}};
};
TXT;
}
public function onServerStarted()
{
$this->restart();
}
public function onSync($remote_config)
{
$this->restart();
}
}

View File

@ -1,8 +0,0 @@
{
"class": "BindPlugin",
"config": {
"restart_cmd": "/etc/init.d/named restart",
"file": "/var/bind/forward.dns",
"forwarders": ["8.8.8.8", "1.1.1.1"]
}
}

View File

@ -1,21 +0,0 @@
import {App} from "/assets/App.js";
(async function () {
$("#buttons").append(`<button id="restart-bind">Restart named</button>`);
$("#restart-bind").click(function () {
const self = $(this);
self.prop("disabled", true);
(async function () {
try {
alert(await App.RPC.__invoke("named::restart"));
} finally {
setTimeout(() => {
self.prop("disabled", false);
}, 5000);
}
})();
});
})();

View File

@ -1,79 +0,0 @@
<?php
class Netsync extends Plugin
{
public function sync()
{
$host = $this->config["master"];
$key = $this->config["key"];
if (empty($key)) {
throw new RuntimeException("API key is empty");
}
$ch = curl_init("http://" . $host . "/rpc");
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"jsonrpc" => "2.0",
"id" => "1",
"method" => "getConfig",
"params" => []
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-Auth: " . md5($key),
"Content-type: application/json"
]);
$output = curl_exec($ch);
try {
if ($err = curl_error($ch)) {
throw new RuntimeException("Failed to fetch remote API: " . $err);
}
} finally {
curl_close($ch);
}
$header = @json_decode($output, true);
$remote_config = $header["result"] ?? null;
$networks = $remote_config["networks"] ?? null;
if ($remote_config === null or $networks === null) {
throw new RuntimeException("Response has invalid data");
}
$available = array_keys((new NetworkConfigReader())->getConfigs());
$remote_enabled = array_filter($networks, function ($e) use ($available) {
return in_array($e, $available);
});
$wrapper = $this->context->getConfig();
$local_enabled = $wrapper["networks"];
$diff = array_diff($remote_enabled, $local_enabled);
if (count($diff) > 0) {
$wrapper["networks"] = array_values($remote_enabled);
$wrapper->save();
}
$last_hash = $this->config["last_hash"] ?? "";
$current_hash = md5(json_encode($remote_config));
if ($last_hash != $current_hash) {
foreach ($this->context->getRPC()->getPlugins() as $plugin) {
/**
* @var IPluggable $plugin
*/
$plugin->onSync($remote_config);
}
$this->config["last_hash"] = $current_hash;
$this->saveConfig();
}
return $diff;
}
}

View File

@ -1,7 +0,0 @@
{
"class": "Netsync",
"config": {
"master": "127.0.0.1:8001",
"key": ""
}
}

View File

@ -1,21 +0,0 @@
import {App} from "/assets/App.js";
(async function () {
$("#buttons").append(`<button id="sync">Force sync</button>`);
$("#sync").click(function () {
const self = $(this);
self.prop("disabled", true);
(async function () {
try {
alert(await App.RPC.__invoke("netsync::sync"));
} finally {
setTimeout(() => {
location.reload();
}, 5000);
}
})();
});
})();

View File

@ -1,44 +0,0 @@
#!/usr/bin/php
<?php
class Openvpn extends Plugin
{
public function restart()
{
//restart ovpn
return shell_exec($this->config["restart_cmd"]);
}
public function getRoutingConfig(): array
{
$networks = (new NetworkConfigReader())->getConfigs();
$data = [];
//add new routes
foreach ($this->context->getConfig() ["networks"] as $key) {
if (isset($networks[$key])) {
foreach ($networks[$key]["networks"] as $route) {
$parts = explode("/", $route);
$mask = long2ip(-1 << (32 - (int)$parts[1]));
$dst = $parts[0];
$data[] = "push \"route {$dst} {$mask}\"";
}
}
}
return $data;
}
public function onServerStarted()
{
$this->restart();
}
public function onSync($remote_config)
{
$this->restart();
}
}

View File

@ -1,6 +0,0 @@
{
"class": "Openvpn",
"config": {
"restart_cmd": "/etc/init.d/openvpn restart"
}
}

View File

@ -1,20 +0,0 @@
import {App} from "/assets/App.js";
(async function () {
$("#buttons").append(`<button id="restart-ovpn">Restart openvpn server</button>`);
$("#restart-ovpn").click(function () {
if (confirm("Are you sure?")) {
const self = $(this);
self.prop("disabled", true);
(async function () {
try {
alert(await App.RPC.__invoke("openvpn::restart"));
} finally {
setTimeout(() => {
self.prop("disabled", false);
}, 5000);
}
})();
}
});
})();

View File

@ -1,84 +0,0 @@
<?php
class QuaggaPlugin extends Plugin
{
const REM_PREFIX = "! routes from file ";
public function restart(): string
{
$configfile = $this->config["file"];
if (!file_exists($configfile)) {
throw new RuntimeException("Quagga config file not found");
}
$networks = (new NetworkConfigReader())->getConfigs();
$routeParser = new RoutingTableReader();
$routes = $routeParser->getRoutes();
$defGatewayInterface = "";
$defGateway = "";
foreach ($routes as $route) {
if ($route["dst"] === "0.0.0.0/0") {
$defGatewayInterface = $route["dev"];
$defGateway = $route["gateway"];
break;
}
}
if (!$defGatewayInterface) {
throw new RuntimeException("Failed to detect default gateway interface");
}
$contents = file_get_contents($configfile);
$lines = explode("\n", $contents);
//remove existing routes
foreach ($lines as $key => $line) {
if (str_starts_with($line, self::REM_PREFIX) or str_starts_with($line, "ip route ") and str_contains($line . " ", $defGateway . " ")) {
unset($lines[$key]);
}
}
//add new routes
foreach ($this->context->getConfig()["networks"] as $key) {
$lines[] = self::REM_PREFIX . $key;
if (isset($networks[$key])) {
foreach ($networks[$key]["networks"] as $route) {
$lines[] = "ip route " . $route . " " . $defGateway;
}
}
}
foreach ($lines as $key => $line) {
if (trim($line) === "") {
unset($lines[$key]);
}
}
$backupFile = $configfile . ".sav";
unlink($backupFile);
rename($configfile, $backupFile);
file_put_contents($configfile, implode("\n", $lines));
sleep(10);
//restart zebra
return shell_exec($this->config["restart_cmd"]);
}
public function onServerStarted()
{
$this->restart();
}
public function onSync($remote_config)
{
$this->restart();
}
}

View File

@ -1,7 +0,0 @@
{
"class": "QuaggaPlugin",
"config": {
"restart_cmd": "/etc/init.d/zebra restart",
"file": "/etc/quagga/zebra.conf"
}
}

View File

@ -1,20 +0,0 @@
import {App} from "/assets/App.js";
(async function () {
$("#buttons").append(`<button id="restart-quagga">Restart quagga</button>`);
$("#restart-quagga").click(function () {
if (confirm("Are you sure?")) {
const self = $(this);
self.prop("disabled", true);
(async function () {
try {
alert(await App.RPC.__invoke("quagga::restart"));
} finally {
setTimeout(() => {
self.prop("disabled", false);
}, 5000);
}
})();
}
});
})();

View File

@ -1,24 +0,0 @@
<?php
class Updates extends Plugin
{
public function check(): ?bool
{
$data = str_replace("=", "", @shell_exec("git --no-pager fetch --dry-run --porcelain --verbose 2>&1 | grep refs"));
$parts = explode(" ", trim($data));
if (count($parts) < 3) {
return null;
}
return $parts[0] != $parts[1];
}
public function install(): string|bool|null
{
return @shell_exec("git --no-pager pull --verbose 2>&1");
}
}

View File

@ -1,5 +0,0 @@
{
"class": "Updates",
"config": {
}
}

View File

@ -1,22 +0,0 @@
import {JSONRPC} from "/assets/jrpc.js";
(async function () {
$("#panel").prepend(`<div id="update-panel">Checking for updates...</div><hr>`);
let state = (await JSONRPC.__invoke("updates::check"));
if (state === null) {
$("#update-panel").html(`<span style="color:red;">Error checking updates</span>`);
} else if (state === false) {
$("#update-panel").html(`<span style="color:black;">There is no updates</span>`);
} else if (state === true) {
$("#update-panel").html(`<span style="color:green;">Some updates are available</span>&nbsp;<button>Update</button>`);
$("#update-panel button").click(async function () {
$("#panel").hide();
$("#loading").show().text("Installing updates...");
try {
alert(await JSONRPC.__invoke("updates::install"));
} finally {
setTimeout(() => location.reload(), 1000);
}
});
}
})();

View File

@ -1,12 +0,0 @@
#!/usr/bin/php
<?php
$memory = trim(shell_exec("free -b | grep Mem:"));
$memory = str_replace(" ", " ", $memory);
while (str_contains($memory, " ")) $memory = str_replace(" ", " ", $memory);
$items = explode(" ", $memory);
$available = intval($items[count($items) - 1]) / 1024 / 1024;
if ($available < 25) {
shell_exec("systemctl restart grass");
}

View File

@ -1,4 +0,0 @@
<?php
require_once __DIR__ . "/loader.php";
$router = new WebRouter();
$router->handleRequest();