Merge pull request #1276 from bunkerity/dev

Merge branch "dev' into branch "staging"
This commit is contained in:
Théophile Diot 2024-06-14 15:43:53 +01:00 committed by GitHub
commit f7280ce7cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
173 changed files with 2155 additions and 1642 deletions

View file

@ -133,7 +133,7 @@ jobs:
versionrpm: ${{ steps.getversionrpm.outputs.versionrpm }}
steps:
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql.yml
setup-python-dependencies: false
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
with:
category: "/language:${{matrix.language}}"

View file

@ -45,7 +45,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Replace VERSION
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}
@ -92,7 +92,7 @@ jobs:
# Build cached image
- name: Build image
if: inputs.CACHE == true
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: ${{ inputs.DOCKERFILE }}
@ -105,7 +105,7 @@ jobs:
# Build non-cached image
- name: Build image
if: inputs.CACHE != true
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: ${{ inputs.DOCKERFILE }}

View file

@ -33,7 +33,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Replace VERSION
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' || inputs.RELEASE == 'ui'
run: ./misc/update-version.sh ${{ inputs.RELEASE }}
@ -94,7 +94,7 @@ jobs:
# Build testing package image
- name: Build package image
if: inputs.RELEASE == 'testing' || inputs.RELEASE == 'dev' || inputs.RELEASE == 'ui'
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
load: true
@ -106,7 +106,7 @@ jobs:
# Build non-testing package image
- name: Build package image
if: inputs.RELEASE != 'testing' && inputs.RELEASE != 'dev'
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
load: true
@ -142,7 +142,7 @@ jobs:
images: ghcr.io/bunkerity/${{ inputs.LINUX }}-tests:${{ inputs.RELEASE }}
- name: Build test image
if: inputs.TEST == true
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: tests/linux/Dockerfile-${{ inputs.LINUX }}

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to Docker Hub
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:
@ -70,7 +70,7 @@ jobs:
images: bunkerity/${{ inputs.IMAGE }}
# Build and push
- name: Build and push
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0
with:
context: .
file: ${{ inputs.DOCKERFILE }}

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Checkout
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install ruby
uses: ruby/setup-ruby@d5fb7a202fc07872cb44f00ba8e6197b70cb0c55 # v1.179.0
uses: ruby/setup-ruby@ff740bc00a01b3a50fffc55a1071b1060eeae9dc # v1.180.0
with:
ruby-version: "3.0"
- name: Install packagecloud

View file

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

View file

@ -21,7 +21,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
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@2e230e8fe0ad3a14a340ad0815ddb96d599d2aff # v3.25.8
uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to ghcr
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:

View file

@ -91,7 +91,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Python 3.12
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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to ghcr
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:

View file

@ -16,7 +16,7 @@ jobs:
steps:
# Prepare
- name: Checkout source code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up Python 3.12
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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Login to ghcr
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
with:

View file

@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- 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

@ -15,6 +15,7 @@
- [FEATURE] Add experimental support of HTTP/3 (QUIC)
- [FEATURE] Optimize the way the scheduler handles jobs and the way the jobs are executed
- [FEATURE] Optimize the way the cache files are being refreshed from the database
- [FEATURE] Add failover logic in case the NGINX configuration is not valid to fallback to the previous configuration and log the error to prevent the service from being stopped
- [UI] Force HTTPS on setup wizard
- [UI] Fallback to self-signed certificate when UI is installed with setup wizard and let's encrypt is not used
- [UI] Add OVERRIDE_ADMIN_CREDS environment variable to allow overriding the default admin credentials even if an admin user already exists

View file

@ -217,7 +217,7 @@ In other words, the scheduler is the brain of BunkerWeb.
## BunkerWeb Cloud
<p align="center">
<img alt="Docker banner" src="https://github.com/bunkerity/bunkerweb/raw/v1.5.8/docs/assets/img/bunkerweb-cloud.svg" />
<img alt="Docker banner" src="https://github.com/bunkerity/bunkerweb/raw/v1.5.8/docs/assets/img/bunkerweb-cloud.webp" />
</p>
BunkerWeb Cloud is the easiest way to get started with BunkerWeb. It offers you a fully managed BunkerWeb service with no hassle. Think of a like a BunkerWeb-as-a-Service !

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -3,7 +3,7 @@
## BunkerWeb Cloud
<figure markdown>
![Overview](assets/img/bunkerweb-cloud.svg){ align=center, width="600" }
![Overview](assets/img/bunkerweb-cloud.webp){ align=center, width="600" }
<figcaption>BunkerWeb Cloud</figcaption>
</figure>

View file

@ -331,9 +331,9 @@ mkdocs-print-site-plugin==2.5.0 \
--hash=sha256:48b3d41ae80384de72062b2712fce677f2e46d8364d9fe603ba837b0cf7156a4 \
--hash=sha256:95dccc8d5cc8a59da67815a2d3304ef0101b065e363f2b9ac919c23d6196dd24
# via -r requirements.in
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via
# mkdocs
# typepy

View file

@ -137,7 +137,7 @@ Besides the HTTPS / SSL/TLS configuration, the following settings related to HTT
| `AUTO_REDIRECT_HTTP_TO_HTTPS` | `yes` | When set to `yes`, will redirect every HTTP request to HTTPS only if BunkerWeb is configured with HTTPS. |
| `SSL_PROTOCOLS` | `TLSv1.2 TLSv1.3` | List of supported SSL/TLS protocols when SSL is enabled. |
| `HTTP2` | `yes` | When set to `yes`, will enable HTTP2 protocol support when using HTTPS. |
| `HTTP3` | `no` | When set to `yes`, will enable HTTP3 protocol support when using HTTPS. |
| `HTTP3` | `no` | When set to `yes`, will enable HTTP3 protocol support when using HTTPS. |
| `HTTP3_ALT_SVC_PORT` | `443` | HTTP3 alternate service port. This value will be used as part of the Alt-Svc header. |
| `LISTEN_HTTP` | `yes` | When set to `no`, BunkerWeb will not listen for HTTP requests. Useful if you want HTTPS only for example. |
@ -354,30 +354,26 @@ STREAM support :warning:
You can use the following settings to set up blacklisting :
| Setting | Default | Context | Multiple | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | -------- | ------------------------------------------------------------------------------------------------ |
| `USE_BLACKLIST` | `yes` | multisite | no | Activate blacklist feature. |
| `BLACKLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to block. |
| `BLACKLIST_IP_URLS` | `https://www.dan.me.uk/torlist/?exit` | global | no | List of URLs, separated with spaces, containing bad IP/network to block. |
| `BLACKLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS blacklist checks on global IP addresses. |
| `BLACKLIST_RDNS` | `.shodan.io .censys.io` | multisite | no | List of reverse DNS suffixes, separated with spaces, to block. |
| `BLACKLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to block. |
| `BLACKLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to block. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. |
| `BLACKLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_USER_AGENT_URLS` | `https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list` | global | no | List of URLs, separated with spaces, containing bad User-Agent to block. |
| `BLACKLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to block. |
| `BLACKLIST_IGNORE_IP` | | multisite | no | List of IP/network, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_IP_URLS` | | global | no | List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. |
| `BLACKLIST_IGNORE_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. |
| `BLACKLIST_IGNORE_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to ignore in the blacklist. |
| `BLACKLIST_IGNORE_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. |
| `BLACKLIST_IGNORE_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
| `BLACKLIST_IGNORE_URI_URLS` | | global | no | List of URLs, separated with spaces, containing URI to ignore in the blacklist. |
| Setting | Default | Context | Multiple | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USE_BLACKLIST` | `yes` | multisite | no | Activate blacklist feature. |
| `BLACKLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to block. |
| `BLACKLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS blacklist checks on global IP addresses. |
| `BLACKLIST_RDNS` | `.shodan.io .censys.io` | multisite | no | List of reverse DNS suffixes, separated with spaces, to block. |
| `BLACKLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to block. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. |
| `BLACKLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to block. |
| `BLACKLIST_IP_URLS` | `https://www.dan.me.uk/torlist/?exit` | global | no | List of URLs, separated with spaces, containing bad IP/network to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_USER_AGENT_URLS` | `https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list` | global | no | List of URLs, separated with spaces, containing bad User-Agent to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_IP_URLS` | | global | no | List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `BLACKLIST_IGNORE_URI_URLS` | | global | no | List of URLs, separated with spaces, containing URI to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -387,20 +383,20 @@ STREAM support :warning:
You can use the following settings to set up greylisting :
| Setting | Default | Context | Multiple | Description |
| -------------------------- | ------- | --------- | -------- | ---------------------------------------------------------------------------------------------- |
| `USE_GREYLIST` | `no` | multisite | no | Activate greylist feature. |
| `GREYLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to put into the greylist. |
| `GREYLIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to put into the greylist. |
| `GREYLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS greylist checks on global IP addresses. |
| `GREYLIST_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
| `GREYLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. |
| `GREYLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to put into the greylist. |
| `GREYLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to put into the greylist. |
| `GREYLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to put into the greylist. |
| `GREYLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to put into the greylist. |
| Setting | Default | Context | Multiple | Description |
| -------------------------- | ------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USE_GREYLIST` | `no` | multisite | no | Activate greylist feature. |
| `GREYLIST_IP` | | multisite | no | List of IP/network, separated with spaces, to put into the greylist. |
| `GREYLIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS greylist checks on global IP addresses. |
| `GREYLIST_RDNS` | | multisite | no | List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
| `GREYLIST_ASN` | | multisite | no | List of ASN numbers, separated with spaces, to put into the greylist. |
| `GREYLIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to put into the greylist. |
| `GREYLIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `GREYLIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
When using stream mode, only IP, RDNS and ASN checks will be done.
@ -410,20 +406,20 @@ STREAM support :warning:
You can use the following settings to set up whitelisting :
| Setting | Default | Context | Multiple | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ---------------------------------------------------------------------------------- |
| `USE_WHITELIST` | `yes` | multisite | no | Activate whitelist feature. |
| `WHITELIST_IP` | `20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8` | multisite | no | List of IP/network, separated with spaces, to put into the whitelist. |
| `WHITELIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to whitelist. |
| `WHITELIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS whitelist checks on global IP addresses. |
| `WHITELIST_RDNS` | `.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` | multisite | no | List of reverse DNS suffixes, separated with spaces, to whitelist. |
| `WHITELIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. |
| `WHITELIST_ASN` | `32934` | multisite | no | List of ASN numbers, separated with spaces, to whitelist. |
| `WHITELIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to whitelist. |
| `WHITELIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to whitelist. |
| `WHITELIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to whitelist. |
| Setting | Default | Context | Multiple | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `USE_WHITELIST` | `yes` | multisite | no | Activate whitelist feature. |
| `WHITELIST_IP` | `20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247 54.208.102.37 107.21.1.8` | multisite | no | List of IP/network, separated with spaces, to put into the whitelist. |
| `WHITELIST_RDNS_GLOBAL` | `yes` | multisite | no | Only perform RDNS whitelist checks on global IP addresses. |
| `WHITELIST_RDNS` | `.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com` | multisite | no | List of reverse DNS suffixes, separated with spaces, to whitelist. |
| `WHITELIST_ASN` | `32934` | multisite | no | List of ASN numbers, separated with spaces, to whitelist. |
| `WHITELIST_USER_AGENT` | | multisite | no | List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_URI` | | multisite | no | List of URI (PCRE regex), separated with spaces, to whitelist. |
| `WHITELIST_IP_URLS` | | global | no | List of URLs, separated with spaces, containing good IP/network to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_RDNS_URLS` | | global | no | List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_ASN_URLS` | | global | no | List of URLs, separated with spaces, containing ASN to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_USER_AGENT_URLS` | | global | no | List of URLs, separated with spaces, containing good User-Agent to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
| `WHITELIST_URI_URLS` | | global | no | List of URLs, separated with spaces, containing bad URI to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
When using stream mode, only IP, RDNS and ASN checks will be done.

View file

@ -140,30 +140,30 @@ STREAM support :warning:
Deny access based on internal and external IP/network/rDNS/ASN blacklists.
| Setting | Default | Context |Multiple| Description |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|--------|------------------------------------------------------------------------------------------------|
|`USE_BLACKLIST` |`yes` |multisite|no |Activate blacklist feature. |
|`BLACKLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to block. |
|`BLACKLIST_RDNS` |`.shodan.io .censys.io` |multisite|no |List of reverse DNS suffixes, separated with spaces, to block. |
|`BLACKLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS blacklist checks on global IP addresses. |
|`BLACKLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to block. |
|`BLACKLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_IGNORE_IP` | |multisite|no |List of IP/network, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IP_URLS` |`https://www.dan.me.uk/torlist/?exit` |global |no |List of URLs, separated with spaces, containing bad IP/network to block. |
|`BLACKLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to block. |
|`BLACKLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to block. |
|`BLACKLIST_USER_AGENT_URLS` |`https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list`|global |no |List of URLs, separated with spaces, containing bad User-Agent to block. |
|`BLACKLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to block. |
|`BLACKLIST_IGNORE_IP_URLS` | |global |no |List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist.|
|`BLACKLIST_IGNORE_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI_URLS` | |global |no |List of URLs, separated with spaces, containing URI to ignore in the blacklist. |
| Setting | Default | Context |Multiple| Description |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|---------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_BLACKLIST` |`yes` |multisite|no |Activate blacklist feature. |
|`BLACKLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to block. |
|`BLACKLIST_RDNS` |`.shodan.io .censys.io` |multisite|no |List of reverse DNS suffixes, separated with spaces, to block. |
|`BLACKLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS blacklist checks on global IP addresses. |
|`BLACKLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to block. |
|`BLACKLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to block. |
|`BLACKLIST_IGNORE_IP` | |multisite|no |List of IP/network, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IGNORE_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to ignore in the blacklist. |
|`BLACKLIST_IP_URLS` |`https://www.dan.me.uk/torlist/?exit` |global |no |List of URLs, separated with spaces, containing bad IP/network to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_USER_AGENT_URLS` |`https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list`|global |no |List of URLs, separated with spaces, containing bad User-Agent to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_IP_URLS` | |global |no |List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
|`BLACKLIST_IGNORE_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`BLACKLIST_IGNORE_URI_URLS` | |global |no |List of URLs, separated with spaces, containing URI to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
## Brotli
@ -287,20 +287,20 @@ STREAM support :warning:
Allow access while keeping security features based on internal and external IP/network/rDNS/ASN greylists.
| Setting |Default| Context |Multiple| Description |
|--------------------------|-------|---------|--------|----------------------------------------------------------------------------------------------|
|`USE_GREYLIST` |`no` |multisite|no |Activate greylist feature. |
|`GREYLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS greylist checks on global IP addresses. |
|`GREYLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to put into the greylist. |
|`GREYLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to put into the greylist. |
|`GREYLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist.|
|`GREYLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to put into the greylist. |
|`GREYLIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to put into the greylist. |
|`GREYLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to put into the greylist. |
| Setting |Default| Context |Multiple| Description |
|--------------------------|-------|---------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_GREYLIST` |`no` |multisite|no |Activate greylist feature. |
|`GREYLIST_IP` | |multisite|no |List of IP/network, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS` | |multisite|no |List of reverse DNS suffixes, separated with spaces, to put into the greylist. |
|`GREYLIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS greylist checks on global IP addresses. |
|`GREYLIST_ASN` | |multisite|no |List of ASN numbers, separated with spaces, to put into the greylist. |
|`GREYLIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to put into the greylist. |
|`GREYLIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`GREYLIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
|`GREYLIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`GREYLIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`GREYLIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
## Gzip
@ -516,14 +516,14 @@ STREAM support :warning:
Get real IP of clients when BunkerWeb is behind a reverse proxy / load balancer.
| Setting | Default | Context |Multiple| Description |
|--------------------|-----------------------------------------|---------|--------|--------------------------------------------------------------------------------------------------------|
|`USE_REAL_IP` |`no` |multisite|no |Retrieve the real IP of client. |
|`USE_PROXY_PROTOCOL`|`no` |multisite|no |Enable PROXY protocol communication. |
|`REAL_IP_FROM` |`192.168.0.0/16 172.16.0.0/12 10.0.0.0/8`|multisite|no |List of trusted IPs / networks, separated with spaces, where proxied requests come from. |
|`REAL_IP_HEADER` |`X-Forwarded-For` |multisite|no |HTTP header containing the real IP or special value proxy_protocol for PROXY protocol. |
|`REAL_IP_RECURSIVE` |`yes` |multisite|no |Perform a recursive search in the header container IP address. |
|`REAL_IP_FROM_URLS` | |global |no |List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from.|
| Setting | Default | Context |Multiple| Description |
|--------------------|-----------------------------------------|---------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_REAL_IP` |`no` |multisite|no |Retrieve the real IP of client. |
|`USE_PROXY_PROTOCOL`|`no` |multisite|no |Enable PROXY protocol communication. |
|`REAL_IP_FROM` |`192.168.0.0/16 172.16.0.0/12 10.0.0.0/8`|multisite|no |List of trusted IPs / networks, separated with spaces, where proxied requests come from. |
|`REAL_IP_HEADER` |`X-Forwarded-For` |multisite|no |HTTP header containing the real IP or special value proxy_protocol for PROXY protocol. |
|`REAL_IP_RECURSIVE` |`yes` |multisite|no |Perform a recursive search in the header container IP address. |
|`REAL_IP_FROM_URLS` | |global |no |List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
## Redirect
@ -676,17 +676,17 @@ STREAM support :warning:
Allow access based on internal and external IP/network/rDNS/ASN whitelists.
| Setting | Default | Context |Multiple| Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|----------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247` |multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com`|multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.|
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. |
| Setting | Default | Context |Multiple| Description |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`USE_WHITELIST` |`yes` |multisite|no |Activate whitelist feature. |
|`WHITELIST_IP` |`20.191.45.212 40.88.21.235 40.76.173.151 40.76.163.7 20.185.79.47 52.142.26.175 20.185.79.15 52.142.24.149 40.76.162.208 40.76.163.23 40.76.162.191 40.76.162.247` |multisite|no |List of IP/network, separated with spaces, to put into the whitelist. |
|`WHITELIST_RDNS` |`.google.com .googlebot.com .yandex.ru .yandex.net .yandex.com .search.msn.com .baidu.com .baidu.jp .crawl.yahoo.net .fwd.linkedin.com .twitter.com .twttr.com .discord.com`|multisite|no |List of reverse DNS suffixes, separated with spaces, to whitelist. |
|`WHITELIST_RDNS_GLOBAL` |`yes` |multisite|no |Only perform RDNS whitelist checks on global IP addresses. |
|`WHITELIST_ASN` |`32934` |multisite|no |List of ASN numbers, separated with spaces, to whitelist. |
|`WHITELIST_USER_AGENT` | |multisite|no |List of User-Agent (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_URI` | |multisite|no |List of URI (PCRE regex), separated with spaces, to whitelist. |
|`WHITELIST_IP_URLS` | |global |no |List of URLs, separated with spaces, containing good IP/network to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`WHITELIST_RDNS_URLS` | |global |no |List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.|
|`WHITELIST_ASN_URLS` | |global |no |List of URLs, separated with spaces, containing ASN to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`WHITELIST_USER_AGENT_URLS`| |global |no |List of URLs, separated with spaces, containing good User-Agent to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |
|`WHITELIST_URI_URLS` | |global |no |List of URLs, separated with spaces, containing bad URI to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme. |

View file

@ -239,27 +239,40 @@
=== "SQLite"
1. **Remove the existing database file.**
1. **Stop the Stack.**
```bash
docker compose down
```
2. **Remove the existing database file.**
```bash
docker exec -u 0 -i <scheduler_container> rm -f /var/lib/bunkerweb/db.sqlite3
```
2. **Restore the backup.**
3. **Restore the backup.**
```bash
docker exec -i -T <scheduler_container> sqlite3 /var/lib/bunkerweb/db.sqlite3 < /path/to/backup/directory/backup.sql
```
=== "MySQL/MariaDB"
3. **Stop the Scheduler container.**
4. **Fix permissions.**
```bash
docker compose down <scheduler_container>
docker exec -u 0 -i <scheduler_container> chown root:nginx /var/lib/bunkerweb/db.sqlite3
docker exec -u 0 -i <scheduler_container> chmod 770 /var/lib/bunkerweb/db.sqlite3
```
4. **Restore the backup.**
=== "MySQL/MariaDB"
5. **Stop the Stack.**
```bash
docker compose down
```
6. **Restore the backup.**
```bash
docker exec -e MYSQL_PWD=<your_password> -i -T <database_container> mysql -u <username> <database_name> < /path/to/backup/directory/backup.sql
@ -267,25 +280,25 @@
=== "PostgreSQL"
5. **Stop the Scheduler container.**
7. **Stop the Stack.**
```bash
docker compose down <scheduler_container>
docker compose down
```
6. **Remove the existing database.**
8. **Remove the existing database.**
```bash
docker exec -i <database_container> dropdb -U <username> --force <database_name>
```
7. **Recreate the database.**
9. **Recreate the database.**
```bash
docker exec -i <database_container> createdb -U <username> <database_name>
```
8. **Restore the backup.**
10. **Restore the backup.**
```bash
docker exec -i -T <database_container> psql -U <username> -d <database_name> < /path/to/backup/directory/backup.sql
@ -309,10 +322,9 @@
...
```
3. **Restart the containers**.
3. **Start the containers**.
```bash
docker compose down
docker compose up -d
```
@ -321,8 +333,7 @@
4. **Stop the services**.
```bash
systemctl stop bunkerweb
systemctl stop bunkerweb-ui
systemctl stop bunkerweb bunkerweb-ui
```
5. **Restore the backup**.
@ -330,8 +341,10 @@
=== "SQLite"
```bash
rm -f /var/lib/bunkerweb/db.sqlite3
sqlite3 /var/lib/bunkerweb/db.sqlite3 < /path/to/backup/directory/backup.sql
sudo rm -f /var/lib/bunkerweb/db.sqlite3
sudo sqlite3 /var/lib/bunkerweb/db.sqlite3 < /path/to/backup/directory/backup.sql
sudo chown root:nginx /var/lib/bunkerweb/db.sqlite3
sudo chmod 770 /var/lib/bunkerweb/db.sqlite3
```
=== "MySQL/MariaDB"

View file

@ -125,6 +125,7 @@ services:
- "bunkerweb.REVERSE_PROXY_URL=/admin"
- "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
- "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
- "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
- bunkerweb.CUSTOM_CONF_MODSEC_CRS_ip-host=SecRuleRemoveById 920350
bw-db:

View file

@ -122,6 +122,7 @@ services:
- "bunkerweb.REVERSE_PROXY_URL=/admin"
- "bunkerweb.REVERSE_PROXY_HOST=http://bw-ui:7000"
- "bunkerweb.INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504"
- "bunkerweb.GENERATE_SELF_SIGNED_SSL=yes"
bw-db:
image: mariadb:11

View file

@ -34,6 +34,7 @@ services:
- www.example.com_REVERSE_PROXY_URL=/admin
- www.example.com_REVERSE_PROXY_HOST=http://bw-ui:7000
- www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
- www.example.com_GENERATE_SELF_SIGNED_SSL=yes
- www.example.com_CUSTOM_CONF_MODSEC_CRS_ip-host=SecRuleRemoveById 920350
- app1.example.com_USE_REVERSE_PROXY=yes
- app1.example.com_REVERSE_PROXY_URL=/

View file

@ -32,6 +32,7 @@ services:
- www.example.com_REVERSE_PROXY_URL=/admin
- www.example.com_REVERSE_PROXY_HOST=http://bw-ui:7000
- www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
- www.example.com_GENERATE_SELF_SIGNED_SSL=yes
- app1.example.com_USE_REVERSE_PROXY=yes
- app1.example.com_REVERSE_PROXY_URL=/
- app1.example.com_REVERSE_PROXY_HOST=http://app1:8080

View file

@ -17,3 +17,4 @@ www.example.com_USE_REVERSE_PROXY=yes
www.example.com_REVERSE_PROXY_URL=/admin
www.example.com_REVERSE_PROXY_HOST=http://127.0.0.1:7000
www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
www.example.com_GENERATE_SELF_SIGNED_SSL=yes

View file

@ -18,4 +18,5 @@ www.example.com_USE_REVERSE_PROXY=yes
www.example.com_REVERSE_PROXY_URL=/admin
www.example.com_REVERSE_PROXY_HOST=http://127.0.0.1:7000
www.example.com_INTERCEPTED_ERROR_CODES=400 404 405 413 429 500 501 502 503 504
www.example.com_GENERATE_SELF_SIGNED_SSL=yes
EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/heads/dev.zip

View file

@ -1,4 +1,4 @@
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb as builder
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c as builder
# Export var for specific actions on linux/arm/v7
ARG TARGETPLATFORM
@ -31,7 +31,7 @@ COPY src/common/helpers helpers
COPY src/common/settings.json settings.json
COPY src/common/utils utils
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -10,9 +10,9 @@ ssl_dhparam /etc/nginx/dhparam;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
{% endif %}
listen 0.0.0.0:{{ LISTEN_STREAM_PORT_SSL }} ssl {% if USE_UDP == "yes" %} udp {% endif %}{% if USE_PROXY_PROTOCOL == "yes" %} proxy_protocol {% endif %};
listen 0.0.0.0:{{ LISTEN_STREAM_PORT_SSL }} ssl {% if USE_PROXY_PROTOCOL == "yes" %} proxy_protocol {% endif %};
{% if USE_IPV6 == "yes" +%}
listen [::]:{{ LISTEN_STREAM_PORT_SSL }} ssl {% if USE_UDP == "yes" %} udp {% endif %}{% if USE_PROXY_PROTOCOL == "yes" %} proxy_protocol {% endif %};
listen [::]:{{ LISTEN_STREAM_PORT_SSL }} ssl {% if USE_PROXY_PROTOCOL == "yes" %} proxy_protocol {% endif %};
{% endif %}
ssl_certificate_by_lua_block {

View file

@ -116,7 +116,7 @@
"BLACKLIST_IP_URLS": {
"context": "global",
"default": "https://www.dan.me.uk/torlist/?exit",
"help": "List of URLs, separated with spaces, containing bad IP/network to block.",
"help": "List of URLs, separated with spaces, containing bad IP/network to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ip-urls",
"label": "Blacklist IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -125,7 +125,7 @@
"BLACKLIST_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to block.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-rdns-urls",
"label": "Blacklist reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -134,7 +134,7 @@
"BLACKLIST_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to block.",
"help": "List of URLs, separated with spaces, containing ASN to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-asn-urls",
"label": "Blacklist ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -143,7 +143,7 @@
"BLACKLIST_USER_AGENT_URLS": {
"context": "global",
"default": "https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents.list",
"help": "List of URLs, separated with spaces, containing bad User-Agent to block.",
"help": "List of URLs, separated with spaces, containing bad User-Agent to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-user-agent-urls",
"label": "Blacklist User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -152,7 +152,7 @@
"BLACKLIST_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing bad URI to block.",
"help": "List of URLs, separated with spaces, containing bad URI to block. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-uri-urls",
"label": "Blacklist URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -161,7 +161,7 @@
"BLACKLIST_IGNORE_IP_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing IP/network to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing IP/network to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-ip-urls",
"label": "Blacklist ignore IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -170,7 +170,7 @@
"BLACKLIST_IGNORE_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-rdns-urls",
"label": "Blacklist ignore reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -179,7 +179,7 @@
"BLACKLIST_IGNORE_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing ASN to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-asn-urls",
"label": "Blacklist ignore ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -188,7 +188,7 @@
"BLACKLIST_IGNORE_USER_AGENT_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing User-Agent to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-user-agent-urls",
"label": "Blacklist ignore User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -197,7 +197,7 @@
"BLACKLIST_IGNORE_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing URI to ignore in the blacklist.",
"help": "List of URLs, separated with spaces, containing URI to ignore in the blacklist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "blacklist-ignore-uri-urls",
"label": "Blacklist ignore URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python3
from contextlib import suppress
from os import getenv, sep
from os.path import join
from pathlib import Path
@ -21,8 +20,8 @@ LOGGER = setup_logger("CUSTOM-CERT", getenv("LOG_LEVEL", "INFO"))
JOB = Job(LOGGER)
def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], first_server: str) -> Tuple[bool, str]:
with suppress(BaseException):
def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], first_server: str) -> Tuple[bool, Union[str, BaseException]]:
try:
ret = False
if not cert_file or not key_file:
return False, "Both variables CUSTOM_SSL_CERT and CUSTOM_SSL_KEY have to be set to use custom certificates"
@ -54,7 +53,8 @@ def check_cert(cert_file: Union[Path, bytes], key_file: Union[Path, bytes], firs
LOGGER.error(f"Error while caching custom-key key.pem file : {err}")
return ret, ""
return False, "exception"
except BaseException as e:
return False, e
status = 0
@ -112,8 +112,8 @@ try:
LOGGER.info(f"Checking certificate for {first_server} ...")
need_reload, err = check_cert(cert_file, key_file, first_server)
if err == "exception":
LOGGER.exception(f"Exception while checking {first_server}'s certificate, skipping ...")
if isinstance(err, BaseException):
LOGGER.error(f"Exception while checking {first_server}'s certificate, skipping ... \n{err}")
skipped_servers.append(first_server)
continue
elif err:

View file

@ -71,7 +71,7 @@
"GREYLIST_IP_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good IP/network to put into the greylist.",
"help": "List of URLs, separated with spaces, containing good IP/network to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-ip-urls",
"label": "Greylist IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -80,7 +80,7 @@
"GREYLIST_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-rdns-urls",
"label": "Greylist reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -89,7 +89,7 @@
"GREYLIST_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to put into the greylist.",
"help": "List of URLs, separated with spaces, containing ASN to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-asn-urls",
"label": "Greylist ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -98,7 +98,7 @@
"GREYLIST_USER_AGENT_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good User-Agent to put into the greylist.",
"help": "List of URLs, separated with spaces, containing good User-Agent to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-user-agent-urls",
"label": "Greylist User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -107,7 +107,7 @@
"GREYLIST_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing bad URI to put into the greylist.",
"help": "List of URLs, separated with spaces, containing bad URI to put into the greylist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "greylist-uri-urls",
"label": "Greylist URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
from os import getenv, sep
from os.path import join
from sys import exit as sys_exit, path as sys_path
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 jobs import Job # type: ignore
from logger import setup_logger # type: ignore
LOGGER = setup_logger("FAILOVER-BACKUP", getenv("LOG_LEVEL", "INFO"))
status = 0
try:
# Restoring the backup failover configuration
JOB = Job(LOGGER)
except:
status = 2
LOGGER.error(f"Exception while running failover-backup.py :\n{format_exc()}")
sys_exit(status)

View file

@ -84,8 +84,8 @@ try:
for chunk in resp.iter_content(chunk_size=4 * 1024):
if chunk:
file_content.write(chunk)
except RequestException:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}")
except RequestException as e:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}: {e}")
sys_exit(2)
try:

View file

@ -84,8 +84,8 @@ try:
for chunk in resp.iter_content(chunk_size=4 * 1024):
if chunk:
file_content.write(chunk)
except RequestException:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}")
except RequestException as e:
LOGGER.error(f"Error while downloading mmdb file from {mmdb_url}: {e}")
sys_exit(2)
try:

View file

@ -0,0 +1,87 @@
#!/usr/bin/env python3
from os import getenv, sep
from os.path import join
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from requests import get
from common_utils import get_version # type: ignore
from logger import setup_logger # type: ignore
LOGGER = setup_logger("UPDATE-CHECK", getenv("LOG_LEVEL", "INFO"))
status = 0
try:
def get_latest_stable_release():
response = get("https://api.github.com/repos/bunkerity/bunkerweb/releases", headers={"User-Agent": "BunkerWeb"}, timeout=3)
response.raise_for_status()
releases = response.json()
for release in releases:
if not release["prerelease"]:
return release
return None
latest_release = get_latest_stable_release()
if not latest_release:
status = 1
LOGGER.error("Failed to fetch latest release information")
sys_exit(status)
current_version = get_version()
latest_version = latest_release["tag_name"].removeprefix("v")
if current_version != latest_version:
# Version details
latest_version_text = f"\033[1;92m{latest_version}\033[0m"
current_version_text = f"\033[1;93m{current_version}\033[0m"
release_notes_url = f"https://github.com/bunkerity/bunkerweb/releases/v{latest_version}"
release_notes_url_text = f"\033[4;94m{release_notes_url}\033[0m"
# Centering based on the longest line length
alert_message = "🚨 A NEW VERSION OF BUNKERWEB IS AVAILABLE! 🚨"
latest_version_line = f"Latest Version: {latest_version_text}"
current_version_line = f"Current Version: {current_version_text}"
release_notes_url_line = f"Release Notes: {release_notes_url_text}"
# Determine the longest line length
longest_line_length = max(
len(alert_message),
len(latest_version_line),
len(current_version_line),
len(release_notes_url_line),
)
# Centering the lines within the box width
alert_message_padded = alert_message.center(longest_line_length - 13)
latest_version_padded = latest_version_line.center(longest_line_length)
current_version_padded = current_version_line.center(longest_line_length)
LOGGER.warning(
(
f"\n\033[1;91m{'*' * (longest_line_length - 5)}\n"
f"\033[1;91m*{' ' * (longest_line_length - 7)}*\n"
f"\033[1;91m* \033[1;97m{alert_message_padded}\033[0m \033[0;91m*\n"
f"\033[1;91m*{' ' * (longest_line_length - 7)}*\n"
f"\033[1;91m* \033[1;97m{latest_version_padded}\033[0;91m *\n"
f"\033[1;91m* \033[1;97m{current_version_padded}\033[0;91m *\n"
f"\033[1;91m*{' ' * (longest_line_length - 7)}*\n"
f"\033[1;91m* \033[1;97m{release_notes_url_line}\033[0;91m *\n"
f"\033[1;91m*{' ' * (longest_line_length - 7)}*\n"
f"\033[1;91m{'*' * (longest_line_length - 5)}\033[0m"
)
)
else:
LOGGER.info(f"Latest version is already installed: {current_version}")
except:
status = 2
LOGGER.error(f"Exception while running update-check.py :\n{format_exc()}")
sys_exit(status)

View file

@ -17,6 +17,18 @@
"file": "mmdb-asn.py",
"every": "day",
"reload": true
},
{
"name": "update-check",
"file": "update-check.py",
"every": "day",
"reload": false
},
{
"name": "failover-backup",
"file": "failover-backup.py",
"every": "once",
"reload": false
}
]
}

View file

@ -41,7 +41,7 @@ try:
if key not in ("version", "integration", "database_version", "is_pro"):
data.pop(key, None)
db_config = JOB.db.get_config(methods=True, with_drafts=True)
db_config = JOB.db.get_non_default_settings(methods=True, with_drafts=True)
services = db_config.get("SERVER_NAME", {"value": ""})["value"].split(" ")
multisite = db_config.get("MULTISITE", {"value": "no"})["value"] == "yes"
@ -85,7 +85,7 @@ try:
data["non_default_settings"] = {}
for setting, setting_data in db_config.items():
if isinstance(setting_data, dict) and setting_data["method"] != "default":
if isinstance(setting_data, dict):
for server in services:
if setting.startswith(server + "_"):
setting = setting[len(server) + 1 :] # noqa: E203

View file

@ -1,35 +0,0 @@
#!/usr/bin/env python3
from os import getenv, sep
from os.path import basename, join
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from requests import get
from common_utils import get_version # type: ignore
from logger import setup_logger # type: ignore
LOGGER = setup_logger("UPDATE-CHECK", getenv("LOG_LEVEL", "INFO"))
status = 0
try:
current_version = f"v{get_version().strip()}"
response = get("https://github.com/bunkerity/bunkerweb/releases/latest", headers={"User-Agent": "BunkerWeb"}, allow_redirects=True, timeout=10)
response.raise_for_status()
latest_version = basename(response.url)
if current_version != latest_version:
LOGGER.warning(f"* \n* \n* 🚨 A new version of BunkerWeb is available: {latest_version} (current: {current_version}) 🚨\n* \n* ")
else:
LOGGER.info(f"Latest version is already installed: {current_version}")
except:
status = 2
LOGGER.error(f"Exception while running update-check.py :\n{format_exc()}")
sys_exit(status)

View file

@ -203,12 +203,6 @@
"every": "once",
"reload": false
},
{
"name": "update-check",
"file": "update-check.py",
"every": "day",
"reload": false
},
{
"name": "anonymous-report",
"file": "anonymous-report.py",

View file

@ -96,7 +96,6 @@ def install_plugin(plugin_path: Path, db, preview: bool = True) -> bool:
try:
db = Database(LOGGER, sqlalchemy_string=getenv("DATABASE_URI"))
db_metadata = db.get_metadata()
db_config = db.get_config()
current_date = datetime.now()
pro_license_key = getenv("PRO_LICENSE_KEY", "").strip()
@ -111,6 +110,7 @@ try:
headers = {"User-Agent": f"BunkerWeb/{data['version']}"}
default_metadata = {
"is_pro": False,
"pro_license": pro_license_key,
"pro_expire": None,
"pro_status": "invalid",
"pro_overlapped": False,
@ -158,7 +158,7 @@ try:
# ? If we already checked today, skip the check and if the metadata is the same, skip the check
if (
pro_license_key == db_config["PRO_LICENSE_KEY"]
pro_license_key == db_metadata.get("pro_license", "")
and metadata.get("is_pro", False) == db_metadata["is_pro"]
and db_metadata["last_pro_check"]
and current_date.replace(hour=0, minute=0, second=0, microsecond=0) == db_metadata["last_pro_check"].replace(hour=0, minute=0, second=0, microsecond=0)

View file

@ -53,7 +53,7 @@
"REAL_IP_FROM_URLS": {
"context": "global",
"default": "",
"help": "List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from.",
"help": "List of URLs containing trusted IPs / networks, separated with spaces, where proxied requests come from. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "real-ip-from-urls",
"label": "Real IP from URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -71,7 +71,7 @@
"WHITELIST_IP_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good IP/network to whitelist.",
"help": "List of URLs, separated with spaces, containing good IP/network to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-ip-urls",
"label": "Whitelist IP/network URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -80,7 +80,7 @@
"WHITELIST_RDNS_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist.",
"help": "List of URLs, separated with spaces, containing reverse DNS suffixes to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-rdns-urls",
"label": "Whitelist reverse DNS URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -89,7 +89,7 @@
"WHITELIST_ASN_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing ASN to whitelist.",
"help": "List of URLs, separated with spaces, containing ASN to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-asn-urls",
"label": "Whitelist ASN URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -98,7 +98,7 @@
"WHITELIST_USER_AGENT_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing good User-Agent to whitelist.",
"help": "List of URLs, separated with spaces, containing good User-Agent to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-user-agent-urls",
"label": "Whitelist User-Agent URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",
@ -107,7 +107,7 @@
"WHITELIST_URI_URLS": {
"context": "global",
"default": "",
"help": "List of URLs, separated with spaces, containing bad URI to whitelist.",
"help": "List of URLs, separated with spaces, containing bad URI to whitelist. Also supports file:// URLs and and auth basic using http://user:pass@url scheme.",
"id": "whitelist-uri-urls",
"label": "Whitelist URI URLs",
"regex": "^( *((https?:\\/\\/|file:\\/\\/\\/)[\\-\\w@:%.+~#=]+[\\-\\w\\(\\)!@:%+.~#?&\\/=$]*)(?!.*\\2(?!.)) *)*$",

View file

@ -6,9 +6,9 @@ from datetime import datetime
from io import BytesIO
from logging import Logger
from os import _exit, getenv, listdir, sep
from os.path import join
from os.path import join as os_join
from pathlib import Path
from re import compile as re_compile
from re import compile as re_compile, escape, search
from sys import argv, path as sys_path
from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union
from time import sleep
@ -33,14 +33,14 @@ from model import (
Metadata,
)
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",))]:
for deps_path in [os_join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",))]:
if deps_path not in sys_path:
sys_path.append(deps_path)
from common_utils import bytes_hash # type: ignore
from pymysql import install_as_MySQLdb
from sqlalchemy import create_engine, event, MetaData as sql_metadata, text, inspect
from sqlalchemy import create_engine, event, MetaData as sql_metadata, join, select as db_select, text, inspect
from sqlalchemy.engine import Engine
from sqlalchemy.exc import (
ArgumentError,
@ -343,7 +343,7 @@ class Database:
return ""
def set_pro_metadata(self, data: Dict[Literal["is_pro", "pro_expire", "pro_status", "pro_overlapped", "pro_services"], Any] = {}) -> str:
def set_pro_metadata(self, data: Dict[Literal["is_pro", "pro_license", "pro_expire", "pro_status", "pro_overlapped", "pro_services"], Any] = {}) -> str:
"""Set the pro metadata values"""
with self.__db_session() as session:
if self.readonly:
@ -396,10 +396,29 @@ class Database:
try:
if multisite:
return session.query(Settings).filter_by(id=setting, context="multisite").first() is not None
return session.query(Settings).filter_by(name=setting).first() is not None
return session.query(Settings).filter_by(id=setting).first() is not None
except (ProgrammingError, OperationalError):
return False
def set_failover(self, value: bool = True) -> str:
"""Set the failover 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)
if not metadata:
return "The metadata are not set yet, try again"
metadata.failover = value
session.commit()
except BaseException as e:
return str(e)
return ""
def initialize_db(self, version: str, integration: str = "Unknown") -> str:
"""Initialize the database"""
with self.__db_session() as session:
@ -428,18 +447,20 @@ class Database:
return ""
def get_metadata(self) -> Dict[str, str]:
def get_metadata(self) -> Dict[str, Any]:
"""Get the metadata from the database"""
data = {
"version": "1.5.8",
"integration": "unknown",
"database_version": "Unknown",
"is_pro": "no",
"pro_license": "",
"pro_expire": None,
"pro_services": 0,
"pro_overlapped": False,
"pro_status": "invalid",
"last_pro_check": None,
"failover": False,
"default": True,
}
with self.__db_session() as session:
@ -454,11 +475,13 @@ class Database:
Metadata.version,
Metadata.integration,
Metadata.is_pro,
Metadata.pro_license,
Metadata.pro_expire,
Metadata.pro_services,
Metadata.pro_overlapped,
Metadata.pro_status,
Metadata.last_pro_check,
Metadata.failover,
)
.filter_by(id=1)
.first()
@ -469,11 +492,13 @@ class Database:
"version": metadata.version,
"integration": metadata.integration,
"is_pro": metadata.is_pro,
"pro_license": metadata.pro_license,
"pro_expire": metadata.pro_expire,
"pro_services": metadata.pro_services,
"pro_overlapped": metadata.pro_overlapped,
"pro_status": metadata.pro_status,
"last_pro_check": metadata.last_pro_check,
"failover": metadata.failover,
"default": False,
}
)
@ -1508,12 +1533,121 @@ class Database:
return message
def get_config(self, global_only: bool = False, methods: bool = False, with_drafts: bool = False) -> Dict[str, Any]:
def get_non_default_settings(
self,
global_only: bool = False,
methods: bool = False,
with_drafts: bool = False,
filtered_settings: Optional[Union[List[str], Set[str], Tuple[str]]] = None,
*,
original_config: Optional[Dict[str, Any]] = None,
original_multisite: Optional[Set[str]] = None,
) -> Dict[str, Any]:
"""Get the config from the database"""
filtered_settings = set(filtered_settings or [])
if filtered_settings and not global_only:
filtered_settings.update(("SERVER_NAME", "MULTISITE"))
with self.__db_session() as session:
config = original_config or {}
multisite = original_multisite or set()
# Define the join operation
j = join(Settings, Global_values, Settings.id == Global_values.setting_id)
# Define the select statement
stmt = (
db_select(Settings.id.label("setting_id"), Settings.context, Settings.multiple, Global_values.value, Global_values.suffix, Global_values.method)
.select_from(j)
.order_by(Settings.order)
)
if filtered_settings:
stmt = stmt.where(Settings.id.in_(filtered_settings))
# Execute the query and fetch all results
results = session.execute(stmt).fetchall()
for global_value in results:
setting_id = global_value.setting_id + (f"_{global_value.suffix}" if global_value.multiple and global_value.suffix > 0 else "")
config[setting_id] = global_value.value if not methods else {"value": global_value.value, "global": True, "method": global_value.method}
if global_value.context == "multisite":
multisite.add(setting_id)
services = session.query(Services).with_entities(Services.id, Services.is_draft)
if not with_drafts:
services = services.filter_by(is_draft=False)
servers = ""
for service in services:
if not global_only:
config[f"{service.id}_IS_DRAFT"] = "yes" if service.is_draft else "no"
if methods:
config[f"{service.id}_IS_DRAFT"] = {"value": config[f"{service.id}_IS_DRAFT"], "global": False, "method": "default"}
for key in multisite:
config[f"{service.id}_{key}"] = config[key]
servers += f"{service.id} "
servers = servers.strip()
config["SERVER_NAME"] = servers if not methods else {"value": servers, "global": True, "method": "default"}
if not global_only and (config.get("MULTISITE", {"value": "no"})["value"] == "yes" if methods else config.get("MULTISITE", "no") == "yes"):
# Define the join operation
j = join(Services, Services_settings, Services.id == Services_settings.service_id)
j = j.join(Settings, Settings.id == Services_settings.setting_id)
# Define the select statement
stmt = (
db_select(
Services.id.label("service_id"),
Settings.id.label("setting_id"),
Settings.multiple,
Services_settings.value,
Services_settings.suffix,
Services_settings.method,
)
.select_from(j)
.order_by(Services.id, Settings.order)
)
if not with_drafts:
stmt = stmt.where(Services.is_draft == False) # noqa: E712
if filtered_settings:
stmt = stmt.where(Settings.id.in_(filtered_settings))
# Execute the query and fetch all results
results = session.execute(stmt).fetchall()
for result in results:
value = result.value
if result.setting_id == "SERVER_NAME" and not search(r"^" + escape(result.service_id) + r"( |$)", value):
split = set(value.split(" "))
split.discard(result.service_id)
value = result.service_id + " " + " ".join(split)
config[f"{result.service_id}_{result.setting_id}" + (f"_{result.suffix}" if result.multiple and result.suffix else "")] = (
value if not methods else {"value": value, "global": False, "method": result.method}
)
return config
def get_config(
self,
global_only: bool = False,
methods: bool = False,
with_drafts: bool = False,
filtered_settings: Optional[Union[List[str], Set[str], Tuple[str]]] = None,
) -> Dict[str, Any]:
"""Get the config from the database"""
with self.__db_session() as session:
config = {}
multisite = []
for setting in (
multisite = set()
query = (
session.query(Settings)
.with_entities(
Settings.id,
@ -1522,81 +1656,25 @@ class Database:
Settings.multiple,
)
.order_by(Settings.order)
):
)
if filtered_settings:
query = query.filter(Settings.id.in_(filtered_settings))
for setting in query:
default = setting.default or ""
config[setting.id] = default if not methods else {"value": default, "global": True, "method": "default"}
for global_value in (
session.query(Global_values).with_entities(Global_values.value, Global_values.suffix, Global_values.method).filter_by(setting_id=setting.id)
):
config[setting.id + (f"_{global_value.suffix}" if setting.multiple and global_value.suffix > 0 else "")] = (
global_value.value
if not methods
else {
"value": global_value.value,
"global": True,
"method": global_value.method,
}
)
if setting.context == "multisite":
multisite.append(setting.id)
multisite.add(setting.id)
is_multisite = config.get("MULTISITE", {"value": "no"})["value"] == "yes" if methods else config.get("MULTISITE", "no") == "yes"
services = session.query(Services).with_entities(Services.id, Services.is_draft)
if not with_drafts:
services = services.filter_by(is_draft=False)
if not global_only and is_multisite:
for service in services:
config[f"{service.id}_IS_DRAFT"] = "yes" if service.is_draft else "no"
if methods:
config[f"{service.id}_IS_DRAFT"] = {"value": config[f"{service.id}_IS_DRAFT"], "global": False, "method": "default"}
checked_settings = []
for key, value in config.copy().items():
original_key = key
if self.suffix_rx.search(key):
key = key[: -len(str(key.split("_")[-1])) - 1]
if key not in multisite:
continue
elif f"{service.id}_{original_key}" not in config:
config[f"{service.id}_{original_key}"] = value
if original_key not in checked_settings:
checked_settings.append(original_key)
else:
continue
for service_setting in (
session.query(Services_settings)
.with_entities(
Services_settings.value,
Services_settings.suffix,
Services_settings.method,
)
.filter_by(service_id=service.id, setting_id=key)
):
value = service_setting.value
if key == "SERVER_NAME" and service.id not in value.split(" "):
value = f"{service.id} {value}".strip()
config[f"{service.id}_{key}" + (f"_{service_setting.suffix}" if service_setting.suffix > 0 else "")] = (
value
if not methods
else {
"value": value,
"global": False,
"method": service_setting.method,
}
)
servers = " ".join(service.id for service in services)
config["SERVER_NAME"] = servers if not methods else {"value": servers, "global": True, "method": "default"}
return config
return self.get_non_default_settings(
global_only=global_only,
methods=methods,
with_drafts=with_drafts,
filtered_settings=filtered_settings,
original_config=config,
original_multisite=multisite,
)
def get_custom_configs(self) -> List[Dict[str, Any]]:
"""Get the custom configs from the database"""

View file

@ -234,6 +234,7 @@ class Metadata(Base):
id = Column(Integer, primary_key=True, default=1)
is_initialized = Column(Boolean, nullable=False)
is_pro = Column(Boolean, default=False, nullable=False)
pro_license = Column(String(128), default="", nullable=True)
pro_expire = Column(DateTime, nullable=True)
pro_status = Column(PRO_STATUS_ENUM, default="invalid", nullable=False)
pro_services = Column(Integer, default=0, nullable=False)
@ -250,5 +251,6 @@ class Metadata(Base):
last_pro_plugins_change = Column(DateTime, nullable=True)
instances_changed = Column(Boolean, default=False, nullable=True)
last_instances_change = Column(DateTime, nullable=True)
failover = Column(Boolean, default=None, nullable=True)
integration = Column(INTEGRATIONS_ENUM, default="Unknown", nullable=False)
version = Column(String(32), default="1.5.8", nullable=False)

View file

@ -222,9 +222,9 @@ sqlalchemy==2.0.30 \
--hash=sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c \
--hash=sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6
# via -r requirements.armv7.in
typing-extensions==4.12.1 \
--hash=sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a \
--hash=sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via
# psycopg
# psycopg-pool

View file

@ -284,9 +284,9 @@ sqlalchemy==2.0.30 \
--hash=sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c \
--hash=sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6
# via -r requirements.in
typing-extensions==4.12.1 \
--hash=sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a \
--hash=sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via
# psycopg
# psycopg-pool

View file

@ -2,4 +2,4 @@ docker==7.1.0
jinja2==3.1.4
kubernetes==30.1.0
python-dotenv==1.0.1
redis==5.0.5
redis==5.0.6

View file

@ -269,9 +269,9 @@ pyyaml==6.0.1 \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
# via kubernetes
redis==5.0.5 \
--hash=sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada \
--hash=sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae
redis==5.0.6 \
--hash=sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197 \
--hash=sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee
# via -r requirements.in
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \

View file

@ -71,15 +71,15 @@
},
{
"id": "libmaxminddb",
"name": "libmaxminddb v1.9.1",
"name": "libmaxminddb v1.10.0",
"url": "https://github.com/maxmind/libmaxminddb.git",
"commit": "e26013e1d2b57eff0ed22b7364270358adb72205"
"commit": "7acfe43a72a5043d01cc3dd6429005acdf812cb3"
},
{
"id": "lua-cjson",
"name": "lua-cjson v2.1.0.12",
"name": "lua-cjson v2.1.0.14",
"url": "https://github.com/openresty/lua-cjson.git",
"commit": "881accc8fadca5ec02aa34d364df2a1aa25cd2f9"
"commit": "f95cd9ea1e39221a36818772eb85f05b4164bbb1"
},
{
"id": "lua-ffi-zlib",

View file

@ -125,9 +125,9 @@ importlib-metadata==7.1.0 \
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
# via build
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:

View file

@ -16,9 +16,9 @@ importlib-metadata==7.1.0 \
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
# via build
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:

View file

@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.9)
project(maxminddb
LANGUAGES C
VERSION 1.9.1
VERSION 1.10.0
)
set(MAXMINDDB_SOVERSION 0.0.7)
set(CMAKE_C_STANDARD 99)
@ -13,6 +13,8 @@ if (WIN32)
endif()
option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF)
option(BUILD_TESTING "Build test programs" ON)
option(MAXMINDDB_BUILD_BINARIES "Build binaries" ON)
option(MAXMINDDB_INSTALL "Generate the install target" ON)
include(GNUInstallDirs)
@ -90,17 +92,20 @@ set(MAXMINDB_HEADERS
)
set_target_properties(maxminddb PROPERTIES PUBLIC_HEADER "${MAXMINDB_HEADERS}")
install(TARGETS maxminddb
EXPORT maxminddb)
if (MAXMINDDB_INSTALL)
install(TARGETS maxminddb
EXPORT maxminddb)
# This is required to work with FetchContent
install(EXPORT maxminddb
FILE maxminddb-config.cmake
NAMESPACE maxminddb::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maxminddb)
# This is required to work with FetchContent
install(EXPORT maxminddb
FILE maxminddb-config.cmake
NAMESPACE maxminddb::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/maxminddb)
endif()
# We always want to build mmdblookup
add_subdirectory(bin)
if (MAXMINDDB_BUILD_BINARIES)
add_subdirectory(bin)
endif()
if (BUILD_TESTING)
enable_testing()
@ -110,14 +115,16 @@ endif()
# Generate libmaxminddb.pc file for pkg-config
# Set the required variables as same with autotools
set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix \${prefix})
set(libdir \${exec_prefix}/lib)
set(includedir \${prefix}/include)
set(exec_prefix ${CMAKE_INSTALL_PREFIX})
set(libdir ${CMAKE_INSTALL_LIBDIR})
set(includedir ${CMAKE_INSTALL_INCLUDEDIR})
set(PACKAGE_VERSION ${maxminddb_VERSION})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libmaxminddb.pc.in
${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
@ONLY)
if (MAXMINDDB_INSTALL)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/libmaxminddb.pc.in
${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
@ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/src/libmaxminddb.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()

View file

@ -1,3 +1,15 @@
## 1.10.0 - 2024-06-10
* When building with CMake, it is now possible to disable the building
of binaries (e.g., `mmdblookup`) with the `MAXMINDDB_BUILD_BINARIES`
option and the install target generation with the `MAXMINDDB_INSTALL`
option. Pull request by Seena Fallah. GitHub #342.
* CMake now makes greater use of GNUInstallDirs. Pull request by Maximilian
Downey Twiss. GitHub #346.
* The reader can now lookup records on a database with a search tree
that is greater than 4 gigabytes without sometimes returning erroneous
results due to an integer overflow.
## 1.9.1 - 2024-01-09
* `SSIZE_MAX` is now defined conditionally on Windows. The 1.9.0
@ -230,7 +242,7 @@
code to think it had found valid metadata when none existed. In addition,
this could lead to an attempt to read past the end of the database
entirely. Finally, if there are multiple metadata markers in the database,
we treat the final one as the start of the metdata, instead of the first.
we treat the final one as the start of the metadata, instead of the first.
Implemented by Tobias Stoeckmann. GitHub #102.
* Don't attempt to mmap a file that is too large to be mmapped on the
system. Implemented by Tobias Stoeckmann. GitHub #101.

View file

@ -15,8 +15,10 @@ if(NOT MSVC)
target_link_libraries(mmdblookup maxminddb pthread)
install(
TARGETS mmdblookup
DESTINATION bin
)
if (MAXMINDDB_INSTALL)
install(
TARGETS mmdblookup
DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
endif()

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.63])
AC_INIT([libmaxminddb], [1.9.1], [support@maxmind.com])
AC_INIT([libmaxminddb], [1.10.0], [support@maxmind.com])
AC_CONFIG_SRCDIR([include/maxminddb.h])
AC_CONFIG_HEADERS([config.h include/maxminddb_config.h])

View file

@ -9,8 +9,10 @@ mmdblookup --file [FILE PATH] --ip [IP ADDRESS] [DATA PATH]
# DESCRIPTION
`mmdblookup` looks up an IP address in the specified MaxMind DB file. The
record for the IP address is displayed in a JSON-like structure with type
annotations.
record for the IP address is displayed with `{}` to denote maps and `[]` to
denote arrays. The values are followed by type annotations. This output is
_not_ JSON and is not intended to be used as such. If you need JSON, please
see [`mmdbinspect`](https://github.com/maxmind/mmdbinspect).
If an IP's data entry resolves to a map or array, you can provide a lookup
path to only show part of that data.

View file

@ -2,7 +2,7 @@
#define MAXMINDDB_CONFIG_H
#ifndef MMDB_UINT128_USING_MODE
/* Define as 1 if we use unsigned int __atribute__ ((__mode__(TI))) for uint128 values */
/* Define as 1 if we use unsigned int __attribute__ ((__mode__(TI))) for uint128 values */
#cmakedefine MMDB_UINT128_USING_MODE @MMDB_UINT128_USING_MODE@
#endif

View file

@ -2,7 +2,7 @@
#define MAXMINDDB_CONFIG_H
#ifndef MMDB_UINT128_USING_MODE
/* Define as 1 if we use unsigned int __atribute__ ((__mode__(TI))) for uint128 values */
/* Define as 1 if we use unsigned int __attribute__ ((__mode__(TI))) for uint128 values */
#define MMDB_UINT128_USING_MODE 0
#endif

View file

@ -947,7 +947,7 @@ static int find_address_in_search_tree(const MMDB_s *const mmdb,
return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
}
uint32_t value = 0;
uint64_t value = 0;
uint16_t current_bit = 0;
if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) {
value = mmdb->ipv4_start_node.node_value;
@ -961,6 +961,7 @@ static int find_address_in_search_tree(const MMDB_s *const mmdb,
uint8_t bit =
1U & (address[current_bit >> 3] >> (7 - (current_bit % 8)));
// Note that value*record_info.record_length can be larger than 2**32
record_pointer = &search_tree[value * record_info.record_length];
if (record_pointer + record_info.record_length > mmdb->data_section) {
return MMDB_CORRUPT_SEARCH_TREE_ERROR;

@ -1 +1 @@
Subproject commit 31a33b3c09ac53028216e82f3d8a6d33749a8df5
Subproject commit 880f6b4b5eb6c12ea9d5c70dd201dec2cb5639a2

View file

@ -3,8 +3,8 @@
void test_metadata(MMDB_s *mmdb, const char *mode_desc) {
cmp_ok(mmdb->metadata.node_count,
"==",
37,
"node_count is 37 - %s",
163,
"node_count is 163 - %s",
mode_desc);
cmp_ok(mmdb->metadata.record_size,
"==",
@ -197,7 +197,7 @@ void test_metadata_as_data_entry_list(MMDB_s *mmdb, const char *mode_desc) {
if (strcmp(key_name, "node_count") == 0) {
MMDB_entry_data_list_s *value = entry_data_list =
entry_data_list->next;
cmp_ok(value->entry_data.uint32, "==", 37, "node_count == 37");
cmp_ok(value->entry_data.uint32, "==", 163, "node_count == 163");
} else if (strcmp(key_name, "record_size") == 0) {
MMDB_entry_data_list_s *value = entry_data_list =
entry_data_list->next;

View file

@ -74,50 +74,55 @@ void run_24_bit_record_tests(int mode, const char *mode_desc) {
MMDB_s *mmdb = open_ok(path, mode, mode_desc);
free(path);
const uint32_t tests[7][5] = {
{0, 1, MMDB_RECORD_TYPE_SEARCH_NODE, 242, MMDB_RECORD_TYPE_EMPTY},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
197,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
103,
242,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
242,
MMDB_RECORD_TYPE_EMPTY,
315,
MMDB_RECORD_TYPE_DATA,
},
{
132,
329,
MMDB_RECORD_TYPE_DATA,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
96,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
}};
const uint32_t tests[7][5] = {{
0,
1,
MMDB_RECORD_TYPE_SEARCH_NODE,
435,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
323,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
148,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
103,
444,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
444,
MMDB_RECORD_TYPE_EMPTY,
514,
MMDB_RECORD_TYPE_DATA,
},
{
132,
527,
MMDB_RECORD_TYPE_DATA,
444,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
444,
MMDB_RECORD_TYPE_EMPTY,
242,
MMDB_RECORD_TYPE_SEARCH_NODE,
}};
run_read_node_tests(mmdb, tests, 7, 24);
MMDB_close(mmdb);
@ -130,50 +135,55 @@ void run_28_bit_record_tests(int mode, const char *mode_desc) {
MMDB_s *mmdb = open_ok(path, mode, mode_desc);
free(path);
const uint32_t tests[7][5] = {
{0, 1, MMDB_RECORD_TYPE_SEARCH_NODE, 242, MMDB_RECORD_TYPE_EMPTY},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
197,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
103,
242,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
242,
MMDB_RECORD_TYPE_EMPTY,
315,
MMDB_RECORD_TYPE_DATA,
},
{
132,
329,
MMDB_RECORD_TYPE_DATA,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
96,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
}};
const uint32_t tests[7][5] = {{
0,
1,
MMDB_RECORD_TYPE_SEARCH_NODE,
435,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
323,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
148,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
103,
444,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
444,
MMDB_RECORD_TYPE_EMPTY,
514,
MMDB_RECORD_TYPE_DATA,
},
{
132,
527,
MMDB_RECORD_TYPE_DATA,
444,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
444,
MMDB_RECORD_TYPE_EMPTY,
242,
MMDB_RECORD_TYPE_SEARCH_NODE,
}};
run_read_node_tests(mmdb, tests, 7, 28);
MMDB_close(mmdb);
@ -186,50 +196,55 @@ void run_32_bit_record_tests(int mode, const char *mode_desc) {
MMDB_s *mmdb = open_ok(path, mode, mode_desc);
free(path);
const uint32_t tests[7][5] = {
{0, 1, MMDB_RECORD_TYPE_SEARCH_NODE, 242, MMDB_RECORD_TYPE_EMPTY},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
197,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
103,
242,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
242,
MMDB_RECORD_TYPE_EMPTY,
315,
MMDB_RECORD_TYPE_DATA,
},
{
132,
329,
MMDB_RECORD_TYPE_DATA,
242,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
96,
MMDB_RECORD_TYPE_SEARCH_NODE,
242,
MMDB_RECORD_TYPE_EMPTY,
}};
const uint32_t tests[7][5] = {{
0,
1,
MMDB_RECORD_TYPE_SEARCH_NODE,
435,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
80,
81,
MMDB_RECORD_TYPE_SEARCH_NODE,
323,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
96,
97,
MMDB_RECORD_TYPE_SEARCH_NODE,
148,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
103,
444,
MMDB_RECORD_TYPE_EMPTY,
104,
MMDB_RECORD_TYPE_SEARCH_NODE,
},
{
127,
444,
MMDB_RECORD_TYPE_EMPTY,
514,
MMDB_RECORD_TYPE_DATA,
},
{
132,
527,
MMDB_RECORD_TYPE_DATA,
444,
MMDB_RECORD_TYPE_EMPTY,
},
{
241,
444,
MMDB_RECORD_TYPE_EMPTY,
242,
MMDB_RECORD_TYPE_SEARCH_NODE,
}};
run_read_node_tests(mmdb, tests, 7, 32);

View file

@ -17,7 +17,7 @@ jobs:
runtestArgs: "LUA_INCLUDE_DIR=.lua/include/luajit-2.1"
runtestEnv: "SKIP_CMAKE=1"
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@master
@ -47,7 +47,7 @@ jobs:
sudo cpanm --notest Test::Base Test::LongString
- name: cppcheck
run: cppcheck -i .lua/ -i .install/ -i dtoa.c --force --error-exitcode=1 --enable=warning .
run: cppcheck -i .lua/ -i .install/ -i dtoa.c --force --error-exitcode=1 --enable=warning --inline-suppr .
- name: prove
run: LUA_BIN=lua prove -Itests tests

View file

@ -1,6 +1,7 @@
*.html
*.o
*.so
*.a
notes
packages
tags

View file

@ -27,7 +27,7 @@ env:
- JOBS=3
- LUAROCKS_VER=2.4.2
matrix:
#- LUA=1 LUA_DIR=/usr LUA_INCLUDE_DIR=$LUA_DIR/include/lua5.1
#- LUA=1 LUA_DIR=/usr LUA_INCLUDE_DIR=$LUA_DIR/include/lua5.1
- LUAJIT=1 LUA_DIR=/usr/local LUA_INCLUDE_DIR=$LUA_DIR/include/luajit-2.1 LUA_SUFFIX=--lua-suffix=jit
install:

View file

@ -68,6 +68,13 @@ else()
set(_lua_module_dir "${_lua_lib_dir}/lua/5.1")
endif()
if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
add_definitions(-Dinline=__inline)
add_definitions(-Dsnprintf=_snprintf)
add_definitions(-Dstrncasecmp=_strnicmp)
endif()
add_library(cjson MODULE lua_cjson.c strbuf.c ${FPCONV_SOURCES})
set_target_properties(cjson PROPERTIES PREFIX "")
target_link_libraries(cjson ${_MODULE_LINK})

View file

@ -23,6 +23,8 @@ LUA_CMODULE_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
LUA_MODULE_DIR ?= $(PREFIX)/share/lua/$(LUA_VERSION)
LUA_BIN_DIR ?= $(PREFIX)/bin
AR= $(CC) -o
##### Platform overrides #####
##
## Tweak one of the platform sections below to suit your situation.
@ -84,12 +86,12 @@ OBJS = lua_cjson.o strbuf.o $(FPCONV_OBJS)
.PHONY: all clean install install-extra doc
.SUFFIXES: .html .txt
.SUFFIXES: .html .adoc
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) $(BUILD_CFLAGS) -o $@ $<
.txt.html:
.adoc.html:
$(ASCIIDOC) -n -a toc $<
all: $(TARGET)
@ -97,7 +99,7 @@ all: $(TARGET)
doc: manual.html performance.html
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) $(CJSON_LDFLAGS) -o $@ $(OBJS)
$(AR) $@ $(LDFLAGS) $(CJSON_LDFLAGS) $(OBJS)
install: $(TARGET)
mkdir -p $(DESTDIR)$(LUA_CMODULE_DIR)

View file

@ -5,7 +5,7 @@
# Build packages. Use current checked out version, or a specific tag/commit.
# Files requiring a version bump
VERSION_FILES="lua-cjson-2.1devel-1.rockspec lua-cjson.spec lua_cjson.c manual.txt runtests.sh tests/test.lua"
VERSION_FILES="lua-cjson-2.1devel-1.rockspec lua-cjson.spec lua_cjson.c manual.adoc runtests.sh tests/test.lua"
[ "$1" ] && BRANCH="$1" || BRANCH="`git describe --match '[1-3].[0-9]*'`"
VERSION="`git describe --match '[1-3].[0-9]*' $BRANCH`"

View file

@ -1,3 +1,7 @@
/* The code comes from https://portal.ampl.com/~dmg/netlib/fp/dtoa.c
* Go to https://portal.ampl.com/~dmg/netlib/fp/changes for the detailed changes.
*/
/****************************************************************
*
* The author of this software is David M. Gay.
@ -1533,12 +1537,18 @@ ThInfo {
set_max_dtoa_threads(unsigned int n)
{
size_t L;
ThInfo *newTI1;
if (n > maxthreads) {
L = n*sizeof(ThInfo);
if (TI1) {
TI1 = (ThInfo*)REALLOC(TI1, L);
memset(TI1 + maxthreads, 0, (n-maxthreads)*sizeof(ThInfo));
newTI1 = (ThInfo*)REALLOC(TI1, L);
if (newTI1) {
TI1 = newTI1;
memset(TI1 + maxthreads, 0, (n-maxthreads)*sizeof(ThInfo));
}
else
return;
}
else {
TI1 = (ThInfo*)MALLOC(L);
@ -1871,7 +1881,7 @@ mult(Bigint *a, Bigint *b MTd)
#else
#ifdef Pack_32
for(; xb < xbe; xb++, xc0++) {
if (y = *xb & 0xffff) {
if ((y = *xb & 0xffff)) {
x = xa;
xc = xc0;
carry = 0;
@ -1885,7 +1895,7 @@ mult(Bigint *a, Bigint *b MTd)
while(x < xae);
*xc = carry;
}
if (y = *xb >> 16) {
if ((y = *xb >> 16)) {
x = xa;
xc = xc0;
carry = 0;
@ -2718,13 +2728,14 @@ enum { /* rounding values: same as FLT_ROUNDS */
};
void
gethex( const char **sp, U *rvp, int rounding, int sign MTd)
gethex(const char **sp, U *rvp, int rounding, int sign MTd)
{
Bigint *b;
char d;
const unsigned char *decpt, *s0, *s, *s1;
Long e, e1;
ULong L, lostbits, *x;
int big, denorm, esign, havedig, k, n, nbits, up, zret;
int big, denorm, esign, havedig, k, n, nb, nbits, nz, up, zret;
#ifdef IBM
int j;
#endif
@ -2742,6 +2753,9 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
#endif
#endif /*}}*/
};
#ifdef IEEE_Arith
int check_denorm = 0;
#endif
#ifdef USE_LOCALE
int i;
#ifdef NO_LOCALE_CACHE
@ -2893,7 +2907,7 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
k++;
b = Balloc(k MTa);
x = b->x;
n = 0;
havedig = n = nz = 0;
L = 0;
#ifdef USE_LOCALE
for(i = 0; decimalpoint[i+1]; ++i);
@ -2908,22 +2922,28 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
if (*--s1 == '.')
continue;
#endif
if ((d = hexdig[*s1]))
havedig = 1;
else if (!havedig) {
e += 4;
continue;
}
if (n == ULbits) {
*x++ = L;
L = 0;
n = 0;
}
L |= (hexdig[*s1] & 0x0f) << n;
L |= (d & 0x0f) << n;
n += 4;
}
*x++ = L;
b->wds = n = x - b->x;
n = ULbits*n - hi0bits(L);
nb = ULbits*n - hi0bits(L);
nbits = Nbits;
lostbits = 0;
x = b->x;
if (n > nbits) {
n -= nbits;
if (nb > nbits) {
n = nb - nbits;
if (any_on(b,n)) {
lostbits = 1;
k = n - 1;
@ -2936,8 +2956,8 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
rshift(b, n);
e += n;
}
else if (n < nbits) {
n = nbits - n;
else if (nb < nbits) {
n = nbits - nb;
b = lshift(b, n MTa);
e -= n;
x = b->x;
@ -2992,12 +3012,49 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
return;
}
k = n - 1;
#ifdef IEEE_Arith
if (!k) {
switch(rounding) {
case Round_near:
if (((b->x[0] & 3) == 3) || (lostbits && (b->x[0] & 1))) {
multadd(b, 1, 1 MTa);
emin_check:
if (b->x[1] == (1 << (Exp_shift + 1))) {
rshift(b,1);
e = emin;
goto normal;
}
}
break;
case Round_up:
if (!sign && (lostbits || (b->x[0] & 1))) {
incr_denorm:
multadd(b, 1, 2 MTa);
check_denorm = 1;
lostbits = 0;
goto emin_check;
}
break;
case Round_down:
if (sign && (lostbits || (b->x[0] & 1)))
goto incr_denorm;
break;
}
}
#endif
if (lostbits)
lostbits = 1;
else if (k > 0)
lostbits = any_on(b,k);
#ifdef IEEE_Arith
else if (check_denorm)
goto no_lostbits;
#endif
if (x[k>>kshift] & 1 << (k & kmask))
lostbits |= 2;
#ifdef IEEE_Arith
no_lostbits:
#endif
nbits -= n;
rshift(b,n);
e = emin;
@ -3022,16 +3079,9 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
k = b->wds;
b = increment(b MTa);
x = b->x;
if (denorm) {
#if 0
if (nbits == Nbits - 1
&& x[nbits >> kshift] & 1 << (nbits & kmask))
denorm = 0; /* not currently used */
#endif
}
else if (b->wds > k
if (!denorm && (b->wds > k
|| ((n = nbits & kmask) !=0
&& hi0bits(x[k-1]) < 32-n)) {
&& hi0bits(x[k-1]) < 32-n))) {
rshift(b,1);
if (++e > Emax)
goto ovfl;
@ -3041,8 +3091,10 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd)
#ifdef IEEE_Arith
if (denorm)
word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0;
else
else {
normal:
word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20);
}
word1(rvp) = b->x[0];
#endif
#ifdef IBM
@ -3409,6 +3461,7 @@ retlow1:
if ((j = ((word0(rv) & Exp_mask) >> Exp_shift) - bc->scale) <= 0) {
i = 1 - j;
if (i <= 31) {
/* cppcheck-suppress integerOverflowCond */
if (word1(rv) & (0x1 << i))
goto odd;
}
@ -3619,10 +3672,11 @@ fpconv_strtod(const char *s00, char **se)
c = *++s;
if (c > '0' && c <= '9') {
L = c - '0';
s1 = s;
while((c = *++s) >= '0' && c <= '9')
L = 10*L + c - '0';
if (s - s1 > 8 || L > 19999)
while((c = *++s) >= '0' && c <= '9') {
if (L <= 19999)
L = 10*L + c - '0';
}
if (L > 19999)
/* Avoid confusion from exponents
* so large that e might overflow.
*/
@ -4884,6 +4938,7 @@ nrv_alloc(const char *s, char *s0, size_t s0len, char **rve, int n MTd)
s0 = rv_alloc(n MTa);
else if (s0len <= n) {
rv = 0;
/* cppcheck-suppress nullPointerArithmetic */
t = rv + n;
goto rve_chk;
}
@ -5237,9 +5292,11 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char
#ifndef SET_INEXACT
#ifdef Check_FLT_ROUNDS
try_quick = Rounding == 1;
#else
try_quick = 1;
#endif
#endif /*SET_INEXACT*/
#endif
#endif /*USE_BF96*/
if (mode > 5) {
mode -= 4;
@ -5281,6 +5338,7 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char
else if (blen <= i) {
buf = 0;
if (rve)
/* cppcheck-suppress nullPointerArithmetic */
*rve = buf + i;
return buf;
}
@ -5469,6 +5527,7 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char
res3 = p10->b1 * dbhi + (tv3 & 0xffffffffull);
res = p10->b0 * dbhi + (tv3>>32) + (res3>>32);
be += p10->e - 0x3fe;
/* cppcheck-suppress integerOverflowCond */
eulp = j1 = be - 54 + ulpadj;
if (!(res & 0x8000000000000000ull)) {
--be;

View file

@ -130,7 +130,7 @@ double fpconv_strtod(const char *nptr, char **endptr)
/* Duplicate number into buffer */
if (buflen >= FPCONV_G_FMT_BUFSIZE) {
/* Handle unusually large numbers */
buf = malloc(buflen + 1);
buf = (char *)malloc(buflen + 1);
if (!buf) {
fprintf(stderr, "Out of memory");
abort();

View file

@ -1,9 +1,9 @@
package = "lua-cjson"
version = "2.1.0.11-1"
version = "2.1.0.14-1"
source = {
url = "git+https://github.com/openresty/lua-cjson",
tag = "2.1.0.11",
tag = "2.1.0.14",
}
description = {

View file

@ -50,7 +50,7 @@ rm -rf "$RPM_BUILD_ROOT"
%files
%defattr(-,root,root,-)
%doc LICENSE NEWS performance.html performance.txt manual.html manual.txt rfc4627.txt THANKS
%doc LICENSE NEWS performance.html performance.adoc manual.html manual.adoc rfc4627.txt THANKS
%{lualibdir}/*
%{luadatadir}/*
%{_bindir}/*

View file

@ -40,6 +40,7 @@
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <limits.h>
#include <lua.h>
#include <lauxlib.h>
@ -65,6 +66,13 @@
#endif
#ifdef _MSC_VER
#define CJSON_EXPORT __declspec(dllexport)
#define strncasecmp(x,y,z) _strnicmp(x,y,z)
#else
#define CJSON_EXPORT extern
#endif
/* Workaround for Solaris platforms missing isinf() */
#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF))
#define isinf(x) (!isnan(x) && isnan((x) - (x)))
@ -103,8 +111,8 @@
#define json_lightudata_mask(ludata) (ludata)
#endif
#if LUA_VERSION_NUM > 501
#define lua_objlen(L,i) lua_rawlen(L, (i))
#if LUA_VERSION_NUM >= 502
#define lua_objlen(L,i) luaL_len(L, (i))
#endif
static const char * const *json_empty_array;
@ -117,6 +125,7 @@ typedef enum {
T_ARR_END,
T_STRING,
T_NUMBER,
T_INTEGER,
T_BOOLEAN,
T_NULL,
T_COLON,
@ -134,6 +143,7 @@ static const char *json_token_type_name[] = {
"T_ARR_END",
"T_STRING",
"T_NUMBER",
"T_INTEGER",
"T_BOOLEAN",
"T_NULL",
"T_COLON",
@ -179,13 +189,14 @@ typedef struct {
typedef struct {
json_token_type_t type;
int index;
size_t index;
union {
const char *string;
double number;
lua_Integer integer;
int boolean;
} value;
int string_len;
size_t string_len;
} json_token_t;
static const char *char2escape[256] = {
@ -233,7 +244,7 @@ static json_config_t *json_fetch_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, lua_upvalueindex(1));
cfg = (json_config_t *)lua_touserdata(l, lua_upvalueindex(1));
if (!cfg)
luaL_error(l, "BUG: Unable to fetch CJSON configuration");
@ -442,7 +453,7 @@ static int json_destroy_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, 1);
cfg = (json_config_t *)lua_touserdata(l, 1);
if (cfg)
strbuf_free(&cfg->encode_buf);
cfg = NULL;
@ -455,7 +466,11 @@ static void json_create_config(lua_State *l)
json_config_t *cfg;
int i;
cfg = lua_newuserdata(l, sizeof(*cfg));
cfg = (json_config_t *)lua_newuserdata(l, sizeof(*cfg));
if (!cfg)
abort();
memset(cfg, 0, sizeof(*cfg));
/* Create GC method to clean up strbuf */
lua_newtable(l);
@ -547,9 +562,9 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js
static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
{
const char *escstr;
unsigned i;
const char *str;
size_t len;
size_t i;
str = lua_tolstring(l, lindex, &len);
@ -557,6 +572,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
* This buffer is reused constantly for small strings
* If there are any excess pages, they won't be hit anyway.
* This gains ~5% speedup. */
if (len > SIZE_MAX / 6 - 3)
abort(); /* Overflow check */
strbuf_ensure_empty_length(json, len * 6 + 2);
strbuf_append_char_unsafe(json, '\"');
@ -646,9 +663,9 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
/* json_append_array args:
* - lua_State
* - JSON strbuf
* - Size of passwd Lua array (top of stack) */
* - Size of passed Lua array (top of stack) */
static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
strbuf_t *json, int array_length)
strbuf_t *json, int array_length, int raw)
{
int comma, i, json_pos, err;
@ -660,7 +677,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
if (comma++ > 0)
strbuf_append_char(json, ',');
lua_rawgeti(l, -1, i);
if (raw) {
lua_rawgeti(l, -1, i);
} else {
#if LUA_VERSION_NUM >= 503
lua_geti(l, -1, i);
#else
lua_pushinteger(l, i);
lua_gettable(l, -2);
#endif
}
err = json_append_data(l, cfg, current_depth, json);
if (err) {
strbuf_set_length(json, json_pos);
@ -677,8 +704,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept
static void json_append_number(lua_State *l, json_config_t *cfg,
strbuf_t *json, int lindex)
{
double num = lua_tonumber(l, lindex);
int len;
#if LUA_VERSION_NUM >= 503
if (lua_isinteger(l, lindex)) {
lua_Integer num = lua_tointeger(l, lindex);
strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); /* max length of int64 is 19 */
len = sprintf(strbuf_empty_ptr(json), LUA_INTEGER_FMT, num);
strbuf_extend_length(json, len);
return;
}
#endif
double num = lua_tonumber(l, lindex);
if (cfg->encode_invalid_numbers == 0) {
/* Prevent encoding invalid numbers */
@ -766,6 +802,7 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
int len;
int as_array = 0;
int has_metatable;
int raw = 1;
switch (lua_type(l, -1)) {
case LUA_TSTRING:
@ -790,17 +827,30 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
lua_pushlightuserdata(l, json_lightudata_mask(&json_array));
lua_rawget(l, LUA_REGISTRYINDEX);
as_array = lua_rawequal(l, -1, -2);
lua_pop(l, 2);
if (as_array) {
raw = 1;
lua_pop(l, 2);
len = lua_objlen(l, -1);
} else {
raw = 0;
lua_pop(l, 2);
if (luaL_getmetafield(l, -1, "__len")) {
lua_pushvalue(l, -2);
lua_call(l, 1, 1);
len = lua_tonumber(l, -1);
lua_pop(l, 1);
as_array = 1;
}
}
}
if (as_array) {
len = lua_objlen(l, -1);
json_append_array(l, cfg, current_depth, json, len);
json_append_array(l, cfg, current_depth, json, len, raw);
} else {
len = lua_array_length(l, cfg, json);
if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object)) {
json_append_array(l, cfg, current_depth, json, len);
json_append_array(l, cfg, current_depth, json, len, raw);
} else {
if (has_metatable) {
lua_getmetatable(l, -1);
@ -810,7 +860,9 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
as_array = lua_rawequal(l, -1, -2);
lua_pop(l, 2); /* pop pointer + metatable */
if (as_array) {
json_append_array(l, cfg, current_depth, json, 0);
len = lua_objlen(l, -1);
raw = 1;
json_append_array(l, cfg, current_depth, json, len, raw);
break;
}
}
@ -825,7 +877,7 @@ static int json_append_data(lua_State *l, json_config_t *cfg,
if (lua_touserdata(l, -1) == NULL) {
strbuf_append_mem(json, "null", 4);
} else if (lua_touserdata(l, -1) == json_lightudata_mask(&json_array)) {
json_append_array(l, cfg, current_depth, json, 0);
json_append_array(l, cfg, current_depth, json, 0, 1);
}
break;
default:
@ -848,7 +900,7 @@ static int json_encode(lua_State *l)
strbuf_t local_encode_buf;
strbuf_t *encode_buf;
char *json;
int len;
size_t len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
@ -1138,13 +1190,19 @@ static int json_is_invalid_number(json_parse_t *json)
static void json_next_number_token(json_parse_t *json, json_token_t *token)
{
char *endptr;
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr)
json_set_token_error(token, json, "invalid number");
else
json->ptr = endptr; /* Skip the processed number */
token->value.integer = strtoll(json->ptr, &endptr, 10);
if (json->ptr == endptr || *endptr == '.' || *endptr == 'e' ||
*endptr == 'E' || *endptr == 'x') {
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr) {
json_set_token_error(token, json, "invalid number");
return;
}
} else {
token->type = T_INTEGER;
}
json->ptr = endptr; /* Skip the processed number */
return;
}
@ -1380,6 +1438,9 @@ static void json_process_value(lua_State *l, json_parse_t *json,
case T_NUMBER:
lua_pushnumber(l, token->value.number);
break;;
case T_INTEGER:
lua_pushinteger(l, token->value.integer);
break;;
case T_BOOLEAN:
lua_pushboolean(l, token->value.boolean);
break;;
@ -1595,7 +1656,7 @@ static int lua_cjson_safe_new(lua_State *l)
return 1;
}
int luaopen_cjson(lua_State *l)
CJSON_EXPORT int luaopen_cjson(lua_State *l)
{
lua_cjson_new(l);
@ -1609,7 +1670,7 @@ int luaopen_cjson(lua_State *l)
return 1;
}
int luaopen_cjson_safe(lua_State *l)
CJSON_EXPORT int luaopen_cjson_safe(lua_State *l)
{
lua_cjson_safe_new(l);

View file

@ -1,6 +1,6 @@
= Lua CJSON 2.1devel Manual =
Mark Pulford <mark@kyne.com.au>
:revdate: 1st March 2012
:revdate: August 2016
Overview
--------
@ -20,7 +20,7 @@ The Lua CJSON module provides JSON support for Lua.
Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for
details.
The latest version of this software is available from the
The current stable version of this software is available from the
http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website].
Feel free to email me if you have any patches, suggestions, or comments.
@ -29,8 +29,8 @@ Feel free to email me if you have any patches, suggestions, or comments.
Installation
------------
Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, or
http://www.luajit.org[LuaJIT] to build.
Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, Lua 5.3,
or http://www.luajit.org[LuaJIT] to build.
The build method can be selected from 4 options:
@ -203,8 +203,8 @@ Import Lua CJSON via the Lua +require+ function. Lua CJSON does not
register a global module table.
The +cjson+ module will throw an error during JSON conversion if any
invalid data is encountered. Refer to <<cjson_encode,+cjson.encode+>>
and <<cjson_decode,+cjson.decode+>> for details.
invalid data is encountered. Refer to <<encode,+cjson.encode+>> and
<<decode,+cjson.decode+>> for details.
The +cjson.safe+ module behaves identically to the +cjson+ module,
except when errors are encountered during JSON conversion. On error, the
@ -238,6 +238,7 @@ workaround if required. Lua CJSON should be reinitialised via
different locale per thread is not supported.
[[decode]]
decode
~~~~~~

View file

@ -26,7 +26,7 @@ http://chiselapp.com/user/dhkolf/repository/dkjson/[DKJSON 2.1]::
https://github.com/brimworks/lua-yajl[Lua YAJL 2.0]::
- C wrapper for the YAJL library
http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CSJON 2.0.0]::
http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]::
- C implementation with no dependencies on other libraries

View file

@ -26,6 +26,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdint.h>
#include "strbuf.h"
@ -38,38 +39,38 @@ static void die(const char *fmt, ...)
va_end(arg);
fprintf(stderr, "\n");
exit(-1);
abort();
}
void strbuf_init(strbuf_t *s, int len)
void strbuf_init(strbuf_t *s, size_t len)
{
int size;
size_t size;
if (len <= 0)
if (!len)
size = STRBUF_DEFAULT_SIZE;
else
size = len + 1; /* \0 terminator */
size = len + 1;
if (size < len)
die("Overflow, len: %zu", len);
s->buf = NULL;
s->size = size;
s->length = 0;
s->increment = STRBUF_DEFAULT_INCREMENT;
s->dynamic = 0;
s->reallocs = 0;
s->debug = 0;
s->buf = malloc(size);
s->buf = (char *)malloc(size);
if (!s->buf)
die("Out of memory");
strbuf_ensure_null(s);
}
strbuf_t *strbuf_new(int len)
strbuf_t *strbuf_new(size_t len)
{
strbuf_t *s;
s = malloc(sizeof(strbuf_t));
s = (strbuf_t*)malloc(sizeof(strbuf_t));
if (!s)
die("Out of memory");
@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
return s;
}
void strbuf_set_increment(strbuf_t *s, int increment)
{
/* Increment > 0: Linear buffer growth rate
* Increment < -1: Exponential buffer growth rate */
if (increment == 0 || increment == -1)
die("BUG: Invalid string increment");
s->increment = increment;
}
static inline void debug_stats(strbuf_t *s)
{
if (s->debug) {
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n",
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
(long)s, s->reallocs, s->length, s->size);
}
}
@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
free(s);
}
char *strbuf_free_to_string(strbuf_t *s, int *len)
char *strbuf_free_to_string(strbuf_t *s, size_t *len)
{
char *buf;
@ -131,57 +122,63 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
return buf;
}
static int calculate_new_size(strbuf_t *s, int len)
static size_t calculate_new_size(strbuf_t *s, size_t len)
{
int reqsize, newsize;
size_t reqsize, newsize;
if (len <= 0)
die("BUG: Invalid strbuf length requested");
/* Ensure there is room for optional NULL termination */
reqsize = len + 1;
if (reqsize < len)
die("Overflow, len: %zu", len);
/* If the user has requested to shrink the buffer, do it exactly */
if (s->size > reqsize)
return reqsize;
newsize = s->size;
if (s->increment < 0) {
if (reqsize >= SIZE_MAX / 2) {
newsize = reqsize;
} else {
/* Exponential sizing */
while (newsize < reqsize)
newsize *= -s->increment;
} else if (s->increment != 0) {
/* Linear sizing */
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
newsize *= 2;
}
if (newsize < reqsize)
die("BUG: strbuf length would overflow, len: %zu", len);
return newsize;
}
/* Ensure strbuf can handle a string length bytes long (ignoring NULL
* optional termination). */
void strbuf_resize(strbuf_t *s, int len)
void strbuf_resize(strbuf_t *s, size_t len)
{
int newsize;
size_t newsize;
newsize = calculate_new_size(s, len);
if (s->debug > 1) {
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n",
fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
(long)s, s->size, newsize);
}
s->size = newsize;
s->buf = realloc(s->buf, s->size);
s->buf = (char *)realloc(s->buf, s->size);
if (!s->buf)
die("Out of memory");
die("Out of memory, len: %zu", len);
s->reallocs++;
}
void strbuf_append_string(strbuf_t *s, const char *str)
{
int space, i;
int i;
size_t space;
space = strbuf_empty_length(s);
@ -197,55 +194,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
}
}
/* strbuf_append_fmt() should only be used when an upper bound
* is known for the output string. */
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
{
va_list arg;
int fmt_len;
strbuf_ensure_empty_length(s, len);
va_start(arg, fmt);
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
va_end(arg);
if (fmt_len < 0)
die("BUG: Unable to convert number"); /* This should never happen.. */
s->length += fmt_len;
}
/* strbuf_append_fmt_retry() can be used when the there is no known
* upper bound for the output string. */
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
{
va_list arg;
int fmt_len, try;
int empty_len;
/* If the first attempt to append fails, resize the buffer appropriately
* and try again */
for (try = 0; ; try++) {
va_start(arg, fmt);
/* Append the new formatted string */
/* fmt_len is the length of the string required, excluding the
* trailing NULL */
empty_len = strbuf_empty_length(s);
/* Add 1 since there is also space to store the terminating NULL. */
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
va_end(arg);
if (fmt_len <= empty_len)
break; /* SUCCESS */
if (try > 0)
die("BUG: length of formatted string changed");
strbuf_resize(s, s->length + fmt_len);
}
s->length += fmt_len;
}
/* vi:ai et sw=4 ts=4:
*/

View file

@ -32,15 +32,13 @@
/* Size: Total bytes allocated to *buf
* Length: String length, excluding optional NULL terminator.
* Increment: Allocation increments when resizing the string buffer.
* Dynamic: True if created via strbuf_new()
*/
typedef struct {
char *buf;
int size;
int length;
int increment;
size_t size;
size_t length;
int dynamic;
int reallocs;
int debug;
@ -49,33 +47,27 @@ typedef struct {
#ifndef STRBUF_DEFAULT_SIZE
#define STRBUF_DEFAULT_SIZE 1023
#endif
#ifndef STRBUF_DEFAULT_INCREMENT
#define STRBUF_DEFAULT_INCREMENT -2
#endif
/* Initialise */
extern strbuf_t *strbuf_new(int len);
extern void strbuf_init(strbuf_t *s, int len);
extern void strbuf_set_increment(strbuf_t *s, int increment);
extern strbuf_t *strbuf_new(size_t len);
extern void strbuf_init(strbuf_t *s, size_t len);
/* Release */
extern void strbuf_free(strbuf_t *s);
extern char *strbuf_free_to_string(strbuf_t *s, int *len);
extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
/* Management */
extern void strbuf_resize(strbuf_t *s, int len);
static int strbuf_empty_length(strbuf_t *s);
static int strbuf_length(strbuf_t *s);
static char *strbuf_string(strbuf_t *s, int *len);
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
extern void strbuf_resize(strbuf_t *s, size_t len);
static size_t strbuf_empty_length(strbuf_t *s);
static size_t strbuf_length(strbuf_t *s);
static char *strbuf_string(strbuf_t *s, size_t *len);
static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
static char *strbuf_empty_ptr(strbuf_t *s);
static void strbuf_extend_length(strbuf_t *s, int len);
static void strbuf_extend_length(strbuf_t *s, size_t len);
static void strbuf_set_length(strbuf_t *s, int len);
/* Update */
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
extern void strbuf_append_string(strbuf_t *s, const char *str);
static void strbuf_append_char(strbuf_t *s, const char c);
static void strbuf_ensure_null(strbuf_t *s);
@ -93,12 +85,12 @@ static inline int strbuf_allocated(strbuf_t *s)
/* Return bytes remaining in the string buffer
* Ensure there is space for a NULL terminator. */
static inline int strbuf_empty_length(strbuf_t *s)
static inline size_t strbuf_empty_length(strbuf_t *s)
{
return s->size - s->length - 1;
}
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
{
if (len > strbuf_empty_length(s))
strbuf_resize(s, s->length + len);
@ -114,12 +106,12 @@ static inline void strbuf_set_length(strbuf_t *s, int len)
s->length = len;
}
static inline void strbuf_extend_length(strbuf_t *s, int len)
static inline void strbuf_extend_length(strbuf_t *s, size_t len)
{
s->length += len;
}
static inline int strbuf_length(strbuf_t *s)
static inline size_t strbuf_length(strbuf_t *s)
{
return s->length;
}
@ -135,14 +127,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
s->buf[s->length++] = c;
}
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
{
strbuf_ensure_empty_length(s, len);
memcpy(s->buf + s->length, c, len);
s->length += len;
}
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
{
memcpy(s->buf + s->length, c, len);
s->length += len;
@ -153,7 +145,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
s->buf[s->length] = 0;
}
static inline char *strbuf_string(strbuf_t *s, int *len)
static inline char *strbuf_string(strbuf_t *s, size_t *len)
{
if (len)
*len = s->length;

View file

@ -332,3 +332,45 @@ Cannot serialise function: type not supported
{"valid":"valid"}
["one","two","three"]
=== TEST 23: array-like proxy object with __len and __index
--- lua
local cjson = require "cjson"
local real_array = {"foo", "bar", "baz"}
local proxy_array = {}
setmetatable(proxy_array, {
__len = function() return 3 end,
__index = function(t, k)
return real_array[k]
end,
})
print(cjson.encode(proxy_array))
--- out
["foo","bar","baz"]
=== TEST 24: check that integers are handled correctly on Lua 5.3+
--- lua
local lv = tonumber((_VERSION):match("Lua 5%.([0-9]+)"))
if lv >= 3 then
local cjson = require "cjson"
local array = cjson.decode [[ [10, 10.0, 3.5] ]]
for i = 1, 4 do
print(tostring(i) .. ": " .. tostring(math.type(array[i])))
end
else
print("1: integer")
print("2: float")
print("3: float")
print("4: nil")
end
--- out
1: integer
2: float
3: float
4: nil

View file

@ -6,6 +6,7 @@
# cff03b039d850f370a7362f3313e5268
use strict;
no warnings 'nonchar';
# 0xD800 - 0xDFFF are used to encode supplementary codepoints
# 0x10000 - 0x10FFFF are supplementary codepoints

View file

@ -1,4 +1,4 @@
FROM redhat/ubi9:9.4@sha256:d31d3e5e92c0c47277c5011c0326b285ab7ae627eff036133be1dccc4208004d as builder
FROM redhat/ubi9:9.4@sha256:d98fdae16212df566150ac975cab860cd8d2cb1b322ed9966d09a13e219112e9 as builder
ENV OS=rhel
ENV NGINX_VERSION 1.26.1
@ -68,7 +68,7 @@ COPY src/scheduler scheduler
COPY src/ui ui
COPY src/VERSION VERSION
FROM redhat/ubi9:9.4@sha256:d31d3e5e92c0c47277c5011c0326b285ab7ae627eff036133be1dccc4208004d
FROM redhat/ubi9:9.4@sha256:d98fdae16212df566150ac975cab860cd8d2cb1b322ed9966d09a13e219112e9
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -1,4 +1,4 @@
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb as builder
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c as builder
# Export var for specific actions on linux/arm/v7
ARG TARGETPLATFORM
@ -35,7 +35,7 @@ COPY src/common/utils utils
COPY src/scheduler scheduler
COPY src/VERSION VERSION
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -9,13 +9,13 @@ from json import load as json_load
from os import _exit, environ, getenv, getpid, sep
from os.path import join
from pathlib import Path
from shutil import copy, rmtree
from shutil import copy, rmtree, copytree
from signal import SIGINT, SIGTERM, signal, SIGHUP
from stat import S_IEXEC
from subprocess import run as subprocess_run, DEVNULL, STDOUT, PIPE
from sys import path as sys_path
from tarfile import TarFile, open as tar_open
from threading import Thread
from threading import Event, Thread
from time import sleep
from traceback import format_exc
from typing import Any, Dict, List, Literal, Optional, Union
@ -30,13 +30,15 @@ from common_utils import bytes_hash, dict_to_frozenset, get_integration # type:
from logger import setup_logger # type: ignore
from Database import Database # type: ignore
from JobScheduler import JobScheduler
from API import API
from jobs import Job # type: ignore
from API import API # type: ignore
APPLYING_CHANGES = Event()
RUN = True
SCHEDULER: Optional[JobScheduler] = None
CACHE_PATH = join(sep, "var", "cache", "bunkerweb")
Path(CACHE_PATH).mkdir(parents=True, exist_ok=True)
CACHE_PATH = Path(sep, "var", "cache", "bunkerweb")
CACHE_PATH.mkdir(parents=True, exist_ok=True)
CUSTOM_CONFIGS_PATH = Path(sep, "etc", "bunkerweb", "configs")
CUSTOM_CONFIGS_PATH.mkdir(parents=True, exist_ok=True)
@ -54,6 +56,8 @@ CUSTOM_CONFIGS_DIRS = (
for custom_config_dir in CUSTOM_CONFIGS_DIRS:
CUSTOM_CONFIGS_PATH.joinpath(custom_config_dir).mkdir(parents=True, exist_ok=True)
CONFIG_PATH = Path(sep, "etc", "nginx")
EXTERNAL_PLUGINS_PATH = Path(sep, "etc", "bunkerweb", "plugins")
EXTERNAL_PLUGINS_PATH.mkdir(parents=True, exist_ok=True)
@ -63,6 +67,9 @@ PRO_PLUGINS_PATH.mkdir(parents=True, exist_ok=True)
TMP_PATH = Path(sep, "var", "tmp", "bunkerweb")
TMP_PATH.mkdir(parents=True, exist_ok=True)
FAILOVER_PATH = TMP_PATH.joinpath("failover")
FAILOVER_PATH.mkdir(parents=True, exist_ok=True)
HEALTHY_PATH = TMP_PATH.joinpath("scheduler.healthy")
SCHEDULER_TMP_ENV_PATH = TMP_PATH.joinpath("scheduler.env")
@ -76,6 +83,14 @@ MASTER_MODE = environ.get("MASTER_MODE", "no") == "yes"
def handle_stop(signum, frame):
current_time = datetime.now()
while APPLYING_CHANGES.is_set() and (datetime.now() - current_time).seconds < 30:
logger.warning("Waiting for the changes to be applied before stopping ...")
sleep(1)
if APPLYING_CHANGES.is_set():
logger.warning("Timeout reached, stopping without waiting for the changes to be applied ...")
if SCHEDULER is not None:
SCHEDULER.clear()
stop(0)
@ -124,6 +139,56 @@ def stop(status):
_exit(status)
def send_nginx_configs(sent_path: Path = CONFIG_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
logger.info(f"Sending {sent_path} folder ...")
ret = SCHEDULER.send_files(sent_path.as_posix(), "/confs")
if not ret:
logger.error("Sending nginx configs failed, configuration will not work as expected...")
def send_nginx_cache(sent_path: Path = CACHE_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
logger.info(f"Sending {sent_path} folder ...")
if not SCHEDULER.send_files(sent_path.as_posix(), "/cache"):
logger.error(f"Error while sending {sent_path} folder")
else:
logger.info(f"Successfully sent {sent_path} folder")
def send_nginx_custom_configs(sent_path: Path = CUSTOM_CONFIGS_PATH):
assert SCHEDULER is not None, "SCHEDULER is not defined"
logger.info(f"Sending {sent_path} folder ...")
if not SCHEDULER.send_files(sent_path.as_posix(), "/custom_configs"):
logger.error(f"Error while sending {sent_path} folder")
else:
logger.info(f"Successfully sent {sent_path} folder")
def listen_for_instances_reload():
from docker import DockerClient
global SCHEDULER
assert SCHEDULER is not None, "SCHEDULER is not defined"
docker_client = DockerClient(base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock"))
for event in docker_client.events(decode=True, filters={"type": "container", "label": "bunkerweb.INSTANCE"}):
if event["Action"] in ("start", "die"):
logger.info(f"🐋 Detected {event['Action']} event on container {event['Actor']['Attributes']['name']}")
SCHEDULER.auto_setup()
try:
ret = SCHEDULER.db.update_instances([api_to_instance(api) for api in SCHEDULER.apis], changed=event["Action"] == "die")
if ret:
logger.error(f"Error while updating instances after {event['Action']} event: {ret}")
continue
if event["Action"] == "start":
ret = SCHEDULER.db.checked_changes(value=True)
if ret:
logger.error(f"Error while setting changes to checked in the database after {event['Action']} event: {ret}")
except BaseException as e:
logger.error(f"Error while updating instances after {event['Action']} event: {e}")
def generate_custom_configs(configs: Optional[List[Dict[str, Any]]] = None, *, original_path: Union[Path, str] = CUSTOM_CONFIGS_PATH):
if not isinstance(original_path, Path):
original_path = Path(original_path)
@ -168,11 +233,7 @@ def generate_custom_configs(configs: Optional[List[Dict[str, Any]]] = None, *, o
)
if SCHEDULER and SCHEDULER.apis:
logger.info("Sending custom configs to BunkerWeb")
ret = SCHEDULER.send_files(original_path, "/custom_configs")
if not ret:
logger.error("Sending custom configs failed, configuration will not work as expected...")
send_nginx_custom_configs(original_path)
def generate_external_plugins(plugins: Optional[List[Dict[str, Any]]] = None, *, original_path: Union[Path, str] = EXTERNAL_PLUGINS_PATH):
@ -351,7 +412,7 @@ def run_in_slave_mode():
"--templates",
join(sep, "usr", "share", "bunkerweb", "confs"),
"--output",
join(sep, "etc", "nginx"),
CONFIG_PATH.as_posix(),
"--variables",
str(SCHEDULER_TMP_ENV_PATH),
],
@ -387,15 +448,19 @@ if __name__ == "__main__":
INTEGRATION = get_integration()
tmp_variables_path = Path(args.variables or join(sep, "var", "tmp", "bunkerweb", "variables.env"))
nginx_variables_path = Path(sep, "etc", "nginx", "variables.env")
nginx_variables_path = CONFIG_PATH.joinpath("variables.env")
dotenv_env = dotenv_values(str(tmp_variables_path))
SCHEDULER = JobScheduler(environ, logger, INTEGRATION, db=Database(logger, sqlalchemy_string=dotenv_env.get("DATABASE_URI", getenv("DATABASE_URI", None)))) # type: ignore
JOB = Job(logger, SCHEDULER.db)
if SLAVE_MODE:
run_in_slave_mode()
stop(1)
APPLYING_CHANGES.set()
if (
INTEGRATION in ("Swarm", "Kubernetes", "Autoconf")
or not tmp_variables_path.exists()
@ -466,6 +531,7 @@ if __name__ == "__main__":
def check_configs_changes():
# Checking if any custom config has been created by the user
assert SCHEDULER is not None, "SCHEDULER is not defined"
logger.info("Checking if there are any changes in custom configs ...")
custom_configs = []
db_configs = SCHEDULER.db.get_custom_configs()
@ -507,6 +573,7 @@ if __name__ == "__main__":
def check_plugin_changes(_type: Literal["external", "pro"] = "external"):
# Check if any external or pro plugin has been added by the user
assert SCHEDULER is not None, "SCHEDULER is not defined"
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)
@ -624,41 +691,6 @@ if __name__ == "__main__":
RUN_JOBS_ONCE = True
CHANGES = []
def send_nginx_configs():
logger.info(f"Sending {join(sep, 'etc', 'nginx')} folder ...")
ret = SCHEDULER.send_files(join(sep, "etc", "nginx"), "/confs")
if not ret:
logger.error("Sending nginx configs failed, configuration will not work as expected...")
def send_nginx_cache():
logger.info(f"Sending {CACHE_PATH} folder ...")
if not SCHEDULER.send_files(CACHE_PATH, "/cache"):
logger.error(f"Error while sending {CACHE_PATH} folder")
else:
logger.info(f"Successfully sent {CACHE_PATH} folder")
def listen_for_instances_reload():
from docker import DockerClient
global SCHEDULER
docker_client = DockerClient(base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock"))
for event in docker_client.events(decode=True, filters={"type": "container", "label": "bunkerweb.INSTANCE"}):
if event["Action"] in ("start", "die"):
logger.info(f"🐋 Detected {event['Action']} event on container {event['Actor']['Attributes']['name']}")
SCHEDULER.auto_setup()
try:
ret = SCHEDULER.db.update_instances([api_to_instance(api) for api in SCHEDULER.apis], changed=event["Action"] == "die")
if ret:
logger.error(f"Error while updating instances after {event['Action']} event: {ret}")
continue
if event["Action"] == "start":
ret = SCHEDULER.db.checked_changes(value=True)
if ret:
logger.error(f"Error while setting changes to checked in the database after {event['Action']} event: {ret}")
except BaseException as e:
logger.error(f"Error while updating instances after {event['Action']} event: {e}")
if INTEGRATION == "Docker" and not override_instances:
Thread(target=listen_for_instances_reload, name="listen_for_instances_reload").start()
@ -692,7 +724,7 @@ if __name__ == "__main__":
"--templates",
join(sep, "usr", "share", "bunkerweb", "confs"),
"--output",
join(sep, "etc", "nginx"),
CONFIG_PATH.as_posix(),
"--variables",
str(SCHEDULER_TMP_ENV_PATH),
]
@ -716,6 +748,7 @@ if __name__ == "__main__":
logger.warning("No BunkerWeb instance found, skipping nginx configs sending ...")
try:
failed = False
if SCHEDULER.apis:
# send cache
thread = Thread(target=send_nginx_cache)
@ -725,10 +758,7 @@ if __name__ == "__main__":
for thread in threads:
thread.join()
if SCHEDULER.send_to_apis("POST", "/reload"):
logger.info("Successfully reloaded nginx")
else:
logger.error("Error while reloading nginx")
failed = not SCHEDULER.send_to_apis("POST", "/reload")
elif INTEGRATION == "Linux":
# Reload nginx
logger.info("Reloading nginx ...")
@ -740,14 +770,47 @@ if __name__ == "__main__":
check=False,
stdout=PIPE,
)
if proc.returncode == 0:
logger.info("Successfully sent reload signal to nginx")
else:
logger.error(
f"Error while reloading nginx - returncode: {proc.returncode} - error: {proc.stdout.decode('utf-8') if proc.stdout else 'no output'}"
)
failed = proc.returncode != 0
else:
logger.warning("No BunkerWeb instance found, skipping nginx reload ...")
logger.warning("No BunkerWeb instance found, skipping bunkerweb reload ...")
try:
SCHEDULER.db.set_failover(failed)
except BaseException as e:
logger.error(f"Error while setting failover to true in the database: {e}")
if failed:
logger.error("Error while reloading bunkerweb, failing over to last working configuration ...")
if (
not FAILOVER_PATH.joinpath("config").is_dir()
or not FAILOVER_PATH.joinpath("custom_configs").is_dir()
or not FAILOVER_PATH.joinpath("cache").is_dir()
):
logger.error("No failover configuration found, ignoring failover ...")
else:
# Failover to last working configuration
if SCHEDULER.apis:
tmp_threads = [
Thread(target=send_nginx_configs, args=(FAILOVER_PATH.joinpath("config"),)),
Thread(target=send_nginx_cache, args=(FAILOVER_PATH.joinpath("cache"),)),
Thread(target=send_nginx_custom_configs, args=(FAILOVER_PATH.joinpath("custom_configs"),)),
]
for thread in tmp_threads:
thread.start()
for thread in tmp_threads:
thread.join()
SCHEDULER.send_to_apis("POST", "/reload")
else:
logger.info("Successfully reloaded bunkerweb")
# Update the failover path with the working configuration
rmtree(FAILOVER_PATH, ignore_errors=True)
FAILOVER_PATH.mkdir(parents=True, exist_ok=True)
copytree(CONFIG_PATH, FAILOVER_PATH.joinpath("config"))
copytree(CUSTOM_CONFIGS_PATH, FAILOVER_PATH.joinpath("custom_configs"))
copytree(CACHE_PATH, FAILOVER_PATH.joinpath("cache"))
Thread(target=JOB.cache_dir, args=(FAILOVER_PATH,), kwargs={"job_name": "failover-backup"}).start()
except:
logger.error(f"Exception while reloading after running jobs once scheduling : {format_exc()}")
@ -783,6 +846,8 @@ if __name__ == "__main__":
if not HEALTHY_PATH.is_file():
HEALTHY_PATH.write_text(datetime.now().isoformat(), encoding="utf-8")
APPLYING_CHANGES.clear()
# infinite schedule for the jobs
logger.info("Executing job scheduler ...")
errors = 0
@ -879,6 +944,7 @@ if __name__ == "__main__":
sleep(5)
if NEED_RELOAD:
APPLYING_CHANGES.set()
logger.debug(f"Changes: {changes}")
SCHEDULER.try_database_readonly(force=True)
CHANGES.clear()

View file

@ -1,6 +1,7 @@
certbot==2.11.0
configobj==5.0.8
cryptography==42.0.8
maxminddb==2.6.1
maxminddb==2.6.2
python-magic==0.4.27
requests==2.32.3
schedule==1.2.2

View file

@ -229,76 +229,76 @@ josepy==1.14.0 \
# via
# acme
# certbot
maxminddb==2.6.1 \
--hash=sha256:03f59f5c06bb54907e74f8a5d5149032a6e14cb2d990e17e4b0446d18195ede6 \
--hash=sha256:0855c3532063e16c71b9ca7f624d3061f0e6da03a1e4ff7fabf9253a278b3016 \
--hash=sha256:0f5286b5db8065a59cf9e005281c9d74d3839a8cda8e8ee04305d42d5afcc523 \
--hash=sha256:103c7c5740a63d42f1062a99c79712d73106b3b0663c4e6c559f502b673c50c8 \
--hash=sha256:153ca60a282d5ba1db86eedd27b6bb0e158d0f94682598f9900f20690e01395f \
--hash=sha256:16dc122ebaae59922c007bcb9cf2a0621f550392b54f7f5e0171baa111be5a55 \
--hash=sha256:17272badaa3e0293858ea9a48fe3e9fe8d6b20cc465a54cd4766d05aeff6ca59 \
--hash=sha256:21e93c0d094d167bb96ab49c89df2746d78c99228c5273bd7dc6d11385dd63b3 \
--hash=sha256:2319e73cad84bb3897a0cfbe8473a87b0e83b7a69b84118be829cc761a4388ac \
--hash=sha256:2396eb49868c2f078ba566359b66249643409dfca1372b5497cef06bf7965c4a \
--hash=sha256:242e572b3e132146acd0e2633c00564a8e33cf6de54c060778c618070d109054 \
--hash=sha256:24f362eb049109f01dda5adba03d703d1a83e73fa95569ea2bc723a7ecbbea2b \
--hash=sha256:2a943e4a0dd59bd6b98ee131f40bdf4efbab8db7667c3dfa9165b1e06ed3b46d \
--hash=sha256:2ad27b9a06da43f0192e19e772b3fc01b72a6d231d55e665ec675a235533b0c5 \
--hash=sha256:30d66df204847ab114b84b04adf60e91a1dc1a30ab42a3e41337ed10efb4f2ab \
--hash=sha256:32571299316c01eecfc364cd5b94cfa2a484ee45b1cb2cd80464d7f666c4be11 \
--hash=sha256:33859797f89c2949f86a98a0b89dc577a40561643e78084ad44307bbdc40dd76 \
--hash=sha256:39be382e82ecf231869e4c3f628f18b21f032b7bc42f980b75f042c16818b991 \
--hash=sha256:3c8db454446d83b65bd605f6093400897a8698de82ca1c20f37494361ee5b6a7 \
--hash=sha256:3ce8cdf86cbfb569fe7f33dbef283476d7693e002a4b73195996655067f770bd \
--hash=sha256:3eb4711af74a6d8e10e28095c2a18a7ab010826d68665757383c140989f7e075 \
--hash=sha256:3f04a217240323caea98adb0eaf0342466656486fc27b18ff53f74414dbaecce \
--hash=sha256:416f3fddd1add9a421483b26d24abaf2dd355f3a5afd72923681698d345d99d6 \
--hash=sha256:41af38a328cfa94041135753b7ab2dc08863b22535a4295f6e65f72de0a862b9 \
--hash=sha256:44d546d6f8ac103c13daf965ac1970a6a32a8b2f33bdbc8a280f87383ce7c5cd \
--hash=sha256:45d78c8e527ea90947d04450709032459221011a2d14cf5ac645ca1f76e8e7f2 \
--hash=sha256:4774750c744c378653536ad6d5f8e28bcb2566e7e24081e881b00c95b51cad09 \
--hash=sha256:4a2a1b713ceea188d066ca676c033f334baad4f41bc1d89640c9795d514b6617 \
--hash=sha256:4a75d73d8aaa82718d3553880951d1b7fe8c1cd309a84b992ca7789b832b1de7 \
--hash=sha256:4d718bb2379d06e8ca3c4aa09f22634e84fe76db44f66845d7c18c1f0e414fd6 \
--hash=sha256:4e21d5ccf34eb1eee14d95c616b7628035953ed4d79ff560188014ae7f1aaaf7 \
--hash=sha256:5555698be89fa568b787570911a2aa5c666c335c12dcc5cd8166f96e3155e210 \
--hash=sha256:5719b58cfbed4464f89afcbdbaf1eb84f9de805f1716f27c671bf11635ce5458 \
--hash=sha256:58a070a18ca6d17d79002b35351fa9373012a98ad5680c0c49d0794c1286d9d9 \
--hash=sha256:6acc873145aade367d39f5c2c013eeba1fc7709c1cca8aa9a46dd25db12958ef \
--hash=sha256:6c5d591f625e03b0a34df0c7ff81580676397b8335e13ece130c6e39e4a3afb9 \
--hash=sha256:6d3790e9ee0157a320b0aa7ddf9f33290f33797608beae604b202a24aaf9db17 \
--hash=sha256:81a1070b61e2fabff936d256490924e49c8b54d3f9fa61f32c0c91b83dc11259 \
--hash=sha256:86457125adccf5c248d481fc1cd80e77674afeaf45995aed480a3c7e0e118ddc \
--hash=sha256:87989f153ce9a0974c69bb0bf26a3cb339c7dfbbfe3330883075543d8ef70fc5 \
--hash=sha256:8eb72818e43d2e52e896e72622f41219afe98913eda456ef626fb10a636acca3 \
--hash=sha256:940c349e0937e1123f1ae7f213e4a7e90e972cd4501c5898ec70814e4c472747 \
--hash=sha256:9ac567627ac141d3e1a797b5696b4e652b1660ddfa6c861f202eed1eb34143ab \
--hash=sha256:9b8ef7ed2bbbd8216a0560dd06caafe2fc1d6f9fa18cf46282c6f4a9a3d91b9c \
--hash=sha256:9c5aa6d50a30cc733b57afa80cbb51c004a7beac23a6c6a56e3550992faaeac1 \
--hash=sha256:9eb6a13e781e2e7a02e88734e29139fb0e5e4024020b146da56202893e425595 \
--hash=sha256:a029d2c23b8ad9f4e8316319d79f0d55899aa8e6d69a2bee77d998991256dee0 \
--hash=sha256:a467ebf7cec651001a318aab5c9582f6774886e8d2d86aac77db33e5006ea118 \
--hash=sha256:ae3c76fbc989eca9b31512ae899528a9dae9092f4c9b7e807ed55b9ff4254ed0 \
--hash=sha256:b1090088504c4b45cf1f3ffc32eabd6d5065e56883d910658e5d5f31e80e4be4 \
--hash=sha256:b3863e6017b96ee3ae1a6bb7ef0c25cd9013b04cccc1fd27880ab6371cdd1d84 \
--hash=sha256:b8e0fa2ec7f58411262ab3edd837d3a1844e6068e128eca222867ad465b97e9d \
--hash=sha256:bcc9ac85007ab222838974b084f49bb62531669e793a7730260dec2cf6e34bfd \
--hash=sha256:c8f9fcd1bb0e016e7a2ff2341920f99932cd0f573e18bc89e9ad168c9cb93392 \
--hash=sha256:c9dd4275749d1d3fb3700df373cd593235ee307f17a3180bad151562e8294a61 \
--hash=sha256:cd4afedf6fab1678e5fac0f0cdeb9be2f77fcc07ae1ebc5abe788aec32dd3de8 \
--hash=sha256:d0a8b70fcfa0980c0e8501e1506115dbd6f2410436f54161647627430d7cbb66 \
--hash=sha256:d44081ec6633a225e051eaf851aa5986aae5f5c8c1f33cf78b3a825c5d0df642 \
--hash=sha256:d45a6a5d964182ff083f2ee545d049517e88f0898ab4df3e119582518cd97b64 \
--hash=sha256:db8e0a5c1262d43ba5d0f6efb357ba9e5b65b7f3fc982b77a9f543f222a7fca3 \
--hash=sha256:e973d98f3bf828a94016d3875cb44e17739ad3957282505c16c68d20cf3a70a1 \
--hash=sha256:f117fe0b5bafee78dbd97606dc60bba2160cfe1968484925174d7aadb7a38f37 \
--hash=sha256:f11a0899eb671c77dc131c8dd5d6702eb2d7c19952790c87b36ef72d73696bc2 \
--hash=sha256:f1481c05b2a7fa909bb48ada037d2c920d7845ea737d9a1e6513ab1c85a64a32 \
--hash=sha256:f40c1a145550a297b8c8743d62b8b1bf9fa572b36fa1df9157ea45fed0da9abc \
--hash=sha256:f6edc11c4fb4c1ecbfb28cc5da167f7db415c4fabc1aeff0171b06473057e5fb \
--hash=sha256:fa43c3783da55ca2a2ed68b97048b63c86ee1462caf32e5f9bfe038db9dac31f \
--hash=sha256:fb56115caee4f3beafd2907845dc8f80c633424cbe270a3738f6ba609ff7248e \
--hash=sha256:fc3526c587f53dd32a5191e81f4239bb3ead70f56a97936b3427b72e3a5cc55f
maxminddb==2.6.2 \
--hash=sha256:058ca89789bc1770fe58d02a88272ca91dabeef9f3fe0011fe506484355f1804 \
--hash=sha256:05e873eb82281cef6e787bd40bd1d58b2e496a21b3689346f0d0420988b3cbb1 \
--hash=sha256:0b281c0eec3601dde1f169a1c04e2615751c66368141aded9f03131fe635450b \
--hash=sha256:0b480a31589750da4e36d1ba04b77ee3ac3853ac7b94d63f337b9d4d0403043f \
--hash=sha256:12207f0becf3f2bf14e7a4bf86efcaa6e90d665a918915ae228c4e77792d7151 \
--hash=sha256:1c096dfd20926c4de7d7fd5b5e75c756eddd4bdac5ab7aafd4bb67d000b13743 \
--hash=sha256:1dc2b511c7255f7cbbb01e8ba01ba82e62e9c1213e382d36f9d9b0ee45c2f6b2 \
--hash=sha256:20662878bc9514e90b0b4c4eb1a76622ecc7504d012e76bad9cdb7372fc0ef96 \
--hash=sha256:28a2eaf9769262c05c486e777016771f3367c843b053c43cd5fde1108755753d \
--hash=sha256:28af9470f28fce2ccb945478235f53fb52d98a505653b1bf4028e34df6149a06 \
--hash=sha256:2aaefb62f881151960bb67e5aeb302c159a32bd2d623cf72dad688bda1020869 \
--hash=sha256:34b6e8d667d724f60d52635f3d959f793ab4e5d57d78b27fe66f02752d8c6b08 \
--hash=sha256:38941a38278491bf95e5ca544969782c7ab33326802f6a93816867289c3f6401 \
--hash=sha256:3987e103396e925edebbef4877e94515822f63b3b436027a0b164b500622fccd \
--hash=sha256:39eab93ddd75fd02f8d5ad6b1bd3f8d894828d91d6f6c1a96bb9e87c34e94aaa \
--hash=sha256:47170ec0e1e76787cc5882301c487f495d67f3146318f2f4e2adc281951a96ef \
--hash=sha256:485c0778f6801e1437c2efd6e3b964a7ae71c8819f063e0b5460c3267d977040 \
--hash=sha256:5662386db91872d5505fde9e7bb0b9530b6aab7a6f3ece7df59a2b43a7b45d17 \
--hash=sha256:58bfd2c55c96aaaa7c4996c704edabfb1bd369dfc1592cedf8957a24062178b1 \
--hash=sha256:5c7c520d06d335b288d06a00b786cea9b7e023bd588efb1a6ef485e94ccc7244 \
--hash=sha256:6a50bc348c699d8f6a5f0aa35e5096515d642ca2f38b944bd71c3dedda3d3588 \
--hash=sha256:6fd1a612110ff182a559d8010e7615e5d05ef9d2c234b5f7de124ee8fdf1ecb9 \
--hash=sha256:7607e45f7eca991fa34d57c03a791a1dfbe774ddd9250d0f35cdcc6f17142a15 \
--hash=sha256:78c3aa70c62be68ace23f819e7f23258545f2bfbd92cd6c33ee398cd261f6b84 \
--hash=sha256:7c1220838ba9b0bcdaa0c5846f9da70a2304df2ac255fe518370f8faf8c18316 \
--hash=sha256:7cd7f525eb2331cf05181c5ba562cc3edec3de4b41dbb18a5fee9ad24884b499 \
--hash=sha256:7cfdf5c29a2739610700b9fea7f8d68ce81dcf30bb8016f1a1853ef889a2624b \
--hash=sha256:7d842d32e2620abc894b7d79a5a1007a69df2c6cf279a06b94c9c3913f66f264 \
--hash=sha256:7e5a90a1cb0c7fd6226aa44e18a87b26fa85b6eebae36d529d7582f93e8dfbd1 \
--hash=sha256:80d20683afe01b4d41bad1c1829f87ab12f3d19c68ec230f83318a2fd13871a7 \
--hash=sha256:80d7495565d30260c630afbe74d61522b13dd31ed05b8916003ec5b127109a12 \
--hash=sha256:80d7f943f6b8bc437eaae5da778a83d8f38e4b7463756fdee04833e1be0bdea2 \
--hash=sha256:8101291e5b92bd272a050c25822a5e30860d453dde16b4fffed9d751f0483a82 \
--hash=sha256:826a1858b93b193df7fa71e3caca65c3051db20545df0020444f55c02e8ed2c3 \
--hash=sha256:85fc9406f42c1311ce8ea9f2c820db5d7ac687a39ab5d932708dc783607378ef \
--hash=sha256:86048ff328793599e584bcc2fc8278c2b7c5d3a4005c70403613449ec93817ef \
--hash=sha256:886af3ba4aa26214ff39214565f53152b62a5abdb6ef9e00c76c194dbfd79231 \
--hash=sha256:8cb992da535264177b380e7b81943c884d57dcbfad6b3335d7f633967144746e \
--hash=sha256:93691c8b4b4c448babb37bedc6f3d51523a3f06ab11bdd171da7ffc4005a7897 \
--hash=sha256:96a1fa38322bce1d587bb6ce39a0e6ca4c1b824f48fbc5739a5ec507f63aa889 \
--hash=sha256:9a2671e8f4161130803cf226cd9cb8b93ec5c4b2493f83a902986177052d95d3 \
--hash=sha256:9dccd7a438f81e3df84dfc31a75af4c8d29adefb6082329385bfde604c9ea01b \
--hash=sha256:a74b60cdc61a69b967ec44201c6259fbc48ef2eab2e885fbdc50ec1accaad545 \
--hash=sha256:a771df92e599ad867c16ae4acb08cc3763c9d1028f4ca772c0571da97f7f86d2 \
--hash=sha256:aa8cb54b01a29a23a0ea6659fbb38deec6f35453588c5decdbf8669feb53b624 \
--hash=sha256:add1e55620033516c5f0734b1d9d03848859192d9f3825aabe720dfa8a783958 \
--hash=sha256:b0a3b9cab1a94cc633df3da85c6567f0188f10165e3338ec9a6c421de9fe53b9 \
--hash=sha256:b31ecf3083b78c77624783bfdf6177e6ac73ae14684ef182855eb5569bc78e7c \
--hash=sha256:b4d9cd7ddd02ee123a44d0d7821166d31540ea85352deb06b29d55e802f32781 \
--hash=sha256:c9e9e893f7c0fa44cfdd5ab819a07d93f63ee398c28b792cedd50b94dcfea7c0 \
--hash=sha256:cd4530b9604d66cfa5e37eb94c671e54feff87769f8ba7fa997cce959e0cb241 \
--hash=sha256:d0970b661c4fac6624b9128057ed5fe35a2d95aa60359272289cd4c7207c9a6d \
--hash=sha256:d15414d251513748cb646d284a2829a5f4c69d8c90963a6e6da53a1a6d0accf7 \
--hash=sha256:d32266792b349f5507b0369d3277d45318fcd346a16dcc98b484aadc208e4d74 \
--hash=sha256:d8ccca5327cb4e706f669456ec6d556badfa92c0fdacd57a15076f3cdc061560 \
--hash=sha256:dc9f1203eb2b139252aa08965960fe13c36cc8b80b536490b94b05c31aa1fca9 \
--hash=sha256:dd90c3798e6c347d48d5d9a9c95dc678b52a5a965f1fb72152067fdf52b994da \
--hash=sha256:e1e40449bd278fdca1f351df442f391e72fd3d98b054ccac1672f27d70210642 \
--hash=sha256:e2b85ffc9fb2e192321c2f0b34d0b291b8e82de6e51a6ec7534645663678e835 \
--hash=sha256:e63649a82926f1d93acdd3df5f7be66dc9473653350afe73f365bb25e5b34368 \
--hash=sha256:e9013076deca5d136c260510cd05e82ec2b4ddb9476d63e2180a13ddfd305c3e \
--hash=sha256:eacd65e38bdf4efdf42bbc15cfa734b09eb818ecfef76b7b36e64be382be4c83 \
--hash=sha256:eb534333f5fd7180e35c0207b3d95d621e4b9be3b8c1709995d0feb6c752b6f4 \
--hash=sha256:ebf9fdf8a8e55862aabb8b2c34a4af31a8a5b686007288eeb561fa20ef348378 \
--hash=sha256:ecce0b2d125691e2311f94dbd564c2d61c36c5033d082919431a21e6c694fa3f \
--hash=sha256:eef1c26210155c7b94c4ca28fef65eb44a5ca1584427b1fbdeec1cd3c81e25c5 \
--hash=sha256:f2e326a99eaa924ff2fb09d6e44127983a43016228e7780888f15e9ba171d7b3 \
--hash=sha256:f412a54f87ef9083911c334267188d3d1b14f2591eac94b94ca32528f21d5f25 \
--hash=sha256:fb38aa94e76a87785b654c035f9f3ee39b74a98e9beea9a10b1aa62abdcc4cbd
# via -r requirements.in
parsedatetime==2.6 \
--hash=sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455 \
@ -334,7 +334,9 @@ pytz==2024.1 \
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
# via acme
# via
# -r requirements.in
# acme
schedule==1.2.2 \
--hash=sha256:5bef4a2a0183abf44046ae0d164cadcac21b1db011bdd8102e4a0c1e91e06a7d
# via importlib-metadata

View file

@ -1,4 +1,4 @@
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb as builder
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c as builder
# Export var for specific actions on linux/arm/v7
ARG TARGETPLATFORM
@ -34,7 +34,7 @@ COPY src/common/templates templates
COPY src/ui ui
COPY src/VERSION VERSION
FROM python:3.12.3-alpine3.19@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb
FROM python:3.12.4-alpine3.19@sha256:ef3397d09070efd36583e83d2619cf8006158641e5b6b629d4d92a9778f5aa1c
# Set default umask to prevent huge recursive chmod increasing the final image size
RUN umask 027

View file

@ -94,72 +94,80 @@ sbin_nginx_path = Path(sep, "usr", "sbin", "nginx")
# Flask app
app = Flask(__name__, static_url_path="/", static_folder="static", template_folder="templates")
PROXY_NUMBERS = int(getenv("PROXY_NUMBERS", "1"))
app.wsgi_app = ReverseProxied(app.wsgi_app, x_for=PROXY_NUMBERS, x_proto=PROXY_NUMBERS, x_host=PROXY_NUMBERS, x_prefix=PROXY_NUMBERS)
app.logger = setup_logger("UI")
with app.app_context():
PROXY_NUMBERS = int(getenv("PROXY_NUMBERS", "1"))
app.wsgi_app = ReverseProxied(app.wsgi_app, x_for=PROXY_NUMBERS, x_proto=PROXY_NUMBERS, x_host=PROXY_NUMBERS, x_prefix=PROXY_NUMBERS)
app.logger = setup_logger("UI")
FLASK_SECRET = getenv("FLASK_SECRET")
FLASK_SECRET = getenv("FLASK_SECRET")
if not FLASK_SECRET:
if not TMP_DIR.joinpath(".flask_secret").is_file():
app.logger.error("The .flask_secret file is missing")
stop(1)
FLASK_SECRET = TMP_DIR.joinpath(".flask_secret").read_text(encoding="utf-8").strip()
if not FLASK_SECRET:
if not TMP_DIR.joinpath(".flask_secret").is_file():
app.logger.error("The .flask_secret file is missing")
stop(1)
FLASK_SECRET = TMP_DIR.joinpath(".flask_secret").read_text(encoding="utf-8").strip()
app.config["SECRET_KEY"] = FLASK_SECRET
app.config["SECRET_KEY"] = FLASK_SECRET
app.config["SESSION_COOKIE_NAME"] = "__Host-bw_ui_session"
app.config["SESSION_COOKIE_PATH"] = "/"
app.config["SESSION_COOKIE_SECURE"] = True # Required for __Host- prefix
app.config["SESSION_COOKIE_HTTPONLY"] = True # Recommended for security
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # Or 'Strict' for stricter settings
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30)
app.config["PREFERRED_URL_SCHEME"] = "https"
login_manager = LoginManager()
login_manager.session_protection = "strong"
login_manager.init_app(app)
login_manager.login_view = "login"
login_manager.anonymous_user = AnonymousUser
PLUGIN_KEYS = ["id", "name", "description", "version", "stream", "settings"]
login_manager = LoginManager()
login_manager.session_protection = "strong"
login_manager.init_app(app)
login_manager.login_view = "login"
login_manager.anonymous_user = AnonymousUser
PLUGIN_KEYS = ["id", "name", "description", "version", "stream", "settings"]
INTEGRATION = get_integration()
INTEGRATION = get_integration()
docker_client = None
kubernetes_client = None
if INTEGRATION in ("Docker", "Swarm", "Autoconf"):
try:
docker_client: DockerClient = DockerClient(base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock"))
except (docker_APIError, DockerException):
app.logger.warning("No docker host found")
elif INTEGRATION == "Kubernetes":
kube_config.load_incluster_config()
kubernetes_client = kube_client.CoreV1Api()
db = Database(app.logger, ui=True, log=False)
USER_PASSWORD_RX = re_compile(r"^(?=.*?\p{Lowercase_Letter})(?=.*?\p{Uppercase_Letter})(?=.*?\d)(?=.*?[ !\"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$")
bw_version = get_version()
if not TMP_DIR.joinpath(".ui.json").is_file():
TMP_DIR.joinpath(".ui.json").write_text("{}", encoding="utf-8")
docker_client = None
kubernetes_client = None
if INTEGRATION in ("Docker", "Swarm", "Autoconf"):
try:
docker_client: DockerClient = DockerClient(base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock"))
except (docker_APIError, DockerException):
app.logger.warning("No docker host found")
elif INTEGRATION == "Kubernetes":
kube_config.load_incluster_config()
kubernetes_client = kube_client.CoreV1Api()
app.config.update(
INSTANCES=Instances(docker_client, kubernetes_client, INTEGRATION, db),
CONFIG=Config(db),
CONFIGFILES=ConfigFiles(),
WTF_CSRF_SSL_STRICT=False,
SEND_FILE_MAX_AGE_DEFAULT=86400,
SCRIPT_NONCE=sha256(urandom(32)).hexdigest(),
DB=db,
UI_TEMPLATES=get_ui_templates(),
)
except FileNotFoundError as e:
app.logger.error(repr(e), e.filename)
stop(1)
db = Database(app.logger, ui=True, log=False)
plugin_id_rx = re_compile(r"^[\w_-]{1,64}$")
USER_PASSWORD_RX = re_compile(r"^(?=.*?\p{Lowercase_Letter})(?=.*?\p{Uppercase_Letter})(?=.*?\d)(?=.*?[ !\"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-]).{8,}$")
# Declare functions for jinja2
app.jinja_env.globals.update(check_settings=check_settings)
bw_version = get_version()
if not TMP_DIR.joinpath(".ui.json").is_file():
TMP_DIR.joinpath(".ui.json").write_text("{}", encoding="utf-8")
try:
app.config.update(
INSTANCES=Instances(docker_client, kubernetes_client, INTEGRATION, db),
CONFIG=Config(db),
CONFIGFILES=ConfigFiles(),
WTF_CSRF_SSL_STRICT=False,
SEND_FILE_MAX_AGE_DEFAULT=86400,
SCRIPT_NONCE=sha256(urandom(32)).hexdigest(),
DB=db,
UI_TEMPLATES=get_ui_templates(),
)
except FileNotFoundError as e:
app.logger.error(repr(e), e.filename)
stop(1)
plugin_id_rx = re_compile(r"^[\w_-]{1,64}$")
# Declare functions for jinja2
app.jinja_env.globals.update(check_settings=check_settings)
# CSRF protection
csrf = CSRFProtect()
csrf.init_app(app)
# CSRF protection
csrf = CSRFProtect()
csrf.init_app(app)
LOG_RX = re_compile(r"^(?P<date>\d+/\d+/\d+\s\d+:\d+:\d+)\s\[(?P<level>[a-z]+)\]\s\d+#\d+:\s(?P<message>[^\n]+)$")
REVERSE_PROXY_PATH = re_compile(r"^(?P<host>https?://.{1,255}(:((6553[0-5])|(655[0-2]\d)|(65[0-4]\d{2})|(6[0-4]\d{3})|([1-5]\d{4})|([0-5]{0,5})|(\d{1,4})))?)$")
@ -224,7 +232,7 @@ def manage_bunkerweb(method: str, *args, operation: str = "reloads", is_draft: b
elif operation == "restart":
operation = app.config["INSTANCES"].restart_instance(args[0])
elif not error:
operation = "The scheduler will be in charge of reloading the instances."
operation = "The scheduler will be in charge of applying the changes."
if operation:
if isinstance(operation, list):
@ -372,13 +380,24 @@ def inject_variables():
ui_data = get_ui_data()
metadata = app.config["DB"].get_metadata()
curr_changes = app.config["DB"].check_changes()
changes_ongoing = any(app.config["DB"].check_changes().values())
if ui_data.get("PRO_LOADING") and not any(curr_changes.values()):
if ui_data.get("PRO_LOADING") and not changes_ongoing:
ui_data["PRO_LOADING"] = False
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
if not changes_ongoing and metadata["failover"]:
flash(
"The last changes could not be applied because it creates a configuration error on NGINX, please check the logs for more information. The configured fell back to the last working one.",
"error",
)
elif not changes_ongoing and not metadata["failover"] and ui_data.get("CONFIG_CHANGED", False):
flash("The last changes have been applied successfully.", "success")
ui_data["CONFIG_CHANGED"] = False
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
# check that is value is in tuple
return dict(
script_nonce=app.config["SCRIPT_NONCE"],
@ -430,8 +449,7 @@ def handle_csrf_error(_):
:param e: The exception object
:return: A template with the error message and a 401 status code.
"""
session.clear()
logout_user()
logout()
flash("Wrong CSRF token !", "error")
if not current_user:
return render_template("setup.html"), 403
@ -529,7 +547,7 @@ def check():
@app.route("/setup", methods=["GET", "POST"])
def setup():
db_config = app.config["CONFIG"].get_config(methods=False)
db_config = app.config["CONFIG"].get_config(methods=False, filtered_settings=("SERVER_NAME", "USE_UI", "UI_HOST"))
for server_name in db_config["SERVER_NAME"].split(" "):
if db_config.get(f"{server_name}_USE_UI", "no") == "yes":
@ -671,8 +689,8 @@ def home():
if r and r.status_code == 200:
remote_version = basename(r.url).strip().replace("v", "")
config = app.config["CONFIG"].get_config(with_drafts=True)
override_instances = config["OVERRIDE_INSTANCES"]["value"] != ""
config = app.config["CONFIG"].get_config(with_drafts=True, filtered_settings=("SERVER_NAME", "OVERRIDE_INSTANCES"))
override_instances = config.get("OVERRIDE_INSTANCES", {"value": ""})["value"] != ""
instances = app.config["INSTANCES"].get_instances(override_instances=override_instances)
instance_health_count = 0
@ -735,9 +753,9 @@ def account():
variable = {}
variable["PRO_LICENSE_KEY"] = request.form["license"]
error = app.config["CONFIG"].check_variables(variable)
variable = app.config["CONFIG"].check_variables(variable, {"PRO_LICENSE_KEY": request.form["license"]})
if error:
if not variable:
return redirect_flash_error("The license key variable checks returned error", "account", True)
# Force job to contact PRO API
@ -761,6 +779,7 @@ def account():
ui_data = get_ui_data()
ui_data["PRO_LOADING"] = True
ui_data["CONFIG_CHANGED"] = True
if any(curr_changes.values()):
ui_data["RELOADING"] = True
@ -792,8 +811,7 @@ def account():
username = request.form["admin_username"]
session.clear()
logout_user()
logout()
if request.form["operation"] == "password":
@ -810,8 +828,7 @@ def account():
password = request.form["admin_password"]
session.clear()
logout_user()
logout()
if request.form["operation"] == "totp":
@ -908,8 +925,8 @@ def instances():
)
# Display instances
config = app.config["CONFIG"].get_config(global_only=True)
override_instances = config["OVERRIDE_INSTANCES"]["value"] != ""
config = app.config["CONFIG"].get_config(global_only=True, methods=False, filtered_settings=("OVERRIDE_INSTANCES",))
override_instances = config.get("OVERRIDE_INSTANCES", "") != ""
instances = app.config["INSTANCES"].get_instances(override_instances=override_instances)
return render_template("instances.html", title="Instances", instances=instances, username=current_user.get_id())
@ -946,7 +963,7 @@ def services():
if "SERVER_NAME" not in variables:
variables["SERVER_NAME"] = variables["OLD_SERVER_NAME"]
config = app.config["CONFIG"].get_config(methods=True, with_drafts=True)
config = app.config["DB"].get_config(methods=True, with_drafts=True)
server_name = variables["SERVER_NAME"].split(" ")[0]
was_draft = config.get(f"{server_name}_IS_DRAFT", {"value": "no"})["value"] == "yes"
operation = request.form["operation"]
@ -980,16 +997,7 @@ def services():
del variables["OLD_SERVER_NAME"]
# Edit check fields and remove already existing ones
for variable, value in deepcopy(variables).items():
if variable == "IS_DRAFT" or variable.endswith("SCHEMA"):
del variables[variable]
continue
if value == "on":
value = "yes"
elif value == "off":
value = "no"
for variable, value in variables.copy().items():
if (
variable in variables
and variable != "SERVER_NAME"
@ -997,6 +1005,8 @@ def services():
):
del variables[variable]
variables = app.config["CONFIG"].check_variables(variables, config)
if (
was_draft == is_draft
and request.form["operation"] == "edit"
@ -1009,26 +1019,19 @@ def services():
elif request.form["operation"] == "new" and not variables:
return redirect_flash_error("The service was not created because all values had the default value.", "services", True)
error = app.config["CONFIG"].check_variables(variables)
if error:
error_message("The config variable checks returned error")
# Delete
if request.form["operation"] == "delete":
is_request_params(["SERVER_NAME"], "services", True)
error = app.config["CONFIG"].check_variables({"SERVER_NAME": request.form["SERVER_NAME"]})
variables = app.config["CONFIG"].check_variables({"SERVER_NAME": request.form["SERVER_NAME"]}, config)
if error:
if not variables:
error_message(f"Error while deleting the service {request.form['SERVER_NAME']}")
if config.get(f"{request.form['SERVER_NAME'].split(' ')[0]}_SERVER_NAME", {"method": "scheduler"})["method"] != "ui":
return redirect_flash_error("The service cannot be deleted because it has not been created with the UI.", "services", True)
error = 0
curr_changes = app.config["DB"].check_changes()
old_server_name = request.form.get("OLD_SERVER_NAME", "")
@ -1048,17 +1051,20 @@ def services():
threaded=threaded,
)
ui_data = get_ui_data()
if any(curr_changes.values()):
ui_data = get_ui_data()
ui_data["RELOADING"] = True
ui_data["LAST_RELOAD"] = time()
Thread(target=update_services, args=(True,)).start()
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
else:
update_services()
ui_data["CONFIG_CHANGED"] = True
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
message = ""
if request.form["operation"] == "new":
@ -1072,7 +1078,7 @@ def services():
# Display services
services = []
global_config = app.config["CONFIG"].get_config(with_drafts=True)
global_config = app.config["DB"].get_config(methods=True, with_drafts=True)
service_names = global_config["SERVER_NAME"]["value"].split(" ")
for service in service_names:
service_settings = []
@ -1132,30 +1138,19 @@ def global_config():
del variables["csrf_token"]
# Edit check fields and remove already existing ones
config = app.config["CONFIG"].get_config(methods=True, with_drafts=True)
config = app.config["DB"].get_config(methods=True, with_drafts=True)
services = config["SERVER_NAME"]["value"].split(" ")
for variable, value in variables.copy().items():
if variable in ("AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE", "SERVER_NAME", "IS_LOADING", "IS_DRAFT") or variable.endswith("SCHEMA"):
del variables[variable]
continue
if value == "on":
value = "yes"
elif value == "off":
value = "no"
setting = config.get(variable, {"value": None, "global": True})
if setting["global"] and value == setting["value"]:
del variables[variable]
continue
variables = app.config["CONFIG"].check_variables(variables, config)
if not variables:
return redirect_flash_error("The global configuration was not edited because no values were changed.", "global_config", True)
error = app.config["CONFIG"].check_variables(variables)
if error:
return redirect_flash_error("The global configuration variable checks returned error", "global_config", True)
for variable, value in variables.copy().items():
for service in services:
setting = config.get(f"{service}_{variable}", None)
@ -1181,6 +1176,8 @@ def global_config():
else:
update_global_config()
ui_data["CONFIG_CHANGED"] = True
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
@ -1197,7 +1194,7 @@ def global_config():
)
# Display global config
global_config = app.config["CONFIG"].get_config(global_only=True)
global_config = app.config["DB"].get_config(global_only=True, methods=True)
return render_template("global_config.html", username=current_user.get_id(), global_config=global_config, dumped_global_config=dumps(global_config))
@ -1304,6 +1301,11 @@ def configs():
app.logger.error(f"Could not save custom configs: {error}")
return redirect_flash_error("Couldn't save custom configs", "configs", True)
ui_data = get_ui_data()
ui_data["CONFIG_CHANGED"] = True
with LOCK:
TMP_DATA_FILE.write_text(dumps(ui_data), encoding="utf-8")
flash(operation)
return redirect(url_for("loading", next=url_for("configs")))
@ -1314,7 +1316,7 @@ def configs():
path_to_dict(
join(sep, "etc", "bunkerweb", "configs"),
db_data=db_configs,
services=app.config["CONFIG"].get_config(methods=False).get("SERVER_NAME", "").split(" "),
services=app.config["CONFIG"].get_config(global_only=True, methods=False, filtered_settings=("SERVER_NAME",)).get("SERVER_NAME", "").split(" "),
)
],
username=current_user.get_id(),
@ -1729,7 +1731,7 @@ def custom_plugin(plugin: str):
if plugin_id is None:
return error_message("Plugin not found"), 404
config = app.config["CONFIG"].get_config(methods=False)
config = app.config["DB"].get_config()
# Check if we are using metrics
for service in config.get("SERVER_NAME", "").split(" "):
@ -1839,7 +1841,7 @@ def cache():
join(sep, "var", "cache", "bunkerweb"),
is_cache=True,
db_data=app.config["DB"].get_jobs_cache_files(),
services=app.config["CONFIG"].get_config(methods=False).get("SERVER_NAME", "").split(" "),
services=app.config["CONFIG"].get_config(global_only=True, methods=False, filtered_settings=("SERVER_NAME",)).get("SERVER_NAME", "").split(" "),
)
],
username=current_user.get_id(),
@ -1849,8 +1851,8 @@ def cache():
@app.route("/logs", methods=["GET"])
@login_required
def logs():
config = app.config["CONFIG"].get_config(with_drafts=True)
override_instances = config["OVERRIDE_INSTANCES"]["value"] != ""
config = app.config["CONFIG"].get_config(global_only=True, methods=False, filtered_settings=("OVERRIDE_INSTANCES",))
override_instances = config.get("OVERRIDE_INSTANCES", "") != ""
instances = app.config["INSTANCES"].get_instances(override_instances=override_instances)
return render_template("logs.html", instances=instances, username=current_user.get_id())
@ -1945,7 +1947,11 @@ def logs_linux():
error_type = (
"error"
if "[error]" in log_lower or "[crit]" in log_lower or "[alert]" in log_lower or "" in log_lower
else (("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "" in log_lower else "message")))
else (
"emerg"
if "[emerg]" in log_lower
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "" in log_lower else "message"))
)
)
logs.append(
@ -2071,7 +2077,11 @@ def logs_container(container_id):
"type": (
"error"
if "[error]" in log_lower or "[crit]" in log_lower or "[alert]" in log_lower or "" in log_lower
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "" in log_lower else "message"))
else (
"emerg"
if "[emerg]" in log_lower
else ("warn" if "[warn]" in log_lower or "⚠️" in log_lower else ("info" if "[info]" in log_lower or "" in log_lower else "message"))
)
),
}
)
@ -2120,7 +2130,25 @@ def bans():
return redirect_flash_error("Database is in read-only mode", "bans")
redis_client = None
db_config = app.config["CONFIG"].get_config(methods=False)
db_config = app.config["CONFIG"].get_config(
global_only=True,
methods=False,
filtered_settings=(
"USE_REDIS",
"REDIS_HOST",
"REDIS_PORT",
"REDIS_DB",
"REDIS_TIMEOUT",
"REDIS_KEEPALIVE_POOL",
"REDIS_SSL",
"REDIS_USERNAME",
"REDIS_PASSWORD",
"REDIS_SENTINEL_HOSTS",
"REDIS_SENTINEL_USERNAME",
"REDIS_SENTINEL_PASSWORD",
"REDIS_SENTINEL_MASTER",
),
)
use_redis = db_config.get("USE_REDIS", "no") == "yes"
redis_host = db_config.get("REDIS_HOST")
if use_redis and redis_host:

View file

@ -145,9 +145,9 @@ markupsafe==2.1.5 \
# jinja2
# werkzeug
# wtforms
packaging==24.0 \
--hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \
--hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9
packaging==24.1 \
--hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \
--hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124
# via gunicorn
pyotp==2.9.0 \
--hash=sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63 \
@ -258,9 +258,9 @@ soupsieve==2.5 \
--hash=sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690 \
--hash=sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7
# via beautifulsoup4
typing-extensions==4.12.1 \
--hash=sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a \
--hash=sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via qrcode
werkzeug==3.0.3 \
--hash=sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18 \

View file

@ -78,7 +78,13 @@ class Config:
def get_settings(self) -> dict:
return self.__settings
def get_config(self, global_only: bool = False, methods: bool = True, with_drafts: bool = False) -> dict:
def get_config(
self,
global_only: bool = False,
methods: bool = True,
with_drafts: bool = False,
filtered_settings: Optional[Union[List[str], Set[str], Tuple[str]]] = None,
) -> dict:
"""Get the nginx variables env file and returns it as a dict
Returns
@ -86,7 +92,7 @@ class Config:
dict
The nginx variables env file as a dict
"""
return self.__db.get_config(global_only=global_only, methods=methods, with_drafts=with_drafts)
return self.__db.get_non_default_settings(global_only=global_only, methods=methods, with_drafts=with_drafts, filtered_settings=filtered_settings)
def get_services(self, methods: bool = True, with_drafts: bool = False) -> list[dict]:
"""Get nginx's services
@ -98,7 +104,7 @@ class Config:
"""
return self.__db.get_services_settings(methods=methods, with_drafts=with_drafts)
def check_variables(self, variables: dict) -> int:
def check_variables(self, variables: dict, config: dict) -> dict:
"""Testify that the variables passed are valid
Parameters
@ -111,32 +117,44 @@ class Config:
int
Return the error code
"""
error = 0
plugins_settings = self.get_plugins_settings()
for k, v in variables.items():
for k, v in variables.copy().items():
check = False
if k.endswith("SCHEMA"):
variables.pop(k)
continue
if k in plugins_settings:
setting = k
else:
setting = k[0 : k.rfind("_")] # noqa: E203
if setting not in plugins_settings or "multiple" not in plugins_settings[setting]:
error = 1
flash(f"Variable {k} is not valid.", "error")
variables.pop(k)
continue
if setting in ("AUTOCONF_MODE", "SWARM_MODE", "KUBERNETES_MODE", "IS_LOADING", "IS_DRAFT"):
flash(f"Variable {k} is not editable, ignoring it", "error")
variables.pop(k)
continue
elif setting not in config and plugins_settings[setting]["default"] == v:
variables.pop(k)
continue
try:
if re_search(plugins_settings[setting]["regex"], v):
check = True
except RegexError:
self.__db.logger.warning(f"Invalid regex for setting {setting} : {plugins_settings[setting]['regex']}, ignoring regex check")
if not check:
error = 1
flash(f"Variable {k} is not valid.", "error")
except RegexError as e:
flash(f"Invalid regex for setting {setting} : {plugins_settings[setting]['regex']}, ignoring regex check:{e}", "error")
variables.pop(k)
continue
return error
if not check:
flash(f"Variable {k} is not valid.", "error")
variables.pop(k)
return variables
def new_service(self, variables: dict, is_draft: bool = False) -> Tuple[str, int]:
"""Creates a new service from the given variables

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