mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
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:
parent
b1cf93af14
commit
b2976de125
35 changed files with 244 additions and 154 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
docker==7.1.0
|
||||
kubernetes==30.1.0
|
||||
pytz==2024.1
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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...")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
jinja2==3.1.4
|
||||
python-dotenv==1.0.1
|
||||
pytz==2024.1
|
||||
redis==5.0.8
|
||||
requests==2.32.3
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
pip==24.2
|
||||
pip-tools==7.4.1
|
||||
setuptools==72.2.0
|
||||
setuptools==73.0.0
|
||||
wheel==0.44.0
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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) ...")
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()}))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Reference in a new issue