From 7c4ad5104c4427682e85c7c8934150adcca23fd9 Mon Sep 17 00:00:00 2001 From: Nick Guy Date: Sun, 2 Mar 2025 14:40:53 +0000 Subject: [PATCH] Initial commit --- api/__init__.py | 41 +++++++++++++++++++ api/pelican.py | 32 +++++++++++++++ main.py | 104 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 ++ 4 files changed, 181 insertions(+) create mode 100644 api/__init__.py create mode 100644 api/pelican.py create mode 100644 main.py create mode 100644 requirements.txt diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..65b893e --- /dev/null +++ b/api/__init__.py @@ -0,0 +1,41 @@ +import requests +import enum +from typing import Tuple + + +class ApiAuthType(enum.Enum): + NoAuth = 0 + Header = 1 + Cookie = 2 + + +class Api(object): + base_url: str = None + + def transform(self, data): + return data + + def apply_authentication(self) -> Tuple[ApiAuthType, dict]: + raise NotImplementedError + + def _get(self, endpoint, raw: bool = False): + + url = self.base_url + endpoint + + headers = { + "Accept": "application/json" + } + + cookies = {} + + ty, auth = self.apply_authentication() + if ty == ApiAuthType.Header: + headers.update(auth) + elif ty == ApiAuthType.Cookie: + cookies.update(auth) + + response = requests.get(url, headers=headers, cookies=cookies) + if raw: + return response + + return self.transform(response.json()) diff --git a/api/pelican.py b/api/pelican.py new file mode 100644 index 0000000..6782558 --- /dev/null +++ b/api/pelican.py @@ -0,0 +1,32 @@ +from api import Api, ApiAuthType +from typing import Tuple + + +class PelicanApi(Api): + def __init__(self, base_url, auth_token): + self.base_url = base_url + self.auth_token = auth_token + + def apply_authentication(self) -> Tuple[ApiAuthType, dict]: + return ApiAuthType.Header, { + "Authorization": "Bearer " + self.auth_token + } + + def transform(self, data): + type = data["object"] + if type == "list": + return [self.transform(x) for x in data["data"]] + + if type in ["allocation", "server", "node"]: + return data["attributes"] + + return data + + def nodes(self): + return self._get("/application/nodes") + + def servers(self): + return self._get("/application/servers") + + def allocations(self, node_id: int): + return self._get("/application/nodes/" + str(node_id) + "/allocations") diff --git a/main.py b/main.py new file mode 100644 index 0000000..7a30d73 --- /dev/null +++ b/main.py @@ -0,0 +1,104 @@ +import os + +import aiohttp +import asyncio +import socket + +import configparser + +cfg = configparser.ConfigParser() + +import asusrouter.modules.port_forwarding +from asusrouter import AsusRouter, AsusData +from api.pelican import PelicanApi +from flask import Flask, request + +RULE_PREFIX = "[auto]::" + + +def create_rule(label, address, port): + return asusrouter.modules.port_forwarding.PortForwardingRule(RULE_PREFIX + label, address, None, "BOTH", None, port) + + +def rule_to_string(rule: asusrouter.modules.port_forwarding.PortForwardingRule): + return f"{rule.name}::{rule.port_external}->{rule.port if rule.port else rule.port_external}@{rule.ip_address}" + + +def apply_port_changes(): + loop = asyncio.get_event_loop() + + session = aiohttp.ClientSession(loop=loop) + + pelican_cfg = cfg["pelican"] + router_cfg = cfg["router"] + pelican = PelicanApi(pelican_cfg["baseurl"], pelican_cfg["auth"]) + router = AsusRouter( + hostname=router_cfg["hostname"], + username=router_cfg["username"], + password=router_cfg["password"], + use_ssl=False, + session=session + ) + + run = loop.run_until_complete + + run(router.async_connect()) + + data = run(router.async_get_data(AsusData.PORT_FORWARDING)) + rules = data["rules"] + x: asusrouter.modules.port_forwarding.PortForwardingRule + # Remove auto-generated rules + rules = [x for x in rules if not x.name.startswith(RULE_PREFIX)] + + # Add new auto-generated rules + allocation_server_lookup = {} + for server in pelican.servers(): + allocation_server_lookup[server["allocation"]] = server + + for node in pelican.nodes(): + node_id = node["id"] + allocs = pelican.allocations(node_id) + + node_internal_name = node["name"] + ".pve.local" + node_internal_ip = socket.getaddrinfo(node_internal_name, 0)[0][4][0] + + for alloc in allocs: + if not alloc["assigned"]: + continue + + label = "Allocation " + str(alloc["id"]) + " for " + node_internal_name + if alloc["id"] in allocation_server_lookup.keys(): + svr = allocation_server_lookup[alloc["id"]] + label = svr["name"] + "(" + svr["identifier"] + ") allocation" + + rules.append(create_rule(label, node_internal_ip, alloc["port"])) + + run(router.async_apply_port_forwarding_rules(rules)) + + print("Currently open ports: ") + for rule in rules: + print("\t" + rule_to_string(rule)) + + run(router.async_disconnect()) + run(session.close()) + + +app = Flask(__name__) + + +@app.route("/pelican-wh", methods=["GET", "POST"]) +def start(): + apply_port_changes() + return "Hello, world!" + + +if __name__ == '__main__': + if os.path.exists("/config/config.ini"): + cfg.read("/config/config.ini") + else: + print("No config file detected, exiting...") + exit(1) + + port = int(cfg["app"]["port"]) + + app.run(host="0.0.0.0", port=port) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dbedc42 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +requests~=2.32.3 +aiohttp~=3.11.13 +asusrouter~=1.13.1 +Flask~=3.1.0 \ No newline at end of file