Initial commit

This commit is contained in:
Nick Guy 2025-03-02 14:40:53 +00:00
parent fabf0319db
commit 7c4ad5104c
4 changed files with 181 additions and 0 deletions

41
api/__init__.py Normal file
View file

@ -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())

32
api/pelican.py Normal file
View file

@ -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")

104
main.py Normal file
View file

@ -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)

4
requirements.txt Normal file
View file

@ -0,0 +1,4 @@
requests~=2.32.3
aiohttp~=3.11.13
asusrouter~=1.13.1
Flask~=3.1.0