Add support for a custom timezone using the TZ env variable with error handling in case of an unknown timezone provided

This commit is contained in:
Théophile Diot 2024-08-20 10:53:12 +01:00
parent b1cf93af14
commit b2976de125
No known key found for this signature in database
GPG key ID: FA995104A0BA376A
35 changed files with 244 additions and 154 deletions

View file

@ -1,4 +1,4 @@
mike==2.1.3
mkdocs-material[imaging]==9.5.31
mkdocs-material[imaging]==9.5.32
mkdocs-print-site-plugin==2.5.0
pytablewriter==1.2.0

View file

@ -215,9 +215,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
importlib-metadata==8.2.0 \
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
importlib-metadata==8.3.0 \
--hash=sha256:42817a4a0be5845d22c6e212db66a94ad261e2318d80b3e0d363894a79df2b67 \
--hash=sha256:9c8fa6e8ea0f9516ad5c8db9246a731c948193c7754d3babb0114a05b27dd364
# via
# markdown
# mike
@ -332,9 +332,9 @@ mkdocs-get-deps==0.2.0 \
--hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \
--hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134
# via mkdocs
mkdocs-material==9.5.31 \
--hash=sha256:1b1f49066fdb3824c1e96d6bacd2d4375de4ac74580b47e79ff44c4d835c5fcb \
--hash=sha256:31833ec664772669f5856f4f276bf3fdf0e642a445e64491eda459249c3a1ca8
mkdocs-material==9.5.32 \
--hash=sha256:38ed66e6d6768dde4edde022554553e48b2db0d26d1320b19e2e2b9da0be1120 \
--hash=sha256:f3704f46b63d31b3cd35c0055a72280bed825786eccaf19c655b44e0cd2c6b3f
# via
# -r requirements.in
# mkdocs-print-site-plugin
@ -636,9 +636,9 @@ requests==2.32.3 \
# importlib-resources
# The following packages are considered to be unsafe in a requirements file:
setuptools==72.2.0 \
--hash=sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9 \
--hash=sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4
setuptools==73.0.0 \
--hash=sha256:3c08705fadfc8c7c445cf4d98078f0fafb9225775b2b4e8447e40348f82597c0 \
--hash=sha256:f2bfcce7ae1784d90b04c57c2802e8649e1976530bb25dc72c2b078d3ecf4864
# via mkdocs-material
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \

View file

@ -1,11 +1,12 @@
#!/usr/bin/env python3
from contextlib import suppress
from datetime import datetime, timezone
from datetime import datetime
from os import getenv
from time import sleep
from typing import Any, Dict, List, Optional
from common_utils import get_timezone # type: ignore
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
@ -82,9 +83,9 @@ class Config:
)
def wait_applying(self, startup: bool = False):
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
ready = False
while not ready and (datetime.now(timezone.utc) - current_time).seconds < 240:
while not ready and (datetime.now(get_timezone()) - current_time).seconds < 240:
db_metadata = self._db.get_metadata()
if isinstance(db_metadata, str):
if not startup:

View file

@ -1,2 +1,3 @@
docker==7.1.0
kubernetes==30.1.0
pytz==2024.1

View file

@ -142,6 +142,10 @@ python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
# via kubernetes
pytz==2024.1 \
--hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \
--hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319
# via -r requirements.in
pyyaml==6.0.2 \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
from datetime import datetime, timezone
from datetime import datetime
from os.path import join, sep
from pathlib import Path
from sys import exit as sys_exit, path as sys_path
@ -13,6 +13,8 @@ if deps_path not in sys_path:
from utils import acquire_db_lock, backup_database, BACKUP_DIR, DB_LOCK_FILE, LOGGER, restore_database
from common_utils import get_timezone # type: ignore
status = 0
try:
@ -49,7 +51,7 @@ try:
sys_exit(1)
LOGGER.info("Backing up the current database before restoring the backup ...")
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
tmp_backup_dir = Path(sep, "tmp", "bunkerweb", "backups")
tmp_backup_dir.mkdir(parents=True, exist_ok=True)
db = backup_database(current_time, backup_dir=tmp_backup_dir)

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
from datetime import datetime, timezone
from datetime import datetime
from os.path import join, sep
from pathlib import Path
from sys import exit as sys_exit, path as sys_path
@ -13,6 +13,8 @@ if deps_path not in sys_path:
from utils import acquire_db_lock, backup_database, BACKUP_DIR, DB_LOCK_FILE, LOGGER
from common_utils import get_timezone # type: ignore
status = 0
try:
@ -39,7 +41,7 @@ try:
LOGGER.info(f"Creating directory {directory} as it does not exist")
directory.mkdir(parents=True, exist_ok=True)
backup_database(datetime.now(timezone.utc), backup_dir=directory)
backup_database(datetime.now(get_timezone()), backup_dir=directory)
except SystemExit as se:
status = se.code
except:

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from json import dumps, loads
from os import getenv, sep
from os.path import join
@ -16,6 +16,8 @@ from logger import setup_logger # type: ignore
from jobs import Job # type: ignore
from utils import backup_database
from common_utils import get_timezone # type: ignore
LOGGER = setup_logger("BACKUP", getenv("LOG_LEVEL", "INFO"))
status = 0
@ -35,7 +37,7 @@ try:
if last_backup_date:
last_backup_date = datetime.fromisoformat(last_backup_date)
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
backup_period = getenv("BACKUP_SCHEDULE", "daily")
PERIOD_STAMPS = {
"daily": timedelta(days=1).total_seconds(),

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime, timezone
from datetime import datetime
from json import dumps, loads
from os import environ, getenv
from os.path import join, sep
@ -16,7 +16,7 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import bytes_hash # type: ignore
from common_utils import bytes_hash, get_timezone # type: ignore
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
from model import Base # type: ignore
@ -30,7 +30,7 @@ DB_LOCK_FILE = Path(sep, "var", "lib", "bunkerweb", "db.lock")
def acquire_db_lock():
"""Acquire the database lock to prevent concurrent access to the database."""
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
while DB_LOCK_FILE.is_file() and DB_LOCK_FILE.stat().st_ctime + 30 > current_time.timestamp():
LOGGER.warning("Database is locked, waiting for it to be unlocked (timeout: 30s) ...")
sleep(1)
@ -46,9 +46,9 @@ def backup_database(current_time: datetime, db: Database = None, backup_dir: Pat
backup_file = backup_dir.joinpath(f"backup-{database}-{current_time.strftime('%Y-%m-%d_%H-%M-%S')}.zip")
LOGGER.debug(f"Backup file path: {backup_file}")
stderr = "Table 'db.test_"
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
while "Table 'db.test_" in stderr and (datetime.now(timezone.utc) - current_time).total_seconds() < 10:
while "Table 'db.test_" in stderr and (datetime.now(get_timezone()) - current_time).total_seconds() < 10:
if database == "sqlite":
match = DB_STRING_RX.search(db.database_uri)
if not match:
@ -94,7 +94,7 @@ def backup_database(current_time: datetime, db: Database = None, backup_dir: Pat
LOGGER.error(f"Failed to dump the database: {stderr}")
sys_exit(1)
if (datetime.now(timezone.utc) - current_time).total_seconds() >= 10:
if (datetime.now(get_timezone()) - current_time).total_seconds() >= 10:
LOGGER.error("Failed to dump the database: Timeout reached")
sys_exit(1)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from datetime import date, datetime, timedelta, timezone
from datetime import date, datetime, timedelta
from gzip import decompress
from io import BytesIO
from os import getenv, sep
@ -18,7 +18,7 @@ from maxminddb import open_database
from requests import RequestException, Response, get
from logger import setup_logger # type: ignore
from common_utils import bytes_hash, file_hash # type: ignore
from common_utils import bytes_hash, get_timezone, file_hash # type: ignore
from jobs import Job # type: ignore
LOGGER = setup_logger("JOBS.mmdb-asn", getenv("LOG_LEVEL", "INFO"))
@ -62,7 +62,7 @@ try:
if response and response.status_code == 200:
skip_dl = response.content.find(bytes_hash(job_cache["data"], algorithm="sha1").encode()) != -1
elif job_cache["last_update"] < (datetime.now(timezone.utc) - timedelta(weeks=1)).timestamp():
elif job_cache["last_update"] < (datetime.now(get_timezone()) - timedelta(weeks=1)).timestamp():
LOGGER.warning("Unable to check if the cache file is the latest version from db-ip.com and file is older than 1 week, checking anyway...")
skip_dl = False

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from datetime import date, datetime, timedelta, timezone
from datetime import date, datetime, timedelta
from gzip import decompress
from io import BytesIO
from os import getenv, sep
@ -18,7 +18,7 @@ from maxminddb import open_database
from requests import RequestException, Response, get
from logger import setup_logger # type: ignore
from common_utils import bytes_hash, file_hash # type: ignore
from common_utils import bytes_hash, get_timezone, file_hash # type: ignore
from jobs import Job # type: ignore
LOGGER = setup_logger("JOBS.mmdb-country", getenv("LOG_LEVEL", "INFO"))
@ -62,7 +62,7 @@ try:
if response and response.status_code == 200:
skip_dl = response.content.find(bytes_hash(job_cache["data"], algorithm="sha1").encode()) != -1
elif job_cache["last_update"] < (datetime.now(timezone.utc) - timedelta(weeks=1)).timestamp():
elif job_cache["last_update"] < (datetime.now(get_timezone()) - timedelta(weeks=1)).timestamp():
LOGGER.warning("Unable to check if the cache file is the latest version from db-ip.com and file is older than 1 week, checking anyway...")
skip_dl = False

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime, timezone
from datetime import datetime
from io import BytesIO
from itertools import chain
from os import getenv, sep
@ -23,7 +23,7 @@ from requests import get
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
from common_utils import bytes_hash, get_os_info, get_integration, get_version # type: ignore
from common_utils import bytes_hash, get_os_info, get_integration, get_timezone, get_version # type: ignore
API_ENDPOINT = "https://api.bunkerweb.io"
PREVIEW_ENDPOINT = "https://assets.bunkerity.com/bw-pro/preview"
@ -95,7 +95,7 @@ def install_plugin(plugin_path: Path, db, preview: bool = True) -> bool:
try:
db = Database(LOGGER, sqlalchemy_string=getenv("DATABASE_URI"))
db_metadata = db.get_metadata()
current_date = datetime.now(timezone.utc)
current_date = datetime.now(get_timezone())
pro_license_key = getenv("PRO_LICENSE_KEY", "").strip()
LOGGER.info("Checking BunkerWeb Pro status...")

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from os import getenv, sep
from os.path import join
from pathlib import Path
@ -56,7 +56,7 @@ def generate_cert(first_server: str, days: str, subj: str, self_signed_path: Pat
LOGGER.warning(
f"Expiration date of self-signed certificate for {first_server} is different from the one in the configuration, regenerating ..."
)
elif not_valid_after < datetime.now(tz=timezone.utc):
elif not_valid_after < datetime.now(tz=not_valid_after.tzinfo):
LOGGER.warning(f"Self-signed certificate for {first_server} has expired, regenerating ...")
else:
LOGGER.info(f"Self-signed certificate for {first_server} is valid")

View file

@ -2,7 +2,7 @@
from contextlib import contextmanager, suppress
from copy import deepcopy
from datetime import datetime, timezone
from datetime import datetime
from io import BytesIO
from json import JSONDecodeError, loads
from logging import Logger
@ -43,7 +43,7 @@ for deps_path in [os_join(sep, "usr", "share", "bunkerweb", *paths) for paths in
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import bytes_hash # type: ignore
from common_utils import bytes_hash, get_timezone # type: ignore
from pymysql import install_as_MySQLdb
from sqlalchemy import create_engine, event, MetaData as sql_metadata, func, join, select as db_select, text, inspect
@ -168,7 +168,7 @@ class Database:
DATABASE_RETRY_TIMEOUT = int(DATABASE_RETRY_TIMEOUT)
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
not_connected = True
fallback = False
@ -185,7 +185,7 @@ class Database:
not_connected = False
except (OperationalError, DatabaseError) as e:
if (datetime.now(timezone.utc) - current_time).total_seconds() > DATABASE_RETRY_TIMEOUT:
if (datetime.now(get_timezone()) - current_time).total_seconds() > DATABASE_RETRY_TIMEOUT:
if not fallback and self.database_uri_readonly:
self.logger.error(f"Can't connect to database after {DATABASE_RETRY_TIMEOUT} seconds. Falling back to read-only database connection")
self.sql_engine.dispose(close=True)
@ -241,7 +241,7 @@ class Database:
def retry_connection(self, *, readonly: bool = False, fallback: bool = False, log: bool = True, **kwargs) -> None:
"""Retry the connection to the database"""
self.last_connection_retry = datetime.now(timezone.utc)
self.last_connection_retry = datetime.now(get_timezone())
if log:
self.logger.debug(f"Retrying the connection to the database{' in read-only mode' if readonly else ''}{' with fallback' if fallback else ''} ...")
@ -476,7 +476,7 @@ class Database:
if not metadata:
return "The metadata are not set yet, try again"
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
if "config" in changes:
if not metadata.first_config_saved:
@ -536,7 +536,7 @@ class Database:
db_ui_version = db_version
self.logger.warning(f"Database version ({db_version}) is different from Bunkerweb version ({bunkerweb_version}), migrating ...")
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
error = True
# ? Wait for the metadata to be available
while error:
@ -545,7 +545,7 @@ class Database:
metadata.reflect(self.sql_engine)
error = False
except BaseException as e:
if (datetime.now(timezone.utc) - current_time).total_seconds() > 10:
if (datetime.now(get_timezone()) - current_time).total_seconds() > 10:
raise e
sleep(1)
@ -1328,7 +1328,7 @@ class Database:
session.query(Custom_configs).filter(Custom_configs.service_id.in_(missing_ids)).delete()
session.query(Jobs_cache).filter(Jobs_cache.service_id.in_(missing_ids)).delete()
session.query(Metadata).filter_by(id=1).update(
{Metadata.custom_configs_changed: True, Metadata.last_custom_configs_change: datetime.now(timezone.utc)}
{Metadata.custom_configs_changed: True, Metadata.last_custom_configs_change: datetime.now(get_timezone())}
)
changed_services = True
@ -1672,7 +1672,7 @@ class Database:
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.custom_configs_changed = True
metadata.last_custom_configs_change = datetime.now(timezone.utc)
metadata.last_custom_configs_change = datetime.now(get_timezone())
try:
session.add_all(to_put)
@ -1994,7 +1994,7 @@ class Database:
if self.readonly:
return "The database is read-only, the changes will not be saved"
session.add(Jobs_runs(job_name=job_name, success=success, start_date=start_date, end_date=end_date or datetime.now(timezone.utc)))
session.add(Jobs_runs(job_name=job_name, success=success, start_date=start_date, end_date=end_date or datetime.now(get_timezone())))
try:
session.commit()
@ -2062,13 +2062,13 @@ class Database:
service_id=service_id,
file_name=file_name,
data=data,
last_update=datetime.now(timezone.utc),
last_update=datetime.now(get_timezone()),
checksum=checksum,
)
)
else:
cache.data = data
cache.last_update = datetime.now(timezone.utc)
cache.last_update = datetime.now(get_timezone())
cache.checksum = checksum
try:
@ -2858,10 +2858,10 @@ class Database:
if metadata is not None:
if _type in ("external", "ui"):
metadata.external_plugins_changed = True
metadata.last_external_plugins_change = datetime.now(timezone.utc)
metadata.last_external_plugins_change = datetime.now(get_timezone())
elif _type == "pro":
metadata.pro_plugins_changed = True
metadata.last_pro_plugins_change = datetime.now(timezone.utc)
metadata.last_pro_plugins_change = datetime.now(get_timezone())
try:
session.add_all(to_put)
@ -2902,10 +2902,10 @@ class Database:
if metadata is not None:
if method in ("external", "ui"):
metadata.external_plugins_changed = True
metadata.last_external_plugins_change = datetime.now(timezone.utc)
metadata.last_external_plugins_change = datetime.now(get_timezone())
elif method == "pro":
metadata.pro_plugins_changed = True
metadata.last_pro_plugins_change = datetime.now(timezone.utc)
metadata.last_pro_plugins_change = datetime.now(get_timezone())
try:
session.commit()
@ -3127,14 +3127,25 @@ class Database:
if db_instance is not None:
return f"Instance {hostname} already exists, will not be added."
session.add(Instances(hostname=hostname, name=name or "static instance", port=port, server_name=server_name, method=method))
current_time = datetime.now(get_timezone())
session.add(
Instances(
hostname=hostname,
name=name or "static instance",
port=port,
server_name=server_name,
method=method,
creation_date=current_time,
last_seen=current_time,
)
)
if changed:
with suppress(ProgrammingError, OperationalError):
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.instances_changed = True
metadata.last_instances_change = datetime.now(timezone.utc)
metadata.last_instances_change = datetime.now(get_timezone())
try:
session.commit()
@ -3161,7 +3172,7 @@ class Database:
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.instances_changed = True
metadata.last_instances_change = datetime.now(timezone.utc)
metadata.last_instances_change = datetime.now(get_timezone())
try:
session.commit()
@ -3183,6 +3194,7 @@ class Database:
if instance.get("hostname") is None:
continue
current_time = datetime.now(get_timezone())
to_put.append(
Instances(
hostname=instance["hostname"],
@ -3192,6 +3204,8 @@ class Database:
type=instance.get("type", "static"),
status="up" if instance.get("health", True) else "down",
method=method,
creation_date=current_time,
last_seen=current_time,
)
)
@ -3200,7 +3214,7 @@ class Database:
metadata = session.query(Metadata).get(1)
if metadata is not None:
metadata.instances_changed = True
metadata.last_instances_change = datetime.now(timezone.utc)
metadata.last_instances_change = datetime.now(get_timezone())
try:
session.add_all(to_put)
@ -3222,7 +3236,7 @@ class Database:
return f"Instance {hostname} does not exist, will not be updated."
db_instance.status = status
db_instance.last_seen = datetime.now(timezone.utc)
db_instance.last_seen = datetime.now(get_timezone())
try:
session.commit()

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from sqlalchemy import TEXT, Boolean, Column, DateTime, Enum, ForeignKey, Identity, Integer, LargeBinary, String, func
from sqlalchemy import TEXT, Boolean, Column, DateTime, Enum, ForeignKey, Identity, Integer, LargeBinary, String
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.schema import UniqueConstraint
@ -175,7 +175,7 @@ class Jobs_runs(Base):
job_name = Column(String(128), ForeignKey("bw_jobs.name", onupdate="cascade", ondelete="cascade"), nullable=False)
success = Column(Boolean, nullable=True, default=False)
start_date = Column(DateTime(timezone=True), nullable=False)
end_date = Column(DateTime(timezone=True), nullable=True, server_default=func.now())
end_date = Column(DateTime(timezone=True), nullable=False)
job = relationship("Jobs", back_populates="runs")
@ -205,8 +205,8 @@ class Instances(Base):
type = Column(INSTANCE_TYPE_ENUM, nullable=False, default="static")
status = Column(INSTANCE_STATUS_ENUM, nullable=False, default="loading")
method = Column(METHODS_ENUM, nullable=False, default="manual")
creation_date = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
last_seen = Column(DateTime(timezone=True), nullable=True, server_default=func.now())
creation_date = Column(DateTime(timezone=True), nullable=False)
last_seen = Column(DateTime(timezone=True), nullable=False)
class Bw_cli_commands(Base):

View file

@ -1,4 +1,5 @@
jinja2==3.1.4
python-dotenv==1.0.1
pytz==2024.1
redis==5.0.8
requests==2.32.3

View file

@ -178,6 +178,10 @@ python-dotenv==1.0.1 \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
# via -r requirements.in
pytz==2024.1 \
--hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \
--hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319
# via -r requirements.in
redis==5.0.8 \
--hash=sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870 \
--hash=sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4

View file

@ -1,10 +1,13 @@
from hashlib import new as new_hash
from io import BytesIO
from os import getenv, sep
from logging import error
from os import environ, getenv, sep
from pathlib import Path
from platform import machine
from typing import Dict, Union
from pytz import UnknownTimeZoneError, timezone
def dict_to_frozenset(d):
if isinstance(d, list):
@ -88,3 +91,12 @@ def bytes_hash(bio: Union[str, bytes, BytesIO], *, algorithm: str = "sha512") ->
_hash.update(data)
bio.seek(0, 0)
return _hash.hexdigest()
def get_timezone():
try:
return timezone(getenv("TZ", "UTC"))
except UnknownTimeZoneError as e:
environ["TZ"] = "UTC"
error(f"Invalid timezone: {e}, using UTC instead")
return timezone("UTC")

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from io import BytesIO
from logging import Logger
from os import getenv
@ -14,7 +14,7 @@ from threading import Lock
from traceback import format_exc
from typing import Any, Dict, Literal, Optional, Tuple, Union
from common_utils import bytes_hash, file_hash
from common_utils import bytes_hash, file_hash, get_timezone
LOCK = Lock()
EXPIRE_TIME = {
@ -144,7 +144,7 @@ class Job:
try:
cache_info = self.get_cache(name, job_name=job_name, service_id=service_id, plugin_id=plugin_id, with_info=True, with_data=False)
if isinstance(cache_info, dict):
current_time = datetime.now(timezone.utc).timestamp()
current_time = datetime.now(get_timezone()).timestamp()
if current_time < cache_info["last_update"]:
return False
is_cached = current_time - cache_info["last_update"] < EXPIRE_TIME[expire]

View file

@ -1,45 +1,81 @@
from datetime import datetime, timezone
from logging import (
CRITICAL,
DEBUG,
ERROR,
INFO,
WARNING,
Formatter,
Logger,
_nameToLevel,
addLevelName,
basicConfig,
getLogger,
setLoggerClass,
StreamHandler,
)
from os import getenv
from typing import Optional, Union
from common_utils import get_timezone
class BwFormatter(Formatter):
"""Overrides logging.Formatter to use an aware datetime object."""
def converter(self, timestamp):
"""Convert timestamp to an aware datetime object in the desired timezone."""
return datetime.fromtimestamp(timestamp, tz=timezone.utc).astimezone(get_timezone())
def formatTime(self, record, datefmt=None):
"""Format the datetime according to the specified format or ISO 8601."""
dt = self.converter(record.created)
if datefmt:
return dt.strftime(datefmt)
try:
return dt.isoformat(timespec="milliseconds")
except TypeError:
return dt.isoformat()
class BWLogger(Logger):
"""Custom logger class inheriting from the standard Logger."""
def __init__(self, name, level=INFO):
self.name = name
super(BWLogger, self).__init__(name, level)
super().__init__(name, level)
# Set the custom logger class as the default
setLoggerClass(BWLogger)
# Set the default logging level based on environment variables
default_level = _nameToLevel.get(getenv("CUSTOM_LOG_LEVEL", getenv("LOG_LEVEL", "INFO")).upper(), INFO)
basicConfig(
format="%(asctime)s [%(name)s] [%(process)d] [%(levelname)s] - %(message)s",
datefmt="[%Y-%m-%d %H:%M:%S %z]",
level=default_level,
)
# Create a custom formatter instance
formatter = BwFormatter(fmt="%(asctime)s [%(name)s] [%(process)d] [%(levelname)s] - %(message)s", datefmt="[%Y-%m-%d %H:%M:%S %z]")
# Create a console handler and set the custom formatter
handler = StreamHandler()
handler.setFormatter(formatter)
# Get the root logger and add the handler to it
root_logger = getLogger()
root_logger.setLevel(default_level)
root_logger.addHandler(handler)
# Set the default logging level for specific SQLAlchemy components
database_default_level = _nameToLevel.get(getenv("DATABASE_LOG_LEVEL", "WARNING").upper(), WARNING)
sqlalchemy_loggers = (
"sqlalchemy.orm.mapper.Mapper",
"sqlalchemy.orm.relationships.RelationshipProperty",
"sqlalchemy.orm.strategies.LazyLoader",
"sqlalchemy.pool.impl.QueuePool",
"sqlalchemy.pool.impl.SingletonThreadPool",
"sqlalchemy.engine.Engine",
)
for logger_name in sqlalchemy_loggers:
getLogger(logger_name).setLevel(database_default_level)
getLogger("sqlalchemy.orm.mapper.Mapper").setLevel(database_default_level)
getLogger("sqlalchemy.orm.relationships.RelationshipProperty").setLevel(database_default_level)
getLogger("sqlalchemy.orm.strategies.LazyLoader").setLevel(database_default_level)
getLogger("sqlalchemy.pool.impl.QueuePool").setLevel(database_default_level)
getLogger("sqlalchemy.pool.impl.SingletonThreadPool").setLevel(database_default_level)
getLogger("sqlalchemy.engine.Engine").setLevel(database_default_level)
# Edit the default levels of the logging module
# Customize log level names with emojis
addLevelName(CRITICAL, "🚨")
addLevelName(DEBUG, "🐛")
addLevelName(ERROR, "")
@ -48,14 +84,13 @@ addLevelName(WARNING, "⚠️ ")
def setup_logger(title: str, level: Optional[Union[str, int]] = None) -> Logger:
"""Set up local logger"""
"""Set up and return a logger with the specified title and level."""
title = title.upper()
logger = getLogger(title)
level = level or default_level
if isinstance(level, str):
logger.setLevel(_nameToLevel.get(level.upper(), default_level))
else:
logger.setLevel(level)
level = _nameToLevel.get(level.upper(), default_level)
logger.setLevel(level)
return logger

View file

@ -2,6 +2,6 @@ pip==24.2
pip-compile-multi==2.6.4
pip-tools==7.4.1
pip-upgrader==1.4.15
setuptools==72.2.0
setuptools==73.0.0
tomli==2.0.1
wheel==0.44.0

View file

@ -121,9 +121,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
importlib-metadata==8.2.0 \
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
importlib-metadata==8.3.0 \
--hash=sha256:42817a4a0be5845d22c6e212db66a94ad261e2318d80b3e0d363894a79df2b67 \
--hash=sha256:9c8fa6e8ea0f9516ad5c8db9246a731c948193c7754d3babb0114a05b27dd364
# via build
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
@ -163,9 +163,9 @@ requests==2.32.3 \
# via
# -r requirements-deps.in
# pip-tools
setuptools==72.2.0 \
--hash=sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9 \
--hash=sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4
setuptools==73.0.0 \
--hash=sha256:3c08705fadfc8c7c445cf4d98078f0fafb9225775b2b4e8447e40348f82597c0 \
--hash=sha256:f2bfcce7ae1784d90b04c57c2802e8649e1976530bb25dc72c2b078d3ecf4864
# via pip-upgrader
terminaltables==3.1.10 \
--hash=sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543 \

View file

@ -1,4 +1,4 @@
pip==24.2
pip-tools==7.4.1
setuptools==72.2.0
setuptools==73.0.0
wheel==0.44.0

View file

@ -12,9 +12,9 @@ click==8.1.7 \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
# via pip-tools
importlib-metadata==8.2.0 \
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
importlib-metadata==8.3.0 \
--hash=sha256:42817a4a0be5845d22c6e212db66a94ad261e2318d80b3e0d363894a79df2b67 \
--hash=sha256:9c8fa6e8ea0f9516ad5c8db9246a731c948193c7754d3babb0114a05b27dd364
# via build
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
@ -36,9 +36,9 @@ pyproject-hooks==1.1.0 \
# via
# -r requirements.in
# pip-tools
setuptools==72.2.0 \
--hash=sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9 \
--hash=sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4
setuptools==73.0.0 \
--hash=sha256:3c08705fadfc8c7c445cf4d98078f0fafb9225775b2b4e8447e40348f82597c0 \
--hash=sha256:f2bfcce7ae1784d90b04c57c2802e8649e1976530bb25dc72c2b078d3ecf4864
# via
# build
# pip-tools

View file

@ -2,7 +2,7 @@
from contextlib import suppress
from copy import deepcopy
from datetime import datetime, timezone
from datetime import datetime
from functools import partial
from glob import glob
from json import loads
@ -27,6 +27,7 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import get_timezone # type: ignore
from Database import Database # type: ignore
from logger import setup_logger # type: ignore
from ApiCaller import ApiCaller # type: ignore
@ -162,7 +163,7 @@ class JobScheduler(ApiCaller):
self.__logger.info(f"Executing job {name} from plugin {plugin} ...")
success = True
ret = -1
start_date = datetime.now(timezone.utc)
start_date = datetime.now(get_timezone())
try:
proc = run(join(path, "jobs", file), stdin=DEVNULL, stderr=STDOUT, env=self.__env, check=False)
ret = proc.returncode
@ -171,7 +172,7 @@ class JobScheduler(ApiCaller):
self.__logger.error(f"Exception while executing job {name} from plugin {plugin} :\n{format_exc()}")
with self.__thread_lock:
self.__job_success = False
end_date = datetime.now(timezone.utc)
end_date = datetime.now(get_timezone())
if ret == 1:
with self.__thread_lock:
@ -351,7 +352,7 @@ class JobScheduler(ApiCaller):
except BaseException:
self.db.readonly = True
return True
elif not force and self.db.last_connection_retry and (datetime.now(timezone.utc) - self.db.last_connection_retry).total_seconds() > 30:
elif not force and self.db.last_connection_retry and (datetime.now(get_timezone()) - self.db.last_connection_retry).total_seconds() > 30:
return True
if self.db.database_uri and self.db.readonly:

View file

@ -2,7 +2,7 @@
from argparse import ArgumentParser
from contextlib import suppress
from datetime import datetime, timezone
from datetime import datetime
from io import BytesIO
from itertools import chain
from json import load as json_load
@ -27,7 +27,7 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
from dotenv import dotenv_values
from schedule import every as schedule_every, run_pending
from common_utils import bytes_hash, dict_to_frozenset, get_integration # type: ignore
from common_utils import bytes_hash, dict_to_frozenset, get_integration, get_timezone # type: ignore
from logger import setup_logger # type: ignore
from Database import Database # type: ignore
from JobScheduler import JobScheduler
@ -97,8 +97,8 @@ MASTER_MODE = getenv("MASTER_MODE", "no") == "yes"
def handle_stop(signum, frame):
current_time = datetime.now(timezone.utc)
while APPLYING_CHANGES.is_set() and (datetime.now(timezone.utc) - current_time).seconds < 30:
current_time = datetime.now(get_timezone())
while APPLYING_CHANGES.is_set() and (datetime.now(get_timezone()) - current_time).seconds < 30:
LOGGER.warning("Waiting for the changes to be applied before stopping ...")
sleep(1)
@ -374,7 +374,7 @@ def run_in_slave_mode(): # TODO: Refactor this feature
sleep(5)
# Instantiate scheduler environment
SCHEDULER.env = env
SCHEDULER.env = env | {"TZ": getenv("TZ", "UTC"), "LOG_LEVEL": getenv("CUSTOM_LOG_LEVEL", env.get("LOG_LEVEL", "notice"))}
threads = [
Thread(target=generate_custom_configs),
@ -563,7 +563,7 @@ if __name__ == "__main__":
env["DATABASE_URI"] = SCHEDULER.db.database_uri
# Instantiate scheduler environment
SCHEDULER.env = env
SCHEDULER.env = env | {"TZ": getenv("TZ", "UTC"), "LOG_LEVEL": getenv("CUSTOM_LOG_LEVEL", env.get("LOG_LEVEL", "notice"))}
threads = []
@ -686,7 +686,7 @@ if __name__ == "__main__":
LOGGER.info("Running plugins download jobs ...")
# Update the environment variables of the scheduler
SCHEDULER.env = env
SCHEDULER.env = env | {"TZ": getenv("TZ", "UTC"), "LOG_LEVEL": getenv("CUSTOM_LOG_LEVEL", env.get("LOG_LEVEL", "notice"))}
if not SCHEDULER.run_single("download-plugins"):
LOGGER.warning("download-plugins job failed at first start, plugins settings set by the user may not be up to date ...")
if not SCHEDULER.run_single("download-pro-plugins"):
@ -746,7 +746,9 @@ if __name__ == "__main__":
if RUN_JOBS_ONCE:
# Only run jobs once
if not SCHEDULER.reload(env | {"LOG_LEVEL": getenv("CUSTOM_LOG_LEVEL", env.get("LOG_LEVEL", "notice"))}, changed_plugins=changed_plugins):
if not SCHEDULER.reload(
env | {"TZ": getenv("TZ", "UTC"), "LOG_LEVEL": getenv("CUSTOM_LOG_LEVEL", env.get("LOG_LEVEL", "notice"))}, changed_plugins=changed_plugins
):
LOGGER.error("At least one job in run_once() failed")
else:
LOGGER.info("All jobs in run_once() were successful")
@ -898,7 +900,7 @@ if __name__ == "__main__":
scheduler_first_start = False
if not HEALTHY_PATH.is_file():
HEALTHY_PATH.write_text(datetime.now(timezone.utc).isoformat(), encoding="utf-8")
HEALTHY_PATH.write_text(datetime.now(get_timezone()).isoformat(), encoding="utf-8")
APPLYING_CHANGES.clear()
schedule_every(HEALTHCHECK_INTERVAL).seconds.do(healthcheck_job)
@ -911,7 +913,7 @@ if __name__ == "__main__":
sleep(3 if SCHEDULER.db.readonly else 1)
run_pending()
SCHEDULER.run_pending()
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
while DB_LOCK_FILE.is_file() and DB_LOCK_FILE.stat().st_ctime + 30 > current_time.timestamp():
LOGGER.debug("Database is locked, waiting for it to be unlocked (timeout: 30s) ...")

View file

@ -227,9 +227,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
importlib-metadata==8.2.0 \
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
importlib-metadata==8.3.0 \
--hash=sha256:42817a4a0be5845d22c6e212db66a94ad261e2318d80b3e0d363894a79df2b67 \
--hash=sha256:9c8fa6e8ea0f9516ad5c8db9246a731c948193c7754d3babb0114a05b27dd364
# via certbot
josepy==1.14.0 \
--hash=sha256:308b3bf9ce825ad4d4bba76372cf19b5dc1c2ce96a9d298f9642975e64bd13dd \
@ -351,9 +351,9 @@ schedule==1.2.2 \
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
setuptools==72.2.0 \
--hash=sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9 \
--hash=sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4
setuptools==73.0.0 \
--hash=sha256:3c08705fadfc8c7c445cf4d98078f0fafb9225775b2b4e8447e40348f82597c0 \
--hash=sha256:f2bfcce7ae1784d90b04c57c2802e8649e1976530bb25dc72c2b078d3ecf4864
# via -r requirements.in
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \

View file

@ -9,7 +9,7 @@ for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in ((
if deps_path not in sys_path:
sys_path.append(deps_path)
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from flask import Flask, Response, flash, jsonify, make_response, redirect, render_template, request, session, url_for
from flask_login import current_user, LoginManager, login_required, logout_user
from flask_principal import ActionNeed, identity_loaded, Permission, Principal, RoleNeed, TypeNeed, UserNeed
@ -18,6 +18,8 @@ from json import dumps
from signal import SIGINT, signal, SIGTERM
from time import time
from common_utils import get_timezone # type: ignore
from src.reverse_proxied import ReverseProxied
from pages.bans import bans
@ -244,7 +246,7 @@ def before_request():
DB.database_uri
and DB.readonly
and (
datetime.now(timezone.utc) - datetime.fromisoformat(DATA.get("LAST_DATABASE_RETRY", "1970-01-01T00:00:00")).replace(tzinfo=timezone.utc)
datetime.now(get_timezone()) - datetime.fromisoformat(DATA.get("LAST_DATABASE_RETRY", "1970-01-01T00:00:00")).replace(tzinfo=get_timezone())
> timedelta(minutes=1)
)
):
@ -263,19 +265,19 @@ def before_request():
DB.retry_connection(fallback=True, pool_timeout=1)
DB.retry_connection(fallback=True, log=False)
DATA["READONLY_MODE"] = True
DATA["LAST_DATABASE_RETRY"] = DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now(timezone.utc).isoformat()
DATA["LAST_DATABASE_RETRY"] = DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now(get_timezone()).isoformat()
elif not DATA.get("READONLY_MODE", False) and request.method == "POST" and not ("/totp" in request.path or "/login" in request.path):
try:
DB.test_write()
DATA["READONLY_MODE"] = False
except BaseException:
DATA["READONLY_MODE"] = True
DATA["LAST_DATABASE_RETRY"] = DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now(timezone.utc).isoformat()
DATA["LAST_DATABASE_RETRY"] = DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now(get_timezone()).isoformat()
else:
try:
DB.test_read()
except BaseException:
DATA["LAST_DATABASE_RETRY"] = DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now(timezone.utc).isoformat()
DATA["LAST_DATABASE_RETRY"] = DB.last_connection_retry.isoformat() if DB.last_connection_retry else datetime.now(get_timezone()).isoformat()
DB.readonly = DATA.get("READONLY_MODE", False)

View file

@ -1,16 +1,17 @@
from datetime import datetime, timezone
from functools import partial
from datetime import datetime
from os.path import join, sep
from sys import path as sys_path
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("db",))]:
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("db",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import get_timezone # type: ignore
from bcrypt import checkpw
from flask_login import AnonymousUserMixin, UserMixin
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy import Boolean, DateTime, Column, Identity, Integer, String, ForeignKey, UnicodeText, func
from sqlalchemy import Boolean, DateTime, Column, Identity, Integer, String, ForeignKey, UnicodeText
from model import METHODS_ENUM # type: ignore
@ -28,8 +29,8 @@ class AnonymousUser(AnonymousUserMixin):
last_login_ip = None
login_count = 0
totp_secret = None
creation_date = datetime.now(timezone.utc)
update_date = datetime.now(timezone.utc)
creation_date = datetime.now(get_timezone())
update_date = datetime.now(get_timezone())
list_roles = []
list_permissions = []
list_recovery_codes = []
@ -51,15 +52,15 @@ class Users(Base, UserMixin):
admin = Column(Boolean, nullable=False, default=False)
# Trackable
last_login_at = Column(DateTime(), nullable=True)
last_login_at = Column(DateTime(timezone=True), nullable=True)
last_login_ip = Column(String(39), nullable=True)
login_count = Column(Integer, default=0, nullable=False)
# 2FA
totp_secret = Column(String(256), nullable=True)
creation_date = Column(DateTime(), nullable=False, server_default=func.now())
update_date = Column(DateTime(), nullable=False, server_default=func.now(), onupdate=partial(datetime.now, timezone.utc))
creation_date = Column(DateTime(timezone=True), nullable=False)
update_date = Column(DateTime(timezone=True), nullable=False)
roles = relationship("RolesUsers", back_populates="user", cascade="all")
recovery_codes = relationship("UserRecoveryCodes", back_populates="user", cascade="all")
@ -79,7 +80,7 @@ class Roles(Base):
name = Column(String(64), primary_key=True)
description = Column(String(256), nullable=False)
update_datetime = Column(DateTime(), nullable=False, server_default=func.now(), onupdate=partial(datetime.now, timezone.utc))
update_datetime = Column(DateTime(timezone=True), nullable=False)
users = relationship("RolesUsers", back_populates="role", cascade="all")
permissions = relationship("RolesPermissions", back_populates="role", cascade="all")

View file

@ -1,5 +1,5 @@
from base64 import b64encode
from datetime import datetime, timezone
from datetime import datetime
from json import dumps, loads as json_loads
from math import floor
from time import time
@ -8,6 +8,8 @@ from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import login_required
from redis import Redis, Sentinel
from common_utils import get_timezone # type: ignore
from builder.bans import bans_builder # type: ignore
from dependencies import BW_CONFIG, BW_INSTANCES_UTILS, DB
@ -168,7 +170,7 @@ def bans_page():
ban_end = float(ban["ban_end"])
except ValueError:
continue
ban_end = (datetime.fromtimestamp(ban_end) - datetime.now(timezone.utc)).total_seconds()
ban_end = (datetime.fromtimestamp(ban_end) - datetime.now(get_timezone())).total_seconds()
if redis_client:
ok = redis_client.set(f"bans_ip_{ban['ip']}", dumps({"reason": reason, "date": time()}))

View file

@ -1,8 +1,10 @@
from datetime import datetime, timezone
from datetime import datetime
from flask import Blueprint, flash, redirect, render_template, request, session, url_for
from flask_login import current_user, login_user
from common_utils import get_timezone # type: ignore
from dependencies import DB
from utils import LOGGER
@ -27,7 +29,7 @@ def login_page():
session["user_agent"] = request.headers.get("User-Agent")
session["totp_validated"] = False
ui_user.last_login_at = datetime.now(timezone.utc)
ui_user.last_login_at = datetime.now(get_timezone())
ui_user.last_login_ip = request.remote_addr
ui_user.login_count += 1

View file

@ -1,6 +1,6 @@
from base64 import b64encode
from copy import deepcopy
from datetime import datetime, timezone
from datetime import datetime
from io import BytesIO
from threading import Thread
from time import sleep, time
@ -10,6 +10,8 @@ from flask import Response, flash, redirect, request, url_for
from qrcode.main import QRCode
from regex import compile as re_compile
from common_utils import get_timezone # type: ignore
from src.instance import Instance
from dependencies import BW_CONFIG, DATA, DB
@ -23,9 +25,9 @@ PLUGIN_ID_RX = re_compile(r"^[\w_-]{1,64}$")
def wait_applying():
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
ready = False
while not ready and (datetime.now(timezone.utc) - current_time).seconds < 120:
while not ready and (datetime.now(get_timezone()) - current_time).seconds < 120:
db_metadata = DB.get_metadata()
if isinstance(db_metadata, str):
LOGGER.error(f"An error occurred when checking for changes in the database : {db_metadata}")

View file

@ -8,7 +8,6 @@ gunicorn[gthread]==23.0.0
passlib==1.7.4
python-magic==0.4.27
python_dateutil==2.9.0.post0
pytz==2024.1
qrcode==7.4.2
regex==2024.7.24
werkzeug==3.0.3

View file

@ -70,9 +70,9 @@ gunicorn==23.0.0 \
--hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \
--hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec
# via -r requirements.in
importlib-metadata==8.2.0 \
--hash=sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369 \
--hash=sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d
importlib-metadata==8.3.0 \
--hash=sha256:42817a4a0be5845d22c6e212db66a94ad261e2318d80b3e0d363894a79df2b67 \
--hash=sha256:9c8fa6e8ea0f9516ad5c8db9246a731c948193c7754d3babb0114a05b27dd364
# via flask
itsdangerous==2.2.0 \
--hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
@ -169,10 +169,6 @@ python-magic==0.4.27 \
--hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
--hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
# via -r requirements.in
pytz==2024.1 \
--hash=sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812 \
--hash=sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319
# via -r requirements.in
qrcode==7.4.2 \
--hash=sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a \
--hash=sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845

View file

@ -1,4 +1,4 @@
from datetime import datetime, timezone
from datetime import datetime
from logging import Logger
from os import sep
from os.path import join
@ -16,6 +16,7 @@ from sqlalchemy import MetaData, inspect, text
from sqlalchemy.orm import joinedload
from sqlalchemy.exc import IntegrityError
from common_utils import get_timezone # type: ignore
from Database import Database # type: ignore
from model import Metadata # type: ignore
@ -47,7 +48,7 @@ class UIDatabase(Database):
if db_version != bunkerweb_version:
self.logger.warning(f"UI tables version ({db_version}) is different from BunkerWeb version ({bunkerweb_version}), migrating them ...")
current_time = datetime.now(timezone.utc)
current_time = datetime.now(get_timezone())
error = True
while error:
try:
@ -55,7 +56,7 @@ class UIDatabase(Database):
metadata.reflect(self.sql_engine)
error = False
except BaseException as e:
if (datetime.now(timezone.utc) - current_time).total_seconds() > 10:
if (datetime.now(get_timezone()) - current_time).total_seconds() > 10:
raise e
sleep(1)
@ -204,6 +205,7 @@ class UIDatabase(Database):
return f"Role {role} doesn't exist"
session.add(RolesUsers(user_name=username, role_name=role))
current_time = datetime.now(get_timezone())
session.add(
Users(
username=username,
@ -212,6 +214,8 @@ class UIDatabase(Database):
method=method,
admin=admin,
totp_secret=totp_secret,
creation_date=current_time,
update_date=current_time,
)
)
@ -251,6 +255,7 @@ class UIDatabase(Database):
user.password = password.decode("utf-8")
user.totp_secret = totp_secret
user.method = method
user.update_date = datetime.now(get_timezone())
try:
session.commit()
@ -313,7 +318,7 @@ class UIDatabase(Database):
if session.query(Roles).with_entities(Roles.name).filter_by(name=name).first():
return f"Role {name} already exists"
session.add(Roles(name=name, description=description))
session.add(Roles(name=name, description=description, update_datetime=datetime.now(get_timezone())))
for permission in permissions:
if not session.query(Permissions).with_entities(Permissions.name).filter_by(name=permission).first():