firegex custom host and better port config managment

This commit is contained in:
Domingo Dirutigliano
2025-08-17 16:23:49 +02:00
parent 81f5ea817a
commit 760529e37a
4 changed files with 126 additions and 9 deletions

1
.gitignore vendored
View File

@@ -30,6 +30,7 @@
/docker-compose.yml /docker-compose.yml
/firegex-compose.yml /firegex-compose.yml
/.firegex-compose.yml /.firegex-compose.yml
/.firegex-conf.json
/.firegex-standalone.pid /.firegex-standalone.pid

View File

@@ -9,7 +9,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import jwt from jose import jwt
from passlib.context import CryptContext from passlib.context import CryptContext
from utils.sqlite import SQLite from utils.sqlite import SQLite
from utils import API_VERSION, FIREGEX_PORT, JWT_ALGORITHM, get_interfaces, socketio_emit, DEBUG, SysctlManager, NORELOAD from utils import API_VERSION, FIREGEX_PORT, FIREGEX_HOST, JWT_ALGORITHM, get_interfaces, socketio_emit, DEBUG, SysctlManager, NORELOAD
from utils.loader import frontend_deploy, load_routers from utils.loader import frontend_deploy, load_routers
from utils.models import ChangePasswordModel, IpInterface, PasswordChangeForm, PasswordForm, ResetRequest, StatusModel, StatusMessageModel from utils.models import ChangePasswordModel, IpInterface, PasswordChangeForm, PasswordForm, ResetRequest, StatusModel, StatusMessageModel
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
@@ -219,7 +219,7 @@ if __name__ == '__main__':
os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir(os.path.dirname(os.path.realpath(__file__)))
uvicorn.run( uvicorn.run(
"app:app", "app:app",
host="0.0.0.0" if DEBUG else None, host=FIREGEX_HOST,
port=FIREGEX_PORT, port=FIREGEX_PORT,
reload=DEBUG and not NORELOAD, reload=DEBUG and not NORELOAD,
access_log=True, access_log=True,

View File

@@ -25,6 +25,7 @@ ON_DOCKER = "DOCKER" in sys.argv
DEBUG = "DEBUG" in sys.argv DEBUG = "DEBUG" in sys.argv
NORELOAD = "NORELOAD" in sys.argv NORELOAD = "NORELOAD" in sys.argv
FIREGEX_PORT = int(os.getenv("PORT","4444")) FIREGEX_PORT = int(os.getenv("PORT","4444"))
FIREGEX_HOST = os.getenv("HOST","0.0.0.0")
JWT_ALGORITHM: str = "HS256" JWT_ALGORITHM: str = "HS256"
API_VERSION = "{{VERSION_PLACEHOLDER}}" if "{" not in "{{VERSION_PLACEHOLDER}}" else "0.0.0" API_VERSION = "{{VERSION_PLACEHOLDER}}" if "{" not in "{{VERSION_PLACEHOLDER}}" else "0.0.0"

129
start.py
View File

@@ -14,6 +14,7 @@ pref = "\033["
reset = f"{pref}0m" reset = f"{pref}0m"
class g: class g:
composefile = ".firegex-compose.yml" composefile = ".firegex-compose.yml"
configfile = ".firegex-conf.json"
build = False build = False
standalone_mode = False standalone_mode = False
rootfs_path = "./firegexfs" rootfs_path = "./firegexfs"
@@ -89,8 +90,45 @@ def composecmd(cmd, composefile=None):
def check_already_running(): def check_already_running():
return "firegex" in cmd_check('docker ps --filter "name=^firegex$"', get_output=True) return "firegex" in cmd_check('docker ps --filter "name=^firegex$"', get_output=True)
def load_config():
"""Load configuration from .firegex-conf.json"""
import json
default_config = {
"port": 4444,
"host": "0.0.0.0"
}
if os.path.isfile(g.configfile):
try:
with open(g.configfile, 'r') as f:
config = json.load(f)
# Ensure all required keys exist
for key, value in default_config.items():
if key not in config:
config[key] = value
return config
except (json.JSONDecodeError, IOError) as e:
puts(f"Warning: Failed to load config file {g.configfile}: {e}", color=colors.yellow)
puts("Using default configuration", color=colors.yellow)
return default_config
def save_config(config):
"""Save configuration to .firegex-conf.json"""
import json
try:
with open(g.configfile, 'w') as f:
json.dump(config, f, indent=2)
return True
except IOError as e:
puts(f"Warning: Failed to save config file {g.configfile}: {e}", color=colors.yellow)
return False
def gen_args(args_to_parse: list[str]|None = None): def gen_args(args_to_parse: list[str]|None = None):
# Load configuration
config = load_config()
#Main parser #Main parser
parser = argparse.ArgumentParser(description="Firegex Manager") parser = argparse.ArgumentParser(description="Firegex Manager")
parser.add_argument('--clear', dest="bef_clear", required=False, action="store_true", help='Delete docker volume associated to firegex resetting all the settings', default=False) parser.add_argument('--clear', dest="bef_clear", required=False, action="store_true", help='Delete docker volume associated to firegex resetting all the settings', default=False)
@@ -107,7 +145,8 @@ def gen_args(args_to_parse: list[str]|None = None):
parser_start.add_argument('--threads', "-t", type=int, required=False, help='Number of threads started for each service/utility', default=-1) parser_start.add_argument('--threads', "-t", type=int, required=False, help='Number of threads started for each service/utility', default=-1)
parser_start.add_argument('--startup-psw','-P', required=False, help='Insert password in the startup screen of firegex', type=str, default=None) parser_start.add_argument('--startup-psw','-P', required=False, help='Insert password in the startup screen of firegex', type=str, default=None)
parser_start.add_argument('--psw-on-web', required=False, help='Setup firegex password on the web interface', action="store_true", default=False) parser_start.add_argument('--psw-on-web', required=False, help='Setup firegex password on the web interface', action="store_true", default=False)
parser_start.add_argument('--port', "-p", type=int, required=False, help='Port where open the web service of the firewall', default=4444) parser_start.add_argument('--port', "-p", type=int, required=False, help=f'Port where open the web service of the firewall (default from config: {config["port"]})', default=config["port"])
parser_start.add_argument('--host', required=False, help=f'Host IP address to bind the service to (default from config: {config["host"]})', default=config["host"])
parser_start.add_argument('--logs', required=False, action="store_true", help='Show firegex logs', default=False) parser_start.add_argument('--logs', required=False, action="store_true", help='Show firegex logs', default=False)
parser_start.add_argument('--version', '-v', required=False, type=str , help='Version of the firegex image to use', default=None) parser_start.add_argument('--version', '-v', required=False, type=str , help='Version of the firegex image to use', default=None)
parser_start.add_argument('--prebuilt', required=False, action="store_true", help='Use prebuilt docker image', default=False) parser_start.add_argument('--prebuilt', required=False, action="store_true", help='Use prebuilt docker image', default=False)
@@ -119,12 +158,22 @@ def gen_args(args_to_parse: list[str]|None = None):
parser_stop.add_argument('--standalone', required=False, action="store_true", help='Force standalone mode', default=False) parser_stop.add_argument('--standalone', required=False, action="store_true", help='Force standalone mode', default=False)
parser_restart = subcommands.add_parser('restart', help='Restart the firewall') parser_restart = subcommands.add_parser('restart', help='Restart the firewall')
parser_restart.add_argument('--port', "-p", type=int, required=False, help=f'Port where open the web service of the firewall (default from config: {config["port"]})', default=config["port"])
parser_restart.add_argument('--host', required=False, help=f'Host IP address to bind the service to (default from config: {config["host"]})', default=config["host"])
parser_restart.add_argument('--logs', required=False, action="store_true", help='Show firegex logs', default=False) parser_restart.add_argument('--logs', required=False, action="store_true", help='Show firegex logs', default=False)
parser_restart.add_argument('--standalone', required=False, action="store_true", help='Force standalone mode', default=False) parser_restart.add_argument('--standalone', required=False, action="store_true", help='Force standalone mode', default=False)
#Status Command #Status Command
parser_status = subcommands.add_parser('status', help='Show firewall status') parser_status = subcommands.add_parser('status', help='Show firewall status')
parser_status.add_argument('--port', "-p", type=int, required=False, help=f'Port where open the web service of the firewall (default from config: {config["port"]})', default=config["port"])
parser_status.add_argument('--host', required=False, help=f'Host IP address to bind the service to (default from config: {config["host"]})', default=config["host"])
parser_status.add_argument('--standalone', required=False, action="store_true", help='Force standalone mode', default=False) parser_status.add_argument('--standalone', required=False, action="store_true", help='Force standalone mode', default=False)
#Config Command
parser_config = subcommands.add_parser('config', help='Manage configuration settings')
parser_config.add_argument('--port', "-p", type=int, required=False, help='Set default port for web service')
parser_config.add_argument('--host', required=False, help='Set default host IP address to bind the service to')
parser_config.add_argument('--show', required=False, action="store_true", help='Show current configuration', default=False)
args = parser.parse_args(args=args_to_parse) args = parser.parse_args(args=args_to_parse)
if "version" in args and args.version and g.build: if "version" in args and args.version and g.build:
@@ -152,8 +201,24 @@ def gen_args(args_to_parse: list[str]|None = None):
if "threads" not in args or args.threads < 1: if "threads" not in args or args.threads < 1:
args.threads = multiprocessing.cpu_count() args.threads = multiprocessing.cpu_count()
# Use config values as fallback, but allow command line to override
if "port" not in args or args.port < 1: if "port" not in args or args.port < 1:
args.port = 4444 args.port = config["port"]
if "host" not in args:
args.host = config["host"]
# Save configuration if values were specified via command line and differ from config
config_changed = False
if hasattr(args, 'port') and args.port != config["port"]:
config["port"] = args.port
config_changed = True
if hasattr(args, 'host') and args.host != config["host"]:
config["host"] = args.host
config_changed = True
if config_changed:
save_config(config)
if args.command is None: if args.command is None:
if not args.clear: if not args.clear:
@@ -168,6 +233,16 @@ args = gen_args()
def is_linux(): def is_linux():
return "linux" in sys.platform and 'microsoft-standard' not in platform.uname().release return "linux" in sys.platform and 'microsoft-standard' not in platform.uname().release
def get_web_interface_url():
# In modalità host network (Linux), l'host configurato non è applicabile
# quindi usiamo sempre localhost
if is_linux():
return f"http://localhost:{args.port}"
# Per altre piattaforme, usiamo l'host configurato se non è 0.0.0.0
# altrimenti usiamo localhost per evitare confusione
display_host = "localhost" if args.host == "0.0.0.0" else args.host
return f"http://{display_host}:{args.port}"
def write_compose(skip_password = True): def write_compose(skip_password = True):
psw_set = get_password() if not skip_password else None psw_set = get_password() if not skip_password else None
with open(g.composefile,"wt") as compose: with open(g.composefile,"wt") as compose:
@@ -182,6 +257,7 @@ def write_compose(skip_password = True):
"network_mode": "host", "network_mode": "host",
"environment": [ "environment": [
f"PORT={args.port}", f"PORT={args.port}",
f"HOST={args.host}",
f"NTHREADS={args.threads}", f"NTHREADS={args.threads}",
*([f"HEX_SET_PSW={psw_set.encode().hex()}"] if psw_set else []) *([f"HEX_SET_PSW={psw_set.encode().hex()}"] if psw_set else [])
], ],
@@ -226,7 +302,7 @@ def write_compose(skip_password = True):
"container_name": "firegex", "container_name": "firegex",
"build" if g.build else "image": "." if g.build else f"ghcr.io/pwnzer0tt1/firegex:{args.version}", "build" if g.build else "image": "." if g.build else f"ghcr.io/pwnzer0tt1/firegex:{args.version}",
"ports": [ "ports": [
f"{args.port}:{args.port}" f"{args.host}:{args.port}:{args.port}"
], ],
"environment": [ "environment": [
f"PORT={args.port}", f"PORT={args.port}",
@@ -676,6 +752,7 @@ def run_standalone():
# Set up environment variables # Set up environment variables
env_vars = [ env_vars = [
f"PORT={args.port}", f"PORT={args.port}",
f"HOST={args.host}",
f"NTHREADS={args.threads}", f"NTHREADS={args.threads}",
] ]
@@ -709,7 +786,7 @@ def run_standalone():
puts(f"Firegex started successfully (PID: {process.pid})", color=colors.green) puts(f"Firegex started successfully (PID: {process.pid})", color=colors.green)
if is_process_running(process.pid): if is_process_running(process.pid):
puts(f"Web interface should be available at: http://localhost:{args.port}", color=colors.cyan) puts(f"Web interface should be available at: {get_web_interface_url()}", color=colors.cyan)
else: else:
puts("Firegex process failed to start", color=colors.red) puts("Firegex process failed to start", color=colors.red)
remove_pid_file() remove_pid_file()
@@ -753,6 +830,40 @@ def clear_standalone():
else: else:
puts("Standalone rootfs not found", color=colors.yellow) puts("Standalone rootfs not found", color=colors.yellow)
def handle_config_command(args):
"""Handle config command"""
config = load_config()
config_changed = False
if args.show:
puts("Current configuration:", color=colors.cyan, is_bold=True)
puts(f"Port: {config['port']}", color=colors.white)
puts(f"Host: {config['host']}", color=colors.white)
puts(f"Config file: {g.configfile}", color=colors.white)
return
if hasattr(args, 'port') and args.port is not None:
if args.port < 1 or args.port > 65535:
puts("Error: Port must be between 1 and 65535", color=colors.red)
exit(1)
config["port"] = args.port
config_changed = True
puts(f"Port set to: {args.port}", color=colors.green)
if hasattr(args, 'host') and args.host is not None:
config["host"] = args.host
config_changed = True
puts(f"Host set to: {args.host}", color=colors.green)
if config_changed:
if save_config(config):
puts(f"Configuration saved to {g.configfile}", color=colors.green)
else:
puts("Failed to save configuration", color=colors.red)
exit(1)
else:
puts("No configuration changes specified. Use --show to view current configuration.", color=colors.yellow)
def status_standalone(): def status_standalone():
"""Show standalone mode status""" """Show standalone mode status"""
puts("Standalone mode status:", color=colors.cyan, is_bold=True) puts("Standalone mode status:", color=colors.cyan, is_bold=True)
@@ -761,7 +872,7 @@ def status_standalone():
if is_standalone_running(): if is_standalone_running():
pid = read_pid_file() pid = read_pid_file()
puts(f"Status: Running (PID: {pid})", color=colors.green) puts(f"Status: Running (PID: {pid})", color=colors.green)
puts(f"Web interface: http://localhost:{args.port}", color=colors.cyan) puts(f"Web interface: {get_web_interface_url()}", color=colors.cyan)
else: else:
puts("Status: Not running", color=colors.red) puts("Status: Not running", color=colors.red)
if os.path.exists(g.rootfs_path): if os.path.exists(g.rootfs_path):
@@ -795,7 +906,7 @@ def main():
if is_standalone_running(): if is_standalone_running():
pid = read_pid_file() pid = read_pid_file()
puts(f"Firegex is already running in standalone mode! (PID: {pid})", color=colors.yellow) puts(f"Firegex is already running in standalone mode! (PID: {pid})", color=colors.yellow)
puts(f"Web interface available at: http://localhost:{args.port}", color=colors.cyan) puts(f"Web interface available at: {get_web_interface_url()}", color=colors.cyan)
return return
if not setup_standalone_rootfs(): if not setup_standalone_rootfs():
@@ -812,6 +923,8 @@ def main():
if not setup_standalone_mounts(): if not setup_standalone_mounts():
exit(1) exit(1)
run_standalone() run_standalone()
elif args.command == "config":
handle_config_command(args)
else: else:
puts("Command not supported in standalone mode", color=colors.red) puts("Command not supported in standalone mode", color=colors.red)
exit(1) exit(1)
@@ -881,9 +994,11 @@ def main():
case "status": case "status":
if check_already_running(): if check_already_running():
puts("Firegex is running in Docker mode", color=colors.green) puts("Firegex is running in Docker mode", color=colors.green)
puts(f"Web interface: http://localhost:{args.port}", color=colors.cyan) puts(f"Web interface: {get_web_interface_url()}", color=colors.cyan)
else: else:
puts("Firegex is not running", color=colors.red) puts("Firegex is not running", color=colors.red)
case "config":
handle_config_command(args)
write_compose() write_compose()