Soft merge branch 'dev' into branch '1.6'

This commit is contained in:
Théophile Diot 2024-05-23 11:52:55 +01:00
commit 2fdfa1a3aa
No known key found for this signature in database
GPG key ID: 248FEA4BAE400D06
105 changed files with 731 additions and 584 deletions

View file

@ -117,7 +117,7 @@ jobs:
# Check OS vulnerabilities
- name: Check OS vulnerabilities
if: ${{ inputs.CACHE_SUFFIX != 'arm' }}
uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # v0.20.0
uses: aquasecurity/trivy-action@fd25fed6972e341ff0007ddb61f77e88103953c2 # v0.21.0
with:
vuln-type: os
skip-dirs: /root/.cargo

View file

@ -42,7 +42,7 @@ jobs:
- name: Check out repository code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Install ruby
uses: ruby/setup-ruby@7dc18ff0ca6e3630d3f29d2a85ebf6cc27ae9d6c # v1.177.0
uses: ruby/setup-ruby@943103cae7d3f1bb1e4951d5fcc7928b40e4b742 # v1.177.1
with:
ruby-version: "3.0"
- name: Install packagecloud

View file

@ -17,7 +17,7 @@ repos:
- id: check-case-conflict
- repo: https://github.com/psf/black
rev: 8fe627072f15ff2e3d380887b92f7868efaf6d05 # frozen: 24.4.0
rev: 3702ba224ecffbcec30af640c149f231d90aebdb # frozen: 24.4.2
hooks:
- id: black
name: Black Python Formatter
@ -61,7 +61,7 @@ repos:
hooks:
- id: codespell
name: Codespell Spell Checker
exclude: (^src/(ui/templates|common/core/.+/files|bw/loading)/.+.html|modsecurity-rules.conf.*)$
exclude: (^src/(ui/templates|common/core/.+/files|bw/loading)/.+.html|modsecurity-rules.conf.*|src/ui/static/js/lottie-web.min.js)$
entry: codespell --ignore-regex="(tabEl|Widgits)" --skip src/ui/static/js/utils/flatpickr.js,src/ui/static/css/style.css,CHANGELOG.md
language: python
types: [text]

View file

@ -13,8 +13,10 @@ README.md
SECURITY.md
tsparticles.bundle.min.js
flatpickr.*
src/ui/static/js/lottie-web.min.js
src/ui/static/js/editor/*
src/ui/static/js/utils/purify/*
src/ui/static/json/particles.json
src/ui/templates/*
src/common/core/*/ui/*
datepicker-foundation.css

View file

@ -1204,6 +1204,6 @@ You can easily deploy BunkerWeb on your Azure subscription in several ways:
Login in [Azure portal](https://portal.azure.com){:target="_blank"}.
Get BunkerWeb from the [Create ressource menu](https://portal.azure.com/#view/Microsoft_Azure_Marketplace/GalleryItemDetailsBladeNopdl/id/bunkerity.bunkerweb){:target="_blank"}.
Get BunkerWeb from the [Create resource menu](https://portal.azure.com/#view/Microsoft_Azure_Marketplace/GalleryItemDetailsBladeNopdl/id/bunkerity.bunkerweb){:target="_blank"}.
You can also go through the [Marketplace](https://azuremarketplace.microsoft.com/fr-fr/marketplace/apps/bunkerity.bunkerweb?tab=Overview){:target="_blank"}.
You can also go through the [Marketplace](https://azuremarketplace.microsoft.com/fr-fr/marketplace/apps/bunkerity.bunkerweb?tab=Overview){:target="_blank"}.

View file

@ -600,9 +600,9 @@ regex==2024.5.15 \
--hash=sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a \
--hash=sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796
# via mkdocs-material
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# importlib-metadata
# importlib-resources

View file

@ -253,10 +253,11 @@ STREAM support :white_check_mark:
Integrate easily the Database.
| Setting | Default |Context|Multiple| Description |
|--------------------|-----------------------------------------|-------|--------|--------------------------------------------------|
|`DATABASE_URI` |`sqlite:////var/lib/bunkerweb/db.sqlite3`|global |no |The database URI, following the sqlalchemy format.|
|`DATABASE_LOG_LEVEL`|`warning` |global |no |The level to use for database logs. |
| Setting | Default |Context|Multiple| Description |
|-----------------------|-----------------------------------------|-------|--------|-----------------------------------------------------------------------------------------------------------------------------------------|
|`DATABASE_URI` |`sqlite:////var/lib/bunkerweb/db.sqlite3`|global |no |The database URI, following the sqlalchemy format. |
|`DATABASE_URI_READONLY`| |global |no |The database URI for read-only operations, it can also serve as a fallback if the main database is down. Following the sqlalchemy format.|
|`DATABASE_LOG_LEVEL` |`warning` |global |no |The level to use for database logs. |
## DNSBL

View file

@ -1,236 +1,232 @@
{
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string"
},
"networkInterfaceName1": {
"type": "string"
},
"networkSecurityGroupName": {
"type": "string"
},
"networkSecurityGroupRules": {
"type": "array"
},
"subnetName": {
"type": "string"
},
"virtualNetworkName": {
"type": "string"
},
"addressPrefixes": {
"type": "array"
},
"subnets": {
"type": "array"
},
"publicIpAddressName1": {
"type": "string"
},
"publicIpAddressType": {
"type": "string"
},
"publicIpAddressSku": {
"type": "string"
},
"pipDeleteOption": {
"type": "string"
},
"virtualMachineName": {
"type": "string"
},
"virtualMachineName1": {
"type": "string"
},
"virtualMachineComputerName1": {
"type": "string"
},
"virtualMachineRG": {
"type": "string"
},
"osDiskType": {
"type": "string"
},
"osDiskDeleteOption": {
"type": "string"
},
"virtualMachineSize": {
"type": "string"
},
"nicDeleteOption": {
"type": "string"
},
"hibernationEnabled": {
"type": "bool"
},
"adminUsername": {
"type": "string"
},
"securityType": {
"type": "string"
},
"secureBoot": {
"type": "bool"
},
"vTPM": {
"type": "bool"
},
"virtualMachine1Zone": {
"type": "string"
}
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string"
},
"variables": {
"nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]",
"vnetName": "[parameters('virtualNetworkName')]",
"vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
"subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]"
"networkInterfaceName1": {
"type": "string"
},
"resources": [
{
"name": "[parameters('networkInterfaceName1')]",
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2022-11-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkSecurityGroups/', parameters('networkSecurityGroupName'))]",
"[concat('Microsoft.Network/virtualNetworks/', parameters('virtualNetworkName'))]",
"[concat('Microsoft.Network/publicIpAddresses/', parameters('publicIpAddressName1'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"subnet": {
"id": "[variables('subnetRef')]"
},
"privateIPAllocationMethod": "Dynamic",
"publicIpAddress": {
"id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName1'))]",
"properties": {
"deleteOption": "[parameters('pipDeleteOption')]"
}
}
}
}
],
"networkSecurityGroup": {
"id": "[variables('nsgId')]"
}
}
},
{
"name": "[parameters('networkSecurityGroupName')]",
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2020-05-01",
"location": "[parameters('location')]",
"properties": {
"securityRules": "[parameters('networkSecurityGroupRules')]"
}
},
{
"name": "[parameters('virtualNetworkName')]",
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2023-02-01",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": "[parameters('addressPrefixes')]"
},
"subnets": "[parameters('subnets')]"
}
},
{
"name": "[parameters('publicIpAddressName1')]",
"type": "Microsoft.Network/publicIpAddresses",
"apiVersion": "2020-08-01",
"location": "[parameters('location')]",
"properties": {
"publicIpAllocationMethod": "[parameters('publicIpAddressType')]"
},
"sku": {
"name": "[parameters('publicIpAddressSku')]"
},
"zones": [
"[parameters('virtualMachine1Zone')]"
]
},
{
"name": "[parameters('virtualMachineName1')]",
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2024-03-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', parameters('networkInterfaceName1'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('virtualMachineSize')]"
},
"storageProfile": {
"osDisk": {
"createOption": "fromImage",
"managedDisk": {
"storageAccountType": "[parameters('osDiskType')]"
},
"deleteOption": "[parameters('osDiskDeleteOption')]"
},
"imageReference": {
"publisher": "bunkerity",
"offer": "bunkerweb",
"sku": "bunkerweb",
"version": "latest"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName1'))]",
"properties": {
"deleteOption": "[parameters('nicDeleteOption')]"
}
}
]
},
"additionalCapabilities": {
"hibernationEnabled": false
},
"osProfile": {
"computerName": "[parameters('virtualMachineComputerName1')]",
"adminUsername": "[parameters('adminUsername')]",
"linuxConfiguration": {
"disablePasswordAuthentication": true
}
},
"securityProfile": {
"securityType": "[parameters('securityType')]",
"uefiSettings": {
"secureBootEnabled": "[parameters('secureBoot')]",
"vTpmEnabled": "[parameters('vTPM')]"
}
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true
}
}
},
"plan": {
"name": "bunkerweb",
"publisher": "bunkerity",
"product": "bunkerweb"
},
"zones": [
"[parameters('virtualMachine1Zone')]"
]
}
],
"outputs": {
"adminUsername": {
"type": "string",
"value": "[parameters('adminUsername')]"
}
"networkSecurityGroupName": {
"type": "string"
},
"networkSecurityGroupRules": {
"type": "array"
},
"subnetName": {
"type": "string"
},
"virtualNetworkName": {
"type": "string"
},
"addressPrefixes": {
"type": "array"
},
"subnets": {
"type": "array"
},
"publicIpAddressName1": {
"type": "string"
},
"publicIpAddressType": {
"type": "string"
},
"publicIpAddressSku": {
"type": "string"
},
"pipDeleteOption": {
"type": "string"
},
"virtualMachineName": {
"type": "string"
},
"virtualMachineName1": {
"type": "string"
},
"virtualMachineComputerName1": {
"type": "string"
},
"virtualMachineRG": {
"type": "string"
},
"osDiskType": {
"type": "string"
},
"osDiskDeleteOption": {
"type": "string"
},
"virtualMachineSize": {
"type": "string"
},
"nicDeleteOption": {
"type": "string"
},
"hibernationEnabled": {
"type": "bool"
},
"adminUsername": {
"type": "string"
},
"securityType": {
"type": "string"
},
"secureBoot": {
"type": "bool"
},
"vTPM": {
"type": "bool"
},
"virtualMachine1Zone": {
"type": "string"
}
}
},
"variables": {
"nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroupName'))]",
"vnetName": "[parameters('virtualNetworkName')]",
"vnetId": "[resourceId(resourceGroup().name,'Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
"subnetRef": "[concat(variables('vnetId'), '/subnets/', parameters('subnetName'))]"
},
"resources": [
{
"name": "[parameters('networkInterfaceName1')]",
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2022-11-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkSecurityGroups/', parameters('networkSecurityGroupName'))]",
"[concat('Microsoft.Network/virtualNetworks/', parameters('virtualNetworkName'))]",
"[concat('Microsoft.Network/publicIpAddresses/', parameters('publicIpAddressName1'))]"
],
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"subnet": {
"id": "[variables('subnetRef')]"
},
"privateIPAllocationMethod": "Dynamic",
"publicIpAddress": {
"id": "[resourceId(resourceGroup().name, 'Microsoft.Network/publicIpAddresses', parameters('publicIpAddressName1'))]",
"properties": {
"deleteOption": "[parameters('pipDeleteOption')]"
}
}
}
}
],
"networkSecurityGroup": {
"id": "[variables('nsgId')]"
}
}
},
{
"name": "[parameters('networkSecurityGroupName')]",
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2020-05-01",
"location": "[parameters('location')]",
"properties": {
"securityRules": "[parameters('networkSecurityGroupRules')]"
}
},
{
"name": "[parameters('virtualNetworkName')]",
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2023-02-01",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": "[parameters('addressPrefixes')]"
},
"subnets": "[parameters('subnets')]"
}
},
{
"name": "[parameters('publicIpAddressName1')]",
"type": "Microsoft.Network/publicIpAddresses",
"apiVersion": "2020-08-01",
"location": "[parameters('location')]",
"properties": {
"publicIpAllocationMethod": "[parameters('publicIpAddressType')]"
},
"sku": {
"name": "[parameters('publicIpAddressSku')]"
},
"zones": ["[parameters('virtualMachine1Zone')]"]
},
{
"name": "[parameters('virtualMachineName1')]",
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2024-03-01",
"location": "[parameters('location')]",
"dependsOn": [
"[concat('Microsoft.Network/networkInterfaces/', parameters('networkInterfaceName1'))]"
],
"properties": {
"hardwareProfile": {
"vmSize": "[parameters('virtualMachineSize')]"
},
"storageProfile": {
"osDisk": {
"createOption": "fromImage",
"managedDisk": {
"storageAccountType": "[parameters('osDiskType')]"
},
"deleteOption": "[parameters('osDiskDeleteOption')]"
},
"imageReference": {
"publisher": "bunkerity",
"offer": "bunkerweb",
"sku": "bunkerweb",
"version": "latest"
}
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName1'))]",
"properties": {
"deleteOption": "[parameters('nicDeleteOption')]"
}
}
]
},
"additionalCapabilities": {
"hibernationEnabled": false
},
"osProfile": {
"computerName": "[parameters('virtualMachineComputerName1')]",
"adminUsername": "[parameters('adminUsername')]",
"linuxConfiguration": {
"disablePasswordAuthentication": true
}
},
"securityProfile": {
"securityType": "[parameters('securityType')]",
"uefiSettings": {
"secureBootEnabled": "[parameters('secureBoot')]",
"vTpmEnabled": "[parameters('vTPM')]"
}
},
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true
}
}
},
"plan": {
"name": "bunkerweb",
"publisher": "bunkerity",
"product": "bunkerweb"
},
"zones": ["[parameters('virtualMachine1Zone')]"]
}
],
"outputs": {
"adminUsername": {
"type": "string",
"value": "[parameters('adminUsername')]"
}
}
}

View file

@ -29,7 +29,7 @@ LETS_ENCRYPT_WORK_DIR = join(sep, "var", "lib", "bunkerweb", "letsencrypt")
LETS_ENCRYPT_LOGS_DIR = join(sep, "var", "log", "bunkerweb")
def certbot_new(domains: str, email: str, use_letsencrypt_staging: bool = False) -> int:
def certbot_new(domains: str, email: str, use_letsencrypt_staging: bool = False, *, force: bool = False) -> int:
process = Popen(
[
CERTBOT_BIN,
@ -54,7 +54,8 @@ def certbot_new(domains: str, email: str, use_letsencrypt_staging: bool = False)
"--agree-tos",
"--expand",
]
+ (["--staging"] if use_letsencrypt_staging else []),
+ (["--staging"] if use_letsencrypt_staging else [])
+ (["--force-renewal"] if force else []),
stdin=DEVNULL,
stderr=PIPE,
universal_newlines=True,
@ -96,7 +97,7 @@ try:
# Restore Let's Encrypt data from db cache
JOB.restore_cache(job_name="certbot-renew")
domains_to_ask = []
domains_to_ask = {}
# Multisite case
if is_multisite:
domains_server_names = {}
@ -133,18 +134,18 @@ try:
if proc.returncode != 0:
LOGGER.error(f"Error while checking certificates :\n{proc.stdout}")
domains_to_ask = server_names
domains_to_ask = {domain: True for domain in server_names}
else:
for first_server, domains in domains_server_names.items():
generated_domains.update(domains.split(" "))
current_domains = search(rf"Domains: {first_server}(?P<domains>.*)$", stdout, MULTILINE)
if not current_domains:
domains_to_ask.append(first_server)
domains_to_ask[first_server] = False
continue
elif set(f"{first_server}{current_domains.groupdict()['domains']}".strip().split(" ")) != set(domains.split(" ")):
LOGGER.warning(f"Domains for {first_server} are not the same as in the certificate, asking new certificate...")
domains_to_ask.append(first_server)
domains_to_ask[first_server] = True
continue
LOGGER.info(f"Certificates already exists for domain(s) {domains}")
@ -159,7 +160,7 @@ try:
use_letsencrypt_staging = getenv(f"{first_server}_USE_LETS_ENCRYPT_STAGING", getenv("USE_LETS_ENCRYPT_STAGING", "no")) == "yes"
LOGGER.info(f"Asking certificates for domain(s) : {domains} (email = {real_email}) to Let's Encrypt {'staging ' if use_letsencrypt_staging else ''}...")
if certbot_new(domains.replace(" ", ","), real_email, use_letsencrypt_staging) != 0:
if certbot_new(domains.replace(" ", ","), real_email, use_letsencrypt_staging, force=domains_to_ask[first_server]) != 0:
status = 2
LOGGER.error(f"Certificate generation failed for domain(s) {domains} ...")
continue

View file

@ -151,9 +151,9 @@ try:
metadata = resp.json()["data"]
LOGGER.debug(f"Got BunkerWeb Pro license metadata: {metadata}")
metadata["pro_expire"] = datetime.strptime(metadata["pro_expire"], "%Y-%m-%d") if metadata["pro_expire"] else None
if metadata["pro_services"] < int(data["service_number"]):
metadata["pro_overlapped"] = True
metadata["is_pro"] = metadata["pro_status"] == "active"
if metadata["is_pro"] and metadata["pro_services"] < int(data["service_number"]):
metadata["pro_overlapped"] = True
# ? If we already checked today, skip the check and if the metadata is the same, skip the check
if (
@ -211,7 +211,7 @@ try:
if not metadata["is_pro"]:
if metadata["pro_overlapped"]:
LOGGER.warning(
f"You have exceeded the number of services allowed by your BunkerWeb Pro license: {metadata['pro_services']} (current: {data['service_number']}"
f"You have exceeded the number of services allowed by your BunkerWeb Pro license: {metadata['pro_services']} (current: {data['service_number']})"
)
if pro_license_key:

View file

@ -77,7 +77,7 @@ class Database:
"""Initialize the database"""
self.logger = logger
self.readonly = False
self.fallback_readonly = False
self.last_fallback = None
if pool:
self.logger.warning("The pool parameter is deprecated, it will be removed in the next version")
@ -157,26 +157,32 @@ class Database:
while not_connected:
try:
if not self.readonly:
if self.readonly:
with self.sql_engine.connect() as conn:
conn.execute(text("SELECT 1"))
else:
with self.sql_engine.connect() as conn:
conn.execute(text("CREATE TABLE IF NOT EXISTS test (id INT)"))
conn.execute(text("DROP TABLE test"))
else:
with self.sql_engine.connect() as conn:
conn.execute(text("SELECT 1"))
not_connected = False
except (OperationalError, DatabaseError) as e:
if retries <= 0:
if not self.readonly and "attempt to write a readonly database" in str(e):
self.logger.warning("The database is read-only, trying one last time to connect in read-only mode")
self.sql_engine.dispose(close=True)
self.sql_engine = create_engine(sqlalchemy_string_readonly, **self._engine_kwargs)
self.readonly = True
self.fallback_readonly = True
continue
self.logger.error(f"Can't connect to database : {format_exc()}")
_exit(1)
if "attempt to write a readonly database" in str(e):
if not self.readonly:
self.logger.warning("The database is read-only, trying one last time to connect in read-only mode")
self.readonly = True
self.last_fallback = datetime.now()
elif self.database_uri_readonly and sqlalchemy_string != self.database_uri_readonly:
self.logger.warning("Can't connect to the database in read-only mode, falling back to read-only one")
sqlalchemy_string = self.database_uri_readonly
self.last_fallback = datetime.now()
else:
self.logger.error(f"Can't connect to database : {format_exc()}")
_exit(1)
else:
self.logger.error(f"Can't connect to database : {format_exc()}")
_exit(1)
if "attempt to write a readonly database" in str(e):
if log:
@ -206,24 +212,25 @@ class Database:
if self.sql_engine:
self.sql_engine.dispose()
def retry_connection(self) -> None:
def retry_connection(self, *, readonly: bool = False, fallback: bool = False, **kwargs) -> None:
"""Retry the connection to the database"""
assert self.sql_engine is not None
try:
self.sql_engine.dispose(close=True)
self.sql_engine = create_engine(self.database_uri, **self._engine_kwargs)
self.fallback_readonly = False
self.readonly = False
except (OperationalError, DatabaseError) as e:
if self.database_uri_readonly and "attempt to write a readonly database" in str(e):
self.sql_engine.dispose(close=True)
self.sql_engine = create_engine(self.database_uri_readonly, **self._engine_kwargs)
self.fallback_readonly = True
self.readonly = True
return
raise e
if fallback and not self.database_uri_readonly:
raise ValueError("The fallback parameter is set to True but the read-only database URI is not set")
self.sql_engine.dispose(close=True)
self.sql_engine = create_engine(self.database_uri_readonly if fallback else self.database_uri, **self._engine_kwargs | kwargs)
if fallback or readonly:
with self.sql_engine.connect() as conn:
conn.execute(text("SELECT 1"))
return
with self.sql_engine.connect() as conn:
conn.execute(text("CREATE TABLE IF NOT EXISTS test (id INT)"))
conn.execute(text("DROP TABLE test"))
@contextmanager
def __db_session(self) -> Any:
@ -233,30 +240,52 @@ class Database:
self.logger.error("The database engine is not initialized")
_exit(1)
if self.fallback_readonly:
if self.database_uri and self.readonly and self.last_fallback and (datetime.now() - self.last_fallback).total_seconds() > 30:
# ? If the database is forced to be read-only, we try to connect as a non read-only user every time until the database is writable
with suppress(OperationalError, DatabaseError):
self.retry_connection()
try:
self.retry_connection(pool_timeout=1)
self.readonly = False
self.logger.info("The database is no longer read-only, defaulting to read-write mode")
except (OperationalError, DatabaseError):
try:
self.retry_connection(readonly=True, pool_timeout=1)
except (OperationalError, DatabaseError):
if self.database_uri_readonly:
with suppress(OperationalError, DatabaseError):
self.retry_connection(fallback=True, pool_timeout=1)
self.readonly = True
with LOCK:
with self.sql_engine.connect() as conn:
session_factory = sessionmaker(bind=conn, autoflush=True, expire_on_commit=False)
session = scoped_session(session_factory)
try:
session = None
try:
with self.sql_engine.connect() as conn:
session_factory = sessionmaker(bind=conn, autoflush=True, expire_on_commit=False)
session = scoped_session(session_factory)
yield session
except BaseException as e:
except BaseException as e:
if session:
session.rollback()
if self.database_uri_readonly and "attempt to write a readonly database" in str(e):
self.sql_engine.dispose(close=True)
self.sql_engine = create_engine(self.database_uri_readonly, **self._engine_kwargs)
self.fallback_readonly = True
if "attempt to write a readonly database" in str(e):
self.logger.warning("The database is read-only, retrying in read-only mode ...")
try:
self.retry_connection(readonly=True, pool_timeout=1)
except (OperationalError, DatabaseError):
if self.database_uri_readonly:
self.logger.warning("Can't connect to the database in read-only mode, falling back to read-only one")
with suppress(OperationalError, DatabaseError):
self.retry_connection(fallback=True, pool_timeout=1)
self.readonly = True
self.last_fallback = datetime.now()
elif isinstance(e, (ConnectionRefusedError, OperationalError)) and self.database_uri_readonly:
self.logger.warning("Can't connect to the database, falling back to read-only one ...")
with suppress(OperationalError, DatabaseError):
self.retry_connection(fallback=True, pool_timeout=1)
self.readonly = True
self.logger.warning("The database is read-only, falling back to read-only mode")
return
raise
finally:
self.last_fallback = datetime.now()
raise
finally:
if session:
session.remove()
def initialize_db(self, version: str, integration: str = "Unknown") -> str:

View file

@ -277,9 +277,9 @@ redis==5.0.4 \
--hash=sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91 \
--hash=sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61
# via -r requirements.in
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# docker
# kubernetes

View file

@ -157,9 +157,9 @@ pyproject-hooks==1.1.0 \
# via
# build
# pip-tools
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# -r requirements-deps.in
# pip-tools

View file

@ -67,7 +67,7 @@ WORKDIR /usr/share/bunkerweb
# Install fpm
RUN dnf update -y && \
dnf install -y ruby ruby-devel redhat-rpm-config rpm-build && \
dnf install -y ruby ruby-devel redhat-rpm-config rpm-build gcc make && \
gem install -N fpm
# Setup BW

View file

@ -1,4 +1,4 @@
FROM redhat/ubi8:8.9@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac as builder
FROM redhat/ubi8:8.10@sha256:a424544997de1960a93466b57d12f1f3fac62be0f4cd35482435bae305a6ca27 as builder
ENV OS=rhel
ENV NGINX_VERSION 1.24.0
@ -68,7 +68,7 @@ COPY src/scheduler scheduler
COPY src/ui ui
COPY src/VERSION VERSION
FROM redhat/ubi8:8.9@sha256:627867e53ad6846afba2dfbf5cef1d54c868a9025633ef0afd546278d4654eac
FROM redhat/ubi8:8.10@sha256:a424544997de1960a93466b57d12f1f3fac62be0f4cd35482435bae305a6ca27
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -226,24 +226,36 @@ class JobScheduler(ApiCaller):
self.__logger.error(f"Exception while scheduling jobs for plugin {plugin} : {format_exc()}")
def run_pending(self) -> bool:
if self.db.readonly:
if self.db.fallback_readonly:
with suppress(BaseException):
self.db.retry_connection()
threads = []
for job in schedule_jobs:
if not job.should_run:
continue
threads.append(Thread(target=self.__run_in_thread, args=((job.run,),)))
if not threads:
return True
if self.db.database_uri and self.db.readonly:
try:
self.db.retry_connection(pool_timeout=5)
self.db.readonly = False
self.__logger.info("The database is no longer read-only, defaulting to read-write mode")
except BaseException:
try:
self.db.retry_connection(readonly=True, pool_timeout=5)
except BaseException:
if self.db.database_uri_readonly:
with suppress(BaseException):
self.db.retry_connection(fallback=True, pool_timeout=5)
self.db.readonly = True
if self.db.readonly:
self.__logger.error("Database is in read-only mode, jobs will not be executed")
return True
threads = []
self.__job_success = True
self.__job_reload = False
for job in schedule_jobs:
if not job.should_run:
continue
threads.append(Thread(target=self.__run_in_thread, args=((job.run,),)))
for thread in threads:
thread.start()
@ -277,10 +289,19 @@ class JobScheduler(ApiCaller):
return success
def run_once(self) -> bool:
if self.db.readonly:
if self.db.fallback_readonly:
with suppress(BaseException):
self.db.retry_connection()
if self.db.database_uri and self.db.readonly:
try:
self.db.retry_connection(pool_timeout=1)
self.db.readonly = False
self.__logger.info("The database is no longer read-only, defaulting to read-write mode")
except BaseException:
try:
self.db.retry_connection(readonly=True, pool_timeout=1)
except BaseException:
if self.db.database_uri_readonly:
with suppress(BaseException):
self.db.retry_connection(fallback=True, pool_timeout=1)
self.db.readonly = True
if self.db.readonly:
self.__logger.error("Database is in read-only mode, jobs will not be executed")
@ -309,10 +330,19 @@ class JobScheduler(ApiCaller):
return ret
def run_single(self, job_name: str) -> bool:
if self.db.readonly:
if self.db.fallback_readonly:
with suppress(BaseException):
self.db.retry_connection()
if self.db.database_uri and self.db.readonly:
try:
self.db.retry_connection(pool_timeout=1)
self.db.readonly = False
self.__logger.info("The database is no longer read-only, defaulting to read-write mode")
except BaseException:
try:
self.db.retry_connection(readonly=True, pool_timeout=1)
except BaseException:
if self.db.database_uri_readonly:
with suppress(BaseException):
self.db.retry_connection(fallback=True, pool_timeout=1)
self.db.readonly = True
if self.db.readonly:
self.__logger.error("Database is in read-only mode, jobs will not be executed")

View file

@ -174,19 +174,18 @@ def generate_external_plugins(plugins: List[Dict[str, Any]], *, original_path: U
# Remove old external/pro plugins files
logger.info(f"Removing old/changed {'pro ' if pro else ''}external plugins files ...")
ignored_plugins = set()
if original_path.is_dir():
for file in original_path.glob("*"):
try:
with suppress(StopIteration, IndexError):
index = next(i for i, plugin in enumerate(plugins) if plugin["id"] == file.name)
except StopIteration:
index = -1
if index > -1:
with BytesIO() as plugin_content:
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
tar.add(file, arcname=file.name, recursive=True)
plugin_content.seek(0, 0)
if bytes_hash(plugin_content, algorithm="sha256") == plugins[index]["checksum"]:
ignored_plugins.add(file.name)
continue
logger.debug(f"Checksum of {file} has changed, removing it ...")
@ -200,6 +199,9 @@ def generate_external_plugins(plugins: List[Dict[str, Any]], *, original_path: U
logger.info(f"Generating new {'pro ' if pro else ''}external plugins ...")
original_path.mkdir(parents=True, exist_ok=True)
for plugin in plugins:
if plugin["id"] in ignored_plugins:
continue
try:
if plugin["data"]:
tmp_path = TMP_PATH.joinpath(f"{plugin['id']}_{plugin['name']}.tar.gz")
@ -502,13 +504,11 @@ if __name__ == "__main__":
}
jobs = common_data.pop("jobs", [])
try:
with suppress(StopIteration, IndexError):
index = next(i for i, plugin in enumerate(db_plugins) if plugin["id"] == common_data["id"])
except StopIteration:
index = -1
if index > -1 and checksum == db_plugins[index]["checksum"] or db_plugins[index]["method"] != "manual":
continue
if checksum == db_plugins[index]["checksum"] or db_plugins[index]["method"] != "manual":
continue
tmp_external_plugins.append(common_data.copy())

View file

@ -331,9 +331,9 @@ pytz==2024.1 \
# acme
# certbot
# pyrfc3339
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via acme
schedule==1.2.1 \
--hash=sha256:14cdeb083a596aa1de6dc77639a1b2ac8bf6eaafa82b1c9279d3612823063d01 \

View file

@ -177,7 +177,7 @@ def wait_applying():
current_time = datetime.now()
ready = False
while not ready and (datetime.now() - current_time).seconds < 120:
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
if isinstance(db_metadata, str):
app.logger.error(f"An error occurred when checking for changes in the database : {db_metadata}")
elif not any(
@ -203,9 +203,9 @@ def get_ui_data():
return ui_data
def manage_bunkerweb(method: str, *args, operation: str = "reloads", is_draft: bool = False, was_draft: bool = False, threaded: bool = False):
def manage_bunkerweb(method: str, *args, operation: str = "reloads", is_draft: bool = False, was_draft: bool = False, threaded: bool = False) -> int:
# Do the operation
error = False
error = 0
ui_data = get_ui_data()
if "TO_FLASH" not in ui_data:
@ -219,19 +219,15 @@ def manage_bunkerweb(method: str, *args, operation: str = "reloads", is_draft: b
elif operation == "delete":
operation, error = app.config["CONFIG"].delete_service(args[2], check_changes=(was_draft != is_draft or not is_draft))
if error:
ui_data["TO_FLASH"].append({"content": operation, "type": "error"})
else:
ui_data["TO_FLASH"].append({"content": operation, "type": "success"})
if not error:
if was_draft != is_draft or not is_draft:
# update changes in db
ret = db.checked_changes(["config", "custom_configs"], value=True)
ret = app.config["DB"].checked_changes(["config", "custom_configs"], value=True)
if ret:
app.logger.error(f"Couldn't set the changes to checked in the database: {ret}")
ui_data["TO_FLASH"].append({"content": f"An error occurred when setting the changes to checked in the database : {ret}", "type": "error"})
elif method == "global_config":
operation = app.config["CONFIG"].edit_global_conf(args[0])
operation, error = app.config["CONFIG"].edit_global_conf(args[0])
if operation == "reload":
operation = app.config["INSTANCES"].reload_instance(args[0])
@ -243,14 +239,12 @@ def manage_bunkerweb(method: str, *args, operation: str = "reloads", is_draft: b
operation = app.config["INSTANCES"].restart_instance(args[0])
elif not error:
operation = "The scheduler will be in charge of reloading the instances."
else:
operation = ""
if operation:
if isinstance(operation, list):
for op in operation:
ui_data["TO_FLASH"].append({"content": f"Reload failed for the instance {op}", "type": "error"})
elif operation.startswith("Can't"):
elif operation.startswith(("Can't", "The database is read-only")):
ui_data["TO_FLASH"].append({"content": operation, "type": "error"})
else:
ui_data["TO_FLASH"].append({"content": operation, "type": "success"})
@ -268,16 +262,18 @@ def manage_bunkerweb(method: str, *args, operation: str = "reloads", is_draft: b
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
return error
# UTILS
def run_action(plugin: str, function_name: str = ""):
message = ""
module = db.get_plugin_actions(plugin)
module = app.config["DB"].get_plugin_actions(plugin)
if module is None:
return {"status": "ko", "code": 404, "message": "The actions.py file for the plugin does not exist"}
obfuscation = db.get_plugin_obfuscation(plugin)
obfuscation = app.config["DB"].get_plugin_obfuscation(plugin)
tmp_dir = None
try:
@ -388,9 +384,9 @@ def error_message(msg: str):
@app.context_processor
def inject_variables():
ui_data = get_ui_data()
metadata = db.get_metadata()
metadata = app.config["DB"].get_metadata()
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
if ui_data.get("PRO_LOADING") and not any(
v
@ -401,10 +397,6 @@ def inject_variables():
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
if db.readonly and db.fallback_readonly:
with suppress(BaseException):
db.retry_connection()
# check that is value is in tuple
return dict(
script_nonce=app.config["SCRIPT_NONCE"],
@ -416,7 +408,7 @@ def inject_variables():
plugins=app.config["CONFIG"].get_plugins(),
pro_loading=ui_data.get("PRO_LOADING", False),
bw_version=metadata["version"],
is_readonly=db.readonly,
is_readonly=app.config["DB"].readonly,
)
@ -463,7 +455,11 @@ def handle_csrf_error(_):
@app.before_request
def before_request():
db_user = db.get_ui_user()
try:
db_user = app.config["DB"].get_ui_user()
except BaseException:
db_user = app.config["DB"].get_ui_user()
if db_user:
app.config["USER"] = User(**db_user)
@ -480,7 +476,9 @@ def before_request():
and not request.path.startswith(("/css", "/images", "/js", "/json", "/webfonts"))
and request.path.endswith("/login")
):
return redirect(url_for("login", next=request.path))
logout_user()
session.clear()
return redirect(url_for("login"))
# Case not login page, keep on 2FA before any other access
if (
@ -566,7 +564,7 @@ def setup():
if not app.config["USER"]:
app.config["USER"] = User(request.form["admin_username"], request.form["admin_password"], method="ui")
ret = db.create_ui_user(request.form["admin_username"], app.config["USER"].password_hash, method="ui")
ret = app.config["DB"].create_ui_user(request.form["admin_username"], app.config["USER"].password_hash, method="ui")
if ret:
return redirect_flash_error(f"Couldn't create the admin user in the database: {ret}", "setup", False, "error")
@ -689,7 +687,7 @@ def home():
version=bw_version,
instances_number=len(instances),
services_number=services,
plugins_errors=db.get_plugins_errors(),
plugins_errors=app.config["DB"].get_plugins_errors(),
instance_health_count=instance_health_count,
services_scheduler_count=services_scheduler_count,
services_ui_count=services_ui_count,
@ -724,19 +722,22 @@ def account():
# Force job to contact PRO API
# by setting the last check to None
metadata = db.get_metadata()
metadata = app.config["DB"].get_metadata()
metadata["last_pro_check"] = None
db.set_metadata(metadata)
app.config["DB"].set_metadata(metadata)
flash("Checking license key to upgrade.", "success")
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
# Reload instances
def update_global_config(threaded: bool = False):
wait_applying()
manage_bunkerweb("global_config", variable, threaded=threaded)
if not manage_bunkerweb("global_config", variable, threaded=threaded):
message = "Checking license key to upgrade."
if threaded:
ui_data["TO_FLASH"].append({"content": message, "type": "success"})
else:
flash(message)
ui_data = get_ui_data()
ui_data["PRO_LOADING"] = True
@ -814,7 +815,7 @@ def account():
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
user = User(username, password, is_two_factor_enabled=is_two_factor_enabled, secret_token=secret_token, method=current_user.method)
ret = db.update_ui_user(
ret = app.config["DB"].update_ui_user(
username, user.password_hash, is_two_factor_enabled, secret_token, current_user.method if request.form["operation"] == "totp" else "ui"
)
if ret:
@ -1007,7 +1008,7 @@ def services():
error = 0
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
old_server_name = request.form.get("OLD_SERVER_NAME", "")
operation = request.form["operation"]
@ -1141,7 +1142,7 @@ def global_config():
if setting and setting["global"] and (setting["value"] != value or setting["value"] == config.get(variable, {"value": None})["value"]):
variables[f"{service}_{variable}"] = value
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
def update_global_config(threaded: bool = False):
wait_applying()
@ -1192,7 +1193,7 @@ def global_config():
@app.route("/configs", methods=["GET", "POST"])
@login_required
def configs():
db_configs = db.get_custom_configs()
db_configs = app.config["DB"].get_custom_configs()
if request.method == "POST":
operation = ""
@ -1284,7 +1285,7 @@ def configs():
del db_configs[index]
operation = f"Deleted config {name}{f' for service {service_id}' if service_id else ''}"
error = db.save_custom_configs([config for config in db_configs if config["method"] == "ui"], "ui")
error = app.config["DB"].save_custom_configs([config for config in db_configs if config["method"] == "ui"], "ui")
if error:
app.logger.error(f"Could not save custom configs: {error}")
return redirect_flash_error("Couldn't save custom configs", "configs", True)
@ -1325,7 +1326,7 @@ def plugins():
if variables["type"] in ("core", "pro"):
return redirect_flash_error(f"Can't delete {variables['type']} plugin {variables['name']}", "plugins", True)
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
def update_plugins(threaded: bool = False): # type: ignore
wait_applying()
@ -1337,7 +1338,7 @@ def plugins():
ui_data = get_ui_data()
err = db.update_external_plugins(plugins)
err = app.config["DB"].update_external_plugins(plugins)
if err:
message = f"Couldn't update external plugins to database: {err}"
if threaded:
@ -1538,7 +1539,7 @@ def plugins():
if errors >= files_count:
return redirect(url_for("loading", next=url_for("plugins")))
db_metadata = db.get_metadata()
db_metadata = app.config["DB"].get_metadata()
def update_plugins(threaded: bool = False):
wait_applying()
@ -1551,7 +1552,7 @@ def plugins():
ui_data = get_ui_data()
err = db.update_external_plugins(new_plugins, delete_missing=False)
err = app.config["DB"].update_external_plugins(new_plugins, delete_missing=False)
if err:
message = f"Couldn't update external plugins to database: {err}"
if threaded:
@ -1692,7 +1693,7 @@ def custom_plugin(plugin: str):
if request.method == "GET":
# Check template
page = db.get_plugin_template(plugin)
page = app.config["DB"].get_plugin_template(plugin)
if not page:
return error_message("The plugin does not have a template"), 404
@ -1825,7 +1826,7 @@ def cache():
path_to_dict(
join(sep, "var", "cache", "bunkerweb"),
is_cache=True,
db_data=db.get_jobs_cache_files(),
db_data=app.config["DB"].get_jobs_cache_files(),
services=app.config["CONFIG"].get_config(methods=False).get("SERVER_NAME", "").split(" "),
)
],
@ -2174,6 +2175,9 @@ def bans():
flash("Couldn't connect to redis, ban list might be incomplete", "error")
if request.method == "POST":
if app.config["DB"].readonly:
return redirect_flash_error("Read only mode is enabled", "bans")
# Check variables
is_request_form("bans")
@ -2281,7 +2285,7 @@ def bans():
@app.route("/jobs", methods=["GET"])
@login_required
def jobs():
return render_template("jobs.html", jobs=db.get_jobs(), jobs_errors=db.get_plugins_errors(), username=current_user.get_id())
return render_template("jobs.html", jobs=app.config["DB"].get_jobs(), jobs_errors=app.config["DB"].get_plugins_errors(), username=current_user.get_id())
@app.route("/jobs/download", methods=["GET"])
@ -2297,7 +2301,7 @@ def jobs_download():
file_name = secure_filename(file_name)
cache_file = db.get_job_cache_file(job_name, file_name, service_id=service_id, plugin_id=plugin_id)
cache_file = app.config["DB"].get_job_cache_file(job_name, file_name, service_id=service_id, plugin_id=plugin_id)
if not cache_file:
return jsonify({"status": "ko", "message": "file not found"}), 404

View file

@ -49,10 +49,7 @@ class Config:
conf["SERVER_NAME"] = " ".join(servers)
conf["DATABASE_URI"] = self.__db.database_uri
err = self.__db.save_config(conf, "ui", changed=check_changes)
if err:
self.__db.logger.warning(f"Couldn't save config to database : {err}, config may not work as expected")
return self.__db.save_config(conf, "ui", changed=check_changes)
def get_plugins_settings(self) -> dict:
return {
@ -139,8 +136,8 @@ class Config:
return error
def reload_config(self) -> None:
self.__gen_conf(self.get_config(methods=False), self.get_services(methods=False))
def reload_config(self) -> Optional[str]:
return self.__gen_conf(self.get_config(methods=False), self.get_services(methods=False))
def new_service(self, variables: dict, is_draft: bool = False) -> Tuple[str, int]:
"""Creates a new service from the given variables
@ -167,7 +164,9 @@ class Config:
return f"Service {service['SERVER_NAME'].split(' ')[0]} already exists.", 1
services.append(variables | {"IS_DRAFT": "yes" if is_draft else "no"})
self.__gen_conf(self.get_config(methods=False), services, check_changes=not is_draft)
ret = self.__gen_conf(self.get_config(methods=False), services, check_changes=not is_draft)
if ret:
return ret, 1
return f"Configuration for {variables['SERVER_NAME'].split(' ')[0]} has been generated.", 0
def edit_service(self, old_server_name: str, variables: dict, *, check_changes: bool = True, is_draft: bool = False) -> Tuple[str, int]:
@ -205,10 +204,12 @@ class Config:
if k.startswith(old_server_name_splitted[0]):
config.pop(k)
self.__gen_conf(config, services, check_changes=check_changes, changed_service=variables["SERVER_NAME"])
ret = self.__gen_conf(config, services, check_changes=check_changes, changed_service=server_name_splitted[0])
if ret:
return ret, 1
return f"Configuration for {old_server_name_splitted[0]} has been edited.", 0
def edit_global_conf(self, variables: dict) -> str:
def edit_global_conf(self, variables: dict) -> Tuple[str, int]:
"""Edits the global conf
Parameters
@ -221,8 +222,10 @@ class Config:
str
the confirmation message
"""
self.__gen_conf(self.get_config(methods=False) | variables, self.get_services(methods=False))
return "The global configuration has been edited."
ret = self.__gen_conf(self.get_config(methods=False) | variables, self.get_services(methods=False))
if ret:
return ret, 1
return "The global configuration has been edited.", 0
def delete_service(self, service_name: str, *, check_changes: bool = True) -> Tuple[str, int]:
"""Deletes a service
@ -269,5 +272,7 @@ class Config:
if k in service:
service.pop(k)
self.__gen_conf(new_env, new_services, check_changes=check_changes)
ret = self.__gen_conf(new_env, new_services, check_changes=check_changes)
if ret:
return ret, 1
return f"Configuration for {service_name} has been deleted.", 0

View file

@ -1,3 +1,4 @@
from contextlib import suppress
from json import loads
from glob import glob
from os import sep
@ -7,7 +8,7 @@ from os.path import join
def get_ui_templates():
ui_templates = []
for template_file in glob(join(sep, "usr", "share", "bunkerweb", "templates", "*.json")):
try:
with suppress(BaseException): # TODO: log exceptions
ui_template = {}
with open(template_file, "r") as f:
bw_template = loads(f.read())
@ -23,9 +24,5 @@ def get_ui_templates():
ui_step["settings"].append(ui_setting)
ui_template["steps"].append(ui_step)
ui_templates.append(ui_template)
except Exception as e:
# print(e)
# TODO: log
pass
# print(ui_templates, flush=True)
return ui_templates

File diff suppressed because one or more lines are too long

View file

@ -513,38 +513,84 @@ class Banner {
class Clipboard {
constructor() {
this.isCopy = false;
this.init();
}
init() {
// Show clipboard copy if https
window.addEventListener("load", () => {
// Show clipboard copy if https and has permissions
window.addEventListener("load", async () => {
if (!window.location.href.startsWith("https://")) return;
document.querySelectorAll("[data-clipboard-copy]").forEach((el) => {
el.classList.remove("hidden");
});
});
window.addEventListener("click", (e) => {
window.addEventListener("click", async (e) => {
if (!e.target.hasAttribute("data-clipboard-target")) return;
this.isCopy = false;
// With Chrome
try {
navigator.permissions
.query({ name: "clipboard-write" })
.then((result) => {
if (result.state === "granted" || result.state === "prompt") {
/* write to the clipboard now */
const copyEl = document.querySelector(
e.target.getAttribute("data-clipboard-target"),
);
navigator.permissions
.query({ name: "clipboard-write" })
.then((result) => {
if (result.state === "granted" || result.state === "prompt") {
/* write to the clipboard now */
const copyEl = document.querySelector(
e.target.getAttribute("data-clipboard-target"),
);
copyEl.select();
copyEl.setSelectionRange(0, 99999); // For mobile devices
copyEl.select();
copyEl.setSelectionRange(0, 99999); // For mobile devices
// Copy the text inside the text field
// Copy the text inside the text field
navigator.clipboard.writeText(copyEl.value);
}
});
navigator.clipboard.writeText(copyEl.value);
// Stop selecting
copyEl.blur();
this.isCopy = true;
}
});
} catch (e) {}
// With Firefox
try {
if (this.isCopy) return;
/* write to the clipboard now */
const copyEl = document.querySelector(
e.target.getAttribute("data-clipboard-target"),
);
copyEl.select();
copyEl.setSelectionRange(0, 99999); // For mobile devices
// Copy the text inside the text field
navigator.clipboard.writeText(copyEl.value);
// Stop selecting
copyEl.blur();
this.isCopy = true;
} catch (e) {}
// Default
try {
if (this.isCopy) return;
/* write to the clipboard now */
const copyEl = document.querySelector(
e.target.getAttribute("data-clipboard-target"),
);
copyEl.select();
copyEl.setSelectionRange(0, 99999); // For mobile devices
// Copy the text inside the text field
navigator.clipboard.writeText(copyEl.value);
// Stop selecting
copyEl.blur();
document.execCommand("copy");
this.isCopy = true;
} catch (e) {}
});
}
}

View file

@ -13,6 +13,12 @@ class Multiple {
constructor(prefix) {
this.prefix = prefix;
this.container = document.querySelector("main");
this.isReadonly =
document
.querySelector("[data-global-is-readonly]")
.getAttribute("data-global-is-readonly") === "true"
? true
: false;
this.init();
}
@ -185,7 +191,9 @@ class Multiple {
? true
: false;
return proDisabled;
if (proDisabled || this.isReadonly) return true;
return false;
}
sortMultipleByContainerAndSuffixe(obj) {
@ -543,6 +551,7 @@ class Multiple {
//for already existing global config multiples
//global is check
setDisabledMultServ(inp, method, global) {
if (o) return inp.setAttribute("disabled", "");
// Check if pro
const proDisabled = inp
.closest("[data-plugin-item]")

File diff suppressed because one or more lines are too long

View file

@ -291,6 +291,12 @@ class FolderDropdown {
class FolderEditor {
constructor() {
this.isReadonly =
document
.querySelector(`[data-global-is-readonly]`)
.getAttribute(`data-global-is-readonly`) === "true"
? true
: false;
this.editor = ace.edit("editor");
this.darkMode = document.querySelector("[data-dark-toggle]");
this.initEditor();
@ -300,6 +306,7 @@ class FolderEditor {
initEditor() {
//editor options
this.editor.setShowPrintMargin(false);
this.editor.setReadOnly(this.isReadonly);
this.setDarkMode();
}
@ -324,10 +331,6 @@ class FolderEditor {
? this.editor.setTheme("ace/theme/dracula")
: this.editor.setTheme("ace/theme/dawn");
}
readOnlyBool(bool) {
this.editor.setReadOnly(bool);
}
}
class FolderModal {
@ -666,8 +669,10 @@ class FolderModal {
//UTILS
disabledDOMInpt(bool) {
this.modalPathName.disabled = bool;
ace.edit("editor").setReadOnly(bool);
if (this.isReadonly) ace.edit("editor").setReadOnly(true);
if (this.isReadonly) this.modalPathName.disabled = true;
if (!this.isReadonly) this.modalPathName.disabled = bool;
if (!this.isReadonly) ace.edit("editor").setReadOnly(bool);
}
closeModal() {

View file

@ -268,15 +268,23 @@ class Password {
class DisabledPop {
constructor() {
this.isReadonly =
document
.querySelector("[data-global-is-readonly]")
.getAttribute("data-global-is-readonly") === "true"
? true
: false;
this.init();
}
init() {
window.addEventListener("pointerover", (e) => {
if (this.isReadonly) return;
//for checkbox and regular inputs
if (
e.target.tagName === "INPUT" &&
e.target.hasAttribute("data-default-method")
(e.target.tagName === "INPUT" &&
e.target.hasAttribute("data-default-method")) ||
e.target.hasAttribute("data-method")
) {
const el = e.target;
this.showPopup(el, "input");
@ -292,6 +300,8 @@ class DisabledPop {
});
window.addEventListener("pointerout", (e) => {
if (this.isReadonly) return;
try {
const popupEl = e.target
.closest("div")
@ -308,7 +318,8 @@ class DisabledPop {
.querySelector("button[data-setting-password]")
? true
: false;
const method = el.getAttribute("data-default-method");
const method =
el.getAttribute("data-method") || el.getAttribute("data-default-method");
const popupHTML = `
<div data-disabled-info class="${
type === "select" ? "translate-y-2" : ""

File diff suppressed because one or more lines are too long

View file

@ -666,13 +666,17 @@
}
.plugins-list-items-delete {
@apply z-20 mx-2 inline-block font-bold text-left text-white uppercase align-middle transition-all cursor-pointer text-xs ease-in tracking-tight-rem hover:-translate-y-px;
@apply z-20 mx-2 inline-block font-bold text-left text-white uppercase align-middle transition-all cursor-pointer text-xs ease-in tracking-tight-rem hover:-translate-y-px disabled:cursor-not-allowed;
}
.plugins-list-items-delete-svg {
@apply h-5 w-5 fill-red-500 dark:brightness-90;
}
.readonly.plugins-list-items-delete-svg {
@apply cursor-not-allowed opacity-[0.5];
}
.plugins-list-items-link {
@apply hover:-translate-y-px mx-1;
}

View file

@ -22,6 +22,7 @@
<!-- info -->
<main class="xl:pl-75 w-full px-2 sm:px-6 pb-0 pt-20 sm:pt-3 min-h-[85vh] flex flex-col justify-between">
<div class="max-w-[1920px] grid gap-y-4 gap-3 sm:gap-4 lg:gap-6 grid-cols-12 w-full">
<div class="hidden" data-global-is-readonly="{%if is_readonly%}true{% else%}false{%endif%}"></div>
{% block content %}{% endblock %}
</div>
{% include "footer.html" %}

View file

@ -40,7 +40,7 @@
<!-- button list-->
<div class="relative w-full flex justify-center sm:justify-end">
{% if instance._type == "local" and instance.health %}
<button {% if is_readonly%}disabled{% endif %} type="submit"
<button type="submit"
name="operation"
value="restart"
class="edit-btn mx-1 text-xs">Restart</button>
@ -50,18 +50,18 @@
class="delete-btn mx-1 text-xs">Stop</button>
{% endif %}
{% if not instance._type == "local" and instance.health %}
<button {% if is_readonly%}disabled{% endif %} type="submit"
<button type="submit"
name="operation"
value="reload"
class="edit-btn mx-1 text-xs">Reload</button>
<button {% if is_readonly%}disabled{% endif %} type="submit"
<button type="submit"
name="operation"
value="stop"
class="delete-btn mx-1 text-xs">Stop</button>
{% endif %}
{% if instance._type == "local" and not instance.health or not
instance._type == "local" and not instance.health %}
<button {% if is_readonly%}disabled{% endif %} type="submit"
<button type="submit"
name="operation"
value="start"
class="valid-btn mx-1 text-xs">Start</button>

View file

@ -106,12 +106,12 @@
</svg>
</a>
{% endif %}
{% if plugin['type'] == "external" %}
{% if plugin['type'] == "external" and plugin['method'] in ('ui', 'manual') %}
<button {% if is_readonly%}disabled{% endif %} data-{{attribute_name}}-action="delete"
name="{{ plugin['id'] }}"
aria-label="delete plugin"
class="plugins-list-items-delete">
<svg class="plugins-list-items-delete-svg"
<svg class="plugins-list-items-delete-svg {% if is_readonly%}readonly{% endif %}"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512">
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z" />

View file

@ -575,7 +575,7 @@
});
// Avoid reload on newsletter submit
this.newsForm.addEventListener('submit' (e) => {
this.newsForm.addEventListener('submit', (e) => {
e.preventDefault()
})

View file

@ -1,2 +1,2 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1,2 +1,2 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1 +1,2 @@
docker==7.0.0
requests<2.32.0

View file

@ -112,10 +112,12 @@ packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
# via docker
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
# via docker
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
# via
# -r requirements.in
# docker
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19

View file

@ -1,2 +1,2 @@
docker==7.0.0
requests==2.32.1
requests==2.32.2

View file

@ -112,9 +112,9 @@ packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
# via docker
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# -r requirements.in
# docker

View file

@ -1,2 +1,2 @@
maxminddb==2.6.1
requests==2.32.1
requests==2.32.2

View file

@ -175,9 +175,9 @@ maxminddb==2.6.1 \
--hash=sha256:fb56115caee4f3beafd2907845dc8f80c633424cbe270a3738f6ba609ff7248e \
--hash=sha256:fc3526c587f53dd32a5191e81f4239bb3ead70f56a97936b3427b72e3a5cc55f
# via -r requirements.in
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1,2 @@
docker==7.0.0
requests<2.32.0

View file

@ -112,10 +112,12 @@ packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
# via docker
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
# via docker
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
# via
# -r requirements.in
# docker
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,2 +1,2 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,3 +1,3 @@
cryptography==42.0.7
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -225,9 +225,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,2 +1,2 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1,2 +1,2 @@
maxminddb==2.6.1
requests==2.32.1
requests==2.32.2

View file

@ -175,9 +175,9 @@ maxminddb==2.6.1 \
--hash=sha256:fb56115caee4f3beafd2907845dc8f80c633424cbe270a3738f6ba609ff7248e \
--hash=sha256:fc3526c587f53dd32a5191e81f4239bb3ead70f56a97936b3427b72e3a5cc55f
# via -r requirements.in
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,2 +1,2 @@
cryptography==42.0.7
requests==2.32.1
requests==2.32.2

View file

@ -196,9 +196,9 @@ pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,2 +1,2 @@
httpx==0.27.0
requests==2.32.1
requests==2.32.2

View file

@ -130,9 +130,9 @@ idna==3.7 \
# anyio
# httpx
# requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,2 +1,2 @@
docker==7.0.0
requests==2.32.1
requests==2.32.2

View file

@ -112,9 +112,9 @@ packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
# via docker
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# -r requirements.in
# docker

View file

@ -1,2 +1,2 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1,5 +1,5 @@
fastapi==0.111.0
redis==5.0.4
requests==2.32.1
requests==2.32.2
selenium<4.17.0
uvicorn[standard]==0.29.0

View file

@ -501,9 +501,9 @@ redis==5.0.4 \
--hash=sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91 \
--hash=sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61
# via -r requirements.in
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
rich==13.7.1 \
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \

View file

@ -1,3 +1,3 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0
websocket-client==1.8.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1,3 +1,3 @@
fastapi==0.111.0
requests==2.32.1
requests==2.32.2
uvicorn[standard]==0.29.0

View file

@ -472,9 +472,9 @@ pyyaml==6.0.1 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
# via uvicorn
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
rich==13.7.1 \
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \

View file

@ -1,2 +1,2 @@
cryptography==42.0.7
requests==2.32.1
requests==2.32.2

View file

@ -196,9 +196,9 @@ pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,2 +1,2 @@
requests==2.32.1
requests==2.32.2
selenium<4.17.0

View file

@ -133,9 +133,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via urllib3
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \

View file

@ -1,2 +1,2 @@
maxminddb==2.6.1
requests==2.32.1
requests==2.32.2

View file

@ -175,9 +175,9 @@ maxminddb==2.6.1 \
--hash=sha256:fb56115caee4f3beafd2907845dc8f80c633424cbe270a3738f6ba609ff7248e \
--hash=sha256:fc3526c587f53dd32a5191e81f4239bb3ead70f56a97936b3427b72e3a5cc55f
# via -r requirements.in
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1 +1 @@
requests==2.32.1
requests==2.32.2

View file

@ -104,9 +104,9 @@ idna==3.7 \
--hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
--hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
# via requests
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

View file

@ -1,4 +1,4 @@
FROM redhat/ubi8-init:8.9-7.1715071668@sha256:3bcb1434ddc595236a1e45b9c4d4722ab8f1348a371c6239973bbf4d67b24c96
FROM redhat/ubi8-init:8.10-2@sha256:26aec3f78f127e39cb45e7eebd1dafc17071246d78dc51be4cfcb205ffc89caa
ENV NGINX_VERSION 1.24.0

View file

@ -1,3 +1,3 @@
pyOpenSSL==24.1.0
pyyaml==6.0.1
requests==2.32.1
requests==2.32.2

View file

@ -253,9 +253,9 @@ pyyaml==6.0.1 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
# via -r requirements.in
requests==2.32.1 \
--hash=sha256:21ac9465cdf8c1650fe1ecde8a71669a93d4e6f147550483a2967d08396a56a5 \
--hash=sha256:eb97e87e64c79e64e5b8ac75cee9dd1f97f49e289b083ee6be96268930725685
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \

Some files were not shown because too many files have changed in this diff Show more