Iptables -> NFtables

This commit is contained in:
DomySh
2022-07-19 15:17:34 +02:00
parent 139fe39130
commit a020e4311d
17 changed files with 2310 additions and 2127 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@
# testing # testing
/frontend/coverage /frontend/coverage
/backend/db/
/backend/db/firegex.db /backend/db/firegex.db
/backend/db/firegex.db-journal /backend/db/firegex.db-journal
/backend/modules/cppqueue /backend/modules/cppqueue

View File

@@ -2,7 +2,7 @@
FROM python:slim-bullseye FROM python:slim-bullseye
RUN apt-get update && apt-get -y install \ RUN apt-get update && apt-get -y install \
build-essential git iptables libpcre2-dev\ build-essential git python3-nftables libpcre2-dev\
libnetfilter-queue-dev libtins-dev\ libnetfilter-queue-dev libtins-dev\
libnfnetlink-dev libmnl-dev libnfnetlink-dev libmnl-dev

View File

@@ -9,10 +9,9 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt from jose import JWTError, jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from fastapi_socketio import SocketManager from fastapi_socketio import SocketManager
from ipaddress import ip_interface
from modules import SQLite, FirewallManager from modules import SQLite, FirewallManager
from modules.firewall import STATUS from modules.firewall import STATUS
from utils import refactor_name, gen_service_id from utils import ip_parse, refactor_name, gen_service_id
ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER" ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER"
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG" DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
@@ -54,8 +53,10 @@ async def startup_event():
@app.on_event("shutdown") @app.on_event("shutdown")
async def shutdown_event(): async def shutdown_event():
db.backup()
await firewall.close() await firewall.close()
db.disconnect() db.disconnect()
db.restore()
def create_access_token(data: dict): def create_access_token(data: dict):
to_encode = data.copy() to_encode = data.copy()
@@ -350,11 +351,8 @@ class ServiceAddResponse(BaseModel):
@app.post('/api/services/add', response_model=ServiceAddResponse) @app.post('/api/services/add', response_model=ServiceAddResponse)
async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)): async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)):
"""Add a new service""" """Add a new service"""
ipv6 = None
try: try:
ip_int = ip_interface(form.ip_int) form.ip_int = ip_parse(form.ip_int)
ipv6 = ip_int.version == 6
form.ip_int = str(ip_int)
except ValueError: except ValueError:
return {"status":"Invalid address"} return {"status":"Invalid address"}
if form.proto not in ["tcp", "udp"]: if form.proto not in ["tcp", "udp"]:
@@ -363,7 +361,7 @@ async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined
try: try:
srv_id = gen_service_id(db) srv_id = gen_service_id(db)
db.query("INSERT INTO services (service_id ,name, port, ipv6, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?, ?)", db.query("INSERT INTO services (service_id ,name, port, ipv6, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?, ?)",
srv_id, refactor_name(form.name), form.port, ipv6, STATUS.STOP, form.proto, form.ip_int) srv_id, refactor_name(form.name), form.port, True, STATUS.STOP, form.proto, form.ip_int)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return {'status': 'This type of service already exists'} return {'status': 'This type of service already exists'}
await firewall.reload() await firewall.reload()

View File

@@ -1,113 +1,140 @@
from typing import Dict, List, Set from typing import Dict, List, Set
from ipaddress import ip_interface from utils import ip_parse, ip_family
from modules.iptables import IPTables
from modules.sqlite import Service from modules.sqlite import Service
import re, os, asyncio import re, os, asyncio
import traceback import traceback, nftables
from modules.sqlite import Regex from modules.sqlite import Regex
class FilterTypes:
INPUT = "FIREGEX-INPUT"
OUTPUT = "FIREGEX-OUTPUT"
QUEUE_BASE_NUM = 1000 QUEUE_BASE_NUM = 1000
class FiregexFilter(): class FiregexFilter():
def __init__(self, proto:str, port:int, ip_int:str, queue=None, target=None, id=None): def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None):
self.target = target self.nftables = nftables.Nftables()
self.id = int(id) if id else None self.id = int(id) if id else None
self.queue = queue self.queue = queue
self.target = target
self.proto = proto self.proto = proto
self.port = int(port) self.port = int(port)
self.ip_int = str(ip_int) self.ip_int = str(ip_int)
def __eq__(self, o: object) -> bool: def __eq__(self, o: object) -> bool:
if isinstance(o, FiregexFilter): if isinstance(o, FiregexFilter):
return self.port == o.port and self.proto == o.proto and ip_interface(self.ip_int) == ip_interface(o.ip_int) return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
return False return False
def ipv6(self):
return ip_interface(self.ip_int).version == 6
def ipv4(self): class FiregexTables:
return ip_interface(self.ip_int).version == 4
class FiregexTables(IPTables): def __init__(self):
self.table_name = "firegex"
self.nft = nftables.Nftables()
def raw_cmd(self, *cmds):
return self.nft.json_cmd({"nftables": list(cmds)})
def __init__(self, ipv6=False): def cmd(self, *cmds):
super().__init__(ipv6, "mangle") code, out, err = self.raw_cmd(*cmds)
self.create_chain(FilterTypes.INPUT)
self.add_chain_to_input(FilterTypes.INPUT)
self.create_chain(FilterTypes.OUTPUT)
self.add_chain_to_output(FilterTypes.OUTPUT)
def target_in_chain(self, chain, target):
for filter in self.list()[chain]:
if filter.target == target:
return True
return False
def add_chain_to_input(self, chain):
if not self.target_in_chain("PREROUTING", str(chain)):
self.insert_rule("PREROUTING", str(chain))
def add_chain_to_output(self, chain):
if not self.target_in_chain("POSTROUTING", str(chain)):
self.insert_rule("POSTROUTING", str(chain))
def add_output(self, queue_range, proto = None, port = None, ip_int = None): if code == 0: return out
else: raise Exception(err)
def init(self):
code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
if code == 0:
self.cmd(
{"create":{"chain":{
"family":"inet",
"table":self.table_name,
"name":"input",
"type":"filter",
"hook":"prerouting",
"prio":-150,
"policy":"accept"
}}},
{"create":{"chain":{
"family":"inet",
"table":self.table_name,
"name":"output",
"type":"filter",
"hook":"postrouting",
"prio":-150,
"policy":"accept"
}}}
)
self.reset()
def reset(self):
self.cmd({"flush":{"table":{"name":"firegex","family":"inet"}}})
def list(self):
return self.cmd({"list": {"ruleset": None}})["nftables"]
def add_output(self, queue_range, proto, port, ip_int):
init, end = queue_range init, end = queue_range
if init > end: init, end = end, init if init > end: init, end = end, init
self.append_rule(FilterTypes.OUTPUT,"NFQUEUE", ip_int = ip_parse(ip_int)
* (["-p", str(proto)] if proto else []), ip_addr = str(ip_int).split("/")[0]
* (["-s", str(ip_int)] if ip_int else []), ip_addr_cidr = int(str(ip_int).split("/")[1])
* (["--sport", str(port)] if port else []), self.cmd({ "insert":{ "rule": {
* (["--queue-num", f"{init}"] if init == end else ["--queue-balance", f"{init}:{end}"]), "family": "inet",
"--queue-bypass" "table": self.table_name,
) "chain": "output",
"expr": [
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, #ip_int
{'match': {'left': {'meta': {'key': 'l4proto'}}, 'op': '==', 'right': str(proto)}},
{'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}},
{"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}}
]
}}})
def add_input(self, queue_range, proto = None, port = None, ip_int = None): def add_input(self, queue_range, proto = None, port = None, ip_int = None):
init, end = queue_range init, end = queue_range
if init > end: init, end = end, init if init > end: init, end = end, init
self.append_rule(FilterTypes.INPUT, "NFQUEUE", ip_int = ip_parse(ip_int)
* (["-p", str(proto)] if proto else []), ip_addr = str(ip_int).split("/")[0]
* (["-d", str(ip_int)] if ip_int else []), ip_addr_cidr = int(str(ip_int).split("/")[1])
* (["--dport", str(port)] if port else []), self.cmd({"insert":{"rule":{
* (["--queue-num", f"{init}"] if init == end else ["--queue-balance", f"{init}:{end}"]), "family": "inet",
"--queue-bypass" "table": self.table_name,
) "chain": "input",
"expr": [
{'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, #ip_int
{'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}},
{"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}}
]
}}})
def get(self) -> List[FiregexFilter]: def get(self) -> List[FiregexFilter]:
res = [] res = []
iptables_filters = self.list() for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]:
for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]: queue_str = str(filter["expr"][2]["queue"]["num"]).split("-")
for filter in iptables_filters[filter_type]: queue = None
port = filter.sport() if filter_type == FilterTypes.OUTPUT else filter.dport() if len(queue_str) == 1: queue = int(queue_str[0]), int(queue_str[0])
queue = filter.nfqueue() else: queue = int(queue_str[0]), int(queue_str[1])
if queue and port: ip_int = None
res.append(FiregexFilter( if isinstance(filter["expr"][0]["match"]["right"],str):
target=filter_type, ip_int = str(ip_parse(filter["expr"][0]["match"]["right"]))
id=filter.id, else:
queue=queue, ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}'
proto=filter.prot, res.append(FiregexFilter(
port=port, target=filter["chain"],
ip_int=filter.source if filter_type == FilterTypes.OUTPUT else filter.destination id=int(filter["handle"]),
)) queue=queue,
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
port=filter["expr"][1]["match"]["right"],
ip_int=ip_int
))
return res return res
async def add(self, filter:FiregexFilter): async def add(self, filter:FiregexFilter):
if filter in self.get(): return None if filter in self.get(): return None
return await FiregexInterceptor.start( iptables=self, filter=filter, n_queues=int(os.getenv("N_THREADS_NFQUEUE","1"))) return await FiregexInterceptor.start( filter=filter, n_queues=int(os.getenv("N_THREADS_NFQUEUE","1")))
def delete_all(self):
for filter_type in [FilterTypes.INPUT, FilterTypes.OUTPUT]:
self.flush_chain(filter_type)
def delete_by_srv(self, srv:Service): def delete_by_srv(self, srv:Service):
for filter in self.get(): for filter in self.get():
if filter.port == srv.port and filter.proto == srv.proto and ip_interface(filter.ip_int) == ip_interface(srv.ip_int): if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int):
self.delete_rule(filter.target, filter.id) print("DELETE CMD", {"delete":{"rule": {"handle": filter.id, "table": self.table_name, "chain": filter.target, "family": "inet"}}})
self.cmd({"delete":{"rule": {"handle": filter.id, "table": self.table_name, "chain": filter.target, "family": "inet"}}})
class RegexFilter: class RegexFilter:
@@ -159,7 +186,6 @@ class FiregexInterceptor:
def __init__(self): def __init__(self):
self.filter:FiregexFilter self.filter:FiregexFilter
self.ipv6:bool
self.filter_map_lock:asyncio.Lock self.filter_map_lock:asyncio.Lock
self.filter_map: Dict[str, RegexFilter] self.filter_map: Dict[str, RegexFilter]
self.regex_filters: Set[RegexFilter] self.regex_filters: Set[RegexFilter]
@@ -167,21 +193,18 @@ class FiregexInterceptor:
self.process:asyncio.subprocess.Process self.process:asyncio.subprocess.Process
self.n_queues:int self.n_queues:int
self.update_task: asyncio.Task self.update_task: asyncio.Task
self.iptables:FiregexTables
@classmethod @classmethod
async def start(cls, iptables: FiregexTables, filter: FiregexFilter, n_queues:int = 1): async def start(cls, filter: FiregexFilter, n_queues:int = 1):
self = cls() self = cls()
self.filter = filter self.filter = filter
self.n_queues = n_queues self.n_queues = n_queues
self.iptables = iptables
self.ipv6 = self.filter.ipv6()
self.filter_map_lock = asyncio.Lock() self.filter_map_lock = asyncio.Lock()
self.update_config_lock = asyncio.Lock() self.update_config_lock = asyncio.Lock()
input_range, output_range = await self._start_binary() input_range, output_range = await self._start_binary()
self.update_task = asyncio.create_task(self.update_blocked()) self.update_task = asyncio.create_task(self.update_blocked())
self.iptables.add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
self.iptables.add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int)
return self return self
async def _start_binary(self): async def _start_binary(self):
@@ -221,7 +244,8 @@ class FiregexInterceptor:
async def stop(self): async def stop(self):
self.update_task.cancel() self.update_task.cancel()
self.process.kill() if self.process and self.process.returncode is None:
self.process.kill()
async def _update_config(self, filters_codes): async def _update_config(self, filters_codes):
async with self.update_config_lock: async with self.update_config_lock:

View File

@@ -24,6 +24,7 @@ class FirewallManager:
del self.proxy_table[srv_id] del self.proxy_table[srv_id]
async def init(self): async def init(self):
FiregexTables().init()
await self.reload() await self.reload()
async def reload(self): async def reload(self):
@@ -47,7 +48,6 @@ class ServiceManager:
def __init__(self, srv: Service, db): def __init__(self, srv: Service, db):
self.srv = srv self.srv = srv
self.db = db self.db = db
self.firegextable = FiregexTables(self.srv.ipv6)
self.status = STATUS.STOP self.status = STATUS.STOP
self.filters: Dict[int, FiregexFilter] = {} self.filters: Dict[int, FiregexFilter] = {}
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
@@ -93,13 +93,13 @@ class ServiceManager:
async def start(self): async def start(self):
if not self.interceptor: if not self.interceptor:
self.firegextable.delete_by_srv(self.srv) FiregexTables().delete_by_srv(self.srv)
self.interceptor = await self.firegextable.add(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int)) self.interceptor = await FiregexTables().add(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int))
await self._update_filters_from_db() await self._update_filters_from_db()
self._set_status(STATUS.ACTIVE) self._set_status(STATUS.ACTIVE)
async def stop(self): async def stop(self):
self.firegextable.delete_by_srv(self.srv) FiregexTables().delete_by_srv(self.srv)
if self.interceptor: if self.interceptor:
await self.interceptor.stop() await self.interceptor.stop()
self.interceptor = None self.interceptor = None

View File

@@ -1,85 +0,0 @@
import os, re
from subprocess import PIPE, Popen
from typing import Dict, List, Tuple, Union
class Rule():
def __init__(self, id, target, prot, opt, source, destination, details):
self.id = id
self.target = target
self.prot = prot
self.opt = opt
self.source = source
self.destination = destination
self.details = details
def __repr__(self) -> str:
return f"Rule {self.id} : {self.target}, {self.prot}, {self.opt}, {self.source}, {self.destination}, {self.details}"
def dport(self) -> Union[int, None]:
port = re.findall(r"dpt:([0-9]+)", self.details)
return int(port[0]) if port else None
def sport(self) -> Union[int, None]:
port = re.findall(r"spt:([0-9]+)", self.details)
return int(port[0]) if port else None
def nfqueue(self) -> Union[Tuple[int,int], None]:
balanced = re.findall(r"NFQUEUE balance ([0-9]+):([0-9]+)", self.details)
numbered = re.findall(r"NFQUEUE num ([0-9]+)", self.details)
queue_num = None
if balanced: queue_num = (int(balanced[0][0]), int(balanced[0][1]))
if numbered: queue_num = (int(numbered[0]), int(numbered[0]))
return queue_num
class IPTables:
def __init__(self, ipv6=False, table="filter"):
self.ipv6 = ipv6
self.table = table
def command(self, params) -> Tuple[bytes, bytes]:
params = ["-t", self.table] + params
if os.geteuid() != 0:
exit("You need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.")
return Popen(["ip6tables"]+params if self.ipv6 else ["iptables"]+params, stdout=PIPE, stderr=PIPE).communicate()
def list(self) -> Dict[str, List[Rule]]:
stdout, strerr = self.command(["-L", "--line-number", "-n"])
lines = stdout.decode().split("\n")
res: Dict[str, List[Rule]] = {}
chain_name = ""
for line in lines:
if line.startswith("Chain"):
chain_name = line.split()[1]
res[chain_name] = []
elif line and line.split()[0].isnumeric():
parsed = re.findall(r"([^ ]*)[ ]{,10}([^ ]*)[ ]{,5}([^ ]*)[ ]{,5}([^ ]*)[ ]{,5}([^ ]*)[ ]+([^ ]*)[ ]+(.*)", line)
if len(parsed) > 0:
parsed = parsed[0]
res[chain_name].append(Rule(
id=parsed[0].strip(),
target=parsed[1].strip(),
prot=parsed[2].strip(),
opt=parsed[3].strip(),
source=parsed[4].strip(),
destination=parsed[5].strip(),
details=" ".join(parsed[6:]).strip() if len(parsed) >= 7 else ""
))
return res
def delete_rule(self, chain, id) -> None:
self.command(["-D", str(chain), str(id)])
def create_chain(self, name) -> None:
self.command(["-N", str(name)])
def flush_chain(self, name) -> None:
self.command(["-F", str(name)])
def insert_rule(self, chain, rule, *args, rulenum=1) -> None:
self.command(["-I", str(chain), str(rulenum), "-j", str(rule), *args])
def append_rule(self, chain, rule, *args) -> None:
self.command(["-A", str(chain), "-j", str(rule), *args])

View File

@@ -8,6 +8,7 @@ class SQLite():
self.conn: Union[None, sqlite3.Connection] = None self.conn: Union[None, sqlite3.Connection] = None
self.cur = None self.cur = None
self.db_name = db_name self.db_name = db_name
self.__backup = None
self.schema = { self.schema = {
'services': { 'services': {
'service_id': 'VARCHAR(100) PRIMARY KEY', 'service_id': 'VARCHAR(100) PRIMARY KEY',
@@ -49,6 +50,17 @@ class SQLite():
return d return d
self.conn.row_factory = dict_factory self.conn.row_factory = dict_factory
def backup(self):
if self.conn:
with open(self.db_name, "rb") as f:
self.__backup = f.read()
def restore(self):
if self.__backup:
with open(self.db_name, "wb") as f:
f.write(self.__backup)
self.__backup = None
def disconnect(self) -> None: def disconnect(self) -> None:
if self.conn: self.conn.close() if self.conn: self.conn.close()
@@ -101,18 +113,17 @@ class SQLite():
class Service: class Service:
def __init__(self, id: str, status: str, port: int, name: str, ipv6: bool, proto: str, ip_int: str): def __init__(self, id: str, status: str, port: int, name: str, proto: str, ip_int: str):
self.id = id self.id = id
self.status = status self.status = status
self.port = port self.port = port
self.name = name self.name = name
self.ipv6 = ipv6
self.proto = proto self.proto = proto
self.ip_int = ip_int self.ip_int = ip_int
@classmethod @classmethod
def from_dict(cls, var: dict): def from_dict(cls, var: dict):
return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], ipv6=var["ipv6"], proto=var["proto"], ip_int=var["ip_int"]) return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"])
class Regex: class Regex:

View File

@@ -12,11 +12,11 @@ void config_updater (){
while (true){ while (true){
getline(cin, line); getline(cin, line);
if (cin.eof()){ if (cin.eof()){
cerr << "[fatal] [upfdater] cin.eof()" << endl; cerr << "[fatal] [updater] cin.eof()" << endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (cin.bad()){ if (cin.bad()){
cerr << "[fatal] [upfdater] cin.bad()" << endl; cerr << "[fatal] [updater] cin.bad()" << endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
cerr << "[info] [updater] Updating configuration with line " << line << endl; cerr << "[info] [updater] Updating configuration with line " << line << endl;

View File

@@ -4,3 +4,4 @@ uvicorn[standard]
passlib[bcrypt] passlib[bcrypt]
python-jose[cryptography] python-jose[cryptography]
fastapi-socketio fastapi-socketio
git+https://salsa.debian.org/pkg-netfilter-team/pkg-nftables#egg=nftables&subdirectory=py

View File

@@ -1,3 +1,4 @@
from ipaddress import ip_interface
import os, socket, secrets import os, socket, secrets
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1")) LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
@@ -12,4 +13,10 @@ def gen_service_id(db):
res = secrets.token_hex(8) res = secrets.token_hex(8)
if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0: if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0:
break break
return res return res
def ip_parse(ip:str):
return str(ip_interface(ip).network)
def ip_family(ip:str):
return "ip6" if ip_interface(ip).version == 6 else "ip"

View File

@@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.08225a85.css", "main.css": "/static/css/main.08225a85.css",
"main.js": "/static/js/main.0e7d88b5.js", "main.js": "/static/js/main.70ebb0b2.js",
"index.html": "/index.html", "index.html": "/index.html",
"main.08225a85.css.map": "/static/css/main.08225a85.css.map", "main.08225a85.css.map": "/static/css/main.08225a85.css.map",
"main.0e7d88b5.js.map": "/static/js/main.0e7d88b5.js.map" "main.70ebb0b2.js.map": "/static/js/main.70ebb0b2.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.08225a85.css", "static/css/main.08225a85.css",
"static/js/main.0e7d88b5.js" "static/js/main.70ebb0b2.js"
] ]
} }

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.0e7d88b5.js"></script><link href="/static/css/main.08225a85.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.70ebb0b2.js"></script><link href="/static/css/main.08225a85.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,7 @@ services:
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
""") """)
#print("Done! You can start firegex with docker-compose up -d --build")
else: else:
sep() sep()
puts("--- WARNING ---", color=colors.yellow) puts("--- WARNING ---", color=colors.yellow)
@@ -73,7 +73,7 @@ services:
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
""") """)
#
sep() sep()
if not args.no_autostart: if not args.no_autostart:
puts("Running 'docker-compose up -d --build'\n", color=colors.green) puts("Running 'docker-compose up -d --build'\n", color=colors.green)