Merge pull request #1201 from bunkerity/dev

Merge branch "dev" into branch "staging"
This commit is contained in:
Théophile Diot 2024-05-22 17:06:14 +01:00 committed by GitHub
commit 9b38952a3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
177 changed files with 2300 additions and 1632 deletions

View file

@ -135,7 +135,7 @@ jobs:
versionrpm: ${{ steps.getversionrpm.outputs.versionrpm }}
steps:
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Get VERSION
id: getversion
run: echo "version=$(cat src/VERSION | tr -d '\n')" >> "$GITHUB_OUTPUT"

View file

@ -19,7 +19,7 @@ jobs:
language: ["python", "javascript"]
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set up Python 3.9
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
if: matrix.language == 'python'
@ -35,12 +35,12 @@ jobs:
python -m pip install --no-cache-dir --require-hashes -r src/common/db/requirements.txt
echo "CODEQL_PYTHON=$(which python)" >> $GITHUB_ENV
- name: Initialize CodeQL
uses: github/codeql-action/init@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 # v3.25.4
uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql.yml
setup-python-dependencies: false
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 # v3.25.4
uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with:
category: "/language:${{matrix.language}}"

View file

@ -45,7 +45,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Replace VERSION
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}

View file

@ -33,7 +33,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Get ARM availabilities
id: availabilities
uses: scaleway/action-scw@be2696f261325a78354eda14988c80405f33e082

View file

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 0
token: ${{ secrets.BUNKERBOT_TOKEN }}

View file

@ -80,7 +80,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- id: set-matrix
run: |
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')
@ -113,7 +113,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- id: set-matrix
run: |
tests=$(find ./tests/core/ -maxdepth 1 -mindepth 1 -type d -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')

View file

@ -13,7 +13,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Install Python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:

View file

@ -37,7 +37,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Replace VERSION
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' || inputs.RELEASE == 'ui'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 0
token: ${{ secrets.BUNKERBOT_TOKEN }}

View file

@ -33,7 +33,7 @@ jobs:
steps:
# Prepare
- name: Check out repository code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Login to Docker Hub
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
# Get PDF doc
- name: Get documentation
if: inputs.VERSION != 'testing'

View file

@ -40,9 +40,9 @@ jobs:
steps:
# Prepare
- name: Check out repository code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Install ruby
uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # v1.176.0
uses: ruby/setup-ruby@7dc18ff0ca6e3630d3f29d2a85ebf6cc27ae9d6c # v1.177.0
with:
ruby-version: "3.0"
- name: Install packagecloud

View file

@ -7,8 +7,8 @@ on:
branches: [master]
jobs:
scorecards-analysis:
uses: ./.github/workflows/scorecards-analysis.yml
# scorecards-analysis:
# uses: ./.github/workflows/scorecards-analysis.yml
codeql:
uses: ./.github/workflows/codeql.yml
@ -106,6 +106,9 @@ jobs:
matrix:
linux: [ubuntu, ubuntu-noble, debian, fedora, rhel, rhel9]
platforms: [linux/amd64, linux/arm64]
exclude:
- linux: ubuntu-noble
platforms: linux/arm64
include:
- release: latest
- linux: ubuntu
@ -143,7 +146,7 @@ jobs:
versionrpm: ${{ steps.getversionrpm.outputs.versionrpm }}
steps:
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Get VERSION
id: getversion
run: echo "version=$(cat src/VERSION | tr -d '\n')" >> "$GITHUB_OUTPUT"
@ -195,6 +198,9 @@ jobs:
matrix:
linux: [ubuntu, ubuntu-noble, debian, fedora, el, el9]
arch: [amd64, arm64]
exclude:
- linux: ubuntu-noble
arch: arm64
include:
- release: latest
repo: bunkerweb
@ -249,9 +255,6 @@ jobs:
- linux: ubuntu
arch: arm64
package_arch: arm64
- linux: ubuntu-noble
arch: arm64
package_arch: arm64
- linux: debian
arch: arm64
package_arch: arm64

View file

@ -21,7 +21,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Delete ARM VM
uses: scaleway/action-scw@be2696f261325a78354eda14988c80405f33e082
with:

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
persist-credentials: false
- name: "Run analysis"
@ -25,6 +25,6 @@ jobs:
results_format: sarif
publish_results: true
- name: "Upload SARIF results to code scanning"
uses: github/codeql-action/upload-sarif@ccf74c947955fd1cf117aef6a0e4e66191ef6f61 # v3.25.4
uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with:
sarif_file: results.sarif

View file

@ -21,7 +21,7 @@ jobs:
run: ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N "" && ssh-keygen -f ~/.ssh/id_rsa -y > ~/.ssh/id_rsa.pub && echo -e "Host *\n StrictHostKeyChecking no" > ~/.ssh/ssh_config
if: inputs.TYPE != 'k8s'
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Install terraform
uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1
- name: Install kubectl

View file

@ -20,7 +20,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Install terraform
uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1
- uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7

View file

@ -25,7 +25,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Login to ghcr
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:

View file

@ -91,7 +91,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- id: set-matrix
run: |
tests=$(find ./tests/core/ -maxdepth 1 -mindepth 1 -type d -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')
@ -102,7 +102,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- id: set-matrix
run: |
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set up Python 3.9
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Login to ghcr
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set up Python 3.9
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:

View file

@ -15,7 +15,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Login to ghcr
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:

View file

@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- id: set-matrix
run: |
tests=$(find ./tests/ui/ -name "*_page.py" -type f -printf "%f\n" | jq -c --raw-input --slurp 'split("\n")| .[0:-1]')

View file

@ -10,3 +10,4 @@ src/ui/templates/settings_plugins.html:hashicorp-tf-password:297
src/ui/templates/settings_plugins.html:hashicorp-tf-password:106
src/ui/templates/account.html:hashicorp-tf-password:154
src/ui/templates/account.html:hashicorp-tf-password:162
src/common/core/errors/files/error.html:aws-access-token:20

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

@ -1,6 +1,11 @@
# Changelog
## v1.5.7 - ????/??/??
## v1.5.8 - ????/??/??
- [FEATURE] Add nightly build of the OWASP coreruleset that are automatically downloaded and updated
- [FEATURE] Enhance security on error pages, default server page and loading page by adding a custom `Content-Security-Policy` header with nonces and removing the `Server` header
## v1.5.7 - 2024/05/14
- [LINUX] Support Ubuntu 24.04 (Noble)
- [LINUX] Support RHEL 9.4 instead of 9.3

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

@ -1,4 +1,4 @@
mike==2.1.1
mkdocs-material[imaging]==9.5.22
mkdocs-material[imaging]==9.5.24
mkdocs-print-site-plugin==2.4.1
pytablewriter==1.2.0

View file

@ -317,9 +317,9 @@ mkdocs-get-deps==0.2.0 \
--hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \
--hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134
# via mkdocs
mkdocs-material==9.5.22 \
--hash=sha256:22a853a456ae8c581c4628159574d6fc7c71b2c7569dc9c3a82cc70432219599 \
--hash=sha256:8c7a377d323567934e6cd46915e64dc209efceaec0dec1cf2202184f5649862c
mkdocs-material==9.5.24 \
--hash=sha256:02d5aaba0ee755e707c3ef6e748f9acb7b3011187c0ea766db31af8905078a34 \
--hash=sha256:e12cd75954c535b61e716f359cf2a5056bf4514889d17161fdebd5df4b0153c6
# via
# -r requirements.in
# mkdocs-print-site-plugin
@ -421,9 +421,9 @@ pillow==10.3.0 \
# via
# cairosvg
# mkdocs-material
platformdirs==4.2.1 \
--hash=sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf \
--hash=sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1
platformdirs==4.2.2 \
--hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \
--hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3
# via mkdocs-get-deps
pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
@ -519,98 +519,98 @@ pyyaml-env-tag==0.1 \
# via
# mike
# mkdocs
regex==2024.5.10 \
--hash=sha256:031219782d97550c2098d9a68ce9e9eaefe67d2d81d8ff84c8354f9c009e720c \
--hash=sha256:0709ba544cf50bd5cb843df4b8bb6701bae2b70a8e88da9add8386cbca5c1385 \
--hash=sha256:0a9f89d7db5ef6bdf53e5cc8e6199a493d0f1374b3171796b464a74ebe8e508a \
--hash=sha256:0bc94873ba11e34837bffd7e5006703abeffc4514e2f482022f46ce05bd25e67 \
--hash=sha256:0ce56a923f4c01d7568811bfdffe156268c0a7aae8a94c902b92fe34c4bde785 \
--hash=sha256:0faecb6d5779753a6066a3c7a0471a8d29fe25d9981ca9e552d6d1b8f8b6a594 \
--hash=sha256:1118ba9def608250250f4b3e3f48c62f4562ba16ca58ede491b6e7554bfa09ff \
--hash=sha256:12446827f43c7881decf2c126762e11425de5eb93b3b0d8b581344c16db7047a \
--hash=sha256:14905ed75c7a6edf423eb46c213ed3f4507c38115f1ed3c00f4ec9eafba50e58 \
--hash=sha256:15e593386ec6331e0ab4ac0795b7593f02ab2f4b30a698beb89fbdc34f92386a \
--hash=sha256:160ba087232c5c6e2a1e7ad08bd3a3f49b58c815be0504d8c8aacfb064491cd8 \
--hash=sha256:161a206c8f3511e2f5fafc9142a2cc25d7fe9a1ec5ad9b4ad2496a7c33e1c5d2 \
--hash=sha256:169fd0acd7a259f58f417e492e93d0e15fc87592cd1e971c8c533ad5703b5830 \
--hash=sha256:193b7c6834a06f722f0ce1ba685efe80881de7c3de31415513862f601097648c \
--hash=sha256:1a3903128f9e17a500618e80c68165c78c741ebb17dd1a0b44575f92c3c68b02 \
--hash=sha256:1d5bd666466c8f00a06886ce1397ba8b12371c1f1c6d1bef11013e9e0a1464a8 \
--hash=sha256:224a9269f133564109ce668213ef3cb32bc72ccf040b0b51c72a50e569e9dc9e \
--hash=sha256:236cace6c1903effd647ed46ce6dd5d76d54985fc36dafc5256032886736c85d \
--hash=sha256:249fbcee0a277c32a3ce36d8e36d50c27c968fdf969e0fbe342658d4e010fbc8 \
--hash=sha256:29d839829209f3c53f004e1de8c3113efce6d98029f044fa5cfee666253ee7e6 \
--hash=sha256:2c8982ee19ccecabbaeac1ba687bfef085a6352a8c64f821ce2f43e6d76a9298 \
--hash=sha256:2f30a5ab8902f93930dc6f627c4dd5da2703333287081c85cace0fc6e21c25af \
--hash=sha256:304e7e2418146ae4d0ef0e9ffa28f881f7874b45b4994cc2279b21b6e7ae50c8 \
--hash=sha256:32e5f3b8e32918bfbdd12eca62e49ab3031125c454b507127ad6ecbd86e62fca \
--hash=sha256:334b79ce9c08f26b4659a53f42892793948a613c46f1b583e985fd5a6bf1c149 \
--hash=sha256:33d19f0cde6838c81acffff25c7708e4adc7dd02896c9ec25c3939b1500a1778 \
--hash=sha256:3799e36d60a35162bb35b2246d8bb012192b7437dff807ef79c14e7352706306 \
--hash=sha256:42be5de7cc8c1edac55db92d82b68dc8e683b204d6f5414c5a51997a323d7081 \
--hash=sha256:44b3267cea873684af022822195298501568ed44d542f9a2d9bebc0212e99069 \
--hash=sha256:458d68d34fb74b906709735c927c029e62f7d06437a98af1b5b6258025223210 \
--hash=sha256:45cc13d398b6359a7708986386f72bd156ae781c3e83a68a6d4cee5af04b1ce9 \
--hash=sha256:4e7eaf9df15423d07b6050fb91f86c66307171b95ea53e2d87a7993b6d02c7f7 \
--hash=sha256:4fad420b14ae1970a1f322e8ae84a1d9d89375eb71e1b504060ab2d1bfe68f3c \
--hash=sha256:504b5116e2bd1821efd815941edff7535e93372a098e156bb9dffde30264e798 \
--hash=sha256:50e7e96a527488334379e05755b210b7da4a60fc5d6481938c1fa053e0c92184 \
--hash=sha256:51d27844763c273a122e08a3e86e7aefa54ee09fb672d96a645ece0454d8425e \
--hash=sha256:5253dcb0bfda7214523de58b002eb0090cb530d7c55993ce5f6d17faf953ece7 \
--hash=sha256:534efd2653ebc4f26fc0e47234e53bf0cb4715bb61f98c64d2774a278b58c846 \
--hash=sha256:560278c9975694e1f0bc50da187abf2cdc1e4890739ea33df2bc4a85eeef143e \
--hash=sha256:571452362d552de508c37191b6abbbb660028b8b418e2d68c20779e0bc8eaaa8 \
--hash=sha256:62b5f7910b639f3c1d122d408421317c351e213ca39c964ad4121f27916631c6 \
--hash=sha256:696639a73ca78a380acfaa0a1f6dd8220616a99074c05bba9ba8bb916914b224 \
--hash=sha256:6ccdeef4584450b6f0bddd5135354908dacad95425fcb629fe36d13e48b60f32 \
--hash=sha256:70364a097437dd0a90b31cd77f09f7387ad9ac60ef57590971f43b7fca3082a5 \
--hash=sha256:7117cb7d6ac7f2e985f3d18aa8a1728864097da1a677ffa69e970ca215baebf1 \
--hash=sha256:7467ad8b0eac0b28e52679e972b9b234b3de0ea5cee12eb50091d2b68145fe36 \
--hash=sha256:7d35d4cc9270944e95f9c88af757b0c9fc43f396917e143a5756608462c5223b \
--hash=sha256:7dda3091838206969c2b286f9832dff41e2da545b99d1cfaea9ebd8584d02708 \
--hash=sha256:853cc36e756ff673bf984e9044ccc8fad60b95a748915dddeab9488aea974c73 \
--hash=sha256:8722f72068b3e1156a4b2e1afde6810f1fc67155a9fa30a4b9d5b4bc46f18fb0 \
--hash=sha256:8c6c71cf92b09e5faa72ea2c68aa1f61c9ce11cb66fdc5069d712f4392ddfd00 \
--hash=sha256:903350bf44d7e4116b4d5898b30b15755d61dcd3161e3413a49c7db76f0bee5a \
--hash=sha256:91b53dea84415e8115506cc62e441a2b54537359c63d856d73cb1abe05af4c9a \
--hash=sha256:951be1eae7b47660412dc4938777a975ebc41936d64e28081bf2e584b47ec246 \
--hash=sha256:972b49f2fe1047b9249c958ec4fa1bdd2cf8ce305dc19d27546d5a38e57732d8 \
--hash=sha256:9a8625849387b9d558d528e263ecc9c0fbde86cfa5c2f0eef43fff480ae24d71 \
--hash=sha256:9cdbb1998da94607d5eec02566b9586f0e70d6438abf1b690261aac0edda7ab6 \
--hash=sha256:9e6d4d6ae1827b2f8c7200aaf7501c37cf3f3896c86a6aaf2566448397c823dd \
--hash=sha256:aab65121229c2ecdf4a31b793d99a6a0501225bd39b616e653c87b219ed34a49 \
--hash=sha256:ab98016541543692a37905871a5ffca59b16e08aacc3d7d10a27297b443f572d \
--hash=sha256:ad45f3bccfcb00868f2871dce02a755529838d2b86163ab8a246115e80cfb7d6 \
--hash=sha256:b43b78f9386d3d932a6ce5af4b45f393d2e93693ee18dc4800d30a8909df700e \
--hash=sha256:b66421f8878a0c82fc0c272a43e2121c8d4c67cb37429b764f0d5ad70b82993b \
--hash=sha256:ba034c8db4b264ef1601eb33cd23d87c5013b8fb48b8161debe2e5d3bd9156b0 \
--hash=sha256:bbdc5db2c98ac2bf1971ffa1410c87ca7a15800415f788971e8ba8520fc0fda9 \
--hash=sha256:bc0db93ad039fc2fe32ccd3dd0e0e70c4f3d6e37ae83f0a487e1aba939bd2fbd \
--hash=sha256:bf7c8ee4861d9ef5b1120abb75846828c811f932d63311596ad25fa168053e00 \
--hash=sha256:bf9596cba92ce7b1fd32c7b07c6e3212c7eed0edc271757e48bfcd2b54646452 \
--hash=sha256:c43395a3b7cc9862801a65c6994678484f186ce13c929abab44fb8a9e473a55a \
--hash=sha256:c46a76a599fcbf95f98755275c5527304cc4f1bb69919434c1e15544d7052910 \
--hash=sha256:ca23b41355ba95929e9505ee04e55495726aa2282003ed9b012d86f857d3e49b \
--hash=sha256:cd832bd9b6120d6074f39bdfbb3c80e416848b07ac72910f1c7f03131a6debc3 \
--hash=sha256:cfa6d61a76c77610ba9274c1a90a453062bdf6887858afbe214d18ad41cf6bde \
--hash=sha256:d8a0f0ab5453e409586b11ebe91c672040bc804ca98d03a656825f7890cbdf88 \
--hash=sha256:e91b1976358e17197157b405cab408a5f4e33310cda211c49fc6da7cffd0b2f0 \
--hash=sha256:ea057306ab469130167014b662643cfaed84651c792948891d003cf0039223a5 \
--hash=sha256:eda3dd46df535da787ffb9036b5140f941ecb91701717df91c9daf64cabef953 \
--hash=sha256:f03b1dbd4d9596dd84955bb40f7d885204d6aac0d56a919bb1e0ff2fb7e1735a \
--hash=sha256:fa9335674d7c819674467c7b46154196c51efbaf5f5715187fd366814ba3fa39
regex==2024.5.15 \
--hash=sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649 \
--hash=sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35 \
--hash=sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb \
--hash=sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68 \
--hash=sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5 \
--hash=sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133 \
--hash=sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0 \
--hash=sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d \
--hash=sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da \
--hash=sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f \
--hash=sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d \
--hash=sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53 \
--hash=sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa \
--hash=sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a \
--hash=sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890 \
--hash=sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67 \
--hash=sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c \
--hash=sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2 \
--hash=sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced \
--hash=sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741 \
--hash=sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f \
--hash=sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa \
--hash=sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf \
--hash=sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4 \
--hash=sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5 \
--hash=sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2 \
--hash=sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384 \
--hash=sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7 \
--hash=sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014 \
--hash=sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704 \
--hash=sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5 \
--hash=sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2 \
--hash=sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49 \
--hash=sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1 \
--hash=sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694 \
--hash=sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629 \
--hash=sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6 \
--hash=sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435 \
--hash=sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c \
--hash=sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835 \
--hash=sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e \
--hash=sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201 \
--hash=sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62 \
--hash=sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5 \
--hash=sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16 \
--hash=sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f \
--hash=sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1 \
--hash=sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f \
--hash=sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f \
--hash=sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145 \
--hash=sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3 \
--hash=sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed \
--hash=sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143 \
--hash=sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca \
--hash=sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9 \
--hash=sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa \
--hash=sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850 \
--hash=sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80 \
--hash=sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe \
--hash=sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656 \
--hash=sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388 \
--hash=sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1 \
--hash=sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294 \
--hash=sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3 \
--hash=sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d \
--hash=sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b \
--hash=sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40 \
--hash=sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600 \
--hash=sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c \
--hash=sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569 \
--hash=sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456 \
--hash=sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9 \
--hash=sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb \
--hash=sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e \
--hash=sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f \
--hash=sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d \
--hash=sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a \
--hash=sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a \
--hash=sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796
# via mkdocs-material
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# importlib-metadata
# importlib-resources
# The following packages are considered to be unsafe in a requirements file:
setuptools==69.5.1 \
--hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \
--hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32
setuptools==70.0.0 \
--hash=sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4 \
--hash=sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0
# via mkdocs-material
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
@ -682,7 +682,7 @@ webencodings==0.5.1 \
# via
# cssselect2
# tinycss2
zipp==3.18.1 \
--hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \
--hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715
zipp==3.18.2 \
--hash=sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059 \
--hash=sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e
# via pytablewriter

View file

@ -241,7 +241,7 @@ ModSecurity is integrated and enabled by default alongside the OWASP Core Rule S
| :-----------------------: | :-----: | :---------------------------------------------------------------------------------------------------- |
| `USE_MODSECURITY` | `yes` | When set to `yes`, ModSecurity will be enabled. |
| `USE_MODSECURITY_CRS` | `yes` | When set to `yes` and `USE_MODSECURITY` is also set to `yes`, the OWASP Core Rule Set will be loaded. |
| `MODSECURITY_CRS_VERSION` | `3` | Version of the OWASP Core Rule Set to use. |
| `MODSECURITY_CRS_VERSION` | `3` | Version of the OWASP Core Rule Set to use with ModSecurity (3, 4 or nightly). |
!!! warning "ModSecurity and the OWASP Core Rule Set"
**We strongly recommend keeping both ModSecurity and the OWASP Core Rule Set enabled**. The only downsides are the false positives that may occur. But they can be fixed with some efforts and the CRS team maintains a list of exclusions for common applications (e.g., WordPress, Nextcloud, Drupal, Cpanel, ...).
@ -250,6 +250,10 @@ You can choose between the following versions of the OWASP Core Rule Set :
- **3** : The version [v3.3.5](https://github.com/coreruleset/coreruleset/releases/tag/v3.3.5) of the OWASP Core Rule Set (***default***)
- **4** : The version [v4.2.0](https://github.com/coreruleset/coreruleset/releases/tag/v4.2.0) of the OWASP Core Rule Set
- **nightly** : The latest [nightly](https://github.com/coreruleset/coreruleset/releases/tag/nightly) build of the OWASP Core Rule Set which is updated every day
!!! example "OWASP Core Rule Set's nightly build"
The nightly build of the OWASP Core Rule Set is updated every day and contains the latest rules. It is recommended to use it in a staging environment before using it in production.
### Custom configurations

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
@ -368,16 +369,16 @@ STREAM support :white_check_mark:
Automatic creation, renewal and configuration of Let's Encrypt certificates using DNS challenges.
| Setting | Default | Context |Multiple| Description |
|----------------------------------|---------|---------|--------|---------------------------------------------------------------------------------------|
|`AUTO_LETS_ENCRYPT_DNS` |`no` |multisite|no |Activate automatic Let's Encrypt DNS. |
|`LETS_ENCRYPT_DNS_EMAIL` | |multisite|no |The email address to use for Let's Encrypt notifications. |
|`USE_LETS_ENCRYPT_DNS_STAGING` |`no` |multisite|no |Use the Let's Encrypt staging environment. |
|`LETS_ENCRYPT_DNS_PROVIDER` | |multisite|no |The DNS provider to use for DNS challenges. |
|`USE_LETS_ENCRYPT_DNS_WILDCARD` |`yes` |multisite|no |Create wildcard certificates for all domains using DNS challenges. |
|`LETS_ENCRYPT_DNS_PROPAGATION` |`default`|multisite|no |The time to wait for DNS propagation in seconds. |
|`LETS_ENCRYPT_DNS_CREDENTIAL_ITEM`| |multisite|yes |Configuration item that will be added to the credentials.ini file for the DNS provider.|
|`LETS_ENCRYPT_DNS_CLEAR_OLD_CERTS`|`no` |global |no |Clear old certificates when renewing. |
| Setting | Default | Context |Multiple| Description |
|----------------------------------|---------|---------|--------|----------------------------------------------------------------------------------------------------------------------------|
|`AUTO_LETS_ENCRYPT_DNS` |`no` |multisite|no |Activate automatic Let's Encrypt DNS. |
|`LETS_ENCRYPT_DNS_EMAIL` | |multisite|no |The email address to use for Let's Encrypt notifications. |
|`USE_LETS_ENCRYPT_DNS_STAGING` |`no` |multisite|no |Use the Let's Encrypt staging environment. |
|`LETS_ENCRYPT_DNS_PROVIDER` | |multisite|no |The DNS provider to use for DNS challenges. |
|`USE_LETS_ENCRYPT_DNS_WILDCARD` |`yes` |multisite|no |Create wildcard certificates for all domains using DNS challenges. |
|`LETS_ENCRYPT_DNS_PROPAGATION` |`default`|multisite|no |The time to wait for DNS propagation in seconds. |
|`LETS_ENCRYPT_DNS_CREDENTIAL_ITEM`| |multisite|yes |Configuration item that will be added to the credentials.ini file for the DNS provider (e.g. 'cloudflare_api_token 123456').|
|`LETS_ENCRYPT_DNS_CLEAR_OLD_CERTS`|`no` |global |no |Clear old certificates when renewing. |
## Limit
@ -448,14 +449,14 @@ STREAM support :x:
Management of the ModSecurity WAF.
| Setting | Default | Context |Multiple| Description |
|---------------------------------|--------------|---------|--------|------------------------------------------|
|`USE_MODSECURITY` |`yes` |multisite|no |Enable ModSecurity WAF. |
|`USE_MODSECURITY_CRS` |`yes` |multisite|no |Enable OWASP Core Rule Set. |
|`MODSECURITY_CRS_VERSION` |`3` |multisite|no |Version of the OWASP Core Rule Set to use.|
|`MODSECURITY_SEC_AUDIT_ENGINE` |`RelevantOnly`|multisite|no |SecAuditEngine directive of ModSecurity. |
|`MODSECURITY_SEC_RULE_ENGINE` |`On` |multisite|no |SecRuleEngine directive of ModSecurity. |
|`MODSECURITY_SEC_AUDIT_LOG_PARTS`|`ABCFHZ` |multisite|no |SecAuditLogParts directive of ModSecurity.|
| Setting | Default | Context |Multiple| Description |
|---------------------------------|--------------|---------|--------|-----------------------------------------------------------------------------|
|`USE_MODSECURITY` |`yes` |multisite|no |Enable ModSecurity WAF. |
|`USE_MODSECURITY_CRS` |`yes` |multisite|no |Enable OWASP Core Rule Set. |
|`MODSECURITY_CRS_VERSION` |`3` |multisite|no |Version of the OWASP Core Rule Set to use with ModSecurity (3, 4 or nightly).|
|`MODSECURITY_SEC_AUDIT_ENGINE` |`RelevantOnly`|multisite|no |SecAuditEngine directive of ModSecurity. |
|`MODSECURITY_SEC_RULE_ENGINE` |`On` |multisite|no |SecRuleEngine directive of ModSecurity. |
|`MODSECURITY_SEC_AUDIT_LOG_PARTS`|`ABCFHZ` |multisite|no |SecAuditLogParts directive of ModSecurity. |
## Monitoring <img src='../assets/img/pro-icon.svg' alt='crow pro icon' height='24px' width='24px' style='transform : translateY(3px);'> (PRO)

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

@ -61,7 +61,8 @@ RUN apk add --no-cache bash && \
chmod 660 INTEGRATION
# Fix CVEs
# There are no CVEs to fix for this image
RUN apk add --no-cache "busybox>=1.36.1-r17" "busybox-binsh>=1.36.1-r17" "ssl_client>=1.36.1-r17" # CVE-2023-42363 CVE-2023-42364 CVE-2023-42365 CVE-2023-42366
RUN apk add --no-cache "libcrypto3>=3.1.5-r0" "libssl3>=3.1.5-r0" # CVE-2024-4603
LABEL maintainer "Bunkerity <contact@bunkerity.com>"
LABEL version "1.5.7"

View file

@ -68,7 +68,7 @@ RUN apk add --no-cache openssl pcre bash python3 yajl geoip libxml2 libgd curl &
ln -s /proc/1/fd/1 /var/log/bunkerweb/access.log
# Fix CVEs
# There are no CVEs to fix for this image
RUN apk add --no-cache "busybox>=1.35.0-r30" "busybox-binsh>=1.35.0-r30" "ssl_client>=1.35.0-r30" # CVE-2023-42366
LABEL maintainer "Bunkerity <contact@bunkerity.com>"
LABEL version "1.5.7"

File diff suppressed because one or more lines are too long

View file

@ -36,11 +36,40 @@ server {
{% endif %}
{% if IS_LOADING == "yes" +%}
root /usr/share/bunkerweb/loading;
try_files /index.html =404;
etag off;
add_header Last-Modified "";
server_tokens off;
location / {
etag off;
add_header Last-Modified "";
server_tokens off;
default_type 'text/html';
root /usr/share/bunkerweb/loading;
content_by_lua_block {
local utils = require "bunkerweb.utils"
local rand = utils.rand
local subsystem = ngx.config.subsystem
local template
local render = nil
if subsystem == "http" then
template = require "resty.template"
render = template.render
end
local nonce_style = rand(16)
-- Override CSP header
ngx.header["Content-Security-Policy"] = "default-src 'none'; form-action 'self'; img-src 'self' data:; style-src 'self' 'nonce-"
.. nonce_style
.. "'; font-src 'self' data:; base-uri 'self'; require-trusted-types-for 'script';"
-- Remove server header
ngx.header["Server"] = nil
-- Render template
render("index.html", {
nonce_style = nonce_style
})
}
}
{% endif %}
# include core and plugins default-server configurations

View file

@ -11,7 +11,16 @@
"help": "The database URI, following the sqlalchemy format.",
"id": "database-uri",
"label": "The database URI",
"regex": "^(postgresql|mysql|mariadb|sqlite|oracle)(\\+[\\w\\-]+)?:.+$",
"regex": "^((postgresql|mysql|mariadb|sqlite)(\\+[\\w\\-]+)?:.+)?$",
"type": "text"
},
"DATABASE_URI_READONLY": {
"context": "global",
"default": "",
"help": "The database URI for read-only operations, it can also serve as a fallback if the main database is down. Following the sqlalchemy format.",
"id": "database-uri-readonly",
"label": "The database URI for read-only operations",
"regex": "^((postgresql|mysql|mariadb|sqlite)(\\+[\\w\\-]+)?:.+)?$",
"type": "text"
},
"DATABASE_LOG_LEVEL": {

View file

@ -1,7 +1,9 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local ngx = ngx
local rand = utils.rand
local subsystem = ngx.config.subsystem
local tostring = tostring
@ -84,12 +86,28 @@ function errors:log()
end
function errors:render_template(code)
local nonce_script = rand(16)
local nonce_style = rand(16)
-- Override headers
local header = "Content-Security-Policy"
if self.variables["CONTENT_SECURITY_POLICY_REPORT_ONLY"] == "yes" then
header = header .. "-Report-Only"
end
ngx.header[header] = "default-src 'none'; form-action 'self'; script-src 'strict-dynamic' 'nonce-"
.. nonce_script
.. "' 'unsafe-inline' http: https:; img-src 'self' data:; style-src 'self' 'nonce-"
.. nonce_style
.. "'; font-src 'self' data:; base-uri 'self'; require-trusted-types-for 'script';"
-- Render template
render("error.html", {
title = code .. " - " .. self.default_errors[code].title,
error_title = self.default_errors[code].title,
error_code = code,
error_text = self.default_errors[code].text,
nonce_script = nonce_script,
nonce_style = nonce_style,
})
end

File diff suppressed because one or more lines are too long

View file

@ -9,7 +9,7 @@ from stat import S_IEXEC
from sys import exit as sys_exit, path as sys_path
from threading import Lock
from uuid import uuid4
from json import JSONDecodeError, loads
from json import JSONDecodeError, load as json_load, loads
from shutil import copytree, rmtree
from tarfile import open as tar_open
from traceback import format_exc
@ -176,26 +176,27 @@ try:
rmtree(plugin_path, ignore_errors=True)
continue
plugin_file = loads(plugin_path.joinpath("plugin.json").read_text(encoding="utf-8"))
with BytesIO() as plugin_content:
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
tar.add(plugin_path, arcname=plugin_path.name)
plugin_content.seek(0)
value = plugin_content.getvalue()
tar.add(plugin_path, arcname=plugin_path.name, recursive=True)
plugin_content.seek(0, 0)
plugin_file.update(
{
"type": "external",
"page": plugin_path.joinpath("ui").is_dir(),
"method": "scheduler",
"data": value,
"checksum": bytes_hash(value, algorithm="sha256"),
}
)
with plugin_path.joinpath("plugin.json").open("r", encoding="utf-8") as f:
plugin_data = json_load(f)
external_plugins.append(plugin_file)
external_plugins_ids.append(plugin_file["id"])
checksum = bytes_hash(plugin_content, algorithm="sha256")
plugin_data.update(
{
"type": "external",
"page": plugin_path.joinpath("ui").is_dir(),
"method": "scheduler",
"data": plugin_content.getvalue(),
"checksum": checksum,
}
)
external_plugins.append(plugin_data)
external_plugins_ids.append(plugin_data["id"])
lock = Lock()

View file

@ -1,9 +1,36 @@
{% if IS_LOADING != "yes" and DISABLE_DEFAULT_SERVER == "no" +%}
root /usr/share/bunkerweb/core/misc/files;
location / {
try_files /default.html =404;
etag off;
add_header Last-Modified "";
server_tokens off;
default_type 'text/html';
root /usr/share/bunkerweb/core/misc/files;
content_by_lua_block {
local utils = require "bunkerweb.utils"
local rand = utils.rand
local subsystem = ngx.config.subsystem
local template
local render = nil
if subsystem == "http" then
template = require "resty.template"
render = template.render
end
local nonce_style = rand(16)
-- Override CSP header
ngx.header["Content-Security-Policy"] = "default-src 'none'; form-action 'self'; img-src 'self' data:; style-src 'self' 'nonce-"
.. nonce_style
.. "'; font-src 'self' data:; base-uri 'self'; require-trusted-types-for 'script';"
-- Remove server header
ngx.header["Server"] = nil
-- Render template
render("default.html", {
nonce_style = nonce_style,
})
}
}
{% endif %}

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,4 @@
{% set os_path = import("os.path") %}
# process rules with disruptive actions
SecRuleEngine {{ MODSECURITY_SEC_RULE_ENGINE }}
@ -66,7 +67,16 @@ SecAuditLog /var/log/bunkerweb/modsec_audit.log
# include OWASP CRS configurations
{% if USE_MODSECURITY_CRS == "yes" %}
{% if MODSECURITY_CRS_VERSION == "nightly" %}
{% if os_path.isfile("/var/cache/bunkerweb/modsecurity/crs/crs-setup-nightly.conf") %}
include /var/cache/bunkerweb/modsecurity/crs/crs-setup-nightly.conf
{% else %}
# fallback to the default CRS setup as the nightly one is not available
include /usr/share/bunkerweb/core/modsecurity/files/crs-setup-v3.conf
{% endif %}
{% else %}
include /usr/share/bunkerweb/core/modsecurity/files/crs-setup-v{{ MODSECURITY_CRS_VERSION }}.conf
{% endif %}
# custom CRS configurations before loading rules (e.g. exclusions)
{% if is_custom_conf("/etc/bunkerweb/configs/modsec-crs") %}
@ -100,7 +110,16 @@ SecRule ENV:is_whitelisted "yes" "id:1000,phase:1,allow,nolog,ctl:ruleEngine=Off
{% endif +%}
# include OWASP CRS rules
{% if MODSECURITY_CRS_VERSION == "nightly" %}
{% if os_path.exists("/var/cache/bunkerweb/modsecurity/crs/crs-nightly/rules") %}
include /var/cache/bunkerweb/modsecurity/crs/crs-nightly/rules/*.conf
{% else %}
# fallback to the default CRS setup as the nightly one is not available
include /usr/share/bunkerweb/core/modsecurity/files/coreruleset-v3/rules/*.conf
{% endif %}
{% else %}
include /usr/share/bunkerweb/core/modsecurity/files/coreruleset-v{{ MODSECURITY_CRS_VERSION }}/rules/*.conf
{% endif %}
{% endif +%}
# custom rules after loading the CRS

View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
from io import BytesIO
from os import getenv, sep
from os.path import join
from pathlib import Path
from re import MULTILINE, search
from shutil import rmtree
from sys import exit as sys_exit, path as sys_path
from tarfile import open as tar_open
from threading import Lock
from traceback import format_exc
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 requests import RequestException, get
from logger import setup_logger # type: ignore
from jobs import Job # type: ignore
LOGGER = setup_logger("MODSECURITY.coreruleset-nightly", getenv("LOG_LEVEL", "INFO"))
status = 0
LOCK = Lock()
CRS_PATH = Path(sep, "var", "cache", "bunkerweb", "modsecurity", "crs")
try:
# * Check if we're using the nightly version of the Core Rule Set (CRS)
use_nightly_crs = False
if getenv("MODSECURITY_CRS_VERSION", "3") == "nightly":
use_nightly_crs = True
elif getenv("MULTISITE", "no") == "yes":
for first_server in getenv("SERVER_NAME", "").split(" "):
if first_server and getenv(f"{first_server}_MODSECURITY_CRS_VERSION", getenv("MODSECURITY_CRS_VERSION", "3")) == "nightly":
use_nightly_crs = True
break
if not use_nightly_crs:
LOGGER.info("Core Rule Set (CRS) nightly is not being used, skipping download...")
sys_exit(0)
JOB = Job(LOGGER)
LOGGER.info("Checking if Core Rule Set (CRS) nightly needs to be downloaded...")
commit_hash = JOB.get_cache("commit_hash")
resp = get("https://github.com/coreruleset/coreruleset/releases/tag/nightly", timeout=5)
resp.raise_for_status()
content = resp.text
page_commit_hash = search(r"/coreruleset/coreruleset/commit/(?P<hash>[0-9a-f]{40})", content, MULTILINE)
if page_commit_hash is None:
LOGGER.error("Failed to find commit hash on Core Rule Set (CRS) nightly page.")
sys_exit(2)
page_commit_hash = page_commit_hash.group("hash")
LOGGER.debug(f"Page commit hash: {page_commit_hash}")
if commit_hash:
LOGGER.debug(f"Current commit hash: {commit_hash.decode()}")
if commit_hash.decode() == page_commit_hash:
LOGGER.info("Core Rule Set (CRS) nightly is up to date.")
sys_exit(0)
LOGGER.info("Core Rule Set (CRS) nightly is outdated.")
cached, err = JOB.cache_file("commit_hash", page_commit_hash.encode())
if not cached:
LOGGER.error(f"Failed to cache the Core Rule Set (CRS) nightly commit hash: {err}")
status = 2
LOGGER.info("Downloading Core Rule Set (CRS) nightly tarball...")
file_content = BytesIO()
try:
with get("https://github.com/coreruleset/coreruleset/archive/refs/tags/nightly.tar.gz", stream=True, timeout=5) as resp:
resp.raise_for_status()
for chunk in resp.iter_content(chunk_size=4 * 1024):
if chunk:
file_content.write(chunk)
except RequestException:
LOGGER.exception("Failed to download Core Rule Set (CRS) nightly tarball.")
sys_exit(2)
file_content.seek(0)
rmtree(CRS_PATH, ignore_errors=True)
CRS_PATH.mkdir(parents=True, exist_ok=True)
LOGGER.info("Extracting Core Rule Set (CRS) nightly tarball...")
with tar_open(fileobj=file_content, mode="r:gz") as tar_file:
try:
tar_file.extractall(CRS_PATH, filter="data")
except TypeError:
tar_file.extractall(CRS_PATH)
# * Rename the extracted folder to "crs-nightly"
extracted_folder = next(CRS_PATH.iterdir())
extracted_folder.rename(CRS_PATH.joinpath("crs-nightly"))
# * Move and rename the example configuration file to "crs-setup-nightly.conf"
example_conf = CRS_PATH.joinpath("crs-nightly", "crs-setup.conf.example")
example_conf.rename(CRS_PATH.joinpath("crs-setup-nightly.conf"))
cached, err = JOB.cache_dir(CRS_PATH)
if not cached:
LOGGER.error(f"Error while saving Core Rule Set (CRS) nightly data to db cache: {err}")
else:
LOGGER.info("Successfully saved Core Rule Set (CRS) nightly data to db cache.")
status = 1
except SystemExit as e:
status = e.code
except:
status = 2
LOGGER.error(f"Exception while running coreruleset-nightly.py :\n{format_exc()}")
sys_exit(status)

View file

@ -26,12 +26,12 @@
"MODSECURITY_CRS_VERSION": {
"context": "multisite",
"default": "3",
"help": "Version of the OWASP Core Rule Set to use.",
"help": "Version of the OWASP Core Rule Set to use with ModSecurity (3, 4 or nightly).",
"id": "modsecurity-crs-version",
"label": "Core Rule Set Version",
"regex": "^(3|4)$",
"regex": "^(3|4|nightly)$",
"type": "select",
"select": ["3", "4"]
"select": ["3", "4", "nightly"]
},
"MODSECURITY_SEC_AUDIT_ENGINE": {
"context": "multisite",
@ -62,5 +62,13 @@
"regex": "^A(([B-K])(?!.*\\2))+Z$",
"type": "text"
}
}
},
"jobs": [
{
"name": "coreruleset-nightly",
"file": "coreruleset-nightly.py",
"every": "day",
"reload": true
}
]
}

View file

@ -10,7 +10,7 @@ from stat import S_IEXEC
from sys import exit as sys_exit, path as sys_path
from threading import Lock
from uuid import uuid4
from json import JSONDecodeError, load, loads
from json import JSONDecodeError, load as json_load, loads
from shutil import copytree, rmtree
from tarfile import open as tar_open
from traceback import format_exc
@ -152,9 +152,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 (
@ -188,7 +188,7 @@ try:
for chunk in resp.iter_content(chunk_size=8192):
resp_content.write(chunk)
resp_content.seek(0)
resp_data = load(resp_content)
resp_data = json_load(resp_content)
clean = resp_data.get("action") == "clean"
@ -212,7 +212,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:
@ -280,26 +280,27 @@ try:
rmtree(plugin_path, ignore_errors=True)
continue
plugin_file = loads(plugin_path.joinpath("plugin.json").read_text(encoding="utf-8"))
with BytesIO() as plugin_content:
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
tar.add(plugin_path, arcname=plugin_path.name)
plugin_content.seek(0)
value = plugin_content.getvalue()
tar.add(plugin_path, arcname=plugin_path.name, recursive=True)
plugin_content.seek(0, 0)
plugin_file.update(
{
"type": "pro",
"page": plugin_path.joinpath("ui").is_dir(),
"method": "scheduler",
"data": value,
"checksum": bytes_hash(value, algorithm="sha256"),
}
)
with plugin_path.joinpath("plugin.json").open("r", encoding="utf-8") as f:
plugin_data = json_load(f)
pro_plugins.append(plugin_file)
pro_plugins_ids.append(plugin_file["id"])
checksum = bytes_hash(plugin_content, algorithm="sha256")
plugin_data.update(
{
"type": "pro",
"page": plugin_path.joinpath("ui").is_dir(),
"method": "scheduler",
"data": plugin_content.getvalue(),
"checksum": checksum,
}
)
pro_plugins.append(plugin_data)
pro_plugins_ids.append(plugin_data["id"])
lock = Lock()

View file

@ -68,9 +68,13 @@ def set_sqlite_pragma(dbapi_connection, _):
class Database:
DB_STRING_RX = re_compile(r"^(?P<database>(mariadb|mysql)(\+pymysql)?|sqlite(\+pysqlite)?|postgresql(\+psycopg)?):/+(?P<path>/[^\s]+)")
def __init__(self, logger: Logger, sqlalchemy_string: Optional[str] = None, *, ui: bool = False, pool: Optional[bool] = None, log: bool = True) -> None:
def __init__(
self, logger: Logger, sqlalchemy_string: Optional[str] = None, *, ui: bool = False, pool: Optional[bool] = None, log: bool = True, **kwargs
) -> None:
"""Initialize the database"""
self.logger = logger
self.readonly = False
self.last_fallback = None
if pool:
self.logger.warning("The pool parameter is deprecated, it will be removed in the next version")
@ -81,6 +85,16 @@ class Database:
if not sqlalchemy_string:
sqlalchemy_string = getenv("DATABASE_URI", "sqlite:////var/lib/bunkerweb/db.sqlite3")
sqlalchemy_string_readonly = getenv("DATABASE_URI_READONLY", "")
if not sqlalchemy_string:
sqlalchemy_string = sqlalchemy_string_readonly or "sqlite:////var/lib/bunkerweb/db.sqlite3"
if sqlalchemy_string == sqlalchemy_string_readonly:
self.readonly = True
if log:
self.logger.warning("The database connection is set to read-only, the changes will not be saved")
match = self.DB_STRING_RX.search(sqlalchemy_string)
if not match:
self.logger.error(f"Invalid database string provided: {sqlalchemy_string}, exiting...")
@ -105,19 +119,20 @@ class Database:
) # ? This is strongly recommended as psycopg is the new way to connect to postgresql
self.database_uri = sqlalchemy_string
self.database_uri_readonly = sqlalchemy_string_readonly
error = False
engine_kwargs = {
self._engine_kwargs = {
"future": True,
"poolclass": QueuePool,
"pool_pre_ping": True,
"pool_recycle": 1800,
"pool_size": 40,
"max_overflow": 20,
}
} | kwargs
try:
self.sql_engine = create_engine(sqlalchemy_string, **engine_kwargs)
self.sql_engine = create_engine(sqlalchemy_string, **self._engine_kwargs)
except ArgumentError:
self.logger.error(f"Invalid database URI: {sqlalchemy_string}")
error = True
@ -139,29 +154,43 @@ class Database:
while not_connected:
try:
with self.sql_engine.connect() as conn:
conn.execute(text("CREATE TABLE IF NOT EXISTS test (id INT)"))
conn.execute(text("DROP TABLE test"))
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"))
not_connected = False
except (OperationalError, DatabaseError) as e:
if retries <= 0:
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:
self.logger.warning("The database is read-only, waiting for it to become writable. Retrying in 5 seconds ...")
self.sql_engine.dispose(close=True)
self.sql_engine = create_engine(sqlalchemy_string, **engine_kwargs)
self.sql_engine = create_engine(sqlalchemy_string, **self._engine_kwargs)
if "Unknown table" in str(e):
not_connected = False
continue
elif log:
self.logger.warning(
"Can't connect to database, retrying in 5 seconds ...",
)
self.logger.warning("Can't connect to database, retrying in 5 seconds ...")
retries -= 1
sleep(5)
except BaseException:
@ -170,7 +199,7 @@ class Database:
self.suffix_rx = re_compile(r"_\d+$")
if log:
self.logger.info("✅ Database connection established")
self.logger.info(f"✅ Database connection established{'' if not self.readonly else ' in read-only mode'}")
def __del__(self) -> None:
"""Close the database"""
@ -180,6 +209,26 @@ class Database:
if self.sql_engine:
self.sql_engine.dispose()
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
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:
try:
@ -188,20 +237,59 @@ class Database:
self.logger.error("The database engine is not initialized")
_exit(1)
with self.sql_engine.connect() as conn:
session_factory = sessionmaker(bind=conn, autoflush=True, expire_on_commit=False)
session = scoped_session(session_factory)
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
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
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:
except BaseException as e:
if session:
session.rollback()
raise
finally:
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.last_fallback = datetime.now()
raise
finally:
if session:
session.remove()
def set_autoconf_load(self, value: bool = True) -> str:
"""Set the autoconf_loaded value"""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
metadata = session.query(Metadata).get(1)
@ -227,6 +315,9 @@ class Database:
def set_scheduler_first_start(self, value: bool = False) -> str:
"""Set the scheduler_first_start value"""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
metadata = session.query(Metadata).get(1)
@ -243,6 +334,9 @@ class Database:
def set_pro_metadata(self, data: Dict[Literal["is_pro", "pro_expire", "pro_status", "pro_overlapped", "pro_services"], Any] = {}) -> str:
"""Set the pro metadata values"""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
metadata = session.query(Metadata).get(1)
@ -287,6 +381,9 @@ class Database:
def initialize_db(self, version: str, integration: str = "Unknown") -> str:
"""Initialize the database"""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
metadata = session.query(Metadata).get(1)
@ -400,6 +497,9 @@ class Database:
"instances",
]
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
metadata = session.query(Metadata).get(1)
@ -426,6 +526,10 @@ class Database:
def init_tables(self, default_plugins: List[dict], bunkerweb_version: str) -> Tuple[bool, str]:
"""Initialize the database tables and return the result"""
if self.readonly:
return False, "The database is read-only, the changes will not be saved"
assert self.sql_engine is not None, "The database engine is not initialized"
inspector = inspect(self.sql_engine)
@ -846,6 +950,9 @@ class Database:
"""Save the config in the database"""
to_put = []
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
# Delete all the old config
session.query(Global_values).filter(Global_values.method == method).delete()
session.query(Services_settings).filter(Services_settings.method == method).delete()
@ -1098,6 +1205,9 @@ class Database:
"""Save the custom configs in the database"""
message = ""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
# Delete all the old config
session.query(Custom_configs).filter(Custom_configs.method == method).delete()
@ -1303,6 +1413,9 @@ class Database:
def update_job(self, plugin_id: str, job_name: str, success: bool) -> str:
"""Update the job last_run in the database"""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
job = session.query(Jobs).filter_by(plugin_id=plugin_id, name=job_name).first()
if not job:
@ -1318,7 +1431,7 @@ class Database:
return ""
def delete_job_cache(self, file_name: str, *, job_name: Optional[str] = None, service_id: Optional[str] = None):
def delete_job_cache(self, file_name: str, *, job_name: Optional[str] = None, service_id: Optional[str] = None) -> str:
job_name = job_name or argv[0].replace(".py", "")
filters = {"file_name": file_name}
if job_name:
@ -1327,7 +1440,15 @@ class Database:
filters["service_id"] = service_id
with self.__db_session() as session:
session.query(Jobs_cache).filter_by(**filters).delete()
if self.readonly:
return "The database is read-only, the changes will not be saved"
try:
session.query(Jobs_cache).filter_by(**filters).delete()
except BaseException:
return format_exc()
return ""
def upsert_job_cache(
self,
@ -1342,6 +1463,9 @@ class Database:
job_name = job_name or argv[0].replace(".py", "")
service_id = service_id or None
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
cache = session.query(Jobs_cache).filter_by(job_name=job_name, service_id=service_id, file_name=file_name).first()
if not cache:
@ -1372,6 +1496,9 @@ class Database:
to_put = []
changes = False
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
db_plugins = session.query(Plugins).with_entities(Plugins.id).filter_by(type=_type).all()
db_ids = []
@ -1422,6 +1549,10 @@ class Database:
)
if db_plugin:
if plugin["method"] not in (db_plugin.method, "autoconf"):
self.logger.warning(f'Plugin "{plugin["id"]}" already exists, but the method is different, skipping update')
continue
if db_plugin.type not in ("external", "pro"):
self.logger.warning(
f"Plugin \"{plugin['id']}\" is not {_type}, skipping update (updating a non-external or non-pro plugin is forbidden for security reasons)", # noqa: E501
@ -1896,7 +2027,6 @@ class Database:
"method": plugin.method,
"page": page is not None,
"settings": {},
"bwcli": {},
"checksum": plugin.checksum,
} | ({"data": plugin.data} if with_data else {})
@ -1932,6 +2062,8 @@ class Database:
]
for command in session.query(BwcliCommands).with_entities(BwcliCommands.name, BwcliCommands.file_name).filter_by(plugin_id=plugin.id):
if "bwcli" not in data:
data["bwcli"] = {}
data["bwcli"][command.name] = command.file_name
plugins.append(data)
@ -2062,6 +2194,9 @@ class Database:
def add_instance(self, hostname: str, port: int, server_name: str, changed: Optional[bool] = True) -> str:
"""Add instance."""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
db_instance = session.query(Instances).with_entities(Instances.hostname).filter_by(hostname=hostname).first()
if db_instance is not None:
@ -2086,6 +2221,9 @@ class Database:
"""Update instances."""
to_put = []
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
session.query(Instances).delete()
for instance in instances:
@ -2175,7 +2313,11 @@ class Database:
def create_ui_user(self, username: str, password: bytes, *, secret_token: Optional[str] = None, method: str = "manual") -> str:
"""Create ui user."""
with self.__db_session() as session:
if self.get_ui_user():
if self.readonly:
return "The database is read-only, the changes will not be saved"
user = session.query(Users).filter_by(id=1).first()
if user:
return "User already exists"
session.add(Users(id=1, username=username, password=password.decode("utf-8"), secret_token=secret_token, method=method))
@ -2192,6 +2334,9 @@ class Database:
) -> str:
"""Update ui user."""
with self.__db_session() as session:
if self.readonly:
return "The database is read-only, the changes will not be saved"
user = session.query(Users).filter_by(id=1).first()
if not user:
return "User not found"

View file

@ -1,4 +1,4 @@
cryptography==42.0.7
psycopg[c,pool]==3.1.19
PyMySQL==1.1.0
PyMySQL==1.1.1
sqlalchemy==2.0.30

View file

@ -167,9 +167,9 @@ pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
pymysql==1.1.0 \
--hash=sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96 \
--hash=sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7
pymysql==1.1.1 \
--hash=sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c \
--hash=sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0
# via -r requirements.armv7.in
sqlalchemy==2.0.30 \
--hash=sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7 \

View file

@ -1,4 +1,4 @@
cryptography==42.0.7
psycopg[binary,pool]==3.1.19
PyMySQL==1.1.0
PyMySQL==1.1.1
sqlalchemy==2.0.30

View file

@ -229,9 +229,9 @@ pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
pymysql==1.1.0 \
--hash=sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96 \
--hash=sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7
pymysql==1.1.1 \
--hash=sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c \
--hash=sha256:e127611aaf2b417403c60bf4dc570124aeb4a57f5f37b8e95ae399a42f904cd0
# via -r requirements.in
sqlalchemy==2.0.30 \
--hash=sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7 \

View file

@ -277,9 +277,9 @@ redis==5.0.4 \
--hash=sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91 \
--hash=sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61
# via -r requirements.in
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# docker
# kubernetes

View file

@ -2,6 +2,6 @@ pip==24.0
pip-compile-multi==2.6.3
pip-tools==7.4.1
pip-upgrader==1.4.15
setuptools==69.5.1
setuptools==70.0.0
tomli==2.0.1
wheel==0.43.0

View file

@ -157,15 +157,15 @@ pyproject-hooks==1.1.0 \
# via
# build
# pip-tools
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via
# -r requirements-deps.in
# pip-tools
setuptools==69.5.1 \
--hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \
--hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32
setuptools==70.0.0 \
--hash=sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4 \
--hash=sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0
# via pip-upgrader
terminaltables==3.1.10 \
--hash=sha256:ba6eca5cb5ba02bba4c9f4f985af80c54ec3dccf94cfcd190154386255e47543 \
@ -192,9 +192,9 @@ wheel==0.43.0 \
# via
# -r requirements-deps.in
# pip-tools
zipp==3.18.1 \
--hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \
--hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715
zipp==3.18.2 \
--hash=sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059 \
--hash=sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e
# via
# -r requirements-deps.in
# pip-tools

View file

@ -1,4 +1,4 @@
pip==24.0
pip-tools==7.4.1
setuptools==69.5.1
setuptools==70.0.0
wheel==0.43.0

View file

@ -36,9 +36,9 @@ pyproject-hooks==1.1.0 \
# via
# -r requirements.in
# pip-tools
setuptools==69.5.1 \
--hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \
--hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32
setuptools==70.0.0 \
--hash=sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4 \
--hash=sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0
# via
# build
# pip-tools
@ -54,9 +54,9 @@ wheel==0.43.0 \
# via
# -r requirements.in
# pip-tools
zipp==3.18.1 \
--hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \
--hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715
zipp==3.18.2 \
--hash=sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059 \
--hash=sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e
# via
# -r requirements.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

@ -90,10 +90,11 @@ COPY src/linux/RPM-GPG-KEY-centosofficial /etc/pki/rpm-gpg/RPM-GPG-KEY-centosoff
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
# Install fpm
RUN dnf install -y wget redhat-rpm-config rpm-build yum-utils && \
RUN dnf install -y wget redhat-rpm-config rpm-build yum-utils gcc make && \
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
rpm -Uvh epel-release*rpm && \
dnf module -y reset ruby && dnf module -y enable ruby:3.1 && dnf module -y install ruby:3.1/common && \
dnf install -y ruby-devel && \
gem install fpm
# Setup BW

View file

@ -81,10 +81,11 @@ COPY --from=builder --chown=0:101 /usr/share/bunkerweb /usr/share/bunkerweb
WORKDIR /usr/share/bunkerweb
# Install fpm
RUN dnf install -y wget redhat-rpm-config rpm-build yum-utils && \
RUN dnf install -y wget redhat-rpm-config rpm-build yum-utils gcc make && \
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && \
rpm -Uvh epel-release*rpm && \
dnf module -y reset ruby && dnf module -y enable ruby:3.1 && dnf module -y install ruby:3.1/common && \
dnf install -y ruby-devel && \
gem install fpm
# Setup BW

View file

@ -69,7 +69,7 @@ COPY --chown=root:scheduler --chmod=770 src/bw/misc/asn.mmdb /var/tmp/bunkerweb/
COPY --chown=root:scheduler --chmod=770 src/bw/misc/country.mmdb /var/tmp/bunkerweb/country.mmdb
# Fix CVEs
# There are no CVEs to fix for this image
RUN apk add --no-cache "busybox>=1.36.1-r17" "busybox-binsh>=1.36.1-r17" "ssl_client>=1.36.1-r17" # CVE-2023-42363 CVE-2023-42364 CVE-2023-42365 CVE-2023-42366
LABEL maintainer "Bunkerity <contact@bunkerity.com>"
LABEL version "1.5.7"

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from contextlib import suppress
from copy import deepcopy
from functools import partial
from glob import glob
@ -45,7 +46,7 @@ class JobScheduler(ApiCaller):
super().__init__(apis or [])
self.__logger = logger or setup_logger("Scheduler", getenv("LOG_LEVEL", "INFO"))
self.__integration = integration
self.__db = db or Database(self.__logger)
self.db = db or Database(self.__logger)
self.__env = env or {}
self.__env.update(environ)
self.__jobs = self.__get_jobs()
@ -76,7 +77,7 @@ class JobScheduler(ApiCaller):
apis = []
try:
with self.__thread_lock:
instances = self.__db.get_instances()
instances = self.db.get_instances()
for instance in instances:
api = API(f"http://{instance['hostname']}:{instance['port']}", host=instance["server_name"])
apis.append(api)
@ -204,7 +205,7 @@ class JobScheduler(ApiCaller):
def __update_job(self, plugin: str, name: str, success: bool):
with self.__thread_lock:
err = self.__db.update_job(plugin, name, success)
err = self.db.update_job(plugin, name, success)
if not err:
self.__logger.info(f"Successfully updated database for the job {name} from plugin {plugin}")
@ -226,14 +227,35 @@ class JobScheduler(ApiCaller):
def run_pending(self) -> bool:
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,),)))
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
self.__job_success = True
self.__job_reload = False
for thread in threads:
thread.start()
@ -267,6 +289,24 @@ class JobScheduler(ApiCaller):
return success
def run_once(self) -> bool:
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")
return True
threads = []
self.__job_success = True
self.__job_reload = False
@ -290,6 +330,24 @@ class JobScheduler(ApiCaller):
return ret
def run_single(self, job_name: str) -> bool:
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")
return True
if self.__lock:
self.__lock.acquire()

View file

@ -173,9 +173,22 @@ def generate_external_plugins(plugins: List[Dict[str, Any]], *, original_path: U
pro = "pro" in original_path.parts
# Remove old external/pro plugins files
logger.info(f"Removing old {'pro ' if pro else ''}external 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("*"):
with suppress(StopIteration, IndexError):
index = next(i for i, plugin in enumerate(plugins) if plugin["id"] == file.name)
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 ...")
if file.is_symlink() or file.is_file():
with suppress(OSError):
file.unlink()
@ -186,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")
@ -272,33 +288,32 @@ def api_to_instance(api):
}
def run_in_slave_mode(db: Database, dotenv_env: Dict[str, Any]):
# Instantiate db
db = Database(logger, sqlalchemy_string=dotenv_env.get("DATABASE_URI", getenv("DATABASE_URI", None)))
def run_in_slave_mode():
assert SCHEDULER is not None
# Wait for init
while not db.is_initialized():
while not SCHEDULER.db.is_initialized():
logger.warning("Database is not initialized, retrying in 5s ...")
sleep(5)
# Wait for first config
env = db.get_config()
while not db.is_first_config_saved() or not env:
env = SCHEDULER.db.get_config()
while not SCHEDULER.db.is_first_config_saved() or not env:
logger.warning("Database doesn't have any config saved yet, retrying in 5s ...")
sleep(5)
env = db.get_config()
env = SCHEDULER.db.get_config()
# Download plugins
pro_plugins = db.get_plugins(_type="pro", with_data=True)
pro_plugins = SCHEDULER.db.get_plugins(_type="pro", with_data=True)
generate_external_plugins(pro_plugins, original_path=PRO_PLUGINS_PATH)
external_plugins = db.get_plugins(_type="external", with_data=True)
external_plugins = SCHEDULER.db.get_plugins(_type="external", with_data=True)
generate_external_plugins(external_plugins)
# Download custom configs
generate_custom_configs(db.get_custom_configs())
generate_custom_configs(SCHEDULER.db.get_custom_configs())
# Download caches
generate_caches(pro_plugins + external_plugins, db)
generate_caches(pro_plugins + external_plugins, SCHEDULER.db)
# Gen config
content = ""
@ -353,18 +368,18 @@ if __name__ == "__main__":
nginx_variables_path = Path(sep, "etc", "nginx", "variables.env")
dotenv_env = dotenv_values(str(tmp_variables_path))
db = Database(logger, sqlalchemy_string=dotenv_env.get("DATABASE_URI", getenv("DATABASE_URI", None)))
SCHEDULER = JobScheduler(environ, logger, INTEGRATION, db=Database(logger, sqlalchemy_string=dotenv_env.get("DATABASE_URI", getenv("DATABASE_URI", None)))) # type: ignore
if SLAVE_MODE:
run_in_slave_mode(db, dotenv_env)
run_in_slave_mode()
stop(1)
if INTEGRATION in ("Swarm", "Kubernetes", "Autoconf"):
while not db.is_initialized():
while not SCHEDULER.db.is_initialized():
logger.warning("Database is not initialized, retrying in 5s ...")
sleep(5)
while not db.is_autoconf_loaded():
while not SCHEDULER.db.is_autoconf_loaded():
logger.warning("Autoconf is not loaded yet in the database, retrying in 5s ...")
sleep(5)
@ -373,8 +388,8 @@ if __name__ == "__main__":
or not tmp_variables_path.exists()
or not nginx_variables_path.exists()
or (tmp_variables_path.read_text(encoding="utf-8") != nginx_variables_path.read_text(encoding="utf-8"))
or db.is_initialized()
and db.get_config() != dotenv_env
or SCHEDULER.db.is_initialized()
and SCHEDULER.db.get_config() != dotenv_env
):
# run the config saver
proc = subprocess_run(
@ -393,19 +408,19 @@ if __name__ == "__main__":
logger.error("Config saver failed, configuration will not work as expected...")
if INTEGRATION not in ("Swarm", "Kubernetes", "Autoconf"):
while not db.is_initialized():
while not SCHEDULER.db.is_initialized():
logger.warning("Database is not initialized, retrying in 5s ...")
sleep(5)
env = db.get_config()
while not db.is_first_config_saved() or not env:
env = SCHEDULER.db.get_config()
while not SCHEDULER.db.is_first_config_saved() or not env:
logger.warning("Database doesn't have any config saved yet, retrying in 5s ...")
sleep(5)
env = db.get_config()
env = SCHEDULER.db.get_config()
env = db.get_config()
env = SCHEDULER.db.get_config()
env["DATABASE_URI"] = db.database_uri
env["DATABASE_URI"] = SCHEDULER.db.database_uri
# Override instances if needed
override_instances = env.get("OVERRIDE_INSTANCES", "")
@ -415,7 +430,7 @@ if __name__ == "__main__":
apis.append(API(instance))
# Instantiate scheduler
SCHEDULER = JobScheduler(env | environ, logger, INTEGRATION, db=db, apis=apis)
SCHEDULER.env = env | environ
if INTEGRATION in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
# Automatically setup the scheduler apis
@ -425,16 +440,16 @@ if __name__ == "__main__":
if not SCHEDULER.apis:
logger.warning("No BunkerWeb API found, retrying in 5s ...")
sleep(5)
db.update_instances([api_to_instance(api) for api in SCHEDULER.apis])
SCHEDULER.db.update_instances([api_to_instance(api) for api in SCHEDULER.apis])
scheduler_first_start = db.is_scheduler_first_start()
scheduler_first_start = SCHEDULER.db.is_scheduler_first_start()
logger.info("Scheduler started ...")
# Checking if any custom config has been created by the user
logger.info("Checking if there are any changes in custom configs ...")
custom_configs = []
db_configs = db.get_custom_configs()
db_configs = SCHEDULER.db.get_custom_configs()
changes = False
for file in CUSTOM_CONFIGS_PATH.rglob("*.conf"):
if len(file.parts) > len(CUSTOM_CONFIGS_PATH.parts) + 3:
@ -460,12 +475,12 @@ if __name__ == "__main__":
changes = changes or {hash(dict_to_frozenset(d)) for d in custom_configs} != {hash(dict_to_frozenset(d)) for d in db_configs}
if changes:
err = db.save_custom_configs(custom_configs, "manual")
err = SCHEDULER.db.save_custom_configs(custom_configs, "manual")
if err:
logger.error(f"Couldn't save some manually created custom configs to database: {err}")
if (scheduler_first_start and db_configs) or changes:
generate_custom_configs(db.get_custom_configs())
generate_custom_configs(SCHEDULER.db.get_custom_configs())
del custom_configs, db_configs
@ -473,51 +488,53 @@ if __name__ == "__main__":
# Check if any external or pro plugin has been added by the user
logger.info(f"Checking if there are any changes in {_type} plugins ...")
plugin_path = EXTERNAL_PLUGINS_PATH if _type == "external" else PRO_PLUGINS_PATH
db_plugins = SCHEDULER.db.get_plugins(_type=_type)
external_plugins = []
tmp_external_plugins = []
for file in plugin_path.glob("*/plugin.json"):
plugin_content = BytesIO()
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
tar.add(file.parent, arcname=file.parent.name, recursive=True)
plugin_content.seek(0, 0)
with BytesIO() as plugin_content:
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
tar.add(file.parent, arcname=file.parent.name, recursive=True)
plugin_content.seek(0, 0)
with file.open("r", encoding="utf-8") as f:
plugin_data = json_load(f)
with file.open("r", encoding="utf-8") as f:
plugin_data = json_load(f)
common_data = plugin_data | {
"type": _type,
"page": file.parent.joinpath("ui").is_dir(),
}
jobs = common_data.pop("jobs", [])
tmp_external_plugins.append(common_data)
checksum = bytes_hash(plugin_content, algorithm="sha256")
external_plugins.append(
common_data
| {
"method": "manual",
"data": plugin_content.getvalue(),
checksum = bytes_hash(plugin_content, algorithm="sha256")
common_data = plugin_data | {
"type": _type,
"page": file.parent.joinpath("ui").is_dir(),
"checksum": checksum,
}
| ({"jobs": jobs} if jobs else {})
)
jobs = common_data.pop("jobs", [])
db_plugins = db.get_plugins(_type=_type)
tmp_db_plugins = []
for db_plugin in db_plugins.copy():
db_plugin.pop("method", None)
tmp_db_plugins.append(db_plugin)
with suppress(StopIteration, IndexError):
index = next(i for i, plugin in enumerate(db_plugins) if plugin["id"] == common_data["id"])
changes = {hash(dict_to_frozenset(d)) for d in tmp_external_plugins} != {hash(dict_to_frozenset(d)) for d in tmp_db_plugins}
if checksum == db_plugins[index]["checksum"] or db_plugins[index]["method"] != "manual":
continue
if changes:
err = db.update_external_plugins(external_plugins, _type=_type, delete_missing=True)
if err:
logger.error(f"Couldn't save some manually added {_type} plugins to database: {err}")
tmp_external_plugins.append(common_data.copy())
if (scheduler_first_start and db_plugins) or changes:
generate_external_plugins(db.get_plugins(_type=_type, with_data=True), original_path=plugin_path)
external_plugins.append(
common_data
| {
"method": "manual",
"data": plugin_content.getvalue(),
}
| ({"jobs": jobs} if jobs else {})
)
if tmp_external_plugins:
changes = {hash(dict_to_frozenset(d)) for d in tmp_external_plugins} != {hash(dict_to_frozenset(d)) for d in db_plugins}
if changes:
err = SCHEDULER.db.update_external_plugins(external_plugins, _type=_type, delete_missing=True)
if err:
logger.error(f"Couldn't save some manually added {_type} plugins to database: {err}")
if (scheduler_first_start and db_plugins) or changes:
generate_external_plugins(SCHEDULER.db.get_plugins(_type=_type, with_data=True), original_path=plugin_path)
check_plugin_changes("external")
check_plugin_changes("pro")
@ -531,12 +548,12 @@ if __name__ == "__main__":
if not SCHEDULER.run_single("download-pro-plugins"):
logger.warning("download-pro-plugins job failed at first start, pro plugins settings set by the user may not be up to date ...")
changes = db.check_changes()
changes = SCHEDULER.db.check_changes()
if INTEGRATION not in ("Swarm", "Kubernetes", "Autoconf") and (changes["pro_plugins_changed"] or changes["external_plugins_changed"]):
if changes["pro_plugins_changed"]:
generate_external_plugins(db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
generate_external_plugins(SCHEDULER.db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
if changes["external_plugins_changed"]:
generate_external_plugins(db.get_plugins(_type="external", with_data=True))
generate_external_plugins(SCHEDULER.db.get_plugins(_type="external", with_data=True))
# run the config saver to save potential ignored external plugins settings
logger.info("Running config saver to save potential ignored external plugins settings ...")
@ -557,8 +574,8 @@ if __name__ == "__main__":
)
SCHEDULER.update_jobs()
env = db.get_config()
env["DATABASE_URI"] = db.database_uri
env = SCHEDULER.db.get_config()
env["DATABASE_URI"] = SCHEDULER.db.database_uri
logger.info("Executing scheduler ...")
@ -582,7 +599,7 @@ if __name__ == "__main__":
else:
logger.info(f"Successfully sent {CACHE_PATH} folder")
def listen_for_instances_reload(db: Database):
def listen_for_instances_reload():
from docker import DockerClient
global SCHEDULER
@ -592,12 +609,12 @@ if __name__ == "__main__":
if event["Action"] in ("start", "die"):
logger.info(f"🐋 Detected {event['Action']} event on container {event['Actor']['Attributes']['name']}")
SCHEDULER.auto_setup()
db.update_instances([api_to_instance(api) for api in SCHEDULER.apis], changed=event["Action"] == "die")
SCHEDULER.db.update_instances([api_to_instance(api) for api in SCHEDULER.apis], changed=event["Action"] == "die")
if event["Action"] == "start":
db.checked_changes(value=True)
SCHEDULER.db.checked_changes(value=True)
if INTEGRATION == "Docker" and not override_instances:
Thread(target=listen_for_instances_reload, args=(db,), name="listen_for_instances_reload").start()
Thread(target=listen_for_instances_reload, name="listen_for_instances_reload").start()
while True:
threads.clear()
@ -685,7 +702,7 @@ if __name__ == "__main__":
except:
logger.error(f"Exception while reloading after running jobs once scheduling : {format_exc()}")
ret = db.checked_changes(CHANGES)
ret = SCHEDULER.db.checked_changes(CHANGES)
if ret:
logger.error(f"An error occurred when setting the changes to checked in the database : {ret}")
@ -700,9 +717,11 @@ if __name__ == "__main__":
INSTANCES_NEED_GENERATION = False
if scheduler_first_start:
ret = db.set_scheduler_first_start()
ret = SCHEDULER.db.set_scheduler_first_start()
if ret:
if ret == "The database is read-only, the changes will not be saved":
logger.warning("The database is read-only, the scheduler first start will not be saved")
elif ret:
logger.error(f"An error occurred when setting the scheduler first start : {ret}")
stop(1)
scheduler_first_start = False
@ -725,7 +744,7 @@ if __name__ == "__main__":
DB_LOCK_FILE.unlink(missing_ok=True)
changes = db.check_changes()
changes = SCHEDULER.db.check_changes()
if isinstance(changes, str):
raise Exception(f"An error occurred when checking for changes in the database : {changes}")
@ -783,22 +802,22 @@ if __name__ == "__main__":
if CONFIGS_NEED_GENERATION:
CHANGES.append("custom_configs")
generate_custom_configs(db.get_custom_configs())
generate_custom_configs(SCHEDULER.db.get_custom_configs())
if PLUGINS_NEED_GENERATION:
CHANGES.append("external_plugins")
generate_external_plugins(db.get_plugins(_type="external", with_data=True))
generate_external_plugins(SCHEDULER.db.get_plugins(_type="external", with_data=True))
SCHEDULER.update_jobs()
if PRO_PLUGINS_NEED_GENERATION:
CHANGES.append("pro_plugins")
generate_external_plugins(db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
generate_external_plugins(SCHEDULER.db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
SCHEDULER.update_jobs()
if CONFIG_NEED_GENERATION:
CHANGES.append("config")
env = db.get_config()
env["DATABASE_URI"] = db.database_uri
env = SCHEDULER.db.get_config()
env["DATABASE_URI"] = SCHEDULER.db.database_uri
except:
logger.error(f"Exception while executing scheduler : {format_exc()}")

View file

@ -331,9 +331,9 @@ pytz==2024.1 \
# acme
# certbot
# pyrfc3339
requests==2.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via acme
schedule==1.2.1 \
--hash=sha256:14cdeb083a596aa1de6dc77639a1b2ac8bf6eaafa82b1c9279d3612823063d01 \
@ -341,9 +341,9 @@ schedule==1.2.1 \
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
setuptools==69.5.1 \
--hash=sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987 \
--hash=sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32
setuptools==70.0.0 \
--hash=sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4 \
--hash=sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0
# via -r requirements.in
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
@ -353,9 +353,9 @@ urllib3==2.2.1 \
--hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
--hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
# via requests
zipp==3.18.1 \
--hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \
--hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715
zipp==3.18.2 \
--hash=sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059 \
--hash=sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e
# via
# acme
# certbot

View file

@ -62,7 +62,8 @@ RUN apk add --no-cache bash unzip libmagic mariadb-client postgresql-client sqli
ln -s /proc/1/fd/2 /var/log/bunkerweb/ui.log
# Fix CVEs
# There are no CVEs to fix for this image
RUN apk add --no-cache "busybox>=1.36.1-r17" "busybox-binsh>=1.36.1-r17" "ssl_client>=1.36.1-r17" # CVE-2023-42363 CVE-2023-42364 CVE-2023-42365 CVE-2023-42366
RUN apk add --no-cache "libcrypto3>=3.1.5-r0" "libssl3>=3.1.5-r0" # CVE-2024-4603
LABEL maintainer "Bunkerity <contact@bunkerity.com>"
LABEL version "1.5.7"

View file

@ -175,7 +175,7 @@ REVERSE_PROXY_PATH = re_compile(r"^(?P<host>https?://.{1,255}(:((6553[0-5])|(655
def wait_applying():
for i in range(31):
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
if isinstance(curr_changes, str):
app.logger.error(f"An error occurred when checking for changes in the database : {curr_changes}")
elif not any(curr_changes.values()):
@ -197,9 +197,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:
@ -213,19 +213,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])
@ -237,14 +233,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"})
@ -262,16 +256,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:
@ -382,9 +378,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()
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
if ui_data.get("PRO_LOADING") and not any(curr_changes.values()):
ui_data["PRO_LOADING"] = False
@ -402,6 +398,7 @@ def inject_variables():
plugins=app.config["CONFIG"].get_plugins(),
pro_loading=ui_data.get("PRO_LOADING", False),
bw_version=metadata["version"],
is_readonly=app.config["DB"].readonly,
)
@ -448,7 +445,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)
@ -465,7 +466,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 (
@ -551,7 +554,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")
@ -674,7 +677,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,
@ -709,19 +712,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_pro_metadata(metadata)
app.config["DB"].set_pro_metadata(metadata)
flash("Checking license key to upgrade.", "success")
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
# 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
@ -795,7 +801,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:
@ -988,7 +994,7 @@ def services():
error = 0
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
old_server_name = request.form.get("OLD_SERVER_NAME", "")
operation = request.form["operation"]
@ -1118,7 +1124,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
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
def update_global_config(threaded: bool = False):
wait_applying()
@ -1165,7 +1171,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 = ""
@ -1257,7 +1263,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)
@ -1298,7 +1304,7 @@ def plugins():
if variables["type"] in ("core", "pro"):
return redirect_flash_error(f"Can't delete {variables['type']} plugin {variables['name']}", "plugins", True)
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
def update_plugins(threaded: bool = False): # type: ignore
wait_applying()
@ -1310,7 +1316,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:
@ -1507,7 +1513,7 @@ def plugins():
if errors >= files_count:
return redirect(url_for("loading", next=url_for("plugins")))
curr_changes = db.check_changes()
curr_changes = app.config["DB"].check_changes()
def update_plugins(threaded: bool = False):
wait_applying()
@ -1520,7 +1526,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:
@ -1657,7 +1663,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
@ -1790,7 +1796,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(" "),
)
],
@ -2139,6 +2145,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")
@ -2246,7 +2255,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"])
@ -2262,7 +2271,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

@ -9,5 +9,5 @@ pyotp==2.9.0
python-magic==0.4.27
python_dateutil==2.9.0.post0
qrcode==7.4.2
regex==2024.5.10
regex==2024.5.15
werkzeug==3.0.3

View file

@ -169,86 +169,86 @@ qrcode==7.4.2 \
--hash=sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a \
--hash=sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845
# via -r requirements.in
regex==2024.5.10 \
--hash=sha256:031219782d97550c2098d9a68ce9e9eaefe67d2d81d8ff84c8354f9c009e720c \
--hash=sha256:0709ba544cf50bd5cb843df4b8bb6701bae2b70a8e88da9add8386cbca5c1385 \
--hash=sha256:0a9f89d7db5ef6bdf53e5cc8e6199a493d0f1374b3171796b464a74ebe8e508a \
--hash=sha256:0bc94873ba11e34837bffd7e5006703abeffc4514e2f482022f46ce05bd25e67 \
--hash=sha256:0ce56a923f4c01d7568811bfdffe156268c0a7aae8a94c902b92fe34c4bde785 \
--hash=sha256:0faecb6d5779753a6066a3c7a0471a8d29fe25d9981ca9e552d6d1b8f8b6a594 \
--hash=sha256:1118ba9def608250250f4b3e3f48c62f4562ba16ca58ede491b6e7554bfa09ff \
--hash=sha256:12446827f43c7881decf2c126762e11425de5eb93b3b0d8b581344c16db7047a \
--hash=sha256:14905ed75c7a6edf423eb46c213ed3f4507c38115f1ed3c00f4ec9eafba50e58 \
--hash=sha256:15e593386ec6331e0ab4ac0795b7593f02ab2f4b30a698beb89fbdc34f92386a \
--hash=sha256:160ba087232c5c6e2a1e7ad08bd3a3f49b58c815be0504d8c8aacfb064491cd8 \
--hash=sha256:161a206c8f3511e2f5fafc9142a2cc25d7fe9a1ec5ad9b4ad2496a7c33e1c5d2 \
--hash=sha256:169fd0acd7a259f58f417e492e93d0e15fc87592cd1e971c8c533ad5703b5830 \
--hash=sha256:193b7c6834a06f722f0ce1ba685efe80881de7c3de31415513862f601097648c \
--hash=sha256:1a3903128f9e17a500618e80c68165c78c741ebb17dd1a0b44575f92c3c68b02 \
--hash=sha256:1d5bd666466c8f00a06886ce1397ba8b12371c1f1c6d1bef11013e9e0a1464a8 \
--hash=sha256:224a9269f133564109ce668213ef3cb32bc72ccf040b0b51c72a50e569e9dc9e \
--hash=sha256:236cace6c1903effd647ed46ce6dd5d76d54985fc36dafc5256032886736c85d \
--hash=sha256:249fbcee0a277c32a3ce36d8e36d50c27c968fdf969e0fbe342658d4e010fbc8 \
--hash=sha256:29d839829209f3c53f004e1de8c3113efce6d98029f044fa5cfee666253ee7e6 \
--hash=sha256:2c8982ee19ccecabbaeac1ba687bfef085a6352a8c64f821ce2f43e6d76a9298 \
--hash=sha256:2f30a5ab8902f93930dc6f627c4dd5da2703333287081c85cace0fc6e21c25af \
--hash=sha256:304e7e2418146ae4d0ef0e9ffa28f881f7874b45b4994cc2279b21b6e7ae50c8 \
--hash=sha256:32e5f3b8e32918bfbdd12eca62e49ab3031125c454b507127ad6ecbd86e62fca \
--hash=sha256:334b79ce9c08f26b4659a53f42892793948a613c46f1b583e985fd5a6bf1c149 \
--hash=sha256:33d19f0cde6838c81acffff25c7708e4adc7dd02896c9ec25c3939b1500a1778 \
--hash=sha256:3799e36d60a35162bb35b2246d8bb012192b7437dff807ef79c14e7352706306 \
--hash=sha256:42be5de7cc8c1edac55db92d82b68dc8e683b204d6f5414c5a51997a323d7081 \
--hash=sha256:44b3267cea873684af022822195298501568ed44d542f9a2d9bebc0212e99069 \
--hash=sha256:458d68d34fb74b906709735c927c029e62f7d06437a98af1b5b6258025223210 \
--hash=sha256:45cc13d398b6359a7708986386f72bd156ae781c3e83a68a6d4cee5af04b1ce9 \
--hash=sha256:4e7eaf9df15423d07b6050fb91f86c66307171b95ea53e2d87a7993b6d02c7f7 \
--hash=sha256:4fad420b14ae1970a1f322e8ae84a1d9d89375eb71e1b504060ab2d1bfe68f3c \
--hash=sha256:504b5116e2bd1821efd815941edff7535e93372a098e156bb9dffde30264e798 \
--hash=sha256:50e7e96a527488334379e05755b210b7da4a60fc5d6481938c1fa053e0c92184 \
--hash=sha256:51d27844763c273a122e08a3e86e7aefa54ee09fb672d96a645ece0454d8425e \
--hash=sha256:5253dcb0bfda7214523de58b002eb0090cb530d7c55993ce5f6d17faf953ece7 \
--hash=sha256:534efd2653ebc4f26fc0e47234e53bf0cb4715bb61f98c64d2774a278b58c846 \
--hash=sha256:560278c9975694e1f0bc50da187abf2cdc1e4890739ea33df2bc4a85eeef143e \
--hash=sha256:571452362d552de508c37191b6abbbb660028b8b418e2d68c20779e0bc8eaaa8 \
--hash=sha256:62b5f7910b639f3c1d122d408421317c351e213ca39c964ad4121f27916631c6 \
--hash=sha256:696639a73ca78a380acfaa0a1f6dd8220616a99074c05bba9ba8bb916914b224 \
--hash=sha256:6ccdeef4584450b6f0bddd5135354908dacad95425fcb629fe36d13e48b60f32 \
--hash=sha256:70364a097437dd0a90b31cd77f09f7387ad9ac60ef57590971f43b7fca3082a5 \
--hash=sha256:7117cb7d6ac7f2e985f3d18aa8a1728864097da1a677ffa69e970ca215baebf1 \
--hash=sha256:7467ad8b0eac0b28e52679e972b9b234b3de0ea5cee12eb50091d2b68145fe36 \
--hash=sha256:7d35d4cc9270944e95f9c88af757b0c9fc43f396917e143a5756608462c5223b \
--hash=sha256:7dda3091838206969c2b286f9832dff41e2da545b99d1cfaea9ebd8584d02708 \
--hash=sha256:853cc36e756ff673bf984e9044ccc8fad60b95a748915dddeab9488aea974c73 \
--hash=sha256:8722f72068b3e1156a4b2e1afde6810f1fc67155a9fa30a4b9d5b4bc46f18fb0 \
--hash=sha256:8c6c71cf92b09e5faa72ea2c68aa1f61c9ce11cb66fdc5069d712f4392ddfd00 \
--hash=sha256:903350bf44d7e4116b4d5898b30b15755d61dcd3161e3413a49c7db76f0bee5a \
--hash=sha256:91b53dea84415e8115506cc62e441a2b54537359c63d856d73cb1abe05af4c9a \
--hash=sha256:951be1eae7b47660412dc4938777a975ebc41936d64e28081bf2e584b47ec246 \
--hash=sha256:972b49f2fe1047b9249c958ec4fa1bdd2cf8ce305dc19d27546d5a38e57732d8 \
--hash=sha256:9a8625849387b9d558d528e263ecc9c0fbde86cfa5c2f0eef43fff480ae24d71 \
--hash=sha256:9cdbb1998da94607d5eec02566b9586f0e70d6438abf1b690261aac0edda7ab6 \
--hash=sha256:9e6d4d6ae1827b2f8c7200aaf7501c37cf3f3896c86a6aaf2566448397c823dd \
--hash=sha256:aab65121229c2ecdf4a31b793d99a6a0501225bd39b616e653c87b219ed34a49 \
--hash=sha256:ab98016541543692a37905871a5ffca59b16e08aacc3d7d10a27297b443f572d \
--hash=sha256:ad45f3bccfcb00868f2871dce02a755529838d2b86163ab8a246115e80cfb7d6 \
--hash=sha256:b43b78f9386d3d932a6ce5af4b45f393d2e93693ee18dc4800d30a8909df700e \
--hash=sha256:b66421f8878a0c82fc0c272a43e2121c8d4c67cb37429b764f0d5ad70b82993b \
--hash=sha256:ba034c8db4b264ef1601eb33cd23d87c5013b8fb48b8161debe2e5d3bd9156b0 \
--hash=sha256:bbdc5db2c98ac2bf1971ffa1410c87ca7a15800415f788971e8ba8520fc0fda9 \
--hash=sha256:bc0db93ad039fc2fe32ccd3dd0e0e70c4f3d6e37ae83f0a487e1aba939bd2fbd \
--hash=sha256:bf7c8ee4861d9ef5b1120abb75846828c811f932d63311596ad25fa168053e00 \
--hash=sha256:bf9596cba92ce7b1fd32c7b07c6e3212c7eed0edc271757e48bfcd2b54646452 \
--hash=sha256:c43395a3b7cc9862801a65c6994678484f186ce13c929abab44fb8a9e473a55a \
--hash=sha256:c46a76a599fcbf95f98755275c5527304cc4f1bb69919434c1e15544d7052910 \
--hash=sha256:ca23b41355ba95929e9505ee04e55495726aa2282003ed9b012d86f857d3e49b \
--hash=sha256:cd832bd9b6120d6074f39bdfbb3c80e416848b07ac72910f1c7f03131a6debc3 \
--hash=sha256:cfa6d61a76c77610ba9274c1a90a453062bdf6887858afbe214d18ad41cf6bde \
--hash=sha256:d8a0f0ab5453e409586b11ebe91c672040bc804ca98d03a656825f7890cbdf88 \
--hash=sha256:e91b1976358e17197157b405cab408a5f4e33310cda211c49fc6da7cffd0b2f0 \
--hash=sha256:ea057306ab469130167014b662643cfaed84651c792948891d003cf0039223a5 \
--hash=sha256:eda3dd46df535da787ffb9036b5140f941ecb91701717df91c9daf64cabef953 \
--hash=sha256:f03b1dbd4d9596dd84955bb40f7d885204d6aac0d56a919bb1e0ff2fb7e1735a \
--hash=sha256:fa9335674d7c819674467c7b46154196c51efbaf5f5715187fd366814ba3fa39
regex==2024.5.15 \
--hash=sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649 \
--hash=sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35 \
--hash=sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb \
--hash=sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68 \
--hash=sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5 \
--hash=sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133 \
--hash=sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0 \
--hash=sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d \
--hash=sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da \
--hash=sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f \
--hash=sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d \
--hash=sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53 \
--hash=sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa \
--hash=sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a \
--hash=sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890 \
--hash=sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67 \
--hash=sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c \
--hash=sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2 \
--hash=sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced \
--hash=sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741 \
--hash=sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f \
--hash=sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa \
--hash=sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf \
--hash=sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4 \
--hash=sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5 \
--hash=sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2 \
--hash=sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384 \
--hash=sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7 \
--hash=sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014 \
--hash=sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704 \
--hash=sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5 \
--hash=sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2 \
--hash=sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49 \
--hash=sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1 \
--hash=sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694 \
--hash=sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629 \
--hash=sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6 \
--hash=sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435 \
--hash=sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c \
--hash=sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835 \
--hash=sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e \
--hash=sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201 \
--hash=sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62 \
--hash=sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5 \
--hash=sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16 \
--hash=sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f \
--hash=sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1 \
--hash=sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f \
--hash=sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f \
--hash=sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145 \
--hash=sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3 \
--hash=sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed \
--hash=sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143 \
--hash=sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca \
--hash=sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9 \
--hash=sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa \
--hash=sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850 \
--hash=sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80 \
--hash=sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe \
--hash=sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656 \
--hash=sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388 \
--hash=sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1 \
--hash=sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294 \
--hash=sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3 \
--hash=sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d \
--hash=sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b \
--hash=sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40 \
--hash=sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600 \
--hash=sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c \
--hash=sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569 \
--hash=sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456 \
--hash=sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9 \
--hash=sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb \
--hash=sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e \
--hash=sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f \
--hash=sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d \
--hash=sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a \
--hash=sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a \
--hash=sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796
# via -r requirements.in
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
@ -273,7 +273,7 @@ wtforms==3.1.2 \
--hash=sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07 \
--hash=sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9
# via flask-wtf
zipp==3.18.1 \
--hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \
--hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715
zipp==3.18.2 \
--hash=sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059 \
--hash=sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e
# via importlib-metadata

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=variables["SERVER_NAME"])
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

@ -24,6 +24,11 @@ class SettingsService {
this.settingsMultiple,
"services",
);
this.isReadOnly =
document
.querySelector("[data-services-modal]")
.getAttribute("data-readonly") === "true";
this.initSettingsService();
}
@ -96,6 +101,7 @@ class SettingsService {
operation,
] = this.getActionData(e.target);
const forceDisabled = this.isReadOnly ? true : false;
const forceEnabled = action === "new" ? true : false;
const setMethodUI =
action === "new" || action === "clone" ? true : false;
@ -110,6 +116,7 @@ class SettingsService {
setMethodUI,
forceEnabled,
emptyServerName,
forceDisabled,
);
}
} catch (err) {}
@ -121,6 +128,10 @@ class ServiceModal {
constructor() {
//modal elements
this.modal = document.querySelector("[data-services-modal]");
this.isReadOnly =
document
.querySelector("[data-services-modal]")
.getAttribute("data-readonly") === "true";
this.modalTitle = this.modal.querySelector("[data-services-modal-title]");
this.modalTabs = this.modal.querySelector(["[data-services-tabs-select]"]);
this.modalTabsHeader = this.modal.querySelector([
@ -235,8 +246,8 @@ class ServiceModal {
this.setCardViewportHeight(action === "delete" ? false : true);
this.setHeaderActionsVisible(action === "delete" ? false : true);
this.SetSelectTabsVisible(action === "delete" ? false : true);
this.resetFilterSettings();
if (action === "edit" || action === "new" || action === "clone") {
this.resetFilterSettings();
this.formNewEdit.classList.remove("hidden");
const oldNameValue = action === "edit" ? oldServName : "";
@ -262,7 +273,10 @@ class ServiceModal {
}
this.setIsDraft(isDraft === "yes" ? true : false, method);
this.openModal();
setTimeout(() => {
this.setActionBtns();
this.openModal();
}, 50);
}
resetFilterSettings() {
@ -279,6 +293,20 @@ class ServiceModal {
inpKeyword.dispatchEvent(new Event("input"));
}
setActionBtns() {
if (this.isReadOnly) {
this.modal.querySelectorAll("button[type='submit']").forEach((btn) => {
btn.setAttribute("disabled", "true");
});
}
if (!this.isReadOnly) {
this.modal.querySelectorAll("button[type='submit']").forEach((btn) => {
btn.removeAttribute("disabled");
});
}
}
setIsDraft(isDraft, method) {
const draftVal = isDraft ? "yes" : "no";

View file

@ -5,6 +5,12 @@ class FolderNav {
`[data-${this.prefix}-breadcrumb]`,
);
this.container = document.querySelector(`[data-${this.prefix}-container]`);
this.isReadonly =
document
.querySelector(`[data-${this.prefix}-container]`)
.getAttribute(`data-readonly`) === "true"
? true
: false;
this.listContainer = document.querySelector(
`[data-${this.prefix}-folders]`,
);
@ -95,6 +101,9 @@ class FolderNav {
updateActions(folder) {
// for root
if (!folder) return this.addFileEl.setAttribute("disabled", "");
if (folder && this.isReadonly)
return this.addFileEl.setAttribute("disabled", "");
//check if folder allow add file/folder
const isAddFile = folder.getAttribute("data-can-create-file") || "False";
isAddFile === "True"
@ -282,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();
@ -291,6 +306,7 @@ class FolderEditor {
initEditor() {
//editor options
this.editor.setShowPrintMargin(false);
this.editor.setReadOnly(this.isReadonly);
this.setDarkMode();
}
@ -315,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 {
@ -326,6 +338,12 @@ class FolderModal {
this.prefix = prefix;
//container
this.container = document.querySelector(`[data-${this.prefix}-container]`);
this.isReadonly =
document
.querySelector(`[data-${this.prefix}-container]`)
.getAttribute(`data-readonly`) === "true"
? true
: false;
//add service/file elements
this.breadContainer = document.querySelector(
`[data-${this.prefix}-breadcrumb]`,
@ -580,28 +598,30 @@ class FolderModal {
if (action === "new") {
this.modalSubmit.textContent = "add";
this.setSubmitBtnType("valid-btn");
return;
}
if (action === "view") {
this.modalSubmit.textContent = "ok";
this.setSubmitBtnType("valid-btn");
return;
}
if (action === "edit") {
this.setSubmitBtnType("edit-btn");
this.modalSubmit.textContent = "edit";
return;
}
if (action === "delete") {
this.setSubmitBtnType("delete-btn");
this.modalSubmit.textContent = "delete";
return;
}
if (action === "download") {
this.setSubmitBtnType("info-btn");
this.modalSubmit.textContent = "download";
return;
}
// readonly logic
if (["new", "edit", "delete"].includes(action) && this.isReadonly) {
this.modalSubmit.setAttribute("disabled", "true");
} else {
this.modalSubmit.removeAttribute("disabled");
}
}
@ -649,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" : ""

View file

@ -1000,6 +1000,7 @@ class Settings {
this.oldServName = "";
this.setMethodUI = false;
this.forceEnabled = false;
this.forceDisabled = false;
this.emptyServerName = false;
this.currSettings = {};
this.initSettings();
@ -1170,6 +1171,7 @@ class Settings {
setMethodUI,
forceEnabled,
emptyServerName,
forceDisabled,
) {
// Get global needed data
this.currAction = action;
@ -1179,6 +1181,7 @@ class Settings {
this.setMethodUI = setMethodUI;
this.forceEnabled = forceEnabled;
this.emptyServerName = emptyServerName;
this.forceDisabled = forceDisabled;
this.updateOperation();
this.updateOldNameValue();
@ -1282,17 +1285,27 @@ class Settings {
? true
: false;
if (proDisabled) return inp.setAttribute("disabled", "");
let inpDisabledState = inp;
if (inp.tagName === "SELECT")
inpDisabledState = inp.parentElement
.querySelector("[data-select-container]")
.querySelector("button[data-setting-select]");
if (this.forceEnabled) return inp.removeAttribute("disabled");
if (this.forceDisabled)
return inpDisabledState.setAttribute("disabled", "");
if (proDisabled)
return inpDisabledState.setAttribute("disabled", "");
if (this.forceEnabled)
return inpDisabledState.removeAttribute("disabled");
if (method === "ui" || method === "default") {
inp.removeAttribute("disabled");
inpDisabledState.removeAttribute("disabled");
} else {
inp.setAttribute("disabled", "");
inpDisabledState.setAttribute("disabled", "");
}
if (global) inp.removeAttribute("disabled");
if (global) inpDisabledState.removeAttribute("disabled");
});
} catch (err) {}
}
@ -1417,7 +1430,8 @@ class SettingsMultiple extends Settings {
inps.forEach((inp) => {
// case checkbox
if (inp.getAttribute("type") === "checkbox") {
const defaultVal = inp.getAttribute("data-default") || "";
const defaultVal =
inp.getAttribute("data-default-value") || "";
if (defaultVal === "yes" && !inp.checked) {
inp.click();
@ -1426,7 +1440,8 @@ class SettingsMultiple extends Settings {
// case regular
if (inp.getAttribute("type") !== "checkbox") {
const defaultVal = inp.getAttribute("data-default") || "";
const defaultVal =
inp.getAttribute("data-default-value") || "";
inp.setAttribute("value", defaultVal);
inp.value = defaultVal;
}
@ -1438,7 +1453,8 @@ class SettingsMultiple extends Settings {
"button[data-setting-select]",
);
selects.forEach((select) => {
const defaultVal = select.getAttribute("data-default") || "";
const defaultVal =
select.getAttribute("data-default-value") || "";
select
.querySelector("data-setting-select-text")
.setAttribute("data-value", defaultVal);
@ -1467,7 +1483,10 @@ class SettingsMultiple extends Settings {
? true
: false;
return proDisabled;
const isReadOnly = this.forceDisabled;
if (proDisabled || isReadOnly) return true;
return false;
}
removePrevMultiples() {
@ -1958,6 +1977,7 @@ class SettingsAdvanced extends SettingsEditor {
setMethodUI = false,
forceEnabled = false,
emptyServerName = false,
forceDisabled = false,
) {
this.updateData(
action,
@ -1967,6 +1987,7 @@ class SettingsAdvanced extends SettingsEditor {
setMethodUI,
forceEnabled,
emptyServerName,
forceDisabled,
);
this.setSettingsAdvanced();
this.resetServerName();

File diff suppressed because one or more lines are too long

View file

@ -47,6 +47,9 @@
@apply tracking-wide dark:brightness-90 inline-block px-4 py-2 md:px-5 md:py-2.5 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-sky-500 hover:bg-sky-500/80 focus:bg-sky-500/80 leading-normal ease-in shadow-xs hover:-translate-y-px active:opacity-85 hover:shadow-md disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
.btn-disabled-style {
@apply disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0;
}
/*----------------------------------------------*/
/*---------------SETTINGS_PLUGINS---------------*/
/*----------------------------------------------*/
@ -663,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

@ -142,7 +142,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">License key</h5>
<label class="sr-only" for="license">License key</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="license"
name="license"
class="col-span-12 regular-input"
@ -173,7 +173,7 @@
</div>
</div>
<div class="col-span-12 flex justify-center mt-6">
<button type="submit"
<button {% if is_readonly%}disabled{% endif %} type="submit"
id="activate-key-button"
name="activate-key-button"
class="valid-btn">SAVE</button>
@ -200,7 +200,7 @@
<div class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">Username</h5>
<label class="sr-only" for="admin_username">New username</label>
<input type="text"
<input {% if is_readonly%}disabled{% endif %} type="text"
id="admin_username"
name="admin_username"
class="col-span-12 regular-input"
@ -216,7 +216,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">Password</h5>
<label class="sr-only" for="curr_password">Password</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="curr_password"
name="curr_password"
class="col-span-12 regular-input"
@ -249,7 +249,7 @@
</div>
<!-- end password inpt-->
<div class="col-span-12 flex justify-center mt-6">
<button type="submit"
<button {% if is_readonly%}disabled{% endif %} type="submit"
id="username-button"
name="username-button"
value="username"
@ -277,7 +277,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">Password</h5>
<label class="sr-only" for="curr_password">Password</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="curr_password"
name="curr_password"
class="col-span-12 regular-input"
@ -314,7 +314,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">New password</h5>
<label class="sr-only" for="admin_password">New password</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="admin_password"
name="admin_password"
class="col-span-12 regular-input"
@ -351,7 +351,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">Confirm new password</h5>
<label class="sr-only" for="admin_password_check">Confirm new password</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="admin_password_check"
name="admin_password_check"
class="col-span-12 regular-input"
@ -384,7 +384,7 @@
<strong class="opacity-0 font-normal text-sm text-red-500" data-pw-alert>Value does not match password</strong>
</div>
<div class="col-span-12 flex justify-center">
<button type="submit" id="pw-button" name="pw-button" class="valid-btn">Save</button>
<button {% if is_readonly%}disabled{% endif %} type="submit" id="pw-button" name="pw-button" class="valid-btn">Save</button>
</div>
</form>
<form data-tab-item="totp"
@ -426,7 +426,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">Secret token</h5>
<label class="sr-only" for="secret_token">secret token</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="secret_token"
name="secret_token"
class="col-span-12 regular-input"
@ -477,7 +477,7 @@
<div class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">2FA code</h5>
<label class="sr-only" for="totp_token">totp code</label>
<input type="text"
<input {% if is_readonly%}disabled{% endif %} type="text"
id="totp_token"
name="totp_token"
class="col-span-12 regular-input"
@ -492,7 +492,7 @@
class="flex flex-col relative col-span-12 px-4 my-2 md:px-6 md:my-3 lg:px-6 lg:my-3 max-w-[400px] w-full">
<h5 class="input-title">Password</h5>
<label class="sr-only" for="curr_password">Password</label>
<input type="password"
<input {% if is_readonly%}disabled{% endif %} type="password"
id="curr_password"
name="curr_password"
class="col-span-12 regular-input"
@ -525,7 +525,7 @@
</div>
<!-- end password inpt-->
<div class="col-span-12 flex justify-center mt-6">
<button type="submit"
<button {% if is_readonly%}disabled{% endif %} type="submit"
id="totp-button"
name="totp-button"
value="totp"

View file

@ -14,9 +14,9 @@
<!-- actions -->
<div class="col-span-12 relative flex justify-center min-w-0 break-words rounded-2xl bg-clip-border">
<button data-add-ban
<button {% if is_readonly%}disabled{% endif %} data-add-ban
type="button"
class="dark:bg-green-500/90 duration-300 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white dark:text-gray-200 uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:bg-green-500/90 duration-300 dark:text-gray-100 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
<span class="mr-2">Add ban</span>
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -139,7 +139,7 @@
data-checkbox-handler="ban-item-{{ loop.index }}"
class="relative mb-7 md:mb-0 z-10 ml-2">
<label class="sr-only" for="ban-item-{{ loop.index }}">Ban ip {{ loop.index }}</label>
<input id="ban-item-{{ loop.index }}"
<input {% if is_readonly%}disabled{% endif %} id="ban-item-{{ loop.index }}"
name="ban-item-{{ loop.index }}"
data-default-method="ui"
data-default-value="no"
@ -182,7 +182,7 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="operation" value="unban">
<input data-unban-inp type="hidden" name="data" value="">
<button data-unban-btn
<button {% if is_readonly%}disabled{% endif %} data-unban-btn
disabled
type="submit"
class="valid-btn mr-3 text-base">UNBAN</button>

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

@ -1,6 +1,6 @@
{% set current_endpoint = url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %}
<!-- main container -->
<div data-{{ current_endpoint }}-container class="min-h-[400px] flex flex-col justify-between dark:brightness-110 md:min-h-50-screen col-span-12 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div data-readonly="{% if is_readonly %}true{% else %}false{% endif %}" data-{{ current_endpoint }}-container class="min-h-[400px] flex flex-col justify-between dark:brightness-110 md:min-h-50-screen col-span-12 p-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
<div class="mb-4 px-3">
<div class="w-full grid-cols-12 grid">
<div class="col-span-12 md:col-span-8">

View file

@ -64,7 +64,7 @@
<!-- end plugin item -->
<!-- submit -->
<div class="flex w-full justify-center mt-8 mb-2">
<button type="submit" class="valid-btn">SAVE</button>
<button {% if is_readonly%}disabled{% endif %} type="submit" class="valid-btn">SAVE</button>
</div>
<!-- end submit -->
</form>

View file

@ -147,7 +147,7 @@
<div class="{{ data['custom_class'] }} relative dark:text-gray-400 text-sm m-0 my-1 mr-1"
data-{{attribute_name}}-files>
{% if value['cache'] %}
<button data-{{attribute_name}}-setting-select="{{ job_name }}"
<button {% if is_readonly%}disabled{% endif %} data-{{attribute_name}}-setting-select="{{ job_name }}"
class="py-1 text-sm disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left leading-6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500">
<span id="jobs-{{ job_name }}"
data-name="jobs-{{ job_name }}"

View file

@ -204,7 +204,7 @@
renderer: 'svg',
loop: true,
autoplay: true,
path: "json/PARTICLES.json" // the path to the animation json
path: "json/particles.json" // the path to the animation json
});
});
</script>

View file

@ -13,6 +13,7 @@
] %}
{% include "card_info.html" %}
{% if not is_readonly %}
<!-- upload layout -->
<div data-{{attribute_name}}-upload
class="p-4 col-span-12 md:col-span-7 2xl:col-span-4 grid grid-cols-12 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border">
@ -49,6 +50,8 @@
</div>
</div>
<!-- end upload layout -->
{% endif %}
<!-- filter -->
{% set filters = [
{
@ -103,12 +106,12 @@
</svg>
</a>
{% endif %}
{% if plugin['type'] == "external" %}
<button data-{{attribute_name}}-action="delete"
{% 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

@ -31,13 +31,13 @@
class="col-span-12 relative flex justify-center min-w-0 break-words rounded-2xl bg-clip-border">
<div data-is-draft class="hidden" data-value="no"></div>
<div data-service-method class="hidden" data-value="ui"></div>
<button data-{{attribute_name}}-action="new"
<button {% if is_readonly%}disabled{% endif %} data-{{attribute_name}}-action="new"
data-{{attribute_name}}-name="service"
data-old-name
data-value="new"
data-settings="{}"
type="button"
class="dark:bg-green-500/90 duration-300 dark:text-gray-100 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:bg-green-500/90 duration-300 dark:text-gray-100 w-80 flex justify-center items-center px-6 py-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer bg-green-500 hover:bg-green-500/80 focus:bg-green-500/80 leading-normal text-base ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 hover:-translate-y-px active:opacity-85 hover:shadow-md">
<span class="mr-2">new service</span>
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
@ -284,8 +284,10 @@
{% endif %}
{% for button in action_buttons %}
<button
{% if button['name'] == "clone" and is_readonly%}disabled{% endif %}
{% if button['name'] == "clone" or button['name'] == "edit"%}
data-settings="{{ service['settings'] }}"
{% endif %}
{% if button['name'] == "new"%}
data-settings="{}"
@ -293,7 +295,7 @@
data-{{attribute_name}}-action="{{ button['name'] }}"
aria-label="{{ button['label'] }}"
data-{{attribute_name}}-name="{{ service['SERVER_NAME']['value'] }}"
class="dark:brightness-90 z-20 mx-1 bg-{{ button['color'] }} hover:bg-{{ button['color'] }}/80 focus:bg-{{ button['color'] }}/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md">
class="disabled:cursor-not-allowed dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400/0 dark:disabled:bg-gray-700 dark:disabled:border-gray-700/0 disabled:hover:translate-y-0 disabled:hover:bg-gray-400 disabled:hover:border-gray-400/0 dark:disabled:hover:translate-y-0 dark:disabled:hover:bg-gray-700 dark:disabled:hover:border-gray-700/0 dark:brightness-90 z-20 mx-1 bg-{{ button['color'] }} hover:bg-{{ button['color'] }}/80 focus:bg-{{ button['color'] }}/80 inline-block p-3 font-bold text-center text-white uppercase align-middle transition-all rounded-lg cursor-pointer leading-normal text-xs ease-in tracking-tight-rem shadow-xs bg-150 bg-x-25 active:opacity-85 hover:shadow-md">
{% if button['name'] == "clone" %}
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"

View file

@ -1,5 +1,5 @@
<!-- modal -->
<div data-services-plugins-container
<div data-readonly="{% if is_readonly %}true{% else %}false{% endif %}" data-services-plugins-container
data-services-modal
class="dark:brightness-110 hidden w-screen h-screen fixed bg-gray-600/50 z-[1001] top-0 left-0 justify-center items-center">
<div data-services-modal-card

View file

@ -13,7 +13,7 @@
<div data-checkbox-handler="{{ inp_id }}"
class="relative mb-7 md:mb-0 z-10 ">
<label class="sr-only" for="{{ inp_name_mult }}">{{ inp_name }}</label>
<input id="{{ inp_name_mult }}"
<input {% if is_readonly%}disabled{% endif %} id="{{ inp_name_mult }}"
name="{{ inp_name_mult }}"
data-default-method="{% if inp_name in ['AUTOCONF_MODE', 'SWARM_MODE', 'KUBERNETES_MODE'] %}mode{% else %}{{ global_config_method }}{% endif %}"
data-default-value="{{ global_config[inp_name]['value'] }}"

View file

@ -13,7 +13,7 @@
<div class="relative flex items-center">
<label class="sr-only" for="{{ inp_name_mult }}">{{ inp_name }}</label>
<input {% if inp_name == "SERVER_NAME" %}required{% endif %}
<input {% if is_readonly%}disabled{% endif %} {% if inp_name == "SERVER_NAME" %}required{% endif %}
data-default-value="{{ global_config_value }}"
data-default-method="{{ global_config_method }}"
data-setting-input

View file

@ -33,7 +33,7 @@
<!-- end default hidden-->
<!--custom-->
<div data-select-container class="relative">
<button {% if global_config_method != 'ui' and global_config_method != 'default' or is_read_only %}disabled{% endif %}
<button {% if is_readonly%}disabled{% endif %} {% if global_config_method != 'ui' and global_config_method != 'default' or is_read_only %}disabled{% endif %}
data-setting-select="{{ inp_id }}"
data-default-value="{{ global_config_value }}"
data-default-method="{{ global_config_method }}"

View file

@ -180,6 +180,7 @@
{% else %}
<h6 class="col-span-12 block text-left font-bold mb-4 mt-2">🧑‍🚀 An admin user already exists</h6>
{% endif %}
<h2 class="col-span-12 block text-left font-bold my-4 text-2xl">Newsletter</h2>
<!-- email inpt-->
<div class="flex flex-col relative col-span-12 mx-2 max-w-[400px] w-full">
<h5 class="text-base mb-1 transition duration-300 ease-in-out text-md font-bold m-0">Email</h5>
@ -536,6 +537,7 @@
this.servInp = document.querySelector("#server_name");
this.sslCheck = document.querySelector("#auto_lets_encrypt");
this.urlInp = document.querySelector("#ui_url");
this.newsForm = document.querySelector("#newsletter-form");
this.emailInp = document.querySelector("#newsletter-email")
this.checkEmailInp = document.querySelector("#newsletter-check");
this.loaderContainer = document.querySelector("[data-loader]");
@ -572,6 +574,11 @@
} catch (err) {}
});
// Avoid reload on newsletter submit
this.newsForm.addEventListener('submit', (e) => {
e.preventDefault()
})
// Submit
this.formEl.addEventListener("submit", (e) => {
{% if not ui_user %}
@ -587,7 +594,7 @@
this.hideErrMsg();
// Send email
if(this.checkEmailInp.checked) {
if(this.checkEmailInp.checked && this.checkEmailInp.checkValidity()) {
this.subscribe();
}
@ -644,7 +651,7 @@
subscribe() {
document.querySelector('#newsletter-form-email').value = this.emailInp.value;
document.querySelector('#newsletter-form').submit();
this.newsForm.submit();
}
{% if not ui_user %}

View file

@ -10,6 +10,8 @@
<link rel="stylesheet" href="css/dashboard.css" />
<link rel="stylesheet" href="css/login.css" />
<script defer src="./js/totp.js" nonce="{{ script_nonce }}"></script>
<script src="js/lottie-web.min.js" defer nonce="{{ script_nonce }}"></script>
</head>
<body>
<div data-loader
@ -108,10 +110,9 @@
<!-- end form -->
<!-- particles -->
<div class="-z-10 fixed bg-primary">
<div id="particles-js" class="login-img [&>*]:bg-primary"></div>
<div id="lottie-particles" data-lottie="/lotties/PARTICLES.json" class="fixed top-0 left-0 w-full h-full login-img [&>*]:bg-primary"></div>
</div>
</main>
<script src="js/tsparticles.bundle.min.js" nonce="{{ script_nonce }}"></script>
<script nonce="{{ script_nonce }}">
class Loader {
constructor() {
@ -187,7 +188,16 @@
const setLoader = new Loader();
const setFlash = new FlashMsg();
tsParticles.loadJSON("particles-js", "json/particles.json");
window.addEventListener("load", () => {
lottie.loadAnimation({
container: document.querySelector("#lottie-particles"), // the dom element that will contain the animation
renderer: 'svg',
loop: true,
autoplay: true,
path: "json/particles.json" // the path to the animation json
});
});
</script>
</body>
</html>

View file

@ -1,2 +1,2 @@
requests==2.31.0
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.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \
@ -149,9 +149,9 @@ sortedcontainers==2.4.0 \
--hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \
--hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0
# via trio
trio==0.25.0 \
--hash=sha256:9b41f5993ad2c0e5f62d0acca320ec657fdb6b2a2c22b8c7aed6caf154475c4e \
--hash=sha256:e6458efe29cc543e557a91e614e2b51710eba2961669329ce9c862d50c6e8e81
trio==0.25.1 \
--hash=sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb \
--hash=sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb
# via
# selenium
# trio-websocket

View file

@ -1,2 +1,2 @@
requests==2.31.0
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.31.0 \
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
requests==2.32.2 \
--hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
--hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
# via -r requirements.in
selenium==4.16.0 \
--hash=sha256:aec71f4e6ed6cb3ec25c9c1b5ed56ae31b6da0a7f17474c7566d303f84e6219f \
@ -149,9 +149,9 @@ sortedcontainers==2.4.0 \
--hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \
--hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0
# via trio
trio==0.25.0 \
--hash=sha256:9b41f5993ad2c0e5f62d0acca320ec657fdb6b2a2c22b8c7aed6caf154475c4e \
--hash=sha256:e6458efe29cc543e557a91e614e2b51710eba2961669329ce9c862d50c6e8e81
trio==0.25.1 \
--hash=sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb \
--hash=sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb
# via
# selenium
# trio-websocket

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