feat: make config loading more flexible

If the environment variable BAPI_ENV_PATH is set, the config system will
try to read configs from the given path instead of .env in pwd

If no file is found .env in pwd will be used as a fallback

Env variables will always override settings in .env files.
This commit is contained in:
fusion44 2025-02-16 20:35:21 +01:00
parent 76b3e5019b
commit 25e4be8441
24 changed files with 444 additions and 235 deletions

View file

@ -1,11 +1,11 @@
import logging
import secrets
from typing import Dict
import psutil
from decouple import config
from fastapi import HTTPException, status
from loguru import logger
from app.api.config import config
from app.api.constants import API_VERSION
from app.auth.auth_handler import sign_jwt
from app.lightning.service import get_ln_info
@ -19,24 +19,25 @@ from app.system.models import (
SystemInfo,
)
_SLEEP_TIME = config("gather_hw_info_interval", default=2, cast=float)
_CPU_AVG_PERIOD = config("cpu_usage_averaging_period", default=0.5, cast=float)
_SLEEP_TIME = config("BAPI_GATHER_HW_INFO_INTERVAL", default=2, cast=float)
_CPU_AVG_PERIOD = config("BAPI_CPU_USAGE_AVERAGING_PERIOD", default=0.5, cast=float)
_HW_INFO_YIELD_TIME = _SLEEP_TIME + _CPU_AVG_PERIOD
class NativePythonSystem(SystemBase):
@logger.catch(exclude=(HTTPException,))
async def get_system_info(self) -> SystemInfo:
lninfo = await get_ln_info()
version = config("np_version", default="")
version = config("BAPI_NP_VERSION", default="")
tor_api = config("np_tor_address_api_endpoint", default="")
tor_api_docs = config("np_tor_address_api_docs", default="")
tor_api = config("BAPI_NP_TOR_ADDRESS_API_ENDPOINT", default="")
tor_api_docs = config("BAPI_NP_TOR_ADDRESS_API_DOCS", default="")
lan_api = config("np_local_address_api_endpoint", default="")
lan_api_docs = config("np_local_address_api_docs", default="")
lan_api = config("BAPI_NP_LOCAL_ADDRESS_API_ENDPOINT", default="")
lan_api_docs = config("BAPI_NP_LOCAL_ADDRESS_API_DOCS", default="")
ssh_address = config("np_ssh_address", default="")
ssh_address = config("BAPI_NP_SSH_ADDRESS", default="")
return SystemInfo(
alias=lninfo.alias,
@ -52,19 +53,26 @@ class NativePythonSystem(SystemBase):
chain=lninfo.chains[0].network,
)
@logger.catch(exclude=(HTTPException,))
async def get_system_health(self, verbose: bool) -> SystemHealthInfo:
return SystemHealthInfo(healthy=True)
@logger.catch(exclude=(HTTPException,))
async def shutdown(self, reboot: bool) -> bool:
logging.info("Shutdown / reboot not supported in native_python mode.")
logger.info("Shutdown / reboot not supported in native_python mode.")
return False
@logger.catch(exclude=(HTTPException,))
async def get_connection_info(self) -> ConnectionInfo:
# return an empty connection info object for now
return ConnectionInfo()
@logger.catch(exclude=(HTTPException,))
async def login(self, i: LoginInput) -> Dict[str, str]:
matches = secrets.compare_digest(i.password, config("login_password", cast=str))
# https://github.com/fusion44/blitz_api/issues/255
matches = secrets.compare_digest(
i.password, config("BAPI_NATIVE_LOGIN_PASSWORD", cast=str)
)
if matches:
return sign_jwt()
@ -72,75 +80,92 @@ class NativePythonSystem(SystemBase):
status.HTTP_401_UNAUTHORIZED, detail="Password is incorrect"
)
@logger.catch(exclude=(HTTPException,))
async def change_password(self, type: str, old_password: str, new_password: str):
raise NotImplementedError()
@logger.catch(exclude=(HTTPException,))
async def get_debug_logs_raw(self) -> RawDebugLogData:
raise NotImplementedError()
@logger.catch(exclude=(HTTPException,))
async def get_hardware_info(self) -> map:
info = {}
info["cpu_overall_percent"] = psutil.cpu_percent(interval=_CPU_AVG_PERIOD)
info["cpu_per_cpu_percent"] = psutil.cpu_percent(
interval=_CPU_AVG_PERIOD, percpu=True
)
try:
info["cpu_overall_percent"] = psutil.cpu_percent(interval=_CPU_AVG_PERIOD)
info["cpu_per_cpu_percent"] = psutil.cpu_percent(
interval=_CPU_AVG_PERIOD, percpu=True
)
v = psutil.virtual_memory()
info["vram_total_bytes"] = v.total
info["vram_available_bytes"] = v.available
info["vram_used_bytes"] = v.used
info["vram_usage_percent"] = v.percent
v = psutil.virtual_memory()
info["vram_total_bytes"] = v.total
info["vram_available_bytes"] = v.available
info["vram_used_bytes"] = v.used
info["vram_usage_percent"] = v.percent
s = psutil.swap_memory()
info["swap_ram_total_bytes"] = s.total
info["swap_used_bytes"] = s.used
info["swap_usage_bytes"] = s.percent
s = psutil.swap_memory()
info["swap_ram_total_bytes"] = s.total
info["swap_used_bytes"] = s.used
info["swap_usage_bytes"] = s.percent
info["temperatures_celsius"] = psutil.sensors_temperatures()
info["boot_time_timestamp"] = psutil.boot_time()
info["temperatures_celsius"] = psutil.sensors_temperatures()
info["boot_time_timestamp"] = psutil.boot_time()
disk_io = psutil.disk_io_counters()
info["disk_io_read_count"] = disk_io.read_count
info["disk_io_write_count"] = disk_io.write_count
info["disk_io_read_bytes"] = disk_io.read_bytes
info["disk_io_write_bytes"] = disk_io.write_bytes
disk_io = psutil.disk_io_counters()
info["disk_io_read_count"] = disk_io.read_count
info["disk_io_write_count"] = disk_io.write_count
info["disk_io_read_bytes"] = disk_io.read_bytes
info["disk_io_write_bytes"] = disk_io.write_bytes
disks = []
partitions = psutil.disk_partitions()
for partition in partitions:
p = {}
p["device"] = partition.device
p["mountpoint"] = partition.mountpoint
p["filesystem_type"] = partition.fstype
disks = []
partitions = psutil.disk_partitions()
for partition in partitions:
p = {}
p["device"] = partition.device
p["mountpoint"] = partition.mountpoint
p["filesystem_type"] = partition.fstype
try:
usage = psutil.disk_usage(partition.mountpoint)
p["partition_total_bytes"] = usage.total
p["partition_used_bytes"] = usage.used
p["partition_free_bytes"] = usage.free
p["partition_percent"] = usage.percent
except PermissionError:
continue
disks.append(p)
info["disks"] = disks
try:
usage = psutil.disk_usage(partition.mountpoint)
p["partition_total_bytes"] = usage.total
p["partition_used_bytes"] = usage.used
p["partition_free_bytes"] = usage.free
p["partition_percent"] = usage.percent
except PermissionError:
continue
disks.append(p)
info["disks"] = disks
nets = []
addresses = psutil.net_if_addrs()
for name, address in addresses.items():
net = {}
nets.append(net)
net["interface_name"] = name
for a in address:
if str(a.family) == "AddressFamily.AF_INET":
net["address"] = a.address
elif str(a.family) == "AddressFamily.AF_PACKET":
net["mac_address"] = a.address
nets = []
addresses = psutil.net_if_addrs()
for name, address in addresses.items():
net = {}
nets.append(net)
net["interface_name"] = name
for a in address:
if str(a.family) == "AddressFamily.AF_INET":
net["address"] = a.address
elif str(a.family) == "AddressFamily.AF_PACKET":
net["mac_address"] = a.address
net_io = psutil.net_io_counters()
info["networks"] = nets
info["networks_bytes_sent"] = net_io.bytes_sent
info["networks_bytes_received"] = net_io.bytes_recv
net_io = psutil.net_io_counters()
info["networks"] = nets
info["networks_bytes_sent"] = net_io.bytes_sent
info["networks_bytes_received"] = net_io.bytes_recv
except FileNotFoundError:
logger.warning("Unable to access /proc/stat to get CPU stats")
except OSError as e:
logger.warning(
f"""Unable to query system: {e}
Check if the system is hardened against such calls.
For example in Nix you must not harden the following:
ProtectProc = "invisible"; // to get HW info
ProcSubset = "pid"; // to get HW info
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; // to get network info
"""
)
return info